@geolibre/core 3.2.0 → 3.4.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.
@@ -0,0 +1,3803 @@
1
+ //#region src/cesium-imagery.d.ts
2
+ /**
3
+ * Maps the project's basemap onto imagery the Cesium globe can render.
4
+ *
5
+ * The 2D panes render `basemapStyleUrl` through MapLibre, which understands
6
+ * vector GL styles and GeoLibre's `geolibre://` sentinels. Cesium understands
7
+ * neither: it draws imagery from tile providers only. So a globe pane that
8
+ * simply mounted the store basemap would show nothing, which is why the pane
9
+ * used to hardcode Ion World Imagery (with a token) or OpenStreetMap (without)
10
+ * and ignore the user's choice entirely — leaving a bright globe beside dark 2D
11
+ * panes and breaking the "same map, different projection" promise the multi-map
12
+ * grid makes.
13
+ *
14
+ * This module is the translation. It is deliberately **engine-free** — it
15
+ * returns a plain descriptor and never imports Cesium — so it lives in
16
+ * `@geolibre/core` beside the basemap catalogs it reads, the 2D and 3D
17
+ * renderers share one source of truth, and the Cesium chunk boundary in
18
+ * `vite.config.ts` stays intact.
19
+ *
20
+ * Three outcomes are possible, and the caller has to handle all three (see
21
+ * {@link CesiumBasemapImagery}): tiles to draw, nothing to draw, or "no raster
22
+ * equivalent exists — pick your own default".
23
+ */
24
+ /** Stable project IDs and the corresponding Cesium bundled thumbnails / ion assets. */
25
+ declare const CESIUM_BASEMAPS: readonly [{
26
+ readonly id: "project";
27
+ readonly name: "Project basemap";
28
+ readonly icon: "naturalEarthII.png";
29
+ }, {
30
+ readonly id: "natural-earth";
31
+ readonly name: "Natural Earth II";
32
+ readonly icon: "naturalEarthII.png";
33
+ }, {
34
+ readonly id: "bing-aerial";
35
+ readonly name: "Bing Maps Aerial";
36
+ readonly icon: "bingAerial.png";
37
+ readonly assetId: 2;
38
+ }, {
39
+ readonly id: "bing-labels";
40
+ readonly name: "Bing Maps Aerial with Labels";
41
+ readonly icon: "bingAerialLabels.png";
42
+ readonly assetId: 3;
43
+ }, {
44
+ readonly id: "bing-roads";
45
+ readonly name: "Bing Maps Roads";
46
+ readonly icon: "bingRoads.png";
47
+ readonly assetId: 4;
48
+ }, {
49
+ readonly id: "sentinel-2";
50
+ readonly name: "Sentinel-2";
51
+ readonly icon: "sentinel-2.png";
52
+ readonly assetId: 3954;
53
+ }, {
54
+ readonly id: "blue-marble";
55
+ readonly name: "Blue Marble";
56
+ readonly icon: "blueMarble.png";
57
+ readonly assetId: 3845;
58
+ }, {
59
+ readonly id: "earth-at-night";
60
+ readonly name: "Earth at night";
61
+ readonly icon: "earthAtNight.png";
62
+ readonly assetId: 3812;
63
+ }, {
64
+ readonly id: "google-satellite";
65
+ readonly name: "Google Maps Satellite";
66
+ readonly icon: "googleSatellite.png";
67
+ readonly assetId: 3830182;
68
+ }, {
69
+ readonly id: "google-labels";
70
+ readonly name: "Google Maps Satellite with Labels";
71
+ readonly icon: "googleSatelliteLabels.png";
72
+ readonly assetId: 3830183;
73
+ }, {
74
+ readonly id: "google-roads";
75
+ readonly name: "Google Maps Roadmap";
76
+ readonly icon: "googleRoadmap.png";
77
+ readonly assetId: 3830184;
78
+ }, {
79
+ readonly id: "google-contour";
80
+ readonly name: "Google Maps Contour";
81
+ readonly icon: "googleContour.png";
82
+ readonly assetId: 3830186;
83
+ }, {
84
+ readonly id: "azure-aerial";
85
+ readonly name: "Azure Maps Aerial";
86
+ readonly icon: "azureAerial.png";
87
+ readonly assetId: 3891168;
88
+ }, {
89
+ readonly id: "azure-roads";
90
+ readonly name: "Azure Maps Roads";
91
+ readonly icon: "azureRoads.png";
92
+ readonly assetId: 3891169;
93
+ }, {
94
+ readonly id: "esri-imagery";
95
+ readonly name: "ArcGIS World Imagery";
96
+ readonly icon: "ArcGisMapServiceWorldImagery.png";
97
+ readonly category: "Other";
98
+ readonly service: "World_Imagery";
99
+ }, {
100
+ readonly id: "esri-hillshade";
101
+ readonly name: "ArcGIS World Hillshade";
102
+ readonly icon: "ArcGisMapServiceWorldHillshade.png";
103
+ readonly category: "Other";
104
+ readonly service: "Elevation/World_Hillshade";
105
+ }, {
106
+ readonly id: "esri-ocean";
107
+ readonly name: "Esri World Ocean";
108
+ readonly icon: "ArcGisMapServiceWorldOcean.png";
109
+ readonly category: "Other";
110
+ readonly service: "Ocean/World_Ocean_Base";
111
+ }, {
112
+ readonly id: "osm";
113
+ readonly name: "OpenStreetMap";
114
+ readonly icon: "openStreetMap.png";
115
+ readonly category: "Other";
116
+ }];
117
+ type CesiumBasemapId = (typeof CESIUM_BASEMAPS)[number]["id"];
118
+ declare function normalizeCesiumBasemap(value: unknown): CesiumBasemapId;
119
+ /**
120
+ * Missing credentials fall back to keyless imagery without changing the saved
121
+ * choice.
122
+ *
123
+ * The fallback is ArcGIS World Imagery rather than the project background: a
124
+ * globe wants to look like the Earth, and the project's own basemap is usually
125
+ * a vector street style whose raster analogue is mostly empty ocean from
126
+ * orbit. It needs no key, so the globe looks the same with or without an Ion
127
+ * token — only terrain and the Ion-only basemaps are lost.
128
+ */
129
+ declare function availableCesiumBasemap(value: unknown, hasIonToken: boolean): CesiumBasemapId;
130
+ /**
131
+ * What the globe should draw underneath the project's data layers.
132
+ *
133
+ * - `xyz` — a tile template to render, with the credit and zoom bounds that go
134
+ * with it.
135
+ * - `none` — the user chose the blank basemap; draw a bare ellipsoid.
136
+ * - `default` — this basemap has no raster equivalent (a custom style URL, a
137
+ * provider style, an offline archive). The renderer falls back to whatever it
138
+ * can offer, which is Ion World Imagery when a token is configured and
139
+ * OpenStreetMap otherwise.
140
+ */
141
+ type CesiumBasemapImagery = {
142
+ kind: "none";
143
+ } | {
144
+ kind: "default";
145
+ } | {
146
+ kind: "ion";
147
+ assetId: number;
148
+ } | {
149
+ kind: "natural-earth";
150
+ } | {
151
+ kind: "arcgis";
152
+ url: string;
153
+ } | {
154
+ kind: "xyz";
155
+ /** Tile template with `{z}`/`{x}`/`{y}` placeholders. */
156
+ template: string;
157
+ /** Credit to show on the globe, as the HTML the 2D map already uses. */
158
+ attribution: string;
159
+ /** Max native zoom of the source, so the globe overzooms rather than 404s. */
160
+ maximumLevel?: number;
161
+ /**
162
+ * Tile row ordering. `"tms"` numbers rows from the bottom; absent means
163
+ * standard XYZ. Cesium has no `scheme` option, so a TMS template needs
164
+ * `{y}` swapped for its `{reverseY}` placeholder at render time.
165
+ */
166
+ scheme?: "tms";
167
+ /**
168
+ * A transparent overlay drawn directly above {@link template} — the
169
+ * roads-and-labels tiles a hybrid basemap (Amap Hybrid) bakes in, so one
170
+ * basemap selection still yields a labelled satellite globe.
171
+ */
172
+ overlayTemplate?: string;
173
+ };
174
+ /**
175
+ * Whether two descriptors would draw the same thing.
176
+ *
177
+ * Descriptors are built fresh on every call, so two basemaps resolving to the
178
+ * same analogue — OpenFreeMap `liberty` and `bright` are both the streets
179
+ * tone — yield equal but distinct objects. A renderer that remembers the
180
+ * descriptor it drew (to skip redundant work) must compare by value: a
181
+ * reference check would miss that case and tear down and re-add an identical
182
+ * tile provider, costing a flash and a round of re-requested tiles for no
183
+ * visible change.
184
+ */
185
+ declare function sameCesiumImagery(a: CesiumBasemapImagery, b: CesiumBasemapImagery): boolean;
186
+ /**
187
+ * Translate a basemap style URL into imagery for the Cesium globe.
188
+ *
189
+ * Planetary and regional basemaps are already raster tile sets, so they carry
190
+ * straight over — the globe shows the same Mars/Moon/Amap tiles the 2D panes
191
+ * do. GeoLibre's own vector styles map to a keyless raster basemap of matching
192
+ * tone, so switching the project to a dark basemap darkens the globe too.
193
+ * Everything else reports `default`, leaving the choice of fallback imagery to
194
+ * the renderer (which knows whether an Ion token is configured).
195
+ *
196
+ * A `geolibre://` sentinel that no longer resolves is treated as the default
197
+ * basemap, mirroring what `resolveMapStyle` does for the 2D map rather than
198
+ * handing the sentinel onward as if it were fetchable.
199
+ *
200
+ * @param styleUrl - The project's `basemapStyleUrl`.
201
+ * @returns What the globe should draw beneath the data layers.
202
+ */
203
+ declare function basemapToCesiumImagery(styleUrl: string | undefined, cesiumBasemap?: CesiumBasemapId): CesiumBasemapImagery;
204
+ //#endregion
205
+ //#region ../../node_modules/@types/geojson/index.d.ts
206
+ /**
207
+ * The value values for the "type" property of GeoJSON Objects.
208
+ * https://tools.ietf.org/html/rfc7946#section-1.4
209
+ */
210
+ type GeoJsonTypes = GeoJSON$1["type"];
211
+ /**
212
+ * Bounding box
213
+ * https://tools.ietf.org/html/rfc7946#section-5
214
+ */
215
+ type BBox = [number, number, number, number] | [number, number, number, number, number, number];
216
+ /**
217
+ * A Position is an array of coordinates.
218
+ * https://tools.ietf.org/html/rfc7946#section-3.1.1
219
+ * Array should contain between two and three elements.
220
+ * The previous GeoJSON specification allowed more elements (e.g., which could be used to represent M values),
221
+ * but the current specification only allows X, Y, and (optionally) Z to be defined.
222
+ *
223
+ * Note: the type will not be narrowed down to `[number, number] | [number, number, number]` due to
224
+ * marginal benefits and the large impact of breaking change.
225
+ *
226
+ * See previous discussions on the type narrowing:
227
+ * - {@link https://github.com/DefinitelyTyped/DefinitelyTyped/pull/21590|Nov 2017}
228
+ * - {@link https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/67773|Dec 2023}
229
+ * - {@link https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/71441| Dec 2024}
230
+ *
231
+ * One can use a
232
+ * {@link https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates|user-defined type guard that returns a type predicate}
233
+ * to determine if a position is a 2D or 3D position.
234
+ *
235
+ * @example
236
+ * import type { Position } from 'geojson';
237
+ *
238
+ * type StrictPosition = [x: number, y: number] | [x: number, y: number, z: number]
239
+ *
240
+ * function isStrictPosition(position: Position): position is StrictPosition {
241
+ * return position.length === 2 || position.length === 3
242
+ * };
243
+ *
244
+ * let position: Position = [-116.91, 45.54];
245
+ *
246
+ * let x: number;
247
+ * let y: number;
248
+ * let z: number | undefined;
249
+ *
250
+ * if (isStrictPosition(position)) {
251
+ * // `tsc` would throw an error if we tried to destructure a fourth parameter
252
+ * [x, y, z] = position;
253
+ * } else {
254
+ * throw new TypeError("Position is not a 2D or 3D point");
255
+ * }
256
+ */
257
+ type Position = number[];
258
+ /**
259
+ * The base GeoJSON object.
260
+ * https://tools.ietf.org/html/rfc7946#section-3
261
+ * The GeoJSON specification also allows foreign members
262
+ * (https://tools.ietf.org/html/rfc7946#section-6.1)
263
+ * Developers should use "&" type in TypeScript or extend the interface
264
+ * to add these foreign members.
265
+ */
266
+ interface GeoJsonObject {
267
+ // Don't include foreign members directly into this type def.
268
+ // in order to preserve type safety.
269
+ // [key: string]: any;
270
+ /**
271
+ * Specifies the type of GeoJSON object.
272
+ */
273
+ type: GeoJsonTypes;
274
+ /**
275
+ * Bounding box of the coordinate range of the object's Geometries, Features, or Feature Collections.
276
+ * The value of the bbox member is an array of length 2*n where n is the number of dimensions
277
+ * represented in the contained geometries, with all axes of the most southwesterly point
278
+ * followed by all axes of the more northeasterly point.
279
+ * The axes order of a bbox follows the axes order of geometries.
280
+ * https://tools.ietf.org/html/rfc7946#section-5
281
+ */
282
+ bbox?: BBox | undefined;
283
+ }
284
+ /**
285
+ * Union of GeoJSON objects.
286
+ */
287
+ type GeoJSON$1<G extends Geometry | null = Geometry, P = GeoJsonProperties> = G | Feature<G, P> | FeatureCollection<G, P>;
288
+ /**
289
+ * Geometry object.
290
+ * https://tools.ietf.org/html/rfc7946#section-3
291
+ */
292
+ type Geometry = Point | MultiPoint | LineString | MultiLineString | Polygon | MultiPolygon | GeometryCollection;
293
+ /**
294
+ * Point geometry object.
295
+ * https://tools.ietf.org/html/rfc7946#section-3.1.2
296
+ */
297
+ interface Point extends GeoJsonObject {
298
+ type: "Point";
299
+ coordinates: Position;
300
+ }
301
+ /**
302
+ * MultiPoint geometry object.
303
+ * https://tools.ietf.org/html/rfc7946#section-3.1.3
304
+ */
305
+ interface MultiPoint extends GeoJsonObject {
306
+ type: "MultiPoint";
307
+ coordinates: Position[];
308
+ }
309
+ /**
310
+ * LineString geometry object.
311
+ * https://tools.ietf.org/html/rfc7946#section-3.1.4
312
+ */
313
+ interface LineString extends GeoJsonObject {
314
+ type: "LineString";
315
+ coordinates: Position[];
316
+ }
317
+ /**
318
+ * MultiLineString geometry object.
319
+ * https://tools.ietf.org/html/rfc7946#section-3.1.5
320
+ */
321
+ interface MultiLineString extends GeoJsonObject {
322
+ type: "MultiLineString";
323
+ coordinates: Position[][];
324
+ }
325
+ /**
326
+ * Polygon geometry object.
327
+ * https://tools.ietf.org/html/rfc7946#section-3.1.6
328
+ */
329
+ interface Polygon extends GeoJsonObject {
330
+ type: "Polygon";
331
+ coordinates: Position[][];
332
+ }
333
+ /**
334
+ * MultiPolygon geometry object.
335
+ * https://tools.ietf.org/html/rfc7946#section-3.1.7
336
+ */
337
+ interface MultiPolygon extends GeoJsonObject {
338
+ type: "MultiPolygon";
339
+ coordinates: Position[][][];
340
+ }
341
+ /**
342
+ * Geometry Collection
343
+ * https://tools.ietf.org/html/rfc7946#section-3.1.8
344
+ */
345
+ interface GeometryCollection<G extends Geometry = Geometry> extends GeoJsonObject {
346
+ type: "GeometryCollection";
347
+ geometries: G[];
348
+ }
349
+ type GeoJsonProperties = {
350
+ [name: string]: any;
351
+ } | null;
352
+ /**
353
+ * A feature object which contains a geometry and associated properties.
354
+ * https://tools.ietf.org/html/rfc7946#section-3.2
355
+ */
356
+ interface Feature<G extends Geometry | null = Geometry, P = GeoJsonProperties> extends GeoJsonObject {
357
+ type: "Feature";
358
+ /**
359
+ * The feature's geometry
360
+ */
361
+ geometry: G;
362
+ /**
363
+ * A value that uniquely identifies this feature in a
364
+ * https://tools.ietf.org/html/rfc7946#section-3.2.
365
+ */
366
+ id?: string | number | undefined;
367
+ /**
368
+ * Properties associated with this feature.
369
+ */
370
+ properties: P;
371
+ }
372
+ /**
373
+ * A collection of feature objects.
374
+ * https://tools.ietf.org/html/rfc7946#section-3.3
375
+ */
376
+ interface FeatureCollection<G extends Geometry | null = Geometry, P = GeoJsonProperties> extends GeoJsonObject {
377
+ type: "FeatureCollection";
378
+ features: Array<Feature<G, P>>;
379
+ }
380
+ //#endregion
381
+ //#region src/print-layout-config.d.ts
382
+ /**
383
+ * Print Layout composer settings that belong to a project.
384
+ *
385
+ * The Print Layout dialog composes a printable page out of the current map:
386
+ * a title block, page size and orientation, the cartographic furniture
387
+ * (legend, scale bar, north arrow), optional data blocks, and an atlas /
388
+ * map-series definition. All of that describes the *project's* map document,
389
+ * so it round-trips through `.geolibre.json` (GeoLibre discussion #1992 —
390
+ * before this, reopening a project lost the title, page format and
391
+ * orientation, and the dialog kept showing the previous project's settings).
392
+ *
393
+ * Deliberately **not** in here: everything that is per-session rather than
394
+ * part of the document — the captured map image, export progress, error and
395
+ * clipboard notices, the current atlas page, and the dialog's own chrome
396
+ * (panel widths, dialog size).
397
+ *
398
+ * The unions below are the storage contract. Their app-side counterparts
399
+ * (`PaperSizeId`, `Orientation`, `BodyCorner`, ... in
400
+ * `apps/geolibre-desktop/src/lib/`) are checked against these by ordinary
401
+ * assignment in the dialog's snapshot/restore paths, so adding a paper size or
402
+ * a corner on one side without the other fails the build.
403
+ */
404
+ /** A page corner an overlay block can be pinned to. */
405
+ type PrintLayoutCorner = "top-left" | "top-right" | "bottom-left" | "bottom-right";
406
+ /** Which features a data block covers when the atlas paginates the map. */
407
+ type PrintLayoutPageFilter = "all" | "contained" | "intersecting";
408
+ /** One hand-authored swatch in the composer's user-defined legend. */
409
+ interface PrintLayoutLegendEntry {
410
+ id: string;
411
+ label: string;
412
+ color: string;
413
+ }
414
+ interface PrintLayoutConfig {
415
+ title: string;
416
+ subtitle: string;
417
+ titlePlacement: "outside" | "inside";
418
+ titleAlign: "left" | "center" | "right";
419
+ paperSize: "a4" | "a3" | "letter" | "legal" | "tabloid" | "fullhd" | "hd" | "uhd4k" | "square" | "custom";
420
+ orientation: "portrait" | "landscape";
421
+ customWidth: number;
422
+ customHeight: number;
423
+ customUnit: "mm" | "px";
424
+ pageMargin: "normal" | "narrow" | "none";
425
+ showPageBorder: boolean;
426
+ pageBorderColor: string;
427
+ pageBorderWidth: number;
428
+ mapBorderColor: string;
429
+ mapBorderWidth: number;
430
+ mapBackground: string;
431
+ showTitle: boolean;
432
+ showSubtitle: boolean;
433
+ showLegend: boolean;
434
+ showScaleBar: boolean;
435
+ showNorthArrow: boolean;
436
+ navigationGrouped: boolean;
437
+ showFooter: boolean;
438
+ footerText: string;
439
+ showDate: boolean;
440
+ /** Blank means "today", resolved when the page is drawn. */
441
+ dateText: string;
442
+ showAttribution: boolean;
443
+ showColorbar: boolean;
444
+ colorbarRamp: string;
445
+ /** Kept as typed text so a half-entered value survives a round trip. */
446
+ colorbarMin: string;
447
+ colorbarMax: string;
448
+ colorbarLabel: string;
449
+ colorbarOrientation: "vertical" | "horizontal";
450
+ colorbarLength: number;
451
+ colorbarPosition: PrintLayoutCorner;
452
+ showCustomLegend: boolean;
453
+ customLegendTitle: string;
454
+ customLegendEntries: PrintLayoutLegendEntry[];
455
+ customLegendPosition: PrintLayoutCorner;
456
+ showDataTable: boolean;
457
+ tableLayerId: string;
458
+ tableTitle: string;
459
+ tableColumns: string[];
460
+ tableSortField: string;
461
+ tableSortDesc: boolean;
462
+ tableMaxRows: number;
463
+ tableFitRows: boolean;
464
+ tablePosition: PrintLayoutCorner;
465
+ tablePageFilter: PrintLayoutPageFilter;
466
+ tableFilterToAtlasFeature: boolean;
467
+ showDataChart: boolean;
468
+ chartLayerId: string;
469
+ chartTitle: string;
470
+ chartType: "bar" | "pie" | "line";
471
+ chartCategoryField: string;
472
+ chartAggregation: "count" | "sum" | "mean";
473
+ chartValueField: string;
474
+ chartPosition: PrintLayoutCorner;
475
+ chartPageFilter: PrintLayoutPageFilter;
476
+ showInfoBlock: boolean;
477
+ author: string;
478
+ projectNumber: string;
479
+ crs: string;
480
+ revision: string;
481
+ captureMode: "viewport" | "extent";
482
+ /** Drawn print extent as `[west, south, east, north]`; null when unset. */
483
+ extentBbox: [number, number, number, number] | null;
484
+ atlasEnabled: boolean;
485
+ atlasLayerId: string;
486
+ atlasCoverage: "features" | "line";
487
+ atlasSegmentKm: string;
488
+ atlasNameField: string;
489
+ atlasExtentMode: "margin" | "scale";
490
+ atlasMarginPct: number;
491
+ atlasMaskEnabled: boolean;
492
+ atlasScale: string;
493
+ atlasSortField: string;
494
+ atlasSortDescending: boolean;
495
+ atlasFilter: string;
496
+ atlasFilenamePattern: string;
497
+ }
498
+ /**
499
+ * Settings a project starts with, and the shape every partial or hand-edited
500
+ * `printLayout` is filled out against. Also the dialog's initial state, so
501
+ * "new project" and "a project saved before this feature" compose identically.
502
+ */
503
+ declare const DEFAULT_PRINT_LAYOUT: PrintLayoutConfig;
504
+ /** A fresh copy of the defaults, safe to hand to a store or a component. */
505
+ declare function createDefaultPrintLayout(): PrintLayoutConfig;
506
+ /**
507
+ * Coerce an untrusted (possibly hand-edited, possibly partial) `printLayout`
508
+ * into a complete {@link PrintLayoutConfig}, filling every missing or
509
+ * malformed field from {@link DEFAULT_PRINT_LAYOUT}.
510
+ *
511
+ * @param value - The raw `printLayout` value read from a project file.
512
+ * @returns The normalized config, or null when the project carries none, so
513
+ * callers can tell "absent" from "explicitly default".
514
+ */
515
+ declare function normalizePrintLayoutConfig(value: unknown): PrintLayoutConfig | null;
516
+ /**
517
+ * Whether a config is still the untouched default, so a project that never
518
+ * opened the composer serializes without a `printLayout` key and stays
519
+ * byte-identical to one saved before this feature.
520
+ */
521
+ declare function isDefaultPrintLayout(config: PrintLayoutConfig): boolean;
522
+ /**
523
+ * Structural equality for two configs. Used to skip no-op store writes, so
524
+ * merely opening the Print Layout dialog never marks a project dirty.
525
+ */
526
+ declare function printLayoutConfigsEqual(a: PrintLayoutConfig, b: PrintLayoutConfig): boolean;
527
+ /**
528
+ * Drop references to layers the project no longer carries, so a composer that
529
+ * pointed at a since-deleted layer opens with the block cleared instead of
530
+ * rendering nothing from a dangling id. Mirrors the widget/comment/legend
531
+ * scrubbing in `applyProjectToStore`.
532
+ *
533
+ * @param config - The config to scrub.
534
+ * @param existingLayerIds - Ids of the layers present in the project.
535
+ * @returns The same object when nothing referenced a missing layer.
536
+ */
537
+ declare function scrubPrintLayoutForLayers(config: PrintLayoutConfig, existingLayerIds: ReadonlySet<string>): PrintLayoutConfig;
538
+ /**
539
+ * The delete-time counterpart, matching `scrubLegendForRemovedLayers` and its
540
+ * siblings: called when layers are removed from the open project so the saved
541
+ * file never carries a block pointing at a layer that is already gone.
542
+ *
543
+ * @param config - The config to scrub.
544
+ * @param layerIds - The removed layer id, or ids.
545
+ * @returns The same object when no block referenced a removed layer.
546
+ */
547
+ declare function scrubPrintLayoutForRemovedLayers(config: PrintLayoutConfig, layerIds: string | Iterable<string>): PrintLayoutConfig;
548
+ //#endregion
549
+ //#region src/types.d.ts
550
+ declare const OPENFREEMAP_BASEMAPS: readonly [{
551
+ readonly id: "liberty";
552
+ readonly name: "Liberty";
553
+ readonly styleUrl: "https://tiles.openfreemap.org/styles/liberty";
554
+ }, {
555
+ readonly id: "liberty-3d";
556
+ readonly name: "Liberty 3D";
557
+ readonly styleUrl: "https://tiles.openfreemap.org/styles/liberty";
558
+ }, {
559
+ readonly id: "positron";
560
+ readonly name: "Positron";
561
+ readonly styleUrl: "https://tiles.openfreemap.org/styles/positron";
562
+ }, {
563
+ readonly id: "bright";
564
+ readonly name: "Bright";
565
+ readonly styleUrl: "https://tiles.openfreemap.org/styles/bright";
566
+ }, {
567
+ readonly id: "dark";
568
+ readonly name: "Dark";
569
+ readonly styleUrl: "https://tiles.openfreemap.org/styles/dark";
570
+ }, {
571
+ readonly id: "fiord";
572
+ readonly name: "Fiord";
573
+ readonly styleUrl: "https://tiles.openfreemap.org/styles/fiord";
574
+ }];
575
+ /**
576
+ * Protomaps v5 basemap flavors. These are resolved to full style URLs at use
577
+ * time by `getProtomapsStyleUrl`, which injects the `VITE_PROTOMAPS_API_KEY`
578
+ * runtime env var. The key is only present in builds configured with it (e.g.
579
+ * the GitHub Pages web demo), so consumers should hide these options when no
580
+ * key is available.
581
+ */
582
+ declare const PROTOMAPS_BASEMAPS: readonly [{
583
+ readonly id: "protomaps-light";
584
+ readonly name: "Light";
585
+ readonly flavor: "light";
586
+ }, {
587
+ readonly id: "protomaps-dark";
588
+ readonly name: "Dark";
589
+ readonly flavor: "dark";
590
+ }, {
591
+ readonly id: "protomaps-white";
592
+ readonly name: "White";
593
+ readonly flavor: "white";
594
+ }, {
595
+ readonly id: "protomaps-grayscale";
596
+ readonly name: "Grayscale";
597
+ readonly flavor: "grayscale";
598
+ }, {
599
+ readonly id: "protomaps-black";
600
+ readonly name: "Black";
601
+ readonly flavor: "black";
602
+ }];
603
+ declare const DEFAULT_BASEMAP = "https://tiles.openfreemap.org/styles/liberty";
604
+ declare const BLANK_BASEMAP = "";
605
+ declare const PROJECT_VERSION = "0.2.0";
606
+ /**
607
+ * Every layer type, as a runtime list so untrusted input (an imported Layer
608
+ * Library bundle, a hand-edited project) can be validated against it.
609
+ * {@link LayerType} is derived from this array, so the two cannot drift.
610
+ */
611
+ declare const LAYER_TYPES: readonly ["geojson", "raster", "wms", "wmts", "xyz", "vector-tiles", "arcgis", "pmtiles", "mbtiles", "zarr", "lidar", "gaussian-splat", "3d-tiles", "cog", "flatgeobuf", "geoparquet", "duckdb-query", "deckgl-viz", "video", "image"];
612
+ type LayerType = (typeof LAYER_TYPES)[number];
613
+ type VectorStyleMode = "single" | "graduated" | "categorized" | "rule-based" | "expression";
614
+ /**
615
+ * One entry in a {@link LayerStyle.vectorRules} rule-based renderer. Each rule
616
+ * pairs a MapLibre filter expression (a boolean expression array, serialized as
617
+ * JSON) with a symbol color. Rules are evaluated top to bottom; the first whose
618
+ * filter matches wins. The catch-all rule (`isElse`) has no filter and supplies
619
+ * the color for features no other rule matched. Mirrors the QGIS/ArcGIS
620
+ * rule-based renderer.
621
+ */
622
+ interface VectorRule {
623
+ id: string;
624
+ /** Human-readable label shown in the editor and the legend. */
625
+ label: string;
626
+ /**
627
+ * A MapLibre boolean filter expression serialized as JSON, e.g.
628
+ * `["==", ["get", "TYPE"], "park"]`. Ignored when {@link isElse} is set or
629
+ * the JSON is invalid.
630
+ */
631
+ filter: string;
632
+ /** The symbol fill/circle color for features this rule matches (6-digit hex). */
633
+ color: string;
634
+ /**
635
+ * When true this is the catch-all "else" rule: {@link filter} is unused and
636
+ * the rule is an unconditional fallback for features no other rule matched,
637
+ * so the {@link minZoom}/{@link maxZoom} and {@link parentId} fields are
638
+ * ignored on it. Its symbol overrides and {@link enabled} toggle do apply.
639
+ */
640
+ isElse: boolean;
641
+ /**
642
+ * Whether the rule participates in rendering. `false` temporarily disables
643
+ * the rule (and, for a group, its whole subtree) without deleting it,
644
+ * mirroring the QGIS rule checkbox. Absent means enabled.
645
+ */
646
+ enabled?: boolean;
647
+ /**
648
+ * Lowest zoom (inclusive) the rule applies at, mirroring MapLibre's layer
649
+ * `minzoom` convention. Absent means no lower bound.
650
+ */
651
+ minZoom?: number;
652
+ /**
653
+ * Zoom the rule stops applying at (exclusive, like MapLibre's layer
654
+ * `maxzoom`). Absent means no upper bound. Together with {@link minZoom}
655
+ * this is the QGIS per-rule "scale range".
656
+ */
657
+ maxZoom?: number;
658
+ /**
659
+ * The id of the parent rule when this rule is nested inside a group (QGIS
660
+ * rule tree). A child rule matches only features that also match every
661
+ * ancestor's filter, and inherits the intersection of the ancestors' zoom
662
+ * ranges and enabled state. A rule that has children acts as a group: its
663
+ * own symbol is not rendered; only leaf rules draw. Absent or dangling
664
+ * means a top-level rule.
665
+ */
666
+ parentId?: string;
667
+ /**
668
+ * Per-rule stroke/outline color override (6-digit hex): the polygon outline
669
+ * and circle stroke color for matching features. Absent inherits the layer
670
+ * {@link LayerStyle.strokeColor}.
671
+ */
672
+ strokeColor?: string;
673
+ /**
674
+ * Per-rule stroke width override in pixels: the line width (lines and
675
+ * polygon outlines) and circle stroke width for matching features. Absent
676
+ * inherits the layer {@link LayerStyle.strokeWidth}.
677
+ */
678
+ strokeWidth?: number;
679
+ /**
680
+ * Per-rule fill/circle opacity override (0..1) for matching features.
681
+ * Absent inherits the layer {@link LayerStyle.fillOpacity}.
682
+ */
683
+ fillOpacity?: number;
684
+ /**
685
+ * Per-rule circle radius override in pixels for matching point features.
686
+ * Absent inherits the layer {@link LayerStyle.circleRadius}.
687
+ */
688
+ circleRadius?: number;
689
+ }
690
+ /**
691
+ * The fill pattern applied to polygon layers. `"none"` keeps a flat fill; the
692
+ * named patterns are generated as recolorable sprite tiles; `"svg"` rasterizes
693
+ * the user-supplied markup in {@link LayerStyle.fillPatternSvg}.
694
+ */
695
+ type FillPattern = "none" | "hatch" | "cross-hatch" | "horizontal" | "vertical" | "dots" | "svg";
696
+ /**
697
+ * The built-in marker shape for a point layer, or `"custom"` to rasterize the
698
+ * user-supplied SVG in {@link LayerStyle.markerSvg}. Built-in shapes are drawn
699
+ * on a sprite tile and recolored via {@link LayerStyle.markerColor}.
700
+ */
701
+ type MarkerShape = "circle" | "square" | "triangle" | "diamond" | "star" | "cross" | "pin" | "custom";
702
+ /**
703
+ * How a point layer is rendered: as individual markers, a density heatmap, or
704
+ * clustered bubbles. Only applies to point geometry.
705
+ */
706
+ type PointRenderer = "single" | "heatmap" | "cluster";
707
+ /**
708
+ * The repeated decoration symbol drawn along line features (and polygon
709
+ * outlines), or `"none"` when decorations are off. Mirrors the QGIS
710
+ * marker-line / arrow symbol layers: `"arrow"` renders directional arrowheads
711
+ * that follow the line, the other shapes render as repeated markers.
712
+ */
713
+ type LineDecoration = "none" | "arrow" | "triangle" | "circle" | "square";
714
+ /**
715
+ * The per-feature derived geometry rendered by the geometry generator, or
716
+ * `"none"` when the generator is off. Mirrors QGIS geometry-generator symbol
717
+ * layers: each feature's derived geometry (its centroid, bounding box, convex
718
+ * hull, or a buffer) is drawn as an extra symbol over the layer's normal
719
+ * symbology. `"centroid"` derives points; the rest derive polygons.
720
+ */
721
+ type GeometryGeneratorType = "none" | "centroid" | "bounding-box" | "convex-hull" | "buffer";
722
+ /**
723
+ * Unit a stroke/line width is measured in. `"pixels"` is constant screen space;
724
+ * `"meters"` is ground distance, so the rendered width scales with the map
725
+ * scale (zoom). See {@link LayerStyle.strokeWidthUnit}.
726
+ */
727
+ type StrokeWidthUnit = "pixels" | "meters";
728
+ /**
729
+ * The chart drawn on top of each feature by the diagram renderer (QGIS-style
730
+ * diagram symbology), or `"none"` when diagrams are off. Diagrams visualize
731
+ * several numeric attributes per feature at once — e.g. election results by
732
+ * party per county — and render through the shared deck.gl overlay on the
733
+ * feature's point location or polygon centroid.
734
+ */
735
+ type DiagramType = "none" | "pie" | "donut" | "bar" | "stacked-bar";
736
+ /**
737
+ * How the overall diagram size is determined.
738
+ *
739
+ * - `"fixed"`: every diagram renders at {@link LayerStyle.diagramSize} pixels.
740
+ * - `"sum"`: scaled by the sum of the mapped attribute values, so the largest
741
+ * total renders at {@link LayerStyle.diagramSize} pixels (area-true square
742
+ * root scaling).
743
+ * - `"attribute"`: scaled the same way by the single numeric attribute in
744
+ * {@link LayerStyle.diagramSizeProperty}.
745
+ */
746
+ type DiagramSizeMode = "fixed" | "sum" | "attribute";
747
+ /** One attribute rendered as a slice/bar of a feature diagram. */
748
+ interface DiagramField {
749
+ /** Numeric feature property visualized by this slice/bar. */
750
+ property: string;
751
+ /** Slice/bar color (6-digit hex). */
752
+ color: string;
753
+ }
754
+ interface VectorStyleStop {
755
+ value: string | number;
756
+ color: string;
757
+ label?: string;
758
+ }
759
+ /** Attribute-driven labeling for a vector layer (rendered as a MapLibre symbol layer). */
760
+ interface LabelStyle {
761
+ /** Whether labels are shown for the layer. */
762
+ enabled: boolean;
763
+ /** Attribute field whose value becomes the label text. */
764
+ field: string;
765
+ /**
766
+ * Optional MapLibre expression (JSON string) for the label text, which
767
+ * overrides {@link field} when non-empty (e.g. concatenating several fields).
768
+ */
769
+ expression: string;
770
+ /** `"point"` labels at the feature/centroid; `"line"` places them along lines. */
771
+ placement: "point" | "line";
772
+ /** Label text size in pixels. */
773
+ size: number;
774
+ /** CSS color string for the label text. */
775
+ color: string;
776
+ /** CSS color string for the text halo drawn behind the label. */
777
+ haloColor: string;
778
+ /** Width of the text halo in pixels. */
779
+ haloWidth: number;
780
+ /** Scale range for labels; `0` / `24` inherit the layer's own zoom range. */
781
+ minZoom: number;
782
+ maxZoom: number;
783
+ /** Let labels overlap instead of hiding colliding ones. */
784
+ allowOverlap: boolean;
785
+ /**
786
+ * Where the label sits relative to its anchor point (MapLibre `text-anchor`),
787
+ * e.g. `"top"` places the text above the point. Ignored for line placement.
788
+ */
789
+ anchor: LabelAnchor;
790
+ /** Horizontal label offset in ems (MapLibre `text-offset` x). */
791
+ offsetX: number;
792
+ /** Vertical label offset in ems (MapLibre `text-offset` y; positive is down). */
793
+ offsetY: number;
794
+ /** Label rotation in degrees clockwise (MapLibre `text-rotate`). */
795
+ rotation: number;
796
+ /** Maximum line width in ems before the label wraps (MapLibre `text-max-width`). */
797
+ maxWidth: number;
798
+ /** Letter-case transform applied to the label text (MapLibre `text-transform`). */
799
+ transform: LabelTransform;
800
+ /**
801
+ * Render a numeric {@link field} with the locale's thousands and decimal
802
+ * separators (issue #2336), so `1234567.5` labels as `1,234,567.5` instead
803
+ * of running together. Non-numeric values are unaffected, and it is not
804
+ * applied to {@link expression}, which formats its own output (MapLibre's
805
+ * `number-format`, offered in the Expression Builder).
806
+ */
807
+ numberFormatEnabled: boolean;
808
+ /** Decimal places kept while {@link numberFormatEnabled} is on (0-10). */
809
+ numberDecimals: number;
810
+ /**
811
+ * BCP 47 tag picking the separators for {@link numberFormatEnabled}, from
812
+ * {@link LABEL_NUMBER_LOCALES}. Empty (the default) follows the app's own
813
+ * language, the way popup number fields do.
814
+ */
815
+ numberLocale: string;
816
+ /**
817
+ * How to handle features that share a label.
818
+ *
819
+ * - `"off"`: every feature is labeled (the historical behavior).
820
+ * - `"unique"`: features stacked at the same point are collapsed to a single
821
+ * label, so co-located points (e.g. several antennas at one cell site) do not
822
+ * stack overlapping text.
823
+ * - `"concatenate"`: co-located points are merged into one label that joins
824
+ * their distinct {@link field} values, one per line.
825
+ *
826
+ * Applies to point layers rendered through the inline GeoJSON path and uses
827
+ * {@link field} (not {@link expression}) as the label value.
828
+ */
829
+ dedupe: LabelDedupe;
830
+ /**
831
+ * Data-defined override for {@link size}: a MapLibre expression (JSON
832
+ * string) producing a number, e.g. sizing labels by population. Empty means
833
+ * "use the literal {@link size}". Like the other data-defined overrides
834
+ * below, it reads source feature attributes, so it is skipped while
835
+ * {@link dedupe} collapsing is active (the aggregated features carry only
836
+ * the label value).
837
+ */
838
+ sizeExpression: string;
839
+ /**
840
+ * Data-defined override for {@link color}: a MapLibre expression (JSON
841
+ * string) producing a color, e.g. coloring labels by category.
842
+ */
843
+ colorExpression: string;
844
+ /**
845
+ * Data-defined label opacity: a MapLibre expression (JSON string) producing
846
+ * a number in 0..1. When set it replaces the layer-wide opacity for labels
847
+ * (wrapping it would invalidate top-level `["zoom"]` interpolations).
848
+ */
849
+ opacityExpression: string;
850
+ /**
851
+ * Per-feature label visibility: a MapLibre expression (JSON string)
852
+ * producing a boolean. Features evaluating false get no label (e.g. hide
853
+ * labels below an attribute threshold). Combined with the layer's other
854
+ * feature filters.
855
+ */
856
+ visibilityExpression: string;
857
+ /**
858
+ * Per-feature placement priority: a MapLibre expression (JSON string)
859
+ * producing a number, applied as `symbol-sort-key`. Labels with lower
860
+ * values are placed first, so they win when space is tight.
861
+ */
862
+ priorityExpression: string;
863
+ }
864
+ /** MapLibre `text-anchor` positions offered for {@link LabelStyle.anchor}. */
865
+ type LabelAnchor = "center" | "left" | "right" | "top" | "bottom" | "top-left" | "top-right" | "bottom-left" | "bottom-right";
866
+ /** Letter-case transform offered for {@link LabelStyle.transform}. */
867
+ type LabelTransform = "none" | "uppercase" | "lowercase";
868
+ /** Duplicate-label handling offered for {@link LabelStyle.dedupe}. */
869
+ type LabelDedupe = "off" | "unique" | "concatenate";
870
+ /**
871
+ * Cartographic blend modes offered for {@link LayerStyle.blendMode}.
872
+ *
873
+ * The list is bounded by what WebGL *fixed-function* blending can express --
874
+ * one `blendFunc` factor pair plus one `blendEquation` -- because that is the
875
+ * only blend state MapLibre's renderer exposes, and it applies to colour and
876
+ * alpha together. Two consequences shaped this list:
877
+ *
878
+ * - Modes needing the destination colour inside a fragment shader (`overlay`,
879
+ * `color-dodge`, `soft-light`, `difference`, and the non-separable HSL modes)
880
+ * are absent. MapLibre draws straight into the map framebuffer, which a
881
+ * shader cannot sample, so computing them would need an extra copy pass.
882
+ * - `darken` and `subtract` are absent even though `MIN` and
883
+ * `FUNC_REVERSE_SUBTRACT` look like they would serve. Both also apply to the
884
+ * alpha channel, where they drive the map canvas transparent: `MIN` takes the
885
+ * whole canvas to `min(0, dst) = 0` everywhere the layer does not cover, and
886
+ * `FUNC_REVERSE_SUBTRACT` leaves `dstA - srcA` inside it. Correcting either
887
+ * needs `blendEquationSeparate` / `blendFuncSeparate`, which MapLibre's state
888
+ * tracker does not offer.
889
+ *
890
+ * Every mode here leaves the canvas opaque and leaves the map untouched
891
+ * wherever the layer contributes nothing. See `docs/user-guide/layers.md`.
892
+ */
893
+ declare const BLEND_MODES: readonly ["normal", "multiply", "screen", "lighten", "add"];
894
+ /**
895
+ * How a layer's pixels combine with the map beneath it. `"normal"` is ordinary
896
+ * alpha compositing (the default, and what every layer did before blend modes
897
+ * existed). See {@link BLEND_MODES}.
898
+ */
899
+ type BlendMode = (typeof BLEND_MODES)[number];
900
+ /**
901
+ * The mode a layer renders with unless it says otherwise. Declared separately
902
+ * from {@link DEFAULT_LAYER_STYLE} so consumers get a non-optional
903
+ * {@link BlendMode} to fall back to: `blendMode` is optional on
904
+ * {@link LayerStyle}, so `DEFAULT_LAYER_STYLE.blendMode` is typed
905
+ * `BlendMode | undefined` however it is initialized.
906
+ */
907
+ declare const DEFAULT_BLEND_MODE: BlendMode;
908
+ interface LayerStyle {
909
+ minZoom: number;
910
+ maxZoom: number;
911
+ fillColor: string;
912
+ strokeColor: string;
913
+ strokeWidth: number;
914
+ /**
915
+ * Unit the {@link strokeWidth} value is expressed in.
916
+ *
917
+ * - `"pixels"` (default): a constant screen-space width that never changes
918
+ * with zoom — the historical behavior.
919
+ * - `"meters"`: a ground-distance width, so the rendered line stays
920
+ * proportional to the map scale (thicker zoomed in, thinner zoomed out),
921
+ * matching QGIS "map units". Only affects line/polygon-outline rendering;
922
+ * point/circle outlines remain pixel-based.
923
+ */
924
+ strokeWidthUnit: StrokeWidthUnit;
925
+ fillOpacity: number;
926
+ circleRadius: number;
927
+ textColor: string;
928
+ textHaloColor: string;
929
+ textHaloWidth: number;
930
+ textSize: number;
931
+ labels: LabelStyle;
932
+ extrusionEnabled: boolean;
933
+ extrusionColor: string;
934
+ extrusionOpacity: number;
935
+ extrusionHeightProperty: string;
936
+ extrusionHeightScale: number;
937
+ extrusionBase: number;
938
+ extrusionAdvancedStyleEnabled: boolean;
939
+ extrusionColorExpression: string;
940
+ extrusionHeightExpression: string;
941
+ /**
942
+ * When true, a vector layer whose coordinates carry Z values (e.g. a GPX
943
+ * track with elevations) renders in true 3D through the shared deck.gl
944
+ * overlay instead of MapLibre's flat 2D layers, so features sit at their
945
+ * own altitude. Orthogonal to {@link extrusionEnabled}, which extrudes flat
946
+ * polygons by an attribute; only one of the two should be on at a time.
947
+ */
948
+ elevation3dEnabled: boolean;
949
+ /** Multiplier applied to each coordinate's Z value (vertical exaggeration). */
950
+ elevation3dVerticalScale: number;
951
+ /** Constant altitude offset in meters added after the vertical scale. */
952
+ elevation3dOffset: number;
953
+ vectorStyleMode: VectorStyleMode;
954
+ vectorStyleProperty: string;
955
+ vectorStyleClassCount: number;
956
+ vectorStyleColorRamp: string;
957
+ vectorStyleClassificationScheme: string;
958
+ vectorStyleStops: VectorStyleStop[];
959
+ vectorStyleExpression: string;
960
+ /**
961
+ * Ordered rules for the `"rule-based"` {@link vectorStyleMode}. Compiled to a
962
+ * MapLibre `case` color expression (first matching filter wins, catch-all
963
+ * last). See {@link VectorRule}.
964
+ */
965
+ vectorRules: VectorRule[];
966
+ /**
967
+ * When true, the point circle radius (or line width) is sized by a numeric
968
+ * field via an `interpolate` between {@link proportionalSizeMinValue} ..
969
+ * {@link proportionalSizeMaxValue} mapped onto {@link proportionalSizeMinRadius}
970
+ * .. {@link proportionalSizeMaxRadius} (QGIS "graduated → size" / proportional
971
+ * symbols). Orthogonal to the color {@link vectorStyleMode}.
972
+ */
973
+ proportionalSizeEnabled: boolean;
974
+ proportionalSizeProperty: string;
975
+ proportionalSizeMinValue: number;
976
+ proportionalSizeMaxValue: number;
977
+ proportionalSizeMinRadius: number;
978
+ proportionalSizeMaxRadius: number;
979
+ /**
980
+ * Polygon fill pattern. `"none"` keeps the flat fill; other values render a
981
+ * recolorable sprite tile ({@link fillPatternColor}); `"svg"` rasterizes
982
+ * {@link fillPatternSvg}. See {@link FillPattern}.
983
+ */
984
+ fillPattern: FillPattern;
985
+ fillPatternColor: string;
986
+ /** Raw SVG markup (or a data URL) used when {@link fillPattern} is `"svg"`. */
987
+ fillPatternSvg: string;
988
+ /**
989
+ * When true, point features render as a marker icon ({@link markerShape})
990
+ * instead of a plain circle. Built-in shapes are recolored via
991
+ * {@link markerColor}; `"custom"` rasterizes {@link markerSvg}.
992
+ */
993
+ markerEnabled: boolean;
994
+ markerShape: MarkerShape;
995
+ markerColor: string;
996
+ markerSize: number;
997
+ /**
998
+ * The custom marker image used when {@link markerShape} is `"custom"`: raw
999
+ * SVG markup, a `data:` URL (SVG or a raster PNG/JPEG/GIF), or an `http(s)`
1000
+ * URL. Only SVG sources take {@link markerColor}; raster images draw as-is.
1001
+ */
1002
+ markerSvg: string;
1003
+ /**
1004
+ * When true, per-feature [simplestyle-spec](https://github.com/mapbox/simplestyle-spec)
1005
+ * properties (`fill`, `fill-opacity`, `stroke`, `stroke-width`,
1006
+ * `stroke-opacity`, `marker-color`) override the flat layer style on a
1007
+ * per-feature basis. Set automatically when a GeoJSON layer is added whose
1008
+ * features carry these properties (e.g. styled KML/KMZ), so embedded
1009
+ * symbology renders without manual configuration.
1010
+ */
1011
+ simpleStyleEnabled: boolean;
1012
+ /**
1013
+ * Per-feature chart symbology (QGIS-style diagrams). `"none"` disables it;
1014
+ * any other value renders one {@link DiagramType} chart per feature over the
1015
+ * layer's normal symbology, built from the numeric attributes in
1016
+ * {@link diagramFields}. See `@geolibre/core`'s `diagram.ts` helpers.
1017
+ */
1018
+ diagramType: DiagramType;
1019
+ /** Attributes (and their colors) charted by the diagram renderer, in order. */
1020
+ diagramFields: DiagramField[];
1021
+ /** How the per-feature diagram size is determined. */
1022
+ diagramSizeMode: DiagramSizeMode;
1023
+ /**
1024
+ * Diagram size in pixels: the rendered diameter/height for `"fixed"` sizing,
1025
+ * or the diameter/height of the largest feature for scaled sizing.
1026
+ */
1027
+ diagramSize: number;
1028
+ /** Numeric attribute driving `"attribute"` sizing (see {@link DiagramSizeMode}). */
1029
+ diagramSizeProperty: string;
1030
+ /** Minimum zoom at which diagrams are drawn, to avoid clutter when zoomed out. */
1031
+ diagramMinZoom: number;
1032
+ /**
1033
+ * When true, diagrams that would overlap an already-placed diagram on screen
1034
+ * are skipped (largest first), decluttering dense areas. Recomputed as the
1035
+ * view changes.
1036
+ */
1037
+ diagramDeclutter: boolean;
1038
+ pointRenderer: PointRenderer;
1039
+ heatmapRadius: number;
1040
+ heatmapIntensity: number;
1041
+ /** Built-in color ramp used by the heatmap density renderer. */
1042
+ heatmapColorRamp: string;
1043
+ /** Numeric feature property used as heatmap weight; blank gives every point equal weight. */
1044
+ heatmapWeightProperty: string;
1045
+ clusterRadius: number;
1046
+ clusterMaxZoom: number;
1047
+ /**
1048
+ * When true, the polygon fill renders *inverted*: the area outside the
1049
+ * features is filled (with {@link fillColor}/{@link fillOpacity}) and the
1050
+ * features themselves become holes, mirroring the QGIS "Inverted polygons"
1051
+ * renderer. Feature outlines still render normally. Only applies to layers
1052
+ * with polygon geometry; ignored while {@link extrusionEnabled} is on.
1053
+ */
1054
+ invertedFillEnabled: boolean;
1055
+ /**
1056
+ * Repeated decoration symbol drawn along line features and polygon outlines
1057
+ * (QGIS marker-line / arrow lines). `"none"` disables it. See
1058
+ * {@link LineDecoration}.
1059
+ */
1060
+ lineDecoration: LineDecoration;
1061
+ /**
1062
+ * Decoration symbol color (6-digit hex). An empty string inherits
1063
+ * {@link strokeColor} so decorations follow the stroke by default.
1064
+ */
1065
+ lineDecorationColor: string;
1066
+ /** Decoration symbol size in pixels. */
1067
+ lineDecorationSize: number;
1068
+ /** Distance between consecutive decoration symbols in pixels. */
1069
+ lineDecorationSpacing: number;
1070
+ /**
1071
+ * Per-feature derived geometry drawn over the layer's normal symbology
1072
+ * (QGIS geometry generator). `"none"` disables it. See
1073
+ * {@link GeometryGeneratorType}.
1074
+ */
1075
+ geometryGenerator: GeometryGeneratorType;
1076
+ /** Buffer distance in meters for the `"buffer"` generator. */
1077
+ geometryGeneratorBufferDistance: number;
1078
+ /**
1079
+ * Attribute driving the `"buffer"` generator's distance, so each feature is
1080
+ * buffered by its own value in meters (QGIS data-defined override on a
1081
+ * geometry-generator symbol). An empty string buffers every feature by the
1082
+ * flat {@link geometryGeneratorBufferDistance}, which also stands in for
1083
+ * features whose value is missing or non-numeric.
1084
+ */
1085
+ geometryGeneratorBufferProperty: string;
1086
+ /** Fill color (6-digit hex) for generated polygons and centroid points. */
1087
+ geometryGeneratorFillColor: string;
1088
+ /** Outline color (6-digit hex) for generated geometry. */
1089
+ geometryGeneratorStrokeColor: string;
1090
+ /** Outline width in pixels for generated geometry. */
1091
+ geometryGeneratorStrokeWidth: number;
1092
+ /** Fill opacity (0..1) for generated polygons and centroid points. */
1093
+ geometryGeneratorOpacity: number;
1094
+ /** Circle radius in pixels for generated centroid points. */
1095
+ geometryGeneratorCircleRadius: number;
1096
+ /**
1097
+ * Attribute driving the radius of generated centroid points, scaling them
1098
+ * between {@link geometryGeneratorSizeMinRadius} and
1099
+ * {@link geometryGeneratorSizeMaxRadius} across
1100
+ * {@link geometryGeneratorSizeMinValue} ..
1101
+ * {@link geometryGeneratorSizeMaxValue} (proportional symbols on the derived
1102
+ * centroids). An empty string draws every centroid at the flat
1103
+ * {@link geometryGeneratorCircleRadius}.
1104
+ *
1105
+ * Deliberately separate from {@link proportionalSizeProperty}: that one also
1106
+ * drives line width, so reusing it here would resize a polygon layer's
1107
+ * outlines as a side effect of sizing its centroids.
1108
+ */
1109
+ geometryGeneratorSizeProperty: string;
1110
+ geometryGeneratorSizeMinValue: number;
1111
+ geometryGeneratorSizeMaxValue: number;
1112
+ geometryGeneratorSizeMinRadius: number;
1113
+ geometryGeneratorSizeMaxRadius: number;
1114
+ rasterBrightnessMin: number;
1115
+ rasterBrightnessMax: number;
1116
+ rasterSaturation: number;
1117
+ rasterContrast: number;
1118
+ rasterHueRotate: number;
1119
+ /**
1120
+ * How this layer composites onto whatever is drawn beneath it. `"normal"`
1121
+ * (the default) is ordinary alpha compositing; `"multiply"` is the classic
1122
+ * cartographic case of laying colour over a hillshade so the relief still
1123
+ * reads through. Applies to the layer's own rendered geometry and raster
1124
+ * tiles, not to its labels, which stay legible on top. See
1125
+ * {@link BLEND_MODES} for the available modes and why the list is what it is.
1126
+ */
1127
+ blendMode?: BlendMode;
1128
+ }
1129
+ declare const DEFAULT_LAYER_STYLE: LayerStyle;
1130
+ /**
1131
+ * Read a layer style property, falling back to the shared default when the
1132
+ * layer does not define it. Shared by `@geolibre/map` and the desktop app so
1133
+ * the two consumers cannot drift.
1134
+ */
1135
+ declare function styleValue<K extends keyof LayerStyle>(style: LayerStyle, key: K): LayerStyle[K];
1136
+ /**
1137
+ * Feature-count threshold above which a local vector (GeoJSON) layer is rendered
1138
+ * through client-side vector tiles (geojson-vt / supercluster served by a custom
1139
+ * MapLibre protocol) instead of one in-memory geojson source pushed via
1140
+ * `setData`. Small layers stay on the simpler inline path. Mirrors the
1141
+ * `MAX_CEREUS_FEATURES` precedent in the desktop SQL engine.
1142
+ */
1143
+ declare const LARGE_VECTOR_FEATURE_THRESHOLD = 50000;
1144
+ /**
1145
+ * Decide whether a GeoJSON layer should use the tiled rendering path.
1146
+ *
1147
+ * @param geojson - The layer's feature collection (may be undefined for
1148
+ * non-vector layers).
1149
+ * @returns `true` when the collection exceeds
1150
+ * {@link LARGE_VECTOR_FEATURE_THRESHOLD} features.
1151
+ */
1152
+ declare function shouldUseTiledRendering(geojson: GeoJSON.FeatureCollection | undefined): boolean;
1153
+ /**
1154
+ * Match statistics from the last time a {@link LayerJoin} was applied, shown in
1155
+ * the Joins UI so silent key mismatches are visible (mirrors QGIS's join
1156
+ * feedback). Recomputed on every apply; persisted harmlessly with the join.
1157
+ */
1158
+ interface LayerJoinStats {
1159
+ /** Target features whose key matched a join-table row. */
1160
+ matchedCount: number;
1161
+ /** Target features with no matching join-table row (their joined columns are null). */
1162
+ unmatchedTargetCount: number;
1163
+ /** Join-table rows (with a non-empty key) that matched no target feature. */
1164
+ unmatchedJoinCount: number;
1165
+ }
1166
+ /**
1167
+ * A persistent attribute join attached to a layer (QGIS Layer Properties →
1168
+ * Joins): a live left join that augments the layer's attribute table with
1169
+ * columns from another layer (typically a geometry-less table added via
1170
+ * Delimited Text with no coordinate fields) matched on a key field. Unlike the
1171
+ * Processing → Vector attribute join, the layer keeps its identity — styles,
1172
+ * labels, and position — and the joined columns refresh whenever the join
1173
+ * table's data changes. Definitions persist in `.geolibre.json` and re-resolve
1174
+ * on project load. See `joins.ts` for the engine.
1175
+ */
1176
+ interface LayerJoin {
1177
+ /** Stable id for list edits. */
1178
+ id: string;
1179
+ /** Id of the layer providing the joined columns (its geometry is ignored). */
1180
+ joinLayerId: string;
1181
+ /** Key field on the layer that owns this join. */
1182
+ targetField: string;
1183
+ /** Key field on the join layer. */
1184
+ joinField: string;
1185
+ /**
1186
+ * Join-layer fields to bring over; `undefined` brings every field except the
1187
+ * key. Names are the join layer's own (pre-prefix) field names.
1188
+ */
1189
+ fields?: string[];
1190
+ /** Optional prefix prepended to every joined column name (as in QGIS). */
1191
+ prefix?: string;
1192
+ /** `false` detaches the joined columns without deleting the definition. */
1193
+ enabled?: boolean;
1194
+ /**
1195
+ * Bookkeeping written by the engine: the output column names this join added
1196
+ * to the layer's features. Applying joins strips these first, which makes
1197
+ * re-application idempotent without keeping a duplicate copy of the base
1198
+ * data (base columns always win a name collision, so a joined column never
1199
+ * shadows one). Not user-editable.
1200
+ */
1201
+ addedFields?: string[];
1202
+ /** Last-run match statistics; see {@link LayerJoinStats}. */
1203
+ stats?: LayerJoinStats;
1204
+ }
1205
+ /** Edit-widget kinds the Attribute Form designer can assign to a field. */
1206
+ type AttributeFormWidget = "text" | "number" | "range" | "checkbox" | "date" | "valueMap";
1207
+ /** One selectable entry of a `valueMap` widget (stored value + display label). */
1208
+ interface AttributeFormValueMapEntry {
1209
+ /** The value written to the feature property (compared as a string). */
1210
+ value: string;
1211
+ /** Human-readable label shown in the dropdown; defaults to {@link value}. */
1212
+ label?: string;
1213
+ }
1214
+ /**
1215
+ * Per-field configuration authored in the Attribute Form designer (layer
1216
+ * properties → Attributes Form, QGIS-style): which edit widget the attribute
1217
+ * editing surfaces render for the field, plus optional expression-based
1218
+ * constraints and conditional visibility. Consumed by the attribute table's
1219
+ * inline editor and the Field Collection capture form; helpers live in
1220
+ * `attribute-form.ts`.
1221
+ */
1222
+ interface AttributeFormFieldConfig {
1223
+ /** Feature property key this configuration applies to. */
1224
+ field: string;
1225
+ widget: AttributeFormWidget;
1226
+ /** Display label override shown in forms instead of the raw field name. */
1227
+ alias?: string;
1228
+ /**
1229
+ * When true, a null/empty value fails validation. Checkbox widgets are
1230
+ * exempt: unchecked is a valid state, not a missing value.
1231
+ */
1232
+ required?: boolean;
1233
+ /** Dropdown entries for the `valueMap` widget. */
1234
+ valueMap?: AttributeFormValueMapEntry[];
1235
+ /**
1236
+ * Declared storage type of the field. When set, coercion follows it instead
1237
+ * of guessing from the value map (a text field whose codes are "1" and "2"
1238
+ * still stores strings), and validation rejects values of another type or,
1239
+ * for `integer`, fractional numbers. Service-derived configs (ArcGIS domains)
1240
+ * set it from the field's declared type; designer-authored configs leave it
1241
+ * unset.
1242
+ */
1243
+ valueType?: "string" | "number" | "integer";
1244
+ /** Lower bound for `number`/`range` widgets (inclusive). */
1245
+ min?: number;
1246
+ /** Upper bound for `number`/`range` widgets (inclusive). */
1247
+ max?: number;
1248
+ /** Step for the `range` widget's input. */
1249
+ step?: number;
1250
+ /**
1251
+ * Boolean MapLibre expression that must evaluate to `true` against the
1252
+ * feature's (candidate) properties for the value to be accepted, e.g.
1253
+ * `[">", ["get", "population"], 0]`. Stored as the expression source string
1254
+ * the Expression Builder edits.
1255
+ */
1256
+ constraintExpression?: string;
1257
+ /** Human-readable message shown when the constraint fails. */
1258
+ constraintDescription?: string;
1259
+ /**
1260
+ * Boolean MapLibre expression controlling whether the field is shown in
1261
+ * attribute forms; `false` hides the field (and skips its validation).
1262
+ * Empty/invalid expressions fail open so a typo cannot hide data entry.
1263
+ */
1264
+ visibilityExpression?: string;
1265
+ }
1266
+ /** The Attribute Form designer's whole per-layer configuration. */
1267
+ interface AttributeFormConfig {
1268
+ fields: AttributeFormFieldConfig[];
1269
+ }
1270
+ /** Point of contact recorded in a layer's {@link LayerDescriptiveMetadata}. */
1271
+ interface LayerMetadataContact {
1272
+ name?: string;
1273
+ email?: string;
1274
+ organization?: string;
1275
+ }
1276
+ /** A related resource recorded in a layer's {@link LayerDescriptiveMetadata}. */
1277
+ interface LayerMetadataLink {
1278
+ /** Absolute URL of the resource. */
1279
+ href: string;
1280
+ /** Relation type (STAC/IANA link relation such as `"license"` or `"related"`). */
1281
+ rel?: string;
1282
+ /** Human-readable label. */
1283
+ title?: string;
1284
+ }
1285
+ /**
1286
+ * Time span the layer's data covers. Each bound is an ISO 8601 date
1287
+ * (`YYYY-MM-DD`) or date-time; either may be omitted for an open interval.
1288
+ */
1289
+ interface LayerMetadataTemporalExtent {
1290
+ start?: string;
1291
+ end?: string;
1292
+ }
1293
+ /**
1294
+ * User-authored, catalog-style description of a layer (issue #2858), edited in
1295
+ * the layer's Metadata dialog and exported as a STAC Item. Distinct from the
1296
+ * internal {@link GeoLibreLayer.metadata} record the renderers and plugins key
1297
+ * off: every field here is free-form documentation that changes nothing about
1298
+ * how the layer renders. Helpers live in `layer-descriptive-metadata.ts`.
1299
+ */
1300
+ interface LayerDescriptiveMetadata {
1301
+ title?: string;
1302
+ /** Free-text summary of the data (STAC `description`, ISO abstract). */
1303
+ abstract?: string;
1304
+ keywords?: string[];
1305
+ /** SPDX license identifier (e.g. `"CC-BY-4.0"`) or free text. */
1306
+ license?: string;
1307
+ /** Credit line for the data's producers. */
1308
+ attribution?: string;
1309
+ contact?: LayerMetadataContact;
1310
+ /** How the data was produced: sources and processing steps. */
1311
+ lineage?: string;
1312
+ temporalExtent?: LayerMetadataTemporalExtent;
1313
+ links?: LayerMetadataLink[];
1314
+ }
1315
+ /**
1316
+ * How a popup renders one field's value (issue #2113). `"auto"` reproduces the
1317
+ * untyped rendering the Identify popup has always done — sanitized KML
1318
+ * `description` markup, an inline `data:image/*;base64` value as a thumbnail,
1319
+ * anything else stringified. The remaining kinds are explicit author choices
1320
+ * so a popup never has to guess from the value.
1321
+ */
1322
+ type PopupFieldKind = "auto" | "text" | "number" | "date" | "link" | "image";
1323
+ /** How a `"date"` field's value is written out. */
1324
+ type PopupDateFormat = "date" | "datetime" | "time" | "iso" | "year";
1325
+ /** Value formatting for one popup field. Every part is optional. */
1326
+ interface PopupFieldFormat {
1327
+ /** Fixed number of decimals for a `"number"` field. */
1328
+ decimals?: number;
1329
+ /** Group thousands with the locale's separator (`"number"` fields). */
1330
+ thousands?: boolean;
1331
+ /** Date rendering for a `"date"` field; defaults to `"date"`. */
1332
+ dateFormat?: PopupDateFormat;
1333
+ /** Text placed before the formatted value. */
1334
+ prefix?: string;
1335
+ /** Text placed after the formatted value, e.g. a unit suffix. */
1336
+ suffix?: string;
1337
+ /** Link text for a `"link"` field; the value itself is used when unset. */
1338
+ linkLabel?: string;
1339
+ }
1340
+ /** One field's entry in a layer's popup configuration. */
1341
+ interface PopupFieldConfig {
1342
+ /** Feature property key. */
1343
+ field: string;
1344
+ /** Display label shown instead of the raw field name. */
1345
+ label?: string;
1346
+ /** How the value renders; defaults to `"auto"`. */
1347
+ kind?: PopupFieldKind;
1348
+ format?: PopupFieldFormat;
1349
+ /** Include this field in the hover tooltip's short subset. */
1350
+ hover?: boolean;
1351
+ }
1352
+ /**
1353
+ * Per-layer configuration for what a viewer sees when they interact with a
1354
+ * feature (issue #2113): which fields the Identify popup shows, in what order,
1355
+ * under what labels and formatting, plus an optional hover tooltip.
1356
+ *
1357
+ * A layer with no `popup` block behaves exactly as it did before this existed:
1358
+ * the layer name as the heading, then every property as a key/value row.
1359
+ * {@link GeoLibreLayer.fieldVisibility} stays authoritative — a `"hidden"` or
1360
+ * `"excluded"` field is dropped even when a popup config names it.
1361
+ */
1362
+ interface LayerPopupConfig {
1363
+ /** `false` suppresses the Identify popup for this layer. Defaults to `true`. */
1364
+ click?: boolean;
1365
+ /** `true` shows a hover tooltip built from the `hover` fields. Defaults to `false`. */
1366
+ hover?: boolean;
1367
+ /** Field whose value titles the popup instead of the layer name. */
1368
+ titleField?: string;
1369
+ /**
1370
+ * MapLibre expression source producing the popup title. Wins over
1371
+ * {@link titleField}; when it fails or produces nothing the title falls
1372
+ * through to `titleField`, and only then to the layer name.
1373
+ */
1374
+ titleExpression?: string;
1375
+ /**
1376
+ * MapLibre expression source producing the whole popup body as text, for
1377
+ * authors who want a sentence rather than a table. When it evaluates, it
1378
+ * replaces the field rows.
1379
+ */
1380
+ bodyExpression?: string;
1381
+ /** `false` drops the synthetic `id` row. Defaults to `true`. */
1382
+ showFeatureId?: boolean;
1383
+ /**
1384
+ * Widest the click popup may grow, in CSS pixels. Unset keeps the default
1385
+ * (520px, or 420px for a popup carrying an image). Clamped to the range
1386
+ * `resolvePopupMaxWidth` enforces and always capped by the viewport, so a
1387
+ * value wider than the window still leaves the map usable.
1388
+ */
1389
+ maxWidth?: number;
1390
+ /**
1391
+ * Tallest an `"image"` field's thumbnail may draw inside the popup, in CSS
1392
+ * pixels. Unset keeps the default (`min(50vh, 420px)`). Clamped by
1393
+ * `resolvePopupImageHeight`. Pair it with {@link maxWidth} for a
1394
+ * bigger picture: the thumbnail keeps its aspect ratio, so widening the
1395
+ * popup is what lets a landscape photo use the extra height.
1396
+ */
1397
+ imageHeight?: number;
1398
+ /**
1399
+ * The fields to show and their order. An empty or absent list keeps today's
1400
+ * behavior: every visible property, in the feature's own key order.
1401
+ */
1402
+ fields?: PopupFieldConfig[];
1403
+ }
1404
+ /**
1405
+ * A virtual field attached to a vector layer (QGIS Field Calculator → "Create
1406
+ * virtual field", issue #1321): a column defined by a MapLibre expression that
1407
+ * recomputes live instead of being written once as static values. The engine
1408
+ * (`virtual-fields.ts`) materializes the computed values into the layer's
1409
+ * feature properties — so the attribute table, Expression Builder,
1410
+ * data-driven styling, labels, and selection all see the column with no
1411
+ * further wiring — and re-derives them whenever the layer's data (or its
1412
+ * joins) change. Definitions persist in `.geolibre.json` and re-resolve on
1413
+ * project load; the expression is a declarative MapLibre expression (never
1414
+ * arbitrary code), so re-evaluating it from a shared project file is safe.
1415
+ */
1416
+ interface LayerVirtualField {
1417
+ /** Stable id for list edits. */
1418
+ id: string;
1419
+ /** The output column name. A name already taken by a base column is skipped. */
1420
+ name: string;
1421
+ /**
1422
+ * MapLibre expression source (JSON text, e.g. `["/", ["get", "pop"],
1423
+ * ["get", "area_km2"]]`) evaluated against each feature.
1424
+ */
1425
+ expression: string;
1426
+ /** `false` detaches the computed column without deleting the definition. */
1427
+ enabled?: boolean;
1428
+ /**
1429
+ * Bookkeeping written by the engine: the column name actually materialized
1430
+ * on the last apply, absent when the field was disabled, failed to compile,
1431
+ * or was skipped because the name collided with an existing column.
1432
+ * Applying virtual fields strips these first, which makes re-application
1433
+ * idempotent (an existing column is never shadowed, so stripping exactly
1434
+ * restores the pre-apply properties). Not user-editable.
1435
+ */
1436
+ addedField?: string;
1437
+ /** Compile error from the last apply, when the expression failed to parse. */
1438
+ error?: string;
1439
+ /**
1440
+ * Features whose evaluation threw at runtime on the last apply (their cell
1441
+ * is null). Surfaced so the UI can warn without one bad feature aborting
1442
+ * the whole column.
1443
+ */
1444
+ errorCount?: number;
1445
+ }
1446
+ /**
1447
+ * Quick filters (issue #2114): the data-driven filter controls a layer offers
1448
+ * in its Quick Filters section. What persists is the *control state* — field,
1449
+ * kind, chosen values — not the compiled output; `quick-filters.ts` compiles it
1450
+ * to a MapLibre filter at sync time so a saved filter can always be reopened
1451
+ * and edited.
1452
+ */
1453
+ /** Which control a quick filter renders, and how it compiles. */
1454
+ type QuickFilterKind = "categorical" | "range" | "date" | "text";
1455
+ /** The comparison a `text` quick filter applies (always case-insensitive). */
1456
+ type QuickFilterTextOperator = "contains" | "startsWith" | "equals";
1457
+ /**
1458
+ * How a date field stores its values, deciding whether a `date` quick filter
1459
+ * compares ISO text or epoch numbers. `iso` covers both `YYYY-MM-DD` and full
1460
+ * `YYYY-MM-DDTHH:MM:SSZ` timestamps: only the leading `YYYY-MM-DD` slice is
1461
+ * compared, so a trailing time, a milliseconds fraction, or a `Z` cannot break
1462
+ * a boundary. Mirrors (deliberately, in a smaller form) the value kinds the
1463
+ * Time Slider's `detectValueKind` reports.
1464
+ */
1465
+ type QuickFilterDateKind = "iso" | "epochMs" | "epochS";
1466
+ /** A single filter control persisted on a layer. */
1467
+ interface LayerQuickFilter {
1468
+ /** Stable id, used as the React key and for edits/removal. */
1469
+ id: string;
1470
+ /** The feature property this control narrows. */
1471
+ field: string;
1472
+ kind: QuickFilterKind;
1473
+ /**
1474
+ * `false` keeps the control configured but inert, so a filter can be muted
1475
+ * without losing the values chosen for it. Defaults to enabled.
1476
+ */
1477
+ enabled?: boolean;
1478
+ /**
1479
+ * `categorical`: the chosen values. An empty (or omitted) selection means
1480
+ * "every value" and compiles to nothing — unchecking the last box clears the
1481
+ * filter rather than emptying the map.
1482
+ */
1483
+ values?: (string | number | boolean)[];
1484
+ /** `range`: inclusive bounds. `null`/omitted leaves that side open. */
1485
+ min?: number | null;
1486
+ max?: number | null;
1487
+ /**
1488
+ * `date`: inclusive `YYYY-MM-DD` bounds. The end day is included in full, so
1489
+ * a timestamp field filtered to a single day keeps that whole day.
1490
+ */
1491
+ start?: string | null;
1492
+ end?: string | null;
1493
+ /** `date`: how the field stores its values. Defaults to `iso`. */
1494
+ dateKind?: QuickFilterDateKind;
1495
+ /** `text`: the comparison. Defaults to `contains`. */
1496
+ operator?: QuickFilterTextOperator;
1497
+ /** `text`: the needle. Blank means no constraint. */
1498
+ text?: string;
1499
+ }
1500
+ /** Persisted refresh policy and most recent synchronization result for a layer. */
1501
+ interface LayerConnection {
1502
+ /** Owning layer id. Repeated here so records remain self-describing when exported. */
1503
+ layerId: string;
1504
+ /** Automatic refresh cadence in seconds, or null for manual synchronization only. */
1505
+ interval: number | null;
1506
+ /** ISO timestamp of the most recent successful synchronization. */
1507
+ lastSyncedAt: string | null;
1508
+ /** Most recent synchronization error. Cleared by a successful synchronization. */
1509
+ lastError: string | null;
1510
+ /** Whether a failed synchronization retains the last good data or clears it. */
1511
+ onFailure: "keep-last" | "clear";
1512
+ }
1513
+ /**
1514
+ * Configuration for automatic feature editor tracking (Issue #1677).
1515
+ * Maintains `created_by`, `created_at`, `edited_by`, `edited_at` fields
1516
+ * automatically when features are created or updated.
1517
+ */
1518
+ interface EditorTrackingConfig {
1519
+ /** `true` enables automatic creation/edit timestamp and author stamping. */
1520
+ enabled: boolean;
1521
+ /** Field name for creation author (default `"created_by"`). */
1522
+ createdByField?: string;
1523
+ /** Field name for creation timestamp (default `"created_at"`). */
1524
+ createdAtField?: string;
1525
+ /** Field name for last-edit author (default `"edited_by"`). */
1526
+ editedByField?: string;
1527
+ /** Field name for last-edit timestamp (default `"edited_at"`). */
1528
+ editedAtField?: string;
1529
+ }
1530
+ /**
1531
+ * Visibility of a layer's attribute field.
1532
+ * - "hidden": Not shown in the attribute table, identify popup, tooltips, or field pickers, but remains in the data.
1533
+ * - "excluded": Removed entirely from the data when the project is shared or exported.
1534
+ */
1535
+ type FieldVisibility = "hidden" | "excluded";
1536
+ /**
1537
+ * Explicit capability flags defining what actions a user/session may perform on a layer.
1538
+ * Any omitted capability falls back to the inferred default behavior for that layer's source.
1539
+ */
1540
+ interface LayerCapabilities {
1541
+ /** Can the layer's features or attributes be queried / identified. */
1542
+ query?: boolean;
1543
+ /** Can new features be created/added to the layer. */
1544
+ create?: boolean;
1545
+ /** Can existing features/attributes be modified. */
1546
+ update?: boolean;
1547
+ /** Can features be deleted from the layer. */
1548
+ delete?: boolean;
1549
+ /** Can the layer data/symbology be exported/downloaded. */
1550
+ export?: boolean;
1551
+ }
1552
+ /**
1553
+ * Application privilege identifiers defining discrete capabilities in GeoLibre.
1554
+ */
1555
+ type AppPrivilege = "layers:edit" | "layers:add-remote" | "layers:add-local" | "processing:run" | "processing:sidecar" | "project:save" | "project:share" | "project:share-public" | "plugins:install" | "assistant:use" | "connections:manage" | "export:data" | "export:image" | "settings:manage";
1556
+ /**
1557
+ * Standard named roles bundling application privileges.
1558
+ */
1559
+ type AppRole = "viewer" | "editor" | "publisher" | "administrator" | "custom";
1560
+ /**
1561
+ * Ephemeral application capabilities state defining the active role, effective privileges,
1562
+ * and optional restriction reason for the current session/deployment.
1563
+ */
1564
+ interface AppCapabilities {
1565
+ /**
1566
+ * The assigned application role. `custom` once an ad-hoc `grantAppPrivilege` /
1567
+ * `revokeAppPrivilege` has moved the set away from the bundle a named role
1568
+ * defines, so the role never claims a shape the privileges do not have.
1569
+ */
1570
+ role: AppRole;
1571
+ /** List of granted privileges for the active role or custom configuration. */
1572
+ privileges: AppPrivilege[];
1573
+ /**
1574
+ * Optional human-readable reason covering the whole set (e.g. "Action disabled
1575
+ * by deployment policy"), used for any privilege without its own.
1576
+ */
1577
+ reason?: string;
1578
+ /**
1579
+ * Per-privilege reasons, and why they exist: two privileges can be withheld by
1580
+ * different causes — a role bundle plus a licence limit, say — and a single
1581
+ * `reason` would make the second revocation relabel the first, so every gate
1582
+ * would explain itself with whichever cause happened to be recorded last.
1583
+ * Takes precedence over `reason` for the privileges it names.
1584
+ */
1585
+ privilegeReasons?: Partial<Record<AppPrivilege, string>>;
1586
+ }
1587
+ interface GeoLibreLayer {
1588
+ id: string;
1589
+ name: string;
1590
+ type: LayerType;
1591
+ source: Record<string, unknown>;
1592
+ visible: boolean;
1593
+ opacity: number;
1594
+ style: LayerStyle;
1595
+ metadata: Record<string, unknown>;
1596
+ beforeId?: string;
1597
+ geojson?: FeatureCollection;
1598
+ /** A SQL Server write needs a successful table reread before it may be submitted again. */
1599
+ mssqlWritebackPending?: boolean;
1600
+ /**
1601
+ * Explicit capability set for the layer (query, create, update, delete, export).
1602
+ * Unset capabilities default to the inferred behavior for the layer's source kind.
1603
+ */
1604
+ capabilities?: LayerCapabilities;
1605
+ /**
1606
+ * Automatic editor tracking configuration for feature creation/updates.
1607
+ */
1608
+ editorTracking?: EditorTrackingConfig;
1609
+ /**
1610
+ * Field-level visibility overrides. Fields marked as "excluded" are physically
1611
+ * removed from the data during export and sharing.
1612
+ */
1613
+ fieldVisibility?: Record<string, FieldVisibility>;
1614
+ /**
1615
+ * Per-field edit-widget, constraint, and visibility configuration authored
1616
+ * in the Attribute Form designer. Applied by the attribute editing surfaces
1617
+ * (attribute table inline editor, Field Collection capture form); persists
1618
+ * with the project like {@link joins}.
1619
+ */
1620
+ attributeForm?: AttributeFormConfig;
1621
+ /**
1622
+ * Popup and hover-tooltip design for this layer, authored in the Style
1623
+ * panel's Popup section. Absent means the default full-property dump.
1624
+ */
1625
+ popup?: LayerPopupConfig;
1626
+ /**
1627
+ * User-authored catalog metadata (title, abstract, keywords, license,
1628
+ * contact, lineage, temporal extent, links), edited in the layer's Metadata
1629
+ * dialog. Absent when nothing has been entered; an empty record is never
1630
+ * written to a project file.
1631
+ */
1632
+ descriptiveMetadata?: LayerDescriptiveMetadata;
1633
+ /**
1634
+ * Persistent attribute joins applied to this layer's features, in order.
1635
+ * The joined columns are materialized into `geojson` feature properties (so
1636
+ * the attribute table, Expression Builder, styling, and labels all see them)
1637
+ * and re-derived whenever the layer's or a join table's data changes.
1638
+ */
1639
+ joins?: LayerJoin[];
1640
+ /**
1641
+ * Expression-backed virtual fields computed for this layer's features, in
1642
+ * order. Applied after joins (so an expression can read joined columns) and
1643
+ * materialized into `geojson` feature properties; re-derived whenever the
1644
+ * layer's data changes. See {@link LayerVirtualField}.
1645
+ */
1646
+ virtualFields?: LayerVirtualField[];
1647
+ /**
1648
+ * Transient MapLibre filter expression applied on top of every rendered
1649
+ * sub-layer's geometry filter. The Time Slider plugin sets this on a bound
1650
+ * vector layer so scrubbing the timeline narrows the visible features to the
1651
+ * current time window, while the layer's own styling and opacity stay
1652
+ * untouched. It is derived from the slider's current date, so it is NOT
1653
+ * persisted (stripped by `prepareLayerForSave`); the binding config lives in
1654
+ * `metadata.timeBinding` and the filter is recomputed live on the next
1655
+ * activation. `undefined` means no time filter is applied.
1656
+ */
1657
+ timeFilter?: unknown[];
1658
+ /** Transient MapLibre expression applied by the iframe embed API. */
1659
+ embedFilter?: unknown[];
1660
+ /**
1661
+ * Project-persisted boolean MapLibre expression that narrows the features
1662
+ * rendered for this layer. Unlike a selection, this leaves the source data
1663
+ * intact and keeps non-matching features hidden until the filter is cleared.
1664
+ * It is composed with transient filters, quick filters, and rule visibility
1665
+ * by the map renderers.
1666
+ */
1667
+ filterExpression?: unknown[];
1668
+ /**
1669
+ * Data-driven filter controls authored in the layer's Quick Filters section
1670
+ * (issue #2114). Unlike {@link timeFilter} and {@link embedFilter} this is
1671
+ * persisted control *state*, not a compiled expression: `@geolibre/map`
1672
+ * compiles it at sync time (see `compileQuickFilters`) and combines the
1673
+ * result with the transient filters and the rule-based visibility filter, so
1674
+ * a host page's filter and a user's filter narrow the layer together.
1675
+ */
1676
+ quickFilters?: LayerQuickFilter[];
1677
+ sourcePath?: string;
1678
+ /**
1679
+ * Id of the {@link LayerGroup} this layer belongs to, or `undefined` when the
1680
+ * layer sits at the top level of the layer panel. Layers sharing a `groupId`
1681
+ * are kept contiguous in the store's flat `layers` array so the group renders
1682
+ * as one block; see `@geolibre/core`'s `layer-groups` helpers.
1683
+ */
1684
+ groupId?: string;
1685
+ /**
1686
+ * Project-persisted connection policy for reloadable layers. Runtime timers
1687
+ * are reconstructed from this record when a project opens.
1688
+ */
1689
+ connection?: LayerConnection;
1690
+ }
1691
+ /**
1692
+ * Options for {@link AppState.addTileLayer}: a native raster tile layer (XYZ,
1693
+ * WMS, or WMTS) that appears in the Layers panel and persists with the project,
1694
+ * just like a layer added through the Add Data dialog. Mirrors the raster
1695
+ * `source` fields MapLibre understands so an external plugin can register tile
1696
+ * layers without touching the map directly.
1697
+ */
1698
+ interface AddTileLayerOptions {
1699
+ /**
1700
+ * One or more XYZ tile URL templates (with `{x}`/`{y}`/`{z}` placeholders).
1701
+ * At least one non-empty template is required, or the layer renders nothing.
1702
+ */
1703
+ tiles: string[];
1704
+ /**
1705
+ * Layer discriminator, controlling how the layer is labelled and (for WMS)
1706
+ * dev-server proxied. Defaults to `"xyz"`. The layer's `source.type` is
1707
+ * always `"raster"`, so any other value (such as `"vector-tiles"` from an
1708
+ * untyped JS caller) throws rather than persisting a mislabelled source.
1709
+ */
1710
+ type?: "xyz" | "wms" | "wmts" | "raster";
1711
+ /** Service or base URL recorded on the source for display and restore. */
1712
+ url?: string;
1713
+ /** Tile size in pixels (default 256). */
1714
+ tileSize?: number;
1715
+ /** Attribution string shown in the map's attribution control. */
1716
+ attribution?: string;
1717
+ /** Visible extent as `[west, south, east, north]` in WGS84 degrees. */
1718
+ bounds?: [number, number, number, number];
1719
+ /** Minimum zoom at which tiles are requested. */
1720
+ minzoom?: number;
1721
+ /** Maximum zoom at which tiles are requested. */
1722
+ maxzoom?: number;
1723
+ /** Tile y-axis scheme; `"tms"` flips the y origin. Defaults to `"xyz"`. */
1724
+ scheme?: "xyz" | "tms";
1725
+ /** Initial visibility (default true). */
1726
+ visible?: boolean;
1727
+ /** Initial opacity in [0, 1] (default 1). */
1728
+ opacity?: number;
1729
+ /**
1730
+ * Extra source fields merged onto the layer's `source` (e.g. the WMS
1731
+ * `layers`/`styles`/`format` recorded for restore). The required `type`,
1732
+ * `tiles`, and `tileSize` always win over keys supplied here.
1733
+ */
1734
+ source?: Record<string, unknown>;
1735
+ /** Extra metadata merged onto the layer record. */
1736
+ metadata?: Record<string, unknown>;
1737
+ }
1738
+ /**
1739
+ * A named, collapsible folder in the layer panel that organizes a contiguous
1740
+ * run of layers. Groups may be nested by referencing another group as their
1741
+ * parent; the root groups omit `parentId`.
1742
+ *
1743
+ * The group's `visible` flag and `opacity` multiplier are folded into each
1744
+ * child layer's effective render state by `applyGroupEffects` before the map
1745
+ * syncs, so children keep their own stored `visible`/`opacity` values.
1746
+ */
1747
+ interface LayerGroup {
1748
+ id: string;
1749
+ name: string;
1750
+ /** Parent folder id, or undefined when this folder is at the panel root. */
1751
+ parentId?: string;
1752
+ /** When true, the group's children are hidden in the panel (not on the map). */
1753
+ collapsed: boolean;
1754
+ /** Group-level visibility; ANDed with each child layer's own visibility. */
1755
+ visible: boolean;
1756
+ /** Group-level opacity in [0, 1]; multiplied into each child's opacity. */
1757
+ opacity: number;
1758
+ }
1759
+ /**
1760
+ * Metadata `sourceKind` marking a live SQL query layer: a GeoJSON-backed layer
1761
+ * created from a SQL Workspace result whose DuckDB statement is stored on the
1762
+ * layer metadata and re-executed on refresh. Defined here so the desktop app
1763
+ * (which owns the query/refresh logic) and `@geolibre/plugins` (which excludes
1764
+ * these layers from in-place geometry editing) share one value.
1765
+ */
1766
+ declare const SQL_QUERY_SOURCE_KIND = "sql-query";
1767
+ /**
1768
+ * Metadata `sourceKind` marking a NetCDF/HDF grid baked into an `image` overlay,
1769
+ * as opposed to the KML ground overlays that otherwise use that layer type.
1770
+ *
1771
+ * Defined here so `@geolibre/map` (which must not run its feature-query identify
1772
+ * on these) and the desktop app (which owns the symbology panel, the pixel
1773
+ * readout, and the spectral profile) share one value.
1774
+ */
1775
+ declare const NETCDF_IMAGE_SOURCE_KIND = "netcdf-image";
1776
+ /**
1777
+ * Detect a DuckDB query layer rendered through the plugin's external deck.gl
1778
+ * overlay. Shared by `@geolibre/map`, `@geolibre/plugins`, and the desktop
1779
+ * app so the detection criteria cannot drift.
1780
+ */
1781
+ declare function isDuckDBQueryLayer(layer: Pick<GeoLibreLayer, "metadata" | "type"> | undefined): boolean;
1782
+ interface MapViewState {
1783
+ center: [number, number];
1784
+ zoom: number;
1785
+ bearing: number;
1786
+ pitch: number;
1787
+ bbox?: [number, number, number, number];
1788
+ }
1789
+ /**
1790
+ * Multi-map (split/grid) support. The workspace can show several map panes in a
1791
+ * `rows x cols` grid. Pane 0 is always the *primary* map: it keeps the existing
1792
+ * single-map wiring (the global `mapView` + `basemap*` fields, the layer/style
1793
+ * panels, plugins, deck.gl). Panes 1..N are *secondary* maps described by
1794
+ * `SecondaryMapView` records: every pane shares the same basemap and the same
1795
+ * `layers`, but each secondary pane may override which layers are visible so
1796
+ * different panes can show different layers. When `syncView` is on,
1797
+ * panning/zooming any pane mirrors the camera to every other pane.
1798
+ */
1799
+ interface MapGridLayout {
1800
+ /** Grid rows (>= 1). */
1801
+ rows: number;
1802
+ /** Grid columns (>= 1). `rows * cols` is the total number of panes. */
1803
+ cols: number;
1804
+ /** When true, all panes share a synchronized camera. */
1805
+ syncView: boolean;
1806
+ }
1807
+ /**
1808
+ * Which engine draws a map pane.
1809
+ *
1810
+ * `"maplibre"` is the 2D MapLibre GL map that owns the app's plugin, styling,
1811
+ * and deck.gl integrations. `"cesium"` is the 3D globe (see `CesiumCanvas`),
1812
+ * which renders the same shared store state — camera, basemap, layers, group
1813
+ * effects — through CesiumJS. `"mapbox"` is Mapbox GL JS and `"arcgis"` the
1814
+ * ArcGIS Maps SDK for JavaScript, loaded from Esri's CDN at runtime (see
1815
+ * `ArcgisCanvas`); both draw the same store state through their own engines.
1816
+ *
1817
+ * Used both for secondary panes ({@link SecondaryMapView.viewKind}) and for the
1818
+ * primary workspace ({@link GeoLibreProject.primaryRenderer}), so the two never
1819
+ * drift apart.
1820
+ */
1821
+ type MapRendererKind = "maplibre" | "cesium" | "mapbox" | "arcgis";
1822
+ /**
1823
+ * The engine that draws the primary map area when a project says nothing. The
1824
+ * 2D map: it is the renderer every tool, plugin, and panel is wired to, so an
1825
+ * existing project (and a new one) opens exactly as it always did.
1826
+ */
1827
+ declare const DEFAULT_PRIMARY_RENDERER: MapRendererKind;
1828
+ /**
1829
+ * A non-primary map pane: shares the primary map's basemap and layers, with its
1830
+ * own camera and per-layer visibility overrides.
1831
+ */
1832
+ interface SecondaryMapView {
1833
+ /** Stable id, used as the React key and the sync-group registration id. */
1834
+ id: string;
1835
+ view: MapViewState;
1836
+ /** Optional user-entered label shown on the pane (e.g. a date or scenario). */
1837
+ label?: string;
1838
+ /**
1839
+ * Which rendering engine draws this pane. Defaults to `"maplibre"` (the 2D
1840
+ * map) when absent, so existing projects and panes are unchanged. `"cesium"`
1841
+ * renders a 3D globe (see {@link CesiumCanvas}) over the same shared layers.
1842
+ */
1843
+ viewKind?: MapRendererKind;
1844
+ /**
1845
+ * Per-layer visibility overrides keyed by layer id. A layer absent from this
1846
+ * map inherits the primary map's visibility (`layer.visible`); an entry forces
1847
+ * the layer visible (`true`) or hidden (`false`) in this pane only.
1848
+ */
1849
+ layerVisibility: Record<string, boolean>;
1850
+ }
1851
+ /** The default single-map grid (one pane, sync enabled so it turns on cleanly). */
1852
+ declare const DEFAULT_MAP_GRID_LAYOUT: MapGridLayout;
1853
+ /** Maximum rows or columns in the map grid (so at most a 4x4 = 16-pane grid). */
1854
+ declare const MAX_MAP_GRID_DIM = 4;
1855
+ /**
1856
+ * Live multi-user collaboration (issue #307). These types describe the
1857
+ * *ephemeral* session state the store holds while a live session is active. It
1858
+ * is intentionally never written to the `.geolibre.json` project file (the
1859
+ * `project.ts` serializers never read it) and never tracked in undo history (the
1860
+ * store's `partialize` never lists it), so it resets cleanly on reload.
1861
+ */
1862
+ type CollaborationRole = "host" | "guest";
1863
+ /** Whether guests may edit (`co-edit`) or only watch (`view-only`). */
1864
+ type CollaborationMode = "view-only" | "co-edit";
1865
+ interface ParticipantIdentity {
1866
+ provider: string;
1867
+ userId: string;
1868
+ username: string;
1869
+ }
1870
+ interface CollabInvite {
1871
+ token: string;
1872
+ role: CollaborationMode;
1873
+ createdAt: number;
1874
+ maxUses?: number;
1875
+ useCount: number;
1876
+ revoked: boolean;
1877
+ }
1878
+ interface CollaborationParticipant {
1879
+ clientId: string;
1880
+ displayName: string;
1881
+ color: string;
1882
+ role: CollaborationRole;
1883
+ /**
1884
+ * Host-set per-participant edit override (#754). `null` follows the session
1885
+ * `mode`; `true`/`false` pins this participant to can-edit / view-only. Always
1886
+ * `null` for the host (the host can always edit).
1887
+ */
1888
+ editOverride: boolean | null;
1889
+ /** Optional account identity when signed-in identity binding is enabled. */
1890
+ identity?: ParticipantIdentity | null;
1891
+ }
1892
+ /** A remote participant's live cursor + viewport, used to render presence. */
1893
+ interface CollaborationPresence {
1894
+ displayName: string;
1895
+ color: string;
1896
+ cursor?: {
1897
+ lng: number;
1898
+ lat: number;
1899
+ } | null;
1900
+ view?: MapViewState | null;
1901
+ }
1902
+ /** One in-session chat message (#754). Ephemeral; never persisted to a project. */
1903
+ interface CollaborationChatMessage {
1904
+ /** Server-assigned id (stable React key / dedupe). */
1905
+ id: string;
1906
+ /** clientId of the author. */
1907
+ clientId: string;
1908
+ displayName: string;
1909
+ color: string;
1910
+ text: string;
1911
+ /** Optional map coordinate the author attached; clickable to recenter. */
1912
+ coordinate?: {
1913
+ lng: number;
1914
+ lat: number;
1915
+ } | null;
1916
+ /** Server-assigned epoch-ms timestamp. */
1917
+ ts: number;
1918
+ }
1919
+ interface CollaborationState {
1920
+ /** True once connected and joined to a session. */
1921
+ isActive: boolean;
1922
+ /** True while connecting/reconnecting (UI shows a spinner). */
1923
+ connecting: boolean;
1924
+ sessionId: string | null;
1925
+ clientId: string | null;
1926
+ role: CollaborationRole | null;
1927
+ mode: CollaborationMode;
1928
+ selfName: string;
1929
+ selfColor: string;
1930
+ participants: CollaborationParticipant[];
1931
+ /** Remote presence keyed by participant clientId (never includes self). */
1932
+ presence: Record<string, CollaborationPresence>;
1933
+ /** When true, this participant's camera follows the host's viewport. */
1934
+ followHost: boolean;
1935
+ /** Recent session chat, oldest first, capped to a bounded window (#754). */
1936
+ chat: CollaborationChatMessage[];
1937
+ /** Session flag requiring participants to be signed in. */
1938
+ requireIdentity: boolean;
1939
+ /**
1940
+ * Whether the connected relay has an identity issuer configured. False (the
1941
+ * default) means it cannot verify a sign-in, so the host UI hides the
1942
+ * "require a signed-in account" toggle instead of offering a gate that would
1943
+ * lock every guest out.
1944
+ */
1945
+ identitySupported: boolean;
1946
+ /** Layer IDs marked locked by the host. */
1947
+ lockedLayerIds: string[];
1948
+ /** Active session invites minted by host. */
1949
+ invites: CollabInvite[];
1950
+ /** Last human-readable error, surfaced in the Collaborate dialog. */
1951
+ error: string | null;
1952
+ }
1953
+ /** Map projection the renderer uses. Mirrors the GlobeControl toggle. */
1954
+ type MapProjection = "globe" | "mercator";
1955
+ /**
1956
+ * Unit system the scale bar reports distances in. `"metric"` uses m/km,
1957
+ * `"imperial"` uses ft/mi, and `"nautical"` uses nautical miles.
1958
+ */
1959
+ type MapScaleUnit = "metric" | "imperial" | "nautical";
1960
+ interface MapPreferences {
1961
+ restrictBounds: boolean;
1962
+ bounds: [number, number, number, number];
1963
+ minZoom: number;
1964
+ maxZoom: number;
1965
+ maxPitch: number;
1966
+ renderWorldCopies: boolean;
1967
+ projection: MapProjection;
1968
+ /**
1969
+ * Celestial body / ellipsoid the project's coordinates describe (keys into the
1970
+ * ellipsoid registry in `@geolibre/core`). Drives measurement radii and pairs
1971
+ * with planetary basemaps. Defaults to `"earth"` (WGS 84).
1972
+ */
1973
+ ellipsoidId: string;
1974
+ /**
1975
+ * Unit system the scale bar displays. Defaults to `"metric"`; switch to
1976
+ * `"imperial"` for feet/miles or `"nautical"` for nautical miles.
1977
+ */
1978
+ scaleUnit: MapScaleUnit;
1979
+ /**
1980
+ * Whether the status bar resolves and shows the ground elevation under the
1981
+ * pointer (issue #1813). **Defaults to `false`**: with 3D terrain off the
1982
+ * lookup falls back to the public Open-Meteo service, so hovering would send
1983
+ * coordinates off the device for a readout the user never asked for. Toggled
1984
+ * from Controls -> Elevation.
1985
+ */
1986
+ showPointerElevation: boolean;
1987
+ /** Whether the built-in 3D terrain control and terrain surface are enabled. */
1988
+ terrainEnabled: boolean;
1989
+ /**
1990
+ * Optional Cesium Ion terrain asset used by the globe when terrain is enabled.
1991
+ * Ignored by 2D renderers; absent means the renderer's normal global source.
1992
+ */
1993
+ terrainIonAssetId?: number;
1994
+ /** Mapbox-only style. New projects use Streets; absent follows the shared basemap. */
1995
+ mapboxStyleUrl?: string;
1996
+ /**
1997
+ * ArcGIS-only basemap: an Esri basemap style id (`arcgis/streets`,
1998
+ * `arcgis/imagery`, `osm/standard`, ...). New projects use Streets. Absent
1999
+ * follows the shared basemap, translated to tiles the SDK can draw; the id
2000
+ * is also set aside when no ArcGIS API key is configured, since Esri's
2001
+ * basemap styles service requires one.
2002
+ */
2003
+ arcgisBasemap?: string;
2004
+ /**
2005
+ * ArcGIS-only projection of the flat map, as a WKID the ArcGIS Maps SDK
2006
+ * resolves (`54099` Spilhaus, `54030` Robinson, ...). Absent means Web
2007
+ * Mercator. Vector layers are reprojected on the client; tiled layers and
2008
+ * basemaps cannot be, so they do not draw in another projection. The globe
2009
+ * (a `SceneView`) ignores it. See `arcgis-projection.ts`.
2010
+ */
2011
+ arcgisWkid?: number;
2012
+ /** Cesium imagery override; absent follows the shared project basemap. */
2013
+ cesiumBasemap?: CesiumBasemapId;
2014
+ /**
2015
+ * Notation the status bar reports the pointer coordinate in: `"dd"` decimal
2016
+ * degrees (default), `"dms"` degrees/minutes/seconds, `"ddm"` degrees and
2017
+ * decimal minutes, `"utm"` zone easting/northing, `"mgrs"`/`"usng"` grid
2018
+ * references, or `"epsg"` x/y in the CRS named by `coordinateEpsgCode`.
2019
+ * Stored as a string rather than a union so `@geolibre/core` does not have to
2020
+ * depend on the formatter, which lives with the app's DMS helpers and the
2021
+ * Gridlines plugin's UTM projection; the app normalises unknown values to
2022
+ * `"dd"`.
2023
+ */
2024
+ coordinateFormat: string;
2025
+ /**
2026
+ * EPSG code the `"epsg"` coordinate format projects the pointer into, e.g.
2027
+ * `3857` or `32618`. Kept beside `coordinateFormat` rather than inside it so
2028
+ * the code survives cycling through the other notations. Absent means the
2029
+ * app default (EPSG:3857).
2030
+ */
2031
+ coordinateEpsgCode?: number;
2032
+ /**
2033
+ * Whether the map fits to a layer's extent after data is added (Add Data
2034
+ * menus, drag-and-drop, file import, tool outputs). Defaults to `true`; turn
2035
+ * it off to keep the current view. Explicit "Zoom to layer" actions always
2036
+ * fit regardless of this setting.
2037
+ */
2038
+ zoomToNewLayers: boolean;
2039
+ }
2040
+ interface RuntimeEnvironmentVariable {
2041
+ key: string;
2042
+ value: string;
2043
+ enabled: boolean;
2044
+ /**
2045
+ * Absent or `true`: the value is a secret (desktop keeps it in the OS
2046
+ * keychain; every egress removes it). `false`: an ordinary value saved in the
2047
+ * project and kept on share/export.
2048
+ */
2049
+ secret?: boolean;
2050
+ }
2051
+ declare global {
2052
+ interface Window {
2053
+ __GEOLIBRE_RUNTIME_ENV__?: Record<string, string>;
2054
+ }
2055
+ }
2056
+ /**
2057
+ * Geocoding backend selection persisted in the project. The provider id keys
2058
+ * into the geocoding registry in `@geolibre/core`; API keys are stored per
2059
+ * provider so switching backends does not discard the others' keys. Empty
2060
+ * endpoint overrides fall back to the provider's default endpoints.
2061
+ */
2062
+ interface GeocodingPreferences {
2063
+ providerId: string;
2064
+ /** Per-provider API key / access token, keyed by provider id. */
2065
+ apiKeys: Record<string, string>;
2066
+ /** Optional custom forward endpoint (else the provider default). */
2067
+ forwardEndpoint?: string;
2068
+ /** Optional custom reverse endpoint (else the provider default). */
2069
+ reverseEndpoint?: string;
2070
+ /** Contact email sent to identify the client (used by Nominatim). */
2071
+ email?: string;
2072
+ }
2073
+ interface ProjectPreferences {
2074
+ map: MapPreferences;
2075
+ environmentVariables: RuntimeEnvironmentVariable[];
2076
+ geocoding: GeocodingPreferences;
2077
+ }
2078
+ type ProjectPluginControlPosition = "top-left" | "top-right" | "bottom-left" | "bottom-right";
2079
+ interface ProjectPluginState {
2080
+ manifestUrls: string[];
2081
+ activePluginIds: string[];
2082
+ mapControlPositions: Record<string, ProjectPluginControlPosition>;
2083
+ settings: Record<string, unknown>;
2084
+ }
2085
+ declare const DEFAULT_PROJECT_PREFERENCES: ProjectPreferences;
2086
+ /**
2087
+ * A single user override for one legend item, keyed in {@link LegendConfig.overrides}
2088
+ * by a stable item key (a layer id for a whole entry, or `${layerId}::${index}`
2089
+ * for an individual class within a graduated/categorized entry).
2090
+ */
2091
+ interface LegendItemOverride {
2092
+ /** User-supplied label that replaces the auto-generated one. */
2093
+ label?: string;
2094
+ /** When true, the item is omitted from the rendered legend. */
2095
+ hidden?: boolean;
2096
+ }
2097
+ /** Swatch shape for a hand-authored legend item. */
2098
+ type LegendCustomShape = "square" | "circle" | "line";
2099
+ /** One hand-authored legend item: a color swatch plus its label. */
2100
+ interface LegendCustomItem {
2101
+ /** Display label (e.g. an NLCD class name). */
2102
+ label: string;
2103
+ /** Swatch color as a CSS color (typically `#rrggbb`). */
2104
+ color: string;
2105
+ /** Swatch shape; defaults to `"square"`. */
2106
+ shape?: LegendCustomShape;
2107
+ /**
2108
+ * Symbol size in map pixels (circle radius / line width). Carried so that
2109
+ * customizing a proportional-symbol entry keeps the graduated sizes it was
2110
+ * seeded from instead of flattening every symbol to one size.
2111
+ */
2112
+ size?: number;
2113
+ }
2114
+ /**
2115
+ * A hand-authored legend entry. Keyed by layer id it REPLACES that layer's
2116
+ * auto-derived classes (the fallback when automatic derivation is wrong or
2117
+ * impossible, e.g. a paletted land-cover raster like NLCD); keyed by a
2118
+ * `custom:` id it renders as a standalone section not tied to any layer.
2119
+ */
2120
+ interface LegendCustomEntry {
2121
+ /** Optional heading; a layer-keyed entry falls back to the layer name. */
2122
+ title?: string;
2123
+ /** The items to render, top to bottom. */
2124
+ items: LegendCustomItem[];
2125
+ }
2126
+ /** Map corner the on-map legend panel docks to. */
2127
+ type LegendPanelPosition = "top-left" | "top-right" | "bottom-left" | "bottom-right";
2128
+ /**
2129
+ * User customizations for the legend (the on-map Legend panel and the Print
2130
+ * Layout legend). The legend itself is always derived from the visible layers'
2131
+ * symbology; this record only stores the edits layered on top (title, ordering,
2132
+ * per-item rename/hide, hand-authored entries), so it survives layer additions
2133
+ * and removals and is persisted in the `.geolibre.json` project.
2134
+ */
2135
+ interface LegendConfig {
2136
+ /** Heading drawn above the legend entries. */
2137
+ title: string;
2138
+ /** When true, classes are grouped under a per-layer heading. */
2139
+ groupByLayer: boolean;
2140
+ /**
2141
+ * Custom top-level entry order by layer id, top-first. Layer ids not listed
2142
+ * keep their default order after the listed ones.
2143
+ */
2144
+ order: string[];
2145
+ /** Per-item overrides keyed by stable item key. */
2146
+ overrides: Record<string, LegendItemOverride>;
2147
+ /**
2148
+ * Hand-authored entries keyed by layer id (replacing that layer's
2149
+ * auto-derived classes) or by a standalone `custom:` id. See
2150
+ * {@link LegendCustomEntry}.
2151
+ */
2152
+ customEntries?: Record<string, LegendCustomEntry>;
2153
+ /** Whether the on-map Legend panel is open (persisted with the project). */
2154
+ panelVisible?: boolean;
2155
+ /** Whether the open panel is collapsed to just its header bar. */
2156
+ panelCollapsed?: boolean;
2157
+ /** Map corner the on-map Legend panel docks to; defaults to `"top-left"`. */
2158
+ panelPosition?: LegendPanelPosition;
2159
+ /**
2160
+ * User-resized panel width in px (via the corner drag handles). Absent means
2161
+ * the default width; height absent means auto-fit to content within the map.
2162
+ */
2163
+ panelWidth?: number;
2164
+ /** User-resized panel height in px. See {@link LegendConfig.panelWidth}. */
2165
+ panelHeight?: number;
2166
+ }
2167
+ declare const DEFAULT_LEGEND_CONFIG: LegendConfig;
2168
+ /**
2169
+ * Camera target captured for a story chapter. `pitch` and `bearing` are
2170
+ * optional in memory: a location built in code may omit them, and every engine
2171
+ * then keeps that part of the current camera (see `storyLocationView`). A
2172
+ * chapter read from a project file or a CSV never omits them: loading fills an
2173
+ * absent value with 0 (`normalizeStoryChapter`, `parseStoryMapCsv`), so a file
2174
+ * without them still plays north-up and flat, as it always has.
2175
+ */
2176
+ interface StoryChapterLocation {
2177
+ center: [number, number];
2178
+ zoom: number;
2179
+ pitch?: number;
2180
+ bearing?: number;
2181
+ }
2182
+ /** Where a chapter's text panel sits over the map. */
2183
+ type StoryChapterAlignment = "left" | "center" | "right" | "full";
2184
+ /** How the map transitions to a chapter's location. */
2185
+ type StoryChapterAnimation = "flyTo" | "easeTo" | "jumpTo";
2186
+ /** A layer opacity change triggered when a chapter is entered or exited. */
2187
+ interface StoryLayerOpacityChange {
2188
+ /** Stable identity for React list keys; optional for older project files. */
2189
+ id?: string;
2190
+ /** GeoLibre store layer id whose opacity should change. */
2191
+ layerId: string;
2192
+ opacity: number;
2193
+ /** Transition duration in milliseconds. */
2194
+ duration?: number;
2195
+ }
2196
+ /** A single scene in a scroll-driven story map. */
2197
+ interface StoryChapter {
2198
+ id: string;
2199
+ title: string;
2200
+ description: string;
2201
+ /** Optional image shown in the chapter panel (URL or data URI). */
2202
+ image?: string;
2203
+ alignment: StoryChapterAlignment;
2204
+ /** Hide the text panel while still transitioning the map. */
2205
+ hidden: boolean;
2206
+ location: StoryChapterLocation;
2207
+ mapAnimation: StoryChapterAnimation;
2208
+ /** Slowly rotate the camera once the transition settles. */
2209
+ rotateAnimation: boolean;
2210
+ onChapterEnter: StoryLayerOpacityChange[];
2211
+ onChapterExit: StoryLayerOpacityChange[];
2212
+ }
2213
+ type StoryInsetPosition = "top-left" | "top-right" | "bottom-left" | "bottom-right";
2214
+ /**
2215
+ * A non-chapter intro/outro slide shown before the first or after the last
2216
+ * chapter (#998). `"none"` disables it. The remaining modes are:
2217
+ * - `"blank"` a solid screen in the panel theme color,
2218
+ * - `"black"` a solid black screen,
2219
+ * - `"global"` a zoomed-out global view of the map with no text,
2220
+ * - `"adjacent"` the camera of the neighboring chapter (the first chapter for
2221
+ * the start slide, the last chapter for the closing slide) with all text and
2222
+ * media hidden, so the presenter can zoom into the full content next.
2223
+ */
2224
+ type StorySlideMode = "none" | "blank" | "black" | "global" | "adjacent";
2225
+ /** A start/closing slide that is actually shown (every mode except `"none"`). */
2226
+ type StoryActiveSlideMode = Exclude<StorySlideMode, "none">;
2227
+ /** Scroll-driven story map authored on top of a GeoLibre project. */
2228
+ interface StoryMap {
2229
+ title: string;
2230
+ subtitle: string;
2231
+ byline: string;
2232
+ footer: string;
2233
+ theme: "light" | "dark";
2234
+ showMarkers: boolean;
2235
+ markerColor: string;
2236
+ inset: boolean;
2237
+ insetPosition: StoryInsetPosition;
2238
+ /**
2239
+ * Start the presentation with the chapter itinerary/navigation panel hidden,
2240
+ * revealing chapters one at a time instead of listing every upcoming location
2241
+ * up front (#995). The presenter still offers a toggle to open the list.
2242
+ */
2243
+ hideChapterNav: boolean;
2244
+ /** Optional intro slide shown before the first chapter (#998). */
2245
+ startSlide: StorySlideMode;
2246
+ /** Optional closing slide shown after the last chapter (#998). */
2247
+ endSlide: StorySlideMode;
2248
+ chapters: StoryChapter[];
2249
+ }
2250
+ declare const DEFAULT_STORY_MAP: StoryMap;
2251
+ /**
2252
+ * One step in a {@link ProcessingModel}: a processing tool invoked with a fixed
2253
+ * set of parameters. The runner chains steps by feeding each step's output layer
2254
+ * into the next step's input layer parameter (`inputParam`, default `"layer"`),
2255
+ * so a step's stored `parameters` for that input is ignored for every step after
2256
+ * the first.
2257
+ */
2258
+ interface ProcessingModelStep {
2259
+ /** Stable id, unique within the model (used as the React key and run label). */
2260
+ id: string;
2261
+ /** The processing tool's registry id (e.g. `"buffer"`). */
2262
+ toolId: string;
2263
+ /** Parameter values keyed by the tool's parameter ids. */
2264
+ parameters: Record<string, unknown>;
2265
+ /**
2266
+ * Which `type: "layer"` parameter receives the previous step's output. Defaults
2267
+ * to `"layer"`; set it for tools whose primary input is named differently.
2268
+ */
2269
+ inputParam?: string;
2270
+ }
2271
+ /**
2272
+ * What flows along a model edge. Vector nodes exchange FeatureCollections;
2273
+ * raster nodes exchange GeoTIFF bytes. A port declaring `"any"` accepts either
2274
+ * and is resolved to a concrete kind at run time by whatever is wired into it.
2275
+ */
2276
+ type ModelPortKind = "vector" | "raster" | "any";
2277
+ /** One connection point on a {@link ModelGraphNode}. */
2278
+ interface ModelGraphPort {
2279
+ /**
2280
+ * Port id, unique within its node and direction. For a tool node's inputs
2281
+ * this is the underlying tool parameter id, so wiring an edge and setting the
2282
+ * parameter by hand are the same operation.
2283
+ */
2284
+ id: string;
2285
+ label: string;
2286
+ kind: ModelPortKind;
2287
+ /** Inputs only: the run fails when nothing is wired in and no value is set. */
2288
+ required?: boolean;
2289
+ }
2290
+ /**
2291
+ * What a node does. `input` sources an existing project layer, `tool` runs a
2292
+ * processing algorithm, and `output` names a result to add back to the map.
2293
+ */
2294
+ type ModelGraphNodeKind = "input" | "tool" | "output";
2295
+ /** One node on the Model Builder canvas. */
2296
+ interface ModelGraphNode {
2297
+ /** Stable id, unique within the graph; referenced by {@link ModelGraphEdge}. */
2298
+ id: string;
2299
+ kind: ModelGraphNodeKind;
2300
+ /** Canvas position in graph coordinates (unscaled by zoom). */
2301
+ x: number;
2302
+ y: number;
2303
+ /** `input` nodes: the project layer id this node sources. */
2304
+ layerId?: string;
2305
+ /**
2306
+ * `tool` nodes: the tool's id within {@link provider}'s registry. Kept
2307
+ * separate from the provider so the same short id can exist in both.
2308
+ */
2309
+ toolId?: string;
2310
+ /** `tool` nodes: which registry resolves {@link toolId}. */
2311
+ provider?: ModelToolProvider;
2312
+ /** `tool` nodes: parameter values for everything not supplied by an edge. */
2313
+ parameters?: Record<string, unknown>;
2314
+ /** `output` nodes: the layer name given to the result added to the map. */
2315
+ name?: string;
2316
+ }
2317
+ /**
2318
+ * A directed connection from one node's output port to another node's input
2319
+ * port. Ports are named, so a tool with several inputs (Clip's target and
2320
+ * overlay, say) wires each one unambiguously.
2321
+ */
2322
+ interface ModelGraphEdge {
2323
+ id: string;
2324
+ from: string;
2325
+ fromPort: string;
2326
+ to: string;
2327
+ toPort: string;
2328
+ }
2329
+ /** The node-and-edge graph authored on the Model Builder canvas. */
2330
+ interface ProcessingModelGraph {
2331
+ nodes: ModelGraphNode[];
2332
+ edges: ModelGraphEdge[];
2333
+ }
2334
+ /** Which registry a {@link ModelGraphNode.toolId} is resolved against. */
2335
+ type ModelToolProvider = "vector" | "whitebox";
2336
+ /**
2337
+ * A reusable processing pipeline ("model" in QGIS Graphical Modeler / ArcGIS
2338
+ * ModelBuilder terms), saved in the project file so it can be reloaded and
2339
+ * re-run.
2340
+ *
2341
+ * Two shapes coexist. {@link steps} is the original strictly linear chain, and
2342
+ * remains the only thing older builds understand. {@link graph} is the
2343
+ * Model Builder's directed graph, which supports multi-input tools, branches
2344
+ * and merges. When both are present `graph` wins; a model saved by the canvas
2345
+ * also writes a `steps` projection whenever its graph happens to be a single
2346
+ * chain, so older builds can still run it.
2347
+ */
2348
+ interface ProcessingModel {
2349
+ id: string;
2350
+ name: string;
2351
+ steps: ProcessingModelStep[];
2352
+ graph?: ProcessingModelGraph;
2353
+ }
2354
+ /**
2355
+ * Which dialog family executed a {@link ProcessingRun}. Drives the History
2356
+ * panel's re-run routing: each kind maps to the dialog (and store open-flag)
2357
+ * that can be reopened pre-filled with the run's parameters.
2358
+ */
2359
+ type ProcessingRunKind = "vector" | "statistics" | "network" | "whitebox" | "raster" | "conversion" | "algorithm";
2360
+ type ProcessingRunStatus = "success" | "error";
2361
+ /** Upper bound on persisted processing-history entries (oldest are dropped). */
2362
+ declare const MAX_PROCESSING_HISTORY = 100;
2363
+ /**
2364
+ * One recorded processing tool run (issue #1292). Appended by every processing
2365
+ * dialog when a run finishes and persisted in the project file, so a saved
2366
+ * project documents how its derived layers were produced. Parameter values are
2367
+ * stored exactly as the dialog dispatched them (layer parameters hold layer
2368
+ * ids), which is what re-run pre-fills and "Copy as Python" emit.
2369
+ */
2370
+ interface ProcessingRun {
2371
+ /** Stable id, unique within the project (React key and update key). */
2372
+ id: string;
2373
+ kind: ProcessingRunKind;
2374
+ /** The tool's registry id (e.g. `"buffer"`, `"slope"`). */
2375
+ toolId: string;
2376
+ /** Human-readable tool name captured at run time. */
2377
+ toolName: string;
2378
+ /** Engine that executed the run: `client`, `wasm`, `sidecar`, `pyodide`, `browser`. */
2379
+ engine: string;
2380
+ /** Parameter values keyed by the tool's parameter ids. */
2381
+ parameters: Record<string, unknown>;
2382
+ /** Names of input layers referenced by layer parameters, keyed by layer id. */
2383
+ inputLayerNames?: Record<string, string>;
2384
+ /** Names of layers the run added to the map. */
2385
+ outputLayerNames?: string[];
2386
+ /** Input file path/name, for file-based tools (raster/conversion). */
2387
+ inputPath?: string;
2388
+ /** Output file path/name, for file-based tools (raster/conversion). */
2389
+ outputPath?: string;
2390
+ /** ISO timestamp of when the run started. */
2391
+ startedAt: string;
2392
+ /** Wall-clock duration of the run in milliseconds. */
2393
+ durationMs?: number;
2394
+ status: ProcessingRunStatus;
2395
+ /** Error message when `status` is `"error"`. */
2396
+ error?: string;
2397
+ }
2398
+ /**
2399
+ * A pending "re-run from History" request. The History panel writes it to the
2400
+ * store and opens the target dialog; the dialog consumes it (pre-filling its
2401
+ * parameter form) and clears it. `autoRun` asks the dialog to start the run
2402
+ * immediately after pre-filling (plain Re-run vs Edit & re-run).
2403
+ */
2404
+ interface ProcessingRerunRequest {
2405
+ kind: ProcessingRunKind;
2406
+ toolId: string;
2407
+ parameters: Record<string, unknown>;
2408
+ /** Engine to preselect, when the dialog offers a choice. */
2409
+ engine?: string;
2410
+ autoRun?: boolean;
2411
+ }
2412
+ /** Column-count bounds for the Dashboard panel's widget grid. */
2413
+ declare const MIN_DASHBOARD_COLUMNS = 1;
2414
+ declare const MAX_DASHBOARD_COLUMNS = 6;
2415
+ declare const DEFAULT_DASHBOARD_COLUMNS = 2;
2416
+ /** The chart a {@link DashboardWidget} draws. Mirrors the attribute Charts
2417
+ * panel's types so a widget reuses the same rendering. The `"indicator"` type
2418
+ * is a non-chart KPI tile (issue #1381). */
2419
+ type DashboardWidgetType = "histogram" | "scatter" | "bar" | "line" | "box" | "pie" | "indicator" | "selector" | "list";
2420
+ /** How a bar widget reduces its category groups. */
2421
+ type DashboardWidgetAggregation = "count" | "sum" | "mean";
2422
+ /**
2423
+ * One chart in the Dashboard panel: a chart type bound to a layer and the
2424
+ * field(s) it plots. Which `field*`/`category`/`aggregation` keys apply depends
2425
+ * on `type` (histogram/line/box use `field`; scatter uses `xField`/`yField`;
2426
+ * bar uses `category` + `aggregation` and, for sum/mean, `valueField`). Unused
2427
+ * keys are simply ignored, so the record stays flat and easy to hand-edit.
2428
+ * Saved in the project file so a dashboard reopens with its widgets intact.
2429
+ */
2430
+ /**
2431
+ * A saved map view in the Bookmarks panel. Mirrors `MapBookmark` from
2432
+ * maplibre-gl-components so the panel can load it unchanged.
2433
+ */
2434
+ interface ProjectBookmark {
2435
+ /** Unique within the project. */
2436
+ id: string;
2437
+ name: string;
2438
+ /** Camera center longitude, -180..180. */
2439
+ lng: number;
2440
+ /** Camera center latitude, -90..90. */
2441
+ lat: number;
2442
+ zoom: number;
2443
+ /** Camera tilt in degrees, 0..85. */
2444
+ pitch: number;
2445
+ /** Camera rotation in degrees. */
2446
+ bearing: number;
2447
+ /** Creation time, milliseconds since the epoch. */
2448
+ createdAt: number;
2449
+ /** The {@link ProjectBookmarkGroup} it belongs to; absent when ungrouped. */
2450
+ groupId?: string;
2451
+ /**
2452
+ * Host state captured with the view. GeoLibre stores `visibleLayerIds`
2453
+ * (the layers to show when the bookmark is opened).
2454
+ */
2455
+ extra?: Record<string, unknown>;
2456
+ }
2457
+ /** A folder in the Bookmarks panel. Folders do not nest. */
2458
+ interface ProjectBookmarkGroup {
2459
+ id: string;
2460
+ name: string;
2461
+ /** Whether the folder is collapsed in the panel. */
2462
+ collapsed: boolean;
2463
+ }
2464
+ interface DashboardWidget {
2465
+ /** Stable id, unique within the project (React key and store key). */
2466
+ id: string;
2467
+ /** The layer whose features feed this widget. */
2468
+ layerId: string;
2469
+ /** The chart to draw. */
2470
+ type: DashboardWidgetType;
2471
+ /** Optional custom title; the panel derives a label from the fields if absent. */
2472
+ title?: string;
2473
+ /** Optional hex color (`#rgb`/`#rrggbb`) for the chart's marks. Single-series
2474
+ * charts use it as the series color; bar/pie use it as the base of a
2475
+ * monochromatic ramp. Defaults to the theme primary / multi-color palette. */
2476
+ color?: string;
2477
+ /** Value field for histogram/line/box. */
2478
+ field?: string;
2479
+ /** X-axis field for scatter. */
2480
+ xField?: string;
2481
+ /** Y-axis field for scatter. */
2482
+ yField?: string;
2483
+ /** Number of bins for a histogram. */
2484
+ bins?: number;
2485
+ /** Category field for a bar chart. */
2486
+ category?: string;
2487
+ /** Aggregation for a bar chart (default `count`). */
2488
+ aggregation?: DashboardWidgetAggregation;
2489
+ /** Value field a bar chart's sum/mean reduces (ignored for `count`). */
2490
+ valueField?: string;
2491
+ /** Indicator widget: aggregation function for the KPI value (issue #1381).
2492
+ * Extends bar aggregation with min, max, and median. */
2493
+ indicatorAggregation?: IndicatorAggregation;
2494
+ /** Indicator widget: optional prefix (e.g. "€", "$"). */
2495
+ prefix?: string;
2496
+ /** Indicator widget: optional suffix (e.g. " kg", " ha"). */
2497
+ suffix?: string;
2498
+ /** Selector widget: whether multiple values can be picked (default false). */
2499
+ multiple?: boolean;
2500
+ /** List widget: columns to display. */
2501
+ listFields?: string[];
2502
+ /** List widget: field to sort by. */
2503
+ sortBy?: string;
2504
+ /** List widget: sort direction (default "desc"). */
2505
+ sortDir?: "asc" | "desc";
2506
+ /** List widget: max rows to show (default 20). */
2507
+ limit?: number;
2508
+ }
2509
+ /** Aggregation functions for indicator widgets (issue #1381). Extends the bar
2510
+ * widget aggregation with min, max, and median. */
2511
+ type IndicatorAggregation = "count" | "sum" | "mean" | "min" | "max" | "median";
2512
+ /**
2513
+ * What slice of a layer's styling a Style Manager entry captures (issue #1294).
2514
+ *
2515
+ * - `"style"`: the layer's complete {@link LayerStyle} snapshot.
2516
+ * - `"symbol"`: fill/stroke/marker/pattern symbology only, so it can restyle a
2517
+ * layer without touching its labels or renderer configuration.
2518
+ * - `"labels"`: the {@link LabelStyle} block only.
2519
+ * - `"ramp"`: the color ramp + classification settings only (ramp name, class
2520
+ * count, classification scheme), independent of the attribute the target
2521
+ * layer classifies on.
2522
+ */
2523
+ type StyleLibraryEntryKind = "style" | "symbol" | "labels" | "ramp";
2524
+ /**
2525
+ * One saved, reusable style in the Style Manager library (issue #1294). The
2526
+ * payload is a {@link LayerStyle} subset chosen by {@link kind}; applying an
2527
+ * entry merges that subset onto the target layer's style. Entries live either
2528
+ * in the app-level library (persisted across projects) or embedded in a
2529
+ * project file's `styleLibrary` array.
2530
+ */
2531
+ interface StyleLibraryEntry {
2532
+ /** Stable id, used as the IndexedDB/store key; upserts overwrite by id. */
2533
+ id: string;
2534
+ /** Display name shown in the Style Manager. */
2535
+ name: string;
2536
+ /** Which style subset {@link style} carries. */
2537
+ kind: StyleLibraryEntryKind;
2538
+ /** Free-form tags for filtering the library. */
2539
+ tags: string[];
2540
+ /** The saved {@link LayerStyle} subset (see {@link kind}). */
2541
+ style: Partial<LayerStyle>;
2542
+ /** ISO timestamp of the last save; empty for built-in presets. */
2543
+ updatedAt: string;
2544
+ }
2545
+ /**
2546
+ * One saved, re-addable layer in the Layer Library (issue #1520) — the "My
2547
+ * Data" section of the Browser panel. Stores the layer's **source
2548
+ * specification** plus its full presentation state (style, labels, filters,
2549
+ * joins, virtual fields, attribute form), deliberately *not* the data, so the
2550
+ * library stays small and an entry always reflects the current contents of its
2551
+ * source.
2552
+ *
2553
+ * The exception is a layer whose features exist only in memory (drawn features,
2554
+ * processing output) or in a local file: those have no re-fetchable source, so
2555
+ * their features are embedded in {@link geojson} behind a size cap. See
2556
+ * `layer-library.ts` for how an entry is captured and re-added.
2557
+ */
2558
+ interface LayerLibraryEntry {
2559
+ /** Stable id, used as the IndexedDB/store key; upserts overwrite by id. */
2560
+ id: string;
2561
+ /** Display name shown in the Browser panel's My Data section. */
2562
+ name: string;
2563
+ /** ISO timestamp of the last save. */
2564
+ addedAt: string;
2565
+ /** The layer type to recreate ({@link GeoLibreLayer.type}). */
2566
+ layerType: LayerType;
2567
+ /** The MapLibre/plugin source spec to recreate the layer from. */
2568
+ source: Record<string, unknown>;
2569
+ /** The saved layer's complete {@link LayerStyle} (labels included). */
2570
+ style: LayerStyle;
2571
+ /** Layer opacity in [0, 1]. */
2572
+ opacity: number;
2573
+ /** The layer metadata the renderers and plugin sync modules key off. */
2574
+ metadata: Record<string, unknown>;
2575
+ /** Absolute local path, for a layer read from a file on disk. */
2576
+ sourcePath?: string;
2577
+ /** Persistent attribute joins to reapply. */
2578
+ joins?: LayerJoin[];
2579
+ /** Expression-backed virtual fields to reapply. */
2580
+ virtualFields?: LayerVirtualField[];
2581
+ /** Per-field edit-widget/constraint configuration to reapply. */
2582
+ attributeForm?: AttributeFormConfig;
2583
+ /** Popup/tooltip design to reapply. */
2584
+ popup?: LayerPopupConfig;
2585
+ /** User-authored catalog metadata to reapply. */
2586
+ descriptiveMetadata?: LayerDescriptiveMetadata;
2587
+ /**
2588
+ * Embedded features, present only for a layer whose source cannot be
2589
+ * re-read (in-memory features) or whose local file may be unavailable.
2590
+ */
2591
+ geojson?: FeatureCollection;
2592
+ /**
2593
+ * True when the entry can only be re-added by a host that can read
2594
+ * {@link sourcePath} — i.e. its features were too large to embed, so the
2595
+ * desktop app must re-read the file and the browser build cannot.
2596
+ */
2597
+ needsLocalFile?: boolean;
2598
+ }
2599
+ /** One saved template in the Template Library. */
2600
+ interface ProjectTemplateEntry {
2601
+ id: string;
2602
+ name: string;
2603
+ description?: string;
2604
+ project: GeoLibreProject;
2605
+ createdAt: string;
2606
+ updatedAt: string;
2607
+ }
2608
+ interface GeoLibreProject {
2609
+ id?: string;
2610
+ version: string;
2611
+ name: string;
2612
+ mapView: MapViewState;
2613
+ basemapStyleUrl: string;
2614
+ basemapVisible: boolean;
2615
+ basemapOpacity: number;
2616
+ /** Custom colour for the Blank background. Null uses the current theme default. */
2617
+ blankBackgroundColor?: string | null;
2618
+ layers: GeoLibreLayer[];
2619
+ /**
2620
+ * Layer selected in the Layers panel when the project was saved. Omitted by
2621
+ * legacy projects; `null` deliberately restores no active layer.
2622
+ */
2623
+ selectedLayerId?: string | null;
2624
+ /** Named folders that organize the flat `layers` list in the layer panel. */
2625
+ layerGroups?: LayerGroup[];
2626
+ styles: Record<string, LayerStyle>;
2627
+ preferences: ProjectPreferences;
2628
+ plugins?: ProjectPluginState;
2629
+ /** User customizations for the Print Layout legend. */
2630
+ legend?: LegendConfig;
2631
+ /**
2632
+ * Print Layout composer settings (title, page size, orientation, blocks,
2633
+ * atlas). Omitted while the composer is untouched, so projects that never
2634
+ * opened it are unaffected (GeoLibre discussion #1992).
2635
+ */
2636
+ printLayout?: PrintLayoutConfig;
2637
+ storymap?: StoryMap;
2638
+ /** Saved processing pipelines (batch/model chaining; issue #344). */
2639
+ models?: ProcessingModel[];
2640
+ /** Recorded processing tool runs (Processing History; issue #1292). */
2641
+ processingHistory?: ProcessingRun[];
2642
+ /**
2643
+ * Object-Based Analysis workbench state and run provenance (issue #3053):
2644
+ * settings, classes, and the tool calls, parameters, seeds and engine
2645
+ * versions behind the last segmentation, measurement and classification.
2646
+ * A versioned JSON object owned by the desktop app; omitted until the
2647
+ * workbench is used.
2648
+ */
2649
+ obia?: Record<string, unknown>;
2650
+ /** Saved Dashboard panel chart widgets (issue #401). */
2651
+ widgets?: DashboardWidget[];
2652
+ /**
2653
+ * Saved map views (the Bookmarks panel), in panel order. Omitted when there
2654
+ * are none. Before #2869 these lived only in the browser's localStorage.
2655
+ */
2656
+ bookmarks?: ProjectBookmark[];
2657
+ /** Folders the {@link bookmarks} are organized into. Omitted when none. */
2658
+ bookmarkGroups?: ProjectBookmarkGroup[];
2659
+ /** Number of columns in the Dashboard widget grid; omitted when default. */
2660
+ dashboardColumns?: number;
2661
+ /**
2662
+ * Multi-map grid layout; omitted (single 1x1 pane) for default projects so
2663
+ * legacy readers and single-map files are unaffected.
2664
+ */
2665
+ mapLayout?: MapGridLayout;
2666
+ /**
2667
+ * Secondary map panes (everything past the primary pane). Omitted when the
2668
+ * grid is a single pane. The primary pane uses the top-level `mapView` /
2669
+ * `basemap*` fields.
2670
+ */
2671
+ secondaryMapViews?: SecondaryMapView[];
2672
+ /** User-entered label for the primary pane; omitted when empty. */
2673
+ primaryMapLabel?: string;
2674
+ /**
2675
+ * Which engine draws the primary map area (issue #2217). Omitted for the
2676
+ * default 2D map, so every project written before this existed — and every
2677
+ * project that never leaves MapLibre — is byte-identical to before. A project
2678
+ * saved as `"cesium"` reopens directly on the 3D globe.
2679
+ *
2680
+ * This is independent of {@link mapLayout}: a 1x1 workspace can be either
2681
+ * renderer, and a multi-pane grid can still mix the two through each pane's
2682
+ * {@link SecondaryMapView.viewKind}.
2683
+ */
2684
+ primaryRenderer?: MapRendererKind;
2685
+ /**
2686
+ * Project-scoped Style Manager entries (issue #1294), so a project can carry
2687
+ * its reusable styles to teammates. Omitted when empty; the app-level
2688
+ * library is persisted outside the project file and never serialized here.
2689
+ */
2690
+ styleLibrary?: StyleLibraryEntry[];
2691
+ /** Anchored review comments on map points or features (issue #1518). */
2692
+ comments?: ProjectComment[];
2693
+ /**
2694
+ * Interaction state applied when the project opens: the Identify target and
2695
+ * which map controls and toolbar panels start shown (issue #2688). Written by
2696
+ * the Python `geolibre` package from `set_identify` / `show_control`, so a
2697
+ * saved or HTML-exported notebook map opens the way it was set up. Omitted
2698
+ * by default; the app keeps what it loaded and writes it back unchanged.
2699
+ */
2700
+ interaction?: ProjectInteraction;
2701
+ metadata: Record<string, unknown>;
2702
+ }
2703
+ /**
2704
+ * Startup interaction state carried by a project (see
2705
+ * {@link GeoLibreProject.interaction}).
2706
+ */
2707
+ interface ProjectInteraction {
2708
+ /**
2709
+ * Identify target on open: a layer id, `"all"` for every visible queryable
2710
+ * layer, a list of layer ids to identify only those, or `null` for off.
2711
+ */
2712
+ identify?: string | string[] | null;
2713
+ /**
2714
+ * Map control or toolbar panel name (`"search"`, `"bookmark"`, `"globe"`,
2715
+ * ...) to whether it starts shown. Unknown names are ignored on open.
2716
+ */
2717
+ controls?: Record<string, boolean>;
2718
+ }
2719
+ type CommentAnchor = {
2720
+ type: "point";
2721
+ lngLat: [number, number];
2722
+ } | {
2723
+ type: "feature";
2724
+ layerId: string;
2725
+ featureId: string | number;
2726
+ lngLat?: [number, number];
2727
+ };
2728
+ interface CommentAuthor {
2729
+ name: string;
2730
+ color: string;
2731
+ }
2732
+ interface CommentReply {
2733
+ id: string;
2734
+ author: CommentAuthor;
2735
+ body: string;
2736
+ createdAt: string;
2737
+ }
2738
+ interface ProjectComment {
2739
+ id: string;
2740
+ anchor: CommentAnchor;
2741
+ author: CommentAuthor;
2742
+ body: string;
2743
+ createdAt: string;
2744
+ resolved: boolean;
2745
+ replies: CommentReply[];
2746
+ }
2747
+ interface RecentProjectEntry {
2748
+ path: string;
2749
+ name: string;
2750
+ openedAt: string;
2751
+ }
2752
+ /**
2753
+ * Size at or above which a local vector file is read through DuckDB instead of
2754
+ * the in-memory JavaScript readers, and the feature count above which the user
2755
+ * is asked before every feature is materialized as GeoJSON.
2756
+ *
2757
+ * These live in core so the desktop loaders (`duckdb-vector-guard.ts`) and the
2758
+ * Add Vector Layer panel (`@geolibre/plugins`, which configures the
2759
+ * `maplibre-gl-vector` control's `autoThreshold`) switch strategy at the *same*
2760
+ * numbers. Before they were unified the two entry points disagreed — the panel
2761
+ * tiled at 25 MB / 50k while drag-and-drop stayed in memory to 100 MB — so the
2762
+ * same file behaved differently depending on how it was added, and no single
2763
+ * number could be documented.
2764
+ */
2765
+ declare const DUCKDB_VECTOR_ROUTE_BYTES: number;
2766
+ declare const DUCKDB_VECTOR_FEATURE_WARN_COUNT = 100000;
2767
+ /** A collection whose coordinates cannot be WGS84 longitude/latitude. */
2768
+ interface NonGeographicCoordinates {
2769
+ /** How many coordinates were inspected. */
2770
+ sampled: number;
2771
+ /** The largest |x| seen — a longitude may not exceed 180. */
2772
+ maxAbsX: number;
2773
+ /** The largest |y| seen — a latitude may not exceed 90. */
2774
+ maxAbsY: number;
2775
+ }
2776
+ /**
2777
+ * Detect a collection that declares (or is assumed to be) WGS84 but carries
2778
+ * projected coordinates — the failure mode where a layer loads cleanly, appears
2779
+ * in the Layers panel, and renders nowhere because its "longitude" is a easting
2780
+ * in metres or feet.
2781
+ *
2782
+ * GeoLibre honours whatever CRS a file declares, so a file that declares
2783
+ * `CRS84`/`GCS_WGS_1984` while holding State Plane or Albers coordinates is
2784
+ * passed through untouched and lands off the map with no error. Callers use
2785
+ * this to warn instead of failing silently; it does not guess the true CRS,
2786
+ * which only the user knows.
2787
+ *
2788
+ * Sampling stops at `sampleLimit` coordinates: out-of-range values are a
2789
+ * property of the whole file, so a prefix is enough and a 3-million-coordinate
2790
+ * collection is not walked twice.
2791
+ *
2792
+ * @returns Details when a coordinate is out of geographic range, else null.
2793
+ */
2794
+ declare function detectNonGeographicCoordinates(geojson: GeoJSON.FeatureCollection | undefined, sampleLimit?: number): NonGeographicCoordinates | null;
2795
+ //#endregion
2796
+ //#region src/ellipsoids.d.ts
2797
+ /**
2798
+ * Celestial-body ellipsoids and the planetary basemaps that pair with them.
2799
+ *
2800
+ * GeoLibre is Earth-centric by construction: MapLibre only renders Web Mercator
2801
+ * and all vector data is kept as WGS84 lon/lat GeoJSON. This module does *not*
2802
+ * change that — MapLibre still treats the planet as a unit sphere and there is
2803
+ * no true multi-CRS rendering (see maplibre-gl-js#168). What it adds is a
2804
+ * per-project notion of *which body* the coordinates describe, so that:
2805
+ *
2806
+ * - distance / area / scale measurements use that body's radius instead of a
2807
+ * hardcoded Earth radius, and
2808
+ * - Moon / Mars basemaps (published in a Web-Mercator tiling scheme) can be
2809
+ * selected like any other basemap.
2810
+ *
2811
+ * The active ellipsoid is a lightweight module-level singleton kept in sync with
2812
+ * the project's map preferences (see the store's `setPreferences`). Measurement
2813
+ * helpers read it lazily at call time, so callers in other packages don't need
2814
+ * the value threaded through their signatures — a deliberate prototype-friendly
2815
+ * shortcut over a full CRS/context refactor.
2816
+ */
2817
+ /** A biaxial (rotational) ellipsoid describing a celestial body. */
2818
+ interface Ellipsoid {
2819
+ /** Stable id persisted in the project (`map.ellipsoidId`). */
2820
+ id: string;
2821
+ /** Human-readable name shown in the UI. */
2822
+ name: string;
2823
+ /** Equatorial (semi-major) radius in metres — the "web mercator" radius. */
2824
+ semiMajorAxisMeters: number;
2825
+ /**
2826
+ * Inverse flattening `1/f`. `0` denotes a perfect sphere (Moon), in which case
2827
+ * the polar radius equals {@link semiMajorAxisMeters}.
2828
+ */
2829
+ inverseFlattening: number;
2830
+ }
2831
+ /**
2832
+ * Built-in ellipsoids. Earth is WGS 84; every other body uses IAU-adopted
2833
+ * figures. Only Earth and Mars are appreciably oblate; the rest are modelled as
2834
+ * spheres (`inverseFlattening: 0`), which is how the IAU/USGS simple-cylindrical
2835
+ * products treat them and is well within the accuracy MapLibre's unit-sphere
2836
+ * rendering can represent anyway.
2837
+ */
2838
+ declare const ELLIPSOIDS: readonly [{
2839
+ readonly id: "earth";
2840
+ readonly name: "Earth (WGS 84)";
2841
+ readonly semiMajorAxisMeters: 6378137;
2842
+ readonly inverseFlattening: 298.257223563;
2843
+ }, {
2844
+ readonly id: "moon";
2845
+ readonly name: "Moon";
2846
+ readonly semiMajorAxisMeters: 1737400;
2847
+ readonly inverseFlattening: 0;
2848
+ }, {
2849
+ readonly id: "mars";
2850
+ readonly name: "Mars (IAU 2000)";
2851
+ readonly semiMajorAxisMeters: 3396190;
2852
+ readonly inverseFlattening: 169.894447;
2853
+ }, {
2854
+ readonly id: "mercury";
2855
+ readonly name: "Mercury";
2856
+ readonly semiMajorAxisMeters: 2439400;
2857
+ readonly inverseFlattening: 0;
2858
+ }, {
2859
+ readonly id: "venus";
2860
+ readonly name: "Venus";
2861
+ readonly semiMajorAxisMeters: 6051800;
2862
+ readonly inverseFlattening: 0;
2863
+ }, {
2864
+ readonly id: "io";
2865
+ readonly name: "Io";
2866
+ readonly semiMajorAxisMeters: 1821600;
2867
+ readonly inverseFlattening: 0;
2868
+ }, {
2869
+ readonly id: "europa";
2870
+ readonly name: "Europa";
2871
+ readonly semiMajorAxisMeters: 1560800;
2872
+ readonly inverseFlattening: 0;
2873
+ }, {
2874
+ readonly id: "ganymede";
2875
+ readonly name: "Ganymede";
2876
+ readonly semiMajorAxisMeters: 2631200;
2877
+ readonly inverseFlattening: 0;
2878
+ }, {
2879
+ readonly id: "callisto";
2880
+ readonly name: "Callisto";
2881
+ readonly semiMajorAxisMeters: 2410300;
2882
+ readonly inverseFlattening: 0;
2883
+ }, {
2884
+ readonly id: "titan";
2885
+ readonly name: "Titan";
2886
+ readonly semiMajorAxisMeters: 2574730;
2887
+ readonly inverseFlattening: 0;
2888
+ }, {
2889
+ readonly id: "pluto";
2890
+ readonly name: "Pluto";
2891
+ readonly semiMajorAxisMeters: 1188300;
2892
+ readonly inverseFlattening: 0;
2893
+ }, {
2894
+ readonly id: "charon";
2895
+ readonly name: "Charon";
2896
+ readonly semiMajorAxisMeters: 606000;
2897
+ readonly inverseFlattening: 0;
2898
+ }];
2899
+ type EllipsoidId = (typeof ELLIPSOIDS)[number]["id"];
2900
+ declare const DEFAULT_ELLIPSOID_ID: EllipsoidId;
2901
+ /** Look an ellipsoid up by id, falling back to Earth for unknown ids. */
2902
+ declare function getEllipsoid(id: string | undefined): Ellipsoid;
2903
+ /**
2904
+ * Mean radius `R = (2a + b) / 3` in metres, where `b` is the polar radius
2905
+ * derived from the inverse flattening. This is the radius used for spherical
2906
+ * (haversine) distance and area math.
2907
+ */
2908
+ declare function meanRadiusMeters(ellipsoid: Ellipsoid): number;
2909
+ /** Earth's mean radius in metres — the radius Turf.js and MapLibre bake in. */
2910
+ declare const EARTH_MEAN_RADIUS_METERS: number;
2911
+ /**
2912
+ * Point the measurement helpers at a body. Unknown ids fall back to Earth so a
2913
+ * malformed project can never break measurements. Safe to call on every
2914
+ * preferences change; it is a cheap assignment.
2915
+ */
2916
+ declare function setActiveEllipsoidId(id: string | undefined): void;
2917
+ declare function getActiveEllipsoid(): Ellipsoid;
2918
+ /** Mean radius (metres) of the active body — for haversine distance/area. */
2919
+ declare function getActiveMeanRadiusMeters(): number;
2920
+ /** Semi-major axis (metres) of the active body — for its Web-Mercator scale. */
2921
+ declare function getActiveSemiMajorAxisMeters(): number;
2922
+ /**
2923
+ * The active body's mean radius as a fraction of Earth's — `R_body / R_earth`,
2924
+ * and exactly `1` on Earth.
2925
+ *
2926
+ * Everything GeoLibre measures ultimately comes from lon/lat *angles*, and the
2927
+ * only thing that turns an angle into a ground distance is the radius it is
2928
+ * multiplied by. Turf.js hardcodes Earth's, with no per-call override (that is
2929
+ * the "Turf is Earth-locked" half of GeoLibre#1128), so rather than patch or
2930
+ * replace Turf we post-scale what it returns and pre-scale what we hand it. The
2931
+ * result is exact for a sphere, which is how every non-Earth body GeoLibre
2932
+ * offers is modelled anyway, and within Mars' 0.6% flattening at worst.
2933
+ *
2934
+ * Suggested by @thareUSGS (USGS Astrogeology) on GeoLibre#1128 as the practical
2935
+ * workaround planetary web maps have long used on Earth-locked engines.
2936
+ */
2937
+ declare function getActiveBodyRadiusRatio(): number;
2938
+ /**
2939
+ * Convert a length Turf.js measured on Earth into the active body's true ground
2940
+ * length. A no-op on Earth.
2941
+ *
2942
+ * Use on anything Turf *returns* — a distance, a length, a perimeter.
2943
+ *
2944
+ * The correction is a dimensionless ratio, so it is unit-agnostic: pass the
2945
+ * length in whatever unit Turf gave it back (metres, kilometres, miles) and the
2946
+ * result is in that same unit. There is no hidden unit conversion here.
2947
+ */
2948
+ declare function earthLengthToBody(length: number): number;
2949
+ /**
2950
+ * Convert a true ground length on the active body into the Earth-equivalent
2951
+ * length to hand Turf.js. A no-op on Earth.
2952
+ *
2953
+ * Use on anything passed *into* Turf as a distance — a buffer width, a circle
2954
+ * or sector radius, a search threshold — so the resulting geometry spans that
2955
+ * distance on this body rather than on Earth.
2956
+ *
2957
+ * Unit-agnostic like {@link earthLengthToBody}: pass the length in whatever
2958
+ * unit you are about to hand Turf alongside it, and the result is in that unit.
2959
+ */
2960
+ declare function bodyLengthToEarth(length: number): number;
2961
+ /**
2962
+ * Convert an area Turf.js measured on Earth into the active body's true ground
2963
+ * area. A no-op on Earth.
2964
+ *
2965
+ * Areas scale with the *square* of the radius ratio, so this is not the same
2966
+ * correction as {@link earthLengthToBody}. Unit-agnostic in the same way, for
2967
+ * whatever squared unit the area is expressed in.
2968
+ */
2969
+ declare function earthAreaToBody(area: number): number;
2970
+ /**
2971
+ * A raster basemap for a celestial body — the Moon and Mars mosaics, plus the
2972
+ * Earth satellite imagery the planet switcher pairs with Earth. The tiles are
2973
+ * XYZ (or TMS, see {@link scheme}) images in that body's Web-Mercator scheme, so
2974
+ * MapLibre renders them directly. The `styleUrl` is a `geolibre://basemap/<id>`
2975
+ * sentinel; the map controller expands it into a raster style at apply time (it
2976
+ * is not a fetchable URL).
2977
+ */
2978
+ interface PlanetaryBasemap {
2979
+ id: string;
2980
+ name: string;
2981
+ /** Sentinel stored as the basemap style URL. */
2982
+ styleUrl: string;
2983
+ /** XYZ (or TMS, see {@link scheme}) tile template. */
2984
+ tileUrl: string;
2985
+ /**
2986
+ * Tile row ordering. OpenPlanetaryMap's single-layer raster mosaics (the S3
2987
+ * datasets) are published as **TMS** (tile origin bottom-left, Y flipped),
2988
+ * whereas the CARTO `opmbuilder` named maps are standard **XYZ**. Omit for
2989
+ * XYZ; MapLibre defaults to `"xyz"` when `scheme` is absent.
2990
+ */
2991
+ scheme?: "tms";
2992
+ /** Max native zoom of the source (MapLibre overzooms beyond this). */
2993
+ maxZoom: number;
2994
+ /** Attribution shown on the map. */
2995
+ attribution: string;
2996
+ /** The body this basemap depicts, so selecting it can set the ellipsoid. */
2997
+ ellipsoidId: EllipsoidId;
2998
+ }
2999
+ declare const PLANETARY_BASEMAP_SENTINEL_PREFIX = "geolibre://basemap/";
3000
+ declare const PLANETARY_BASEMAPS: readonly PlanetaryBasemap[];
3001
+ /**
3002
+ * The section a planetary basemap is grouped under in the New Project and
3003
+ * Change Basemap pickers. The Moon and Mars keep dedicated sections (they are
3004
+ * the most-used bodies and each has several mosaics); every other body is
3005
+ * collapsed into a single "other" section so the picker isn't a wall of
3006
+ * one-item headings.
3007
+ */
3008
+ type PlanetaryBasemapSectionId = "moon" | "mars" | "other";
3009
+ /** A picker section paired with the planetary basemaps it lists. */
3010
+ interface PlanetaryBasemapGroup {
3011
+ id: PlanetaryBasemapSectionId;
3012
+ basemaps: readonly PlanetaryBasemap[];
3013
+ }
3014
+ /**
3015
+ * {@link PLANETARY_BASEMAPS} grouped for the pickers: one section each for the
3016
+ * Moon and Mars, then a single "Other celestial bodies" section for the rest,
3017
+ * in {@link PLANETARY_BASEMAPS} order (grouped by body). Empty sections are
3018
+ * dropped.
3019
+ */
3020
+ declare const PLANETARY_BASEMAP_GROUPS: readonly PlanetaryBasemapGroup[];
3021
+ /** Resolve a `geolibre://basemap/<id>` sentinel to its planetary basemap. */
3022
+ declare function getPlanetaryBasemapByStyleUrl(styleUrl: string | undefined): PlanetaryBasemap | undefined;
3023
+ /** Look a planetary basemap up by its stable id. */
3024
+ declare function getPlanetaryBasemapById(id: string): PlanetaryBasemap | undefined;
3025
+ /**
3026
+ * The celestial bodies offered by the Layers-panel planet switcher, in menu
3027
+ * order (like Google Earth's planet dropdown). Each body is paired with the
3028
+ * basemap the switcher applies for it — USGS satellite imagery for Earth, the
3029
+ * OpenPlanetaryMap Hillshaded Albedo mosaic for the Moon, and the OpenPlanetaryMap
3030
+ * Viking mosaic for Mars.
3031
+ */
3032
+ declare const PLANET_SWITCHER_OPTIONS: readonly [{
3033
+ readonly ellipsoidId: "earth";
3034
+ readonly basemapId: "earth-usgs-imagery";
3035
+ }, {
3036
+ readonly ellipsoidId: "moon";
3037
+ readonly basemapId: "moon-hillshaded-albedo";
3038
+ }, {
3039
+ readonly ellipsoidId: "mars";
3040
+ readonly basemapId: "mars-viking-mdim21";
3041
+ }, {
3042
+ readonly ellipsoidId: "callisto";
3043
+ readonly basemapId: "callisto-galileo-voyager";
3044
+ }, {
3045
+ readonly ellipsoidId: "charon";
3046
+ readonly basemapId: "charon-mosaic";
3047
+ }, {
3048
+ readonly ellipsoidId: "europa";
3049
+ readonly basemapId: "europa-galileo-voyager";
3050
+ }, {
3051
+ readonly ellipsoidId: "ganymede";
3052
+ readonly basemapId: "ganymede-galileo-voyager";
3053
+ }, {
3054
+ readonly ellipsoidId: "io";
3055
+ readonly basemapId: "io-galileo-color";
3056
+ }, {
3057
+ readonly ellipsoidId: "mercury";
3058
+ readonly basemapId: "mercury-messenger-color";
3059
+ }, {
3060
+ readonly ellipsoidId: "pluto";
3061
+ readonly basemapId: "pluto-mosaic";
3062
+ }, {
3063
+ readonly ellipsoidId: "titan";
3064
+ readonly basemapId: "titan-cassini";
3065
+ }, {
3066
+ readonly ellipsoidId: "venus";
3067
+ readonly basemapId: "venus-magellan-color";
3068
+ }];
3069
+ //#endregion
3070
+ //#region src/geojson-z.d.ts
3071
+ /**
3072
+ * Helpers for vector data whose coordinates carry a Z value (elevation), e.g.
3073
+ * GPX tracks with `<ele>` readings or LineStringZ/PointZ geometries. MapLibre's
3074
+ * 2D style layers ignore the third coordinate, so layers that want to render
3075
+ * their Z values use these helpers to detect and rescale elevations before
3076
+ * handing the data to a deck.gl overlay. The same data can carry its elevation
3077
+ * in a `bbox` member too, which `horizontalBbox` trims back to two dimensions.
3078
+ */
3079
+ /**
3080
+ * Reduces a bounding box to its horizontal extent, `[west, south, east, north]`.
3081
+ *
3082
+ * RFC 7946 §5 lets a `bbox` member carry elevation, in which case it holds six
3083
+ * values — `[west, south, minAltitude, east, north, maxAltitude]` — and
3084
+ * `@turf/bbox` returns a collection's own member verbatim rather than
3085
+ * recomputing it. A consumer reading such a box as four values takes the
3086
+ * altitude for a longitude and the east edge for a latitude; that is how a 3D
3087
+ * feed reached MapLibre as an out-of-range latitude (#2358).
3088
+ *
3089
+ * Returns null when there is no usable horizontal extent: a box of any other
3090
+ * length, or one carrying a non-finite value anywhere — an empty collection, or
3091
+ * one whose features all have a null geometry, bboxes to ±Infinity, and a box
3092
+ * whose altitudes are not finite is not one to trust the rest of.
3093
+ *
3094
+ * @param box - A GeoJSON or Turf bounding box, of four or six values.
3095
+ */
3096
+ declare function horizontalBbox(box: readonly number[] | null | undefined): [number, number, number, number] | null;
3097
+ /**
3098
+ * Returns true when any coordinate in the GeoJSON carries a finite, non-zero
3099
+ * Z value. All-zero Z arrays are treated as flat data because rendering them
3100
+ * "in 3D" would be indistinguishable from the 2D result. The result is
3101
+ * cached per GeoJSON object, so repeated calls on the same data are free.
3102
+ *
3103
+ * @param geojson - Any GeoJSON object (geometry, feature, or collection).
3104
+ * @param includeZero - Accept zero elevations when explicit absolute-height placement matters.
3105
+ */
3106
+ declare function geojsonHasZCoordinates(geojson: GeoJSON$1 | null | undefined, includeZero?: boolean): boolean;
3107
+ /**
3108
+ * Returns a copy of the GeoJSON with every coordinate's Z value mapped to
3109
+ * `z * verticalScale + offset` (missing or non-finite Z treated as 0). Used
3110
+ * to apply vertical exaggeration and a constant altitude offset before 3D
3111
+ * rendering. The identity transform (`verticalScale === 1 && offset === 0`)
3112
+ * still produces a copy so the non-finite-Z sanitization applies on every
3113
+ * path — WebGL does not handle NaN positions gracefully.
3114
+ *
3115
+ * @param geojson - The feature collection to transform.
3116
+ * @param verticalScale - Multiplier applied to each Z value.
3117
+ * @param offset - Constant altitude in meters added after scaling.
3118
+ */
3119
+ declare function transformGeojsonElevation(geojson: FeatureCollection, verticalScale: number, offset: number): FeatureCollection;
3120
+ //#endregion
3121
+ //#region src/polyline.d.ts
3122
+ /**
3123
+ * Pure TypeScript Google Encoded Polyline algorithm codec.
3124
+ *
3125
+ * Supports arbitrary precision:
3126
+ * - precision = 5: Google Maps standard, OSRM, HERE (factor 1e5, ~1.1m precision)
3127
+ * - precision = 6: Valhalla, Mapbox, Open Source Routing Machine 6 (factor 1e6, ~0.11m precision)
3128
+ *
3129
+ * Coordinates are represented as `[lon, lat]` pairs in GeoJSON format.
3130
+ */
3131
+ /**
3132
+ * Unescapes backslashes in a polyline string.
3133
+ *
3134
+ * When polyline strings are copied from JSON payloads, log files, or source code,
3135
+ * backslashes are frequently double-escaped as `\\` or formatted with escape sequences.
3136
+ *
3137
+ * @param str - Input string with potentially escaped backslashes.
3138
+ * @returns Cleaned polyline string.
3139
+ */
3140
+ declare function unescapePolyline(str: string): string;
3141
+ interface PolylineDecodeResult {
3142
+ coordinates: [number, number][];
3143
+ complete: boolean;
3144
+ }
3145
+ /**
3146
+ * Decodes an encoded polyline string into an array of `[lon, lat]` coordinates,
3147
+ * along with a `complete` flag indicating whether the entire string was consumed
3148
+ * as complete coordinate pairs without truncation or malformed characters.
3149
+ *
3150
+ * @param encoded - The ASCII-encoded polyline string.
3151
+ * @param precision - Number of decimal digits (default: 5).
3152
+ * @param unescape - Whether to unescape double-escaped backslashes before decoding (default: false).
3153
+ * @returns Object with decoded `coordinates` and `complete` boolean status.
3154
+ */
3155
+ declare function decodePolylineDetailed(encoded: string, precision?: number, unescape?: boolean): PolylineDecodeResult;
3156
+ /**
3157
+ * Decodes an encoded polyline string into an array of `[lon, lat]` coordinates.
3158
+ *
3159
+ * @param encoded - The ASCII-encoded polyline string.
3160
+ * @param precision - Number of decimal digits (default: 5).
3161
+ * @param unescape - Whether to unescape double-escaped backslashes before decoding (default: false).
3162
+ * @returns Array of `[lon, lat]` coordinate pairs in GeoJSON order.
3163
+ */
3164
+ declare function decodePolyline(encoded: string, precision?: number, unescape?: boolean): [number, number][];
3165
+ /**
3166
+ * Encodes an array of `[lon, lat]` coordinates into a Google Encoded Polyline string.
3167
+ *
3168
+ * @param coordinates - Array of `[lon, lat]` coordinate pairs.
3169
+ * @param precision - Number of decimal digits (default: 5).
3170
+ * @returns The encoded polyline string.
3171
+ */
3172
+ declare function encodePolyline(coordinates: [number, number][], precision?: number): string;
3173
+ /**
3174
+ * Decodes an encoded polyline string into a GeoJSON `Feature<LineString>`.
3175
+ *
3176
+ * @param encoded - The polyline string.
3177
+ * @param precision - Coordinate precision (default: 5).
3178
+ * @param properties - Optional feature properties.
3179
+ * @returns A GeoJSON LineString Feature.
3180
+ */
3181
+ declare function polylineStrToGeoJSON(encoded: string, precision?: number, properties?: GeoJsonProperties): Feature<LineString>;
3182
+ /**
3183
+ * Encodes a GeoJSON LineString, MultiLineString, Feature, or FeatureCollection into encoded polyline string(s).
3184
+ *
3185
+ * @param geojson - The GeoJSON input geometry or feature.
3186
+ * @param precision - Coordinate precision (default: 5).
3187
+ * @returns An encoded polyline string for LineString, or an array of strings for MultiLineString / FeatureCollection.
3188
+ */
3189
+ declare function geoJSONToPolylineStr(geojson: LineString | MultiLineString | Feature<LineString | MultiLineString> | FeatureCollection<LineString | MultiLineString>, precision?: number): string | string[];
3190
+ interface PolylineBatchOptions {
3191
+ /** Coordinate precision (default: 5) */
3192
+ precision?: number;
3193
+ /** Whether to unescape `\\` backslashes (default: true) */
3194
+ unescape?: boolean;
3195
+ /** Custom delimiter string or RegExp separating individual polylines (default: newline) */
3196
+ delimiter?: string | RegExp;
3197
+ /** Whether to merge lines into a single MultiLineString feature (default: false) */
3198
+ asMultiLine?: boolean;
3199
+ /** Optional base properties to assign to output features */
3200
+ baseProperties?: GeoJsonProperties;
3201
+ }
3202
+ /**
3203
+ * Parses and batch-decodes multiple polylines from a text block or file content.
3204
+ *
3205
+ * @param input - Multiline string or delimited text containing encoded polylines.
3206
+ * @param options - Decoding and parsing configuration.
3207
+ * @returns A FeatureCollection containing LineString (or MultiLineString) features.
3208
+ */
3209
+ declare function batchDecodePolylines(input: string, options?: PolylineBatchOptions): FeatureCollection<LineString | MultiLineString>;
3210
+ //#endregion
3211
+ //#region src/joins.d.ts
3212
+ /**
3213
+ * Match key for a persistent join: empty values (null/undefined/NaN/empty
3214
+ * string) never match a row, mirroring a SQL/pandas NaN join key. Non-empty
3215
+ * values are keyed by their string form, so a numeric `5` and the string `"5"`
3216
+ * join while a zero-padded code like `"01001"` only matches another `"01001"`.
3217
+ * Kept in sync with the Processing attribute join's `attributeJoinKey`.
3218
+ */
3219
+ declare function layerJoinKey(value: unknown): string | null;
3220
+ /**
3221
+ * Remove every column previously added by `joins` (per their `addedFields`
3222
+ * bookkeeping) from a copy of `features`, restoring the pre-join properties.
3223
+ * Features without any tracked column are returned unchanged (same reference).
3224
+ */
3225
+ declare function stripJoinFields(features: Feature[], joins: LayerJoin[] | undefined): Feature[];
3226
+ /** Result of {@link applyLayerJoins}: joined features plus refreshed join records. */
3227
+ interface ApplyLayerJoinsResult {
3228
+ features: Feature[];
3229
+ /** Input joins with `addedFields` and `stats` recomputed for this run. */
3230
+ joins: LayerJoin[];
3231
+ }
3232
+ /**
3233
+ * Apply `joins` in order to base (already-stripped) `features`, returning new
3234
+ * feature objects with the joined columns merged into their properties.
3235
+ *
3236
+ * Semantics per join: left join, first matching join row wins, and joined
3237
+ * columns are null-filled on unmatched features so the schema stays
3238
+ * consistent. An output name (prefix + field) that collides with an existing
3239
+ * column — from the base data or an earlier join — drops that one column;
3240
+ * the join's other columns still apply. A disabled join, or one whose source
3241
+ * cannot be resolved, contributes nothing and gets empty `addedFields` and no
3242
+ * stats.
3243
+ *
3244
+ * @param features - The layer's base features (strip previous joins first).
3245
+ * @param joins - Join definitions in application order.
3246
+ * @param resolveSource - Maps a join's `joinLayerId` to that layer's current
3247
+ * feature collection, or `undefined` when the layer is gone (or is the
3248
+ * target itself; self-joins are refused by the caller).
3249
+ */
3250
+ declare function applyLayerJoins(features: Feature[], joins: LayerJoin[] | undefined, resolveSource: (joinLayerId: string) => FeatureCollection | undefined): ApplyLayerJoinsResult;
3251
+ /**
3252
+ * Strip-and-reapply this layer's derived columns — persistent joins, then
3253
+ * virtual fields (so an expression can read joined columns) — resolving join
3254
+ * sources from `allLayers`. Pass `nextJoins`/`nextVirtualFields` to replace
3255
+ * the definitions (the layer's current bookkeeping still drives the strip, so
3256
+ * removed definitions clean up after themselves); omit them to refresh the
3257
+ * existing definitions against current data. Layers without a feature
3258
+ * collection are returned unchanged.
3259
+ */
3260
+ declare function applyJoinsToLayer(layer: GeoLibreLayer, allLayers: GeoLibreLayer[], nextJoins?: LayerJoin[], nextVirtualFields?: LayerVirtualField[]): GeoLibreLayer;
3261
+ /**
3262
+ * Ids of every layer whose data (directly or transitively) feeds `layerId`'s
3263
+ * joins, following `joinLayerId` edges through each source's own joins.
3264
+ * Disabled joins count too: re-enabling one must not spring a cycle. The Joins
3265
+ * UI uses this to keep a layer that already consumes the target — however
3266
+ * indirectly — out of the join-source picker, so circular joins cannot be
3267
+ * authored (the refresh cascade still breaks them defensively for hand-edited
3268
+ * projects).
3269
+ */
3270
+ declare function collectTransitiveJoinSourceIds(layers: GeoLibreLayer[], layerId: string): Set<string>;
3271
+ /**
3272
+ * Refresh every layer whose joins (directly or transitively) consume the
3273
+ * layer identified by `seedId`, after that layer's data changed — or after it
3274
+ * was removed (a gone source resolves to nothing, so its frozen columns strip
3275
+ * away). The affected layers re-derive in topological dependency order, so a
3276
+ * layer joining two other affected layers sees both of them fresh regardless
3277
+ * of their positions in the layer panel. The seed itself is not touched.
3278
+ */
3279
+ declare function cascadeLayerJoinRefresh(layers: GeoLibreLayer[], seedId: string): GeoLibreLayer[];
3280
+ /**
3281
+ * Re-resolve every layer's persistent joins against the freshly loaded layer
3282
+ * set (project open). Layers re-derive in topological order of their join
3283
+ * dependencies, so a chain (`C joins A, A joins B`) resolves A before C; a
3284
+ * hand-edited cycle is broken by forcing one of its members through first
3285
+ * (see {@link topologicalJoinOrder}), so consumers downstream of the cycle
3286
+ * still re-derive after it. Layers with virtual fields but no joins re-derive
3287
+ * too (their expressions run against the freshly loaded features). Layer sets
3288
+ * without joins or virtual fields pass through by reference.
3289
+ */
3290
+ declare function reapplyLayerJoins(layers: GeoLibreLayer[]): GeoLibreLayer[];
3291
+ //#endregion
3292
+ //#region src/project.d.ts
3293
+ /** Placeholder name a project carries before the user names it. */
3294
+ declare const DEFAULT_PROJECT_NAME = "Untitled Project";
3295
+ interface CreateProjectOptions {
3296
+ basemapStyleUrl?: string;
3297
+ mapView?: MapViewState;
3298
+ /** Celestial body the project describes; defaults to Earth when omitted. */
3299
+ ellipsoidId?: string;
3300
+ }
3301
+ declare function normalizeBlankBackgroundColor(value: unknown): string | null;
3302
+ declare function createDefaultMapView(): MapViewState;
3303
+ declare function createEmptyProject(name?: string, options?: CreateProjectOptions): GeoLibreProject;
3304
+ /**
3305
+ * Serialize a project to `.geolibre.json` text: indented project structure with
3306
+ * compact (unindented) embedded GeoJSON. See {@link serializeProjectValue} for
3307
+ * why the two halves are formatted differently.
3308
+ *
3309
+ * @param project Project to serialize.
3310
+ * @returns The file contents to write.
3311
+ */
3312
+ declare function serializeProject(project: GeoLibreProject): string;
3313
+ /**
3314
+ * Per-layer serialized text kept across {@link serializeProjectWithLayerCache}
3315
+ * calls, keyed by the layer record each entry was built from. A `WeakMap`, so a
3316
+ * layer the store has replaced or removed drops its text with it.
3317
+ */
3318
+ type ProjectLayerSerializationCache = WeakMap<object, {
3319
+ index: number;
3320
+ text: string;
3321
+ }>;
3322
+ /** Create an empty {@link ProjectLayerSerializationCache}. */
3323
+ declare function createProjectLayerSerializationCache(): ProjectLayerSerializationCache;
3324
+ /**
3325
+ * Serialize a project exactly as {@link serializeProject} does, reusing the
3326
+ * text of every layer whose source record is unchanged since an earlier call.
3327
+ *
3328
+ * Feature data lives inside layer records, so re-stringifying every layer on
3329
+ * each autosave costs megabytes of main-thread work for a project with large
3330
+ * GeoJSON layers — even when only the camera moved (GeoLibre#2633). The store
3331
+ * replaces a layer object whenever it changes, so object identity is a sound
3332
+ * cache key: a camera move reuses every layer, and an edit to one layer
3333
+ * re-serializes only that one.
3334
+ *
3335
+ * `layerSources[i]` must be the record `project.layers[i]` was derived from
3336
+ * (e.g. the store's `layers`, before `projectFromStore` prepared them), and that
3337
+ * derivation must depend on the record alone — as `buildProjectSnapshot`'s does
3338
+ * — or a cached entry could outlive a change it should have reflected. When the
3339
+ * two arrays differ in length the cache is bypassed rather than trusted.
3340
+ *
3341
+ * @param project Project to serialize, as built from `layerSources`.
3342
+ * @param layerSources Immutable source record for each entry of
3343
+ * `project.layers`, in the same order, used as the cache key.
3344
+ * @param cache Cache shared between calls; see
3345
+ * {@link createProjectLayerSerializationCache}.
3346
+ * @returns The same text {@link serializeProject} returns for `project`.
3347
+ */
3348
+ declare function serializeProjectWithLayerCache(project: GeoLibreProject, layerSources: readonly object[], cache: ProjectLayerSerializationCache): string;
3349
+ /**
3350
+ * {@link serializeProjectWithLayerCache} that hands the main thread back
3351
+ * between layers, so autosave on a project with several large layers is many
3352
+ * short tasks instead of one 300-400 ms freeze (GeoLibre#2869).
3353
+ *
3354
+ * The cache makes a camera move free, so the cost lands on the first autosave
3355
+ * and on edits to a big layer: each layer that misses the cache is serialized
3356
+ * in its own slice, and the thread is released once a slice has run longer than
3357
+ * a few milliseconds. A single huge layer is still one slice. The result is
3358
+ * identical to the synchronous call. `project` and `layerSources` must not be
3359
+ * mutated while this runs; the store replaces records rather than editing them,
3360
+ * so a snapshot taken from it stays valid.
3361
+ *
3362
+ * @param project Project to serialize, as built from `layerSources`.
3363
+ * @param layerSources Immutable source record for each entry of
3364
+ * `project.layers`, in the same order, used as the cache key.
3365
+ * @param cache Cache shared between calls.
3366
+ * @param yieldFn Hands the thread back; replaced in tests.
3367
+ * @returns The same text {@link serializeProject} returns for `project`.
3368
+ */
3369
+ declare function serializeProjectWithLayerCacheAsync(project: GeoLibreProject, layerSources: readonly object[], cache: ProjectLayerSerializationCache, yieldFn?: () => Promise<void>): Promise<string>;
3370
+ declare function parseProject(json: string): GeoLibreProject;
3371
+ /**
3372
+ * Coerce an untrusted (possibly hand-edited) legend config into a valid
3373
+ * {@link LegendConfig}, dropping malformed entries. Returns undefined when no
3374
+ * usable config is present so the default is applied downstream.
3375
+ */
3376
+ declare function normalizeLegendConfig(legend: unknown): LegendConfig | undefined;
3377
+ /**
3378
+ * Validate and coerce a story map loaded from an untrusted project file.
3379
+ *
3380
+ * Returns null when the value carries no chapters so empty story maps stay out
3381
+ * of the saved project, mirroring how plugins are only persisted when present.
3382
+ *
3383
+ * @param storymap Raw value read from the project JSON.
3384
+ * @returns A normalized story map, or null when there is nothing to keep.
3385
+ */
3386
+ declare function normalizeStoryMap(storymap: unknown): StoryMap | null;
3387
+ /** Whether a story map carries chapters or any non-default setting. */
3388
+ declare function storyMapHasContent(story: StoryMap): boolean;
3389
+ /**
3390
+ * Coerce an untrusted (possibly hand-edited) `models` array into valid
3391
+ * {@link ProcessingModel} records. Drops models and steps without a usable id or
3392
+ * tool id, de-duplicates models by id, and keeps step `parameters` as a plain
3393
+ * object (the runner validates parameter values per tool at run time). Returns
3394
+ * `null` when there is nothing worth persisting, so a model-less project stays
3395
+ * free of the key.
3396
+ *
3397
+ * @param value Raw `models` value from the project JSON.
3398
+ * @returns Normalized models, or `null` when none survive.
3399
+ */
3400
+ declare function normalizeModels(value: unknown): ProcessingModel[] | null;
3401
+ /**
3402
+ * Coerce an untrusted `graph` value into a {@link ProcessingModelGraph}. Drops
3403
+ * nodes without a usable id or an unknown kind, de-duplicates node ids, and
3404
+ * drops edges that do not connect two surviving nodes or that name an empty
3405
+ * port. Self-edges are dropped too, since a node cannot feed itself.
3406
+ *
3407
+ * Structural validity beyond this (cycles, type mismatches, missing required
3408
+ * inputs) is the runner's job — those depend on the tool registries, which the
3409
+ * project layer deliberately does not import.
3410
+ *
3411
+ * @param value Raw `graph` value from the project JSON.
3412
+ * @returns The normalized graph, or `null` when it has no usable nodes.
3413
+ */
3414
+ declare function normalizeModelGraph(value: unknown): ProcessingModelGraph | null;
3415
+ /**
3416
+ * Coerce an untrusted (possibly hand-edited) `processingHistory` array into
3417
+ * valid {@link ProcessingRun} records. Drops entries without a usable id, tool
3418
+ * id, or known kind, de-duplicates by id, keeps `parameters` as a plain object,
3419
+ * and caps the list at {@link MAX_PROCESSING_HISTORY} (keeping the newest,
3420
+ * i.e. last, entries). Returns `null` when nothing survives, so a history-less
3421
+ * project stays free of the key.
3422
+ *
3423
+ * @param value Raw `processingHistory` value from the project JSON.
3424
+ * @returns Normalized runs, or `null` when none survive.
3425
+ */
3426
+ /**
3427
+ * Accept the Object-Based Analysis workbench blob (`obia`) only as a plain,
3428
+ * JSON-serializable object carrying a numeric `version`. Its fields are owned
3429
+ * and validated by the desktop app, which treats anything it does not
3430
+ * recognise as absent; core only guarantees the shape survives a round trip.
3431
+ *
3432
+ * @param value Raw `obia` value from the project JSON.
3433
+ * @returns A detached copy, or `null` when absent or malformed.
3434
+ */
3435
+ declare function normalizeObiaWorkbench(value: unknown): Record<string, unknown> | null;
3436
+ declare function normalizeProcessingHistory(value: unknown): ProcessingRun[] | null;
3437
+ /**
3438
+ * Coerce an untrusted (possibly hand-edited) camera object into a valid
3439
+ * {@link MapViewState}, falling back to the default view for missing parts.
3440
+ */
3441
+ declare function normalizeMapViewState(value: unknown): MapViewState;
3442
+ /**
3443
+ * Coerce an untrusted `mapLayout` into a valid {@link MapGridLayout}. Returns
3444
+ * null when absent or effectively single-pane so default projects stay
3445
+ * byte-identical (the field is only written when the grid is larger than 1x1).
3446
+ */
3447
+ declare function normalizeMapLayout(value: unknown): MapGridLayout | null;
3448
+ /**
3449
+ * Coerce an untrusted `primaryRenderer` into a known engine id (issue #2217).
3450
+ *
3451
+ * Returns null for the default 2D map — absent, unknown, or an explicit
3452
+ * `"maplibre"` — because the field is only written when it is not the default,
3453
+ * so a MapLibre project serializes byte-identically to before this existed.
3454
+ * The return type is narrowed to `"cesium" | "mapbox" | "arcgis" | null` rather
3455
+ * than the full {@link MapRendererKind} for that reason: `"maplibre"` is never a
3456
+ * result.
3457
+ */
3458
+ declare function normalizePrimaryRenderer(value: unknown): "cesium" | "mapbox" | "arcgis" | null;
3459
+ /**
3460
+ * Coerce an untrusted `secondaryMapViews` array into valid
3461
+ * {@link SecondaryMapView} records, dropping entries without a usable id and
3462
+ * de-duplicating by id. Returns null when none are valid.
3463
+ */
3464
+ declare function normalizeSecondaryMapViews(value: unknown): SecondaryMapView[] | null;
3465
+ /**
3466
+ * Reconcile a parsed grid layout with its secondary panes so the store invariant
3467
+ * holds: `secondaryMapViews.length === rows * cols - 1`. Surplus panes are
3468
+ * dropped; missing panes are filled by cloning the primary map. A null/absent
3469
+ * layout (or a 1x1 grid) collapses to the single-map default.
3470
+ */
3471
+ declare function resolveMapGrid(layout: MapGridLayout | null, secondaryViews: SecondaryMapView[] | null, primary: {
3472
+ mapView: MapViewState;
3473
+ }): {
3474
+ mapLayout: MapGridLayout;
3475
+ secondaryMapViews: SecondaryMapView[];
3476
+ };
3477
+ /** Bookmarks kept per project, as the panel's own default limit allows. */
3478
+ declare const MAX_PROJECT_BOOKMARKS = 500;
3479
+ /**
3480
+ * Coerce an untrusted `bookmarkGroups` array into valid folders: drops entries
3481
+ * without a string id and de-duplicates by id. A missing name becomes
3482
+ * "Folder"; `collapsed` defaults to false.
3483
+ *
3484
+ * @param value Raw `bookmarkGroups` value from the project JSON.
3485
+ * @returns The folders (possibly empty).
3486
+ */
3487
+ declare function normalizeBookmarkGroups(value: unknown): ProjectBookmarkGroup[];
3488
+ /**
3489
+ * Coerce an untrusted `bookmarks` array into valid saved views: drops entries
3490
+ * without a string id or a finite, in-range camera, de-duplicates by id, wraps
3491
+ * the bearing into 0-360, and drops a `groupId` that names no folder in
3492
+ * `groups`. Keeps at most {@link MAX_PROJECT_BOOKMARKS}.
3493
+ *
3494
+ * @param value Raw `bookmarks` value from the project JSON.
3495
+ * @param groups The project's (normalized) folders.
3496
+ * @returns The bookmarks (possibly empty).
3497
+ */
3498
+ declare function normalizeBookmarks(value: unknown, groups?: readonly ProjectBookmarkGroup[]): ProjectBookmark[];
3499
+ /**
3500
+ * Coerce an untrusted (possibly hand-edited) `widgets` array into valid
3501
+ * {@link DashboardWidget} records. Drops widgets without a usable id, layer id,
3502
+ * or recognized chart type, de-duplicates by id, and keeps only the optional
3503
+ * keys that are present and well-typed (the Dashboard panel falls back to
3504
+ * sensible defaults for anything missing). Returns `null` when there is nothing
3505
+ * worth persisting, so a widget-less project stays free of the key.
3506
+ *
3507
+ * @param value Raw `widgets` value from the project JSON.
3508
+ * @returns Normalized widgets, or `null` when none survive.
3509
+ */
3510
+ declare function normalizeWidgets(value: unknown): DashboardWidget[] | null;
3511
+ /**
3512
+ * Clamp an untrusted dashboard column count into the supported range, falling
3513
+ * back to the default for a missing or non-finite value.
3514
+ *
3515
+ * @param value Raw `dashboardColumns` value from the project JSON.
3516
+ * @returns An integer column count within [MIN, MAX].
3517
+ */
3518
+ declare function normalizeDashboardColumns(value: unknown): number;
3519
+ /**
3520
+ * Coerce an untrusted `interaction` block into a {@link ProjectInteraction}.
3521
+ *
3522
+ * Layer ids are kept even when no such layer exists: the block is replayed
3523
+ * against whatever layers the project has when it opens, and the loader drops
3524
+ * the ones it cannot find. Control names are kept as written for the same
3525
+ * reason; the app ignores names it does not know.
3526
+ *
3527
+ * @param value - Raw `interaction` value from the project JSON or the store.
3528
+ * @returns The normalized block, or null when it carries nothing to apply.
3529
+ */
3530
+ declare function normalizeProjectInteraction(value: unknown): ProjectInteraction | null;
3531
+ declare function isAllowedPluginManifestUrl(url: string): boolean;
3532
+ declare function normalizeProjectComments(rawComments: unknown): ProjectComment[];
3533
+ declare function projectFromStore(state: {
3534
+ projectName: string;
3535
+ mapView: MapViewState;
3536
+ basemapStyleUrl: string;
3537
+ basemapVisible: boolean;
3538
+ basemapOpacity: number;
3539
+ blankBackgroundColor?: string | null;
3540
+ layers: GeoLibreLayer[];
3541
+ selectedLayerId?: string | null;
3542
+ layerGroups?: LayerGroup[];
3543
+ preferences: ProjectPreferences;
3544
+ plugins?: ProjectPluginState | null;
3545
+ legend?: LegendConfig | null;
3546
+ printLayout?: PrintLayoutConfig | null;
3547
+ storymap?: StoryMap | null;
3548
+ models?: ProcessingModel[] | null;
3549
+ processingHistory?: ProcessingRun[] | null;
3550
+ obia?: Record<string, unknown> | null;
3551
+ widgets?: DashboardWidget[] | null;
3552
+ bookmarks?: ProjectBookmark[] | null;
3553
+ bookmarkGroups?: ProjectBookmarkGroup[] | null;
3554
+ dashboardColumns?: number;
3555
+ mapLayout?: MapGridLayout;
3556
+ secondaryMapViews?: SecondaryMapView[];
3557
+ primaryMapLabel?: string;
3558
+ primaryRenderer?: MapRendererKind;
3559
+ /** Project-scoped Style Manager entries (the store's `projectStyleLibrary`). */
3560
+ styleLibrary?: StyleLibraryEntry[] | null;
3561
+ comments?: ProjectComment[] | null;
3562
+ /** Startup interaction state loaded with the project, written back as-is. */
3563
+ interaction?: ProjectInteraction | null;
3564
+ metadata: Record<string, unknown>;
3565
+ }): GeoLibreProject;
3566
+ /**
3567
+ * Whether a layer lives only for the current session and is never written to a
3568
+ * project file. A plugin sets `metadata.sessionOnly` on a store row that mirrors
3569
+ * something it draws itself and cannot restore, such as a point cloud streamed
3570
+ * from a short-lived signed URL, so the row shows in the Layers panel while the
3571
+ * plugin runs but a reopened project carries no dead entry for it.
3572
+ *
3573
+ * @param layer - The layer to check.
3574
+ * @returns `true` when the layer must be left out of saved projects.
3575
+ */
3576
+ declare function isSessionOnlyLayer(layer: GeoLibreLayer): boolean;
3577
+ /**
3578
+ * The plain HTTP(S) WMS template inside the desktop app's `geolibre-wms://`
3579
+ * wrapper (its CORS-exempt native fetcher), or the tile unchanged when it is
3580
+ * not wrapped or the wrapped URL is not HTTP(S). Shared by project export and
3581
+ * the ArcGIS engine's story-export templates.
3582
+ *
3583
+ * @param tile - A tile template, wrapped or not.
3584
+ * @returns The unwrapped template, or `tile` itself.
3585
+ */
3586
+ declare function portableWmsTileUrl(tile: unknown): unknown;
3587
+ /**
3588
+ * The store form of one project layer: its style completed from
3589
+ * {@link DEFAULT_LAYER_STYLE} and the project's top-level `styles` entry.
3590
+ *
3591
+ * Legacy and externally-authored projects can carry a partial top-level style
3592
+ * alongside newer fields on the layer itself. Those layer fields are kept,
3593
+ * while the top-level copy stays authoritative where it explicitly supplies a
3594
+ * value.
3595
+ *
3596
+ * @param project - The project the layer belongs to (for its `styles` map).
3597
+ * @param layer - One of `project.layers`.
3598
+ * @returns A new layer record ready for the store.
3599
+ */
3600
+ declare function hydrateProjectLayer(project: Pick<GeoLibreProject, "styles">, layer: GeoLibreLayer): GeoLibreLayer;
3601
+ declare function applyProjectToStore(project: GeoLibreProject): {
3602
+ projectName: string;
3603
+ mapView: MapViewState;
3604
+ basemapStyleUrl: string;
3605
+ basemapVisible: boolean;
3606
+ basemapOpacity: number;
3607
+ blankBackgroundColor: string | null;
3608
+ layers: GeoLibreLayer[];
3609
+ layerGroups: LayerGroup[];
3610
+ preferences: ProjectPreferences;
3611
+ projectPlugins: ProjectPluginState | null;
3612
+ legend: LegendConfig;
3613
+ printLayout: PrintLayoutConfig;
3614
+ storymap: StoryMap | null;
3615
+ models: ProcessingModel[];
3616
+ processingHistory: ProcessingRun[];
3617
+ obiaWorkbench: Record<string, unknown> | null;
3618
+ widgets: DashboardWidget[];
3619
+ bookmarks: ProjectBookmark[];
3620
+ bookmarkGroups: ProjectBookmarkGroup[];
3621
+ dashboardColumns: number;
3622
+ mapLayout: MapGridLayout;
3623
+ secondaryMapViews: SecondaryMapView[];
3624
+ primaryMapLabel: string;
3625
+ primaryRenderer: MapRendererKind;
3626
+ projectStyleLibrary: StyleLibraryEntry[];
3627
+ comments: ProjectComment[];
3628
+ projectInteraction: ProjectInteraction | null;
3629
+ metadata: Record<string, unknown>;
3630
+ };
3631
+ /**
3632
+ * Create an unlinked copy of a project, suffixed with "(copy)" by default and
3633
+ * stripped of share-specific metadata (shareId, shareUrl, etc.).
3634
+ */
3635
+ declare function detachProjectCopy(project: GeoLibreProject, options?: {
3636
+ nameSuffix?: string;
3637
+ }): GeoLibreProject;
3638
+ /**
3639
+ * Create a template snapshot of a project. Optionally strips data layers while
3640
+ * keeping basemap, layer groups, styles, legend config, preferences, widgets, and
3641
+ * print layout.
3642
+ */
3643
+ declare function createProjectTemplate(project: GeoLibreProject, options?: {
3644
+ name?: string;
3645
+ stripDataLayers?: boolean;
3646
+ }): GeoLibreProject;
3647
+ //#endregion
3648
+ //#region src/project-diff.d.ts
3649
+ /** One changed value, with bounded human-readable previews of each side. */
3650
+ interface ProjectValueChange {
3651
+ /** Dotted key path relative to the section, e.g. `fillColor` or `labels.field`. */
3652
+ path: string;
3653
+ /** Preview of the earlier value; `undefined` when the key was absent. */
3654
+ before?: string;
3655
+ /** Preview of the later value; `undefined` when the key is now absent. */
3656
+ after?: string;
3657
+ }
3658
+ /** Counts of embedded GeoJSON feature changes in one layer. */
3659
+ interface FeatureChangeSummary {
3660
+ added: number;
3661
+ removed: number;
3662
+ modified: number;
3663
+ unchanged: number;
3664
+ /**
3665
+ * How features were matched: `"id"` when every feature carries a unique
3666
+ * `id`, `"geometry"` when none does (matched by geometry hash), `"mixed"`
3667
+ * otherwise.
3668
+ */
3669
+ matchedBy: "id" | "geometry" | "mixed";
3670
+ }
3671
+ /** Changes to one layer between the two projects. */
3672
+ interface LayerDiff {
3673
+ id: string;
3674
+ /** Layer name in the later project (or the earlier one when removed). */
3675
+ name: string;
3676
+ /** Layer type in the later project (or the earlier one when removed). */
3677
+ type: string;
3678
+ status: "added" | "removed" | "changed";
3679
+ /** Set when the layer was renamed. */
3680
+ renamed?: {
3681
+ before: string;
3682
+ after: string;
3683
+ };
3684
+ /**
3685
+ * Set when the layer changed position relative to the layers present in
3686
+ * both projects (bottom-to-top indexes in each project's `layers`).
3687
+ */
3688
+ moved?: {
3689
+ before: number;
3690
+ after: number;
3691
+ };
3692
+ visibility?: {
3693
+ before: boolean;
3694
+ after: boolean;
3695
+ };
3696
+ opacity?: {
3697
+ before: number;
3698
+ after: number;
3699
+ };
3700
+ /** Style keys other than labels. */
3701
+ style: ProjectValueChange[];
3702
+ /** Label style keys (`style.labels.*`), without the `labels.` prefix. */
3703
+ labels: ProjectValueChange[];
3704
+ /** `filterExpression` and `quickFilters`. */
3705
+ filter: ProjectValueChange[];
3706
+ /** `type`, `source.*` and `sourcePath`. */
3707
+ source: ProjectValueChange[];
3708
+ /** Any other persisted layer field (popup, joins, metadata.*, ...). */
3709
+ other: ProjectValueChange[];
3710
+ /** Embedded GeoJSON feature changes; absent when the data is unchanged. */
3711
+ features?: FeatureChangeSummary;
3712
+ /** Feature count for an added or removed layer with embedded GeoJSON. */
3713
+ featureCount?: number;
3714
+ }
3715
+ /** Changes to one plugin's project state. */
3716
+ interface PluginDiff {
3717
+ id: string;
3718
+ /** `added`/`removed` mean the plugin was activated/deactivated. */
3719
+ status: "added" | "removed" | "changed";
3720
+ changes: ProjectValueChange[];
3721
+ }
3722
+ /** The full structured difference between two projects. */
3723
+ interface ProjectDiff {
3724
+ layers: {
3725
+ added: LayerDiff[];
3726
+ removed: LayerDiff[];
3727
+ changed: LayerDiff[];
3728
+ /** True when any layer present in both projects changed position. */
3729
+ reordered: boolean;
3730
+ };
3731
+ camera: ProjectValueChange[];
3732
+ basemap: ProjectValueChange[];
3733
+ /** Map projection (`preferences.map.projection`). */
3734
+ projection: ProjectValueChange[];
3735
+ /** Other project preferences (`preferences.*` minus the projection). */
3736
+ preferences: ProjectValueChange[];
3737
+ plugins: PluginDiff[];
3738
+ /** Plugin manifest URLs added to or removed from the project. */
3739
+ pluginManifests: {
3740
+ added: string[];
3741
+ removed: string[];
3742
+ };
3743
+ /** Project name (`name`), `metadata.description` and other metadata keys. */
3744
+ metadata: ProjectValueChange[];
3745
+ /** Other top-level sections that changed (`legend`, `storymap`, ...). */
3746
+ sections: string[];
3747
+ /** Total number of reported changes; 0 means the projects are equivalent. */
3748
+ changeCount: number;
3749
+ }
3750
+ /** Options for {@link diffProjects}. */
3751
+ interface ProjectDiffOptions {
3752
+ /** Maximum characters of a before/after preview (default 80). */
3753
+ maxPreviewLength?: number;
3754
+ }
3755
+ /**
3756
+ * Deep structural equality for JSON-like values. A key whose value is
3757
+ * `undefined` is treated as absent, so an in-memory record compares equal to
3758
+ * its JSON round trip.
3759
+ *
3760
+ * @param a - First value.
3761
+ * @param b - Second value.
3762
+ * @returns True when the two values are structurally equal.
3763
+ */
3764
+ declare function jsonValuesEqual(a: unknown, b: unknown): boolean;
3765
+ /**
3766
+ * Render a bounded JSON-ish preview of a value without serializing more of it
3767
+ * than the preview can show, so a large array or object costs no more than
3768
+ * `maxLength` characters of work.
3769
+ *
3770
+ * @param value - The value to preview.
3771
+ * @param maxLength - Maximum preview length; longer output ends with `…`.
3772
+ * @returns The preview, or `undefined` for `undefined`.
3773
+ */
3774
+ declare function previewValue(value: unknown, maxLength?: number): string | undefined;
3775
+ /**
3776
+ * Count the features added, removed and modified between two feature
3777
+ * collections. Features are matched by `id` when they carry one; id-less
3778
+ * features are matched by geometry hash, so a feature whose properties
3779
+ * changed but whose geometry did not counts as modified, while a moved
3780
+ * id-less feature counts as one removal plus one addition.
3781
+ *
3782
+ * @param before - Earlier collection (absent counts as empty).
3783
+ * @param after - Later collection (absent counts as empty).
3784
+ * @returns The change counts.
3785
+ */
3786
+ declare function diffFeatureCollections(before: FeatureCollection | null | undefined, after: FeatureCollection | null | undefined): FeatureChangeSummary;
3787
+ /**
3788
+ * Compute a structured summary of what changed from `before` to `after`.
3789
+ *
3790
+ * Layers are matched by id. Embedded GeoJSON is compared per feature (by id,
3791
+ * falling back to geometry hash) and each feature is hashed once per object,
3792
+ * so diffing the same parsed project against several others reuses its
3793
+ * hashes; callers keep and pass the same parsed project objects to benefit.
3794
+ *
3795
+ * @param before - The earlier project (e.g. an autosave snapshot).
3796
+ * @param after - The later project (e.g. the current project).
3797
+ * @param options - Preview length and other tuning.
3798
+ * @returns The grouped change summary.
3799
+ */
3800
+ declare function diffProjects(before: GeoLibreProject, after: GeoLibreProject, options?: ProjectDiffOptions): ProjectDiff;
3801
+ //#endregion
3802
+ export { collectTransitiveJoinSourceIds as $, LegendCustomEntry as $n, SQL_QUERY_SOURCE_KIND as $r, CollaborationParticipant as $t, normalizeMapViewState as A, scrubPrintLayoutForRemovedLayers as Ai, IndicatorAggregation as An, PopupFieldConfig as Ar, getActiveBodyRadiusRatio as At, normalizeWidgets as B, CesiumBasemapImagery as Bi, LayerGroup as Bn, ProjectBookmark as Br, AppCapabilities as Bt, isSessionOnlyLayer as C, PrintLayoutLegendEntry as Ci, EditorTrackingConfig as Cn, NonGeographicCoordinates as Cr, PLANET_SWITCHER_OPTIONS as Ct, normalizeDashboardColumns as D, normalizePrintLayoutConfig as Di, GeoLibreProject as Dn, ParticipantIdentity as Dr, bodyLengthToEarth as Dt, normalizeBookmarks as E, isDefaultPrintLayout as Ei, GeoLibreLayer as En, PROTOMAPS_BASEMAPS as Er, PlanetaryBasemapSectionId as Et, normalizeProcessingHistory as F, MultiPolygon as Fi, LabelStyle as Fn, ProcessingModelStep as Fr, getPlanetaryBasemapById as Ft, serializeProject as G, LayerMetadataLink as Gn, ProjectPluginState as Gr, AttributeFormValueMapEntry as Gt, portableWmsTileUrl as H, basemapToCesiumImagery as Hi, LayerJoinStats as Hn, ProjectComment as Hr, AppRole as Ht, normalizeProjectComments as I, Point as Ii, LabelTransform as In, ProcessingRerunRequest as Ir, getPlanetaryBasemapByStyleUrl as It, storyMapHasContent as J, LayerQuickFilter as Jn, QuickFilterDateKind as Jr, BLEND_MODES as Jt, serializeProjectWithLayerCache as K, LayerMetadataTemporalExtent as Kn, ProjectPreferences as Kr, AttributeFormWidget as Kt, normalizeProjectInteraction as L, Polygon as Li, LayerCapabilities as Ln, ProcessingRun as Lr, meanRadiusMeters as Lt, normalizeModels as M, FeatureCollection as Mi, LAYER_TYPES as Mn, PopupFieldKind as Mr, getActiveMeanRadiusMeters as Mt, normalizeObiaWorkbench as N, Geometry as Ni, LabelAnchor as Nn, ProcessingModel as Nr, getActiveSemiMajorAxisMeters as Nt, normalizeLegendConfig as O, printLayoutConfigsEqual as Oi, GeocodingPreferences as On, PointRenderer as Or, earthAreaToBody as Ot, normalizePrimaryRenderer as P, LineString as Pi, LabelDedupe as Pn, ProcessingModelGraph as Pr, getEllipsoid as Pt, cascadeLayerJoinRefresh as Q, LegendConfig as Qn, RuntimeEnvironmentVariable as Qr, CollaborationMode as Qt, normalizeSecondaryMapViews as R, CESIUM_BASEMAPS as Ri, LayerConnection as Rn, ProcessingRunKind as Rr, setActiveEllipsoidId as Rt, isAllowedPluginManifestUrl as S, PrintLayoutCorner as Si, DiagramType as Sn, NETCDF_IMAGE_SOURCE_KIND as Sr, PLANETARY_BASEMAP_SENTINEL_PREFIX as St, normalizeBookmarkGroups as T, createDefaultPrintLayout as Ti, FillPattern as Tn, PROJECT_VERSION as Tr, PlanetaryBasemapGroup as Tt, projectFromStore as U, normalizeCesiumBasemap as Ui, LayerLibraryEntry as Un, ProjectInteraction as Ur, AttributeFormConfig as Ut, parseProject as V, availableCesiumBasemap as Vi, LayerJoin as Vn, ProjectBookmarkGroup as Vr, AppPrivilege as Vt, resolveMapGrid as W, sameCesiumImagery as Wi, LayerMetadataContact as Wn, ProjectPluginControlPosition as Wr, AttributeFormFieldConfig as Wt, applyJoinsToLayer as X, LayerType as Xn, QuickFilterTextOperator as Xr, CollabInvite as Xt, ApplyLayerJoinsResult as Y, LayerStyle as Yn, QuickFilterKind as Yr, BlendMode as Yt, applyLayerJoins as Z, LayerVirtualField as Zn, RecentProjectEntry as Zr, CollaborationChatMessage as Zt, createEmptyProject as _, isDuckDBQueryLayer as _i, DashboardWidget as _n, ModelGraphNode as _r, ELLIPSOIDS as _t, ProjectDiffOptions as a, StoryChapterLocation as ai, CommentReply as an, MAX_DASHBOARD_COLUMNS as ar, batchDecodePolylines as at, detachProjectCopy as b, DEFAULT_PRINT_LAYOUT as bi, DiagramField as bn, ModelPortKind as br, PLANETARY_BASEMAPS as bt, diffProjects as c, StoryMap as ci, DEFAULT_DASHBOARD_COLUMNS as cn, MIN_DASHBOARD_COLUMNS as cr, encodePolyline as ct, CreateProjectOptions as d, StyleLibraryEntry as di, DEFAULT_MAP_GRID_LAYOUT as dn, MapProjection as dr, unescapePolyline as dt, SecondaryMapView as ei, CollaborationPresence as en, LegendCustomItem as er, layerJoinKey as et, DEFAULT_PROJECT_NAME as f, StyleLibraryEntryKind as fi, DEFAULT_PRIMARY_RENDERER as fn, MapRendererKind as fr, geojsonHasZCoordinates as ft, createDefaultMapView as g, detectNonGeographicCoordinates as gi, DUCKDB_VECTOR_ROUTE_BYTES as gn, ModelGraphEdge as gr, EARTH_MEAN_RADIUS_METERS as gt, applyProjectToStore as h, VectorStyleStop as hi, DUCKDB_VECTOR_FEATURE_WARN_COUNT as hn, MarkerShape as hr, DEFAULT_ELLIPSOID_ID as ht, ProjectDiff as i, StoryChapterAnimation as ii, CommentAuthor as in, LineDecoration as ir, PolylineDecodeResult as it, normalizeModelGraph as j, Feature as ji, LARGE_VECTOR_FEATURE_THRESHOLD as jn, PopupFieldFormat as jr, getActiveEllipsoid as jt, normalizeMapLayout as k, scrubPrintLayoutForLayers as ki, GeometryGeneratorType as kn, PopupDateFormat as kr, earthLengthToBody as kt, jsonValuesEqual as l, StorySlideMode as li, DEFAULT_LAYER_STYLE as ln, MapGridLayout as lr, geoJSONToPolylineStr as lt, ProjectLayerSerializationCache as m, VectorStyleMode as mi, DEFAULT_STORY_MAP as mn, MapViewState as mr, transformGeojsonElevation as mt, LayerDiff as n, StoryChapter as ni, CollaborationState as nn, LegendItemOverride as nr, stripJoinFields as nt, ProjectValueChange as o, StoryInsetPosition as oi, DEFAULT_BASEMAP as on, MAX_MAP_GRID_DIM as or, decodePolyline as ot, MAX_PROJECT_BOOKMARKS as p, VectorRule as pi, DEFAULT_PROJECT_PREFERENCES as pn, MapScaleUnit as pr, horizontalBbox as pt, serializeProjectWithLayerCacheAsync as q, LayerPopupConfig as qn, ProjectTemplateEntry as qr, BLANK_BASEMAP as qt, PluginDiff as r, StoryChapterAlignment as ri, CommentAnchor as rn, LegendPanelPosition as rr, PolylineBatchOptions as rt, diffFeatureCollections as s, StoryLayerOpacityChange as si, DEFAULT_BLEND_MODE as sn, MAX_PROCESSING_HISTORY as sr, decodePolylineDetailed as st, FeatureChangeSummary as t, StoryActiveSlideMode as ti, CollaborationRole as tn, LegendCustomShape as tr, reapplyLayerJoins as tt, previewValue as u, StrokeWidthUnit as ui, DEFAULT_LEGEND_CONFIG as un, MapPreferences as ur, polylineStrToGeoJSON as ut, createProjectLayerSerializationCache as v, shouldUseTiledRendering as vi, DashboardWidgetAggregation as vn, ModelGraphNodeKind as vr, Ellipsoid as vt, normalizeBlankBackgroundColor as w, PrintLayoutPageFilter as wi, FieldVisibility as wn, OPENFREEMAP_BASEMAPS as wr, PlanetaryBasemap as wt, hydrateProjectLayer as x, PrintLayoutConfig as xi, DiagramSizeMode as xn, ModelToolProvider as xr, PLANETARY_BASEMAP_GROUPS as xt, createProjectTemplate as y, styleValue as yi, DashboardWidgetType as yn, ModelGraphPort as yr, EllipsoidId as yt, normalizeStoryMap as z, CesiumBasemapId as zi, LayerDescriptiveMetadata as zn, ProcessingRunStatus as zr, AddTileLayerOptions as zt };
3803
+ //# sourceMappingURL=project-diff-BD23T4xv.d.mts.map