@geolibre/core 2.8.0 → 3.0.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.d.mts +1442 -566
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1457 -19
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -169,6 +169,9 @@ const DEFAULT_LAYER_STYLE = {
|
|
|
169
169
|
rotation: 0,
|
|
170
170
|
maxWidth: 10,
|
|
171
171
|
transform: "none",
|
|
172
|
+
numberFormatEnabled: false,
|
|
173
|
+
numberDecimals: 0,
|
|
174
|
+
numberLocale: "",
|
|
172
175
|
dedupe: "off",
|
|
173
176
|
sizeExpression: "",
|
|
174
177
|
colorExpression: "",
|
|
@@ -227,6 +230,8 @@ const DEFAULT_LAYER_STYLE = {
|
|
|
227
230
|
pointRenderer: "single",
|
|
228
231
|
heatmapRadius: 30,
|
|
229
232
|
heatmapIntensity: 1,
|
|
233
|
+
heatmapColorRamp: "turbo",
|
|
234
|
+
heatmapWeightProperty: "",
|
|
230
235
|
clusterRadius: 50,
|
|
231
236
|
clusterMaxZoom: 14,
|
|
232
237
|
invertedFillEnabled: false,
|
|
@@ -306,6 +311,12 @@ const NETCDF_IMAGE_SOURCE_KIND = "netcdf-image";
|
|
|
306
311
|
function isDuckDBQueryLayer(layer) {
|
|
307
312
|
return layer?.type === "duckdb-query" && layer.metadata.sourceKind === "duckdb-query" && layer.metadata.externalDeckLayer === true;
|
|
308
313
|
}
|
|
314
|
+
/**
|
|
315
|
+
* The engine that draws the primary map area when a project says nothing. The
|
|
316
|
+
* 2D map: it is the renderer every tool, plugin, and panel is wired to, so an
|
|
317
|
+
* existing project (and a new one) opens exactly as it always did.
|
|
318
|
+
*/
|
|
319
|
+
const DEFAULT_PRIMARY_RENDERER = "maplibre";
|
|
309
320
|
/** The default single-map grid (one pane, sync enabled so it turns on cleanly). */
|
|
310
321
|
const DEFAULT_MAP_GRID_LAYOUT = {
|
|
311
322
|
rows: 1,
|
|
@@ -331,6 +342,7 @@ const DEFAULT_PROJECT_PREFERENCES = {
|
|
|
331
342
|
ellipsoidId: "earth",
|
|
332
343
|
scaleUnit: "metric",
|
|
333
344
|
showPointerElevation: false,
|
|
345
|
+
terrainEnabled: false,
|
|
334
346
|
coordinateFormat: "dd"
|
|
335
347
|
},
|
|
336
348
|
environmentVariables: [],
|
|
@@ -1400,14 +1412,393 @@ function isRegionalBasemapSentinel(styleUrl) {
|
|
|
1400
1412
|
return Boolean(styleUrl?.startsWith(REGIONAL_BASEMAP_SENTINEL_PREFIX));
|
|
1401
1413
|
}
|
|
1402
1414
|
//#endregion
|
|
1415
|
+
//#region src/cesium-imagery.ts
|
|
1416
|
+
/**
|
|
1417
|
+
* Maps the project's basemap onto imagery the Cesium globe can render.
|
|
1418
|
+
*
|
|
1419
|
+
* The 2D panes render `basemapStyleUrl` through MapLibre, which understands
|
|
1420
|
+
* vector GL styles and GeoLibre's `geolibre://` sentinels. Cesium understands
|
|
1421
|
+
* neither: it draws imagery from tile providers only. So a globe pane that
|
|
1422
|
+
* simply mounted the store basemap would show nothing, which is why the pane
|
|
1423
|
+
* used to hardcode Ion World Imagery (with a token) or OpenStreetMap (without)
|
|
1424
|
+
* and ignore the user's choice entirely — leaving a bright globe beside dark 2D
|
|
1425
|
+
* panes and breaking the "same map, different projection" promise the multi-map
|
|
1426
|
+
* grid makes.
|
|
1427
|
+
*
|
|
1428
|
+
* This module is the translation. It is deliberately **engine-free** — it
|
|
1429
|
+
* returns a plain descriptor and never imports Cesium — so it lives in
|
|
1430
|
+
* `@geolibre/core` beside the basemap catalogs it reads, the 2D and 3D
|
|
1431
|
+
* renderers share one source of truth, and the Cesium chunk boundary in
|
|
1432
|
+
* `vite.config.ts` stays intact.
|
|
1433
|
+
*
|
|
1434
|
+
* Three outcomes are possible, and the caller has to handle all three (see
|
|
1435
|
+
* {@link CesiumBasemapImagery}): tiles to draw, nothing to draw, or "no raster
|
|
1436
|
+
* equivalent exists — pick your own default".
|
|
1437
|
+
*/
|
|
1438
|
+
/** Stable project IDs and the corresponding Cesium bundled thumbnails / ion assets. */
|
|
1439
|
+
const CESIUM_BASEMAPS = [
|
|
1440
|
+
{
|
|
1441
|
+
id: "project",
|
|
1442
|
+
name: "Project basemap",
|
|
1443
|
+
icon: "naturalEarthII.png"
|
|
1444
|
+
},
|
|
1445
|
+
{
|
|
1446
|
+
id: "natural-earth",
|
|
1447
|
+
name: "Natural Earth II",
|
|
1448
|
+
icon: "naturalEarthII.png"
|
|
1449
|
+
},
|
|
1450
|
+
{
|
|
1451
|
+
id: "bing-aerial",
|
|
1452
|
+
name: "Bing Maps Aerial",
|
|
1453
|
+
icon: "bingAerial.png",
|
|
1454
|
+
assetId: 2
|
|
1455
|
+
},
|
|
1456
|
+
{
|
|
1457
|
+
id: "bing-labels",
|
|
1458
|
+
name: "Bing Maps Aerial with Labels",
|
|
1459
|
+
icon: "bingAerialLabels.png",
|
|
1460
|
+
assetId: 3
|
|
1461
|
+
},
|
|
1462
|
+
{
|
|
1463
|
+
id: "bing-roads",
|
|
1464
|
+
name: "Bing Maps Roads",
|
|
1465
|
+
icon: "bingRoads.png",
|
|
1466
|
+
assetId: 4
|
|
1467
|
+
},
|
|
1468
|
+
{
|
|
1469
|
+
id: "sentinel-2",
|
|
1470
|
+
name: "Sentinel-2",
|
|
1471
|
+
icon: "sentinel-2.png",
|
|
1472
|
+
assetId: 3954
|
|
1473
|
+
},
|
|
1474
|
+
{
|
|
1475
|
+
id: "blue-marble",
|
|
1476
|
+
name: "Blue Marble",
|
|
1477
|
+
icon: "blueMarble.png",
|
|
1478
|
+
assetId: 3845
|
|
1479
|
+
},
|
|
1480
|
+
{
|
|
1481
|
+
id: "earth-at-night",
|
|
1482
|
+
name: "Earth at night",
|
|
1483
|
+
icon: "earthAtNight.png",
|
|
1484
|
+
assetId: 3812
|
|
1485
|
+
},
|
|
1486
|
+
{
|
|
1487
|
+
id: "google-satellite",
|
|
1488
|
+
name: "Google Maps Satellite",
|
|
1489
|
+
icon: "googleSatellite.png",
|
|
1490
|
+
assetId: 3830182
|
|
1491
|
+
},
|
|
1492
|
+
{
|
|
1493
|
+
id: "google-labels",
|
|
1494
|
+
name: "Google Maps Satellite with Labels",
|
|
1495
|
+
icon: "googleSatelliteLabels.png",
|
|
1496
|
+
assetId: 3830183
|
|
1497
|
+
},
|
|
1498
|
+
{
|
|
1499
|
+
id: "google-roads",
|
|
1500
|
+
name: "Google Maps Roadmap",
|
|
1501
|
+
icon: "googleRoadmap.png",
|
|
1502
|
+
assetId: 3830184
|
|
1503
|
+
},
|
|
1504
|
+
{
|
|
1505
|
+
id: "google-contour",
|
|
1506
|
+
name: "Google Maps Contour",
|
|
1507
|
+
icon: "googleContour.png",
|
|
1508
|
+
assetId: 3830186
|
|
1509
|
+
},
|
|
1510
|
+
{
|
|
1511
|
+
id: "azure-aerial",
|
|
1512
|
+
name: "Azure Maps Aerial",
|
|
1513
|
+
icon: "azureAerial.png",
|
|
1514
|
+
assetId: 3891168
|
|
1515
|
+
},
|
|
1516
|
+
{
|
|
1517
|
+
id: "azure-roads",
|
|
1518
|
+
name: "Azure Maps Roads",
|
|
1519
|
+
icon: "azureRoads.png",
|
|
1520
|
+
assetId: 3891169
|
|
1521
|
+
},
|
|
1522
|
+
{
|
|
1523
|
+
id: "esri-imagery",
|
|
1524
|
+
name: "ArcGIS World Imagery",
|
|
1525
|
+
icon: "ArcGisMapServiceWorldImagery.png",
|
|
1526
|
+
category: "Other",
|
|
1527
|
+
service: "World_Imagery"
|
|
1528
|
+
},
|
|
1529
|
+
{
|
|
1530
|
+
id: "esri-hillshade",
|
|
1531
|
+
name: "ArcGIS World Hillshade",
|
|
1532
|
+
icon: "ArcGisMapServiceWorldHillshade.png",
|
|
1533
|
+
category: "Other",
|
|
1534
|
+
service: "Elevation/World_Hillshade"
|
|
1535
|
+
},
|
|
1536
|
+
{
|
|
1537
|
+
id: "esri-ocean",
|
|
1538
|
+
name: "Esri World Ocean",
|
|
1539
|
+
icon: "ArcGisMapServiceWorldOcean.png",
|
|
1540
|
+
category: "Other",
|
|
1541
|
+
service: "Ocean/World_Ocean_Base"
|
|
1542
|
+
},
|
|
1543
|
+
{
|
|
1544
|
+
id: "osm",
|
|
1545
|
+
name: "OpenStreetMap",
|
|
1546
|
+
icon: "openStreetMap.png",
|
|
1547
|
+
category: "Other"
|
|
1548
|
+
},
|
|
1549
|
+
{
|
|
1550
|
+
id: "stadia-watercolor",
|
|
1551
|
+
name: "Stadia x Stamen Watercolor",
|
|
1552
|
+
icon: "stamenWatercolor.png",
|
|
1553
|
+
category: "Other",
|
|
1554
|
+
stadiaStyle: "stamen_watercolor",
|
|
1555
|
+
maximumLevel: 16,
|
|
1556
|
+
extension: "jpg"
|
|
1557
|
+
},
|
|
1558
|
+
{
|
|
1559
|
+
id: "stadia-toner",
|
|
1560
|
+
name: "Stadia x Stamen Toner",
|
|
1561
|
+
icon: "stamenToner.png",
|
|
1562
|
+
category: "Other",
|
|
1563
|
+
stadiaStyle: "stamen_toner",
|
|
1564
|
+
maximumLevel: 20,
|
|
1565
|
+
extension: "png"
|
|
1566
|
+
},
|
|
1567
|
+
{
|
|
1568
|
+
id: "stadia-smooth",
|
|
1569
|
+
name: "Stadia Alidade Smooth",
|
|
1570
|
+
icon: "stadiaAlidadeSmooth.png",
|
|
1571
|
+
category: "Other",
|
|
1572
|
+
stadiaStyle: "alidade_smooth",
|
|
1573
|
+
maximumLevel: 20,
|
|
1574
|
+
extension: "png"
|
|
1575
|
+
},
|
|
1576
|
+
{
|
|
1577
|
+
id: "stadia-dark",
|
|
1578
|
+
name: "Stadia Alidade Smooth Dark",
|
|
1579
|
+
icon: "stadiaAlidadeSmoothDark.png",
|
|
1580
|
+
category: "Other",
|
|
1581
|
+
stadiaStyle: "alidade_smooth_dark",
|
|
1582
|
+
maximumLevel: 20,
|
|
1583
|
+
extension: "png"
|
|
1584
|
+
}
|
|
1585
|
+
];
|
|
1586
|
+
function normalizeCesiumBasemap(value) {
|
|
1587
|
+
return CESIUM_BASEMAPS.find((entry) => entry.id === value)?.id ?? "project";
|
|
1588
|
+
}
|
|
1589
|
+
/** Missing credentials fall back to the project background without changing the saved choice. */
|
|
1590
|
+
function availableCesiumBasemap(value, hasIonToken) {
|
|
1591
|
+
const entry = CESIUM_BASEMAPS.find((entry) => entry.id === value);
|
|
1592
|
+
return entry && (!("assetId" in entry) || hasIonToken) ? entry.id : "project";
|
|
1593
|
+
}
|
|
1594
|
+
const OSM_CREDIT = "© <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors";
|
|
1595
|
+
/** EOX Maps serves these layers keyless under CC BY-SA 4.0. */
|
|
1596
|
+
const EOX_CREDIT = "<a href=\"https://maps.eox.at\">EOX Maps</a> (CC BY-SA 4.0)";
|
|
1597
|
+
const eoxLayer = (layer, extension) => `https://tiles.maps.eox.at/wmts/1.0.0/${layer}/default/g/{z}/{y}/{x}.${extension}`;
|
|
1598
|
+
/** Colourful streets — the tone of Liberty and Bright. */
|
|
1599
|
+
const STREETS = {
|
|
1600
|
+
template: "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
|
|
1601
|
+
attribution: OSM_CREDIT,
|
|
1602
|
+
maximumLevel: 19
|
|
1603
|
+
};
|
|
1604
|
+
/** Light and muted — the tone of Positron and the pale Protomaps flavors. */
|
|
1605
|
+
const LIGHT = {
|
|
1606
|
+
template: eoxLayer("terrain-light_3857", "jpg"),
|
|
1607
|
+
attribution: `Terrain Light · ${OSM_CREDIT} · ${EOX_CREDIT}`,
|
|
1608
|
+
maximumLevel: 16
|
|
1609
|
+
};
|
|
1610
|
+
/**
|
|
1611
|
+
* Dark — the tone of Dark, Fiord, and the dark Protomaps flavors. NASA's Black
|
|
1612
|
+
* Marble night lights is the one keyless dark raster basemap left, and it suits
|
|
1613
|
+
* a globe: the point of matching here is that a dark project does not end up
|
|
1614
|
+
* with a glaring bright globe in the corner.
|
|
1615
|
+
*/
|
|
1616
|
+
const DARK = {
|
|
1617
|
+
template: eoxLayer("blackmarble_3857", "jpg"),
|
|
1618
|
+
attribution: `Black Marble © NASA Earth Observatory · ${EOX_CREDIT}`,
|
|
1619
|
+
maximumLevel: 8
|
|
1620
|
+
};
|
|
1621
|
+
/** Raster analogue per OpenFreeMap style name (the last path segment). */
|
|
1622
|
+
const OPENFREEMAP_ANALOGUES = {
|
|
1623
|
+
liberty: STREETS,
|
|
1624
|
+
bright: STREETS,
|
|
1625
|
+
positron: LIGHT,
|
|
1626
|
+
dark: DARK,
|
|
1627
|
+
fiord: DARK
|
|
1628
|
+
};
|
|
1629
|
+
/** Raster analogue per Protomaps v5 flavor. */
|
|
1630
|
+
const PROTOMAPS_ANALOGUES = {
|
|
1631
|
+
light: LIGHT,
|
|
1632
|
+
white: LIGHT,
|
|
1633
|
+
grayscale: LIGHT,
|
|
1634
|
+
dark: DARK,
|
|
1635
|
+
black: DARK
|
|
1636
|
+
};
|
|
1637
|
+
function toImagery(analogue) {
|
|
1638
|
+
return {
|
|
1639
|
+
kind: "xyz",
|
|
1640
|
+
template: analogue.template,
|
|
1641
|
+
attribution: analogue.attribution,
|
|
1642
|
+
maximumLevel: analogue.maximumLevel
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
/**
|
|
1646
|
+
* Whether two descriptors would draw the same thing.
|
|
1647
|
+
*
|
|
1648
|
+
* Descriptors are built fresh on every call, so two basemaps resolving to the
|
|
1649
|
+
* same analogue — OpenFreeMap `liberty` and `bright` are both the streets
|
|
1650
|
+
* tone — yield equal but distinct objects. A renderer that remembers the
|
|
1651
|
+
* descriptor it drew (to skip redundant work) must compare by value: a
|
|
1652
|
+
* reference check would miss that case and tear down and re-add an identical
|
|
1653
|
+
* tile provider, costing a flash and a round of re-requested tiles for no
|
|
1654
|
+
* visible change.
|
|
1655
|
+
*/
|
|
1656
|
+
function sameCesiumImagery(a, b) {
|
|
1657
|
+
if (a.kind !== b.kind) return false;
|
|
1658
|
+
if (a.kind === "arcgis" && b.kind === "arcgis") return a.url === b.url;
|
|
1659
|
+
if (a.kind === "ion" && b.kind === "ion") return a.assetId === b.assetId;
|
|
1660
|
+
if (a.kind !== "xyz" || b.kind !== "xyz") return true;
|
|
1661
|
+
return a.template === b.template && a.apiKeyProvider === b.apiKeyProvider && a.attribution === b.attribution && a.maximumLevel === b.maximumLevel && a.scheme === b.scheme && a.overlayTemplate === b.overlayTemplate;
|
|
1662
|
+
}
|
|
1663
|
+
/**
|
|
1664
|
+
* `decodeURIComponent` that reports failure instead of throwing. A URL keeps a
|
|
1665
|
+
* malformed escape (`%E0%A4`) in its pathname quite happily, but decoding one
|
|
1666
|
+
* throws `URIError` — and this runs inside a React render, so an unhandled throw
|
|
1667
|
+
* would take the globe pane down instead of falling through to the default.
|
|
1668
|
+
*/
|
|
1669
|
+
function safeDecode(value) {
|
|
1670
|
+
try {
|
|
1671
|
+
return decodeURIComponent(value);
|
|
1672
|
+
} catch {
|
|
1673
|
+
return;
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
/**
|
|
1677
|
+
* The raster analogue for a vector GL style URL, or undefined when the style is
|
|
1678
|
+
* not one GeoLibre ships (a provider style, a user's own URL, a Mapbox style).
|
|
1679
|
+
* Matching on host + path rather than searching the URL for "dark"/"light"
|
|
1680
|
+
* keeps an unrelated style whose *key* happens to contain one of those words
|
|
1681
|
+
* from being mapped to the wrong tone.
|
|
1682
|
+
*/
|
|
1683
|
+
function vectorStyleAnalogue(styleUrl) {
|
|
1684
|
+
let parsed;
|
|
1685
|
+
try {
|
|
1686
|
+
parsed = new URL(styleUrl);
|
|
1687
|
+
} catch {
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
if (parsed.hostname === "tiles.openfreemap.org") {
|
|
1691
|
+
const name = parsed.pathname.split("/").filter(Boolean).at(-1);
|
|
1692
|
+
return name ? OPENFREEMAP_ANALOGUES[name] : void 0;
|
|
1693
|
+
}
|
|
1694
|
+
if (parsed.hostname === "api.protomaps.com") {
|
|
1695
|
+
const segments = parsed.pathname.split("/").filter(Boolean);
|
|
1696
|
+
const flavor = segments[0] === "styles" ? segments[2] : void 0;
|
|
1697
|
+
const decoded = flavor ? safeDecode(flavor) : void 0;
|
|
1698
|
+
return decoded ? PROTOMAPS_ANALOGUES[decoded] : void 0;
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
/**
|
|
1702
|
+
* Translate a basemap style URL into imagery for the Cesium globe.
|
|
1703
|
+
*
|
|
1704
|
+
* Planetary and regional basemaps are already raster tile sets, so they carry
|
|
1705
|
+
* straight over — the globe shows the same Mars/Moon/Amap tiles the 2D panes
|
|
1706
|
+
* do. GeoLibre's own vector styles map to a keyless raster basemap of matching
|
|
1707
|
+
* tone, so switching the project to a dark basemap darkens the globe too.
|
|
1708
|
+
* Everything else reports `default`, leaving the choice of fallback imagery to
|
|
1709
|
+
* the renderer (which knows whether an Ion token is configured).
|
|
1710
|
+
*
|
|
1711
|
+
* A `geolibre://` sentinel that no longer resolves is treated as the default
|
|
1712
|
+
* basemap, mirroring what `resolveMapStyle` does for the 2D map rather than
|
|
1713
|
+
* handing the sentinel onward as if it were fetchable.
|
|
1714
|
+
*
|
|
1715
|
+
* @param styleUrl - The project's `basemapStyleUrl`.
|
|
1716
|
+
* @returns What the globe should draw beneath the data layers.
|
|
1717
|
+
*/
|
|
1718
|
+
function basemapToCesiumImagery(styleUrl, cesiumBasemap = "project") {
|
|
1719
|
+
const entry = CESIUM_BASEMAPS.find((entry) => entry.id === cesiumBasemap);
|
|
1720
|
+
if (entry && "service" in entry) return {
|
|
1721
|
+
kind: "arcgis",
|
|
1722
|
+
url: `https://services.arcgisonline.com/ArcGIS/rest/services/${entry.service}/MapServer`
|
|
1723
|
+
};
|
|
1724
|
+
if (entry && "stadiaStyle" in entry) return {
|
|
1725
|
+
kind: "xyz",
|
|
1726
|
+
template: `https://tiles.stadiamaps.com/tiles/${entry.stadiaStyle}/{z}/{x}/{y}.${entry.extension}`,
|
|
1727
|
+
maximumLevel: entry.maximumLevel,
|
|
1728
|
+
apiKeyProvider: "stadia",
|
|
1729
|
+
attribution: "<a href=\"https://stadiamaps.com/\">© Stadia Maps</a> " + (entry.stadiaStyle.startsWith("stamen_") ? "<a href=\"https://stamen.com/\">© Stamen Design</a> " : "") + "<a href=\"https://openmaptiles.org/\">© OpenMapTiles</a> <a href=\"https://www.openstreetmap.org/copyright\">© OpenStreetMap contributors</a>"
|
|
1730
|
+
};
|
|
1731
|
+
if (entry && "assetId" in entry) return {
|
|
1732
|
+
kind: "ion",
|
|
1733
|
+
assetId: entry.assetId
|
|
1734
|
+
};
|
|
1735
|
+
if (cesiumBasemap === "natural-earth") return { kind: "natural-earth" };
|
|
1736
|
+
if (cesiumBasemap === "osm") return toImagery(STREETS);
|
|
1737
|
+
if (styleUrl === "") return { kind: "none" };
|
|
1738
|
+
const url = styleUrl ?? "https://tiles.openfreemap.org/styles/liberty";
|
|
1739
|
+
const planetary = getPlanetaryBasemapByStyleUrl(url);
|
|
1740
|
+
if (planetary) return {
|
|
1741
|
+
kind: "xyz",
|
|
1742
|
+
template: planetary.tileUrl,
|
|
1743
|
+
attribution: planetary.attribution,
|
|
1744
|
+
maximumLevel: planetary.maxZoom,
|
|
1745
|
+
...planetary.scheme ? { scheme: planetary.scheme } : {}
|
|
1746
|
+
};
|
|
1747
|
+
const regional = getRegionalBasemapByStyleUrl(url);
|
|
1748
|
+
if (regional) return {
|
|
1749
|
+
kind: "xyz",
|
|
1750
|
+
template: regional.tileUrl,
|
|
1751
|
+
attribution: regional.attribution,
|
|
1752
|
+
maximumLevel: regional.maxZoom,
|
|
1753
|
+
...regional.scheme ? { scheme: regional.scheme } : {},
|
|
1754
|
+
...regional.overlayTileUrl ? { overlayTemplate: regional.overlayTileUrl } : {}
|
|
1755
|
+
};
|
|
1756
|
+
const analogue = vectorStyleAnalogue(url.startsWith("geolibre://") ? DEFAULT_BASEMAP : url);
|
|
1757
|
+
return analogue ? toImagery(analogue) : { kind: "default" };
|
|
1758
|
+
}
|
|
1759
|
+
//#endregion
|
|
1403
1760
|
//#region src/geojson-z.ts
|
|
1404
1761
|
/**
|
|
1405
1762
|
* Helpers for vector data whose coordinates carry a Z value (elevation), e.g.
|
|
1406
1763
|
* GPX tracks with `<ele>` readings or LineStringZ/PointZ geometries. MapLibre's
|
|
1407
1764
|
* 2D style layers ignore the third coordinate, so layers that want to render
|
|
1408
1765
|
* their Z values use these helpers to detect and rescale elevations before
|
|
1409
|
-
* handing the data to a deck.gl overlay.
|
|
1410
|
-
|
|
1766
|
+
* handing the data to a deck.gl overlay. The same data can carry its elevation
|
|
1767
|
+
* in a `bbox` member too, which `horizontalBbox` trims back to two dimensions.
|
|
1768
|
+
*/
|
|
1769
|
+
/**
|
|
1770
|
+
* Reduces a bounding box to its horizontal extent, `[west, south, east, north]`.
|
|
1771
|
+
*
|
|
1772
|
+
* RFC 7946 §5 lets a `bbox` member carry elevation, in which case it holds six
|
|
1773
|
+
* values — `[west, south, minAltitude, east, north, maxAltitude]` — and
|
|
1774
|
+
* `@turf/bbox` returns a collection's own member verbatim rather than
|
|
1775
|
+
* recomputing it. A consumer reading such a box as four values takes the
|
|
1776
|
+
* altitude for a longitude and the east edge for a latitude; that is how a 3D
|
|
1777
|
+
* feed reached MapLibre as an out-of-range latitude (#2358).
|
|
1778
|
+
*
|
|
1779
|
+
* Returns null when there is no usable horizontal extent: a box of any other
|
|
1780
|
+
* length, or one carrying a non-finite value anywhere — an empty collection, or
|
|
1781
|
+
* one whose features all have a null geometry, bboxes to ±Infinity, and a box
|
|
1782
|
+
* whose altitudes are not finite is not one to trust the rest of.
|
|
1783
|
+
*
|
|
1784
|
+
* @param box - A GeoJSON or Turf bounding box, of four or six values.
|
|
1785
|
+
*/
|
|
1786
|
+
function horizontalBbox(box) {
|
|
1787
|
+
if (!box) return null;
|
|
1788
|
+
if (box.length !== 4 && box.length !== 6) return null;
|
|
1789
|
+
if (!box.every((value) => Number.isFinite(value))) return null;
|
|
1790
|
+
return box.length === 6 ? [
|
|
1791
|
+
box[0],
|
|
1792
|
+
box[1],
|
|
1793
|
+
box[3],
|
|
1794
|
+
box[4]
|
|
1795
|
+
] : [
|
|
1796
|
+
box[0],
|
|
1797
|
+
box[1],
|
|
1798
|
+
box[2],
|
|
1799
|
+
box[3]
|
|
1800
|
+
];
|
|
1801
|
+
}
|
|
1411
1802
|
const hasZCache = /* @__PURE__ */ new WeakMap();
|
|
1412
1803
|
/**
|
|
1413
1804
|
* Returns true when any coordinate in the GeoJSON carries a finite, non-zero
|
|
@@ -2383,13 +2774,18 @@ function getMapboxAccessToken(env) {
|
|
|
2383
2774
|
/**
|
|
2384
2775
|
* Resolves the Cesium Ion access token from the runtime environment.
|
|
2385
2776
|
*
|
|
2386
|
-
*
|
|
2777
|
+
* Cesium World Terrain and Ion World Imagery need a Cesium Ion token. It is
|
|
2387
2778
|
* supplied via `VITE_CESIUM_TOKEN` (baked in at build time from the bare
|
|
2388
2779
|
* `CESIUM_TOKEN` env var, which `vite.config.ts` copies into the prefixed name)
|
|
2389
2780
|
* or set at runtime through Settings → Environment variables
|
|
2390
2781
|
* (`window.__GEOLIBRE_RUNTIME_ENV__`, which bypasses Vite's envPrefix allowlist,
|
|
2391
|
-
* so a bare `CESIUM_TOKEN` entry works there too).
|
|
2392
|
-
*
|
|
2782
|
+
* so a bare `CESIUM_TOKEN` entry works there too).
|
|
2783
|
+
*
|
|
2784
|
+
* The 3D-globe view itself does **not** need one: it draws the project basemap
|
|
2785
|
+
* as its base imagery whenever that basemap has a raster form (see
|
|
2786
|
+
* {@link basemapToCesiumImagery}), and falls back to Ion World Imagery — or, with
|
|
2787
|
+
* no token, to OpenStreetMap — for the ones that do not. So an absent token
|
|
2788
|
+
* costs terrain and the quality of that fallback, not the view.
|
|
2393
2789
|
*
|
|
2394
2790
|
* @param env - Environment record (defaults to the runtime environment);
|
|
2395
2791
|
* injectable for testing.
|
|
@@ -2842,7 +3238,8 @@ const EXPRESSION_FUNCTION_CATEGORIES = [
|
|
|
2842
3238
|
fn("concat", "[\"concat\", [\"get\", \"field\"], \" suffix\"]", "concat"),
|
|
2843
3239
|
fn("upcase", "[\"upcase\", [\"get\", \"field\"]]", "upcase"),
|
|
2844
3240
|
fn("downcase", "[\"downcase\", [\"get\", \"field\"]]", "downcase"),
|
|
2845
|
-
fn("slice", "[\"slice\", [\"get\", \"field\"], 0, 3]", "slice")
|
|
3241
|
+
fn("slice", "[\"slice\", [\"get\", \"field\"], 0, 3]", "slice"),
|
|
3242
|
+
fn("number-format", "[\"number-format\", [\"get\", \"field\"], {\"locale\": \"en-US\", \"max-fraction-digits\": 2}]", "numberFormat")
|
|
2846
3243
|
]
|
|
2847
3244
|
},
|
|
2848
3245
|
{
|
|
@@ -3060,11 +3457,11 @@ function compileFeatureExpression(source, options = {}) {
|
|
|
3060
3457
|
ok: false,
|
|
3061
3458
|
errors: validation.errors
|
|
3062
3459
|
};
|
|
3063
|
-
const
|
|
3460
|
+
const compiledZoom = options.zoom ?? 0;
|
|
3064
3461
|
return {
|
|
3065
3462
|
ok: true,
|
|
3066
3463
|
errors: [],
|
|
3067
|
-
evaluate: (feature) => expression.evaluateWithoutErrorHandling({ zoom }, {
|
|
3464
|
+
evaluate: (feature, zoom = compiledZoom) => expression.evaluateWithoutErrorHandling({ zoom }, {
|
|
3068
3465
|
type: feature.geometry?.type ?? "Unknown",
|
|
3069
3466
|
properties: feature.properties ?? {},
|
|
3070
3467
|
...feature.id !== void 0 ? { id: feature.id } : {},
|
|
@@ -3698,6 +4095,23 @@ function vectorFillOpacityValue(style, fallback) {
|
|
|
3698
4095
|
return ruleOverrideValue(style, "fillOpacity", fallback);
|
|
3699
4096
|
}
|
|
3700
4097
|
/**
|
|
4098
|
+
* The color-ramp colors a density heatmap paints with.
|
|
4099
|
+
*
|
|
4100
|
+
* {@link getVectorColorRamp} falls back to the *first* ramp ("viridis") for an
|
|
4101
|
+
* unknown name, which is not the heatmap default ("turbo"). Both the map paint
|
|
4102
|
+
* (`heatmapPaint` in `@geolibre/map`) and the auto-legend gradient resolve the
|
|
4103
|
+
* ramp here so a stale or hand-edited `heatmapColorRamp` cannot make the legend
|
|
4104
|
+
* and the rendered heatmap disagree about which ramp is in use.
|
|
4105
|
+
*
|
|
4106
|
+
* @param style - The layer style.
|
|
4107
|
+
* @returns The ramp's colors, or the default ramp's colors for an unknown name.
|
|
4108
|
+
*/
|
|
4109
|
+
function heatmapRampColors(style) {
|
|
4110
|
+
const value = styleValue(style, "heatmapColorRamp");
|
|
4111
|
+
const ramp = getVectorColorRamp(value);
|
|
4112
|
+
return (ramp.value === value ? ramp : getVectorColorRamp(DEFAULT_LAYER_STYLE.heatmapColorRamp)).colors;
|
|
4113
|
+
}
|
|
4114
|
+
/**
|
|
3701
4115
|
* The proportional-size configuration, or `null` when proportional sizing
|
|
3702
4116
|
* does not apply: disabled, no field chosen, non-finite or degenerate
|
|
3703
4117
|
* (`maxValue <= minValue`) value range, or non-finite radii. This is the
|
|
@@ -3937,6 +4351,236 @@ function extrusionColorValue(style) {
|
|
|
3937
4351
|
return parseJsonExpression(styleValue(style, "extrusionAdvancedStyleEnabled") ? styleValue(style, "extrusionColorExpression") : "") ?? flat;
|
|
3938
4352
|
}
|
|
3939
4353
|
//#endregion
|
|
4354
|
+
//#region src/document-locale.ts
|
|
4355
|
+
/**
|
|
4356
|
+
* The document language, so formatted numbers and dates follow the UI locale.
|
|
4357
|
+
*
|
|
4358
|
+
* Shared by the Identify popup's value formatting and by label number
|
|
4359
|
+
* formatting, which both fall back to it when the author has not pinned an
|
|
4360
|
+
* explicit locale. Returns `undefined` outside a browser (tests, the headless
|
|
4361
|
+
* project authoring paths), which leaves `Intl` on its own default.
|
|
4362
|
+
*/
|
|
4363
|
+
function documentLocale() {
|
|
4364
|
+
return (typeof document !== "undefined" ? document.documentElement.lang.trim() : "") || void 0;
|
|
4365
|
+
}
|
|
4366
|
+
//#endregion
|
|
4367
|
+
//#region src/label-number-format.ts
|
|
4368
|
+
/**
|
|
4369
|
+
* Locale-aware number formatting for map labels (issue #2336).
|
|
4370
|
+
*
|
|
4371
|
+
* A numeric attribute rendered straight into a label reads as `1234567.5`,
|
|
4372
|
+
* which is hard to compare at a glance. QGIS solves this with
|
|
4373
|
+
* `format_number(field, places, language)`; the equivalent here is three
|
|
4374
|
+
* {@link LabelStyle} fields — {@link LabelStyle.numberFormatEnabled},
|
|
4375
|
+
* {@link LabelStyle.numberDecimals} and {@link LabelStyle.numberLocale} —
|
|
4376
|
+
* whose effect this module renders two ways:
|
|
4377
|
+
*
|
|
4378
|
+
* - {@link labelFieldTextField} builds the MapLibre `text-field` expression the
|
|
4379
|
+
* 2D map and the Mapbox/MapLibre style export both use, so the map and the
|
|
4380
|
+
* exported style agree.
|
|
4381
|
+
* - {@link formatLabelNumber} does the same in JavaScript for the paths that
|
|
4382
|
+
* resolve label text themselves: the Cesium globe and the duplicate-label
|
|
4383
|
+
* collapsing modes.
|
|
4384
|
+
*
|
|
4385
|
+
* Formatting applies to {@link LabelStyle.field} only. A label
|
|
4386
|
+
* {@link LabelStyle.expression} formats its own output (with `number-format`,
|
|
4387
|
+
* offered in the Expression Builder), so wrapping it here would fight the
|
|
4388
|
+
* author.
|
|
4389
|
+
*/
|
|
4390
|
+
/**
|
|
4391
|
+
* Locales offered for {@link LabelStyle.numberLocale}, alongside the default
|
|
4392
|
+
* empty value that follows the app's own language.
|
|
4393
|
+
*
|
|
4394
|
+
* Deliberately short, and every entry groups with a character the map's glyph
|
|
4395
|
+
* stack can draw: ASCII `,`/`.` or U+00A0. Locales whose CLDR grouping uses
|
|
4396
|
+
* the narrow no-break space U+202F (`fr-FR`) or non-Latin digits (`ar-EG`)
|
|
4397
|
+
* are left out because a missing glyph renders as a blank box on the map
|
|
4398
|
+
* rather than as a separator. An author who needs one of those can still
|
|
4399
|
+
* write `["number-format", …]` in the label expression.
|
|
4400
|
+
*/
|
|
4401
|
+
const LABEL_NUMBER_LOCALES = [
|
|
4402
|
+
"en-US",
|
|
4403
|
+
"de-DE",
|
|
4404
|
+
"ru-RU",
|
|
4405
|
+
"hi-IN"
|
|
4406
|
+
];
|
|
4407
|
+
/**
|
|
4408
|
+
* Magnitude at which the text-field stops treating a value as a finite number.
|
|
4409
|
+
*
|
|
4410
|
+
* JSON carries no `Infinity`, so the expression cannot test against it
|
|
4411
|
+
* directly; this is the largest power of ten a double holds, which doubles as
|
|
4412
|
+
* the `to-number` fallback so an unconvertible value fails the same test. A
|
|
4413
|
+
* real attribute at or above it labels as plain text instead of a formatted
|
|
4414
|
+
* number -- a trade worth making for values already past 1e308.
|
|
4415
|
+
*/
|
|
4416
|
+
const NON_FINITE_SENTINEL = 1e308;
|
|
4417
|
+
/** Sample value used to preview a locale's separators in the Style panel. */
|
|
4418
|
+
const LABEL_NUMBER_SAMPLE = 1234567.5;
|
|
4419
|
+
/**
|
|
4420
|
+
* Cached `Intl.NumberFormat`s, keyed by locale + decimals. `formatLabelNumber`
|
|
4421
|
+
* runs once per labeled feature (per Cesium entity, per deduplicated point), so
|
|
4422
|
+
* constructing a formatter on every call is the wrong order of magnitude.
|
|
4423
|
+
*
|
|
4424
|
+
* The key space is small: {@link LABEL_NUMBER_LOCALES} plus the app's own
|
|
4425
|
+
* language, times eleven decimal settings, times whatever a hand-edited project
|
|
4426
|
+
* pins.
|
|
4427
|
+
*/
|
|
4428
|
+
const formatterCache = /* @__PURE__ */ new Map();
|
|
4429
|
+
/** Locale tags already accepted or rejected, so each is tested once. */
|
|
4430
|
+
const localeSupport = /* @__PURE__ */ new Map();
|
|
4431
|
+
/**
|
|
4432
|
+
* A formatter for this locale/decimals pair. Grouping is always on: it is the
|
|
4433
|
+
* whole point of the setting. The locale must already have been vetted by
|
|
4434
|
+
* {@link resolveLabelNumberLocale}, so this never throws.
|
|
4435
|
+
*/
|
|
4436
|
+
function labelNumberFormatter(locale, decimals) {
|
|
4437
|
+
const digits = clampLabelDecimals(decimals);
|
|
4438
|
+
const key = `${locale ?? ""}\u0000${digits}`;
|
|
4439
|
+
const cached = formatterCache.get(key);
|
|
4440
|
+
if (cached) return cached;
|
|
4441
|
+
const formatter = new Intl.NumberFormat(locale, {
|
|
4442
|
+
useGrouping: true,
|
|
4443
|
+
minimumFractionDigits: digits,
|
|
4444
|
+
maximumFractionDigits: digits
|
|
4445
|
+
});
|
|
4446
|
+
formatterCache.set(key, formatter);
|
|
4447
|
+
return formatter;
|
|
4448
|
+
}
|
|
4449
|
+
/**
|
|
4450
|
+
* Whether every character a locale's grouping produces is one the map's glyph
|
|
4451
|
+
* stack can draw: ASCII, or the no-break space U+00A0.
|
|
4452
|
+
*
|
|
4453
|
+
* {@link LABEL_NUMBER_LOCALES} is curated on exactly this basis, but the picker
|
|
4454
|
+
* is not the only way a locale reaches the renderer. A hand-edited project can
|
|
4455
|
+
* pin any tag, and the default (`numberLocale: ""`) follows the app's own
|
|
4456
|
+
* language, so a French or Arabic UI would otherwise put U+202F or Arabic-Indic
|
|
4457
|
+
* digits into the `text-field` and label the map with blank boxes. Testing the
|
|
4458
|
+
* rendered sample rather than the tag covers both routes and needs no list to
|
|
4459
|
+
* keep in sync.
|
|
4460
|
+
*/
|
|
4461
|
+
function glyphSafeLocale(tag) {
|
|
4462
|
+
try {
|
|
4463
|
+
const format = new Intl.NumberFormat(tag, {
|
|
4464
|
+
useGrouping: true,
|
|
4465
|
+
minimumFractionDigits: 2,
|
|
4466
|
+
maximumFractionDigits: 2
|
|
4467
|
+
});
|
|
4468
|
+
for (const sample of [format.format(LABEL_NUMBER_SAMPLE), format.format(-1234567.5)]) for (const char of sample) {
|
|
4469
|
+
const code = char.codePointAt(0) ?? 0;
|
|
4470
|
+
if (code >= 128 && code !== 160) return false;
|
|
4471
|
+
}
|
|
4472
|
+
return true;
|
|
4473
|
+
} catch {
|
|
4474
|
+
return false;
|
|
4475
|
+
}
|
|
4476
|
+
}
|
|
4477
|
+
/** Clamp a decimal-places setting to the range the UI and `Intl` both accept. */
|
|
4478
|
+
function clampLabelDecimals(decimals) {
|
|
4479
|
+
if (!Number.isFinite(decimals)) return 0;
|
|
4480
|
+
return Math.max(0, Math.min(10, Math.trunc(decimals)));
|
|
4481
|
+
}
|
|
4482
|
+
/**
|
|
4483
|
+
* The first locale tag that both `Intl` accepts and the map can draw, or
|
|
4484
|
+
* `undefined` to leave `Intl` and MapLibre on the runtime default.
|
|
4485
|
+
*
|
|
4486
|
+
* Every path resolves its locale through here -- the JavaScript formatter, the
|
|
4487
|
+
* MapLibre expression, and the dedup cache key -- so all three agree on which
|
|
4488
|
+
* locale is actually in effect.
|
|
4489
|
+
*/
|
|
4490
|
+
function resolveLabelNumberLocale(...tags) {
|
|
4491
|
+
for (const tag of tags) {
|
|
4492
|
+
if (!tag) continue;
|
|
4493
|
+
let usable = localeSupport.get(tag);
|
|
4494
|
+
if (usable === void 0) {
|
|
4495
|
+
usable = glyphSafeLocale(tag);
|
|
4496
|
+
localeSupport.set(tag, usable);
|
|
4497
|
+
}
|
|
4498
|
+
if (usable) return tag;
|
|
4499
|
+
}
|
|
4500
|
+
}
|
|
4501
|
+
/**
|
|
4502
|
+
* Format one label value as a number, or `null` when the setting is off or the
|
|
4503
|
+
* value is not a finite number (a text attribute keeps its own rendering).
|
|
4504
|
+
*
|
|
4505
|
+
* @param value - The raw attribute value.
|
|
4506
|
+
* @param labels - The layer's label configuration.
|
|
4507
|
+
* @param fallbackLocale - Locale used when {@link LabelStyle.numberLocale} is
|
|
4508
|
+
* empty; the caller passes the app's language. Omitted means the runtime
|
|
4509
|
+
* default, matching how popups format their numbers.
|
|
4510
|
+
*/
|
|
4511
|
+
function formatLabelNumber(value, labels, fallbackLocale) {
|
|
4512
|
+
if (!labels.numberFormatEnabled) return null;
|
|
4513
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return null;
|
|
4514
|
+
return labelNumberFormatter(resolveLabelNumberLocale(labels.numberLocale, fallbackLocale), labels.numberDecimals).format(value);
|
|
4515
|
+
}
|
|
4516
|
+
/**
|
|
4517
|
+
* The MapLibre expression that renders {@link LabelStyle.field} as text, with
|
|
4518
|
+
* number formatting applied when it is on. Returns `""` when no field is set,
|
|
4519
|
+
* which callers treat as "no label text".
|
|
4520
|
+
*
|
|
4521
|
+
* Number formatting is guarded by a `typeof` test rather than applied blindly
|
|
4522
|
+
* so a mixed or text column still labels normally; only real numbers take the
|
|
4523
|
+
* `number-format` branch.
|
|
4524
|
+
*/
|
|
4525
|
+
function labelFieldTextField(labels, fallbackLocale) {
|
|
4526
|
+
const field = labels.field;
|
|
4527
|
+
if (!field) return "";
|
|
4528
|
+
const asText = ["to-string", [
|
|
4529
|
+
"coalesce",
|
|
4530
|
+
["get", field],
|
|
4531
|
+
""
|
|
4532
|
+
]];
|
|
4533
|
+
if (!labels.numberFormatEnabled) return asText;
|
|
4534
|
+
const locale = resolveLabelNumberLocale(labels.numberLocale, fallbackLocale);
|
|
4535
|
+
const digits = clampLabelDecimals(labels.numberDecimals);
|
|
4536
|
+
const options = {};
|
|
4537
|
+
if (locale) options.locale = locale;
|
|
4538
|
+
const numeric = [
|
|
4539
|
+
"to-number",
|
|
4540
|
+
["get", field],
|
|
4541
|
+
NON_FINITE_SENTINEL
|
|
4542
|
+
];
|
|
4543
|
+
const number = digits > 0 ? numeric : ["round", numeric];
|
|
4544
|
+
if (digits > 0) {
|
|
4545
|
+
options["min-fraction-digits"] = digits;
|
|
4546
|
+
options["max-fraction-digits"] = digits;
|
|
4547
|
+
}
|
|
4548
|
+
return [
|
|
4549
|
+
"case",
|
|
4550
|
+
[
|
|
4551
|
+
"all",
|
|
4552
|
+
[
|
|
4553
|
+
"==",
|
|
4554
|
+
["typeof", ["get", field]],
|
|
4555
|
+
"number"
|
|
4556
|
+
],
|
|
4557
|
+
[
|
|
4558
|
+
"<",
|
|
4559
|
+
["abs", numeric],
|
|
4560
|
+
NON_FINITE_SENTINEL
|
|
4561
|
+
]
|
|
4562
|
+
],
|
|
4563
|
+
[
|
|
4564
|
+
"number-format",
|
|
4565
|
+
number,
|
|
4566
|
+
options
|
|
4567
|
+
],
|
|
4568
|
+
asText
|
|
4569
|
+
];
|
|
4570
|
+
}
|
|
4571
|
+
/**
|
|
4572
|
+
* Render {@link LABEL_NUMBER_SAMPLE} the way a locale/decimals pair would, so
|
|
4573
|
+
* the Style panel can name each choice by the separators it actually produces
|
|
4574
|
+
* ("1,234,567.50") instead of by an opaque language tag.
|
|
4575
|
+
*
|
|
4576
|
+
* A tag `Intl` rejects (or one the map cannot draw) falls back to the runtime
|
|
4577
|
+
* locale's formatting rather than to an unformatted number, matching what
|
|
4578
|
+
* {@link formatLabelNumber} and the emitted expression do with the same tag.
|
|
4579
|
+
*/
|
|
4580
|
+
function formatLabelNumberSample(locale, decimals) {
|
|
4581
|
+
return labelNumberFormatter(resolveLabelNumberLocale(locale), decimals).format(LABEL_NUMBER_SAMPLE);
|
|
4582
|
+
}
|
|
4583
|
+
//#endregion
|
|
3940
4584
|
//#region src/external-native-paint.ts
|
|
3941
4585
|
/**
|
|
3942
4586
|
* Whether a layer is drawn by a **custom render layer** its control created — a
|
|
@@ -4630,6 +5274,10 @@ function valueToString(value) {
|
|
|
4630
5274
|
function layerJoinKey(value) {
|
|
4631
5275
|
return value === null || value === void 0 || typeof value === "number" && Number.isNaN(value) || valueToString(value) === "" ? null : valueToString(value);
|
|
4632
5276
|
}
|
|
5277
|
+
/** GeoJSON columns are own properties; inherited object members are not data. */
|
|
5278
|
+
function fieldValue(properties, field) {
|
|
5279
|
+
return properties && Object.hasOwn(properties, field) ? properties[field] : void 0;
|
|
5280
|
+
}
|
|
4633
5281
|
/**
|
|
4634
5282
|
* Remove every column previously added by `joins` (per their `addedFields`
|
|
4635
5283
|
* bookkeeping) from a copy of `features`, restoring the pre-join properties.
|
|
@@ -4722,7 +5370,7 @@ function applyLayerJoins(features, joins, resolveSource) {
|
|
|
4722
5370
|
}
|
|
4723
5371
|
const lookup = /* @__PURE__ */ new Map();
|
|
4724
5372
|
for (const jf of joinFeatures) {
|
|
4725
|
-
const key = layerJoinKey(jf.properties
|
|
5373
|
+
const key = layerJoinKey(fieldValue(jf.properties, join.joinField));
|
|
4726
5374
|
if (key === null || lookup.has(key)) continue;
|
|
4727
5375
|
lookup.set(key, jf.properties ?? {});
|
|
4728
5376
|
}
|
|
@@ -4730,17 +5378,17 @@ function applyLayerJoins(features, joins, resolveSource) {
|
|
|
4730
5378
|
const targetKeys = /* @__PURE__ */ new Set();
|
|
4731
5379
|
for (const feature of out) {
|
|
4732
5380
|
const props = feature.properties;
|
|
4733
|
-
const key = layerJoinKey(props
|
|
5381
|
+
const key = layerJoinKey(fieldValue(props, join.targetField));
|
|
4734
5382
|
if (key !== null) targetKeys.add(key);
|
|
4735
5383
|
const row = key === null ? void 0 : lookup.get(key);
|
|
4736
5384
|
if (row) {
|
|
4737
5385
|
matched += 1;
|
|
4738
|
-
for (const [field, outputName] of fieldPairs) props[outputName] = row
|
|
5386
|
+
for (const [field, outputName] of fieldPairs) props[outputName] = fieldValue(row, field) ?? null;
|
|
4739
5387
|
} else for (const [, outputName] of fieldPairs) props[outputName] = null;
|
|
4740
5388
|
}
|
|
4741
5389
|
let unmatchedJoin = 0;
|
|
4742
5390
|
for (const jf of joinFeatures) {
|
|
4743
|
-
const key = layerJoinKey(jf.properties
|
|
5391
|
+
const key = layerJoinKey(fieldValue(jf.properties, join.joinField));
|
|
4744
5392
|
if (key !== null && !targetKeys.has(key)) unmatchedJoin += 1;
|
|
4745
5393
|
}
|
|
4746
5394
|
return {
|
|
@@ -5150,6 +5798,42 @@ function clearQuickFilterValues(filters) {
|
|
|
5150
5798
|
}));
|
|
5151
5799
|
}
|
|
5152
5800
|
//#endregion
|
|
5801
|
+
//#region src/layer-filters.ts
|
|
5802
|
+
/**
|
|
5803
|
+
* Return a layer's persisted expression filter when it has a usable array
|
|
5804
|
+
* shape, otherwise return null.
|
|
5805
|
+
*
|
|
5806
|
+
* @param layer - The layer whose expression filter should be read.
|
|
5807
|
+
* @returns The active expression filter, or null when none is configured.
|
|
5808
|
+
*/
|
|
5809
|
+
function activeLayerFilterExpression(layer) {
|
|
5810
|
+
const expression = layer.filterExpression;
|
|
5811
|
+
return Array.isArray(expression) && expression.length > 0 ? expression : null;
|
|
5812
|
+
}
|
|
5813
|
+
/**
|
|
5814
|
+
* Compile all project-authored filters for a layer into one MapLibre boolean
|
|
5815
|
+
* expression. An explicit expression and Quick Filter controls narrow the
|
|
5816
|
+
* layer together.
|
|
5817
|
+
*
|
|
5818
|
+
* @param layer - The layer whose authored filters should be composed.
|
|
5819
|
+
* @returns A MapLibre filter expression, or null when no authored filter is active.
|
|
5820
|
+
*/
|
|
5821
|
+
function compileLayerFilters(layer) {
|
|
5822
|
+
const filters = [activeLayerFilterExpression(layer), compileQuickFilters(layer.quickFilters)].filter((filter) => filter !== null);
|
|
5823
|
+
if (filters.length === 0) return null;
|
|
5824
|
+
return filters.length === 1 ? filters[0] : ["all", ...filters];
|
|
5825
|
+
}
|
|
5826
|
+
/**
|
|
5827
|
+
* Whether a project-authored expression or Quick Filter currently narrows a
|
|
5828
|
+
* layer. This drives the filtered indicator and clear action in the layer UI.
|
|
5829
|
+
*
|
|
5830
|
+
* @param layer - The layer to inspect.
|
|
5831
|
+
* @returns True when at least one persisted filter is active.
|
|
5832
|
+
*/
|
|
5833
|
+
function hasActiveLayerFilter(layer) {
|
|
5834
|
+
return compileLayerFilters(layer) !== null;
|
|
5835
|
+
}
|
|
5836
|
+
//#endregion
|
|
5153
5837
|
//#region src/capabilities.ts
|
|
5154
5838
|
/**
|
|
5155
5839
|
* Default inferred capabilities for a layer based on its type and metadata.
|
|
@@ -5228,6 +5912,210 @@ function normalizeLayerCapabilities(raw) {
|
|
|
5228
5912
|
}
|
|
5229
5913
|
return hasAny ? caps : void 0;
|
|
5230
5914
|
}
|
|
5915
|
+
/**
|
|
5916
|
+
* All supported application privilege identifiers in GeoLibre.
|
|
5917
|
+
*/
|
|
5918
|
+
const ALL_APP_PRIVILEGES = [
|
|
5919
|
+
"layers:edit",
|
|
5920
|
+
"layers:add-remote",
|
|
5921
|
+
"layers:add-local",
|
|
5922
|
+
"processing:run",
|
|
5923
|
+
"processing:sidecar",
|
|
5924
|
+
"project:save",
|
|
5925
|
+
"project:share",
|
|
5926
|
+
"project:share-public",
|
|
5927
|
+
"plugins:install",
|
|
5928
|
+
"assistant:use",
|
|
5929
|
+
"connections:manage",
|
|
5930
|
+
"export:data",
|
|
5931
|
+
"export:image",
|
|
5932
|
+
"settings:manage"
|
|
5933
|
+
];
|
|
5934
|
+
/**
|
|
5935
|
+
* Standard privilege bundles for named application roles.
|
|
5936
|
+
*/
|
|
5937
|
+
const ROLE_PRIVILEGES = {
|
|
5938
|
+
viewer: ["export:image", "export:data"],
|
|
5939
|
+
editor: [
|
|
5940
|
+
"export:image",
|
|
5941
|
+
"export:data",
|
|
5942
|
+
"layers:edit",
|
|
5943
|
+
"layers:add-local",
|
|
5944
|
+
"layers:add-remote",
|
|
5945
|
+
"processing:run",
|
|
5946
|
+
"project:save"
|
|
5947
|
+
],
|
|
5948
|
+
publisher: [
|
|
5949
|
+
"export:image",
|
|
5950
|
+
"export:data",
|
|
5951
|
+
"layers:edit",
|
|
5952
|
+
"layers:add-local",
|
|
5953
|
+
"layers:add-remote",
|
|
5954
|
+
"processing:run",
|
|
5955
|
+
"project:save",
|
|
5956
|
+
"project:share",
|
|
5957
|
+
"project:share-public",
|
|
5958
|
+
"processing:sidecar",
|
|
5959
|
+
"assistant:use"
|
|
5960
|
+
],
|
|
5961
|
+
administrator: ALL_APP_PRIVILEGES
|
|
5962
|
+
};
|
|
5963
|
+
/**
|
|
5964
|
+
* Resolves the effective privilege list for a given role, applying custom overrides if specified.
|
|
5965
|
+
*/
|
|
5966
|
+
function resolveRolePrivileges(role, customPrivileges) {
|
|
5967
|
+
if (role === "custom") {
|
|
5968
|
+
if (!customPrivileges || customPrivileges.length === 0) return [];
|
|
5969
|
+
const validSet = new Set(ALL_APP_PRIVILEGES);
|
|
5970
|
+
return [...new Set(customPrivileges.filter((p) => validSet.has(p)))];
|
|
5971
|
+
}
|
|
5972
|
+
return [...ROLE_PRIVILEGES[role]];
|
|
5973
|
+
}
|
|
5974
|
+
/**
|
|
5975
|
+
* Intersects multiple sets of privileges to derive the effective permissions when multiple
|
|
5976
|
+
* policies (e.g. deployment, organization, and share link) apply simultaneously.
|
|
5977
|
+
*/
|
|
5978
|
+
function intersectPrivileges(...privilegeSets) {
|
|
5979
|
+
if (privilegeSets.length === 0) return [];
|
|
5980
|
+
if (privilegeSets.length === 1) return [...new Set(privilegeSets[0])];
|
|
5981
|
+
let current = new Set(privilegeSets[0]);
|
|
5982
|
+
for (let i = 1; i < privilegeSets.length; i++) {
|
|
5983
|
+
const nextSet = new Set(privilegeSets[i]);
|
|
5984
|
+
current = new Set([...current].filter((p) => nextSet.has(p)));
|
|
5985
|
+
}
|
|
5986
|
+
return [...current];
|
|
5987
|
+
}
|
|
5988
|
+
/**
|
|
5989
|
+
* Evaluates whether an application capability set grants a specific privilege.
|
|
5990
|
+
*/
|
|
5991
|
+
function hasAppPrivilege(capabilities, privilege) {
|
|
5992
|
+
if (!capabilities) return true;
|
|
5993
|
+
return capabilities.privileges.includes(privilege);
|
|
5994
|
+
}
|
|
5995
|
+
/**
|
|
5996
|
+
* Resolves why a privilege is withheld: its own reason if it has one, otherwise
|
|
5997
|
+
* the reason recorded for the capability set as a whole.
|
|
5998
|
+
*
|
|
5999
|
+
* @param capabilities - The active capability set, if any.
|
|
6000
|
+
* @param privilege - The privilege being explained.
|
|
6001
|
+
* @returns The reason, or undefined when none was recorded.
|
|
6002
|
+
*/
|
|
6003
|
+
function appPrivilegeReason(capabilities, privilege) {
|
|
6004
|
+
if (!capabilities) return void 0;
|
|
6005
|
+
return capabilities.privilegeReasons?.[privilege] ?? capabilities.reason;
|
|
6006
|
+
}
|
|
6007
|
+
/**
|
|
6008
|
+
* Creates the default unconstrained application capabilities (Administrator role).
|
|
6009
|
+
*/
|
|
6010
|
+
function createDefaultAppCapabilities() {
|
|
6011
|
+
return {
|
|
6012
|
+
role: "administrator",
|
|
6013
|
+
privileges: [...ALL_APP_PRIVILEGES]
|
|
6014
|
+
};
|
|
6015
|
+
}
|
|
6016
|
+
/**
|
|
6017
|
+
* Normalizes an untrusted array of privilege strings.
|
|
6018
|
+
*/
|
|
6019
|
+
function normalizeAppPrivileges(raw) {
|
|
6020
|
+
if (!Array.isArray(raw)) return void 0;
|
|
6021
|
+
const validSet = new Set(ALL_APP_PRIVILEGES);
|
|
6022
|
+
const result = [];
|
|
6023
|
+
for (const item of raw) if (typeof item === "string" && validSet.has(item)) result.push(item);
|
|
6024
|
+
return result.length > 0 ? [...new Set(result)] : [];
|
|
6025
|
+
}
|
|
6026
|
+
//#endregion
|
|
6027
|
+
//#region src/deployment-capabilities.ts
|
|
6028
|
+
/** Every capability, in a stable order for docs and tests. */
|
|
6029
|
+
const DEPLOYMENT_CAPABILITIES = [
|
|
6030
|
+
"project:edit",
|
|
6031
|
+
"data:add",
|
|
6032
|
+
"processing:run",
|
|
6033
|
+
"export:data",
|
|
6034
|
+
"plugins:install",
|
|
6035
|
+
"settings:manage"
|
|
6036
|
+
];
|
|
6037
|
+
/**
|
|
6038
|
+
* The default grant: everything. `full` must stay the default so an existing
|
|
6039
|
+
* deployment that configures nothing behaves exactly as it did before.
|
|
6040
|
+
*/
|
|
6041
|
+
const ALL_DEPLOYMENT_CAPABILITIES = new Set(DEPLOYMENT_CAPABILITIES);
|
|
6042
|
+
/** Whether an arbitrary string names a capability we know how to enforce. */
|
|
6043
|
+
function isDeploymentCapability(value) {
|
|
6044
|
+
return DEPLOYMENT_CAPABILITIES.includes(value);
|
|
6045
|
+
}
|
|
6046
|
+
/**
|
|
6047
|
+
* The reserved token that grants nothing, for a deployment that wants the map
|
|
6048
|
+
* and nothing else.
|
|
6049
|
+
*
|
|
6050
|
+
* Needed because a blank value cannot mean it: the deployment env reader treats
|
|
6051
|
+
* an empty string as unset (a `-e VAR=` produces one), and unset has to keep
|
|
6052
|
+
* meaning "full" so existing deployments are unchanged. Without a spelling for
|
|
6053
|
+
* "none", the most locked-down configuration would be the one you cannot write.
|
|
6054
|
+
*/
|
|
6055
|
+
const NO_DEPLOYMENT_CAPABILITIES = "none";
|
|
6056
|
+
/**
|
|
6057
|
+
* Parse a comma-separated capability list from deployment configuration.
|
|
6058
|
+
*
|
|
6059
|
+
* Unknown tokens are dropped rather than throwing: a newer container image may
|
|
6060
|
+
* be configured with a capability this build does not know about, and the safe
|
|
6061
|
+
* reading of an unrecognized grant is "not granted".
|
|
6062
|
+
*
|
|
6063
|
+
* Fails closed on purpose. An operator who set the variable at all meant to
|
|
6064
|
+
* restrict something, so a list that parses to nothing grants nothing — only an
|
|
6065
|
+
* absent (or blank) value falls back to the full set, which is the caller's job
|
|
6066
|
+
* to detect.
|
|
6067
|
+
*
|
|
6068
|
+
* @param raw - The configured value, e.g. `"data:add, export:data"`.
|
|
6069
|
+
* @returns The granted capabilities, with duplicates and unknown names removed.
|
|
6070
|
+
*/
|
|
6071
|
+
function parseDeploymentCapabilities(raw) {
|
|
6072
|
+
const granted = /* @__PURE__ */ new Set();
|
|
6073
|
+
if (raw.trim().toLowerCase() === "none") return granted;
|
|
6074
|
+
for (const token of raw.split(",")) {
|
|
6075
|
+
const name = token.trim();
|
|
6076
|
+
if (isDeploymentCapability(name)) granted.add(name);
|
|
6077
|
+
}
|
|
6078
|
+
return granted;
|
|
6079
|
+
}
|
|
6080
|
+
//#endregion
|
|
6081
|
+
//#region src/file-name.ts
|
|
6082
|
+
/**
|
|
6083
|
+
* A bare Android SAF document ID, as the browser `File.name` carries after a
|
|
6084
|
+
* native import: a known volume prefix (`primary:`, `home:`, `raw:`, `msf:`, or a
|
|
6085
|
+
* removable `XXXX-XXXX:` volume), or any provider prefix followed by a folder
|
|
6086
|
+
* separator. Ordinary encoded filenames such as `Report%3AQ1.geojson` do not match.
|
|
6087
|
+
*/
|
|
6088
|
+
const SAF_DOCUMENT_ID = /^(?:(?:primary|home|raw|msf|[0-9a-f]{4}-[0-9a-f]{4})%3a|[^%/\\:]+%3a.*%2f)/i;
|
|
6089
|
+
/** Extract a display filename without changing the source used for file access. */
|
|
6090
|
+
function localFileName(path) {
|
|
6091
|
+
const isUri = /^(?:content|file):\/\//i.test(path);
|
|
6092
|
+
const value = isUri ? path.split(/[?#]/)[0] : path;
|
|
6093
|
+
const segment = value.split(/[/\\]/).pop() ?? value;
|
|
6094
|
+
if (!isUri && (!SAF_DOCUMENT_ID.test(segment) || /[/\\]/.test(path))) return segment;
|
|
6095
|
+
let decoded = segment;
|
|
6096
|
+
try {
|
|
6097
|
+
decoded = decodeURIComponent(segment);
|
|
6098
|
+
} catch {
|
|
6099
|
+
return segment;
|
|
6100
|
+
}
|
|
6101
|
+
return decoded.replace(/^[^/\\:]+:/, "").split(/[/\\]/).pop() ?? decoded;
|
|
6102
|
+
}
|
|
6103
|
+
/**
|
|
6104
|
+
* Whether a layer name was derived from its local source filename (with or
|
|
6105
|
+
* without the extension) rather than supplied explicitly by a caller.
|
|
6106
|
+
*/
|
|
6107
|
+
function isSourceDerivedLayerName(name, sourcePath) {
|
|
6108
|
+
const fileName = localFileName(sourcePath);
|
|
6109
|
+
return name === fileName || name === fileName.replace(/\.[^.]+$/, "");
|
|
6110
|
+
}
|
|
6111
|
+
/** Keep imported layers distinguishable, including within a multi-file import. */
|
|
6112
|
+
function uniqueImportedLayerName(name, names) {
|
|
6113
|
+
const existing = new Set(names);
|
|
6114
|
+
let candidate = name;
|
|
6115
|
+
let suffix = 2;
|
|
6116
|
+
while (existing.has(candidate)) candidate = `${name}_${suffix++}`;
|
|
6117
|
+
return candidate;
|
|
6118
|
+
}
|
|
5231
6119
|
//#endregion
|
|
5232
6120
|
//#region src/history.ts
|
|
5233
6121
|
/**
|
|
@@ -6833,6 +7721,9 @@ function scrubPrintLayoutForRemovedLayers(config, layerIds) {
|
|
|
6833
7721
|
//#region src/project.ts
|
|
6834
7722
|
/** Placeholder name a project carries before the user names it. */
|
|
6835
7723
|
const DEFAULT_PROJECT_NAME = "Untitled Project";
|
|
7724
|
+
function normalizeBlankBackgroundColor(value) {
|
|
7725
|
+
return typeof value === "string" && /^#[0-9a-f]{6}$/i.test(value) ? value : null;
|
|
7726
|
+
}
|
|
6836
7727
|
function createDefaultMapView() {
|
|
6837
7728
|
return {
|
|
6838
7729
|
center: [-100, 40],
|
|
@@ -6849,6 +7740,7 @@ function createEmptyProject(name = DEFAULT_PROJECT_NAME, options = {}) {
|
|
|
6849
7740
|
basemapStyleUrl: options.basemapStyleUrl ?? "https://tiles.openfreemap.org/styles/liberty",
|
|
6850
7741
|
basemapVisible: true,
|
|
6851
7742
|
basemapOpacity: 1,
|
|
7743
|
+
blankBackgroundColor: null,
|
|
6852
7744
|
layers: [],
|
|
6853
7745
|
layerGroups: [],
|
|
6854
7746
|
styles: {},
|
|
@@ -6970,6 +7862,7 @@ function parseProject(json) {
|
|
|
6970
7862
|
const basemapStyleUrl = data.basemapStyleUrl ?? "https://tiles.openfreemap.org/styles/liberty";
|
|
6971
7863
|
const basemapVisible = data.basemapVisible ?? true;
|
|
6972
7864
|
const basemapOpacity = data.basemapOpacity ?? 1;
|
|
7865
|
+
const blankBackgroundColor = normalizeBlankBackgroundColor(data.blankBackgroundColor);
|
|
6973
7866
|
const mapView = normalizeMapViewState(data.mapView);
|
|
6974
7867
|
const { mapLayout, secondaryMapViews } = resolveMapGrid(normalizeMapLayout(data.mapLayout), normalizeSecondaryMapViews(data.secondaryMapViews), { mapView });
|
|
6975
7868
|
const styleLibrary = normalizeStyleLibraryEntries(data.styleLibrary);
|
|
@@ -6981,6 +7874,7 @@ function parseProject(json) {
|
|
|
6981
7874
|
basemapStyleUrl,
|
|
6982
7875
|
basemapVisible,
|
|
6983
7876
|
basemapOpacity,
|
|
7877
|
+
blankBackgroundColor,
|
|
6984
7878
|
layers,
|
|
6985
7879
|
...selectedLayerId !== void 0 ? { selectedLayerId } : {},
|
|
6986
7880
|
...layerGroups.length > 0 ? { layerGroups } : {},
|
|
@@ -6999,6 +7893,7 @@ function parseProject(json) {
|
|
|
6999
7893
|
secondaryMapViews,
|
|
7000
7894
|
...normalizeString(data.primaryMapLabel) ? { primaryMapLabel: normalizeString(data.primaryMapLabel) } : {}
|
|
7001
7895
|
} : {},
|
|
7896
|
+
...normalizePrimaryRenderer(data.primaryRenderer) ? { primaryRenderer: "cesium" } : {},
|
|
7002
7897
|
...styleLibrary.length > 0 ? { styleLibrary } : {},
|
|
7003
7898
|
...parsedComments.length > 0 ? { comments: parsedComments } : {},
|
|
7004
7899
|
metadata: data.metadata ?? {}
|
|
@@ -7464,6 +8359,18 @@ function normalizeMapLayout(value) {
|
|
|
7464
8359
|
};
|
|
7465
8360
|
}
|
|
7466
8361
|
/**
|
|
8362
|
+
* Coerce an untrusted `primaryRenderer` into a known engine id (issue #2217).
|
|
8363
|
+
*
|
|
8364
|
+
* Returns null for the default 2D map — absent, unknown, or an explicit
|
|
8365
|
+
* `"maplibre"` — because the field is only written when it is not the default,
|
|
8366
|
+
* so a MapLibre project serializes byte-identically to before this existed.
|
|
8367
|
+
* The return type is narrowed to `"cesium" | null` rather than the full
|
|
8368
|
+
* {@link MapRendererKind} for that reason: `"maplibre"` is never a result.
|
|
8369
|
+
*/
|
|
8370
|
+
function normalizePrimaryRenderer(value) {
|
|
8371
|
+
return value === "cesium" ? "cesium" : null;
|
|
8372
|
+
}
|
|
8373
|
+
/**
|
|
7467
8374
|
* Coerce an untrusted `secondaryMapViews` array into valid
|
|
7468
8375
|
* {@link SecondaryMapView} records, dropping entries without a usable id and
|
|
7469
8376
|
* de-duplicating by id. Returns null when none are valid.
|
|
@@ -7665,6 +8572,8 @@ function normalizeProjectPreferences(preferences) {
|
|
|
7665
8572
|
ellipsoidId: getEllipsoid(map.ellipsoidId).id,
|
|
7666
8573
|
scaleUnit: normalizeScaleUnit(map.scaleUnit),
|
|
7667
8574
|
showPointerElevation: normalizeBoolean(map.showPointerElevation, DEFAULT_PROJECT_PREFERENCES.map.showPointerElevation),
|
|
8575
|
+
cesiumBasemap: normalizeCesiumBasemap(map.cesiumBasemap),
|
|
8576
|
+
terrainEnabled: normalizeBoolean(map.terrainEnabled, DEFAULT_PROJECT_PREFERENCES.map.terrainEnabled),
|
|
7668
8577
|
coordinateFormat: typeof map.coordinateFormat === "string" ? map.coordinateFormat : DEFAULT_PROJECT_PREFERENCES.map.coordinateFormat
|
|
7669
8578
|
},
|
|
7670
8579
|
environmentVariables: Array.isArray(candidate.environmentVariables) ? candidate.environmentVariables.map(normalizeEnvironmentVariable).filter((variable) => Boolean(variable)) : [],
|
|
@@ -7801,8 +8710,9 @@ function isPlainObject$2(value) {
|
|
|
7801
8710
|
return prototype === Object.prototype || prototype === null;
|
|
7802
8711
|
}
|
|
7803
8712
|
function normalizeLayer(layer) {
|
|
7804
|
-
const { capabilities: rawCapabilities, ...rest } = layer;
|
|
8713
|
+
const { capabilities: rawCapabilities, filterExpression: rawFilterExpression, ...rest } = layer;
|
|
7805
8714
|
const capabilities = normalizeLayerCapabilities(rawCapabilities);
|
|
8715
|
+
const filterExpression = Array.isArray(rawFilterExpression) && rawFilterExpression.length > 0 && validateMapExpression(JSON.stringify(rawFilterExpression), { expectedType: "boolean" }).ok ? rawFilterExpression : void 0;
|
|
7806
8716
|
return {
|
|
7807
8717
|
...rest,
|
|
7808
8718
|
style: {
|
|
@@ -7813,7 +8723,8 @@ function normalizeLayer(layer) {
|
|
|
7813
8723
|
opacity: layer.opacity ?? 1,
|
|
7814
8724
|
metadata: layer.metadata ?? {},
|
|
7815
8725
|
source: layer.source ?? {},
|
|
7816
|
-
...capabilities ? { capabilities } : {}
|
|
8726
|
+
...capabilities ? { capabilities } : {},
|
|
8727
|
+
...filterExpression ? { filterExpression } : {}
|
|
7817
8728
|
};
|
|
7818
8729
|
}
|
|
7819
8730
|
function normalizeProjectComments(rawComments) {
|
|
@@ -7897,6 +8808,7 @@ function projectFromStore(state) {
|
|
|
7897
8808
|
basemapStyleUrl: state.basemapStyleUrl,
|
|
7898
8809
|
basemapVisible: state.basemapVisible,
|
|
7899
8810
|
basemapOpacity: state.basemapOpacity,
|
|
8811
|
+
...state.blankBackgroundColor ? { blankBackgroundColor: state.blankBackgroundColor } : {},
|
|
7900
8812
|
layers: state.layers.map(prepareLayerForSave),
|
|
7901
8813
|
...selectedLayerId !== void 0 ? { selectedLayerId } : {},
|
|
7902
8814
|
...layerGroups.length > 0 ? { layerGroups } : {},
|
|
@@ -7915,6 +8827,7 @@ function projectFromStore(state) {
|
|
|
7915
8827
|
secondaryMapViews,
|
|
7916
8828
|
...normalizeString(state.primaryMapLabel) ? { primaryMapLabel: normalizeString(state.primaryMapLabel) } : {}
|
|
7917
8829
|
} : {},
|
|
8830
|
+
...normalizePrimaryRenderer(state.primaryRenderer) ? { primaryRenderer: "cesium" } : {},
|
|
7918
8831
|
...styleLibrary.length > 0 ? { styleLibrary } : {},
|
|
7919
8832
|
...comments.length > 0 ? { comments } : {},
|
|
7920
8833
|
metadata: state.metadata
|
|
@@ -7926,6 +8839,13 @@ function hasRestorableSourceUrl(layer) {
|
|
|
7926
8839
|
return typeof sourceUrl === "string" && sourceUrl.trim() !== "" || typeof originalUrl === "string" && originalUrl.trim() !== "";
|
|
7927
8840
|
}
|
|
7928
8841
|
function prepareLayerForSave(layer) {
|
|
8842
|
+
if (layer.metadata.geometryEdited !== void 0) {
|
|
8843
|
+
const { geometryEdited: _geometryEdited, ...metadata } = layer.metadata;
|
|
8844
|
+
layer = {
|
|
8845
|
+
...layer,
|
|
8846
|
+
metadata
|
|
8847
|
+
};
|
|
8848
|
+
}
|
|
7929
8849
|
if (layer.timeFilter !== void 0) {
|
|
7930
8850
|
const { timeFilter: _timeFilter, ...rest } = layer;
|
|
7931
8851
|
layer = rest;
|
|
@@ -7943,6 +8863,18 @@ function prepareLayerForSave(layer) {
|
|
|
7943
8863
|
const { geojson: _geojson, ...rest } = layer;
|
|
7944
8864
|
layer = rest;
|
|
7945
8865
|
}
|
|
8866
|
+
if (layer.type === "wms") {
|
|
8867
|
+
const tiles = layer.source.tiles;
|
|
8868
|
+
if (!Array.isArray(tiles)) return layer;
|
|
8869
|
+
const portableTiles = tiles.map((tile) => portableWmsTileUrl(tile));
|
|
8870
|
+
return portableTiles.some((tile, index) => tile !== tiles[index]) ? {
|
|
8871
|
+
...layer,
|
|
8872
|
+
source: {
|
|
8873
|
+
...layer.source,
|
|
8874
|
+
tiles: portableTiles
|
|
8875
|
+
}
|
|
8876
|
+
} : layer;
|
|
8877
|
+
}
|
|
7946
8878
|
if (layer.type !== "xyz") return layer;
|
|
7947
8879
|
const originalUrl = typeof layer.metadata.originalUrl === "string" && layer.metadata.originalUrl.trim() ? layer.metadata.originalUrl : typeof layer.source.url === "string" && layer.source.url.trim() ? layer.source.url : null;
|
|
7948
8880
|
if (!originalUrl) return layer;
|
|
@@ -7958,11 +8890,23 @@ function prepareLayerForSave(layer) {
|
|
|
7958
8890
|
metadata
|
|
7959
8891
|
};
|
|
7960
8892
|
}
|
|
8893
|
+
function portableWmsTileUrl(tile) {
|
|
8894
|
+
if (typeof tile !== "string" || !tile.startsWith("geolibre-wms://")) return tile;
|
|
8895
|
+
try {
|
|
8896
|
+
const url = new URL(tile).searchParams.get("url");
|
|
8897
|
+
if (!url) return tile;
|
|
8898
|
+
const parsed = new URL(url);
|
|
8899
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:" ? url : tile;
|
|
8900
|
+
} catch {
|
|
8901
|
+
return tile;
|
|
8902
|
+
}
|
|
8903
|
+
}
|
|
7961
8904
|
function applyProjectToStore(project) {
|
|
7962
8905
|
const layers = project.layers.map((layer) => ({
|
|
7963
8906
|
...layer,
|
|
7964
8907
|
style: project.styles[layer.id] ? {
|
|
7965
8908
|
...DEFAULT_LAYER_STYLE,
|
|
8909
|
+
...layer.style,
|
|
7966
8910
|
...project.styles[layer.id]
|
|
7967
8911
|
} : {
|
|
7968
8912
|
...DEFAULT_LAYER_STYLE,
|
|
@@ -7978,6 +8922,7 @@ function applyProjectToStore(project) {
|
|
|
7978
8922
|
const basemapStyleUrl = project.basemapStyleUrl;
|
|
7979
8923
|
const basemapVisible = project.basemapVisible ?? true;
|
|
7980
8924
|
const basemapOpacity = project.basemapOpacity ?? 1;
|
|
8925
|
+
const blankBackgroundColor = normalizeBlankBackgroundColor(project.blankBackgroundColor);
|
|
7981
8926
|
const mapView = normalizeMapViewState(project.mapView);
|
|
7982
8927
|
const { mapLayout, secondaryMapViews } = resolveMapGrid(normalizeMapLayout(project.mapLayout), normalizeSecondaryMapViews(project.secondaryMapViews), { mapView });
|
|
7983
8928
|
const existingLayerIds = new Set(normalizedLayers.map((l) => l.id));
|
|
@@ -8006,6 +8951,7 @@ function applyProjectToStore(project) {
|
|
|
8006
8951
|
basemapStyleUrl,
|
|
8007
8952
|
basemapVisible,
|
|
8008
8953
|
basemapOpacity,
|
|
8954
|
+
blankBackgroundColor,
|
|
8009
8955
|
layers: normalizedLayers,
|
|
8010
8956
|
layerGroups,
|
|
8011
8957
|
preferences: normalizeProjectPreferences(project.preferences),
|
|
@@ -8020,6 +8966,7 @@ function applyProjectToStore(project) {
|
|
|
8020
8966
|
mapLayout,
|
|
8021
8967
|
secondaryMapViews,
|
|
8022
8968
|
primaryMapLabel: normalizeString(project.primaryMapLabel),
|
|
8969
|
+
primaryRenderer: normalizePrimaryRenderer(project.primaryRenderer) ?? "maplibre",
|
|
8023
8970
|
projectStyleLibrary: normalizeStyleLibraryEntries(project.styleLibrary),
|
|
8024
8971
|
comments: scrubbedComments,
|
|
8025
8972
|
metadata: project.metadata
|
|
@@ -8364,6 +9311,313 @@ function rescaleEntryCount(state) {
|
|
|
8364
9311
|
return 1;
|
|
8365
9312
|
}
|
|
8366
9313
|
//#endregion
|
|
9314
|
+
//#region src/czml.ts
|
|
9315
|
+
/**
|
|
9316
|
+
* CZML (Cesium Language) domain models and layer authoring (issue #2290).
|
|
9317
|
+
*
|
|
9318
|
+
* CZML is a JSON-based format for describing dynamic, time-varying 3D geospatial
|
|
9319
|
+
* scenes on the Cesium globe (trajectories, satellites, vehicles, sensor
|
|
9320
|
+
* networks, moving entities with orientations and paths). The globe loads CZML
|
|
9321
|
+
* natively through `CzmlDataSource`; the 2D map has no equivalent, so CZML
|
|
9322
|
+
* layers are badged "3D only" there.
|
|
9323
|
+
*/
|
|
9324
|
+
/** `metadata.sourceKind` of a layer that carries or references CZML data. */
|
|
9325
|
+
const CZML_SOURCE_KIND = "czml";
|
|
9326
|
+
/** One-click CZML samples offered in the Add Data dialog. */
|
|
9327
|
+
const CZML_QUICK_PICKS = [{
|
|
9328
|
+
name: "CZML Point",
|
|
9329
|
+
data: [{
|
|
9330
|
+
id: "document",
|
|
9331
|
+
name: "CZML Point Sample",
|
|
9332
|
+
version: "1.0"
|
|
9333
|
+
}, {
|
|
9334
|
+
id: "point 1",
|
|
9335
|
+
name: "Extruded Point",
|
|
9336
|
+
position: { cartographicDegrees: [
|
|
9337
|
+
-75.59777,
|
|
9338
|
+
40.03883,
|
|
9339
|
+
1e3
|
|
9340
|
+
] },
|
|
9341
|
+
point: {
|
|
9342
|
+
color: { rgba: [
|
|
9343
|
+
255,
|
|
9344
|
+
128,
|
|
9345
|
+
0,
|
|
9346
|
+
255
|
|
9347
|
+
] },
|
|
9348
|
+
pixelSize: 14,
|
|
9349
|
+
outlineColor: { rgba: [
|
|
9350
|
+
255,
|
|
9351
|
+
255,
|
|
9352
|
+
255,
|
|
9353
|
+
255
|
|
9354
|
+
] },
|
|
9355
|
+
outlineWidth: 2
|
|
9356
|
+
}
|
|
9357
|
+
}]
|
|
9358
|
+
}, {
|
|
9359
|
+
name: "CZML Dynamic Trajectory",
|
|
9360
|
+
data: [{
|
|
9361
|
+
id: "document",
|
|
9362
|
+
name: "CZML Trajectory Sample",
|
|
9363
|
+
version: "1.0",
|
|
9364
|
+
clock: {
|
|
9365
|
+
interval: "2026-09-09T00:00:00Z/2026-09-09T02:00:00Z",
|
|
9366
|
+
currentTime: "2026-09-09T00:00:00Z",
|
|
9367
|
+
multiplier: 60,
|
|
9368
|
+
range: "LOOP_STOP"
|
|
9369
|
+
}
|
|
9370
|
+
}, {
|
|
9371
|
+
id: "satellite",
|
|
9372
|
+
name: "Satellite Track",
|
|
9373
|
+
availability: "2026-09-09T00:00:00Z/2026-09-09T02:00:00Z",
|
|
9374
|
+
path: {
|
|
9375
|
+
material: { solidColor: { color: { rgba: [
|
|
9376
|
+
0,
|
|
9377
|
+
200,
|
|
9378
|
+
255,
|
|
9379
|
+
255
|
|
9380
|
+
] } } },
|
|
9381
|
+
width: 2,
|
|
9382
|
+
leadTime: 1800,
|
|
9383
|
+
trailTime: 1800
|
|
9384
|
+
},
|
|
9385
|
+
position: {
|
|
9386
|
+
epoch: "2026-09-09T00:00:00Z",
|
|
9387
|
+
cartographicDegrees: [
|
|
9388
|
+
0,
|
|
9389
|
+
-75,
|
|
9390
|
+
40,
|
|
9391
|
+
25e4,
|
|
9392
|
+
1800,
|
|
9393
|
+
-30,
|
|
9394
|
+
20,
|
|
9395
|
+
25e4,
|
|
9396
|
+
3600,
|
|
9397
|
+
20,
|
|
9398
|
+
0,
|
|
9399
|
+
25e4,
|
|
9400
|
+
5400,
|
|
9401
|
+
70,
|
|
9402
|
+
-20,
|
|
9403
|
+
25e4,
|
|
9404
|
+
7200,
|
|
9405
|
+
120,
|
|
9406
|
+
-40,
|
|
9407
|
+
25e4
|
|
9408
|
+
]
|
|
9409
|
+
},
|
|
9410
|
+
point: {
|
|
9411
|
+
color: { rgba: [
|
|
9412
|
+
255,
|
|
9413
|
+
255,
|
|
9414
|
+
255,
|
|
9415
|
+
255
|
|
9416
|
+
] },
|
|
9417
|
+
pixelSize: 10
|
|
9418
|
+
}
|
|
9419
|
+
}]
|
|
9420
|
+
}];
|
|
9421
|
+
/**
|
|
9422
|
+
* Parse and validate a CZML payload from a JSON string or raw object.
|
|
9423
|
+
* Returns an array of CZML packets or null when the input is malformed.
|
|
9424
|
+
*/
|
|
9425
|
+
function parseCzml(value) {
|
|
9426
|
+
if (!value) return null;
|
|
9427
|
+
let parsed = value;
|
|
9428
|
+
if (typeof value === "string") {
|
|
9429
|
+
const trimmed = value.trim();
|
|
9430
|
+
if (!trimmed.startsWith("[") && !trimmed.startsWith("{")) return null;
|
|
9431
|
+
try {
|
|
9432
|
+
parsed = JSON.parse(trimmed);
|
|
9433
|
+
} catch {
|
|
9434
|
+
return null;
|
|
9435
|
+
}
|
|
9436
|
+
}
|
|
9437
|
+
if (Array.isArray(parsed)) return parsed.length > 0 && parsed.every((item) => item && typeof item === "object") ? parsed : null;
|
|
9438
|
+
if (parsed && typeof parsed === "object") return [parsed];
|
|
9439
|
+
return null;
|
|
9440
|
+
}
|
|
9441
|
+
/**
|
|
9442
|
+
* Whether `layer` represents a CZML dynamic 3D scene.
|
|
9443
|
+
*/
|
|
9444
|
+
function isCzmlLayer(layer) {
|
|
9445
|
+
return layer.metadata?.sourceKind === CZML_SOURCE_KIND;
|
|
9446
|
+
}
|
|
9447
|
+
/**
|
|
9448
|
+
* Extract the CZML content and/or URL from a layer, or null if not a CZML layer.
|
|
9449
|
+
*/
|
|
9450
|
+
function czmlSource(layer) {
|
|
9451
|
+
if (!isCzmlLayer(layer)) return null;
|
|
9452
|
+
const raw = layer.source?.czmlData;
|
|
9453
|
+
const data = Array.isArray(raw) ? raw.length > 0 ? raw : void 0 : raw && typeof raw === "object" ? [raw] : raw || void 0;
|
|
9454
|
+
const rawUrl = layer.source?.url;
|
|
9455
|
+
const url = typeof rawUrl === "string" && rawUrl.trim() ? rawUrl.trim() : void 0;
|
|
9456
|
+
if (!data && !url) return null;
|
|
9457
|
+
return {
|
|
9458
|
+
url,
|
|
9459
|
+
data
|
|
9460
|
+
};
|
|
9461
|
+
}
|
|
9462
|
+
/** Generate a unique identifier for a newly created layer. */
|
|
9463
|
+
function newLayerId$1() {
|
|
9464
|
+
return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
9465
|
+
}
|
|
9466
|
+
/**
|
|
9467
|
+
* Build a typed GeoLibreLayer representing a CZML dynamic scene on the Cesium globe.
|
|
9468
|
+
*/
|
|
9469
|
+
function createCzmlLayer(options) {
|
|
9470
|
+
const id = options.id ?? newLayerId$1();
|
|
9471
|
+
const data = options.data;
|
|
9472
|
+
const url = options.url?.trim() || void 0;
|
|
9473
|
+
const sourcePath = options.sourcePath?.trim() || void 0;
|
|
9474
|
+
return {
|
|
9475
|
+
id,
|
|
9476
|
+
name: options.name,
|
|
9477
|
+
type: "3d-tiles",
|
|
9478
|
+
...sourcePath ? { sourcePath } : {},
|
|
9479
|
+
source: {
|
|
9480
|
+
type: "3d-tiles",
|
|
9481
|
+
sourceId: id,
|
|
9482
|
+
...data !== void 0 ? { czmlData: data } : {},
|
|
9483
|
+
...url ? { url } : {},
|
|
9484
|
+
...sourcePath ? { sourcePath } : {}
|
|
9485
|
+
},
|
|
9486
|
+
visible: true,
|
|
9487
|
+
opacity: 1,
|
|
9488
|
+
style: { ...DEFAULT_LAYER_STYLE },
|
|
9489
|
+
metadata: {
|
|
9490
|
+
sourceKind: CZML_SOURCE_KIND,
|
|
9491
|
+
externalNativeLayer: true,
|
|
9492
|
+
identifiable: false,
|
|
9493
|
+
sourceId: id,
|
|
9494
|
+
nativeLayerIds: [id]
|
|
9495
|
+
}
|
|
9496
|
+
};
|
|
9497
|
+
}
|
|
9498
|
+
//#endregion
|
|
9499
|
+
//#region src/cesium-kml.ts
|
|
9500
|
+
/** Native KML/KMZ documents retain their own styling on the globe. */
|
|
9501
|
+
const CESIUM_KML_SOURCE_KIND = "cesium-kml";
|
|
9502
|
+
function isCesiumKmlLayer(layer) {
|
|
9503
|
+
return layer.metadata?.sourceKind === CESIUM_KML_SOURCE_KIND;
|
|
9504
|
+
}
|
|
9505
|
+
function cesiumKmlSource(layer) {
|
|
9506
|
+
if (!isCesiumKmlLayer(layer)) return null;
|
|
9507
|
+
for (const value of [layer.source.kmlData, layer.source.url]) if (typeof value === "string" && value.trim()) return value.trim();
|
|
9508
|
+
return null;
|
|
9509
|
+
}
|
|
9510
|
+
function createCesiumKmlLayer(options) {
|
|
9511
|
+
const data = options.data?.trim();
|
|
9512
|
+
const url = options.url?.trim();
|
|
9513
|
+
if (!data && !url) throw new Error("Provide a KML/KMZ document or URL.");
|
|
9514
|
+
const id = options.id ?? crypto.randomUUID();
|
|
9515
|
+
return {
|
|
9516
|
+
id,
|
|
9517
|
+
name: options.name,
|
|
9518
|
+
type: "3d-tiles",
|
|
9519
|
+
...options.sourcePath ? { sourcePath: options.sourcePath } : {},
|
|
9520
|
+
source: {
|
|
9521
|
+
type: "3d-tiles",
|
|
9522
|
+
sourceId: id,
|
|
9523
|
+
...data ? { kmlData: data } : { url }
|
|
9524
|
+
},
|
|
9525
|
+
visible: true,
|
|
9526
|
+
opacity: 1,
|
|
9527
|
+
style: { ...DEFAULT_LAYER_STYLE },
|
|
9528
|
+
metadata: {
|
|
9529
|
+
sourceKind: CESIUM_KML_SOURCE_KIND,
|
|
9530
|
+
externalNativeLayer: true,
|
|
9531
|
+
identifiable: false
|
|
9532
|
+
}
|
|
9533
|
+
};
|
|
9534
|
+
}
|
|
9535
|
+
//#endregion
|
|
9536
|
+
//#region src/cesium-ion.ts
|
|
9537
|
+
/** `metadata.sourceKind` of a layer that references a Cesium Ion asset. */
|
|
9538
|
+
const CESIUM_ION_SOURCE_KIND = "cesium-ion";
|
|
9539
|
+
/** Cesium OSM Buildings, the one-click 3D Tiles asset every Ion account can use. */
|
|
9540
|
+
const CESIUM_OSM_BUILDINGS_ASSET_ID = 96188;
|
|
9541
|
+
/** Bing Maps Aerial through Ion, the imagery asset every Ion account can use. */
|
|
9542
|
+
const CESIUM_BING_AERIAL_ASSET_ID = 2;
|
|
9543
|
+
/** Ion assets offered as one-click picks in the Add Data dialog. */
|
|
9544
|
+
const CESIUM_ION_QUICK_PICKS = [{
|
|
9545
|
+
assetId: CESIUM_OSM_BUILDINGS_ASSET_ID,
|
|
9546
|
+
kind: "3d-tiles",
|
|
9547
|
+
name: "Cesium OSM Buildings"
|
|
9548
|
+
}, {
|
|
9549
|
+
assetId: 2,
|
|
9550
|
+
kind: "imagery",
|
|
9551
|
+
name: "Bing Maps Aerial (Ion)"
|
|
9552
|
+
}];
|
|
9553
|
+
/** Parse an Ion asset id the way the Add Data form and the project file carry it. */
|
|
9554
|
+
function parseCesiumIonAssetId(value) {
|
|
9555
|
+
const id = typeof value === "string" ? Number(value.trim()) : value;
|
|
9556
|
+
return typeof id === "number" && Number.isInteger(id) && id > 0 ? id : null;
|
|
9557
|
+
}
|
|
9558
|
+
/**
|
|
9559
|
+
* The Ion asset id a layer references, or null when the layer is not an Ion
|
|
9560
|
+
* asset layer (or carries no usable id).
|
|
9561
|
+
*/
|
|
9562
|
+
function cesiumIonAssetId(layer) {
|
|
9563
|
+
if (layer.metadata?.sourceKind !== "cesium-ion") return null;
|
|
9564
|
+
return parseCesiumIonAssetId(layer.source?.ionAssetId);
|
|
9565
|
+
}
|
|
9566
|
+
/** Whether `layer` references a Cesium Ion asset. */
|
|
9567
|
+
function isCesiumIonLayer(layer) {
|
|
9568
|
+
return cesiumIonAssetId(layer) !== null;
|
|
9569
|
+
}
|
|
9570
|
+
/** What the Ion asset draws as: a tileset primitive or an imagery layer. */
|
|
9571
|
+
function cesiumIonAssetKind(layer) {
|
|
9572
|
+
return layer.type === "3d-tiles" ? "3d-tiles" : "imagery";
|
|
9573
|
+
}
|
|
9574
|
+
/**
|
|
9575
|
+
* Whether only the 3D globe can render `layer`: the mirror of the globe's
|
|
9576
|
+
* `isCesiumSupportedLayerType`, for the Layers panel to badge on the 2D map.
|
|
9577
|
+
*/
|
|
9578
|
+
function isCesiumOnlyLayer(layer) {
|
|
9579
|
+
return isCesiumIonLayer(layer) || isCzmlLayer(layer) || isCesiumKmlLayer(layer);
|
|
9580
|
+
}
|
|
9581
|
+
function newLayerId() {
|
|
9582
|
+
return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
9583
|
+
}
|
|
9584
|
+
/**
|
|
9585
|
+
* Build the store layer for a Cesium Ion asset. A tileset is a `3d-tiles`
|
|
9586
|
+
* layer whose `sourceKind` keeps it out of the 2D deck.gl control; imagery is
|
|
9587
|
+
* a `raster` layer with no tiles, marked external so the 2D sync leaves it
|
|
9588
|
+
* alone. Both persist only the asset id: the token is runtime configuration.
|
|
9589
|
+
*/
|
|
9590
|
+
function createCesiumIonLayer(options) {
|
|
9591
|
+
const id = options.id ?? newLayerId();
|
|
9592
|
+
const isTileset = options.kind === "3d-tiles";
|
|
9593
|
+
const altitudeOffset = Number.isFinite(options.altitudeOffset) ? options.altitudeOffset : 0;
|
|
9594
|
+
return {
|
|
9595
|
+
id,
|
|
9596
|
+
name: options.name,
|
|
9597
|
+
type: isTileset ? "3d-tiles" : "raster",
|
|
9598
|
+
source: {
|
|
9599
|
+
type: isTileset ? "3d-tiles" : "raster",
|
|
9600
|
+
ionAssetId: options.assetId,
|
|
9601
|
+
sourceId: id,
|
|
9602
|
+
...isTileset ? { altitudeOffset } : {}
|
|
9603
|
+
},
|
|
9604
|
+
visible: true,
|
|
9605
|
+
opacity: 1,
|
|
9606
|
+
style: { ...DEFAULT_LAYER_STYLE },
|
|
9607
|
+
metadata: {
|
|
9608
|
+
sourceKind: CESIUM_ION_SOURCE_KIND,
|
|
9609
|
+
externalNativeLayer: true,
|
|
9610
|
+
identifiable: false,
|
|
9611
|
+
sourceId: id,
|
|
9612
|
+
nativeLayerIds: [id],
|
|
9613
|
+
...isTileset ? {
|
|
9614
|
+
customLayerType: "3d-tiles",
|
|
9615
|
+
altitudeOffset
|
|
9616
|
+
} : {}
|
|
9617
|
+
}
|
|
9618
|
+
};
|
|
9619
|
+
}
|
|
9620
|
+
//#endregion
|
|
8367
9621
|
//#region src/credentials.ts
|
|
8368
9622
|
/**
|
|
8369
9623
|
* Project fields whose values are credentials. Keep this registry as the
|
|
@@ -8414,7 +9668,8 @@ const PROJECT_CREDENTIAL_FIELDS = {
|
|
|
8414
9668
|
*/
|
|
8415
9669
|
const PUBLISHABLE_PLUGIN_SETTINGS = {
|
|
8416
9670
|
"maplibre-gl-swipe": null,
|
|
8417
|
-
"maplibre-gl-components": ["legend", "colorbar"]
|
|
9671
|
+
"maplibre-gl-components": ["legend", "colorbar"],
|
|
9672
|
+
"maplibre-gl-time-slider": null
|
|
8418
9673
|
};
|
|
8419
9674
|
/**
|
|
8420
9675
|
* Hash a credential value to a short, stable 64-bit token: two independent
|
|
@@ -8709,8 +9964,9 @@ const MAX_LAYER_LIBRARY_ENTRIES = 500;
|
|
|
8709
9964
|
* Metadata keys dropped when capturing an entry. `resolvedUrl` is the dev-server
|
|
8710
9965
|
* proxy rewrite of an XYZ template — a per-session artifact that must not be
|
|
8711
9966
|
* baked into a saved source (the same reason `prepareLayerForSave` strips it).
|
|
9967
|
+
* `geometryEdited` tracks changes to the live layer, not a library source.
|
|
8712
9968
|
*/
|
|
8713
|
-
const TRANSIENT_METADATA_KEYS = ["resolvedUrl"];
|
|
9969
|
+
const TRANSIENT_METADATA_KEYS = ["resolvedUrl", "geometryEdited"];
|
|
8714
9970
|
function nonEmptyString(value) {
|
|
8715
9971
|
return typeof value === "string" && value.trim() !== "";
|
|
8716
9972
|
}
|
|
@@ -8729,6 +9985,7 @@ function hasRestorableLayerSource(layer) {
|
|
|
8729
9985
|
if (nonEmptyString(source.url)) return true;
|
|
8730
9986
|
if (nonEmptyString(source.data)) return true;
|
|
8731
9987
|
if (Array.isArray(source.tiles) && source.tiles.some(nonEmptyString)) return true;
|
|
9988
|
+
if (cesiumIonAssetId(layer) !== null) return true;
|
|
8732
9989
|
return nonEmptyString((layer.metadata ?? {}).originalUrl);
|
|
8733
9990
|
}
|
|
8734
9991
|
/**
|
|
@@ -9333,6 +10590,8 @@ function createLayerLibraryEntryId() {
|
|
|
9333
10590
|
}
|
|
9334
10591
|
//#endregion
|
|
9335
10592
|
//#region src/store.ts
|
|
10593
|
+
/** Reserved Identify target for querying every visible queryable layer at once. */
|
|
10594
|
+
const IDENTIFY_ALL_LAYERS_ID = "__geolibre_identify_all_layers__";
|
|
9336
10595
|
const MAX_RECENT_PROJECTS = 10;
|
|
9337
10596
|
/**
|
|
9338
10597
|
* A fresh, inactive collaboration slice (no live session). Frozen (like
|
|
@@ -9571,6 +10830,7 @@ const useAppStore = create()(temporal((set, get) => ({
|
|
|
9571
10830
|
basemapStyleUrl: DEFAULT_BASEMAP,
|
|
9572
10831
|
basemapVisible: true,
|
|
9573
10832
|
basemapOpacity: 1,
|
|
10833
|
+
blankBackgroundColor: null,
|
|
9574
10834
|
layers: [],
|
|
9575
10835
|
layerGroups: [],
|
|
9576
10836
|
preferences: DEFAULT_PROJECT_PREFERENCES,
|
|
@@ -9589,6 +10849,7 @@ const useAppStore = create()(temporal((set, get) => ({
|
|
|
9589
10849
|
mapLayout: { ...DEFAULT_MAP_GRID_LAYOUT },
|
|
9590
10850
|
secondaryMapViews: [],
|
|
9591
10851
|
primaryMapLabel: "",
|
|
10852
|
+
primaryRenderer: DEFAULT_PRIMARY_RENDERER,
|
|
9592
10853
|
copiedLayerStyle: null,
|
|
9593
10854
|
selectedLayerId: null,
|
|
9594
10855
|
selectedFeatureId: null,
|
|
@@ -9602,7 +10863,9 @@ const useAppStore = create()(temporal((set, get) => ({
|
|
|
9602
10863
|
metadata: {},
|
|
9603
10864
|
recentProjects: [],
|
|
9604
10865
|
attributeFilter: "",
|
|
10866
|
+
deploymentCapabilities: ALL_DEPLOYMENT_CAPABILITIES,
|
|
9605
10867
|
collaboration: DEFAULT_COLLABORATION_STATE,
|
|
10868
|
+
capabilities: createDefaultAppCapabilities(),
|
|
9606
10869
|
ui: {
|
|
9607
10870
|
processingOpen: false,
|
|
9608
10871
|
processingInitialTool: null,
|
|
@@ -9627,6 +10890,7 @@ const useAppStore = create()(temporal((set, get) => ({
|
|
|
9627
10890
|
storymapPanelOpen: false,
|
|
9628
10891
|
storymapPresenting: false,
|
|
9629
10892
|
storymapReturnToEditor: false,
|
|
10893
|
+
storymapLayerOpacity: {},
|
|
9630
10894
|
storymapComposingId: null,
|
|
9631
10895
|
batchToolsOpen: false,
|
|
9632
10896
|
modelBuilderOpen: false,
|
|
@@ -9799,6 +11063,10 @@ const useAppStore = create()(temporal((set, get) => ({
|
|
|
9799
11063
|
primaryMapLabel: label,
|
|
9800
11064
|
isDirty: true
|
|
9801
11065
|
}),
|
|
11066
|
+
setPrimaryRenderer: (renderer) => set((s) => s.primaryRenderer === renderer ? s : {
|
|
11067
|
+
primaryRenderer: renderer,
|
|
11068
|
+
isDirty: true
|
|
11069
|
+
}),
|
|
9802
11070
|
setSecondaryMapLabel: (id, label) => set((s) => {
|
|
9803
11071
|
let changed = false;
|
|
9804
11072
|
const secondaryMapViews = s.secondaryMapViews.map((pane) => {
|
|
@@ -9880,6 +11148,10 @@ const useAppStore = create()(temporal((set, get) => ({
|
|
|
9880
11148
|
basemapOpacity: opacity,
|
|
9881
11149
|
isDirty: true
|
|
9882
11150
|
}),
|
|
11151
|
+
setBlankBackgroundColor: (color) => set({
|
|
11152
|
+
blankBackgroundColor: normalizeBlankBackgroundColor(color),
|
|
11153
|
+
isDirty: true
|
|
11154
|
+
}),
|
|
9883
11155
|
setPreferences: (preferences) => set({
|
|
9884
11156
|
preferences,
|
|
9885
11157
|
isDirty: true
|
|
@@ -9996,8 +11268,23 @@ const useAppStore = create()(temporal((set, get) => ({
|
|
|
9996
11268
|
setStorymapPresenting: (presenting, returnToEditor = false) => set((s) => ({ ui: {
|
|
9997
11269
|
...s.ui,
|
|
9998
11270
|
storymapPresenting: presenting,
|
|
9999
|
-
storymapReturnToEditor: presenting ? returnToEditor : false
|
|
11271
|
+
storymapReturnToEditor: presenting ? returnToEditor : false,
|
|
11272
|
+
storymapLayerOpacity: {}
|
|
10000
11273
|
} })),
|
|
11274
|
+
setStorymapLayerOpacity: (changes) => set((s) => {
|
|
11275
|
+
const next = { ...s.ui.storymapLayerOpacity };
|
|
11276
|
+
let changed = false;
|
|
11277
|
+
for (const [layerId, opacity] of Object.entries(changes)) {
|
|
11278
|
+
const clamped = Math.min(1, Math.max(0, opacity));
|
|
11279
|
+
if (next[layerId] === clamped) continue;
|
|
11280
|
+
next[layerId] = clamped;
|
|
11281
|
+
changed = true;
|
|
11282
|
+
}
|
|
11283
|
+
return changed ? { ui: {
|
|
11284
|
+
...s.ui,
|
|
11285
|
+
storymapLayerOpacity: next
|
|
11286
|
+
} } : s;
|
|
11287
|
+
}),
|
|
10001
11288
|
setStorymapComposing: (chapterId) => set((s) => ({ ui: {
|
|
10002
11289
|
...s.ui,
|
|
10003
11290
|
storymapComposingId: chapterId
|
|
@@ -10229,6 +11516,7 @@ const useAppStore = create()(temporal((set, get) => ({
|
|
|
10229
11516
|
projectName: name,
|
|
10230
11517
|
isDirty: true
|
|
10231
11518
|
}),
|
|
11519
|
+
setDeploymentCapabilities: (capabilities) => set({ deploymentCapabilities: new Set(capabilities) }),
|
|
10232
11520
|
setRecentProjects: (projects) => set({ recentProjects: normalizeRecentProjects(projects) }),
|
|
10233
11521
|
rememberRecentProject: (entry) => set((s) => ({ recentProjects: normalizeRecentProjects([entry, ...s.recentProjects]) })),
|
|
10234
11522
|
forgetRecentProject: (path) => {
|
|
@@ -10239,6 +11527,11 @@ const useAppStore = create()(temporal((set, get) => ({
|
|
|
10239
11527
|
markSaved: () => set({ isDirty: false }),
|
|
10240
11528
|
addLayer: (layer, beforeLayerId = null) => set((s) => {
|
|
10241
11529
|
const layers = [...s.layers];
|
|
11530
|
+
const { sourcePath } = layer;
|
|
11531
|
+
if (sourcePath && (!/^[a-z][a-z0-9+.-]*:\/\//i.test(sourcePath) || /^(content|file):\/\//i.test(sourcePath)) && isSourceDerivedLayerName(layer.name, sourcePath)) layer = {
|
|
11532
|
+
...layer,
|
|
11533
|
+
name: uniqueImportedLayerName(layer.name, layers.map((item) => item.name))
|
|
11534
|
+
};
|
|
10242
11535
|
const beforeIndex = beforeLayerId ? layers.findIndex((l) => l.id === beforeLayerId) : -1;
|
|
10243
11536
|
const layerWithBeforeId = beforeLayerId && beforeIndex < 0 ? {
|
|
10244
11537
|
...layer,
|
|
@@ -10309,6 +11602,7 @@ const useAppStore = create()(temporal((set, get) => ({
|
|
|
10309
11602
|
};
|
|
10310
11603
|
}),
|
|
10311
11604
|
setLayerQuickFilters: (id, filters) => get().updateLayer(id, { quickFilters: filters.length > 0 ? filters : void 0 }),
|
|
11605
|
+
setLayerFilterExpression: (id, expression) => get().updateLayer(id, { filterExpression: expression ?? void 0 }),
|
|
10312
11606
|
setLayerVisibility: (id, visible) => get().updateLayer(id, { visible }),
|
|
10313
11607
|
setLayerOpacity: (id, opacity) => get().updateLayer(id, { opacity }),
|
|
10314
11608
|
setLayerStyle: (id, style) => set((s) => ({
|
|
@@ -10641,6 +11935,7 @@ const useAppStore = create()(temporal((set, get) => ({
|
|
|
10641
11935
|
...s.ui,
|
|
10642
11936
|
storymapPresenting: false,
|
|
10643
11937
|
storymapReturnToEditor: false,
|
|
11938
|
+
storymapLayerOpacity: {},
|
|
10644
11939
|
storymapPanelOpen: false,
|
|
10645
11940
|
storymapComposingId: null,
|
|
10646
11941
|
selectByExpressionOpen: false,
|
|
@@ -10676,6 +11971,7 @@ const useAppStore = create()(temporal((set, get) => ({
|
|
|
10676
11971
|
...s.ui,
|
|
10677
11972
|
storymapPresenting: presentStory,
|
|
10678
11973
|
storymapReturnToEditor: false,
|
|
11974
|
+
storymapLayerOpacity: {},
|
|
10679
11975
|
storymapPanelOpen: false,
|
|
10680
11976
|
storymapComposingId: null,
|
|
10681
11977
|
selectByExpressionOpen: false,
|
|
@@ -10693,6 +11989,51 @@ const useAppStore = create()(temporal((set, get) => ({
|
|
|
10693
11989
|
name: project.name,
|
|
10694
11990
|
openedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10695
11991
|
});
|
|
11992
|
+
},
|
|
11993
|
+
setAppRole: (role, options) => {
|
|
11994
|
+
set({ capabilities: {
|
|
11995
|
+
role,
|
|
11996
|
+
privileges: resolveRolePrivileges(role, options?.customPrivileges),
|
|
11997
|
+
reason: options?.reason
|
|
11998
|
+
} });
|
|
11999
|
+
},
|
|
12000
|
+
setAppPrivileges: (privileges, reason) => {
|
|
12001
|
+
set({ capabilities: {
|
|
12002
|
+
role: "custom",
|
|
12003
|
+
privileges: normalizeAppPrivileges(privileges) ?? [],
|
|
12004
|
+
reason
|
|
12005
|
+
} });
|
|
12006
|
+
},
|
|
12007
|
+
grantAppPrivilege: (privilege) => {
|
|
12008
|
+
const current = get().capabilities;
|
|
12009
|
+
if (current.privileges.includes(privilege)) return;
|
|
12010
|
+
const { [privilege]: _granted, ...privilegeReasons } = current.privilegeReasons ?? {};
|
|
12011
|
+
set({ capabilities: {
|
|
12012
|
+
...current,
|
|
12013
|
+
role: "custom",
|
|
12014
|
+
privileges: [...current.privileges, privilege],
|
|
12015
|
+
privilegeReasons
|
|
12016
|
+
} });
|
|
12017
|
+
},
|
|
12018
|
+
revokeAppPrivilege: (privilege, reason) => {
|
|
12019
|
+
const current = get().capabilities;
|
|
12020
|
+
const held = current.privileges.includes(privilege);
|
|
12021
|
+
if (!held && !reason) return;
|
|
12022
|
+
set({ capabilities: {
|
|
12023
|
+
...current,
|
|
12024
|
+
role: held ? "custom" : current.role,
|
|
12025
|
+
privileges: held ? current.privileges.filter((p) => p !== privilege) : current.privileges,
|
|
12026
|
+
privilegeReasons: reason ? {
|
|
12027
|
+
...current.privilegeReasons,
|
|
12028
|
+
[privilege]: reason
|
|
12029
|
+
} : current.privilegeReasons
|
|
12030
|
+
} });
|
|
12031
|
+
},
|
|
12032
|
+
resetAppCapabilities: () => {
|
|
12033
|
+
set({ capabilities: createDefaultAppCapabilities() });
|
|
12034
|
+
},
|
|
12035
|
+
hasAppPrivilege: (privilege) => {
|
|
12036
|
+
return hasAppPrivilege(get().capabilities, privilege);
|
|
10696
12037
|
}
|
|
10697
12038
|
}), {
|
|
10698
12039
|
partialize: (s) => ({
|
|
@@ -10701,10 +12042,11 @@ const useAppStore = create()(temporal((set, get) => ({
|
|
|
10701
12042
|
basemapStyleUrl: s.basemapStyleUrl,
|
|
10702
12043
|
basemapVisible: s.basemapVisible,
|
|
10703
12044
|
basemapOpacity: s.basemapOpacity,
|
|
12045
|
+
blankBackgroundColor: s.blankBackgroundColor,
|
|
10704
12046
|
storymap: s.storymap,
|
|
10705
12047
|
comments: s.comments
|
|
10706
12048
|
}),
|
|
10707
|
-
equality: (a, b) => a.basemapStyleUrl === b.basemapStyleUrl && a.basemapVisible === b.basemapVisible && a.basemapOpacity === b.basemapOpacity && a.storymap === b.storymap && shallow(a.layers, b.layers) && shallow(a.comments, b.comments) && layerGroupsEqualForHistory(a.layerGroups, b.layerGroups),
|
|
12049
|
+
equality: (a, b) => a.basemapStyleUrl === b.basemapStyleUrl && a.basemapVisible === b.basemapVisible && a.basemapOpacity === b.basemapOpacity && a.blankBackgroundColor === b.blankBackgroundColor && a.storymap === b.storymap && shallow(a.layers, b.layers) && shallow(a.comments, b.comments) && layerGroupsEqualForHistory(a.layerGroups, b.layerGroups),
|
|
10708
12050
|
limit: 100,
|
|
10709
12051
|
handleSet: (baseHandleSet) => {
|
|
10710
12052
|
const debounced = leadingDebounce(baseHandleSet, getHistoryCoalesceMs);
|
|
@@ -10829,6 +12171,19 @@ function clearHistory() {
|
|
|
10829
12171
|
notifyProjectRestoreHistory();
|
|
10830
12172
|
}
|
|
10831
12173
|
}
|
|
12174
|
+
/**
|
|
12175
|
+
* React hook for consuming application capability state for a specific privilege.
|
|
12176
|
+
*
|
|
12177
|
+
* @param privilege - The privilege to check.
|
|
12178
|
+
* @returns `{ granted: boolean, reason?: string }`
|
|
12179
|
+
*/
|
|
12180
|
+
function useAppCapability(privilege) {
|
|
12181
|
+
const capabilities = useAppStore((state) => state.capabilities);
|
|
12182
|
+
return {
|
|
12183
|
+
granted: capabilities.privileges.includes(privilege),
|
|
12184
|
+
reason: appPrivilegeReason(capabilities, privilege)
|
|
12185
|
+
};
|
|
12186
|
+
}
|
|
10832
12187
|
//#endregion
|
|
10833
12188
|
//#region src/selection-actions.ts
|
|
10834
12189
|
/**
|
|
@@ -11154,6 +12509,23 @@ function formatCameraAltitude(meters, unit) {
|
|
|
11154
12509
|
})} ${label}`;
|
|
11155
12510
|
}
|
|
11156
12511
|
//#endregion
|
|
12512
|
+
//#region src/pixel-format.ts
|
|
12513
|
+
/**
|
|
12514
|
+
* Trim a float sample to something readable. A 32-bit raster value decoded to a
|
|
12515
|
+
* JS double prints all 17 digits of its binary representation
|
|
12516
|
+
* (`48.11851119995117`), which is noise past the sensor's precision — six
|
|
12517
|
+
* significant digits is more than any COG carries. Integers are left alone so a
|
|
12518
|
+
* classification code is never shown in exponential form.
|
|
12519
|
+
*
|
|
12520
|
+
* @param value A decoded raster sample.
|
|
12521
|
+
* @returns The sample rendered for display.
|
|
12522
|
+
*/
|
|
12523
|
+
function formatPixelValue(value) {
|
|
12524
|
+
if (!Number.isFinite(value)) return String(value);
|
|
12525
|
+
if (Number.isInteger(value)) return String(value);
|
|
12526
|
+
return String(Number(value.toPrecision(6)));
|
|
12527
|
+
}
|
|
12528
|
+
//#endregion
|
|
11157
12529
|
//#region src/storymap-sample.ts
|
|
11158
12530
|
const SAMPLE_ASSET_BASE = "https://opengeos.org/maplibre-gl-storymaps/assets";
|
|
11159
12531
|
/**
|
|
@@ -11274,6 +12646,72 @@ function createSampleStoryMap() {
|
|
|
11274
12646
|
};
|
|
11275
12647
|
}
|
|
11276
12648
|
//#endregion
|
|
12649
|
+
//#region src/storymap-playback.ts
|
|
12650
|
+
/** The clamped opacity recorded for a layer, or undefined when untouched. */
|
|
12651
|
+
function recordedStoryOpacity(opacities, layerId) {
|
|
12652
|
+
const value = opacities?.[layerId];
|
|
12653
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return void 0;
|
|
12654
|
+
return Math.min(1, Math.max(0, value));
|
|
12655
|
+
}
|
|
12656
|
+
/**
|
|
12657
|
+
* The opacity a story presentation has applied to a layer.
|
|
12658
|
+
*
|
|
12659
|
+
* @param opacities The store's `ui.storymapLayerOpacity` record.
|
|
12660
|
+
* @param layerId Store layer id.
|
|
12661
|
+
* @returns The recorded opacity in `[0, 1]`, or `1` when the presentation has
|
|
12662
|
+
* not touched the layer (or nothing is presenting).
|
|
12663
|
+
*/
|
|
12664
|
+
function storyLayerOpacityFactor(opacities, layerId) {
|
|
12665
|
+
return recordedStoryOpacity(opacities, layerId) ?? 1;
|
|
12666
|
+
}
|
|
12667
|
+
/**
|
|
12668
|
+
* A layer whose opacity reflects the story presentation's fade.
|
|
12669
|
+
*
|
|
12670
|
+
* A chapter's opacity *replaces* the layer's own opacity rather than scaling
|
|
12671
|
+
* it, matching `MapController.setStoryLayerOpacity`, which writes the chapter
|
|
12672
|
+
* value as the absolute paint opacity; otherwise a layer set to 0.7 in the
|
|
12673
|
+
* Layers panel and faded to 0.4 by a chapter would draw its diagrams at 0.28
|
|
12674
|
+
* while its MapLibre layers draw at 0.4.
|
|
12675
|
+
*
|
|
12676
|
+
* Returns the same object when the presentation has not touched the layer (or
|
|
12677
|
+
* recorded the opacity it already has), so identity-keyed caches downstream
|
|
12678
|
+
* keep hitting.
|
|
12679
|
+
*
|
|
12680
|
+
* @param layer Store layer.
|
|
12681
|
+
* @param opacities The store's `ui.storymapLayerOpacity` record.
|
|
12682
|
+
*/
|
|
12683
|
+
function applyStoryLayerOpacity(layer, opacities) {
|
|
12684
|
+
const opacity = recordedStoryOpacity(opacities, layer.id);
|
|
12685
|
+
if (opacity === void 0 || opacity === layer.opacity) return layer;
|
|
12686
|
+
return {
|
|
12687
|
+
...layer,
|
|
12688
|
+
opacity
|
|
12689
|
+
};
|
|
12690
|
+
}
|
|
12691
|
+
/**
|
|
12692
|
+
* Whether a story presentation has faded a layer fully out.
|
|
12693
|
+
*
|
|
12694
|
+
* @param opacities The store's `ui.storymapLayerOpacity` record.
|
|
12695
|
+
* @param layerId Store layer id.
|
|
12696
|
+
*/
|
|
12697
|
+
function isStoryHiddenLayer(opacities, layerId) {
|
|
12698
|
+
return storyLayerOpacityFactor(opacities, layerId) === 0;
|
|
12699
|
+
}
|
|
12700
|
+
/**
|
|
12701
|
+
* The layers a reader can currently see during a story presentation: the
|
|
12702
|
+
* input minus those the active chapter has faded fully out. Outside a
|
|
12703
|
+
* presentation the input is returned as-is (same array identity).
|
|
12704
|
+
*
|
|
12705
|
+
* @param layers Store layers in panel order.
|
|
12706
|
+
* @param presenting The store's `ui.storymapPresenting` flag.
|
|
12707
|
+
* @param opacities The store's `ui.storymapLayerOpacity` record.
|
|
12708
|
+
*/
|
|
12709
|
+
function storyVisibleLayers(layers, presenting, opacities) {
|
|
12710
|
+
if (!presenting) return layers;
|
|
12711
|
+
if (!layers.some((layer) => isStoryHiddenLayer(opacities, layer.id))) return layers;
|
|
12712
|
+
return layers.filter((layer) => !isStoryHiddenLayer(opacities, layer.id));
|
|
12713
|
+
}
|
|
12714
|
+
//#endregion
|
|
11277
12715
|
//#region src/storymap-io.ts
|
|
11278
12716
|
/**
|
|
11279
12717
|
* Serialize a story map to pretty-printed JSON for export.
|
|
@@ -12631,6 +14069,6 @@ function currentEditorIdentity() {
|
|
|
12631
14069
|
return pickEditorIdentity(isActive ? selfName : void 0, readStoredAuthorName());
|
|
12632
14070
|
}
|
|
12633
14071
|
//#endregion
|
|
12634
|
-
export { BLANK_BASEMAP, BLEND_MODES, BUILT_IN_STYLE_PRESETS, CARTOCIUDAD_MIN_INTERVAL_MS, CARTOCIUDAD_PUBLIC_HOST, CHINA_BASEMAPS, DEFAULT_BASEMAP, DEFAULT_BLEND_MODE, DEFAULT_COLLABORATION_STATE, DEFAULT_DASHBOARD_COLUMNS, DEFAULT_EDITOR_IDENTITY, DEFAULT_EDITOR_TRACKING_CONFIG, DEFAULT_ELLIPSOID_ID, DEFAULT_FORWARD_GEOCODE_ENDPOINT, DEFAULT_GEOCODING_PROVIDER_ID, DEFAULT_LAYER_GROUP_OPACITY, DEFAULT_LAYER_STYLE, DEFAULT_LEGEND_CONFIG, DEFAULT_MAP_GRID_LAYOUT, DEFAULT_PRINT_LAYOUT, DEFAULT_PROJECT_NAME, DEFAULT_PROJECT_PREFERENCES, DEFAULT_REVERSE_GEOCODE_ENDPOINT, DEFAULT_ROUTING_ENDPOINT, DEFAULT_STORY_MAP, DUCKDB_VECTOR_FEATURE_WARN_COUNT, DUCKDB_VECTOR_ROUTE_BYTES, EARTH_MEAN_RADIUS_METERS, EDITOR_IDENTITY_STORAGE_KEY, ELEVATION_REQUEST_TIMEOUT_MS, ELLIPSOIDS, EXPRESSION_FUNCTION_CATEGORIES, ElevationFetchError, FEET_PER_METER, FEET_PER_MILE, GEOCODE_DISPLAY_NAME_KEY, GEOCODE_LAT_KEY, GEOCODE_LON_KEY, GEOCODE_PROVIDER_KEY, GEOCODE_SCORE_KEY, GEOCODE_STATUS_KEY, GEOCODING_PROVIDERS, GOOGLE_MAPS_API_KEY_HEADER, LARGE_VECTOR_FEATURE_THRESHOLD, LAYER_LIBRARY_BUNDLE_TYPE, LAYER_LIBRARY_BUNDLE_VERSION, LAYER_PALETTE, LAYER_TYPES, MAX_DASHBOARD_COLUMNS, MAX_DIAGRAM_FEATURES, MAX_DIAGRAM_SCAN_FEATURES, MAX_LAYER_LIBRARY_ENTRIES, MAX_LAYER_LIBRARY_ENTRY_BYTES, MAX_MAP_GRID_DIM, MAX_POINTS_PER_REQUEST, MAX_PROCESSING_HISTORY, MAX_REDACT_DEPTH, MERCATOR_METERS_PER_PIXEL_AT_ZOOM_0, METERS_PER_FOOT, METERS_PER_MILE, METERS_PER_NAUTICAL_MILE, MIN_DASHBOARD_COLUMNS, MIN_DIAGRAM_SIZE, NETCDF_IMAGE_SOURCE_KIND, NOMINATIM_MIN_INTERVAL_MS, NOMINATIM_PUBLIC_HOST, OPENFREEMAP_BASEMAPS, PHOTO_FULL_PROPERTY, PHOTO_PROPERTY, PLANETARY_BASEMAPS, PLANETARY_BASEMAP_GROUPS, PLANETARY_BASEMAP_SENTINEL_PREFIX, PLANET_SWITCHER_OPTIONS, POINTER_ELEVATION_CACHE_LIMIT, POINTER_ELEVATION_DEBOUNCE_MS, PROJECT_CREDENTIAL_FIELDS, PROJECT_VERSION, PROTOMAPS_BASEMAPS, PUBLIC_GEOCODE_ROW_CAP, PUBLISHABLE_PLUGIN_SETTINGS, RAMP_STYLE_KEYS, RASTER_APPEARANCE_STATE_KEYS, REGIONAL_BASEMAPS, REGIONAL_BASEMAP_GROUPS, REGIONAL_BASEMAP_SENTINEL_PREFIX, RoutingRequestError, SELECTION_MODES, SQL_QUERY_SOURCE_KIND, STYLE_LIBRARY_BUNDLE_TYPE, STYLE_LIBRARY_BUNDLE_VERSION, SYMBOL_STYLE_KEYS, VECTOR_COLOR_RAMPS, activeQuickFilters, applyCopiedLayerStyle, applyGroupEffects, applyJoinsToLayer, applyLayerJoins, applyLayerVirtualFields, applyMatchedSelection, applyProjectToStore, applySelectionMode, attributeFormFieldLabel, attributeLinkUrl, batchDecodePolylines, bodyLengthToEarth, buildForwardGeocodeUrl, buildIsochroneRequest, buildLayerPanelUnits, buildLayerTree, buildMatrixRequest, buildReverseGeocodeUrl, buildRouteRequest, canRedoProjectRestore, canSaveLayerToLibrary, canUndoProjectRestore, captureLayerLibraryEntry, cascadeLayerJoinRefresh, circleRadiusValue, clearExternalNativePaintBridge, clearHistory, clearQuickFilterValues, coerceAttributeFormValue, collectDiagramData, collectTransitiveJoinSourceIds, compareSequenceValues, compileFeatureExpression, compileQuickFilter, compileQuickFilters, controlRendersLayer, copyableLayerStyleKind, createDefaultMapView, createDefaultPrintLayout, createEmptyProject, createEqualIntervalBreaks, createGraduatedClassBreaks, createLayerLibraryEntryId, createPointerElevationResolver, createProjectTemplate, createQuantileBreaks, createSampleStoryMap, createStyleLibraryEntryId, csvRowsToGeocodeRequests, currentEditorIdentity, darkenHex, decodePolyline, decodePolylineDetailed, detachProjectCopy, detectNonGeographicCoordinates, diagramAnchor, diagramPixelSize, diagramsSuppressedByPointRenderer, dominantGeometry, drawMarkerPath, earthAreaToBody, earthLengthToBody, editorTrackingFieldNames, effectiveLayerRenderState, effectiveVectorRules, encodePolyline, ensureEditorTrackingFields, evaluateMapExpression, excludeHiddenFieldsFromGeojson, excludeHiddenFieldsFromProject, extractCopiedLayerStyle, extractStyleLibraryStyle, extrusionColorValue, extrusionHeightValue, featureSelectionId, fetchElevations, formatCameraAltitude, formatExpressionPreviewValue, formatPopupValue, formatRoundNum, generatorCircleRadiusValue, generatorSizeRange, geoJSONToPolylineStr, geocodeForward, geocodeMatchToFeature, geocodeReverse, geocoderMinIntervalMs, geocoderNeedsApiKey, geojsonHasZCoordinates, getActiveBodyRadiusRatio, getActiveEllipsoid, getActiveMeanRadiusMeters, getActiveSemiMajorAxisMeters, getAttributeFormField, getBuildEnvironment, getCesiumIonToken, getEllipsoid, getExternalNativePaintBridge, getGeocoderConfig, getGeocodingProvider, getGoogleMapsApiKey, getHistoryCoalesceMs, getMapboxAccessToken, getMaxHistoryFeatureCount, getPlanetaryBasemapById, getPlanetaryBasemapByStyleUrl, getProtomapsApiKey, getProtomapsStyleUrl, getRegionalBasemapById, getRegionalBasemapByStyleUrl, getRoundNum, getRoutingConfig, getRuntimeEnvironment, getSpatialExtensionPath, getVectorColorRamp, googleMapsApiKeyHeaderValue, groupMemberIndices, hasActiveQuickFilter, hasPathTraversal, hasRestorableLayerSource, hasSimpleStyleProperties, inferFieldTypes, inferLayerCapabilities, initialLayerStyle, interpolateColors, interpolateHexColor, interpolateRampColors, invertSelection, isAbsoluteFilesystemPath, isAllowedPluginManifestUrl, isAttributeFormFieldVisible, isCredentialFieldName, isDefaultPrintLayout, isDiagramStyleEnabled, isDuckDBQueryLayer, isExternalNativeLayerRecord, isGooglePhotorealisticTilesetUrl, isHexColor, isInitialLayerStyle, isInlineImageValue, isInternalPopupField, isIpadDesktopUserAgent, isMaintainedEditorTrackingField, isPopupClickEnabled, isPopupHoverEnabled, isRegionalBasemapSentinel, isSafePopupUrl, isStyleLibraryTargetLayer, isStyleSpecColor, isVectorColorExpression, isochroneResponseToFeatures, layerGroupDepth, layerGroupMoveability, layerJoinKey, layerLibraryConfigPatch, layerLibraryEntryNeedsLocalFile, layerPanelGroupHeaders, lineWidthValue, mapZoomStepOutputs, matchFeaturesByExpression, matrixResponseToFeatures, meanRadiusMeters, mercatorMetersPerPixelAtZoom0, metersWidthExpression, nextDelayMs, nextLayerPaletteColor, nominatimResultToFeature, nominatimReverseResultToDisplay, nonEmptyRecord, normalizeDashboardColumns, normalizeGeocodingProviderId, normalizeGroupContiguity, normalizeHexColor, normalizeLayerCapabilities, normalizeLayerLibraryEntries, normalizeMapLayout, normalizeMapViewState, normalizeModelGraph, normalizeModels, normalizePrintLayoutConfig, normalizeProcessingHistory, normalizeProjectComments, normalizeSecondaryMapViews, normalizeStoryMap, normalizeStyleLibraryEntries, normalizeWidgets, parseContours, parseHexColor, parseHexColorList, parseJsonExpression, parseLayerLibrary, parseProject, parseStoryMapCsv, parseStoryMapJson, parseStyleLibrary, parseValueMapText, persistedThreeDTilesRequestHeaders, pickEditorIdentity, planLayerLibraryAdd, pluginOwnsPaint, polylineStrToGeoJSON, popupFieldLabel, printLayoutConfigsEqual, projectFromStore, projectPathLabel, proportionalRadiusExpression, proportionalSizeRange, readStoredAuthorName, reapplyLayerJoins, redactCredentials, redactProjectCredentials, redactUrlCredentials, redo, registerProjectRestoreHistory, removeTrailingJsonCommas, reorderLayerGroupInPanel, requestIsochrone, requestMatrix, requestRoute, resolveConfiguredPopupTitle, resolveEditorTrackingConfig, resolveGeocoderConfig, resolveLayerCapabilities, resolveMapGrid, resolvePopupBody, resolvePopupRows, resolvePopupTitle, resolveSvgSource, resolveThreeDTilesRequestHeaders, rgbToHex, routeResponseToFeatures, rowCap, ruleBasedColorExpression, ruleBasedVisibilityFilter, sampleMapTerrain, sampleMapTerrainPoint, sampleRemoteElevations, sanitizeLayerStylePatch, scaleAltitudeToActiveBody, scaleDenomination, scaleSpan, scrubCommentsForRemovedLayers, scrubLegendForRemovedLayers, scrubPrintLayoutForLayers, scrubPrintLayoutForRemovedLayers, scrubWidgetsForRemovedLayers, serializeLayerLibrary, serializeProject, serializeStoryMapCsv, serializeStoryMapJson, serializeStyleLibrary, setActiveEllipsoidId, setExternalNativePaintBridge, setGeocodingFetch, setHistoryCoalesceMs, setMaxHistoryFeatureCount, setStoredAuthorName, shouldThrottle, shouldUseTiledRendering, simpleStyleNumberValue, stampFeatureCollectionEditorTracking, stampFeatureEditorTracking, stampFeaturePropertiesEditorTracking, storyMapHasContent, stringifyPopupValue, stripGoogleMapsApiKeyHeader, stripJoinFields, stripVirtualFieldColumns, styleValue, subscribeProjectRestoreHistory, substituteExpressionVariables, supportsBridgedOpacity, transformGeojsonElevation, trimHistoryBySize, undo, unescapePolyline, unmatchedGeocodeFeature, unresolvedLayerLibraryJoins, useAppStore, validateAttributeFormField, validateAttributeFormValues, validateMapExpression, valueMapLabelFor, valueMapToText, vectorCircleColorValue, vectorColorExpression, vectorFillColorValue, vectorFillOpacityValue, vectorLineColorValue, vectorOutlineColorValue, vectorStrokeWidthValue, visiblePopupFields, visiblePopupProperties };
|
|
14072
|
+
export { ALL_APP_PRIVILEGES, ALL_DEPLOYMENT_CAPABILITIES, BLANK_BASEMAP, BLEND_MODES, BUILT_IN_STYLE_PRESETS, CARTOCIUDAD_MIN_INTERVAL_MS, CARTOCIUDAD_PUBLIC_HOST, CESIUM_BASEMAPS, CESIUM_BING_AERIAL_ASSET_ID, CESIUM_ION_QUICK_PICKS, CESIUM_ION_SOURCE_KIND, CESIUM_KML_SOURCE_KIND, CESIUM_OSM_BUILDINGS_ASSET_ID, CHINA_BASEMAPS, CZML_QUICK_PICKS, CZML_SOURCE_KIND, DEFAULT_BASEMAP, DEFAULT_BLEND_MODE, DEFAULT_COLLABORATION_STATE, DEFAULT_DASHBOARD_COLUMNS, DEFAULT_EDITOR_IDENTITY, DEFAULT_EDITOR_TRACKING_CONFIG, DEFAULT_ELLIPSOID_ID, DEFAULT_FORWARD_GEOCODE_ENDPOINT, DEFAULT_GEOCODING_PROVIDER_ID, DEFAULT_LAYER_GROUP_OPACITY, DEFAULT_LAYER_STYLE, DEFAULT_LEGEND_CONFIG, DEFAULT_MAP_GRID_LAYOUT, DEFAULT_PRIMARY_RENDERER, DEFAULT_PRINT_LAYOUT, DEFAULT_PROJECT_NAME, DEFAULT_PROJECT_PREFERENCES, DEFAULT_REVERSE_GEOCODE_ENDPOINT, DEFAULT_ROUTING_ENDPOINT, DEFAULT_STORY_MAP, DEPLOYMENT_CAPABILITIES, DUCKDB_VECTOR_FEATURE_WARN_COUNT, DUCKDB_VECTOR_ROUTE_BYTES, EARTH_MEAN_RADIUS_METERS, EDITOR_IDENTITY_STORAGE_KEY, ELEVATION_REQUEST_TIMEOUT_MS, ELLIPSOIDS, EXPRESSION_FUNCTION_CATEGORIES, ElevationFetchError, FEET_PER_METER, FEET_PER_MILE, GEOCODE_DISPLAY_NAME_KEY, GEOCODE_LAT_KEY, GEOCODE_LON_KEY, GEOCODE_PROVIDER_KEY, GEOCODE_SCORE_KEY, GEOCODE_STATUS_KEY, GEOCODING_PROVIDERS, GOOGLE_MAPS_API_KEY_HEADER, IDENTIFY_ALL_LAYERS_ID, LABEL_NUMBER_LOCALES, LABEL_NUMBER_SAMPLE, LARGE_VECTOR_FEATURE_THRESHOLD, LAYER_LIBRARY_BUNDLE_TYPE, LAYER_LIBRARY_BUNDLE_VERSION, LAYER_PALETTE, LAYER_TYPES, MAX_DASHBOARD_COLUMNS, MAX_DIAGRAM_FEATURES, MAX_DIAGRAM_SCAN_FEATURES, MAX_LAYER_LIBRARY_ENTRIES, MAX_LAYER_LIBRARY_ENTRY_BYTES, MAX_MAP_GRID_DIM, MAX_POINTS_PER_REQUEST, MAX_PROCESSING_HISTORY, MAX_REDACT_DEPTH, MERCATOR_METERS_PER_PIXEL_AT_ZOOM_0, METERS_PER_FOOT, METERS_PER_MILE, METERS_PER_NAUTICAL_MILE, MIN_DASHBOARD_COLUMNS, MIN_DIAGRAM_SIZE, NETCDF_IMAGE_SOURCE_KIND, NOMINATIM_MIN_INTERVAL_MS, NOMINATIM_PUBLIC_HOST, NO_DEPLOYMENT_CAPABILITIES, OPENFREEMAP_BASEMAPS, PHOTO_FULL_PROPERTY, PHOTO_PROPERTY, PLANETARY_BASEMAPS, PLANETARY_BASEMAP_GROUPS, PLANETARY_BASEMAP_SENTINEL_PREFIX, PLANET_SWITCHER_OPTIONS, POINTER_ELEVATION_CACHE_LIMIT, POINTER_ELEVATION_DEBOUNCE_MS, PROJECT_CREDENTIAL_FIELDS, PROJECT_VERSION, PROTOMAPS_BASEMAPS, PUBLIC_GEOCODE_ROW_CAP, PUBLISHABLE_PLUGIN_SETTINGS, RAMP_STYLE_KEYS, RASTER_APPEARANCE_STATE_KEYS, REGIONAL_BASEMAPS, REGIONAL_BASEMAP_GROUPS, REGIONAL_BASEMAP_SENTINEL_PREFIX, ROLE_PRIVILEGES, RoutingRequestError, SELECTION_MODES, SQL_QUERY_SOURCE_KIND, STYLE_LIBRARY_BUNDLE_TYPE, STYLE_LIBRARY_BUNDLE_VERSION, SYMBOL_STYLE_KEYS, VECTOR_COLOR_RAMPS, activeLayerFilterExpression, activeQuickFilters, appPrivilegeReason, applyCopiedLayerStyle, applyGroupEffects, applyJoinsToLayer, applyLayerJoins, applyLayerVirtualFields, applyMatchedSelection, applyProjectToStore, applySelectionMode, applyStoryLayerOpacity, attributeFormFieldLabel, attributeLinkUrl, availableCesiumBasemap, basemapToCesiumImagery, batchDecodePolylines, bodyLengthToEarth, buildForwardGeocodeUrl, buildIsochroneRequest, buildLayerPanelUnits, buildLayerTree, buildMatrixRequest, buildReverseGeocodeUrl, buildRouteRequest, canRedoProjectRestore, canSaveLayerToLibrary, canUndoProjectRestore, captureLayerLibraryEntry, cascadeLayerJoinRefresh, cesiumIonAssetId, cesiumIonAssetKind, cesiumKmlSource, circleRadiusValue, clampLabelDecimals, clearExternalNativePaintBridge, clearHistory, clearQuickFilterValues, coerceAttributeFormValue, collectDiagramData, collectTransitiveJoinSourceIds, compareSequenceValues, compileFeatureExpression, compileLayerFilters, compileQuickFilter, compileQuickFilters, controlRendersLayer, copyableLayerStyleKind, createCesiumIonLayer, createCesiumKmlLayer, createCzmlLayer, createDefaultAppCapabilities, createDefaultMapView, createDefaultPrintLayout, createEmptyProject, createEqualIntervalBreaks, createGraduatedClassBreaks, createLayerLibraryEntryId, createPointerElevationResolver, createProjectTemplate, createQuantileBreaks, createSampleStoryMap, createStyleLibraryEntryId, csvRowsToGeocodeRequests, currentEditorIdentity, czmlSource, darkenHex, decodePolyline, decodePolylineDetailed, detachProjectCopy, detectNonGeographicCoordinates, diagramAnchor, diagramPixelSize, diagramsSuppressedByPointRenderer, documentLocale, dominantGeometry, drawMarkerPath, earthAreaToBody, earthLengthToBody, editorTrackingFieldNames, effectiveLayerRenderState, effectiveVectorRules, encodePolyline, ensureEditorTrackingFields, evaluateMapExpression, excludeHiddenFieldsFromGeojson, excludeHiddenFieldsFromProject, extractCopiedLayerStyle, extractStyleLibraryStyle, extrusionColorValue, extrusionHeightValue, featureSelectionId, fetchElevations, formatCameraAltitude, formatExpressionPreviewValue, formatLabelNumber, formatLabelNumberSample, formatPixelValue, formatPopupValue, formatRoundNum, generatorCircleRadiusValue, generatorSizeRange, geoJSONToPolylineStr, geocodeForward, geocodeMatchToFeature, geocodeReverse, geocoderMinIntervalMs, geocoderNeedsApiKey, geojsonHasZCoordinates, getActiveBodyRadiusRatio, getActiveEllipsoid, getActiveMeanRadiusMeters, getActiveSemiMajorAxisMeters, getAttributeFormField, getBuildEnvironment, getCesiumIonToken, getEllipsoid, getExternalNativePaintBridge, getGeocoderConfig, getGeocodingProvider, getGoogleMapsApiKey, getHistoryCoalesceMs, getMapboxAccessToken, getMaxHistoryFeatureCount, getPlanetaryBasemapById, getPlanetaryBasemapByStyleUrl, getProtomapsApiKey, getProtomapsStyleUrl, getRegionalBasemapById, getRegionalBasemapByStyleUrl, getRoundNum, getRoutingConfig, getRuntimeEnvironment, getSpatialExtensionPath, getVectorColorRamp, googleMapsApiKeyHeaderValue, groupMemberIndices, hasActiveLayerFilter, hasActiveQuickFilter, hasAppPrivilege, hasPathTraversal, hasRestorableLayerSource, hasSimpleStyleProperties, heatmapRampColors, horizontalBbox, inferFieldTypes, inferLayerCapabilities, initialLayerStyle, interpolateColors, interpolateHexColor, interpolateRampColors, intersectPrivileges, invertSelection, isAbsoluteFilesystemPath, isAllowedPluginManifestUrl, isAttributeFormFieldVisible, isCesiumIonLayer, isCesiumKmlLayer, isCesiumOnlyLayer, isCredentialFieldName, isCzmlLayer, isDefaultPrintLayout, isDeploymentCapability, isDiagramStyleEnabled, isDuckDBQueryLayer, isExternalNativeLayerRecord, isGooglePhotorealisticTilesetUrl, isHexColor, isInitialLayerStyle, isInlineImageValue, isInternalPopupField, isIpadDesktopUserAgent, isMaintainedEditorTrackingField, isPopupClickEnabled, isPopupHoverEnabled, isRegionalBasemapSentinel, isSafePopupUrl, isStoryHiddenLayer, isStyleLibraryTargetLayer, isStyleSpecColor, isVectorColorExpression, isochroneResponseToFeatures, labelFieldTextField, layerGroupDepth, layerGroupMoveability, layerJoinKey, layerLibraryConfigPatch, layerLibraryEntryNeedsLocalFile, layerPanelGroupHeaders, lineWidthValue, localFileName, mapZoomStepOutputs, matchFeaturesByExpression, matrixResponseToFeatures, meanRadiusMeters, mercatorMetersPerPixelAtZoom0, metersWidthExpression, nextDelayMs, nextLayerPaletteColor, nominatimResultToFeature, nominatimReverseResultToDisplay, nonEmptyRecord, normalizeAppPrivileges, normalizeBlankBackgroundColor, normalizeCesiumBasemap, normalizeDashboardColumns, normalizeGeocodingProviderId, normalizeGroupContiguity, normalizeHexColor, normalizeLayerCapabilities, normalizeLayerLibraryEntries, normalizeMapLayout, normalizeMapViewState, normalizeModelGraph, normalizeModels, normalizePrimaryRenderer, normalizePrintLayoutConfig, normalizeProcessingHistory, normalizeProjectComments, normalizeSecondaryMapViews, normalizeStoryMap, normalizeStyleLibraryEntries, normalizeWidgets, parseCesiumIonAssetId, parseContours, parseCzml, parseDeploymentCapabilities, parseHexColor, parseHexColorList, parseJsonExpression, parseLayerLibrary, parseProject, parseStoryMapCsv, parseStoryMapJson, parseStyleLibrary, parseValueMapText, persistedThreeDTilesRequestHeaders, pickEditorIdentity, planLayerLibraryAdd, pluginOwnsPaint, polylineStrToGeoJSON, popupFieldLabel, printLayoutConfigsEqual, projectFromStore, projectPathLabel, proportionalRadiusExpression, proportionalSizeRange, readStoredAuthorName, reapplyLayerJoins, redactCredentials, redactProjectCredentials, redactUrlCredentials, redo, registerProjectRestoreHistory, removeTrailingJsonCommas, reorderLayerGroupInPanel, requestIsochrone, requestMatrix, requestRoute, resolveConfiguredPopupTitle, resolveEditorTrackingConfig, resolveGeocoderConfig, resolveLabelNumberLocale, resolveLayerCapabilities, resolveMapGrid, resolvePopupBody, resolvePopupRows, resolvePopupTitle, resolveRolePrivileges, resolveSvgSource, resolveThreeDTilesRequestHeaders, rgbToHex, routeResponseToFeatures, rowCap, ruleBasedColorExpression, ruleBasedVisibilityFilter, sameCesiumImagery, sampleMapTerrain, sampleMapTerrainPoint, sampleRemoteElevations, sanitizeLayerStylePatch, scaleAltitudeToActiveBody, scaleDenomination, scaleSpan, scrubCommentsForRemovedLayers, scrubLegendForRemovedLayers, scrubPrintLayoutForLayers, scrubPrintLayoutForRemovedLayers, scrubWidgetsForRemovedLayers, serializeLayerLibrary, serializeProject, serializeStoryMapCsv, serializeStoryMapJson, serializeStyleLibrary, setActiveEllipsoidId, setExternalNativePaintBridge, setGeocodingFetch, setHistoryCoalesceMs, setMaxHistoryFeatureCount, setStoredAuthorName, shouldThrottle, shouldUseTiledRendering, simpleStyleNumberValue, stampFeatureCollectionEditorTracking, stampFeatureEditorTracking, stampFeaturePropertiesEditorTracking, storyLayerOpacityFactor, storyMapHasContent, storyVisibleLayers, stringifyPopupValue, stripGoogleMapsApiKeyHeader, stripJoinFields, stripVirtualFieldColumns, styleValue, subscribeProjectRestoreHistory, substituteExpressionVariables, supportsBridgedOpacity, transformGeojsonElevation, trimHistoryBySize, undo, unescapePolyline, uniqueImportedLayerName, unmatchedGeocodeFeature, unresolvedLayerLibraryJoins, useAppCapability, useAppStore, validateAttributeFormField, validateAttributeFormValues, validateMapExpression, valueMapLabelFor, valueMapToText, vectorCircleColorValue, vectorColorExpression, vectorFillColorValue, vectorFillOpacityValue, vectorLineColorValue, vectorOutlineColorValue, vectorStrokeWidthValue, visiblePopupFields, visiblePopupProperties };
|
|
12635
14073
|
|
|
12636
14074
|
//# sourceMappingURL=index.mjs.map
|