@geolibre/core 2.9.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.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,
@@ -1424,6 +1435,162 @@ function isRegionalBasemapSentinel(styleUrl) {
1424
1435
  * {@link CesiumBasemapImagery}): tiles to draw, nothing to draw, or "no raster
1425
1436
  * equivalent exists — pick your own default".
1426
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
+ }
1427
1594
  const OSM_CREDIT = "© <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors";
1428
1595
  /** EOX Maps serves these layers keyless under CC BY-SA 4.0. */
1429
1596
  const EOX_CREDIT = "<a href=\"https://maps.eox.at\">EOX Maps</a> (CC BY-SA 4.0)";
@@ -1488,8 +1655,10 @@ function toImagery(analogue) {
1488
1655
  */
1489
1656
  function sameCesiumImagery(a, b) {
1490
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;
1491
1660
  if (a.kind !== "xyz" || b.kind !== "xyz") return true;
1492
- return a.template === b.template && a.attribution === b.attribution && a.maximumLevel === b.maximumLevel && a.scheme === b.scheme && a.overlayTemplate === b.overlayTemplate;
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;
1493
1662
  }
1494
1663
  /**
1495
1664
  * `decodeURIComponent` that reports failure instead of throwing. A URL keeps a
@@ -1546,7 +1715,25 @@ function vectorStyleAnalogue(styleUrl) {
1546
1715
  * @param styleUrl - The project's `basemapStyleUrl`.
1547
1716
  * @returns What the globe should draw beneath the data layers.
1548
1717
  */
1549
- function basemapToCesiumImagery(styleUrl) {
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);
1550
1737
  if (styleUrl === "") return { kind: "none" };
1551
1738
  const url = styleUrl ?? "https://tiles.openfreemap.org/styles/liberty";
1552
1739
  const planetary = getPlanetaryBasemapByStyleUrl(url);
@@ -1576,8 +1763,42 @@ function basemapToCesiumImagery(styleUrl) {
1576
1763
  * GPX tracks with `<ele>` readings or LineStringZ/PointZ geometries. MapLibre's
1577
1764
  * 2D style layers ignore the third coordinate, so layers that want to render
1578
1765
  * their Z values use these helpers to detect and rescale elevations before
1579
- * handing the data to a deck.gl overlay.
1580
- */
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
+ }
1581
1802
  const hasZCache = /* @__PURE__ */ new WeakMap();
1582
1803
  /**
1583
1804
  * Returns true when any coordinate in the GeoJSON carries a finite, non-zero
@@ -3017,7 +3238,8 @@ const EXPRESSION_FUNCTION_CATEGORIES = [
3017
3238
  fn("concat", "[\"concat\", [\"get\", \"field\"], \" suffix\"]", "concat"),
3018
3239
  fn("upcase", "[\"upcase\", [\"get\", \"field\"]]", "upcase"),
3019
3240
  fn("downcase", "[\"downcase\", [\"get\", \"field\"]]", "downcase"),
3020
- 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")
3021
3243
  ]
3022
3244
  },
3023
3245
  {
@@ -3235,11 +3457,11 @@ function compileFeatureExpression(source, options = {}) {
3235
3457
  ok: false,
3236
3458
  errors: validation.errors
3237
3459
  };
3238
- const zoom = options.zoom ?? 0;
3460
+ const compiledZoom = options.zoom ?? 0;
3239
3461
  return {
3240
3462
  ok: true,
3241
3463
  errors: [],
3242
- evaluate: (feature) => expression.evaluateWithoutErrorHandling({ zoom }, {
3464
+ evaluate: (feature, zoom = compiledZoom) => expression.evaluateWithoutErrorHandling({ zoom }, {
3243
3465
  type: feature.geometry?.type ?? "Unknown",
3244
3466
  properties: feature.properties ?? {},
3245
3467
  ...feature.id !== void 0 ? { id: feature.id } : {},
@@ -3873,6 +4095,23 @@ function vectorFillOpacityValue(style, fallback) {
3873
4095
  return ruleOverrideValue(style, "fillOpacity", fallback);
3874
4096
  }
3875
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
+ /**
3876
4115
  * The proportional-size configuration, or `null` when proportional sizing
3877
4116
  * does not apply: disabled, no field chosen, non-finite or degenerate
3878
4117
  * (`maxValue <= minValue`) value range, or non-finite radii. This is the
@@ -4112,6 +4351,236 @@ function extrusionColorValue(style) {
4112
4351
  return parseJsonExpression(styleValue(style, "extrusionAdvancedStyleEnabled") ? styleValue(style, "extrusionColorExpression") : "") ?? flat;
4113
4352
  }
4114
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
4115
4584
  //#region src/external-native-paint.ts
4116
4585
  /**
4117
4586
  * Whether a layer is drawn by a **custom render layer** its control created — a
@@ -4805,6 +5274,10 @@ function valueToString(value) {
4805
5274
  function layerJoinKey(value) {
4806
5275
  return value === null || value === void 0 || typeof value === "number" && Number.isNaN(value) || valueToString(value) === "" ? null : valueToString(value);
4807
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
+ }
4808
5281
  /**
4809
5282
  * Remove every column previously added by `joins` (per their `addedFields`
4810
5283
  * bookkeeping) from a copy of `features`, restoring the pre-join properties.
@@ -4897,7 +5370,7 @@ function applyLayerJoins(features, joins, resolveSource) {
4897
5370
  }
4898
5371
  const lookup = /* @__PURE__ */ new Map();
4899
5372
  for (const jf of joinFeatures) {
4900
- const key = layerJoinKey(jf.properties?.[join.joinField]);
5373
+ const key = layerJoinKey(fieldValue(jf.properties, join.joinField));
4901
5374
  if (key === null || lookup.has(key)) continue;
4902
5375
  lookup.set(key, jf.properties ?? {});
4903
5376
  }
@@ -4905,17 +5378,17 @@ function applyLayerJoins(features, joins, resolveSource) {
4905
5378
  const targetKeys = /* @__PURE__ */ new Set();
4906
5379
  for (const feature of out) {
4907
5380
  const props = feature.properties;
4908
- const key = layerJoinKey(props[join.targetField]);
5381
+ const key = layerJoinKey(fieldValue(props, join.targetField));
4909
5382
  if (key !== null) targetKeys.add(key);
4910
5383
  const row = key === null ? void 0 : lookup.get(key);
4911
5384
  if (row) {
4912
5385
  matched += 1;
4913
- for (const [field, outputName] of fieldPairs) props[outputName] = row[field] !== void 0 ? row[field] : null;
5386
+ for (const [field, outputName] of fieldPairs) props[outputName] = fieldValue(row, field) ?? null;
4914
5387
  } else for (const [, outputName] of fieldPairs) props[outputName] = null;
4915
5388
  }
4916
5389
  let unmatchedJoin = 0;
4917
5390
  for (const jf of joinFeatures) {
4918
- const key = layerJoinKey(jf.properties?.[join.joinField]);
5391
+ const key = layerJoinKey(fieldValue(jf.properties, join.joinField));
4919
5392
  if (key !== null && !targetKeys.has(key)) unmatchedJoin += 1;
4920
5393
  }
4921
5394
  return {
@@ -5325,6 +5798,42 @@ function clearQuickFilterValues(filters) {
5325
5798
  }));
5326
5799
  }
5327
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
5328
5837
  //#region src/capabilities.ts
5329
5838
  /**
5330
5839
  * Default inferred capabilities for a layer based on its type and metadata.
@@ -5569,6 +6078,45 @@ function parseDeploymentCapabilities(raw) {
5569
6078
  return granted;
5570
6079
  }
5571
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
+ }
6119
+ //#endregion
5572
6120
  //#region src/history.ts
5573
6121
  /**
5574
6122
  * Runtime-settable window (ms) used to coalesce rapid history-producing changes
@@ -7345,6 +7893,7 @@ function parseProject(json) {
7345
7893
  secondaryMapViews,
7346
7894
  ...normalizeString(data.primaryMapLabel) ? { primaryMapLabel: normalizeString(data.primaryMapLabel) } : {}
7347
7895
  } : {},
7896
+ ...normalizePrimaryRenderer(data.primaryRenderer) ? { primaryRenderer: "cesium" } : {},
7348
7897
  ...styleLibrary.length > 0 ? { styleLibrary } : {},
7349
7898
  ...parsedComments.length > 0 ? { comments: parsedComments } : {},
7350
7899
  metadata: data.metadata ?? {}
@@ -7810,6 +8359,18 @@ function normalizeMapLayout(value) {
7810
8359
  };
7811
8360
  }
7812
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
+ /**
7813
8374
  * Coerce an untrusted `secondaryMapViews` array into valid
7814
8375
  * {@link SecondaryMapView} records, dropping entries without a usable id and
7815
8376
  * de-duplicating by id. Returns null when none are valid.
@@ -8011,6 +8572,7 @@ function normalizeProjectPreferences(preferences) {
8011
8572
  ellipsoidId: getEllipsoid(map.ellipsoidId).id,
8012
8573
  scaleUnit: normalizeScaleUnit(map.scaleUnit),
8013
8574
  showPointerElevation: normalizeBoolean(map.showPointerElevation, DEFAULT_PROJECT_PREFERENCES.map.showPointerElevation),
8575
+ cesiumBasemap: normalizeCesiumBasemap(map.cesiumBasemap),
8014
8576
  terrainEnabled: normalizeBoolean(map.terrainEnabled, DEFAULT_PROJECT_PREFERENCES.map.terrainEnabled),
8015
8577
  coordinateFormat: typeof map.coordinateFormat === "string" ? map.coordinateFormat : DEFAULT_PROJECT_PREFERENCES.map.coordinateFormat
8016
8578
  },
@@ -8148,8 +8710,9 @@ function isPlainObject$2(value) {
8148
8710
  return prototype === Object.prototype || prototype === null;
8149
8711
  }
8150
8712
  function normalizeLayer(layer) {
8151
- const { capabilities: rawCapabilities, ...rest } = layer;
8713
+ const { capabilities: rawCapabilities, filterExpression: rawFilterExpression, ...rest } = layer;
8152
8714
  const capabilities = normalizeLayerCapabilities(rawCapabilities);
8715
+ const filterExpression = Array.isArray(rawFilterExpression) && rawFilterExpression.length > 0 && validateMapExpression(JSON.stringify(rawFilterExpression), { expectedType: "boolean" }).ok ? rawFilterExpression : void 0;
8153
8716
  return {
8154
8717
  ...rest,
8155
8718
  style: {
@@ -8160,7 +8723,8 @@ function normalizeLayer(layer) {
8160
8723
  opacity: layer.opacity ?? 1,
8161
8724
  metadata: layer.metadata ?? {},
8162
8725
  source: layer.source ?? {},
8163
- ...capabilities ? { capabilities } : {}
8726
+ ...capabilities ? { capabilities } : {},
8727
+ ...filterExpression ? { filterExpression } : {}
8164
8728
  };
8165
8729
  }
8166
8730
  function normalizeProjectComments(rawComments) {
@@ -8263,6 +8827,7 @@ function projectFromStore(state) {
8263
8827
  secondaryMapViews,
8264
8828
  ...normalizeString(state.primaryMapLabel) ? { primaryMapLabel: normalizeString(state.primaryMapLabel) } : {}
8265
8829
  } : {},
8830
+ ...normalizePrimaryRenderer(state.primaryRenderer) ? { primaryRenderer: "cesium" } : {},
8266
8831
  ...styleLibrary.length > 0 ? { styleLibrary } : {},
8267
8832
  ...comments.length > 0 ? { comments } : {},
8268
8833
  metadata: state.metadata
@@ -8274,6 +8839,13 @@ function hasRestorableSourceUrl(layer) {
8274
8839
  return typeof sourceUrl === "string" && sourceUrl.trim() !== "" || typeof originalUrl === "string" && originalUrl.trim() !== "";
8275
8840
  }
8276
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
+ }
8277
8849
  if (layer.timeFilter !== void 0) {
8278
8850
  const { timeFilter: _timeFilter, ...rest } = layer;
8279
8851
  layer = rest;
@@ -8394,6 +8966,7 @@ function applyProjectToStore(project) {
8394
8966
  mapLayout,
8395
8967
  secondaryMapViews,
8396
8968
  primaryMapLabel: normalizeString(project.primaryMapLabel),
8969
+ primaryRenderer: normalizePrimaryRenderer(project.primaryRenderer) ?? "maplibre",
8397
8970
  projectStyleLibrary: normalizeStyleLibraryEntries(project.styleLibrary),
8398
8971
  comments: scrubbedComments,
8399
8972
  metadata: project.metadata
@@ -8738,6 +9311,313 @@ function rescaleEntryCount(state) {
8738
9311
  return 1;
8739
9312
  }
8740
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
8741
9621
  //#region src/credentials.ts
8742
9622
  /**
8743
9623
  * Project fields whose values are credentials. Keep this registry as the
@@ -8788,7 +9668,8 @@ const PROJECT_CREDENTIAL_FIELDS = {
8788
9668
  */
8789
9669
  const PUBLISHABLE_PLUGIN_SETTINGS = {
8790
9670
  "maplibre-gl-swipe": null,
8791
- "maplibre-gl-components": ["legend", "colorbar"]
9671
+ "maplibre-gl-components": ["legend", "colorbar"],
9672
+ "maplibre-gl-time-slider": null
8792
9673
  };
8793
9674
  /**
8794
9675
  * Hash a credential value to a short, stable 64-bit token: two independent
@@ -9083,8 +9964,9 @@ const MAX_LAYER_LIBRARY_ENTRIES = 500;
9083
9964
  * Metadata keys dropped when capturing an entry. `resolvedUrl` is the dev-server
9084
9965
  * proxy rewrite of an XYZ template — a per-session artifact that must not be
9085
9966
  * baked into a saved source (the same reason `prepareLayerForSave` strips it).
9967
+ * `geometryEdited` tracks changes to the live layer, not a library source.
9086
9968
  */
9087
- const TRANSIENT_METADATA_KEYS = ["resolvedUrl"];
9969
+ const TRANSIENT_METADATA_KEYS = ["resolvedUrl", "geometryEdited"];
9088
9970
  function nonEmptyString(value) {
9089
9971
  return typeof value === "string" && value.trim() !== "";
9090
9972
  }
@@ -9103,6 +9985,7 @@ function hasRestorableLayerSource(layer) {
9103
9985
  if (nonEmptyString(source.url)) return true;
9104
9986
  if (nonEmptyString(source.data)) return true;
9105
9987
  if (Array.isArray(source.tiles) && source.tiles.some(nonEmptyString)) return true;
9988
+ if (cesiumIonAssetId(layer) !== null) return true;
9106
9989
  return nonEmptyString((layer.metadata ?? {}).originalUrl);
9107
9990
  }
9108
9991
  /**
@@ -9966,6 +10849,7 @@ const useAppStore = create()(temporal((set, get) => ({
9966
10849
  mapLayout: { ...DEFAULT_MAP_GRID_LAYOUT },
9967
10850
  secondaryMapViews: [],
9968
10851
  primaryMapLabel: "",
10852
+ primaryRenderer: DEFAULT_PRIMARY_RENDERER,
9969
10853
  copiedLayerStyle: null,
9970
10854
  selectedLayerId: null,
9971
10855
  selectedFeatureId: null,
@@ -10006,6 +10890,7 @@ const useAppStore = create()(temporal((set, get) => ({
10006
10890
  storymapPanelOpen: false,
10007
10891
  storymapPresenting: false,
10008
10892
  storymapReturnToEditor: false,
10893
+ storymapLayerOpacity: {},
10009
10894
  storymapComposingId: null,
10010
10895
  batchToolsOpen: false,
10011
10896
  modelBuilderOpen: false,
@@ -10178,6 +11063,10 @@ const useAppStore = create()(temporal((set, get) => ({
10178
11063
  primaryMapLabel: label,
10179
11064
  isDirty: true
10180
11065
  }),
11066
+ setPrimaryRenderer: (renderer) => set((s) => s.primaryRenderer === renderer ? s : {
11067
+ primaryRenderer: renderer,
11068
+ isDirty: true
11069
+ }),
10181
11070
  setSecondaryMapLabel: (id, label) => set((s) => {
10182
11071
  let changed = false;
10183
11072
  const secondaryMapViews = s.secondaryMapViews.map((pane) => {
@@ -10379,8 +11268,23 @@ const useAppStore = create()(temporal((set, get) => ({
10379
11268
  setStorymapPresenting: (presenting, returnToEditor = false) => set((s) => ({ ui: {
10380
11269
  ...s.ui,
10381
11270
  storymapPresenting: presenting,
10382
- storymapReturnToEditor: presenting ? returnToEditor : false
11271
+ storymapReturnToEditor: presenting ? returnToEditor : false,
11272
+ storymapLayerOpacity: {}
10383
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
+ }),
10384
11288
  setStorymapComposing: (chapterId) => set((s) => ({ ui: {
10385
11289
  ...s.ui,
10386
11290
  storymapComposingId: chapterId
@@ -10623,6 +11527,11 @@ const useAppStore = create()(temporal((set, get) => ({
10623
11527
  markSaved: () => set({ isDirty: false }),
10624
11528
  addLayer: (layer, beforeLayerId = null) => set((s) => {
10625
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
+ };
10626
11535
  const beforeIndex = beforeLayerId ? layers.findIndex((l) => l.id === beforeLayerId) : -1;
10627
11536
  const layerWithBeforeId = beforeLayerId && beforeIndex < 0 ? {
10628
11537
  ...layer,
@@ -10693,6 +11602,7 @@ const useAppStore = create()(temporal((set, get) => ({
10693
11602
  };
10694
11603
  }),
10695
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 }),
10696
11606
  setLayerVisibility: (id, visible) => get().updateLayer(id, { visible }),
10697
11607
  setLayerOpacity: (id, opacity) => get().updateLayer(id, { opacity }),
10698
11608
  setLayerStyle: (id, style) => set((s) => ({
@@ -11025,6 +11935,7 @@ const useAppStore = create()(temporal((set, get) => ({
11025
11935
  ...s.ui,
11026
11936
  storymapPresenting: false,
11027
11937
  storymapReturnToEditor: false,
11938
+ storymapLayerOpacity: {},
11028
11939
  storymapPanelOpen: false,
11029
11940
  storymapComposingId: null,
11030
11941
  selectByExpressionOpen: false,
@@ -11060,6 +11971,7 @@ const useAppStore = create()(temporal((set, get) => ({
11060
11971
  ...s.ui,
11061
11972
  storymapPresenting: presentStory,
11062
11973
  storymapReturnToEditor: false,
11974
+ storymapLayerOpacity: {},
11063
11975
  storymapPanelOpen: false,
11064
11976
  storymapComposingId: null,
11065
11977
  selectByExpressionOpen: false,
@@ -11734,6 +12646,72 @@ function createSampleStoryMap() {
11734
12646
  };
11735
12647
  }
11736
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
11737
12715
  //#region src/storymap-io.ts
11738
12716
  /**
11739
12717
  * Serialize a story map to pretty-printed JSON for export.
@@ -13091,6 +14069,6 @@ function currentEditorIdentity() {
13091
14069
  return pickEditorIdentity(isActive ? selfName : void 0, readStoredAuthorName());
13092
14070
  }
13093
14071
  //#endregion
13094
- export { ALL_APP_PRIVILEGES, ALL_DEPLOYMENT_CAPABILITIES, 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, 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, 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, activeQuickFilters, appPrivilegeReason, applyCopiedLayerStyle, applyGroupEffects, applyJoinsToLayer, applyLayerJoins, applyLayerVirtualFields, applyMatchedSelection, applyProjectToStore, applySelectionMode, attributeFormFieldLabel, attributeLinkUrl, basemapToCesiumImagery, 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, createDefaultAppCapabilities, 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, 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, hasActiveQuickFilter, hasAppPrivilege, hasPathTraversal, hasRestorableLayerSource, hasSimpleStyleProperties, inferFieldTypes, inferLayerCapabilities, initialLayerStyle, interpolateColors, interpolateHexColor, interpolateRampColors, intersectPrivileges, invertSelection, isAbsoluteFilesystemPath, isAllowedPluginManifestUrl, isAttributeFormFieldVisible, isCredentialFieldName, isDefaultPrintLayout, isDeploymentCapability, 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, normalizeAppPrivileges, normalizeBlankBackgroundColor, normalizeDashboardColumns, normalizeGeocodingProviderId, normalizeGroupContiguity, normalizeHexColor, normalizeLayerCapabilities, normalizeLayerLibraryEntries, normalizeMapLayout, normalizeMapViewState, normalizeModelGraph, normalizeModels, normalizePrintLayoutConfig, normalizeProcessingHistory, normalizeProjectComments, normalizeSecondaryMapViews, normalizeStoryMap, normalizeStyleLibraryEntries, normalizeWidgets, parseContours, 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, 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, storyMapHasContent, stringifyPopupValue, stripGoogleMapsApiKeyHeader, stripJoinFields, stripVirtualFieldColumns, styleValue, subscribeProjectRestoreHistory, substituteExpressionVariables, supportsBridgedOpacity, transformGeojsonElevation, trimHistoryBySize, undo, unescapePolyline, unmatchedGeocodeFeature, unresolvedLayerLibraryJoins, useAppCapability, 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 };
13095
14073
 
13096
14074
  //# sourceMappingURL=index.mjs.map