5th Overlays item, generated client-side from the same online Terrarium DEM tiles Terrain shading already fetches (no offline fallback — the plugin's DemSource has no public hook to route through the pmtiles:// protocol the offline basemap uses, so this quietly renders nothing without a network, same as Radar/the Wikipedia layer). A plain vector source/layer under MapLibre's own tile lifecycle, no manual refetch-on-move code needed. Thresholds are the plugin's own generic starting point, not yet tuned against real terrain. Labels skipped for now (font-name matching across the online/offline styles' own glyph sets adds real risk for a nice-to-have, not the actual ask).
100 lines
3.3 KiB
JavaScript
100 lines
3.3 KiB
JavaScript
import { addProtocol } from "/vendor/maplibre-gl/maplibre-gl.mjs";
|
|
import { ONLINE_TERRAIN_TILES } from "/js/overlays.js";
|
|
import { firstSymbolLayerId } from "/js/radar.js";
|
|
import { CONTOUR_ICON } from "/js/icons.js";
|
|
|
|
const SOURCE_ID = "contour-lines";
|
|
const LINE_LAYER_ID = "contour-lines-line";
|
|
|
|
// Topographic contour lines, generated client-side by the vendored
|
|
// maplibre-contour plugin (public/vendor/maplibre-contour/) from the same
|
|
// online Terrarium DEM tiles overlays.js's HillshadeControl already
|
|
// fetches. Online only, unlike Terrain shading/Terrain shadow: the
|
|
// plugin's DemSource has no public hook to route its own tile fetch
|
|
// through the pmtiles:// protocol HillshadeControl uses for the offline
|
|
// basemap, so — same story as the Wikipedia layer and Radar — this just
|
|
// quietly renders nothing without a network.
|
|
export class ContourControl {
|
|
constructor(map) {
|
|
this._map = map;
|
|
this._active = false;
|
|
this._demSource = new window.mlcontour.DemSource({
|
|
url: ONLINE_TERRAIN_TILES[0],
|
|
encoding: "terrarium",
|
|
maxzoom: 15,
|
|
worker: true,
|
|
cacheSize: 100,
|
|
});
|
|
this._demSource.setupMaplibre({ addProtocol });
|
|
|
|
// setStyle() (switching Online/Offline) tears down all custom sources/
|
|
// layers — put contour lines back if they were on, same as every other
|
|
// Overlays control.
|
|
map.on("style.load", () => {
|
|
if (this._active) this._addLayer();
|
|
});
|
|
}
|
|
|
|
mount(li) {
|
|
li.innerHTML = `
|
|
<label>
|
|
<input type="checkbox" class="overlays-panel__toggle">
|
|
<span class="overlays-panel__icon">${CONTOUR_ICON}</span>
|
|
<span>Contour lines</span>
|
|
</label>
|
|
`;
|
|
this._checkbox = li.querySelector(".overlays-panel__toggle");
|
|
this._checkbox.addEventListener("change", (event) => this._toggle(event.target.checked));
|
|
}
|
|
|
|
_toggle(active) {
|
|
this._active = active;
|
|
if (active) this._addLayer();
|
|
else this._removeLayer();
|
|
}
|
|
|
|
_addLayer() {
|
|
if (this._map.getSource(SOURCE_ID)) return;
|
|
|
|
// Starting-point thresholds (README's own generic example) — meters,
|
|
// more/tighter contours at higher zoom. Expect to eyeball-tune against
|
|
// real Tatras-scale terrain.
|
|
const tiles = [
|
|
this._demSource.contourProtocolUrl({
|
|
thresholds: {
|
|
11: [200, 1000],
|
|
12: [100, 500],
|
|
14: [50, 200],
|
|
15: [20, 100],
|
|
},
|
|
contourLayer: "contours",
|
|
elevationKey: "ele",
|
|
levelKey: "level",
|
|
}),
|
|
];
|
|
this._map.addSource(SOURCE_ID, { type: "vector", tiles, maxzoom: 15 });
|
|
this._map.addLayer(
|
|
{
|
|
id: LINE_LAYER_ID,
|
|
type: "line",
|
|
source: SOURCE_ID,
|
|
"source-layer": "contours",
|
|
paint: {
|
|
// Roughly the same brown as the mid-slope band of overlays.js's
|
|
// own ELEVATION_COLOR_RAMP, so this reads as part of the same
|
|
// terrain treatment rather than a bolted-on layer.
|
|
"line-color": "#8a6d3b",
|
|
"line-width": ["match", ["get", "level"], 1, 1.2, 0.6],
|
|
"line-opacity": 0.6,
|
|
},
|
|
},
|
|
firstSymbolLayerId(this._map),
|
|
);
|
|
}
|
|
|
|
_removeLayer() {
|
|
if (this._map.getLayer(LINE_LAYER_ID)) this._map.removeLayer(LINE_LAYER_ID);
|
|
if (this._map.getSource(SOURCE_ID)) this._map.removeSource(SOURCE_ID);
|
|
}
|
|
}
|