@mapmap/maps 0.2.0 → 0.3.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
@@ -85,7 +85,7 @@ function watchForAuthFailures(map, apiKey) {
85
85
  on.call(map, "error", (ev) => {
86
86
  const status = ev?.error?.status;
87
87
  const message = ev?.error?.message ?? "";
88
- const unauthorised = status === 401 || /\b401\b|unauthori[sz]ed/i.test(message);
88
+ const unauthorised = status === 401 || /unauthori[sz]ed/i.test(message);
89
89
  if (!unauthorised) return;
90
90
  report(
91
91
  "invalid-api-key",
@@ -96,6 +96,52 @@ function watchForAuthFailures(map, apiKey) {
96
96
  }
97
97
  }
98
98
 
99
+ // src/coords.ts
100
+ function toLngLat(point) {
101
+ if (Array.isArray(point)) {
102
+ const [lng2, lat2] = point;
103
+ assertFinite(lng2, lat2);
104
+ return [lng2, lat2];
105
+ }
106
+ const lng = "lng" in point ? point.lng : point.lon;
107
+ const lat = point.lat;
108
+ assertFinite(lng, lat);
109
+ return [lng, lat];
110
+ }
111
+ function formatCoord(point) {
112
+ const [lng, lat] = toLngLat(point);
113
+ return `${lng},${lat}`;
114
+ }
115
+ function formatCoords(points) {
116
+ if (points.length < 2) {
117
+ throw new Error("at least two coordinates are required for a route");
118
+ }
119
+ return points.map(formatCoord).join(";");
120
+ }
121
+ function unwrapLngs(coordinates) {
122
+ const out = [];
123
+ for (const [lng, lat] of coordinates) {
124
+ const prev = out[out.length - 1];
125
+ let unwrapped = lng;
126
+ if (prev) {
127
+ while (unwrapped - prev[0] > 180) unwrapped -= 360;
128
+ while (unwrapped - prev[0] < -180) unwrapped += 360;
129
+ }
130
+ out.push([unwrapped, lat]);
131
+ }
132
+ return out;
133
+ }
134
+ function assertFinite(lng, lat) {
135
+ if (!Number.isFinite(lng) || !Number.isFinite(lat)) {
136
+ throw new Error(`invalid coordinate: lng=${lng}, lat=${lat}`);
137
+ }
138
+ if (lng < -180 || lng > 180 || lat < -90 || lat > 90) {
139
+ throw new Error(
140
+ `coordinate out of range: lng=${lng} (\xB1180), lat=${lat} (\xB190) - check lng/lat order`
141
+ );
142
+ }
143
+ }
144
+
99
145
  // src/effects.ts
100
146
  var FLOW_COLOUR = "#3a86ff";
101
147
  var EARTH_RADIUS_M = 63710088e-1;
@@ -115,7 +161,7 @@ function lngLatToMercator(lngLat) {
115
161
  }
116
162
  function tessellateRouteRibbon(coordinates) {
117
163
  const points = [];
118
- for (const lngLat of coordinates) {
164
+ for (const lngLat of unwrapLngs(coordinates)) {
119
165
  const p = lngLatToMercator(lngLat);
120
166
  const last = points[points.length - 1];
121
167
  if (!last || last[0] !== p[0] || last[1] !== p[1]) points.push(p);
@@ -1481,39 +1527,6 @@ function resolveStyle(style, territoryTilesUrl) {
1481
1527
  return style;
1482
1528
  }
1483
1529
 
1484
- // src/coords.ts
1485
- function toLngLat(point) {
1486
- if (Array.isArray(point)) {
1487
- const [lng2, lat2] = point;
1488
- assertFinite(lng2, lat2);
1489
- return [lng2, lat2];
1490
- }
1491
- const lng = "lng" in point ? point.lng : point.lon;
1492
- const lat = point.lat;
1493
- assertFinite(lng, lat);
1494
- return [lng, lat];
1495
- }
1496
- function formatCoord(point) {
1497
- const [lng, lat] = toLngLat(point);
1498
- return `${lng},${lat}`;
1499
- }
1500
- function formatCoords(points) {
1501
- if (points.length < 2) {
1502
- throw new Error("at least two coordinates are required for a route");
1503
- }
1504
- return points.map(formatCoord).join(";");
1505
- }
1506
- function assertFinite(lng, lat) {
1507
- if (!Number.isFinite(lng) || !Number.isFinite(lat)) {
1508
- throw new Error(`invalid coordinate: lng=${lng}, lat=${lat}`);
1509
- }
1510
- if (lng < -180 || lng > 180 || lat < -90 || lat > 90) {
1511
- throw new Error(
1512
- `coordinate out of range: lng=${lng} (\xB1180), lat=${lat} (\xB190) - check lng/lat order`
1513
- );
1514
- }
1515
- }
1516
-
1517
1530
  // src/osrm.ts
1518
1531
  function buildRouteQuery(truck, guidance) {
1519
1532
  const params = new URLSearchParams({
@@ -1587,10 +1600,38 @@ function numberOr(value, fallback) {
1587
1600
  // src/route.ts
1588
1601
  var SIGNAL_BLUE = "#3a86ff";
1589
1602
  var CASING_COLOR = "#1f438a";
1603
+ var PROGRESS_COLOR = "#b0b0b0";
1604
+ function arrowImage() {
1605
+ const size = 24;
1606
+ const data = new Uint8Array(size * size * 4);
1607
+ const head = 13;
1608
+ const put = (x, y, edge) => {
1609
+ const i = (y * size + x) * 4;
1610
+ const v = edge ? 31 : 255;
1611
+ data[i] = v;
1612
+ data[i + 1] = v;
1613
+ data[i + 2] = edge ? 58 : 255;
1614
+ data[i + 3] = 255;
1615
+ };
1616
+ for (let y = 2; y < head; y++) {
1617
+ const half = Math.round((y - 2) / (head - 3) * 9);
1618
+ for (let x = 11 - half; x <= 12 + half; x++) {
1619
+ put(x, y, x === 11 - half || x === 12 + half || y === 2);
1620
+ }
1621
+ }
1622
+ for (let y = head; y < 22; y++) {
1623
+ for (let x = 9; x <= 14; x++) {
1624
+ put(x, y, x === 9 || x === 14 || y === 21);
1625
+ }
1626
+ }
1627
+ return { width: size, height: size, data };
1628
+ }
1590
1629
  var RouteLayer = class {
1591
1630
  constructor(map, options = {}) {
1631
+ this.progress = 0;
1592
1632
  this.handleStyleLoad = () => {
1593
1633
  if (this.lastRoute) this.install(this.lastRoute);
1634
+ else this.installManeuver();
1594
1635
  };
1595
1636
  if (map instanceof MapMapMap) {
1596
1637
  this.map = map.map;
@@ -1611,6 +1652,10 @@ var RouteLayer = class {
1611
1652
  this.sourceId = `${id}-src`;
1612
1653
  this.casingLayerId = `${id}-casing`;
1613
1654
  this.lineLayerId = `${id}-line`;
1655
+ this.maneuverSourceId = `${id}-maneuver-src`;
1656
+ this.maneuverLayerId = `${id}-maneuver`;
1657
+ this.arrowImageId = `${id}-arrow`;
1658
+ this.progressColor = options.progressColor ?? PROGRESS_COLOR;
1614
1659
  this.map.on("style.load", this.handleStyleLoad);
1615
1660
  }
1616
1661
  /**
@@ -1672,9 +1717,11 @@ var RouteLayer = class {
1672
1717
  if (existing) {
1673
1718
  existing.setData(data);
1674
1719
  } else {
1675
- this.map.addSource(this.sourceId, { type: "geojson", data });
1720
+ this.map.addSource(this.sourceId, { type: "geojson", data, lineMetrics: true });
1676
1721
  }
1677
1722
  if (this.map.getLayer(this.casingLayerId) && this.map.getLayer(this.lineLayerId)) {
1723
+ this.applyProgress();
1724
+ this.installManeuver();
1678
1725
  return;
1679
1726
  }
1680
1727
  const casing = {
@@ -1707,15 +1754,86 @@ var RouteLayer = class {
1707
1754
  };
1708
1755
  if (!this.map.getLayer(this.casingLayerId)) this.map.addLayer(casing);
1709
1756
  if (!this.map.getLayer(this.lineLayerId)) this.map.addLayer(line);
1757
+ this.applyProgress();
1758
+ this.installManeuver();
1759
+ }
1760
+ /**
1761
+ * Sets how much of the route has been travelled, as a fraction in `[0, 1]`
1762
+ * of the line's length. The travelled part dims to `progressColor` (the
1763
+ * "vanishing route line"); `0` restores the untinted line. The value is
1764
+ * remembered across {@link draw} calls and style swaps. Pair with the
1765
+ * guidance module's distance-remaining to derive the fraction.
1766
+ */
1767
+ setProgress(fraction) {
1768
+ this.progress = Math.min(1, Math.max(0, fraction));
1769
+ if (this.map.getLayer(this.lineLayerId)) this.applyProgress();
1770
+ }
1771
+ /** Applies the current progress fraction to the line layer's gradient. */
1772
+ applyProgress() {
1773
+ const routeColor = this.design?.color ?? SIGNAL_BLUE;
1774
+ const gradient = this.progress > 0 ? ["step", ["line-progress"], this.progressColor, this.progress, routeColor] : void 0;
1775
+ this.map.setPaintProperty(this.lineLayerId, "line-gradient", gradient);
1776
+ }
1777
+ /**
1778
+ * Shows (or moves) the upcoming-manoeuvre arrow: a small map-aligned
1779
+ * arrow at `lngLat` rotated to `bearingDeg` (clockwise from north).
1780
+ * Survives style swaps until {@link clearManeuver}.
1781
+ */
1782
+ setManeuver(lngLat, bearingDeg) {
1783
+ this.maneuver = { lngLat, bearingDeg };
1784
+ if (this.map.isStyleLoaded()) this.installManeuver();
1785
+ }
1786
+ /** Hides the manoeuvre arrow. */
1787
+ clearManeuver() {
1788
+ this.maneuver = void 0;
1789
+ if (this.map.getLayer(this.maneuverLayerId)) this.map.removeLayer(this.maneuverLayerId);
1790
+ if (this.map.getSource(this.maneuverSourceId)) this.map.removeSource(this.maneuverSourceId);
1791
+ }
1792
+ /** Add-or-update the manoeuvre arrow source/layer for the current style. */
1793
+ installManeuver() {
1794
+ if (!this.maneuver) return;
1795
+ const data = {
1796
+ type: "Feature",
1797
+ properties: { bearing: this.maneuver.bearingDeg },
1798
+ geometry: { type: "Point", coordinates: this.maneuver.lngLat }
1799
+ };
1800
+ const source = this.map.getSource(this.maneuverSourceId);
1801
+ if (source) {
1802
+ source.setData(data);
1803
+ } else {
1804
+ this.map.addSource(this.maneuverSourceId, { type: "geojson", data });
1805
+ }
1806
+ if (!this.map.hasImage(this.arrowImageId)) {
1807
+ this.map.addImage(this.arrowImageId, arrowImage());
1808
+ }
1809
+ if (!this.map.getLayer(this.maneuverLayerId)) {
1810
+ this.map.addLayer({
1811
+ id: this.maneuverLayerId,
1812
+ type: "symbol",
1813
+ source: this.maneuverSourceId,
1814
+ layout: {
1815
+ "icon-image": this.arrowImageId,
1816
+ "icon-rotate": ["get", "bearing"],
1817
+ "icon-rotation-alignment": "map",
1818
+ "icon-allow-overlap": true,
1819
+ "icon-ignore-placement": true,
1820
+ "icon-size": ["interpolate", ["linear"], ["zoom"], 12, 0.7, 18, 1.4]
1821
+ }
1822
+ });
1823
+ } else {
1824
+ this.map.setLayoutProperty(this.maneuverLayerId, "icon-rotate", ["get", "bearing"]);
1825
+ }
1710
1826
  }
1711
1827
  /** Remove the route's layers and source from the map. */
1712
1828
  clear() {
1829
+ this.clearManeuver();
1713
1830
  for (const layerId of [this.lineLayerId, this.casingLayerId]) {
1714
1831
  if (this.map.getLayer(layerId)) this.map.removeLayer(layerId);
1715
1832
  }
1716
1833
  if (this.map.getSource(this.sourceId)) this.map.removeSource(this.sourceId);
1717
1834
  this.lastRoute = void 0;
1718
1835
  this.owner?.setRouteEffectGeometry(null);
1836
+ this.progress = 0;
1719
1837
  }
1720
1838
  /**
1721
1839
  * Remove the route and detach the layer's `style.load` listener. Call
@@ -1741,6 +1859,12 @@ function effectiveImageUrl(design) {
1741
1859
  }
1742
1860
  return url;
1743
1861
  }
1862
+ function shortestArcDeg(fromDeg, toDeg) {
1863
+ const raw = ((toDeg - fromDeg) % 360 + 360) % 360;
1864
+ return raw > 180 ? raw - 360 : raw;
1865
+ }
1866
+ var MAX_TWEEN_MS = 900;
1867
+ var MIN_TWEEN_MS = 100;
1744
1868
  var PositionPuck = class {
1745
1869
  /**
1746
1870
  * Creates the puck (not yet on the map - it appears on the first
@@ -1748,12 +1872,17 @@ var PositionPuck = class {
1748
1872
  * `navDesign.puck` when given a `MapMapMap` whose theme carried an
1749
1873
  * `extra.nav` block, then to the built-in blue puck.
1750
1874
  */
1751
- constructor(map, design) {
1875
+ constructor(map, design, options) {
1752
1876
  this.added = false;
1753
1877
  this.map = map instanceof MapMapMap ? map.map : map;
1754
1878
  this.design = design ?? (map instanceof MapMapMap ? map.navDesign?.puck : void 0) ?? defaultNavDesign().puck;
1755
1879
  this.element = createPuckElement();
1756
1880
  stylePuckElement(this.element, this.design);
1881
+ this.interpolate = options?.interpolate ?? true;
1882
+ this.now = options?.now ?? Date.now;
1883
+ const g = globalThis;
1884
+ this.requestFrame = options?.requestFrame ?? (g.requestAnimationFrame ? (cb) => g.requestAnimationFrame(() => cb()) : void 0);
1885
+ this.cancelFrame = options?.cancelFrame ?? (g.cancelAnimationFrame ? (h) => g.cancelAnimationFrame(h) : void 0);
1757
1886
  this.marker = new maplibregl.Marker({
1758
1887
  element: this.element,
1759
1888
  rotationAlignment: "map",
@@ -1764,20 +1893,72 @@ var PositionPuck = class {
1764
1893
  * Moves the puck (adding it to the map on the first call). `headingDeg`
1765
1894
  * rotates the whole element - arrow or custom image - clockwise from
1766
1895
  * north; omit it to keep the previous heading.
1896
+ *
1897
+ * With interpolation on (the default) every call after the first glides
1898
+ * from the currently rendered position - a fix arriving mid-tween
1899
+ * retargets smoothly rather than jumping.
1767
1900
  */
1768
1901
  setLocation(location, headingDeg) {
1769
- this.marker.setLngLat([location.lon, location.lat]);
1770
- if (headingDeg !== void 0) this.marker.setRotation(headingDeg);
1771
- if (!this.added) {
1902
+ const first = !this.added;
1903
+ const nowMs = this.now();
1904
+ const interval = this.lastFixAt === void 0 ? MAX_TWEEN_MS : nowMs - this.lastFixAt;
1905
+ this.lastFixAt = nowMs;
1906
+ this.cancelTween();
1907
+ const from = this.rendered;
1908
+ const target = {
1909
+ lat: location.lat,
1910
+ lon: location.lon,
1911
+ heading: headingDeg ?? from?.heading ?? 0
1912
+ };
1913
+ const duration = Math.min(MAX_TWEEN_MS, interval);
1914
+ if (first || !this.interpolate || !this.requestFrame || !from || duration < MIN_TWEEN_MS) {
1915
+ this.render(target);
1916
+ } else {
1917
+ this.tween(from, target, nowMs, duration);
1918
+ }
1919
+ if (first) {
1772
1920
  this.marker.addTo(this.map);
1773
1921
  this.added = true;
1774
1922
  }
1775
1923
  }
1776
- /** Removes the puck from the map. `setLocation` re-adds it. */
1924
+ /** Removes the puck from the map (cancelling any tween). `setLocation` re-adds it. */
1777
1925
  remove() {
1926
+ this.cancelTween();
1778
1927
  this.marker.remove();
1779
1928
  this.added = false;
1780
1929
  }
1930
+ /** Applies a position/heading to the marker immediately. */
1931
+ render(state) {
1932
+ this.marker.setLngLat([state.lon, state.lat]);
1933
+ this.marker.setRotation(state.heading);
1934
+ this.rendered = state;
1935
+ }
1936
+ /** Runs a linear position lerp + shortest-arc heading tween via rAF. */
1937
+ tween(from, to, startedAt, duration) {
1938
+ const headingDelta = shortestArcDeg(from.heading, to.heading);
1939
+ const step = () => {
1940
+ const t = Math.min(1, (this.now() - startedAt) / duration);
1941
+ this.render({
1942
+ lat: from.lat + (to.lat - from.lat) * t,
1943
+ lon: from.lon + (to.lon - from.lon) * t,
1944
+ heading: from.heading + headingDelta * t
1945
+ });
1946
+ if (t < 1) {
1947
+ this.frameHandle = this.requestFrame(step);
1948
+ } else {
1949
+ this.frameHandle = void 0;
1950
+ this.render(to);
1951
+ }
1952
+ };
1953
+ this.frameHandle = this.requestFrame(step);
1954
+ }
1955
+ /** Cancels an in-flight tween, leaving the marker where it rendered last. */
1956
+ cancelTween() {
1957
+ if (this.frameHandle !== void 0) {
1958
+ this.cancelFrame?.(this.frameHandle);
1959
+ this.frameHandle = void 0;
1960
+ }
1961
+ }
1781
1962
  };
1782
1963
  function createPuckElement() {
1783
1964
  const doc = globalThis.document;
@@ -1826,6 +2007,186 @@ function stylePuckElement(el, puck) {
1826
2007
  dot.style.cssText = `position:absolute;inset:0;border-radius:50%;background:${puck.color};border:2px solid #ffffff;box-shadow:0 1px 6px rgba(0,0,0,0.45)`;
1827
2008
  arrow.style.cssText = puck.headingArrow ? `position:absolute;left:50%;top:${(-s * 0.55).toFixed(1)}px;transform:translateX(-50%);width:0;height:0;border-left:${(s * 0.35).toFixed(1)}px solid transparent;border-right:${(s * 0.35).toFixed(1)}px solid transparent;border-bottom:${(s * 0.6).toFixed(1)}px solid ${puck.color}` : "display:none";
1828
2009
  }
2010
+
2011
+ // src/daynight.ts
2012
+ var DEG = Math.PI / 180;
2013
+ var DAY_MS = 864e5;
2014
+ var JULIAN_EPOCH = 24405875e-1;
2015
+ var J2000 = 2451545;
2016
+ function fromJulian(julian) {
2017
+ return new Date((julian - JULIAN_EPOCH) * DAY_MS);
2018
+ }
2019
+ function sunTimes(date, lat, lng) {
2020
+ const julianDay = Math.ceil(date.getTime() / DAY_MS + JULIAN_EPOCH - J2000 - 9e-4 + lng / 360);
2021
+ const meanSolarTime = julianDay + 9e-4 - lng / 360;
2022
+ const meanAnomalyDeg = (357.5291 + 0.98560028 * meanSolarTime) % 360;
2023
+ const m = meanAnomalyDeg * DEG;
2024
+ const centreDeg = 1.9148 * Math.sin(m) + 0.02 * Math.sin(2 * m) + 3e-4 * Math.sin(3 * m);
2025
+ const eclipticLngDeg = (meanAnomalyDeg + centreDeg + 180 + 102.9372) % 360;
2026
+ const l = eclipticLngDeg * DEG;
2027
+ const transit = J2000 + meanSolarTime + 53e-4 * Math.sin(m) - 69e-4 * Math.sin(2 * l);
2028
+ const sinDeclination = Math.sin(l) * Math.sin(23.4397 * DEG);
2029
+ const cosDeclination = Math.cos(Math.asin(sinDeclination));
2030
+ const cosHourAngle = (Math.sin(-0.833 * DEG) - Math.sin(lat * DEG) * sinDeclination) / (Math.cos(lat * DEG) * cosDeclination);
2031
+ if (cosHourAngle < -1) return "polarDay";
2032
+ if (cosHourAngle > 1) return "polarNight";
2033
+ const hourAngleDeg = Math.acos(cosHourAngle) / DEG;
2034
+ return {
2035
+ sunrise: fromJulian(transit - hourAngleDeg / 360),
2036
+ sunset: fromJulian(transit + hourAngleDeg / 360)
2037
+ };
2038
+ }
2039
+ function resolveTheme(date, lat, lng) {
2040
+ const times = sunTimes(date, lat, lng);
2041
+ if (times === "polarDay") return "light";
2042
+ if (times === "polarNight") return "dark";
2043
+ return date >= times.sunrise && date < times.sunset ? "light" : "dark";
2044
+ }
2045
+ var POLAR_RECHECK_MS = 6 * 36e5;
2046
+ var BOUNDARY_MARGIN_MS = 1e3;
2047
+ var ThemeScheduler = class {
2048
+ constructor(options) {
2049
+ this.disposed = false;
2050
+ this.lat = options.lat;
2051
+ this.lng = options.lng;
2052
+ this.onLight = options.onLight;
2053
+ this.onDark = options.onDark;
2054
+ this.now = options.now ?? Date.now;
2055
+ this.setTimeoutFn = options.setTimeoutFn ?? ((cb, ms) => setTimeout(cb, ms));
2056
+ this.clearTimeoutFn = options.clearTimeoutFn ?? ((h) => clearTimeout(h));
2057
+ this.evaluate();
2058
+ }
2059
+ /** The theme most recently applied, if any. */
2060
+ get current() {
2061
+ return this.applied;
2062
+ }
2063
+ /** Moves the observer (e.g. a new GPS fix region) and re-evaluates. */
2064
+ setPosition(lat, lng) {
2065
+ this.lat = lat;
2066
+ this.lng = lng;
2067
+ this.evaluate();
2068
+ }
2069
+ /** Stops all future flips. */
2070
+ dispose() {
2071
+ this.disposed = true;
2072
+ if (this.handle !== void 0) this.clearTimeoutFn(this.handle);
2073
+ this.handle = void 0;
2074
+ }
2075
+ /** Applies the theme for now and arms the timer for the next boundary. */
2076
+ evaluate() {
2077
+ if (this.disposed) return;
2078
+ if (this.handle !== void 0) {
2079
+ this.clearTimeoutFn(this.handle);
2080
+ this.handle = void 0;
2081
+ }
2082
+ const nowDate = new Date(this.now());
2083
+ const theme = resolveTheme(nowDate, this.lat, this.lng);
2084
+ if (theme !== this.applied) {
2085
+ this.applied = theme;
2086
+ (theme === "light" ? this.onLight : this.onDark)();
2087
+ }
2088
+ const delay = this.nextBoundaryDelay(nowDate);
2089
+ this.handle = this.setTimeoutFn(() => this.evaluate(), delay);
2090
+ }
2091
+ /** Milliseconds until the next sunrise/sunset (or the polar re-check). */
2092
+ nextBoundaryDelay(nowDate) {
2093
+ const nowMs = nowDate.getTime();
2094
+ for (const dayOffset of [0, 1]) {
2095
+ const times = sunTimes(new Date(nowMs + dayOffset * DAY_MS), this.lat, this.lng);
2096
+ if (times === "polarDay" || times === "polarNight") continue;
2097
+ for (const event of [times.sunrise, times.sunset]) {
2098
+ const delta = event.getTime() - nowMs;
2099
+ if (delta > 0) return delta + BOUNDARY_MARGIN_MS;
2100
+ }
2101
+ }
2102
+ return POLAR_RECHECK_MS;
2103
+ }
2104
+ };
2105
+
2106
+ // src/language.ts
2107
+ var LANGUAGE_TAG = /^[a-z]{2,3}(-[A-Za-z0-9]{2,4})?$/;
2108
+ function languageTextField(language) {
2109
+ if (language === null) {
2110
+ return ["coalesce", ["get", "name:en"], ["get", "name"]];
2111
+ }
2112
+ return [
2113
+ "coalesce",
2114
+ ["get", `name:${language}`],
2115
+ ["get", "name:latin"],
2116
+ ["get", "name"]
2117
+ ];
2118
+ }
2119
+ function isNameTextField(textField) {
2120
+ if (typeof textField === "string") {
2121
+ return /\{name(?::[A-Za-z0-9-]+)?\}/.test(textField);
2122
+ }
2123
+ if (Array.isArray(textField)) {
2124
+ if (textField.length === 2 && textField[0] === "get" && typeof textField[1] === "string") {
2125
+ return textField[1] === "name" || textField[1].startsWith("name:");
2126
+ }
2127
+ return textField.some((part) => isNameTextField(part));
2128
+ }
2129
+ return false;
2130
+ }
2131
+ function setMapLanguage(map, language) {
2132
+ if (language !== null && !LANGUAGE_TAG.test(language)) {
2133
+ throw new Error(
2134
+ `invalid language tag ${JSON.stringify(language)}; use e.g. "de", "pt-BR", "zh-Hans"`
2135
+ );
2136
+ }
2137
+ const ml = map instanceof MapMapMap ? map.map : map;
2138
+ const layers = ml.getStyle()?.layers ?? [];
2139
+ const changed = [];
2140
+ for (const layer of layers) {
2141
+ if (layer.type !== "symbol") continue;
2142
+ const textField = layer.layout?.["text-field"];
2143
+ if (textField === void 0 || !isNameTextField(textField)) continue;
2144
+ ml.setLayoutProperty(layer.id, "text-field", languageTextField(language));
2145
+ changed.push(layer.id);
2146
+ }
2147
+ return changed;
2148
+ }
2149
+
2150
+ // src/probe.ts
2151
+ function buildProbeUrl(baseUrl) {
2152
+ return `${baseUrl.replace(/\/+$/, "")}/v1/probe`;
2153
+ }
2154
+ async function uploadProbeBatch(baseUrl, apiKey, body, options = {}) {
2155
+ const doFetch = options.fetch ?? globalThis.fetch;
2156
+ const maxAttempts = options.maxAttempts ?? 4;
2157
+ const backoffMs = options.backoffMs ?? ((attempt) => attempt * 1e3);
2158
+ const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
2159
+ const url = buildProbeUrl(baseUrl);
2160
+ for (let attempt = 1; ; attempt++) {
2161
+ let outcome;
2162
+ try {
2163
+ const response = await doFetch(url, {
2164
+ method: "POST",
2165
+ headers: {
2166
+ Authorization: `Bearer ${apiKey}`,
2167
+ "Content-Type": "application/json"
2168
+ },
2169
+ body,
2170
+ signal: options.signal
2171
+ });
2172
+ outcome = classifyStatus(response.status);
2173
+ } catch {
2174
+ outcome = "gaveUp";
2175
+ }
2176
+ if (outcome === "gaveUp" && attempt < maxAttempts) {
2177
+ await sleep(backoffMs(attempt));
2178
+ continue;
2179
+ }
2180
+ return outcome;
2181
+ }
2182
+ }
2183
+ function classifyStatus(status) {
2184
+ if (status === 202) return "accepted";
2185
+ if (status === 403) return "refused";
2186
+ if (status === 501) return "notEnabled";
2187
+ if (status >= 500 && status <= 599) return "gaveUp";
2188
+ return "rejected";
2189
+ }
1829
2190
  var EARTH_RADIUS_M2 = 63710088e-1;
1830
2191
  var CLUSTER_TEXT_FONT = "Noto Sans Regular";
1831
2192
  function placesFromGeoJSON(collection) {
@@ -2464,8 +2825,9 @@ function shortestArcDelta(from, to) {
2464
2825
  }
2465
2826
  var PoseSampler = class {
2466
2827
  constructor(coordinates) {
2467
- this.points = coordinates.filter(
2468
- (p, i) => i === 0 || p[0] !== coordinates[i - 1][0] || p[1] !== coordinates[i - 1][1]
2828
+ const unwrapped = unwrapLngs(coordinates);
2829
+ this.points = unwrapped.filter(
2830
+ (p, i) => i === 0 || p[0] !== unwrapped[i - 1][0] || p[1] !== unwrapped[i - 1][1]
2469
2831
  );
2470
2832
  if (this.points.length < 2) {
2471
2833
  throw new Error(
@@ -2576,6 +2938,7 @@ function flythrough(map, route, options = {}) {
2576
2938
  seek(to) {
2577
2939
  if (destroyed) return;
2578
2940
  t = Math.min(1, Math.max(0, to));
2941
+ bearing = void 0;
2579
2942
  applyPose(1 / 60);
2580
2943
  },
2581
2944
  get speed() {
@@ -2748,6 +3111,7 @@ var IsochroneLayer = class {
2748
3111
  this.fillOpacity()
2749
3112
  );
2750
3113
  this.map.setPaintProperty(this.lineLayerId, "line-color", this.lastColor);
3114
+ this.map.setPaintProperty(this.labelLayerId, "text-color", this.lastColor);
2751
3115
  return;
2752
3116
  }
2753
3117
  const fill2 = {
@@ -3138,6 +3502,6 @@ function clampVolume(volume) {
3138
3502
  return Math.min(1, Math.max(0, volume));
3139
3503
  }
3140
3504
 
3141
- export { AdrCheck, DEFAULT_GLYPHS_URL, DEFAULT_TERRITORY_TILES_URL, EFFECTS_METADATA_KEY, FLOW_DEFAULTS, FULL_ATTRIBUTION, FlowRouteEffectLayer, GuidanceBanner, IsochroneLayer, LOGO_SVG, LogoControl, MAX_PUCK_IMAGE_BYTES, MapMapMap, NAV_CAMERA_DEFAULTS, 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, VoiceGuidance, applyPoiDesign, bannerLanes, bearingBetween, bindFlythroughToScroll, buildAdrCheckBody, buildRouteQuery, buildRouteUrl, buildStyle, builtInPoiColor, createMap, createRouteEffect, defaultNavDesign, defaultPoiDesign, directionArrow, effectsFromStyleMetadata, extractGuidance, flythrough, flythroughPose, formatCoord, formatCoords, haversineDistanceM, lngLatToMercator, navDesignFromTheme, navDesignFromThemeUrl, parseCssColour, parseNavDesign, parseOsrmRoute, parsePoiDesign, placesFromGeoJSON, poiDesignFromTheme, poiDesignFromThemeUrl, poiDesignIsDefault, poiTextColorExpression, prefersReducedMotion, registerPmtilesProtocol, resetDiagnostics, runMapDiagnostics, severityProsody, shortestArcDelta, speak, ssmlToText, tessellateRouteRibbon, toLngLat, toPmtilesUrl };
3505
+ export { AdrCheck, DEFAULT_GLYPHS_URL, DEFAULT_TERRITORY_TILES_URL, EFFECTS_METADATA_KEY, FLOW_DEFAULTS, FULL_ATTRIBUTION, FlowRouteEffectLayer, GuidanceBanner, IsochroneLayer, LOGO_SVG, LogoControl, MAX_PUCK_IMAGE_BYTES, MapMapMap, NAV_CAMERA_DEFAULTS, 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, applyPoiDesign, bannerLanes, bearingBetween, bindFlythroughToScroll, buildAdrCheckBody, buildProbeUrl, buildRouteQuery, buildRouteUrl, buildStyle, builtInPoiColor, createMap, createRouteEffect, defaultNavDesign, defaultPoiDesign, directionArrow, effectsFromStyleMetadata, extractGuidance, flythrough, flythroughPose, formatCoord, formatCoords, haversineDistanceM, isNameTextField, languageTextField, lngLatToMercator, navDesignFromTheme, navDesignFromThemeUrl, parseCssColour, parseNavDesign, parseOsrmRoute, parsePoiDesign, placesFromGeoJSON, poiDesignFromTheme, poiDesignFromThemeUrl, poiDesignIsDefault, poiTextColorExpression, prefersReducedMotion, registerPmtilesProtocol, resetDiagnostics, resolveTheme, runMapDiagnostics, setMapLanguage, severityProsody, shortestArcDeg, shortestArcDelta, speak, ssmlToText, sunTimes, tessellateRouteRibbon, toLngLat, toPmtilesUrl, uploadProbeBatch };
3142
3506
  //# sourceMappingURL=index.js.map
3143
3507
  //# sourceMappingURL=index.js.map