Files
TREK/client/src/components/Tours/tourMapLayers.ts
T
Maurice 06e6e25b25 feat(tours): OpenHikingMap as a hiking base map of the tour planner (MAP-01)
While a hike is planned, the planner's map switch offers "Hiking" beside the
map, topo and satellite: OpenHikingMap, with marked trails over the terrain.
It is a choice of the planner's view alone, like topo: the global map
setting, the tour and its days stay as they are. With no tour open, over an
import viewed as it came in and for a paddle tour it is not offered, and a
choice made earlier falls back to the standard map there.

openmaps.fr's tile usage policy is followed: tiles load only as the map
shows them, with the browser's Referer, from the server's own caching; no
service worker rule keeps them and the 3D flyover's warm-up passes their
source by. Its credit and donation link are on the map; the 3D map, whose
attribution is folded away under the panel, shows them as a line of its own
above its controls.

The service is best effort. A run of failed tiles while it is shown marks
the switch, with a tooltip and a status for screen readers that say it is
not answering and that the tour is untouched; one tile arriving clears it.
The tile health the seamark overlay had is now useTileHealth, shared by both.

The source is free for non-commercial installs up to 500,000 tiles a month.
tours_hiking_map_enabled is an admin switch under Tours (admin tours-hiking-map,
audited), on by default, for an install that is commercial or larger to
switch off until it has an agreement with the operator. /api/addons carries
it as toursHikingMap, true only while Tours is on.

Leaflet, GL and the 3D map read one table of the planner's raster bases
(tourRasterBases), so topo and the hiking map are drawn the same way.
tile.openmaps.fr is on the CSP's connect-src for the GL maps.
2026-10-19 13:07:36 +02:00

71 lines
3.1 KiB
TypeScript

import type { TourTypeKey } from '@trek/shared';
import type { TourBaseLayer } from '../Map/MapLayerSwitcher';
/**
* P2B-06: which maps the tour planner offers for each tour type. The planner
* reads this one table; the global map setting is never touched, and a choice
* made here lasts only as long as the planner keeps it.
*
* A base layer is the map itself (one at a time). An overlay is information
* laid over it and switched on its own; it is off until the user turns it on.
* Only sources TREK already ships or that a card cleared are listed: OpenTopoMap
* and the Esri imagery the planner had, OpenHikingMap (MAP-01), the via ferrata
* layer (RC-10) and OpenSeaMap's seamarks (PAD-10).
*
* - Hike: the standard map first, as before; topo, OpenHikingMap (MAP-01, while the admin
* allows it) and satellite beside it; via ferratas.
* - Paddle: the standard map, whose water reads clearly, and satellite. No topo and
* no ferratas, those are hiking maps. Seamarks as an overlay.
* - Any other type (switched off, or one this build does not plan): the standard map
* and satellite, no overlays.
*
* If an overlay fails, the base map and the planning go on; the switch says so.
*/
type TourOverlayId = 'ferratas' | 'seamarks';
export interface TourMapLayerSet {
bases: readonly TourBaseLayer[];
defaultBase: TourBaseLayer;
overlays: readonly TourOverlayId[];
}
const HIKE: TourMapLayerSet = {
bases: ['default', 'topo', 'satellite'],
defaultBase: 'default',
overlays: ['ferratas'],
};
const HIKE_WITH_HIKING_MAP: TourMapLayerSet = { ...HIKE, bases: ['default', 'topo', 'hiking', 'satellite'] };
const PADDLE: TourMapLayerSet = { bases: ['default', 'satellite'], defaultBase: 'default', overlays: ['seamarks'] };
const OTHER: TourMapLayerSet = { bases: ['default', 'satellite'], defaultBase: 'default', overlays: [] };
/** `hikingMap`: the admin allows OpenHikingMap on this install (MAP-01). */
export function tourMapLayersFor(
type: TourTypeKey | null | undefined,
{ hikingMap = false }: { hikingMap?: boolean } = {}
): TourMapLayerSet {
if (type === 'hike') return hikingMap ? HIKE_WITH_HIKING_MAP : HIKE;
if (type === 'kayak') return PADDLE;
return OTHER;
}
/** The base layer to show: the one chosen while this type offers it, else the type's own default. */
export function baseLayerFor(set: TourMapLayerSet, chosen: TourBaseLayer): TourBaseLayer {
return set.bases.includes(chosen) ? chosen : set.defaultBase;
}
/**
* OpenSeaMap's transparent seamark layer, as its embedding guide names it:
* HTTPS, the seamark path only, no basemap of theirs. Tiles CC-BY-SA 2.0, data
* ODbL. Checked on 16.10.2026: from zoom 6 to 18 every tile answers 200 with a
* PNG (an empty one is transparent), CORS open, no Cache-Control; so a failed
* tile means the service is unreachable, not that the water has no seamarks.
*/
export const SEAMARK_OVERLAY = {
id: 'seamarks',
url: 'https://tiles.openseamap.org/seamark/{z}/{x}/{y}.png',
attribution:
'Seamarks &copy; <a href="https://www.openseamap.org">OpenSeaMap</a> contributors (<a href="https://creativecommons.org/licenses/by-sa/2.0/">CC BY-SA 2.0</a>)',
minZoom: 6,
maxZoom: 18,
} as const;