Add an offline terrain shadow overlay to the Overlays panel

Shades the visible map by whether each point is currently blocked from
the sun by surrounding terrain (shademap.app-style), computed fully
offline via the same Terrarium DEM tiles the Terrain shading layer
already renders, plus this session's sun-position math and Radar's
checkbox+expandable UI shape.

dem.js pulls elevation-at-a-point out on its own since TODO.adoc's
slope readout and solar calculator will need the same lookup.
This commit is contained in:
randy committed 2026-09-10 21:43:51 +02:00
1 parent a8d58abaca
commit c9561a5489
7 files changed
+357 -3

No files matched your search

+12 -2
View File
@@ -3,6 +3,7 @@ import { OVERLAYS_ICON, WIKI_ICON, WILDERNESS_ICON, MAP_MARKER_ICON } from "/js/
import { notifySidePanelOpen, onlyOneSidePanelOpen } from "/js/panels.js";
import { svgToImageData, badgeImageId, escapeHtml } from "/js/gpx.js";
import { RadarControl } from "/js/radar.js";
import { ShadowControl } from "/js/shadow.js";
const ICON_IMAGE_SIZE = 48; // rasterized once at this size, rendered much smaller by icon-size below
const ICON_SIZE = 0.3; // 48 * 0.3 = ~14px, sized to sit inside the marker badge below
@@ -35,7 +36,9 @@ const ELEVATION_COLOR_RAMP = [
// is skipped in favor of a local terrain.pmtiles if one's been generated
// (see docs/development.adoc), same optional-local-file story as
// offline.pmtiles itself.
const ONLINE_TERRAIN_TILES = ["https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png"];
// Exported: dem.js's elevation lookups (shadow.js's raymarch) hit the same
// online tile source, rather than duplicating the URL template.
export const ONLINE_TERRAIN_TILES = ["https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png"];
// A plain on/off Overlays item — there's nothing to choose, just whether
// the shading shows, unlike Radar's expandable scrub/forecast.
@@ -121,6 +124,7 @@ export class OverlaysControl {
this._map = map;
this._hillshade = new HillshadeControl(map);
this._radar = new RadarControl(map);
this._shadow = new ShadowControl(map);
this._wikiEnabled = false; // the checkbox's own state
this._wikiMoveHandler = null; // the moveend listener driving its live refetch, while enabled
this._wikiRequestSeq = 0; // ignores an in-flight refetch a newer one has already superseded
@@ -152,11 +156,13 @@ export class OverlaysControl {
<li class="overlays-panel__item"></li>
<li class="overlays-panel__item"></li>
<li class="overlays-panel__item overlays-panel__item--expandable"></li>
<li class="overlays-panel__item overlays-panel__item--expandable"></li>
`;
const [hillshadeItem, wikiItem, radarItem] = list.children;
const [hillshadeItem, wikiItem, radarItem, shadowItem] = list.children;
this._hillshade.mount(hillshadeItem);
this._mountWiki(wikiItem);
this._radar.mount(radarItem);
this._shadow.mount(shadowItem);
onlyOneSidePanelOpen(this._panel, () => this._icon.classList.remove("active"));
@@ -174,6 +180,10 @@ export class OverlaysControl {
this._hillshade.setOfflineAvailable(available);
}
setShadowOfflineAvailable(available) {
this._shadow.setOfflineAvailable(available);
}
_mountWiki(li) {
li.innerHTML = `
<label>