mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 05:26:47 +00:00
dce348b947
Opening a trip on 4.1.0 could land on the error boundary with "Map has no maxZoom specified", and so could the map settings tab, which left no screen from which to pick a different basemap. Leaflet answers getMaxZoom() from the map options first and otherwise from a layer that brought one, and only a GridLayer ever contributes via its beforeAdd hook. A vector basemap is a GL canvas on a plain L.Layer, so it contributes nothing, and MapContainer set no ceiling of its own. MarkerClusterGroup.onAdd refuses an infinite one by throwing, before any clustering, so an empty trip hit it too. That path opened when the default basemap became a MapLibre style. It reaches further than an operator choosing OpenFreeMap: resolveTileUrl sends a keyless CARTO template to the app default, which is now a vector style, so every instance still carrying the old CARTO basemap fell into it on upgrade. The ceiling belongs on the map rather than on the base layer, because it has to hold whichever of the three branches renders. 19 matches what the raster and satellite layers already carried, so nothing changes for the maps that worked. SharedTripPage gets the same treatment: it has no cluster to throw, but it draws the same vector basemap and its fitBounds asks for a ceiling. mapZoomCeiling.test.ts pins Leaflet's own semantics against the real library rather than a mock, since a mocked react-leaflet would have kept passing through all of this.
60 lines
3.3 KiB
TypeScript
60 lines
3.3 KiB
TypeScript
export const DEFAULT_MAP_LAT = 0
|
|
export const DEFAULT_MAP_LNG = 0
|
|
export const DEFAULT_MAP_ZOOM = 2
|
|
export const DEFAULT_MAP_CENTER: [number, number] = [DEFAULT_MAP_LAT, DEFAULT_MAP_LNG]
|
|
|
|
/**
|
|
* Zoom ceiling for a Leaflet map, set on the map rather than on its base layer.
|
|
*
|
|
* Leaflet answers `getMaxZoom()` from the map options first and only then from a
|
|
* layer that carried one, and only a GridLayer ever contributes: a vector
|
|
* basemap is a GL canvas, so a map drawn by one has no ceiling from anywhere.
|
|
* `MarkerClusterGroup.onAdd` refuses an infinite ceiling by throwing, which is
|
|
* how a basemap choice could take down the whole planner. Matches the raster and
|
|
* satellite layers so nothing changes for the maps that already had one.
|
|
*/
|
|
export const MAP_MAX_ZOOM = 19
|
|
|
|
// Tokenless satellite base layer (ESRI World Imagery) — works without an API key.
|
|
export const SATELLITE_TILE_URL =
|
|
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'
|
|
export const SATELLITE_TILE_ATTRIBUTION =
|
|
'Imagery © <a href="https://www.esri.com">Esri</a>, Maxar, Earthstar Geographics'
|
|
export const SATELLITE_TILE_MAXZOOM = 19
|
|
|
|
// OpenFreeMap, the default basemap since CARTO began watermarking keyless tiles
|
|
// on 26.08.2026 and moved its key behind a request by mail. No key, no
|
|
// registration, no request limits, commercial use allowed, attribution required.
|
|
//
|
|
// These are MapLibre STYLE documents, not {z}/{x}/{y} templates: OpenFreeMap
|
|
// serves vector tiles only. Leaflet draws them through VectorBasemap. Positron
|
|
// is the same design CARTO's light basemap was, so the maps look like they did.
|
|
export const OFM_POSITRON = 'https://tiles.openfreemap.org/styles/positron'
|
|
export const OFM_DARK = 'https://tiles.openfreemap.org/styles/dark'
|
|
export const OFM_ATTRIBUTION =
|
|
'<a href="https://openfreemap.org">OpenFreeMap</a> © <a href="https://www.openmaptiles.org/">OpenMapTiles</a> Data from <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
|
|
|
const OSM_ATTRIBUTION = '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
|
|
|
/**
|
|
* Attribution for whatever basemap a map ended up with. OpenFreeMap asks for a
|
|
* credit of its own, and printing OpenStreetMap alone under its tiles is both
|
|
* wrong and a licence problem, so the URL decides rather than a flag at the
|
|
* call site.
|
|
*/
|
|
export function attributionForTile(url: string | null | undefined): string {
|
|
if (!url) return OSM_ATTRIBUTION
|
|
if (url.includes('openfreemap.org')) return OFM_ATTRIBUTION
|
|
if (url.includes('arcgisonline.com')) return SATELLITE_TILE_ATTRIBUTION
|
|
return OSM_ATTRIBUTION
|
|
}
|
|
|
|
// CARTO basemaps. Keyless tiles carry an "API KEY REQUIRED" watermark since
|
|
// 26.08.2026, so these are always passed through withTileApiKey() (#2054).
|
|
// Kept as an opt-in for operators who hold a key; nothing defaults to them.
|
|
export const CARTO_LIGHT = 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png'
|
|
export const CARTO_DARK = 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png'
|
|
export const CARTO_LIGHT_NOLABELS = 'https://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png'
|
|
export const CARTO_DARK_NOLABELS = 'https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png'
|
|
export const CARTO_VOYAGER = 'https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png'
|