mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2025-10-27 08:49:27 +00:00
Partial map implementation #42
This commit is contained in:
182
webassets/js/map.js
Normal file
182
webassets/js/map.js
Normal file
@@ -0,0 +1,182 @@
|
||||
// How often to query the server?
|
||||
const REFRESH_INTERVAL_SEC = 60;
|
||||
|
||||
// Storage for the spot data that the server gives us.
|
||||
var spots = []
|
||||
// Marker layer
|
||||
var markersLayer;
|
||||
|
||||
// Load spots and populate the table.
|
||||
function loadSpots() {
|
||||
$.getJSON('/api/v1/spots' + buildQueryString(), function(jsonData) {
|
||||
// Store data
|
||||
spots = jsonData;
|
||||
// Update map
|
||||
updateMap();
|
||||
});
|
||||
}
|
||||
|
||||
// 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();
|
||||
return str;
|
||||
}
|
||||
|
||||
// Update the spots map
|
||||
function updateMap() {
|
||||
// Clear existing content
|
||||
markersLayer.clearLayers();
|
||||
// Make new markers for all spots with a good location
|
||||
spots.forEach(function (s) {
|
||||
if (s["dx_location_good"]) {
|
||||
let m = L.marker([s["dx_latitude"], s["dx_longitude"]], {icon: getIcon(s)});
|
||||
markersLayer.addLayer(m);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Get an icon for a spot, based on its band, using PSK Reporter colours, its program etc.
|
||||
function getIcon(s) {
|
||||
return L.ExtraMarkers.icon({
|
||||
icon: "fa-" + s["icon"],
|
||||
iconColor: "white", // todo
|
||||
markerColor: "black", // todo
|
||||
shape: 'circle',
|
||||
prefix: 'fa',
|
||||
svg: true
|
||||
});
|
||||
}
|
||||
|
||||
// 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 bullets and band toggle buttons
|
||||
addBandColourCSS(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);
|
||||
});
|
||||
}
|
||||
|
||||
// Dynamically add CSS code for the band bullets and band toggle buttons to be in the appropriate colour.
|
||||
// Some band names contain decimal points which are not allowed in CSS classes, so we text-replace them to "p".
|
||||
function addBandColourCSS(band_options) {
|
||||
var $style = $('<style>');
|
||||
band_options.forEach(o => {
|
||||
// CSS doesn't like IDs with decimal points in, so we need to replace that
|
||||
var cssFormattedBandName = o['name'] ? o['name'].replace('.', 'p') : "unknown";
|
||||
$style.append(`.band-bullet-${cssFormattedBandName} { color: ${o['color']}; }`);
|
||||
$style.append(`#filter-button-label-band-${cssFormattedBandName} { border-color: ${o['color']}; color: var(--bs-primary);}`);
|
||||
$style.append(`.btn-check:checked + #filter-button-label-band-${cssFormattedBandName} { background-color: ${o['color']}; color: ${o['contrast_color']};}`);
|
||||
});
|
||||
$('html > head').append($style);
|
||||
}
|
||||
|
||||
// Generate bands filter card. This one is a special case.
|
||||
function generateBandsMultiToggleFilterCard(band_options) {
|
||||
// Create a button for each option
|
||||
band_options.forEach(o => {
|
||||
// CSS doesn't like IDs with decimal points in, so we need to replace that in the same way as when we originally
|
||||
// queried the options endpoint and set our CSS.
|
||||
var cssFormattedBandName = o['name'] ? o['name'].replace('.', 'p') : "unknown";
|
||||
$("#band-options").append(`<input type="checkbox" class="btn-check filter-button-band storeable-checkbox" name="options" id="filter-button-band-${cssFormattedBandName}" value="${o['name']}" autocomplete="off" onClick="filtersUpdated()" checked><label class="btn btn-outline" id="filter-button-label-band-${cssFormattedBandName}" for="filter-button-band-${cssFormattedBandName}">${o['name']}</label> `);
|
||||
});
|
||||
// Create All/None buttons
|
||||
$("#band-options").append(` <span style="display: inline-block"><button id="filter-button-band-all" type="button" class="btn btn-outline-secondary" onclick="toggleFilterButtons('band', true);">All</button> <button id="filter-button-band-none" type="button" class="btn btn-outline-secondary" onclick="toggleFilterButtons('band', false);">None</button></span>`);
|
||||
}
|
||||
|
||||
// Method called when any filter is changed to reload the spots and persist the filter settings.
|
||||
function filtersUpdated() {
|
||||
loadSpots();
|
||||
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();
|
||||
}
|
||||
|
||||
// Set up the map
|
||||
function setUpMap() {
|
||||
// Create map
|
||||
map = L.map('map', {
|
||||
zoomControl: false,
|
||||
minZoom: 2,
|
||||
maxZoom: 12
|
||||
});
|
||||
|
||||
// Add basemap
|
||||
backgroundTileLayer = L.tileLayer.provider("CartoDB.Voyager", {
|
||||
opacity: 1,
|
||||
edgeBufferTiles: 1
|
||||
});
|
||||
backgroundTileLayer.addTo(map);
|
||||
backgroundTileLayer.bringToBack();
|
||||
|
||||
// Add marker layer
|
||||
markersLayer = new L.LayerGroup();
|
||||
markersLayer.addTo(map);
|
||||
|
||||
// Add terminator/greyline
|
||||
terminator = L.terminator({
|
||||
interactive: false
|
||||
});
|
||||
terminator.setStyle({fillColor: '#00000050'});
|
||||
terminator.addTo(map);
|
||||
|
||||
// Display a default view.
|
||||
map.setView([30, 0], 3);
|
||||
}
|
||||
|
||||
// Startup
|
||||
$(document).ready(function() {
|
||||
// Hide the extra things that need to be hidden on this page
|
||||
$(".hideonmap").hide();
|
||||
// Set up map
|
||||
setUpMap();
|
||||
// Call loadOptions(), this will then trigger loading spots and setting up timers.
|
||||
loadOptions();
|
||||
});
|
||||
Reference in New Issue
Block a user