mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2025-10-27 08:49:27 +00:00
Compare commits
3 Commits
53977c5306
...
fc951ead41
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc951ead41 | ||
|
|
0db674eeb2 | ||
|
|
6ca9f28a56 |
@@ -39,6 +39,7 @@ class WebServer:
|
||||
# Routes for templated pages
|
||||
bottle.get("/")(lambda: self.serve_template('webpage_spots'))
|
||||
bottle.get("/map")(lambda: self.serve_template('webpage_map'))
|
||||
bottle.get("/bands")(lambda: self.serve_template('webpage_bands'))
|
||||
bottle.get("/alerts")(lambda: self.serve_template('webpage_alerts'))
|
||||
bottle.get("/status")(lambda: self.serve_template('webpage_status'))
|
||||
bottle.get("/about")(lambda: self.serve_template('webpage_about'))
|
||||
@@ -206,6 +207,11 @@ class WebServer:
|
||||
needs_sig = query.get(k).upper() == "TRUE"
|
||||
if needs_sig:
|
||||
spots = [s for s in spots if s.sig]
|
||||
case "needs_sig_ref":
|
||||
# If true, at least one sig ref is required, regardless of what it is, it just can't be missing.
|
||||
needs_sig_ref = query.get(k).upper() == "TRUE"
|
||||
if needs_sig_ref:
|
||||
spots = [s for s in spots if s.sig_refs and len(s.sig_refs) > 0]
|
||||
case "band":
|
||||
bands = query.get(k).split(",")
|
||||
spots = [s for s in spots if s.band and s.band in bands]
|
||||
@@ -224,6 +230,16 @@ class WebServer:
|
||||
case "comment_includes":
|
||||
comment_includes = query.get(k).strip()
|
||||
spots = [s for s in spots if s.comment and comment_includes.upper() in s.comment.upper()]
|
||||
case "allow_qrt":
|
||||
# If false, spots that are flagged as QRT are not returned.
|
||||
prevent_qrt = query.get(k).upper() == "FALSE"
|
||||
if prevent_qrt:
|
||||
spots = [s for s in spots if not s.qrt or s.qrt == False]
|
||||
case "needs_good_location":
|
||||
# If true, spots require a "good" location to be returned
|
||||
needs_good_location = query.get(k).upper() == "TRUE"
|
||||
if needs_good_location:
|
||||
spots = [s for s in spots if s.dx_location_good]
|
||||
case "dedupe":
|
||||
# Ensure only the latest spot of each callsign is present in the list. This relies on the list being
|
||||
# in reverse time order, so if any future change allows re-ordering the list, that should be done
|
||||
|
||||
123
views/webpage_bands.tpl
Normal file
123
views/webpage_bands.tpl
Normal file
@@ -0,0 +1,123 @@
|
||||
% rebase('webpage_base.tpl')
|
||||
|
||||
<div class="mt-3">
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<i class="fa-solid fa-triangle-exclamation"></i> This page is a work in progress. It will be refined as Spothole heads towards v1.0.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<div class="row">
|
||||
<div class="col-auto me-auto pt-3">
|
||||
<p id="timing-container">Loading...</p>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<p class="d-inline-flex gap-1">
|
||||
<button id="filters-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button" onclick="toggleFiltersPanel();"><i class="fa-solid fa-filter"></i> Filters</button>
|
||||
<button id="display-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button" onclick="toggleDisplayPanel();"><i class="fa-solid fa-desktop"></i> Display</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="filters-area" class="appearing-panel card mb-3">
|
||||
<div class="card-header text-white bg-primary">
|
||||
<div class="row">
|
||||
<div class="col-auto me-auto">
|
||||
Filters
|
||||
</div>
|
||||
<div class="col-auto d-inline-flex">
|
||||
<button id="close-filters-button" type="button" class="btn-close btn-close-white" aria-label="Close" onclick="closeFiltersPanel();"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row row-cols-1 g-4 mb-4">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Bands</h5>
|
||||
<p id="band-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row row-cols-1 row-cols-md-4 g-4">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">DX Continent</h5>
|
||||
<p id="dx-continent-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">DE Continent</h5>
|
||||
<p id="de-continent-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Modes</h5>
|
||||
<p id="mode-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Sources</h5>
|
||||
<p id="source-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="display-area" class="appearing-panel card mb-3">
|
||||
<div class="card-header text-white bg-primary">
|
||||
<div class="row">
|
||||
<div class="col-auto me-auto">
|
||||
Display
|
||||
</div>
|
||||
<div class="col-auto d-inline-flex">
|
||||
<button id="close-display-button" type="button" class="btn-close btn-close-white" aria-label="Close" onclick="closeDisplayPanel();"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="display-container" class="row row-cols-1 row-cols-md-4 g-4">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Spot Age</h5>
|
||||
<p class="card-text spothole-card-text">Last
|
||||
<select id="max-spot-age" class="storeable-select form-select ms-2 me-2 d-inline-block" oninput="filtersUpdated();" style="width: 5em; display: inline-block;">
|
||||
<option value="300">5</option>
|
||||
<option value="600">10</option>
|
||||
<option value="1800" selected>30</option>
|
||||
<option value="3600">60</option>
|
||||
</select>
|
||||
minutes
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="bands-container"></div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/js/common.js"></script>
|
||||
<script src="/js/spotandmap.js"></script>
|
||||
<script src="/js/bands.js"></script>
|
||||
<script>$(document).ready(function() { $("#nav-link-bands").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||
@@ -59,6 +59,7 @@
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item ms-4"><a href="/" class="nav-link" id="nav-link-spots"><i class="fa-solid fa-tower-cell"></i> Spots</a></li>
|
||||
<li class="nav-item ms-4"><a href="/map" class="nav-link" id="nav-link-map"><i class="fa-solid fa-map"></i> Map</a></li>
|
||||
<li class="nav-item ms-4"><a href="/bands" class="nav-link" id="nav-link-bands"><i class="fa-solid fa-ruler-vertical"></i> Bands</a></li>
|
||||
<li class="nav-item ms-4"><a href="/alerts" class="nav-link" id="nav-link-alerts"><i class="fa-solid fa-bell"></i> Alerts</a></li>
|
||||
<li class="nav-item ms-4"><a href="/status" class="nav-link" id="nav-link-status"><i class="fa-solid fa-chart-simple"></i> Status</a></li>
|
||||
<li class="nav-item ms-4"><a href="/about" class="nav-link" id="nav-link-about"><i class="fa-solid fa-circle-info"></i> About</a></li>
|
||||
|
||||
@@ -85,7 +85,14 @@ paths:
|
||||
- IOTA
|
||||
- name: needs_sig
|
||||
in: query
|
||||
description: "Limit the spots to only ones from a Special Interest Grous such as POTA. Because supplying all known SIGs as a `sigs` parameter is unwieldy, and leaving `sigs` blank will also return spots with *no* SIG, this parameter can be set true to return only spots with a SIG, regardless of what it is, so long as it's not blank. This is what Field Spotter uses to exclude generic cluster spots and only retrieve xOTA things."
|
||||
description: "Limit the spots to only ones with a Special Interest Group such as POTA. Because supplying all known SIGs as a `sigs` parameter is unwieldy, and leaving `sigs` blank will also return spots with *no* SIG, this parameter can be set true to return only spots with a SIG, regardless of what it is, so long as it's not blank. This is what Field Spotter uses to exclude generic cluster spots and only retrieve xOTA things."
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
- name: needs_sig_ref
|
||||
in: query
|
||||
description: "Limit the spots to only ones which have at least one reference (e.g. a park reference) for Special Interest Groups such as POTA."
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
@@ -195,6 +202,20 @@ paths:
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- name: needs_good_location
|
||||
in: query
|
||||
description: "Return only spots with a 'good' location. (See the spot `dx_location_good` parameter for details. Useful for map-based clients, to avoid spots with 'bad' locations e.g. loads of cluster spots ending up in the centre of the DXCC entitity.)"
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
- name: allow_qrt
|
||||
in: query
|
||||
description: Allow spots that are known to be QRT to be returned.
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
default: true
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
|
||||
@@ -150,13 +150,117 @@ div#map {
|
||||
}
|
||||
|
||||
|
||||
/* BANDS PANEL */
|
||||
|
||||
div#bands-container {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
overscroll-behavior-x: none;
|
||||
}
|
||||
|
||||
/* Bands panel inner layout */
|
||||
div.bandCol {
|
||||
height: 100%;
|
||||
min-width: 8em;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
overflow-y: clip;
|
||||
}
|
||||
|
||||
div.bandColHeader {
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
div.bandColMiddle {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
div.bandColMiddle ul {
|
||||
display: table;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
div.bandColMiddle ul li {
|
||||
display: table-row;
|
||||
line-height: 0.5em;
|
||||
}
|
||||
|
||||
/*noinspection CssUnusedSymbol*/
|
||||
div.bandColMiddle ul li.withSpots {
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
div.bandColMiddle ul li span {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
div.bandColMiddle ul {
|
||||
display: table;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
border-left: 2px dotted;
|
||||
}
|
||||
|
||||
div.bandColHeader {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
div.bandColMiddle {
|
||||
margin-left: 3px;
|
||||
border-left: 2px dotted var(--text);
|
||||
}
|
||||
|
||||
div.bandColSpot {
|
||||
display: block;
|
||||
border-radius: 3px;
|
||||
padding: 3px;
|
||||
background: lightyellow;
|
||||
margin-right: 2em;
|
||||
}
|
||||
|
||||
span.bandColSpot {
|
||||
vertical-align: bottom;
|
||||
display: inline !important;
|
||||
}
|
||||
|
||||
/* Don't wrap frequencies */
|
||||
span.bandColSpotFreq {
|
||||
white-space: nowrap;
|
||||
display: inline !important;
|
||||
}
|
||||
|
||||
span.bandColSpotMode {
|
||||
padding-left: 0.5em;
|
||||
font-size: 0.8em;
|
||||
line-height: 0.4em;
|
||||
}
|
||||
|
||||
|
||||
/* GENERAL MOBILE SUPPORT */
|
||||
|
||||
@media (max-width: 991.99px) {
|
||||
.hideonmobile {
|
||||
display: none !important;
|
||||
}
|
||||
div#map, div#table-container {
|
||||
div#map, div#table-container, div#bands-container {
|
||||
margin-left: -1em;
|
||||
margin-right: -1em;
|
||||
}
|
||||
|
||||
213
webassets/js/bands.js
Normal file
213
webassets/js/bands.js
Normal file
@@ -0,0 +1,213 @@
|
||||
// Load spots and populate the bands display.
|
||||
function loadSpots() {
|
||||
$.getJSON('/api/v1/spots' + buildQueryString(), function(jsonData) {
|
||||
// Store last updated time
|
||||
lastUpdateTime = moment.utc();
|
||||
updateRefreshDisplay();
|
||||
// Store data
|
||||
spots = jsonData;
|
||||
// Update bands display
|
||||
updateBands();
|
||||
});
|
||||
}
|
||||
|
||||
// Build a query string for the API, based on the filters that the user has selected.
|
||||
function buildQueryString() {
|
||||
var str = "?";
|
||||
["dx_continent", "de_continent", "mode_type", "source", "band"].forEach(fn => {
|
||||
if (!allFilterOptionsSelected(fn)) {
|
||||
str = str + getQueryStringFor(fn) + "&";
|
||||
}
|
||||
});
|
||||
str = str + "max_age=" + $("#max-spot-age option:selected").val();
|
||||
// Additional filters for the bands view: No dupes, no QRT
|
||||
str = str + "&dedupe=true&allow_qrt=false";
|
||||
return str;
|
||||
}
|
||||
|
||||
// Update the bands display
|
||||
function updateBands() {
|
||||
// Stop here if nothing to display
|
||||
var bandsPanel = $("#bands-container");
|
||||
if (spots.length === 0) {
|
||||
bandsPanel.html("<div class='alert alert-danger' role='alert'>No spots match your filters.</div>");
|
||||
return;
|
||||
}
|
||||
|
||||
// Do some harsher de-duping. Because we only display callsign, frequency and mode here, the previous
|
||||
// de-duplication could have let some through that don't look like dupes on the map, but would do here.
|
||||
// Typically that's a person activating two programs at the same time, e.g. POTA & WWFF.
|
||||
spotList = removeDuplicatesForBandPanel(spots);
|
||||
|
||||
// Convert to a map of band names to the spots on that band. Bands with no
|
||||
// spots in view will not be present.
|
||||
const bandToSpots = new Map();
|
||||
options["bands"].forEach(function (band) {
|
||||
const matchingSpots = spotList.filter(function (s) {
|
||||
return s.band === band.name;
|
||||
});
|
||||
if (matchingSpots.length > 0) {
|
||||
bandToSpots.set(band.name, matchingSpots);
|
||||
}
|
||||
});
|
||||
|
||||
// Build up HTML content for each band
|
||||
let html = "";
|
||||
const columnWidthPercent = Math.max(30, 100 / bandToSpots.size);
|
||||
let columnIndex = 0;
|
||||
bandToSpots.forEach(function (spotList, bandName) {
|
||||
// Get the colours for the band from the first spot, and prepare the header
|
||||
html += "<div class='bandCol' style='width:" + columnWidthPercent + "%'>";
|
||||
html += "<div class='bandColHeader' style='background-color:" + spotList[0].band_color + "; color:" + spotList[0].band_contrast_color + "'>" + spotList[0].band + "</div>";
|
||||
html += "<div class='bandColMiddle'>";
|
||||
|
||||
// Get the band data to fetch start and end frequencies
|
||||
let band = options["bands"].filter(function (b) {
|
||||
return b.name === bandName;
|
||||
})[0];
|
||||
// Start printing the band
|
||||
const freqStep = (band.end_freq - band.start_freq) / 40.0;
|
||||
html += "<ul>";
|
||||
html += "<li><span>-</span></li>";
|
||||
|
||||
// Do 40 steps down the band
|
||||
for (let i = 0; i <= 40; i++) {
|
||||
|
||||
// Work out if there are any spots in this step
|
||||
const freqStepStart = band.start_freq + i * freqStep;
|
||||
const freqStepEnd = freqStepStart + freqStep;
|
||||
const spotsInStep = spotList.filter(function (s) {
|
||||
// Normally we do >= start and < end, but in the special case where this is the last step and there is a spot
|
||||
// right at the end of the band, we include this too
|
||||
return s.freq >= freqStepStart && (s.freq < freqStepEnd || (s.freq === freqStepEnd && freqStepEnd === band.end_freq));
|
||||
});
|
||||
|
||||
if (spotsInStep.length > 0) {
|
||||
// If this step has spots in it, print them
|
||||
html += "<li class='withSpots'><span>";
|
||||
spotsInStep.sort((a, b) => (a.freq > b.freq) ? 1 : ((b.freq > a.freq) ? -1 : 0));
|
||||
spotsInStep.forEach(function (s) {
|
||||
html += "<div class='bandColSpot'><span class='bandColSpot'>" + s.dx_call + "<br/><span class='bandColSpotFreq'>" + (s.freq/1000000) + "</span>";
|
||||
if (s.mode != null && s.mode.length > 0 && s.mode !== "Unknown") {
|
||||
html += "<span class='bandColSpotMode'>" + s.mode + "</span>";
|
||||
}
|
||||
html += "</span></div>";
|
||||
});
|
||||
html += "</li></span>";
|
||||
|
||||
} else {
|
||||
// Step had no spots in it, so just print a marker. This is a frequency on multiples of 4, or a dash otherwise.
|
||||
if (i % 4 === 0) {
|
||||
html += "<li><span>—" + ((band.start_freq + i * freqStep)/1000000).toFixed(3) + "</span></li>";
|
||||
} else if (i % 4 === 2) {
|
||||
html += "<li><span>–</span></li>";
|
||||
} else {
|
||||
html += "<li><span>-</span></li>";
|
||||
}
|
||||
}
|
||||
}
|
||||
html += "<li><span>-</span></li>";
|
||||
html += "</ul>";
|
||||
|
||||
html += "</div></div>";
|
||||
columnIndex++;
|
||||
});
|
||||
// Update the DOM with the band HTML
|
||||
bandsPanel.html(html);
|
||||
|
||||
// Desktop mouse wheel to scroll bands horizontally if used on the headers
|
||||
// noinspection JSDeprecatedSymbols
|
||||
$(".bandColHeader").on("wheel", () => bandsPanel.scrollLeft(bandsPanel.scrollLeft() + event.deltaY / 10.0));
|
||||
}
|
||||
|
||||
// Iterate through a temporary list of spots, merging duplicates in a way suitable for the band panel. If two or more
|
||||
// spots with the activator, mode and frequency are found, these will be merged and reduced until only one remains,
|
||||
// with the best data. Note that unlike removeDuplicates(), which operates on the main spot map, this operates only
|
||||
// on the temporary array of spots provided as an argument, and returns the output, for use when constructing the
|
||||
// band panel.
|
||||
function removeDuplicatesForBandPanel(spotList) {
|
||||
const spotsToRemove = [];
|
||||
spotList.forEach(function (check) {
|
||||
spotList.forEach(function (s) {
|
||||
if (s !== check) {
|
||||
if (s.dx_call === check.dx_call && s.freq === check.freq && s.mode === check.mode) {
|
||||
// Find which one to keep and which to delete
|
||||
const checkSpotNewer = check.time > s.time;
|
||||
const keepSpot = checkSpotNewer ? check : s;
|
||||
const deleteSpot = checkSpotNewer ? s : check;
|
||||
// Aggregate list of spots to remove
|
||||
spotsToRemove.push(deleteSpot.uid);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
// Perform the removal
|
||||
return spotList.filter(s => !spotsToRemove.includes(s.uid));
|
||||
}
|
||||
|
||||
// Load server options. Once a successful callback is made from this, we then query spots and set up the timer to query
|
||||
// spots repeatedly.
|
||||
function loadOptions() {
|
||||
$.getJSON('/api/v1/options', function(jsonData) {
|
||||
// Store options
|
||||
options = jsonData;
|
||||
|
||||
// Add CSS for band toggle buttons
|
||||
addBandToggleColourCSS(options["bands"]);
|
||||
|
||||
// Populate the filters panel
|
||||
generateBandsMultiToggleFilterCard(options["bands"]);
|
||||
generateMultiToggleFilterCard("#dx-continent-options", "dx_continent", options["continents"]);
|
||||
generateMultiToggleFilterCard("#de-continent-options", "de_continent", options["continents"]);
|
||||
generateMultiToggleFilterCard("#mode-options", "mode_type", options["mode_types"]);
|
||||
generateMultiToggleFilterCard("#source-options", "source", options["spot_sources"]);
|
||||
|
||||
// Load settings from settings storage now all the controls are available
|
||||
loadSettings();
|
||||
|
||||
// Load spots and set up the timer
|
||||
loadSpots();
|
||||
setInterval(loadSpots, REFRESH_INTERVAL_SEC * 1000);
|
||||
});
|
||||
}
|
||||
|
||||
// Method called when any display property is changed to reload the map and persist the display settings.
|
||||
function displayUpdated() {
|
||||
updateMap();
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
// React to toggling/closing panels
|
||||
function toggleFiltersPanel() {
|
||||
// If we are going to show the filters panel, hide the display panel
|
||||
if (!$("#filters-area").is(":visible") && $("#display-area").is(":visible")) {
|
||||
$("#display-area").hide();
|
||||
$("#display-button").button("toggle");
|
||||
}
|
||||
$("#filters-area").toggle();
|
||||
}
|
||||
function closeFiltersPanel() {
|
||||
$("#filters-button").button("toggle");
|
||||
$("#filters-area").hide();
|
||||
}
|
||||
|
||||
function toggleDisplayPanel() {
|
||||
// If we are going to show the display panel, hide the filters panel
|
||||
if (!$("#display-area").is(":visible") && $("#filters-area").is(":visible")) {
|
||||
$("#filters-area").hide();
|
||||
$("#filters-button").button("toggle");
|
||||
}
|
||||
$("#display-area").toggle();
|
||||
}
|
||||
function closeDisplayPanel() {
|
||||
$("#display-button").button("toggle");
|
||||
$("#display-area").hide();
|
||||
}
|
||||
|
||||
// Startup
|
||||
$(document).ready(function() {
|
||||
// Call loadOptions(), this will then trigger loading spots and setting up timers.
|
||||
loadOptions();
|
||||
// Update the refresh timing display every second
|
||||
setInterval(updateRefreshDisplay, 1000);
|
||||
});
|
||||
@@ -3,7 +3,7 @@ var markersLayer;
|
||||
var geodesicsLayer;
|
||||
var terminator;
|
||||
|
||||
// Load spots and populate the table.
|
||||
// Load spots and populate the map.
|
||||
function loadSpots() {
|
||||
$.getJSON('/api/v1/spots' + buildQueryString(), function(jsonData) {
|
||||
// Store data
|
||||
@@ -23,6 +23,8 @@ function buildQueryString() {
|
||||
}
|
||||
});
|
||||
str = str + "max_age=" + $("#max-spot-age option:selected").val();
|
||||
// Additional filters for the map view: No dupes, no QRT, only spots with good locations
|
||||
str = str + "&dedupe=true&allow_qrt=false&needs_good_location=true";
|
||||
return str;
|
||||
}
|
||||
|
||||
@@ -32,29 +34,20 @@ function updateMap() {
|
||||
markersLayer.clearLayers();
|
||||
geodesicsLayer.clearLayers();
|
||||
|
||||
// Make new markers for all spots with a good location, not QRT, and not a duplicate spot within the data set.
|
||||
var callsAlreadyDisplayed = [];
|
||||
// Make new markers for all spots that match the filter
|
||||
spots.forEach(function (s) {
|
||||
if (s["dx_location_good"] && (s["qrt"] == null || s["qrt"] == false)) {
|
||||
if (!callsAlreadyDisplayed.includes(s["dx_call"])) {
|
||||
var m = L.marker([s["dx_latitude"], s["dx_longitude"]], {icon: getIcon(s)});
|
||||
m.bindPopup(getTooltipText(s));
|
||||
markersLayer.addLayer(m);
|
||||
|
||||
// OK, create the marker
|
||||
var m = L.marker([s["dx_latitude"], s["dx_longitude"]], {icon: getIcon(s)});
|
||||
m.bindPopup(getTooltipText(s));
|
||||
markersLayer.addLayer(m);
|
||||
|
||||
// Create geodesics if required
|
||||
if ($("#mapShowGeodesics")[0].checked && s["de_latitude"] != null && s["de_longitude"] != null) {
|
||||
var geodesic = L.geodesic([[s["de_latitude"], s["de_longitude"]], m.getLatLng()], {
|
||||
color: s["band_color"],
|
||||
wrap: false,
|
||||
steps: 5
|
||||
});
|
||||
geodesicsLayer.addLayer(geodesic);
|
||||
}
|
||||
|
||||
}
|
||||
callsAlreadyDisplayed.push(s["dx_call"]);
|
||||
// Create geodesics if required
|
||||
if ($("#mapShowGeodesics")[0].checked && s["de_latitude"] != null && s["de_longitude"] != null) {
|
||||
var geodesic = L.geodesic([[s["de_latitude"], s["de_longitude"]], m.getLatLng()], {
|
||||
color: s["band_color"],
|
||||
wrap: false,
|
||||
steps: 5
|
||||
});
|
||||
geodesicsLayer.addLayer(geodesic);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user