@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/LICENSE +3 -3
- package/README.md +1 -1
- package/dist/index.d.ts +233 -3
- package/dist/index.js +408 -44
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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 ||
|
|
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.
|
|
1770
|
-
|
|
1771
|
-
|
|
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
|
-
|
|
2468
|
-
|
|
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
|