@mapmap/maps 0.7.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -104,6 +104,72 @@ function watchForAuthFailures(map, apiKey) {
104
104
  }
105
105
  }
106
106
 
107
+ // src/terrain.ts
108
+ var DEFAULT_TERRAIN_URL = "https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png";
109
+ var DEFAULT_TERRAIN_ATTRIBUTION = '<a href="https://registry.opendata.aws/terrain-tiles/">Terrain data</a> Mapzen, AWS Open Data';
110
+ var TERRAIN_SOURCE_ID = "mapmap-terrain-dem";
111
+ var HILLSHADE_SOURCE_ID = "mapmap-hillshade-dem";
112
+ var HILLSHADE_LAYER_ID = "mapmap-hillshade";
113
+ var MAX_EXAGGERATION = 8;
114
+ function resolveTerrain(input) {
115
+ if (!input) return null;
116
+ const o = input === true ? {} : input;
117
+ const raw = o.exaggeration ?? 1;
118
+ return {
119
+ // NaN must not reach MapLibre: it silently flattens the whole terrain.
120
+ exaggeration: Number.isFinite(raw) ? Math.min(MAX_EXAGGERATION, Math.max(0, raw)) : 1,
121
+ url: o.url ?? DEFAULT_TERRAIN_URL,
122
+ encoding: o.encoding ?? "terrarium",
123
+ maxzoom: o.maxzoom ?? 15,
124
+ tileSize: o.tileSize ?? 256,
125
+ attribution: o.attribution ?? DEFAULT_TERRAIN_ATTRIBUTION,
126
+ hillshade: o.hillshade ?? false
127
+ };
128
+ }
129
+ function applyTerrain(map, cfg) {
130
+ const demSpec = {
131
+ type: "raster-dem",
132
+ tiles: [cfg.url],
133
+ encoding: cfg.encoding,
134
+ tileSize: cfg.tileSize,
135
+ maxzoom: cfg.maxzoom,
136
+ attribution: cfg.attribution
137
+ };
138
+ if (!map.getSource(TERRAIN_SOURCE_ID)) {
139
+ map.addSource(TERRAIN_SOURCE_ID, demSpec);
140
+ }
141
+ map.setTerrain({ source: TERRAIN_SOURCE_ID, exaggeration: cfg.exaggeration });
142
+ if (!cfg.hillshade) {
143
+ if (map.getLayer(HILLSHADE_LAYER_ID)) map.removeLayer(HILLSHADE_LAYER_ID);
144
+ return;
145
+ }
146
+ if (!map.getSource(HILLSHADE_SOURCE_ID)) {
147
+ map.addSource(HILLSHADE_SOURCE_ID, demSpec);
148
+ }
149
+ if (!map.getLayer(HILLSHADE_LAYER_ID)) {
150
+ const layers = map.getStyle()?.layers ?? [];
151
+ const firstLineOrSymbol = layers.find(
152
+ (l) => l.type === "line" || l.type === "symbol"
153
+ );
154
+ map.addLayer(
155
+ {
156
+ id: HILLSHADE_LAYER_ID,
157
+ type: "hillshade",
158
+ source: HILLSHADE_SOURCE_ID,
159
+ paint: { "hillshade-exaggeration": 0.5 }
160
+ },
161
+ firstLineOrSymbol?.id
162
+ );
163
+ }
164
+ }
165
+ function removeTerrain(map) {
166
+ map.setTerrain(null);
167
+ if (map.getLayer(HILLSHADE_LAYER_ID)) map.removeLayer(HILLSHADE_LAYER_ID);
168
+ for (const id of [TERRAIN_SOURCE_ID, HILLSHADE_SOURCE_ID]) {
169
+ if (map.getSource(id)) map.removeSource(id);
170
+ }
171
+ }
172
+
107
173
  // src/coords.ts
108
174
  function toLngLat(point) {
109
175
  if (Array.isArray(point)) {
@@ -1887,10 +1953,22 @@ var MapMapMap = class {
1887
1953
  this.effectExplicit = false;
1888
1954
  /** True while an effect re-apply is already queued behind the style. */
1889
1955
  this.effectWaiting = false;
1956
+ /** Resolved terrain config, or null when terrain is off. */
1957
+ this.terrainConfig = null;
1890
1958
  // Lifecycle: `destroy()` must silence anything still queued behind a
1891
1959
  // style load, and the cleanups let it detach those listeners.
1892
1960
  this.destroyed = false;
1893
1961
  this.pendingWaits = /* @__PURE__ */ new Set();
1962
+ // Enforcement notices already logged for THIS map. Per instance, not
1963
+ // per page (diagnostics.ts dedups per page because a duplicated
1964
+ // maplibre-gl is one page-level fault): each map that asks for a
1965
+ // removal is its own licence problem and deserves its own line, but a
1966
+ // theme swap or re-render must never repeat one.
1967
+ this.notices = /* @__PURE__ */ new Set();
1968
+ this.handleStyleLoadForTerrain = () => {
1969
+ if (this.destroyed || !this.terrainConfig) return;
1970
+ applyTerrain(this.map, this.terrainConfig);
1971
+ };
1894
1972
  this.handleStyleLoadForEffects = () => {
1895
1973
  if (this.destroyed) return;
1896
1974
  if (!this.effectExplicit) {
@@ -1908,22 +1986,62 @@ var MapMapMap = class {
1908
1986
  this.navDesign = navDesignFromTheme(theme);
1909
1987
  this.poiDesign = poiDesignFromTheme(theme);
1910
1988
  const style = resolveStyle(options.style, options.territoryTilesUrl);
1989
+ const requestedAttribution = options.mapOptions?.attributionControl;
1990
+ if (requestedAttribution === false) {
1991
+ this.notice(
1992
+ "attribution-removal",
1993
+ "mapOptions.attributionControl: false is ignored - the OpenStreetMap attribution stays on. The credit is a condition of the ODbL that the map data is served under, for MapMap and for you. Pass { compact: true } to collapse it, or { customAttribution } to add your own credit alongside it."
1994
+ );
1995
+ }
1996
+ const attributionControl = {
1997
+ compact: false,
1998
+ ...requestedAttribution === false ? void 0 : requestedAttribution
1999
+ };
1911
2000
  this.map = new maplibregl.Map({
1912
2001
  container: options.container,
1913
2002
  style,
1914
2003
  center: options.center ?? DEFAULT_CENTER,
1915
2004
  zoom: options.zoom ?? DEFAULT_ZOOM,
1916
- attributionControl: { compact: false },
1917
- ...options.mapOptions
2005
+ ...options.mapOptions,
2006
+ // Never let the constructor mount the attribution: it would land in
2007
+ // MapLibre's default corner, bottom-RIGHT, which is the mark's
2008
+ // corner. The canonical MapMap layout (Matt, 4 Aug 2026) is
2009
+ // attribution small bottom-LEFT, mark bottom-RIGHT, never stacked —
2010
+ // an SDK map must produce that with zero configuration, because
2011
+ // every embed that got the old both-bottom-right default shipped
2012
+ // the mark sitting on top of the OSM credit, and one demo "fixed"
2013
+ // that by display:none-ing the credit, which is an ODbL breach.
2014
+ attributionControl: false
1918
2015
  });
1919
- if (options.logo !== false) {
1920
- const logoOptions = options.logo === true ? {} : options.logo ?? {};
1921
- this.map.addControl(
1922
- new LogoControl(logoOptions),
1923
- logoOptions.position ?? "bottom-right"
2016
+ const attributionPosition = options.attributionPosition ?? "bottom-left";
2017
+ this.map.addControl(
2018
+ new maplibregl.AttributionControl(attributionControl),
2019
+ attributionPosition
2020
+ );
2021
+ if (options.logo === false) {
2022
+ this.notice(
2023
+ "logo-removal",
2024
+ "logo: false is ignored - the MapMap mark stays on the map. Carrying the mark is a condition of the @mapmap/maps licence. Pass logo: { position } to move it to another corner."
1924
2025
  );
1925
2026
  }
2027
+ const logoOptions = typeof options.logo === "object" && options.logo !== null ? options.logo : {};
2028
+ let logoPosition = logoOptions.position ?? "bottom-right";
2029
+ if (logoPosition === attributionPosition) {
2030
+ logoPosition = attributionPosition.endsWith("left") ? "bottom-right" : "bottom-left";
2031
+ this.notice(
2032
+ "logo-collision",
2033
+ `the MapMap mark and the attribution cannot share the ${attributionPosition} corner (the mark would cover the OpenStreetMap credit, which the ODbL requires readable). The mark has moved to ${logoPosition}.`
2034
+ );
2035
+ }
2036
+ this.map.addControl(new LogoControl(logoOptions), logoPosition);
1926
2037
  this.map.on("style.load", this.handleStyleLoadForEffects);
2038
+ this.terrainConfig = resolveTerrain(options.terrain);
2039
+ this.map.on("style.load", this.handleStyleLoadForTerrain);
2040
+ if (this.terrainConfig) {
2041
+ this.whenStyleReady(() => {
2042
+ if (this.terrainConfig) applyTerrain(this.map, this.terrainConfig);
2043
+ });
2044
+ }
1927
2045
  runMapDiagnostics({
1928
2046
  container: options.container,
1929
2047
  map: this.map,
@@ -1931,6 +2049,17 @@ var MapMapMap = class {
1931
2049
  apiKey: this.apiKey
1932
2050
  });
1933
2051
  }
2052
+ /**
2053
+ * Log an enforcement notice at most once for this map. A warning, not
2054
+ * an error and never a throw: `logo: false` and
2055
+ * `attributionControl: false` are published, documented options, and a
2056
+ * hard failure would break a customer's build on a patch upgrade.
2057
+ */
2058
+ notice(notice, message) {
2059
+ if (this.notices.has(notice)) return;
2060
+ this.notices.add(notice);
2061
+ console.warn(`MapMap [${notice}]: ${message}`);
2062
+ }
1934
2063
  /**
1935
2064
  * Run `action` as soon as the style can take layer edits, or now if it
1936
2065
  * already can.
@@ -1962,6 +2091,19 @@ var MapMapMap = class {
1962
2091
  this.pendingWaits.add(cleanup);
1963
2092
  for (const event of events) this.map.on(event, run);
1964
2093
  }
2094
+ /**
2095
+ * Turn terrain on or off after construction. `false` removes the DEM
2096
+ * sources and any hillshade layer this SDK added.
2097
+ */
2098
+ setTerrain(terrain) {
2099
+ if (this.destroyed) return;
2100
+ this.terrainConfig = resolveTerrain(terrain);
2101
+ const cfg = this.terrainConfig;
2102
+ this.whenStyleReady(() => {
2103
+ if (cfg) applyTerrain(this.map, cfg);
2104
+ else removeTerrain(this.map);
2105
+ });
2106
+ }
1965
2107
  /** The current style's `metadata`, if it can be read yet. */
1966
2108
  styleMetadata() {
1967
2109
  try {
@@ -2310,6 +2452,11 @@ var MAX_MARKER_ID_LENGTH = 64;
2310
2452
  var MAX_MARKER_LABEL_LENGTH = 120;
2311
2453
  var MAX_MARKER_TEXT_LENGTH = 3;
2312
2454
  var MARKERS_ID = "mm-user-markers";
2455
+ var BAKED_MARKERS_GLYPH_ID = "mm-user-markers-glyph";
2456
+ function hasBakedMarkers(map) {
2457
+ const ml = map instanceof MapMapMap ? map.map : map;
2458
+ return ml.getLayer(BAKED_MARKERS_GLYPH_ID) !== void 0;
2459
+ }
2313
2460
  var MARKER_IMAGE_MIME = /^data:image\/(png|jpeg|webp|svg\+xml);base64,/;
2314
2461
  function isRecord3(v) {
2315
2462
  return typeof v === "object" && v !== null && !Array.isArray(v);
@@ -2564,11 +2711,12 @@ var MarkersLayer = class {
2564
2711
  type: "FeatureCollection",
2565
2712
  features: this.items.map((item) => this.featureFor(item))
2566
2713
  };
2714
+ const tookOver = this.takeOverBakedLayers();
2567
2715
  const source = this.map.getSource(MARKERS_ID);
2568
- if (source) {
2716
+ if (source && !tookOver) {
2569
2717
  this.registerImages();
2570
2718
  source.setData(this.data);
2571
- } else if (this.map.isStyleLoaded()) {
2719
+ } else if (tookOver || this.map.isStyleLoaded()) {
2572
2720
  this.install();
2573
2721
  }
2574
2722
  }
@@ -2671,9 +2819,33 @@ var MarkersLayer = class {
2671
2819
  }
2672
2820
  for (const key of needed) this.registeredImageKeys.add(key);
2673
2821
  }
2822
+ /**
2823
+ * TAKE OVER from a baked style. A style compiled from a theme with an
2824
+ * `extra.markers` block already draws those markers itself, from an
2825
+ * inline GeoJSON source plus two sprite-driven symbol layers — so
2826
+ * installing on top would draw every marker twice, and pointing the SDK's
2827
+ * feature shape at the baked layers would draw nothing sensible (they
2828
+ * read `bodySize`/`anchor`, this layer writes `iconImage`/`iconSize`).
2829
+ *
2830
+ * Take-over rather than no-op, because this layer is a strict superset:
2831
+ * it also renders the custom `data:` image markers and the short-text
2832
+ * numbered pins that a static sprite cannot carry. The baked SOURCE is
2833
+ * kept and its data replaced in place; only the two baked LAYERS go.
2834
+ *
2835
+ * Only ever runs once markers have been set on this layer, so a
2836
+ * `new MarkersLayer(map)` that is never given any leaves a baked style
2837
+ * completely alone. Idempotent; returns whether anything was taken over.
2838
+ */
2839
+ takeOverBakedLayers() {
2840
+ if (this.map.getLayer(BAKED_MARKERS_GLYPH_ID) === void 0) return false;
2841
+ this.map.removeLayer(BAKED_MARKERS_GLYPH_ID);
2842
+ if (this.map.getLayer(MARKERS_ID)) this.map.removeLayer(MARKERS_ID);
2843
+ return true;
2844
+ }
2674
2845
  /** Add-or-update the images, source and layer on the current style. */
2675
2846
  install() {
2676
2847
  if (!this.data) return;
2848
+ this.takeOverBakedLayers();
2677
2849
  this.registerImages();
2678
2850
  const existing = this.map.getSource(MARKERS_ID);
2679
2851
  if (existing) {
@@ -5687,6 +5859,6 @@ function clampVolume(volume) {
5687
5859
  return Math.min(1, Math.max(0, volume));
5688
5860
  }
5689
5861
 
5690
- export { ALERT_CONTRAST_MIN, AdrCheck, CameraAlertChip, DEFAULT_GLYPHS_URL, DEFAULT_TERRITORY_TILES_URL, EFFECTS_METADATA_KEY, FLOW_DEFAULTS, FULL_ATTRIBUTION, FlowRouteEffectLayer, GuidanceBanner, IsochroneLayer, LOGO_SVG, LogoControl, MARKERS_ID, MARKER_DEFAULT_COLOUR, MARKER_GLYPH_IDS, MARKER_GLYPH_VIEWBOX, MARKER_LABEL_TEXT_SIZES, MARKER_PIN_BODY_PATH, MARKER_SIZES, MARKER_SIZES_PX, MAX_MARKER_ID_LENGTH, MAX_MARKER_IMAGE_BYTES, MAX_MARKER_ITEMS, MAX_MARKER_LABEL_LENGTH, MAX_MARKER_TEXT_LENGTH, MAX_PUCK_IMAGE_BYTES, MapMapMap, MarkersLayer, NAV_ALERT_AUDIO_MODES, NAV_ALERT_CHIP_POSITIONS, NAV_ALERT_ICON_SETS, NAV_ALERT_LEAD_DISTANCES, NAV_ALERT_LEAD_DISTANCE_M, NAV_ALERT_MODES, NAV_ALERT_SOUNDS, NAV_ALERT_SOUND_BASE_URL, NAV_ALERT_SOUND_FILES, NAV_CAMERA_DEFAULTS, NAV_CAMERA_KINDS, NavigationCamera, OPENMAPTILES_ATTRIBUTION, OSM_ATTRIBUTION, PALETTE_SLOTS, POI_CATEGORY_COLORS, POI_CATEGORY_IDS, POI_CLASS_CATEGORIES, PlacesLayer, PositionPuck, RIBBON_FLOATS_PER_VERTEX, ROUTE_EFFECTS, RouteLayer, SIGNAL_BLUE, SOURCE_LAYERS, ThemeScheduler, VoiceGuidance, alertChipContent, alertContrastIssues, alertIconSvg, alertIconUrl, alertPresentation, alertSoundUrl, alertSpeedReadoutColor, applyPoiDesign, bannerLanes, bearingBetween, bindFlythroughToScroll, buildAdrCheckBody, buildProbeUrl, buildRouteQuery, buildRouteUrl, buildStyle, builtInPoiColor, cameraKindLabel, cameraKindsShownOnMap, cameraShownOnMap, cameraSpriteName, cameraSymbolLayer, contrastRatio, createMap, createRouteEffect, defaultNavAlertsDesign, defaultNavDesign, defaultPoiDesign, directionArrow, effectsFromStyleMetadata, extractGuidance, flythrough, flythroughPose, formatCoord, formatCoords, haversineDistanceM, isMarkerGlyphId, isNameTextField, laneArrowDirection, languageTextField, lngLatToMercator, markerGlyphPaths, markerImage, markerImageDataKey, markerImageKey, markerText, markerTextImageKey, markersFromTheme, markersFromThemeUrl, navAlertLeadDistanceM, navAlertSoundUrl, navCameraKind, navDesignFromTheme, navDesignFromThemeUrl, parseCssColour, parseHexColor, parseMarkers, parseNavAlertsDesign, parseNavDesign, parseOsrmRoute, parsePoiDesign, placesFromGeoJSON, poiDesignFromTheme, poiDesignFromThemeUrl, poiDesignIsDefault, poiTextColorExpression, prefersReducedMotion, registerPmtilesProtocol, relativeLuminance, renderMarkerImage, resetDiagnostics, resolveTheme, runMapDiagnostics, setMapLanguage, severityProsody, shortestArcDeg, shortestArcDelta, speak, ssmlToText, sunTimes, tessellateRouteRibbon, toLngLat, toPmtilesUrl, truncateChars, uploadProbeBatch };
5862
+ export { ALERT_CONTRAST_MIN, AdrCheck, BAKED_MARKERS_GLYPH_ID, CameraAlertChip, DEFAULT_GLYPHS_URL, DEFAULT_TERRAIN_ATTRIBUTION, DEFAULT_TERRAIN_URL, DEFAULT_TERRITORY_TILES_URL, EFFECTS_METADATA_KEY, FLOW_DEFAULTS, FULL_ATTRIBUTION, FlowRouteEffectLayer, GuidanceBanner, IsochroneLayer, LOGO_SVG, LogoControl, MARKERS_ID, MARKER_DEFAULT_COLOUR, MARKER_GLYPH_IDS, MARKER_GLYPH_VIEWBOX, MARKER_LABEL_TEXT_SIZES, MARKER_PIN_BODY_PATH, MARKER_SIZES, MARKER_SIZES_PX, MAX_MARKER_ID_LENGTH, MAX_MARKER_IMAGE_BYTES, MAX_MARKER_ITEMS, MAX_MARKER_LABEL_LENGTH, MAX_MARKER_TEXT_LENGTH, MAX_PUCK_IMAGE_BYTES, MapMapMap, MarkersLayer, NAV_ALERT_AUDIO_MODES, NAV_ALERT_CHIP_POSITIONS, NAV_ALERT_ICON_SETS, NAV_ALERT_LEAD_DISTANCES, NAV_ALERT_LEAD_DISTANCE_M, NAV_ALERT_MODES, NAV_ALERT_SOUNDS, NAV_ALERT_SOUND_BASE_URL, NAV_ALERT_SOUND_FILES, NAV_CAMERA_DEFAULTS, NAV_CAMERA_KINDS, NavigationCamera, OPENMAPTILES_ATTRIBUTION, OSM_ATTRIBUTION, PALETTE_SLOTS, POI_CATEGORY_COLORS, POI_CATEGORY_IDS, POI_CLASS_CATEGORIES, PlacesLayer, PositionPuck, RIBBON_FLOATS_PER_VERTEX, ROUTE_EFFECTS, RouteLayer, SIGNAL_BLUE, SOURCE_LAYERS, ThemeScheduler, VoiceGuidance, alertChipContent, alertContrastIssues, alertIconSvg, alertIconUrl, alertPresentation, alertSoundUrl, alertSpeedReadoutColor, applyPoiDesign, applyTerrain, bannerLanes, bearingBetween, bindFlythroughToScroll, buildAdrCheckBody, buildProbeUrl, buildRouteQuery, buildRouteUrl, buildStyle, builtInPoiColor, cameraKindLabel, cameraKindsShownOnMap, cameraShownOnMap, cameraSpriteName, cameraSymbolLayer, contrastRatio, createMap, createRouteEffect, defaultNavAlertsDesign, defaultNavDesign, defaultPoiDesign, directionArrow, effectsFromStyleMetadata, extractGuidance, flythrough, flythroughPose, formatCoord, formatCoords, hasBakedMarkers, haversineDistanceM, isMarkerGlyphId, isNameTextField, laneArrowDirection, languageTextField, lngLatToMercator, markerGlyphPaths, markerImage, markerImageDataKey, markerImageKey, markerText, markerTextImageKey, markersFromTheme, markersFromThemeUrl, navAlertLeadDistanceM, navAlertSoundUrl, navCameraKind, navDesignFromTheme, navDesignFromThemeUrl, parseCssColour, parseHexColor, parseMarkers, parseNavAlertsDesign, parseNavDesign, parseOsrmRoute, parsePoiDesign, placesFromGeoJSON, poiDesignFromTheme, poiDesignFromThemeUrl, poiDesignIsDefault, poiTextColorExpression, prefersReducedMotion, registerPmtilesProtocol, relativeLuminance, removeTerrain, renderMarkerImage, resetDiagnostics, resolveTerrain, resolveTheme, runMapDiagnostics, setMapLanguage, severityProsody, shortestArcDeg, shortestArcDelta, speak, ssmlToText, sunTimes, tessellateRouteRibbon, toLngLat, toPmtilesUrl, truncateChars, uploadProbeBatch };
5691
5863
  //# sourceMappingURL=index.js.map
5692
5864
  //# sourceMappingURL=index.js.map