Files
maptool/public/js/map.js
T
randy f9ddf860ea
CI / check (push) Successful in 6m49s
CI / deploy (push) Successful in 23s
Add Garmin LiveTrack session tracking
A Gmail-watching Apps Script (documented in docs/deployment.adoc) posts
the session ID from each LiveTrack email to a new webhook, which stores
it in data/livetrack.json; a new sidebar panel lists what's been
received.
2026-09-06 21:05:35 +02:00

148 lines
4.7 KiB
JavaScript

import {
Map as MaplibreMap,
NavigationControl,
GeolocateControl,
addProtocol,
} from "/vendor/maplibre-gl/maplibre-gl.mjs";
import { RadarControl } from "/js/radar.js";
import { GpxPanelControl } from "/js/gpx.js";
import { GnssControl } from "/js/gnss.js";
import { LiveTrackPanelControl } from "/js/livetrack.js";
import { LAYERS_ICON } from "/js/icons.js";
import { notifySidePanelOpen, onlyOneSidePanelOpen } from "/js/panels.js";
const CENTER = [17.1077, 48.1486];
const ZOOM = 13;
const ONLINE_STYLE = "https://tiles.openfreemap.org/styles/liberty";
const protocol = new pmtiles.Protocol();
addProtocol("pmtiles", protocol.tile);
// A few color roles nudged from protomaps-themes-base's stock "light" theme
// towards the online OpenFreeMap "liberty" style's actual palette (sampled
// from its style JSON — see docs/architecture.adoc), so switching between
// Online/Offline doesn't look like two different apps. Not a full recolor:
// most roles (labels, casings, minor terrain fills, ...) are left at their
// theme defaults.
const OFFLINE_THEME = {
...protomaps_themes_base.namedTheme("light"),
background: "#f8f4f0",
earth: "#f8f4f0",
water: "#9ebdff",
wood_a: "#ace38c",
wood_b: "#ace38c",
buildings: "#dcd9d6",
highway: "#f2934a",
major: "#ffeeaa",
link: "#ffeeaa",
};
function offlineStyle() {
const pmtilesUrl = `${window.location.origin}/tiles/offline.pmtiles`;
return {
version: 8,
glyphs: `${window.location.origin}/vendor/fonts/{fontstack}/{range}.pbf`,
sources: {
offline: {
type: "vector",
url: `pmtiles://${pmtilesUrl}`,
attribution: "© OpenStreetMap contributors",
},
},
// lang: "en" pulls in labels_layers (see docs/architecture.adoc) — only
// the Latin-script glyph ranges are vendored, so labels using other
// scripts won't render offline.
layers: protomaps_themes_base.layers("offline", OFFLINE_THEME, { lang: "en" }),
};
}
// A sidebar icon that opens a full-height side panel (like the GPX one)
// with the Online/Offline choice — modelled after OsmAnd's "Configure map"
// panel, rather than a small dropdown flyout.
class LayerToggleControl {
constructor(onSelect) {
this._onSelect = onSelect;
}
mount(sidebar) {
this._container = document.createElement("div");
this._container.className = "sidebar-item";
this._container.innerHTML = `<button type="button" class="sidebar-icon" title="Base map" aria-label="Base map">${LAYERS_ICON}</button>`;
this._icon = this._container.querySelector(".sidebar-icon");
this._icon.addEventListener("click", () => this._togglePanel());
this._panel = document.getElementById("basemap-panel");
this._panel
.querySelector(".basemap-panel__close")
.addEventListener("click", () => this._togglePanel(false));
this._panel.querySelectorAll(".basemap-panel__option").forEach((button) => {
button.addEventListener("click", () => {
this._onSelect(button.dataset.style);
this._togglePanel(false);
});
});
onlyOneSidePanelOpen(this._panel, () => this._icon.classList.remove("active"));
sidebar.appendChild(this._container);
}
_togglePanel(force) {
const open = force === undefined ? this._panel.hidden : force;
this._panel.hidden = !open;
this._icon.classList.toggle("active", open);
if (open) notifySidePanelOpen(this._panel);
}
setActive(name) {
this._panel.querySelectorAll(".basemap-panel__option").forEach((button) => {
button.classList.toggle("active", button.dataset.style === name);
});
}
}
const map = new MaplibreMap({
container: "map",
style: ONLINE_STYLE,
center: CENTER,
zoom: ZOOM,
});
map.addControl(new NavigationControl(), "bottom-right");
map.addControl(
new GeolocateControl({
positionOptions: { enableHighAccuracy: true },
trackUserLocation: true,
showAccuracyCircle: true,
}),
"bottom-right",
);
const sidebar = document.getElementById("sidebar");
const layerToggle = new LayerToggleControl(applyStyle);
layerToggle.mount(sidebar);
layerToggle.setActive("online");
new GpxPanelControl(map).mount(sidebar);
new RadarControl(map).mount(sidebar);
new GnssControl(map).mount(sidebar);
new LiveTrackPanelControl().mount(sidebar);
function applyStyle(name) {
layerToggle.setActive(name);
map.setStyle(name === "offline" ? offlineStyle() : ONLINE_STYLE);
}
// If a local PMTiles file is present, prefer it as the default style — its
// presence signals the app is meant to be used offline here.
fetch("/api/offline-tiles")
.then((response) => response.json())
.then(({ available }) => {
if (available) applyStyle("offline");
})
.catch(() => {
// Backend check failed; keep the online style.
});