@geolibre/core 3.7.0 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +304 -6
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1558 -7
- package/dist/index.mjs.map +1 -1
- package/dist/{project-diff-B6AfZk8o.d.mts → project-diff-BksHN-qY.d.mts} +101 -4
- package/dist/project-diff-BksHN-qY.d.mts.map +1 -0
- package/dist/{project-diff-DPGO-zCS.mjs → project-diff-DJkOd8ff.mjs} +211 -11
- package/dist/project-diff-DJkOd8ff.mjs.map +1 -0
- package/dist/worker-safe.d.mts +1 -1
- package/dist/worker-safe.mjs +1 -1
- package/package.json +3 -3
- package/dist/project-diff-B6AfZk8o.d.mts.map +0 -1
- package/dist/project-diff-DPGO-zCS.mjs.map +0 -1
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["uuidv4","isPlainObject","isPlainObject","uuidv4","MAX_REDACT_DEPTH","uuidv4"],"sources":["../src/tile-address.ts","../src/diagram.ts","../src/marker-shape.ts","../src/hyperlink.ts","../src/photo.ts","../src/paths.ts","../src/routing.ts","../src/style-layer-evaluator.ts","../src/document-locale.ts","../src/label-number-format.ts","../src/external-native-paint.ts","../src/attribute-form.ts","../src/map-click-claims.ts","../src/popup.ts","../src/quick-filters.ts","../src/level-filter.ts","../src/layer-filters.ts","../src/zip-directory.ts","../src/imdf-detect.ts","../src/deployment-capabilities.ts","../src/store/capabilities-slice.ts","../src/store/collaboration-slice.ts","../src/store/session-slice.ts","../src/store/layer-removal.ts","../src/store/layer-groups-slice.ts","../src/file-name.ts","../src/layer-defaults.ts","../src/layer-style-clipboard.ts","../src/layer-style-file.ts","../src/store/layers-slice.ts","../src/layer-library.ts","../src/store/libraries-slice.ts","../src/history.ts","../src/store/undo-history.ts","../src/store/map-view-slice.ts","../src/store/processing-slice.ts","../src/store/project-content-slice.ts","../src/store/project-slice.ts","../src/store/ui-slice.ts","../src/store.ts","../src/selection-actions.ts","../src/scale-units.ts","../src/elevation.ts","../src/camera-altitude.ts","../src/layers-file.ts","../src/pixel-format.ts","../src/s3.ts","../src/storymap-sample.ts","../src/storymap-playback.ts","../src/storymap-io.ts","../src/layer-selectors.ts","../src/platform.ts","../src/three-d-tiles.ts","../src/project-credential-refs.ts","../src/visibility.ts","../src/editor-tracking.ts","../src/editor-identity.ts","../src/datum-shift.ts","../src/cog-jpeg-tables.ts"],"sourcesContent":["export type TileScheme = \"xyz\" | \"tms\";\n\n/** A layer record that carries a tile scheme. Only XYZ templates are rewritten. */\nexport interface TileAddressLayer {\n type?: string;\n source?: { scheme?: unknown };\n}\n\n/** `\"tms\"` is the only scheme that flips a row. Anything else is XYZ. */\nexport function asTileScheme(scheme: unknown): TileScheme {\n return scheme === \"tms\" ? \"tms\" : \"xyz\";\n}\n\n/**\n * Express a `{-y}` row flip in the form MapLibre understands.\n *\n * `{-y}` flips the row. It does not select TMS. `scheme: \"tms\"` is its own\n * flip, so a template that has both is requested with an XYZ row. The stored\n * template and scheme stay as the user wrote them; callers apply this when\n * they build a render source. A template may use `{y}` or `{-y}`, not both,\n * and every template in one list must use the same row token. One shared\n * scheme applies to the whole list, so a plain `{y}` template cannot sit\n * beside a `{-y}` template.\n */\nexport function resolveTileAddress(\n tiles: readonly string[],\n scheme: TileScheme = \"xyz\",\n): { tiles: string[]; scheme: TileScheme } {\n let flipped = false;\n let plain = false;\n const next = tiles.map((tile) => {\n const flipY = tile.includes(\"{-y}\");\n const plainY = tile.includes(\"{y}\");\n if (flipY && plainY) {\n throw new Error(\"A tile template can use {y} or {-y}, not both.\");\n }\n if (flipY) flipped = true;\n if (plainY) plain = true;\n return flipY ? tile.replaceAll(\"{-y}\", \"{y}\") : tile;\n });\n if (flipped && plain) {\n throw new Error(\"A tile list can use {y} or {-y}, not both.\");\n }\n const nextScheme: TileScheme = flipped ? (scheme === \"tms\" ? \"xyz\" : \"tms\") : scheme;\n return { tiles: next, scheme: nextScheme };\n}\n\n/**\n * The row a layer's templates should request.\n *\n * XYZ layers run through {@link resolveTileAddress}. Every other layer keeps\n * its templates and only normalizes `scheme`.\n */\nexport function resolveLayerTileAddress(\n layer: TileAddressLayer,\n tiles: readonly string[],\n): { tiles: string[]; scheme: TileScheme } {\n const scheme = asTileScheme(layer.source?.scheme);\n if (layer.type !== \"xyz\") return { tiles: [...tiles], scheme };\n return resolveTileAddress(tiles, scheme);\n}\n\n/** True when {@link resolveTileAddress} rejected a mixed `{y}` / `{-y}` template. */\nexport function isTileAddressError(error: unknown): boolean {\n return error instanceof Error && error.message.includes(\"{y} or {-y}\");\n}\n","/**\n * Pure helpers for the diagram renderer (per-feature pie/donut/bar charts,\n * QGIS-style diagram symbology). Everything here is DOM-free so the desktop\n * app, the deck.gl overlay, and the node test suite share one implementation:\n * anchor placement, value extraction, and size scaling. The canvas drawing and\n * deck.gl layer construction live in `@geolibre/plugins`.\n */\nimport type { Feature, FeatureCollection, Geometry, Position } from \"geojson\";\nimport { styleValue, type LayerStyle } from \"./types\";\n\n/**\n * Hard cap on how many features get a diagram. Each diagram is rasterized into\n * an icon atlas, so an unbounded layer (e.g. a million-point parquet) would\n * hang the UI; typical thematic layers (counties, districts) sit well below\n * this.\n */\nexport const MAX_DIAGRAM_FEATURES = 2000;\n\n/**\n * Hard cap on how many raw features {@link collectDiagramData} visits while\n * looking for drawable diagrams. Without it, a layer whose drawable ratio is\n * low (sparse attributes, degenerate geometry) degenerates to a full scan of\n * an arbitrarily large collection on every rebuild. Features past this bound\n * are treated as truncation, which the Style Panel surfaces.\n */\nexport const MAX_DIAGRAM_SCAN_FEATURES = 100_000;\n\n/** Smallest rendered diagram size in pixels under scaled sizing. */\nexport const MIN_DIAGRAM_SIZE = 8;\n\n/** One feature's chart, ready for drawing. */\nexport interface DiagramDatum {\n /** Anchor position: the point location or polygon/line midpoint centroid. */\n position: [number, number];\n /** One value per configured diagram field (negatives clamped to 0). */\n values: number[];\n /** Sum of {@link values}. */\n total: number;\n /** The value driving scaled sizing (total, attribute value, or 1 for fixed). */\n sizeValue: number;\n}\n\n/** The per-layer diagram dataset plus the maxima needed for scaling. */\nexport interface DiagramData {\n data: DiagramDatum[];\n /** Largest {@link DiagramDatum.sizeValue} (0 when there is no data). */\n maxSizeValue: number;\n /** Largest single field value across all features (for bar axis scaling). */\n maxFieldValue: number;\n /** Largest {@link DiagramDatum.total} across all features. */\n maxTotal: number;\n /** True when the feature cap truncated the dataset. */\n truncated: boolean;\n}\n\n/**\n * Whether a layer style has a drawable diagram configuration: a chart type and\n * at least one mapped attribute.\n *\n * @param style - The layer style to test.\n */\nexport function isDiagramStyleEnabled(style: LayerStyle): boolean {\n return (\n styleValue(style, \"diagramType\") !== \"none\" &&\n styleValue(style, \"diagramFields\").some((field) => field.property !== \"\")\n );\n}\n\n/**\n * Whether a heatmap/cluster point renderer suppresses the layer's diagrams.\n * The point renderer only applies (and is only user-editable) on point-only\n * layers, so a stale `pointRenderer` value on a layer that has since gained\n * non-point geometry must not block diagrams — this mirrors the Style Panel's\n * own `!supportsPointRenderer || pointRenderer === \"single\"` visibility gate,\n * keeping the render and legend gates consistent with what the panel lets the\n * user configure. The full feature list is scanned (matching the panel's\n * isPointOnlyGeoJsonLayer, which uses an uncapped every()) so the two gates\n * can never disagree on layers whose non-point features sit past a sampling\n * window; the scan only runs for the uncommon non-\"single\" renderer values.\n *\n * @param geojson - The layer's feature collection (undefined suppresses nothing).\n * @param style - The layer style carrying `pointRenderer`.\n */\nexport function diagramsSuppressedByPointRenderer(\n geojson: FeatureCollection | undefined,\n style: LayerStyle,\n): boolean {\n if (styleValue(style, \"pointRenderer\") === \"single\") return false;\n const features = geojson?.features ?? [];\n if (features.length === 0) return false;\n return features.every((feature) => {\n const type = feature?.geometry?.type;\n return type === \"Point\" || type === \"MultiPoint\";\n });\n}\n\n/** Shoelace area of a linear ring (planar, in degree space; sign = winding). */\nfunction ringArea(ring: Position[]): number {\n let area = 0;\n for (let i = 0, j = ring.length - 1; i < ring.length; j = i, i += 1) {\n // Same sign convention as ringCentroid's cross terms, so centroid / area\n // divisions stay consistent for either winding.\n area += ring[j][0] * ring[i][1] - ring[i][0] * ring[j][1];\n }\n return area / 2;\n}\n\n/**\n * Area-weighted centroid of a polygon's outer ring, falling back to the ring's\n * vertex average when the area is degenerate. Planar math in degree space is\n * fine at diagram-placement accuracy.\n */\nfunction ringCentroid(ring: Position[]): [number, number] | null {\n if (ring.length === 0) return null;\n const area = ringArea(ring);\n if (Math.abs(area) < 1e-12) {\n let x = 0;\n let y = 0;\n for (const [lng, lat] of ring) {\n x += lng;\n y += lat;\n }\n return [x / ring.length, y / ring.length];\n }\n let cx = 0;\n let cy = 0;\n for (let i = 0, j = ring.length - 1; i < ring.length; j = i, i += 1) {\n const cross = ring[j][0] * ring[i][1] - ring[i][0] * ring[j][1];\n cx += (ring[j][0] + ring[i][0]) * cross;\n cy += (ring[j][1] + ring[i][1]) * cross;\n }\n return [cx / (6 * area), cy / (6 * area)];\n}\n\nfunction finitePosition(position: Position | undefined): [number, number] | null {\n if (!position) return null;\n const [lng, lat] = position;\n return Number.isFinite(lng) && Number.isFinite(lat) ? [lng as number, lat as number] : null;\n}\n\n/**\n * Where a feature's diagram is anchored: the point itself, a line's middle\n * vertex, or the polygon centroid (largest part of a multi-polygon). Returns\n * null for empty/degenerate geometry so the feature is skipped.\n *\n * @param geometry - The feature geometry (may be null for null-geometry rows).\n */\nexport function diagramAnchor(geometry: Geometry | null | undefined): [number, number] | null {\n if (!geometry) return null;\n switch (geometry.type) {\n case \"Point\":\n return finitePosition(geometry.coordinates);\n case \"MultiPoint\":\n return finitePosition(geometry.coordinates[0]);\n case \"LineString\": {\n const coords = geometry.coordinates;\n return finitePosition(coords[Math.floor(coords.length / 2)]);\n }\n case \"MultiLineString\": {\n // Anchor on the longest part (by vertex count) so the diagram sits on\n // the feature's dominant line.\n let longest: Position[] = [];\n for (const part of geometry.coordinates) {\n if (part.length > longest.length) longest = part;\n }\n return finitePosition(longest[Math.floor(longest.length / 2)]);\n }\n case \"Polygon\":\n return ringCentroid(geometry.coordinates[0] ?? []);\n case \"MultiPolygon\": {\n let best: Position[] = [];\n let bestArea = -1;\n for (const polygon of geometry.coordinates) {\n const ring = polygon[0] ?? [];\n const area = Math.abs(ringArea(ring));\n if (area > bestArea) {\n bestArea = area;\n best = ring;\n }\n }\n return ringCentroid(best);\n }\n case \"GeometryCollection\": {\n for (const child of geometry.geometries) {\n const anchor = diagramAnchor(child);\n if (anchor) return anchor;\n }\n return null;\n }\n default:\n return null;\n }\n}\n\n/** Reads a feature property as a non-negative finite number (else 0). */\nfunction readValue(feature: Feature, property: string): number {\n const raw = feature.properties?.[property];\n const num = typeof raw === \"number\" ? raw : Number(raw);\n return Number.isFinite(num) && num > 0 ? num : 0;\n}\n\n/**\n * Extract the per-feature diagram dataset from a layer's GeoJSON and style:\n * one {@link DiagramDatum} per feature that has an anchor and at least one\n * positive value, capped at {@link MAX_DIAGRAM_FEATURES}, plus the global\n * maxima scaled sizing and bar axes need.\n *\n * @param geojson - The layer's feature collection.\n * @param style - The layer style carrying the diagram configuration.\n */\nexport function collectDiagramData(geojson: FeatureCollection, style: LayerStyle): DiagramData {\n const properties = styleValue(style, \"diagramFields\")\n .map((field) => field.property)\n .filter((property) => property !== \"\");\n const sizeMode = styleValue(style, \"diagramSizeMode\");\n const sizeProperty = styleValue(style, \"diagramSizeProperty\");\n\n const data: DiagramDatum[] = [];\n let maxSizeValue = 0;\n let maxFieldValue = 0;\n let maxTotal = 0;\n let truncated = false;\n let visited = 0;\n\n for (const feature of geojson.features) {\n if (data.length >= MAX_DIAGRAM_FEATURES || visited >= MAX_DIAGRAM_SCAN_FEATURES) {\n truncated = true;\n break;\n }\n visited += 1;\n const position = diagramAnchor(feature.geometry);\n if (!position) continue;\n const values = properties.map((property) => readValue(feature, property));\n const total = values.reduce((sum, value) => sum + value, 0);\n if (total <= 0) continue;\n // Attribute sizing with no attribute chosen yet falls back to a constant\n // (full-size diagrams) instead of collapsing everything to the floor.\n const sizeValue =\n sizeMode === \"sum\"\n ? total\n : sizeMode === \"attribute\" && sizeProperty !== \"\"\n ? readValue(feature, sizeProperty)\n : 1;\n if (sizeValue > maxSizeValue) maxSizeValue = sizeValue;\n if (total > maxTotal) maxTotal = total;\n for (const value of values) {\n if (value > maxFieldValue) maxFieldValue = value;\n }\n data.push({ position, values, total, sizeValue });\n }\n\n return { data, maxSizeValue, maxFieldValue, maxTotal, truncated };\n}\n\n/**\n * The rendered size (diameter for pie/donut, box height/width for bars) of one\n * diagram in pixels. Fixed sizing returns the configured size; scaled sizing\n * maps the datum's size value onto the configured size by square root, so\n * diagram *area* tracks the value, with a small floor to keep every drawn\n * diagram legible.\n *\n * @param datum - The feature's diagram datum.\n * @param style - The layer style carrying the diagram configuration.\n * @param maxSizeValue - The dataset's largest size value (from\n * {@link collectDiagramData}).\n */\nexport function diagramPixelSize(\n datum: DiagramDatum,\n style: LayerStyle,\n maxSizeValue: number,\n): number {\n const size = Math.max(4, styleValue(style, \"diagramSize\"));\n if (styleValue(style, \"diagramSizeMode\") === \"fixed\") return size;\n if (maxSizeValue <= 0 || datum.sizeValue <= 0) return MIN_DIAGRAM_SIZE;\n return Math.max(MIN_DIAGRAM_SIZE, size * Math.sqrt(datum.sizeValue / maxSizeValue));\n}\n","import type { MarkerShape } from \"./types\";\n\n/**\n * Trace the outline of a built-in marker {@link MarkerShape} onto a 2D canvas\n * path, centered in a `size`×`size` box. The caller supplies the fill/stroke;\n * this only builds the path (via `beginPath`/`closePath`), so it stays free of\n * any DOM other than the passed context and can be shared by the map's sprite\n * baker (`@geolibre/map`) and the Print Layout legend renderer.\n *\n * `\"custom\"` (SVG) markers have no built-in path and are handled by the caller\n * (rasterized separately); passing one draws the default circle so the marker\n * never vanishes silently.\n *\n * @param ctx - Any 2D path-drawing context (a real `CanvasRenderingContext2D`\n * satisfies this structurally).\n * @param shape - The built-in marker shape to trace.\n * @param size - The box edge length in pixels; the shape is inset slightly so a\n * stroke is not clipped at the edge.\n */\nexport function drawMarkerPath(\n ctx: CanvasRenderingContext2D,\n shape: MarkerShape,\n size: number,\n): void {\n const c = size / 2;\n // Leave a small inset so the stroke is not clipped at the tile edge.\n const r = c * 0.82;\n ctx.beginPath();\n switch (shape) {\n case \"square\":\n ctx.rect(c - r, c - r, r * 2, r * 2);\n break;\n case \"triangle\":\n ctx.moveTo(c, c - r);\n ctx.lineTo(c + r, c + r);\n ctx.lineTo(c - r, c + r);\n ctx.closePath();\n break;\n case \"diamond\":\n ctx.moveTo(c, c - r);\n ctx.lineTo(c + r, c);\n ctx.lineTo(c, c + r);\n ctx.lineTo(c - r, c);\n ctx.closePath();\n break;\n case \"star\": {\n const outer = r;\n const inner = r * 0.42;\n for (let point = 0; point < 10; point += 1) {\n const radius = point % 2 === 0 ? outer : inner;\n const angle = (Math.PI / 5) * point - Math.PI / 2;\n const x = c + radius * Math.cos(angle);\n const y = c + radius * Math.sin(angle);\n if (point === 0) ctx.moveTo(x, y);\n else ctx.lineTo(x, y);\n }\n ctx.closePath();\n break;\n }\n case \"cross\": {\n const arm = r * 0.42;\n ctx.moveTo(c - arm, c - r);\n ctx.lineTo(c + arm, c - r);\n ctx.lineTo(c + arm, c - arm);\n ctx.lineTo(c + r, c - arm);\n ctx.lineTo(c + r, c + arm);\n ctx.lineTo(c + arm, c + arm);\n ctx.lineTo(c + arm, c + r);\n ctx.lineTo(c - arm, c + r);\n ctx.lineTo(c - arm, c + arm);\n ctx.lineTo(c - r, c + arm);\n ctx.lineTo(c - r, c - arm);\n ctx.lineTo(c - arm, c - arm);\n ctx.closePath();\n break;\n }\n case \"pin\": {\n // A teardrop: a circle bowl with a point at the bottom.\n const bowlR = r * 0.7;\n const bowlY = c - r * 0.2;\n ctx.moveTo(c, c + r);\n ctx.quadraticCurveTo(c - bowlR, bowlY + bowlR * 0.4, c - bowlR, bowlY);\n ctx.arc(c, bowlY, bowlR, Math.PI, Math.PI * 2);\n ctx.quadraticCurveTo(c + bowlR, bowlY + bowlR * 0.4, c, c + r);\n ctx.closePath();\n break;\n }\n case \"circle\":\n default:\n ctx.arc(c, c, r, 0, Math.PI * 2);\n }\n}\n\nconst RASTER_IMAGE_EXTENSION = /\\.(?:png|jpe?g|gif|webp|bmp|avif)$/i;\n\n/**\n * Whether a custom marker source is a raster image (PNG, JPEG, GIF, ...) rather\n * than SVG: a non-SVG `data:image/...` URL, or an `http(s)` URL whose path ends\n * in a raster extension. Raster markers cannot be recolored, so the marker\n * color and per-class colors only apply to SVG sources. A remote URL without a\n * telling extension is not classified here; the map sniffs its content type.\n *\n * @param markup - The `markerSvg` value.\n * @returns `true` for a recognizably raster source.\n */\nexport function isRasterMarkerSource(markup: string): boolean {\n const trimmed = markup.trim();\n if (/^data:image\\//i.test(trimmed)) return !/^data:image\\/svg\\+xml/i.test(trimmed);\n if (!/^https?:\\/\\//i.test(trimmed)) return false;\n return RASTER_IMAGE_EXTENSION.test(urlPath(trimmed));\n}\n\n/**\n * Whether a remote marker response holds SVG text, judged by its\n * `content-type` header: `image/svg+xml`, or a text/XML type (a raw GitHub\n * file is served as `text/plain`), reads as SVG and any other `image/*` does\n * not. A missing or generic header falls back to a `.svg` path.\n *\n * @param url - The requested URL.\n * @param contentType - The response's `content-type` header, if any.\n * @returns `true` when the body should be read as SVG markup.\n */\nexport function isSvgMarkerResponse(url: string, contentType: string | null): boolean {\n const type = (contentType ?? \"\").split(\";\")[0].trim().toLowerCase();\n if (type === \"image/svg+xml\") return true;\n if (type.startsWith(\"image/\")) return false;\n if (type.startsWith(\"text/\") || type.endsWith(\"/xml\") || type.endsWith(\"+xml\")) return true;\n return /\\.svg$/i.test(urlPath(url));\n}\n\nfunction urlPath(url: string): string {\n try {\n return new URL(url).pathname;\n } catch {\n return url.split(/[?#]/)[0];\n }\n}\n\n// Remote SVG sources we have already warned about, so the console message below\n// fires once per distinct URL instead of on every image regeneration.\nconst warnedRemoteSvgSources = new Set<string>();\n\n/**\n * Resolve user-supplied SVG input to an `Image.src`: inline markup (starting\n * with `<`) is encoded as a data URL; otherwise only `data:` and `http(s):`\n * URLs are accepted. Returns null for empty input or an unsupported scheme\n * (e.g. `file:`), which the caller treats as \"no image\" rather than letting an\n * arbitrary URL be loaded.\n *\n * Remote `http(s):` URLs are supported intentionally (custom marker/pattern\n * SVGs) but trigger a cross-origin request when rendered. Because a shared\n * `.geolibre.json` can carry such a URL, we log a one-time warning so the\n * outbound request is visible; prefer inline `<svg>` or `data:` in shared\n * projects.\n *\n * Lives here (rather than beside the map's sprite baker) because every surface\n * that previews a custom marker — the map, the Print Layout legend, the on-map\n * Legend panel — must resolve the same input the same way. A second, local\n * `data:image/svg+xml,…` wrapper is exactly what left URL and `data:` markers\n * blank in the on-map legend while the map drew them fine.\n *\n * @param markup - Raw SVG markup, a `data:` URL, or an `http(s)` URL.\n * @returns An `Image.src` value, or `null` when nothing loadable was given.\n */\nexport function resolveSvgSource(markup: string): string | null {\n const trimmed = markup.trim();\n if (!trimmed) return null;\n if (trimmed.startsWith(\"<\")) {\n return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(trimmed)}`;\n }\n if (/^https?:\\/\\//i.test(trimmed)) {\n if (!warnedRemoteSvgSources.has(trimmed)) {\n warnedRemoteSvgSources.add(trimmed);\n console.warn(\n `[geolibre] Loading a custom image from a remote URL triggers a ` +\n `cross-origin request: ${trimmed}. Prefer inline <svg> markup or a ` +\n `data: URL in shared projects.`,\n );\n }\n return trimmed;\n }\n if (trimmed.startsWith(\"data:\")) {\n return trimmed;\n }\n return null;\n}\n","/**\n * Return a whole-value HTTP(S) URL that is safe to expose as an attribute link.\n *\n * Attribute values may contain arbitrary user data, so only explicit web URLs\n * are accepted. Substrings in prose and schemes such as `javascript:` and\n * `file:` remain plain text.\n */\nexport function attributeLinkUrl(value: unknown): string | null {\n if (typeof value !== \"string\") return null;\n const trimmed = value.trim();\n if (!trimmed || /\\s/.test(trimmed)) return null;\n // Bidirectional controls can make an untrusted hostname appear to read as a\n // different domain even though the browser opens the original URL.\n if (/[\\u061c\\u200e\\u200f\\u202a-\\u202e\\u2066-\\u2069]/u.test(trimmed)) return null;\n if (!/^https?:\\/\\/[^/?#]/i.test(trimmed)) return null;\n\n try {\n const { protocol } = new URL(trimmed);\n return protocol === \"http:\" || protocol === \"https:\" ? trimmed : null;\n } catch {\n return null;\n }\n}\n","/**\n * Feature-property keys for inline photo images, shared by the app and the map\n * package so neither hardcodes a second copy that could drift. These keys are\n * part of the project schema: they are serialized into `.geolibre.json` on a\n * geotagged-photo or field-collection layer.\n */\n\n/** Property key under which a photo's downscaled thumbnail (data URL) is stored. */\nexport const PHOTO_PROPERTY = \"photo\";\n\n/**\n * Property key under which a geotagged photo's full-resolution image (a data URL\n * of the original, un-re-encoded bytes) is stored. {@link PHOTO_PROPERTY} holds\n * the small thumbnail shown on the map marker/popup; this holds the\n * native-resolution original used by the enlarged/fullscreen viewer and a \"Save\n * image\". Absent when the source is already at or below the thumbnail cap, or a\n * format a browser cannot display at native size (TIFF/HEIC).\n */\nexport const PHOTO_FULL_PROPERTY = \"photo_full\";\n","// Matches a `..` directory-traversal segment (bounded by separators or the\n// string ends), without rejecting a `..` inside a filename like `v1..2.gpkg`.\nconst PATH_TRAVERSAL = /(?:^|[/\\\\])\\.\\.(?:[/\\\\]|$)/;\n\n/**\n * Whether a path contains a `..` directory-traversal segment. Used to reject a\n * (possibly hand-edited) project's `sourcePath` before re-reading it off disk.\n *\n * @param path - The path to check.\n * @returns True when the path contains a traversal segment.\n */\nexport function hasPathTraversal(path: string): boolean {\n return PATH_TRAVERSAL.test(path);\n}\n\n/**\n * Whether a path is an absolute local filesystem path a host could read.\n *\n * Matches the raw path (not a trimmed copy): a whitespace-padded value would\n * pass a trimmed check but reach the file read unchanged and fail there, so it\n * is rejected up front. Accepts POSIX paths and Windows drive-letter paths only.\n * UNC paths (`\\\\server\\share`) are deliberately rejected: reading one can make\n * Windows auto-authenticate against a remote host (NTLM hash capture), and a\n * remote share is not a supported local data source.\n *\n * Lives in core so validation of a persisted path is available wherever a\n * record is normalized — the desktop app's `isAbsoluteLocalPath` re-exports the\n * same rule for its Tauri call sites.\n *\n * @param path - The path to check.\n * @returns True when the path is an absolute POSIX or Windows path.\n */\nexport function isAbsoluteFilesystemPath(path: string): boolean {\n return path.startsWith(\"/\") || /^[a-z]:[\\\\/]/i.test(path);\n}\n","import type { Feature, FeatureCollection, LineString, MultiPolygon, Polygon } from \"geojson\";\nimport { decodePolyline } from \"./polyline\";\nimport { getRuntimeEnvironment } from \"./runtime-env\";\n\n/**\n * Routing / network-analysis client and pure helpers backing the Processing →\n * Network tools (isochrones / service areas, OD cost matrices).\n *\n * Provider: Valhalla. OSRM (the Directions backend) has no isochrone endpoint,\n * so isochrones require Valhalla, which also serves the OD matrix\n * (`/sources_to_targets`) — one consistent API. The default is the shared\n * public FOSSGIS server, overridable via runtime env (`VITE_ROUTING_ENDPOINT`)\n * so a self-hosted Valhalla can be used instead. The pure request builders and\n * response parsers carry no React/MapLibre dependency so they can be\n * unit-tested without a browser or network.\n */\n\n/** Default public Valhalla server (FOSSGIS). CORS-enabled; usage limits apply. */\nexport const DEFAULT_ROUTING_ENDPOINT = \"https://valhalla1.openstreetmap.de\";\n\n/** Valhalla costing models exposed in the UI. */\nexport type RoutingMode = \"auto\" | \"pedestrian\" | \"bicycle\";\n\n/** Isochrone contour metric: travel time (minutes) or distance (km). */\nexport type RoutingMetric = \"time\" | \"distance\";\n\nexport interface RoutingConfig {\n /** Base URL of the Valhalla server (no trailing slash). */\n endpoint: string;\n}\n\n/** A point with a stable id, used as an isochrone origin or matrix source/target. */\nexport interface RoutingPoint {\n id: string | number;\n lon: number;\n lat: number;\n}\n\n/**\n * Resolves the routing configuration from runtime env, defaulting to the public\n * Valhalla server. `VITE_ROUTING_ENDPOINT` overrides the endpoint.\n *\n * @returns The resolved routing configuration.\n */\nexport function getRoutingConfig(): RoutingConfig {\n const env = getRuntimeEnvironment();\n return {\n endpoint: stripTrailingSlash(env.VITE_ROUTING_ENDPOINT?.trim()) || DEFAULT_ROUTING_ENDPOINT,\n };\n}\n\nfunction stripTrailingSlash(value: string | undefined): string | undefined {\n return value ? value.replace(/\\/+$/, \"\") : value;\n}\n\n/**\n * Parses a comma/space-separated contour string (\"5, 10, 15\") into a sorted,\n * de-duplicated list of positive numbers.\n *\n * @param value - The raw contour string.\n * @returns Ascending unique positive contour values.\n */\nexport function parseContours(value: string): number[] {\n const seen = new Set<number>();\n for (const token of value.split(/[\\s,]+/)) {\n if (!token) continue;\n const n = Number(token);\n if (Number.isFinite(n) && n > 0) seen.add(n);\n }\n return [...seen].sort((a, b) => a - b);\n}\n\nexport interface IsochroneRequestBody {\n locations: { lon: number; lat: number }[];\n costing: RoutingMode;\n contours: ({ time: number } | { distance: number })[];\n polygons: true;\n}\n\n/**\n * Builds a Valhalla `/isochrone` request body for a single origin.\n *\n * @param point - The origin `[lon, lat]`.\n * @param opts - Travel mode, metric, and contour values (minutes for time, km for distance).\n * @returns The request body.\n */\nexport function buildIsochroneRequest(\n point: [number, number],\n opts: { mode: RoutingMode; metric: RoutingMetric; contours: number[] },\n): IsochroneRequestBody {\n const [lon, lat] = point;\n return {\n locations: [{ lon, lat }],\n costing: opts.mode,\n contours: opts.contours.map((value) =>\n opts.metric === \"time\" ? { time: value } : { distance: value },\n ),\n polygons: true,\n };\n}\n\ntype IsochroneFeatureProps = {\n source_id: string | number;\n mode: RoutingMode;\n metric: RoutingMetric;\n contour: number | null;\n};\n\n/**\n * Converts a Valhalla isochrone GeoJSON response into polygon features tagged\n * with their origin, mode, metric, and contour value. Non-polygon features\n * (Valhalla can also emit contour LineStrings) are dropped.\n *\n * @param response - The Valhalla `/isochrone` GeoJSON FeatureCollection.\n * @param ctx - Origin id, travel mode, and metric to tag onto each polygon.\n * @returns The tagged polygon features.\n */\nexport function isochroneResponseToFeatures(\n response: unknown,\n ctx: { sourceId: string | number; mode: RoutingMode; metric: RoutingMetric },\n): Feature<Polygon | MultiPolygon, IsochroneFeatureProps>[] {\n const features = (response as FeatureCollection | null)?.features;\n if (!Array.isArray(features)) return [];\n const out: Feature<Polygon | MultiPolygon, IsochroneFeatureProps>[] = [];\n for (const feature of features) {\n const type = feature?.geometry?.type;\n if (type !== \"Polygon\" && type !== \"MultiPolygon\") continue;\n const contour = (feature.properties as { contour?: unknown } | null)?.contour;\n out.push({\n type: \"Feature\",\n geometry: feature.geometry as Polygon | MultiPolygon,\n properties: {\n source_id: ctx.sourceId,\n mode: ctx.mode,\n metric: ctx.metric,\n contour: typeof contour === \"number\" ? contour : null,\n },\n });\n }\n return out;\n}\n\nexport interface MatrixRequestBody {\n sources: { lon: number; lat: number }[];\n targets: { lon: number; lat: number }[];\n costing: RoutingMode;\n}\n\n/**\n * Builds a Valhalla `/sources_to_targets` request body.\n *\n * @param origins - The source points.\n * @param targets - The target points.\n * @param mode - The travel mode.\n * @returns The request body.\n */\nexport function buildMatrixRequest(\n origins: RoutingPoint[],\n targets: RoutingPoint[],\n mode: RoutingMode,\n): MatrixRequestBody {\n return {\n sources: origins.map((p) => ({ lon: p.lon, lat: p.lat })),\n targets: targets.map((p) => ({ lon: p.lon, lat: p.lat })),\n costing: mode,\n };\n}\n\ntype MatrixCell = {\n from_index: number;\n to_index: number;\n /** Travel time in seconds, or null when unreachable. */\n time: number | null;\n /** Travel distance in km, or null when unreachable. */\n distance: number | null;\n};\n\ntype MatrixFeatureProps = {\n origin_id: string | number;\n dest_id: string | number;\n time_s: number;\n distance_km: number;\n mode: RoutingMode;\n};\n\n/**\n * Converts a Valhalla `/sources_to_targets` response into one LineString per\n * reachable origin→destination pair, carrying the travel time and distance.\n * Unreachable pairs (null time) are dropped.\n *\n * @param response - The Valhalla matrix response.\n * @param origins - The origin points (indexed by `from_index`).\n * @param targets - The target points (indexed by `to_index`).\n * @param ctx - The travel mode to tag onto each pair.\n * @returns The OD-pair LineString features.\n */\nexport function matrixResponseToFeatures(\n response: unknown,\n origins: RoutingPoint[],\n targets: RoutingPoint[],\n ctx: { mode: RoutingMode },\n): Feature<LineString, MatrixFeatureProps>[] {\n const rows = (response as { sources_to_targets?: MatrixCell[][] } | null)?.sources_to_targets;\n if (!Array.isArray(rows)) return [];\n const out: Feature<LineString, MatrixFeatureProps>[] = [];\n for (const row of rows) {\n if (!Array.isArray(row)) continue;\n for (const cell of row) {\n if (cell?.time == null || cell.distance == null) continue;\n const origin = origins[cell.from_index];\n const target = targets[cell.to_index];\n if (!origin || !target) continue;\n out.push({\n type: \"Feature\",\n geometry: {\n type: \"LineString\",\n coordinates: [\n [origin.lon, origin.lat],\n [target.lon, target.lat],\n ],\n },\n properties: {\n origin_id: origin.id,\n dest_id: target.id,\n time_s: cell.time,\n distance_km: cell.distance,\n mode: ctx.mode,\n },\n });\n }\n }\n return out;\n}\n\nexport interface RouteRequestBody {\n locations: { lon: number; lat: number }[];\n costing: RoutingMode;\n directions_options: { units: \"kilometers\" };\n}\n\n/**\n * Builds a Valhalla `/route` request body that visits the points in the given\n * order. Valhalla snaps each location to the nearest routable edge, so points\n * that are off the road network (cell sites, sensors) still route cleanly.\n *\n * @param points - The waypoints in visiting order (at least two).\n * @param mode - The travel mode (costing model).\n * @returns The request body.\n */\nexport function buildRouteRequest(points: RoutingPoint[], mode: RoutingMode): RouteRequestBody {\n return {\n locations: points.map((p) => ({ lon: p.lon, lat: p.lat })),\n costing: mode,\n directions_options: { units: \"kilometers\" },\n };\n}\n\ntype RouteLeg = {\n shape?: string;\n summary?: { time?: number; length?: number };\n};\n\ntype RouteFeatureProps = {\n leg_index: number;\n from_id: string | number;\n to_id: string | number;\n time_s: number;\n distance_km: number;\n mode: RoutingMode;\n};\n\n/**\n * Converts a Valhalla `/route` response into one LineString per leg (the road\n * path between two consecutive waypoints), carrying the leg's travel time and\n * distance. Legs without a decodable shape are dropped.\n *\n * @param response - The Valhalla `/route` response.\n * @param points - The waypoints passed to the request, indexing the legs.\n * @param ctx - The travel mode to tag onto each leg.\n * @returns One LineString feature per routed leg.\n */\nexport function routeResponseToFeatures(\n response: unknown,\n points: RoutingPoint[],\n ctx: { mode: RoutingMode },\n): Feature<LineString, RouteFeatureProps>[] {\n const legs = (response as { trip?: { legs?: RouteLeg[] } } | null)?.trip?.legs;\n if (!Array.isArray(legs)) return [];\n const out: Feature<LineString, RouteFeatureProps>[] = [];\n legs.forEach((leg, index) => {\n if (typeof leg?.shape !== \"string\") return;\n const coordinates = decodePolyline(leg.shape, 6);\n if (coordinates.length < 2) return;\n const from = points[index];\n const to = points[index + 1];\n out.push({\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates },\n properties: {\n leg_index: index,\n from_id: from?.id ?? index,\n to_id: to?.id ?? index + 1,\n time_s: typeof leg.summary?.time === \"number\" ? leg.summary.time : 0,\n distance_km: typeof leg.summary?.length === \"number\" ? leg.summary.length : 0,\n mode: ctx.mode,\n },\n });\n });\n return out;\n}\n\n/**\n * Comparator for ordering route waypoints by a chosen attribute value. Numeric\n * strings and timestamps (anything `Date.parse` understands, e.g. ISO 8601)\n * sort chronologically/numerically; values that parse this way sort before\n * free-form text, which falls back to a locale string comparison. Empty/missing\n * values sort last so unlabeled points trail the ordered ones.\n *\n * Numeric parsing is tried before date parsing, so a single column is expected\n * to use one convention: a column mixing plain integers with ISO timestamps\n * would compare them on the same numeric axis (epoch millis dwarf small\n * integers), which is not a meaningful ordering. Real sequence/time columns use\n * one convention, so this is acceptable in practice.\n *\n * @param a - The first value.\n * @param b - The second value.\n * @returns A negative, zero, or positive number for ascending order.\n */\nexport function compareSequenceValues(a: unknown, b: unknown): number {\n const aMissing = isMissingValue(a);\n const bMissing = isMissingValue(b);\n if (aMissing && bMissing) return 0;\n if (aMissing) return 1;\n if (bMissing) return -1;\n const an = toComparableNumber(a);\n const bn = toComparableNumber(b);\n if (an !== null && bn !== null) return an - bn;\n if (an !== null) return -1;\n if (bn !== null) return 1;\n return String(a).localeCompare(String(b));\n}\n\nfunction isMissingValue(value: unknown): boolean {\n return value == null || (typeof value === \"string\" && value.trim() === \"\");\n}\n\nfunction toComparableNumber(value: unknown): number | null {\n if (typeof value === \"number\") return Number.isFinite(value) ? value : null;\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (trimmed === \"\") return null;\n // Only treat plain decimal/scientific notation as numeric, so hex (\"0x1A\")\n // and other Number()-coercible forms sort as text rather than by their\n // surprising numeric value.\n if (/^[+-]?\\d+(\\.\\d+)?([eE][+-]?\\d+)?$/.test(trimmed)) {\n return Number(trimmed);\n }\n const parsed = Date.parse(trimmed);\n if (Number.isFinite(parsed)) return parsed;\n }\n return null;\n}\n\n/**\n * Error thrown when a Valhalla request returns a non-2xx response. Carries the\n * HTTP status so callers can branch on it (e.g. a 4xx rejection) without\n * pattern-matching the message string.\n */\nexport class RoutingRequestError extends Error {\n readonly status: number;\n\n constructor(status: number, statusText: string) {\n super(`Routing request failed (${status} ${statusText})`);\n this.name = \"RoutingRequestError\";\n this.status = status;\n }\n}\n\nasync function postJson(url: string, body: unknown, signal?: AbortSignal): Promise<unknown> {\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n signal,\n });\n if (!response.ok) {\n throw new RoutingRequestError(response.status, response.statusText);\n }\n return response.json();\n}\n\n/**\n * Requests an isochrone from the Valhalla server.\n *\n * @param endpoint - The Valhalla base URL.\n * @param body - The request body from {@link buildIsochroneRequest}.\n * @param signal - Optional abort signal.\n * @returns The Valhalla GeoJSON response.\n */\nexport function requestIsochrone(\n endpoint: string,\n body: IsochroneRequestBody,\n signal?: AbortSignal,\n): Promise<unknown> {\n return postJson(`${stripTrailingSlash(endpoint)}/isochrone`, body, signal);\n}\n\n/**\n * Requests an OD cost matrix from the Valhalla server.\n *\n * @param endpoint - The Valhalla base URL.\n * @param body - The request body from {@link buildMatrixRequest}.\n * @param signal - Optional abort signal.\n * @returns The Valhalla matrix response.\n */\nexport function requestMatrix(\n endpoint: string,\n body: MatrixRequestBody,\n signal?: AbortSignal,\n): Promise<unknown> {\n return postJson(`${stripTrailingSlash(endpoint)}/sources_to_targets`, body, signal);\n}\n\n/**\n * Requests a route through ordered waypoints from the Valhalla server.\n *\n * @param endpoint - The Valhalla base URL.\n * @param body - The request body from {@link buildRouteRequest}.\n * @param signal - Optional abort signal.\n * @returns The Valhalla `/route` response.\n */\nexport function requestRoute(\n endpoint: string,\n body: RouteRequestBody,\n signal?: AbortSignal,\n): Promise<unknown> {\n return postJson(`${stripTrailingSlash(endpoint)}/route`, body, signal);\n}\n\n/**\n * Requests a turn-by-turn route from the Valhalla server. Unlike\n * {@link requestRoute}, the body is passed through as is, so the caller can ask\n * for Valhalla's OSRM-compatible output with voice and banner instructions,\n * alternates, a start heading, and costing options (the navigation tool).\n *\n * @param endpoint - The Valhalla base URL.\n * @param body - The `/route` request body.\n * @param signal - Optional abort signal.\n * @returns The Valhalla `/route` response.\n */\nexport function requestNavigationRoute(\n endpoint: string,\n body: Record<string, unknown>,\n signal?: AbortSignal,\n): Promise<unknown> {\n return postJson(`${stripTrailingSlash(endpoint)}/route`, body, signal);\n}\n","import {\n Color,\n featureFilter,\n latest,\n normalizePropertyExpression,\n} from \"@maplibre/maplibre-gl-style-spec\";\n\n/** The geometry kinds a MapLibre style expression tells apart (`[\"geometry-type\"]`). */\nexport type StyleGeometryKind = \"Point\" | \"LineString\" | \"Polygon\";\n\n/** A feature as a style layer's expressions read it. */\nexport interface StyleFeatureInput {\n id?: string | number;\n properties: Record<string, unknown> | null | undefined;\n kind: StyleGeometryKind;\n}\n\n/** A style layer's filter and property values, evaluated as MapLibre would. */\nexport interface StyleLayerEvaluator {\n /** Whether the layer's `filter` keeps the feature at `zoom`. */\n passes: (zoom: number, feature: StyleFeatureInput) => boolean;\n /**\n * A paint or layout property's value for a feature at `zoom`: the layer's\n * value (a literal, a legacy function or an expression) or the spec's\n * default. A color comes back as `[r, g, b, a]` with channels 0-255 and\n * alpha 0-1; formatted text as its plain string. `undefined` for a property\n * the spec does not know or an expression that does not compile.\n */\n value: (\n group: \"paint\" | \"layout\",\n name: string,\n zoom: number,\n feature: StyleFeatureInput,\n ) => unknown;\n}\n\ninterface StyleLayerInput {\n id?: string;\n type: string;\n filter?: unknown;\n paint?: Record<string, unknown>;\n layout?: Record<string, unknown>;\n}\n\nconst GEOMETRY_TYPE: Record<StyleGeometryKind, number> = { Point: 1, LineString: 2, Polygon: 3 };\n\ntype Evaluate = (globals: { zoom: number }, feature: unknown) => unknown;\n\n/**\n * Compile a MapLibre style layer's filter and properties for evaluation\n * outside MapLibre, for a renderer that draws the layer itself (the ArcGIS\n * renderer drawing a plugin's own GeoJSON overlay, say). Uses the style spec's\n * own evaluator, so data-driven and zoom expressions, legacy functions and\n * defaults behave as they do on MapLibre. Each property compiles once.\n *\n * @param layer - The style layer (its `type`, `filter`, `paint`, `layout`).\n * @returns The layer's evaluator.\n */\nexport function createStyleLayerEvaluator(layer: StyleLayerInput): StyleLayerEvaluator {\n const root = `layers[${layer.id ?? \"\"}]`;\n let filter: ((zoom: number, feature: unknown) => boolean) | null;\n try {\n const compiled = featureFilter(layer.filter as never, `${root}.filter`);\n filter = (zoom, feature) => compiled.filter({ zoom } as never, feature as never);\n } catch {\n // MapLibre refuses the layer outright; draw nothing rather than everything.\n filter = () => false;\n }\n const compiled = new Map<string, Evaluate | null>();\n const compile = (group: \"paint\" | \"layout\", name: string): Evaluate | null => {\n const key = `${group}/${name}`;\n if (compiled.has(key)) return compiled.get(key)!;\n const spec = (latest as unknown as Record<string, Record<string, unknown>>)[\n `${group}_${layer.type}`\n ]?.[name] as { default?: unknown } | undefined;\n let evaluate: Evaluate | null = null;\n if (spec) {\n const raw = layer[group]?.[name];\n try {\n const expression = normalizePropertyExpression(\n (raw ?? spec.default) as never,\n `${root}.${group}.${name}`,\n spec as never,\n );\n evaluate = (globals, feature) => expression.evaluate(globals as never, feature as never);\n } catch {\n evaluate = null;\n }\n }\n compiled.set(key, evaluate);\n return evaluate;\n };\n const input = (feature: StyleFeatureInput) => ({\n type: GEOMETRY_TYPE[feature.kind],\n id: feature.id,\n properties: feature.properties ?? {},\n });\n return {\n passes: (zoom, feature) => {\n try {\n return filter(zoom, input(feature));\n } catch {\n return false;\n }\n },\n value: (group, name, zoom, feature) => {\n const evaluate = compile(group, name);\n if (!evaluate) return undefined;\n let result: unknown;\n try {\n result = evaluate({ zoom }, input(feature));\n } catch {\n return undefined;\n }\n if (result instanceof Color) {\n const [r, g, b, a] = result.rgb;\n return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255), a];\n }\n if (result && typeof result === \"object\" && \"sections\" in result) return String(result);\n return result;\n },\n };\n}\n","/**\n * The document language, so formatted numbers and dates follow the UI locale.\n *\n * Shared by the Identify popup's value formatting and by label number\n * formatting, which both fall back to it when the author has not pinned an\n * explicit locale. Returns `undefined` outside a browser (tests, the headless\n * project authoring paths), which leaves `Intl` on its own default.\n */\nexport function documentLocale(): string | undefined {\n const lang = typeof document !== \"undefined\" ? document.documentElement.lang.trim() : \"\";\n return lang || undefined;\n}\n","import type { LabelStyle } from \"./types\";\n\n/**\n * Locale-aware number formatting for map labels (issue #2336).\n *\n * A numeric attribute rendered straight into a label reads as `1234567.5`,\n * which is hard to compare at a glance. QGIS solves this with\n * `format_number(field, places, language)`; the equivalent here is three\n * {@link LabelStyle} fields — {@link LabelStyle.numberFormatEnabled},\n * {@link LabelStyle.numberDecimals} and {@link LabelStyle.numberLocale} —\n * whose effect this module renders two ways:\n *\n * - {@link labelFieldTextField} builds the MapLibre `text-field` expression the\n * 2D map and the Mapbox/MapLibre style export both use, so the map and the\n * exported style agree.\n * - {@link formatLabelNumber} does the same in JavaScript for the paths that\n * resolve label text themselves: the Cesium globe and the duplicate-label\n * collapsing modes.\n *\n * Formatting applies to {@link LabelStyle.field} only. A label\n * {@link LabelStyle.expression} formats its own output (with `number-format`,\n * offered in the Expression Builder), so wrapping it here would fight the\n * author.\n */\n\n/**\n * Locales offered for {@link LabelStyle.numberLocale}, alongside the default\n * empty value that follows the app's own language.\n *\n * Deliberately short, and every entry groups with a character the map's glyph\n * stack can draw: ASCII `,`/`.` or U+00A0. Locales whose CLDR grouping uses\n * the narrow no-break space U+202F (`fr-FR`) or non-Latin digits (`ar-EG`)\n * are left out because a missing glyph renders as a blank box on the map\n * rather than as a separator. An author who needs one of those can still\n * write `[\"number-format\", …]` in the label expression.\n */\nexport const LABEL_NUMBER_LOCALES = [\"en-US\", \"de-DE\", \"ru-RU\", \"hi-IN\"] as const;\n\n/**\n * Magnitude at which the text-field stops treating a value as a finite number.\n *\n * JSON carries no `Infinity`, so the expression cannot test against it\n * directly; this is the largest power of ten a double holds, which doubles as\n * the `to-number` fallback so an unconvertible value fails the same test. A\n * real attribute at or above it labels as plain text instead of a formatted\n * number -- a trade worth making for values already past 1e308.\n */\nconst NON_FINITE_SENTINEL = 1e308;\n\n/** Sample value used to preview a locale's separators in the Style panel. */\nexport const LABEL_NUMBER_SAMPLE = 1234567.5;\n\n/**\n * Cached `Intl.NumberFormat`s, keyed by locale + decimals. `formatLabelNumber`\n * runs once per labeled feature (per Cesium entity, per deduplicated point), so\n * constructing a formatter on every call is the wrong order of magnitude.\n *\n * The key space is small: {@link LABEL_NUMBER_LOCALES} plus the app's own\n * language, times eleven decimal settings, times whatever a hand-edited project\n * pins.\n */\nconst formatterCache = new Map<string, Intl.NumberFormat>();\n\n/** Locale tags already accepted or rejected, so each is tested once. */\nconst localeSupport = new Map<string, boolean>();\n\n/**\n * A formatter for this locale/decimals pair. Grouping is always on: it is the\n * whole point of the setting. The locale must already have been vetted by\n * {@link resolveLabelNumberLocale}, so this never throws.\n */\nfunction labelNumberFormatter(locale: string | undefined, decimals: number): Intl.NumberFormat {\n const digits = clampLabelDecimals(decimals);\n const key = `${locale ?? \"\"}\\u0000${digits}`;\n const cached = formatterCache.get(key);\n if (cached) return cached;\n const formatter = new Intl.NumberFormat(locale, {\n useGrouping: true,\n minimumFractionDigits: digits,\n maximumFractionDigits: digits,\n });\n formatterCache.set(key, formatter);\n return formatter;\n}\n\n/**\n * Whether every character a locale's grouping produces is one the map's glyph\n * stack can draw: ASCII, or the no-break space U+00A0.\n *\n * {@link LABEL_NUMBER_LOCALES} is curated on exactly this basis, but the picker\n * is not the only way a locale reaches the renderer. A hand-edited project can\n * pin any tag, and the default (`numberLocale: \"\"`) follows the app's own\n * language, so a French or Arabic UI would otherwise put U+202F or Arabic-Indic\n * digits into the `text-field` and label the map with blank boxes. Testing the\n * rendered sample rather than the tag covers both routes and needs no list to\n * keep in sync.\n */\nfunction glyphSafeLocale(tag: string): boolean {\n try {\n const format = new Intl.NumberFormat(tag, {\n useGrouping: true,\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n });\n // Both signs: sv-SE, fi-FI and nb-NO group with U+00A0 (safe) but write\n // negatives with U+2212 MINUS SIGN, so a positive-only check would pass\n // them and then blank-box every negative label.\n for (const sample of [\n format.format(LABEL_NUMBER_SAMPLE),\n format.format(-LABEL_NUMBER_SAMPLE),\n ]) {\n for (const char of sample) {\n const code = char.codePointAt(0) ?? 0;\n if (code >= 0x80 && code !== 0x00a0) return false;\n }\n }\n return true;\n } catch {\n // A malformed BCP 47 tag (`\"en_US\"`, `\"not a locale\"`) makes Intl throw a\n // RangeError. It would propagate out of the Cesium labeler and the dedup\n // builder and take down the layer's labels, and MapLibre evaluates its own\n // Intl.NumberFormat per feature, so the expression path is exposed too.\n return false;\n }\n}\n\n/** Clamp a decimal-places setting to the range the UI and `Intl` both accept. */\nexport function clampLabelDecimals(decimals: number): number {\n if (!Number.isFinite(decimals)) return 0;\n return Math.max(0, Math.min(10, Math.trunc(decimals)));\n}\n\n/**\n * The first locale tag that both `Intl` accepts and the map can draw, or\n * `undefined` to leave `Intl` and MapLibre on the runtime default.\n *\n * Every path resolves its locale through here -- the JavaScript formatter, the\n * MapLibre expression, and the dedup cache key -- so all three agree on which\n * locale is actually in effect.\n */\nexport function resolveLabelNumberLocale(...tags: (string | undefined)[]): string | undefined {\n for (const tag of tags) {\n if (!tag) continue;\n let usable = localeSupport.get(tag);\n if (usable === undefined) {\n usable = glyphSafeLocale(tag);\n localeSupport.set(tag, usable);\n }\n if (usable) return tag;\n }\n return undefined;\n}\n\n/**\n * Format one label value as a number, or `null` when the setting is off or the\n * value is not a finite number (a text attribute keeps its own rendering).\n *\n * @param value - The raw attribute value.\n * @param labels - The layer's label configuration.\n * @param fallbackLocale - Locale used when {@link LabelStyle.numberLocale} is\n * empty; the caller passes the app's language. Omitted means the runtime\n * default, matching how popups format their numbers.\n */\nexport function formatLabelNumber(\n value: unknown,\n labels: Pick<LabelStyle, \"numberFormatEnabled\" | \"numberDecimals\" | \"numberLocale\">,\n fallbackLocale?: string,\n): string | null {\n if (!labels.numberFormatEnabled) return null;\n if (typeof value !== \"number\" || !Number.isFinite(value)) return null;\n const locale = resolveLabelNumberLocale(labels.numberLocale, fallbackLocale);\n return labelNumberFormatter(locale, labels.numberDecimals).format(value);\n}\n\n/**\n * The MapLibre expression that renders {@link LabelStyle.field} as text, with\n * number formatting applied when it is on. Returns `\"\"` when no field is set,\n * which callers treat as \"no label text\".\n *\n * Number formatting is guarded by a `typeof` test rather than applied blindly\n * so a mixed or text column still labels normally; only real numbers take the\n * `number-format` branch.\n */\nexport function labelFieldTextField(\n labels: Pick<LabelStyle, \"field\" | \"numberFormatEnabled\" | \"numberDecimals\" | \"numberLocale\">,\n fallbackLocale?: string,\n): unknown[] | \"\" {\n const field = labels.field;\n if (!field) return \"\";\n const asText = [\"to-string\", [\"coalesce\", [\"get\", field], \"\"]];\n if (!labels.numberFormatEnabled) return asText;\n const locale = resolveLabelNumberLocale(labels.numberLocale, fallbackLocale);\n const digits = clampLabelDecimals(labels.numberDecimals);\n const options: Record<string, unknown> = {};\n if (locale) options.locale = locale;\n // MapLibre's `number-format` ignores a falsy option, so zero fraction digits\n // cannot be requested that way — it would silently fall back to the spec\n // default of up to three. Rounding first gives an integer, which then prints\n // with no fraction digits at all.\n // `to-number` carries a fallback so it can never throw: MapLibre hands a NaN\n // property through as null, and an unguarded conversion raised \"Could not\n // convert null to number\" from inside the render. The fallback is the same\n // sentinel the finiteness test rejects, so a value that could not convert\n // takes the plain-text branch rather than labelling as the fallback itself.\n const numeric = [\"to-number\", [\"get\", field], NON_FINITE_SENTINEL];\n const number = digits > 0 ? numeric : [\"round\", numeric];\n if (digits > 0) {\n options[\"min-fraction-digits\"] = digits;\n options[\"max-fraction-digits\"] = digits;\n }\n return [\n \"case\",\n [\n \"all\",\n [\"==\", [\"typeof\", [\"get\", field]], \"number\"],\n // Finite only, matching formatLabelNumber's Number.isFinite guard.\n // Without it the map rendered Infinity as U+221E -- a glyph the label\n // font need not carry, so a tofu box -- while the JavaScript path fell\n // back to the plain string, which is the disagreement the parity tests\n // exist to prevent. NaN was worse: it reached the render as null and\n // threw out of the conversion.\n [\"<\", [\"abs\", numeric], NON_FINITE_SENTINEL],\n ],\n [\"number-format\", number, options],\n asText,\n ];\n}\n\n/**\n * Render {@link LABEL_NUMBER_SAMPLE} the way a locale/decimals pair would, so\n * the Style panel can name each choice by the separators it actually produces\n * (\"1,234,567.50\") instead of by an opaque language tag.\n *\n * A tag `Intl` rejects (or one the map cannot draw) falls back to the runtime\n * locale's formatting rather than to an unformatted number, matching what\n * {@link formatLabelNumber} and the emitted expression do with the same tag.\n */\nexport function formatLabelNumberSample(locale: string, decimals: number): string {\n return labelNumberFormatter(resolveLabelNumberLocale(locale), decimals).format(\n LABEL_NUMBER_SAMPLE,\n );\n}\n","/**\n * Paint ownership for plugin-registered native layers.\n *\n * A plugin can register a MapLibre `CustomLayerInterface` (a WebGL layer that\n * draws itself, e.g. a Zarr renderer that reprojects chunks on the GPU) through\n * `registerExternalNativeLayer`. Such a layer has **no MapLibre paint\n * properties**, so GeoLibre's Style panel editors (raster opacity/brightness/\n * saturation/contrast/hue, or the vector paint editors) cannot reach it: every\n * `setPaintProperty` call is dropped and the control looks functional while\n * doing nothing (opengeos/GeoLibre#1445).\n *\n * The registration therefore declares `paintMode: \"plugin\"`, which lands in the\n * store layer as `metadata.paintMode` so the UI can hide the editors it cannot\n * apply, and may additionally supply a {@link ExternalNativePaintBridge} so the\n * generic controls GeoLibre keeps (opacity, visibility) are forwarded to the\n * custom layer's own API.\n *\n * The bridge holds live functions, so it cannot live in `metadata` (that is\n * serialized into `.geolibre.json`). It is kept in this module-level registry\n * instead, keyed by layer id, and the layer record only carries the\n * serializable `paintMode` flag.\n */\nexport interface ExternalNativePaintBridge {\n /**\n * Apply an opacity in [0, 1] to the plugin's layer (e.g.\n * `zarrLayer.setOpacity(value)`). When supplied, GeoLibre keeps its Opacity\n * sliders live for the layer and calls this whenever the value changes.\n */\n setOpacity?: (opacity: number) => void;\n /**\n * Apply the panel's show/hide state. MapLibre already honors `visibility` on\n * a custom layer, so this is only needed by layers that also want to release\n * resources or stop fetching while hidden.\n */\n setVisibility?: (visible: boolean) => void;\n}\n\n/** Serializable paint-ownership marker stored in `GeoLibreLayer.metadata`. */\nexport type ExternalNativePaintMode = \"geolibre\" | \"plugin\";\n\n/**\n * Whether a layer is drawn by a **custom render layer** its control created — a\n * deck.gl overlay, 3D Tiles, or Add Vector Layer's own MapLibre layers — rather\n * than by GeoLibre's layer sync. `metadata.customLayerType` is what marks one,\n * and it is the flag `syncExternalNativeLayer` branches on: for these layers the\n * sync only *moves and filters* native layers that already exist, so nothing\n * appears on the map unless the owning control (re)creates them.\n *\n * Lives here, in core, so the map's dispatch and the consumers that need to\n * reason about it — the Layer Library's \"can this be re-added and rendered?\"\n * gate (issue #1520) — share one definition and cannot drift.\n *\n * Distinct from {@link pluginOwnsPaint}: that asks who applies *paint* to an\n * existing layer, this asks who creates the layer at all.\n *\n * @param layer - A store layer (metadata only).\n * @returns True when only the owning control can create the layer's map output.\n */\nexport function controlRendersLayer(layer: { metadata: Record<string, unknown> }): boolean {\n return typeof layer.metadata.customLayerType === \"string\";\n}\n\n/**\n * True when the plugin that registered this layer paints it itself, so GeoLibre\n * must not offer (or apply) MapLibre paint properties for it.\n */\nexport function pluginOwnsPaint(layer: { metadata: Record<string, unknown> }): boolean {\n return layer.metadata.paintMode === \"plugin\";\n}\n\nconst paintBridges = new Map<string, ExternalNativePaintBridge>();\n\n/** Register (or replace) the paint bridge a plugin supplied for a layer. */\nexport function setExternalNativePaintBridge(\n layerId: string,\n bridge: ExternalNativePaintBridge | undefined,\n): void {\n if (bridge && (bridge.setOpacity || bridge.setVisibility)) {\n paintBridges.set(layerId, bridge);\n } else {\n paintBridges.delete(layerId);\n }\n}\n\n/** Drop a layer's paint bridge (on unregister, or when the layer is removed). */\nexport function clearExternalNativePaintBridge(layerId: string): void {\n paintBridges.delete(layerId);\n}\n\n/** The paint bridge for a layer, if its plugin supplied one. */\nexport function getExternalNativePaintBridge(\n layerId: string,\n): ExternalNativePaintBridge | undefined {\n return paintBridges.get(layerId);\n}\n\n/**\n * True when a plugin-painted layer bridged `setOpacity`, so an Opacity slider\n * would actually reach the renderer. It says nothing about GeoLibre-painted\n * layers, which have no bridge: the Style and Layers panels combine it with\n * `!pluginOwnsPaint(layer)` to decide whether to show the slider at all.\n */\nexport function supportsBridgedOpacity(layerId: string): boolean {\n return typeof paintBridges.get(layerId)?.setOpacity === \"function\";\n}\n\n/**\n * True when the primary renderer draws this plugin-painted layer itself, from\n * the store record, and so applies `layer.opacity` without any plugin bridge.\n *\n * A Zarr layer is a MapLibre custom layer owned by the Zarr control on the 2D\n * map, but the ArcGIS view and the Cesium globe render it natively (an ArcGIS\n * layer's `opacity`, a Cesium `ImageryLayer.alpha`) and mount no control to\n * register a bridge. Without this the Opacity slider would be hidden there\n * although it works (opengeos/GeoLibre#2261).\n *\n * @param layer - A store layer.\n * @param renderer - The primary renderer (`useAppStore().primaryRenderer`).\n * @returns Whether an Opacity slider reaches the layer through the renderer.\n */\nexport function rendererAppliesOpacity(\n layer: { type: string },\n renderer: string | undefined,\n): boolean {\n // @geolibre/core sits below @geolibre/map and cannot read its capabilities;\n // these are the renderers whose `nativeZarr` is set, which a test pins.\n // eslint-disable-next-line local/no-renderer-kind-checks -- see above\n return (renderer === \"arcgis\" || renderer === \"cesium\") && layer.type === \"zarr\";\n}\n\n/**\n * Whether `layer` is an Add Vector Layer layer GeoLibre adopted\n * (`maplibre-gl-vector-adopted`, ADOPTED_VECTOR_SOURCE_KIND in\n * @geolibre/plugins) that has no features yet. A project saves a URL- or\n * path-backed adopted layer without them, and the vector control reads them\n * again on reopen. Until then its `source.url` may name GeoParquet or\n * GeoPackage, so a renderer must wait rather than load that URL as GeoJSON.\n *\n * @param layer - A store layer.\n * @returns True while the adopted layer awaits its features.\n */\nexport function isAdoptedVectorAwaitingFeatures(layer: {\n metadata: Record<string, unknown>;\n geojson?: unknown;\n}): boolean {\n return layer.metadata.sourceKind === \"maplibre-gl-vector-adopted\" && !layer.geojson;\n}\n","/**\n * Pure helpers for the Attribute Form designer (#1322): coercing raw form\n * strings into widget-typed values, validating values against per-field rules\n * (required, numeric bounds, value maps, boolean constraint expressions), and\n * evaluating conditional field visibility.\n *\n * The designer UI (layer properties → Attributes Form) authors an\n * {@link AttributeFormConfig} stored on the layer; the attribute editing\n * surfaces (attribute table inline editor, Field Collection capture form) call\n * these helpers so both enforce the same rules. Everything here is side-effect\n * free so it can be unit tested without a DOM or the app store.\n *\n * Constraint and visibility expressions are MapLibre expressions compiled and\n * evaluated through `expressions.ts` (the same engine as the Expression\n * Builder), always with `expectedType: \"boolean\"`.\n */\nimport type { Feature } from \"geojson\";\nimport { evaluateMapExpression } from \"./expressions\";\nimport type {\n AttributeFormConfig,\n AttributeFormFieldConfig,\n AttributeFormValueMapEntry,\n} from \"./types\";\n\n/** Options threaded to expression evaluation (constraints, visibility). */\nexport interface AttributeFormEvalOptions {\n /** Map zoom for `[\"zoom\"]` in expressions; defaults to 0. */\n zoom?: number;\n /**\n * Constraints a data service enforces regardless of the form's\n * presentation (ArcGIS domains). A field the form hides is still checked\n * against its entry here, so a visibility rule cannot let an invalid value\n * reach the service.\n */\n serviceForm?: AttributeFormConfig;\n /**\n * Real feature backing the properties, when the caller has one — its\n * geometry feeds `[\"geometry-type\"]` and geometry-aware operators. Its\n * `properties` are ignored in favor of the candidate record being\n * validated.\n */\n feature?: Feature;\n}\n\n/** Why a field's value was rejected; codes map to localized messages. */\nexport interface AttributeFormFieldError {\n code: \"required\" | \"number\" | \"integer\" | \"text\" | \"range\" | \"valueMap\" | \"constraint\";\n /**\n * For `constraint`: the author's `constraintDescription`, or the\n * expression's own error text when it failed to evaluate.\n */\n message?: string;\n /** Bounds echoed back for `range` error messages. */\n min?: number;\n max?: number;\n}\n\n/** Outcome of validating a whole properties record against a form config. */\nexport interface AttributeFormValidation {\n ok: boolean;\n /** Field name → error for every failing, visible, configured field. */\n errors: Record<string, AttributeFormFieldError>;\n}\n\n/** Look up the configuration for a field, if any. */\nexport function getAttributeFormField(\n form: AttributeFormConfig | undefined,\n field: string,\n): AttributeFormFieldConfig | undefined {\n return form?.fields.find((entry) => entry.field === field);\n}\n\n/** The label attribute forms should show for a configured field. */\nexport function attributeFormFieldLabel(config: AttributeFormFieldConfig): string {\n return config.alias?.trim() || config.field;\n}\n\n/**\n * Parse the designer's value-map text — one entry per line, `value=label` or\n * just `value` — into entries. Blank lines and duplicate values are dropped.\n */\nexport function parseValueMapText(text: string): AttributeFormValueMapEntry[] {\n const seen = new Set<string>();\n const entries: AttributeFormValueMapEntry[] = [];\n for (const line of text.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n const eq = trimmed.indexOf(\"=\");\n const value = (eq >= 0 ? trimmed.slice(0, eq) : trimmed).trim();\n const label = eq >= 0 ? trimmed.slice(eq + 1).trim() : \"\";\n if (!value || seen.has(value)) continue;\n seen.add(value);\n entries.push(label && label !== value ? { value, label } : { value });\n }\n return entries;\n}\n\n/** Serialize value-map entries back into the designer's editable text form. */\nexport function valueMapToText(entries: readonly AttributeFormValueMapEntry[] | undefined): string {\n return (entries ?? [])\n .map((entry) =>\n entry.label && entry.label !== entry.value ? `${entry.value}=${entry.label}` : entry.value,\n )\n .join(\"\\n\");\n}\n\n/** Display label for a stored value under a value-map widget. */\nexport function valueMapLabelFor(config: AttributeFormFieldConfig, value: unknown): string {\n const text = value == null ? \"\" : String(value);\n const entry = config.valueMap?.find((candidate) => candidate.value === text);\n return entry?.label ?? text;\n}\n\n/**\n * Coerce a raw form string into the typed value the widget stores on the\n * feature. An empty/blank string means \"no value\" (`null`). A `number`/`range`\n * string that does not parse is returned verbatim so validation can flag it\n * instead of silently dropping the input.\n */\nexport function coerceAttributeFormValue(config: AttributeFormFieldConfig, raw: string): unknown {\n const trimmed = raw.trim();\n if (trimmed === \"\") return null;\n if (config.valueType) {\n // A declared type wins over the widget heuristics below. A value-map pick\n // is kept verbatim so a code with surrounding spaces still matches.\n const picked =\n config.widget === \"valueMap\" && config.valueMap?.some((entry) => entry.value === raw)\n ? raw\n : trimmed;\n if (config.valueType === \"string\") return picked;\n const parsed = Number(picked);\n return Number.isFinite(parsed) ? parsed : raw;\n }\n if (config.widget === \"number\" || config.widget === \"range\") {\n const parsed = Number(trimmed);\n return Number.isFinite(parsed) ? parsed : raw;\n }\n if (config.widget === \"checkbox\") {\n const normalized = trimmed.toLowerCase();\n if (normalized === \"true\") return true;\n if (normalized === \"false\") return false;\n return raw;\n }\n // A value map whose entries are all canonical numeric codes stores numbers,\n // so an edited row keeps the same property type as untouched rows (strict-\n // equality style/filter expressions would otherwise silently miss edited\n // features). Canonical means the string round-trips through Number (\"1\",\n // \"-2\", \"3.5\") — zero-padded or exotic forms like \"01\" or \"1e3\" are\n // identifiers, not numbers, and stay strings so the membership check in\n // validateAttributeFormField keeps matching the entry verbatim.\n if (\n config.widget === \"valueMap\" &&\n config.valueMap?.length &&\n config.valueMap.every((entry) => String(Number(entry.value)) === entry.value)\n ) {\n const parsed = Number(trimmed);\n if (Number.isFinite(parsed)) return parsed;\n }\n // text, date (ISO yyyy-mm-dd string), and valueMap store the string verbatim.\n return trimmed;\n}\n\n/**\n * Whether a configured field is currently shown, given the form's candidate\n * properties. An empty, invalid, or erroring visibility expression fails open\n * (visible) so a typo cannot hide data entry.\n */\nexport function isAttributeFormFieldVisible(\n config: AttributeFormFieldConfig,\n properties: Record<string, unknown>,\n options: AttributeFormEvalOptions = {},\n): boolean {\n const source = config.visibilityExpression?.trim();\n if (!source) return true;\n const result = evaluateMapExpression(source, {\n feature: featureForEvaluation(properties, options.feature),\n zoom: options.zoom,\n expectedType: \"boolean\",\n });\n return result.kind === \"value\" ? result.value === true : true;\n}\n\n/**\n * Validate one configured field against the candidate properties record (the\n * field's own candidate value lives at `properties[config.field]`). Returns\n * `null` when the value passes. Checks run in order: required, widget typing,\n * numeric bounds, value-map membership, then the constraint expression — the\n * constraint sees the whole record, so cross-field rules work.\n */\nexport function validateAttributeFormField(\n config: AttributeFormFieldConfig,\n properties: Record<string, unknown>,\n options: AttributeFormEvalOptions = {},\n): AttributeFormFieldError | null {\n const value = properties[config.field];\n const empty = value == null || value === \"\";\n\n if (empty) {\n // An unchecked checkbox is a valid false-like state, not a missing value —\n // requiring it would block every save with a confusing \"value required\".\n if (config.required && config.widget !== \"checkbox\") return { code: \"required\" };\n } else {\n if (config.valueType === \"string\" && typeof value !== \"string\") return { code: \"text\" };\n if (config.valueType === \"number\" || config.valueType === \"integer\") {\n if (typeof value !== \"number\" || !Number.isFinite(value)) return { code: \"number\" };\n if (config.valueType === \"integer\" && !Number.isSafeInteger(value))\n return { code: \"integer\" };\n }\n if (config.widget === \"number\" || config.widget === \"range\") {\n const numeric = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(numeric)) return { code: \"number\" };\n if (\n (config.min != null && numeric < config.min) ||\n (config.max != null && numeric > config.max)\n ) {\n return { code: \"range\", min: config.min, max: config.max };\n }\n }\n if (\n config.widget === \"valueMap\" &&\n config.valueMap?.length &&\n !config.valueMap.some((entry) => entry.value === String(value))\n ) {\n return { code: \"valueMap\" };\n }\n }\n\n const source = config.constraintExpression?.trim();\n if (source) {\n const result = evaluateMapExpression(source, {\n feature: featureForEvaluation(properties, options.feature),\n zoom: options.zoom,\n expectedType: \"boolean\",\n });\n if (result.kind === \"error\") {\n // An unevaluable constraint cannot be verified, so the value is\n // rejected — surfacing the compile/runtime message rather than\n // silently accepting data the author meant to gate.\n return { code: \"constraint\", message: (result.errors ?? []).join(\"; \") };\n }\n if (result.kind === \"value\" && result.value !== true) {\n return {\n code: \"constraint\",\n ...(config.constraintDescription ? { message: config.constraintDescription } : {}),\n };\n }\n }\n\n return null;\n}\n\n/**\n * Validate a whole candidate properties record against a form config. Hidden\n * fields (visibility expression currently false) skip the form's own rules —\n * a field the form does not show cannot block a save — but are still checked\n * against `options.serviceForm`, whose constraints the data service enforces.\n */\nexport function validateAttributeFormValues(\n form: AttributeFormConfig | undefined,\n properties: Record<string, unknown>,\n options: AttributeFormEvalOptions = {},\n): AttributeFormValidation {\n const errors: Record<string, AttributeFormFieldError> = {};\n const checked = new Set<string>();\n for (const config of form?.fields ?? []) {\n if (!isAttributeFormFieldVisible(config, properties, options)) continue;\n checked.add(config.field);\n const error = validateAttributeFormField(config, properties, options);\n if (error) errors[config.field] = error;\n }\n for (const config of options.serviceForm?.fields ?? []) {\n if (checked.has(config.field)) continue;\n const error = validateAttributeFormField(config, properties, options);\n if (error) errors[config.field] = error;\n }\n return { ok: Object.keys(errors).length === 0, errors };\n}\n\n/**\n * Combine a layer's designer-authored form with the constraints its data\n * service publishes (for example ArcGIS coded-value and range domains) into\n * the form the editing surfaces use. The author's presentation choices\n * (alias, visibility, constraint expression, a narrower value map or bounds)\n * survive, but never widen what the service accepts: a service value map\n * forces the value-map widget and keeps only author entries the service also\n * lists (all service entries when none overlap), bounds take the tighter of\n * each side, and the service's declared value type always applies.\n *\n * @param form - The layer's designer-authored config, if any.\n * @param service - Service-derived configs, if the layer has any.\n * @returns The effective config, or `form` itself when there is no service form.\n */\nexport function mergeAttributeFormConfigs(\n form: AttributeFormConfig | undefined,\n service: AttributeFormConfig | undefined,\n): AttributeFormConfig | undefined {\n if (!service?.fields.length) return form;\n const serviceFields = new Map(service.fields.map((entry) => [entry.field, entry]));\n const fields: AttributeFormFieldConfig[] = [];\n for (const own of form?.fields ?? []) {\n const constraint = serviceFields.get(own.field);\n serviceFields.delete(own.field);\n fields.push(constraint ? mergeAttributeFormField(own, constraint) : own);\n }\n fields.push(...serviceFields.values());\n return { fields };\n}\n\nfunction mergeAttributeFormField(\n own: AttributeFormFieldConfig,\n service: AttributeFormFieldConfig,\n): AttributeFormFieldConfig {\n const merged: AttributeFormFieldConfig = { ...own, valueType: service.valueType };\n if (!own.alias?.trim() && service.alias) merged.alias = service.alias;\n if (service.required) merged.required = true;\n if (service.widget === \"valueMap\" && service.valueMap) {\n const allowed = new Map(service.valueMap.map((entry) => [entry.value, entry]));\n const narrowed =\n own.widget === \"valueMap\"\n ? (own.valueMap ?? [])\n .filter((entry) => allowed.has(entry.value))\n .map((entry) => ({\n value: entry.value,\n label: entry.label ?? allowed.get(entry.value)!.label,\n }))\n : [];\n merged.widget = \"valueMap\";\n merged.valueMap = narrowed.length ? narrowed : service.valueMap;\n delete merged.min;\n delete merged.max;\n delete merged.step;\n return merged;\n }\n if (own.widget !== \"number\" && own.widget !== \"range\") {\n merged.widget = service.widget;\n delete merged.valueMap;\n }\n merged.min = tighter(own.min, service.min, Math.max);\n merged.max = tighter(own.max, service.max, Math.min);\n if (merged.min === undefined) delete merged.min;\n if (merged.max === undefined) delete merged.max;\n return merged;\n}\n\nfunction tighter(\n a: number | undefined,\n b: number | undefined,\n pick: (a: number, b: number) => number,\n): number | undefined {\n if (a == null) return b;\n if (b == null) return a;\n return pick(a, b);\n}\n\n/**\n * Build the feature handed to expression evaluation: the caller's real\n * feature geometry when available (so `[\"geometry-type\"]` works), always with\n * the candidate properties being validated.\n */\nfunction featureForEvaluation(\n properties: Record<string, unknown>,\n feature: Feature | undefined,\n): Feature {\n return {\n type: \"Feature\",\n geometry: feature?.geometry ?? { type: \"Point\", coordinates: [0, 0] },\n ...(feature?.id !== undefined ? { id: feature.id } : {}),\n properties: properties as Feature[\"properties\"],\n };\n}\n","/**\n * Map tools that own the map's clicks while they draw (the Measure control\n * placing points, the Geo Editor sketching a shape) register a claim here, and\n * the Identify click handlers stand down while any claim holds. Without it a\n * click meant as a measure vertex also opens an Identify popup over the layer\n * underneath (issue #3237).\n *\n * A claim is a predicate read at click time rather than a flag the tool sets,\n * so a tool whose drawing state changes without an event (Measure stops\n * drawing silently when its panel collapses) never leaves a stale claim.\n */\nconst claims = new Set<() => boolean>();\n\n/**\n * Register a tool whose drawing state claims the map's clicks.\n *\n * Args:\n * isClaiming: Returns true while the tool is drawing and owns map clicks.\n *\n * Returns:\n * A function that removes the claim.\n */\nexport function registerMapClickClaim(isClaiming: () => boolean): () => void {\n // A fresh entry per registration, so two tools sharing one predicate keep\n // independent claims and releasing one leaves the other in place.\n const entry = () => isClaiming();\n claims.add(entry);\n return () => {\n claims.delete(entry);\n };\n}\n\n/**\n * Whether a registered map tool currently owns the map's clicks.\n *\n * Returns:\n * True when any registered claim reports that its tool is drawing.\n */\nexport function isMapClickClaimed(): boolean {\n for (const isClaiming of claims) {\n try {\n if (isClaiming()) return true;\n } catch (error) {\n // A tool torn down mid-read reports no claim rather than breaking\n // Identify for every other click; warn so a broken predicate (an\n // upstream API change, say) does not fail silently.\n console.warn(\"[GeoLibre] A map click claim could not be read\", error);\n }\n }\n return false;\n}\n","/**\n * Pure helpers for the popup and tooltip designer (#2113): resolving which\n * fields a layer's Identify popup (or hover tooltip) shows and in what order,\n * and formatting each value the way the author asked for.\n *\n * The designer UI (Style panel → Popup) authors a {@link LayerPopupConfig} on\n * the layer; `MapCanvas` renders the popup from these helpers so the click\n * popup and the hover tooltip agree on labels, order and formatting.\n * Everything here is side-effect free so it can be unit tested without a DOM\n * or the app store.\n *\n * Two rules the rest of the app depends on:\n *\n * - {@link GeoLibreLayer.fieldVisibility} is authoritative. A field marked\n * `\"hidden\"` or `\"excluded\"` never reaches a popup, even when a popup config\n * names it explicitly — the config selects from what is visible, it cannot\n * re-expose what the author hid.\n * - A layer with no config behaves exactly as it did before the designer\n * existed: every property, in the feature's own key order, unformatted.\n */\nimport type { Feature } from \"geojson\";\nimport { compileFeatureExpression, type CompiledFeatureExpression } from \"./expressions\";\nimport { PHOTO_FULL_PROPERTY } from \"./photo\";\nimport type {\n FieldVisibility,\n LayerPopupConfig,\n PopupDateFormat,\n PopupFieldConfig,\n PopupFieldFormat,\n PopupFieldKind,\n} from \"./types\";\n\n/** Match an inline base64 raster image (excludes SVG, which can carry scripts). */\nconst INLINE_IMAGE_DATA_URL = /^data:image\\/(?!svg)[\\w.+-]+;base64,/i;\n\n/** One resolved popup row, ready for the renderer. */\nexport interface PopupRow {\n /** Feature property key. */\n field: string;\n /** Label to print in the key column. */\n label: string;\n /** The raw property value, for kinds that need it (image `src`, link `href`). */\n value: unknown;\n /** The formatted value text. */\n text: string;\n /** How the renderer should draw the value. */\n kind: PopupFieldKind;\n /** Link text for a `\"link\"` row; falls back to the value. */\n linkLabel?: string;\n}\n\n/**\n * A trimmed non-empty string, or `undefined` for anything else.\n *\n * Popup configs arrive from untrusted JSON — a hand-edited `.geolibre.json`, an\n * imported layer-library bundle, an MCP-authored project — so a field typed as\n * `string` may hold a number, an object or null. `value.trim()` on one of those\n * throws and takes the whole popup render with it, so every string read here\n * goes through this.\n */\nfunction trimmedString(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const trimmed = value.trim();\n return trimmed === \"\" ? undefined : trimmed;\n}\n\n/** Whether the Identify popup should open for a layer. Default: yes. */\nexport function isPopupClickEnabled(popup: LayerPopupConfig | undefined): boolean {\n return popup?.click !== false;\n}\n\n/** Whether a hover tooltip should follow the pointer. Default: no. */\nexport function isPopupHoverEnabled(popup: LayerPopupConfig | undefined): boolean {\n return popup?.hover === true;\n}\n\n/**\n * Bounds for {@link LayerPopupConfig.maxWidth}, in CSS pixels. The floor is\n * the popup's own minimum width (`min-w-[18rem]`), below which the setting\n * would be overruled by the layout and read as broken; the ceiling keeps a\n * hand-edited project from authoring a popup that blankets the map.\n */\nexport const POPUP_MAX_WIDTH_RANGE = { min: 288, max: 1200 } as const;\n\n/**\n * Bounds for {@link LayerPopupConfig.imageHeight}, in CSS pixels. Below the\n * floor a thumbnail carries no information; the ceiling matches\n * {@link POPUP_MAX_WIDTH_RANGE}, since a taller picture than that is what the\n * lightbox is for.\n */\nexport const POPUP_IMAGE_HEIGHT_RANGE = { min: 40, max: 1200 } as const;\n\n/**\n * Coerce an author-supplied pixel size to a usable number, or `undefined`.\n *\n * Popup configs arrive from untrusted JSON, so the value may be a string, null\n * or NaN. Anything that is not a finite positive number means \"unset\" (keep\n * the default) rather than an error that would take the popup render down,\n * and a number outside `range` is clamped rather than dropped — a project\n * asking for a 4000px popup wants the widest one it can have.\n */\nfunction pixelSize(\n value: unknown,\n range: { readonly min: number; readonly max: number },\n): number | undefined {\n const size = typeof value === \"number\" ? value : Number(trimmedString(value) ?? NaN);\n if (!Number.isFinite(size) || size <= 0) return undefined;\n return Math.round(Math.max(range.min, Math.min(range.max, size)));\n}\n\n/**\n * The widest the click popup may draw for a layer, in CSS pixels, or\n * `undefined` to keep the renderer's default.\n */\nexport function resolvePopupMaxWidth(popup: LayerPopupConfig | undefined): number | undefined {\n return pixelSize(popup?.maxWidth, POPUP_MAX_WIDTH_RANGE);\n}\n\n/**\n * The tallest an `\"image\"` field may draw inside the popup, in CSS pixels, or\n * `undefined` to keep the stylesheet's default.\n */\nexport function resolvePopupImageHeight(popup: LayerPopupConfig | undefined): number | undefined {\n return pixelSize(popup?.imageHeight, POPUP_IMAGE_HEIGHT_RANGE);\n}\n\n/** The label a popup shows for a configured field. */\nexport function popupFieldLabel(config: PopupFieldConfig): string {\n return trimmedString(config.label) ?? config.field;\n}\n\n/**\n * Property keys that never belong in a popup: GeoLibre's own internal columns\n * and the full-resolution companion of a photo thumbnail (an internal twin of\n * a value already shown, and often multiple megabytes of data URL).\n */\nexport function isInternalPopupField(key: string): boolean {\n return key === PHOTO_FULL_PROPERTY || key.startsWith(\"__geolibre_\");\n}\n\n/**\n * The property keys a popup may show, before any popup config narrows them:\n * the feature's own keys minus the internal ones and anything the author\n * marked `\"hidden\"` or `\"excluded\"`.\n */\nexport function visiblePopupFields(\n properties: Record<string, unknown>,\n fieldVisibility?: Record<string, FieldVisibility>,\n): string[] {\n return Object.keys(properties).filter(\n (key) => !isInternalPopupField(key) && !fieldVisibility?.[key],\n );\n}\n\n/** Options for {@link formatPopupValue} and {@link resolvePopupRows}. */\nexport interface PopupFormatOptions {\n /**\n * BCP 47 tag for number/date formatting. Omitted means the runtime default,\n * which is what the browser uses for every other locale-aware rendering.\n */\n locale?: string;\n}\n\nfunction formatNumber(value: number, format: PopupFieldFormat, locale?: string): string {\n const options: Intl.NumberFormatOptions = {\n useGrouping: format.thousands === true,\n };\n if (format.decimals != null && Number.isFinite(format.decimals)) {\n const digits = Math.max(0, Math.min(20, Math.trunc(format.decimals)));\n options.minimumFractionDigits = digits;\n options.maximumFractionDigits = digits;\n }\n return new Intl.NumberFormat(locale, options).format(value);\n}\n\n/**\n * Coerce a property value to a Date. A number is epoch milliseconds (what a\n * GeoJSON timestamp column normally holds); a string goes through `Date`'s own\n * parser. Anything that does not parse comes back as `null` so the caller can\n * fall back to the raw text rather than printing \"Invalid Date\".\n */\nfunction toDate(value: unknown): Date | null {\n if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;\n if (typeof value === \"number\" && Number.isFinite(value)) {\n const fromEpoch = new Date(value);\n return Number.isNaN(fromEpoch.getTime()) ? null : fromEpoch;\n }\n if (typeof value === \"string\" && value.trim() !== \"\") {\n const parsed = new Date(value);\n return Number.isNaN(parsed.getTime()) ? null : parsed;\n }\n return null;\n}\n\nfunction formatDate(date: Date, dateFormat: PopupDateFormat, locale?: string): string {\n switch (dateFormat) {\n case \"datetime\":\n return date.toLocaleString(locale);\n case \"time\":\n return date.toLocaleTimeString(locale);\n case \"iso\":\n return date.toISOString();\n case \"year\":\n // Local, like the date/datetime/time cases above — only `iso` is\n // deliberately UTC, because that is what the format itself means. Reading\n // the year in UTC while the neighbouring formats read local would make\n // \"Year\" disagree with \"Date\" for the same timestamp near a year\n // boundary (2026-01-01T02:00:00Z is Dec 31 2025 in UTC-8).\n return String(date.getFullYear());\n case \"date\":\n default:\n return date.toLocaleDateString(locale);\n }\n}\n\n/** Stringify a value the way the untyped popup always has. */\nexport function stringifyPopupValue(value: unknown): string {\n if (value == null) return \"\";\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n}\n\n/**\n * Render one value as text under a field's kind and format. `\"auto\"` and\n * `\"image\"` values are returned verbatim (the renderer draws them itself);\n * every other kind is stringified, formatted, then wrapped in the configured\n * prefix/suffix.\n */\nexport function formatPopupValue(\n value: unknown,\n config: Pick<PopupFieldConfig, \"kind\" | \"format\"> = {},\n options: PopupFormatOptions = {},\n): string {\n const kind = config.kind ?? \"auto\";\n const format = config.format ?? {};\n if (kind === \"auto\" || kind === \"image\") return stringifyPopupValue(value);\n\n // A missing value stays missing under every kind. Without this a `number`\n // field would coerce null (and \"\") through `Number(\"\")` to a confident 0,\n // reporting a population of zero for a city that simply has no figure.\n if (value == null || (typeof value === \"string\" && value.trim() === \"\")) return \"\";\n\n let body: string;\n if (kind === \"number\") {\n const numeric = typeof value === \"number\" ? value : Number(stringifyPopupValue(value));\n body = Number.isFinite(numeric)\n ? formatNumber(numeric, format, options.locale)\n : stringifyPopupValue(value);\n } else if (kind === \"date\") {\n const date = toDate(value);\n body = date\n ? formatDate(date, format.dateFormat ?? \"date\", options.locale)\n : stringifyPopupValue(value);\n } else {\n body = stringifyPopupValue(value);\n }\n\n // An empty value gets no affixes: \"$\" alone, or a bare \" km\", reads as data\n // that is present when it is not.\n if (body === \"\") return \"\";\n return `${format.prefix ?? \"\"}${body}${format.suffix ?? \"\"}`;\n}\n\n/** Whether a row would draw as a picture rather than as text. */\nfunction rendersAsImage(kind: PopupFieldKind, value: unknown): boolean {\n return kind === \"image\" || (kind === \"auto\" && isInlineImageValue(value));\n}\n\n/**\n * The properties a popup may read, with the author's hidden and excluded\n * fields and GeoLibre's internal columns removed.\n *\n * Expressions get this rather than the raw record. `[\"get\", \"ssn\"]` in a title\n * or body would otherwise reach a column the author hid, printing it into the\n * popup — and onto the hover tooltip, which needs no click at all. The rest of\n * this module already honors `fieldVisibility` field by field; an expression\n * has no field list to filter, so the filtering has to happen to its input.\n */\nexport function visiblePopupProperties(\n properties: Record<string, unknown>,\n fieldVisibility?: Record<string, FieldVisibility>,\n): Record<string, unknown> {\n const visible: Record<string, unknown> = {};\n for (const key of visiblePopupFields(properties, fieldVisibility)) {\n visible[key] = properties[key];\n }\n return visible;\n}\n\n/** Options for {@link resolvePopupRows}. */\nexport interface ResolvePopupRowsOptions extends PopupFormatOptions {\n popup?: LayerPopupConfig;\n fieldVisibility?: Record<string, FieldVisibility>;\n /** Resolve the hover subset (fields flagged `hover`) instead of the full list. */\n hover?: boolean;\n}\n\n/**\n * The rows a popup (or hover tooltip) should render for one feature's\n * properties.\n *\n * With no `popup.fields`, every visible property is returned in the feature's\n * own key order and left untyped, which is exactly what the popup did before\n * the designer existed. With a list, only the listed fields appear, in the\n * listed order, under their labels and formats — and a listed field the\n * feature does not carry is skipped rather than printed empty, so one config\n * can cover a layer whose features have ragged properties.\n *\n * `hover: true` narrows the list to fields flagged `hover`. A hover request\n * with no flagged field returns nothing: an author who turned the tooltip on\n * without choosing a field gets no tip rather than the whole table following\n * the pointer.\n */\nexport function resolvePopupRows(\n properties: Record<string, unknown>,\n options: ResolvePopupRowsOptions = {},\n): PopupRow[] {\n const { popup, fieldVisibility, hover = false, locale } = options;\n // Untrusted JSON again: `fields` may be absent, a non-array, or hold entries\n // whose `field` is not a string. Anything unusable is dropped here so the\n // loop below can treat every entry as a real field name.\n const configured = (Array.isArray(popup?.fields) ? popup.fields : []).filter(\n (config): config is PopupFieldConfig =>\n Boolean(config) && typeof (config as PopupFieldConfig).field === \"string\",\n );\n\n if (configured.length === 0) {\n if (hover) return [];\n return visiblePopupFields(properties, fieldVisibility).map((field) => ({\n field,\n label: field,\n value: properties[field],\n text: stringifyPopupValue(properties[field]),\n kind: \"auto\" as const,\n }));\n }\n\n const rows: PopupRow[] = [];\n const seen = new Set<string>();\n for (const config of configured) {\n if (hover && config.hover !== true) continue;\n // fieldVisibility wins: a popup config can order and relabel what is\n // visible, it can never re-expose a field the author hid or excluded.\n if (fieldVisibility?.[config.field]) continue;\n if (isInternalPopupField(config.field)) continue;\n if (!(config.field in properties)) continue;\n if (seen.has(config.field)) continue;\n const value = properties[config.field];\n // A hover tip is a glance, and it re-renders on every animation frame the\n // pointer is over the feature. An image row has no useful text form — its\n // value is a multi-kilobyte data URL — so it would print that URL as the\n // tip's body, over and over. Skip it and let the click popup show the\n // picture.\n if (hover && rendersAsImage(config.kind ?? \"auto\", value)) continue;\n seen.add(config.field);\n rows.push({\n field: config.field,\n label: popupFieldLabel(config),\n value,\n text: formatPopupValue(value, config, { locale }),\n kind: config.kind ?? \"auto\",\n ...(trimmedString(config.format?.linkLabel)\n ? { linkLabel: trimmedString(config.format?.linkLabel) }\n : {}),\n });\n }\n return rows;\n}\n\n/**\n * Compiled title/body expressions, keyed by zoom and source.\n *\n * `createHoverTooltipElement` calls {@link resolvePopupTitle} once per\n * animation frame for as long as the pointer sits on a hovered feature, and\n * compiling means a `JSON.parse` plus a full style-spec validation. Without\n * this the same unchanged source string would be re-parsed sixty times a\n * second. Zoom is part of the key because a compiled expression bakes it in\n * for `[\"zoom\"]`; it is constant through a hover, so the cache still hits.\n */\nconst EXPRESSION_CACHE = new Map<string, CompiledFeatureExpression>();\n/** Bound on the cache, so a project full of layers cannot grow it without end. */\nconst EXPRESSION_CACHE_LIMIT = 64;\n\nfunction compiledPopupExpression(source: string, zoom: number): CompiledFeatureExpression {\n const key = `${zoom}\\u0000${source}`;\n const cached = EXPRESSION_CACHE.get(key);\n if (cached) return cached;\n const compiled = compileFeatureExpression(source, { zoom });\n // Map iterates in insertion order, so the first key is the oldest entry.\n if (EXPRESSION_CACHE.size >= EXPRESSION_CACHE_LIMIT) {\n const oldest = EXPRESSION_CACHE.keys().next().value;\n if (oldest !== undefined) EXPRESSION_CACHE.delete(oldest);\n }\n EXPRESSION_CACHE.set(key, compiled);\n return compiled;\n}\n\n/**\n * Evaluate a popup expression against a feature's properties, or return\n * `undefined` when it does not compile or throws. Both failures mean the same\n * thing to every caller here: fall back to what the popup would show without\n * the expression.\n */\nfunction evaluatePopupExpression(\n source: string,\n properties: Record<string, unknown>,\n options: PopupExpressionOptions,\n): unknown {\n const compiled = compiledPopupExpression(source, options.zoom ?? 0);\n if (!compiled.ok || !compiled.evaluate) return undefined;\n try {\n return compiled.evaluate(\n featureFor(visiblePopupProperties(properties, options.fieldVisibility), options.feature),\n );\n } catch {\n return undefined;\n }\n}\n\n/** Build the synthetic feature an expression is evaluated against. */\nfunction featureFor(properties: Record<string, unknown>, feature?: Feature | null): Feature {\n if (feature) return { ...feature, properties };\n return { type: \"Feature\", geometry: null as never, properties };\n}\n\n/** Options for {@link resolvePopupTitle} and {@link resolvePopupBody}. */\nexport interface PopupExpressionOptions {\n /**\n * The author's field visibility, so an expression cannot read a hidden or\n * excluded column. Omitting it means nothing is hidden.\n */\n fieldVisibility?: Record<string, FieldVisibility>;\n /** The real feature, when the caller has one — feeds `[\"geometry-type\"]`. */\n feature?: Feature | null;\n /** Map zoom for `[\"zoom\"]`; defaults to 0. */\n zoom?: number;\n}\n\n/**\n * The heading a popup should show: the title expression's value, else the\n * title field's value, else the layer name. An expression that fails to\n * compile or evaluate, and a title field that is empty or hidden, both fall\n * back to the layer name rather than leaving the popup headless.\n */\nexport function resolvePopupTitle(\n layerName: string,\n properties: Record<string, unknown>,\n popup: LayerPopupConfig | undefined,\n options: PopupExpressionOptions = {},\n): string {\n return resolveConfiguredPopupTitle(properties, popup, options) ?? layerName;\n}\n\n/**\n * The title the author's own configuration produced, or `null` when there is\n * none — no title field or expression, or one that evaluated to nothing, or a\n * field the author hid.\n *\n * {@link resolvePopupTitle} is this plus the layer-name fallback. The hover\n * tooltip needs the two apart: a tip with no fields and no configured title\n * would be a box repeating the layer name, so it is suppressed — but that\n * decision has to rest on whether a title was *configured*, not on whether the\n * resolved text happens to equal the layer name. A city layer named \"Olympia\"\n * whose feature is also called \"Olympia\" is a real title, not a fallback.\n */\nexport function resolveConfiguredPopupTitle(\n properties: Record<string, unknown>,\n popup: LayerPopupConfig | undefined,\n options: PopupExpressionOptions = {},\n): string | null {\n const source = trimmedString(popup?.titleExpression);\n if (source) {\n const text = stringifyPopupValue(evaluatePopupExpression(source, properties, options)).trim();\n if (text) return text;\n }\n const field = trimmedString(popup?.titleField);\n if (field && !options.fieldVisibility?.[field] && !isInternalPopupField(field)) {\n const text = stringifyPopupValue(properties[field]).trim();\n if (text) return text;\n }\n return null;\n}\n\n/**\n * The popup body text when the author supplied a body expression, or `null`\n * when there is none (or it failed) and the field rows should render instead.\n */\nexport function resolvePopupBody(\n properties: Record<string, unknown>,\n popup: LayerPopupConfig | undefined,\n options: PopupExpressionOptions = {},\n): string | null {\n const source = trimmedString(popup?.bodyExpression);\n if (!source) return null;\n const preview = evaluatePopupExpression(source, properties, options);\n if (preview === undefined) return null;\n const text = stringifyPopupValue(preview);\n return text.trim() ? text : null;\n}\n\n/**\n * Whether a value should render as an inline image thumbnail under `\"auto\"`.\n * Kept here so the renderer and the designer's preview agree on what counts.\n */\nexport function isInlineImageValue(value: unknown): value is string {\n return typeof value === \"string\" && INLINE_IMAGE_DATA_URL.test(value);\n}\n\n/**\n * Whether a URL is safe to put in a popup `href`/`src`. Only http(s) and\n * inline raster data URLs: a `javascript:` or `data:text/html` value coming\n * from an untrusted GeoJSON must never become a live link.\n */\nexport function isSafePopupUrl(value: unknown, allowDataImage = false): value is string {\n if (typeof value !== \"string\") return false;\n const trimmed = value.trim();\n if (/^https?:\\/\\//i.test(trimmed)) return true;\n return allowDataImage && INLINE_IMAGE_DATA_URL.test(trimmed);\n}\n","import type { GeoLibreLayer, LayerQuickFilter } from \"./types\";\n\n/**\n * Quick filters (issue #2114): per-layer, data-driven filter controls that\n * compile down to the MapLibre filter a layer already supports.\n *\n * The persisted record is the *control state* (field, kind, chosen values),\n * never the compiled expression. Storing the compiled output would make a\n * filter unreadable and un-editable the moment it is saved, which is exactly\n * the wall this feature exists to remove: a rule filter, Select by Expression,\n * and the attribute table's filter box all already accept a hand-written\n * MapLibre expression.\n *\n * Compilation happens at sync time in `@geolibre/map`'s layer sync, which\n * combines the result with the transient `timeFilter`, the embed API's\n * `embedFilter`, and the rule-based visibility filter under one `[\"all\", …]`,\n * so a host page's filter and a user's filter narrow the layer together\n * instead of clobbering each other.\n *\n * Every clause reads the property in place, so the same expression is correct\n * for a GeoJSON source and for a tile-backed source whose tiles decode later.\n */\n\n/** Day length in milliseconds, used to make a date filter's end day inclusive. */\nconst DAY_MS = 86_400_000;\n\n/** The `YYYY-MM-DD` prefix length every ISO date comparison slices to. */\nconst ISO_DAY_LENGTH = 10;\n\n/** Whether a filter is switched on (the field defaults to enabled). */\nfunction isEnabled(filter: LayerQuickFilter): boolean {\n return filter.enabled !== false;\n}\n\n/** A validated date bound: its canonical `YYYY-MM-DD` text and UTC midnight. */\ninterface IsoDayBound {\n day: string;\n ms: number;\n}\n\n/**\n * Validate a `YYYY-MM-DD` bound, returning its canonical text and UTC midnight,\n * or `null` when the text is blank, malformed, or not a real calendar day.\n *\n * The round-trip check is what rejects a day that does not exist: `Date.parse`\n * happily reads `2026-02-30` as March 2, so shape-checking alone would compile\n * a bound the user never chose. Rejecting here means a half-typed or impossible\n * bound compiles to no clause at all, leaving that side unconstrained rather\n * than filtering on a date nobody asked for.\n */\nfunction parseIsoDayBound(day: string | null | undefined): IsoDayBound | null {\n if (typeof day !== \"string\") return null;\n const trimmed = day.trim();\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(trimmed)) return null;\n const ms = Date.parse(`${trimmed}T00:00:00Z`);\n if (!Number.isFinite(ms)) return null;\n return new Date(ms).toISOString().slice(0, ISO_DAY_LENGTH) === trimmed\n ? { day: trimmed, ms }\n : null;\n}\n\n/** A finite number, or `null` for every other input (including `NaN`). */\nfunction finite(value: number | null | undefined): number | null {\n return typeof value === \"number\" && Number.isFinite(value) ? value : null;\n}\n\n/**\n * `[\"get\", field]` coerced for comparison, guarded by `[\"has\", field]` where a\n * missing property would otherwise coerce into range (`to-number` turns a\n * missing property into `0`, and `to-string` into `\"\"`, either of which can sit\n * inside a one-sided bound).\n */\nfunction presenceGuard(field: string): unknown[] {\n return [\"has\", field];\n}\n\nfunction compileCategorical(filter: LayerQuickFilter): unknown[] | null {\n const values = filter.values ?? [];\n if (values.length === 0) return null;\n // `in` keeps the values at their original type, so a numeric code and its\n // string spelling never collapse into one another the way `match` labels do.\n return [\"in\", [\"get\", filter.field], [\"literal\", values]];\n}\n\nfunction compileRange(filter: LayerQuickFilter): unknown[] | null {\n const first = finite(filter.min);\n const second = finite(filter.max);\n if (first === null && second === null) return null;\n // Order the bounds rather than trusting which box they were typed into. The\n // slider's thumbs cannot cross, but the two number inputs commit\n // independently, so a bound is briefly inverted on the way to any value whose\n // first digit is smaller (typing `200000` into a max already showing `800000`\n // passes through `2`). Compiling that literally yields an unsatisfiable\n // filter, which empties the layer with nothing on screen to explain why.\n const min = first !== null && second !== null ? Math.min(first, second) : first;\n const max = first !== null && second !== null ? Math.max(first, second) : second;\n const value = [\"to-number\", [\"get\", filter.field]];\n const clauses: unknown[] = [presenceGuard(filter.field)];\n if (min !== null) clauses.push([\">=\", value, min]);\n if (max !== null) clauses.push([\"<=\", value, max]);\n return [\"all\", ...clauses];\n}\n\nfunction compileDate(filter: LayerQuickFilter): unknown[] | null {\n // Both branches read the *validated* bounds, never the raw strings: with one\n // bound valid and the other malformed the filter still compiles, and pushing\n // the raw text would compare against a value no real date can satisfy — which\n // hides every feature instead of leaving that side open.\n const first = parseIsoDayBound(filter.start);\n const second = parseIsoDayBound(filter.end);\n if (first === null && second === null) return null;\n // Ordered for the same reason as `compileRange`: a date typed into the wrong\n // box (or a from/to pair edited out of order) should read as the interval\n // between them, not as an empty layer.\n const inverted = first !== null && second !== null && first.ms > second.ms;\n const start = inverted ? second : first;\n const end = inverted ? first : second;\n const clauses: unknown[] = [presenceGuard(filter.field)];\n\n if ((filter.dateKind ?? \"iso\") === \"iso\") {\n // Compare the leading `YYYY-MM-DD` slice on both sides: ISO text sorts\n // chronologically, and slicing makes a datetime comparable to a plain day\n // bound without parsing. Both bounds are inclusive because the slice drops\n // the time of day.\n const value = [\"slice\", [\"to-string\", [\"get\", filter.field]], 0, ISO_DAY_LENGTH];\n if (start !== null) clauses.push([\">=\", value, start.day]);\n if (end !== null) clauses.push([\"<=\", value, end.day]);\n return [\"all\", ...clauses];\n }\n\n // Epoch numbers: the end bound is exclusive at the *next* day's midnight, so\n // the chosen end day is kept in full rather than only its midnight instant.\n const scale = filter.dateKind === \"epochS\" ? 0.001 : 1;\n const value = [\"to-number\", [\"get\", filter.field]];\n if (start !== null) clauses.push([\">=\", value, start.ms * scale]);\n if (end !== null) clauses.push([\"<\", value, (end.ms + DAY_MS) * scale]);\n return [\"all\", ...clauses];\n}\n\nfunction compileText(filter: LayerQuickFilter): unknown[] | null {\n const needle = filter.text?.trim().toLowerCase() ?? \"\";\n if (needle === \"\") return null;\n // Case-insensitive on both sides: a viewer typing \"portland\" should not have\n // to guess how the data capitalizes it.\n const haystack = [\"downcase\", [\"to-string\", [\"get\", filter.field]]];\n switch (filter.operator ?? \"contains\") {\n case \"equals\":\n return [\"==\", haystack, needle];\n case \"startsWith\":\n return [\"==\", [\"index-of\", needle, haystack], 0];\n default:\n return [\"!=\", [\"index-of\", needle, haystack], -1];\n }\n}\n\n/**\n * Compile one quick filter to a MapLibre filter expression, or `null` when it\n * places no constraint (switched off, no field, or nothing chosen yet). A\n * control that has been added but not yet answered must never hide features.\n *\n * @param filter - The persisted control state.\n * @returns A MapLibre filter expression, or `null`.\n */\nexport function compileQuickFilter(filter: LayerQuickFilter): unknown[] | null {\n if (!isEnabled(filter) || typeof filter.field !== \"string\" || filter.field === \"\") return null;\n switch (filter.kind) {\n case \"categorical\":\n return compileCategorical(filter);\n case \"range\":\n return compileRange(filter);\n case \"date\":\n return compileDate(filter);\n case \"text\":\n return compileText(filter);\n default:\n return null;\n }\n}\n\n/**\n * Compile a layer's quick filters into a single MapLibre filter expression.\n * Multiple controls narrow the data together (`[\"all\", …]`), matching how a\n * viewer reads a stack of filters. Returns `null` when no control constrains\n * anything, so the common path adds no filter at all.\n *\n * @param filters - The layer's persisted quick filters.\n * @returns A MapLibre filter expression, or `null`.\n */\nexport function compileQuickFilters(\n filters: readonly LayerQuickFilter[] | undefined,\n): unknown[] | null {\n if (!filters || filters.length === 0) return null;\n const clauses: unknown[] = [];\n for (const filter of filters) {\n const compiled = compileQuickFilter(filter);\n if (compiled) clauses.push(compiled);\n }\n if (clauses.length === 0) return null;\n return clauses.length === 1 ? (clauses[0] as unknown[]) : [\"all\", ...clauses];\n}\n\n/**\n * The quick filters that actually constrain the layer right now. Drives the\n * \"filtered\" badge on the layer row and its count, so an active filter is\n * never mistaken for missing data.\n *\n * @param layer - The layer to inspect.\n * @returns The constraining filters, in their configured order.\n */\nexport function activeQuickFilters(layer: GeoLibreLayer): LayerQuickFilter[] {\n return (layer.quickFilters ?? []).filter((filter) => compileQuickFilter(filter) !== null);\n}\n\n/**\n * Whether the layer is currently narrowed by at least one quick filter.\n *\n * @param layer - The layer to inspect.\n * @returns `true` when a quick filter constrains the rendered features.\n */\nexport function hasActiveQuickFilter(layer: GeoLibreLayer): boolean {\n return (layer.quickFilters ?? []).some((filter) => compileQuickFilter(filter) !== null);\n}\n\n/**\n * Clear every chosen value while keeping the controls themselves, so \"Clear\n * filters\" is one action that leaves the layer's configured controls in place\n * for the next question.\n *\n * @param filters - The layer's persisted quick filters.\n * @returns The same controls with their selections emptied.\n */\nexport function clearQuickFilterValues(\n filters: readonly LayerQuickFilter[] | undefined,\n): LayerQuickFilter[] {\n return (filters ?? []).map((filter) => ({\n ...filter,\n values: filter.kind === \"categorical\" ? [] : filter.values,\n min: filter.kind === \"range\" ? null : filter.min,\n max: filter.kind === \"range\" ? null : filter.max,\n start: filter.kind === \"date\" ? null : filter.start,\n end: filter.kind === \"date\" ? null : filter.end,\n text: filter.kind === \"text\" ? \"\" : filter.text,\n }));\n}\n","import type { LayerLevelFilter, LevelFilterMode } from \"./types\";\n\n/**\n * Floor picker (issue #3341): the levels a multi-floor layer holds, and the\n * filter that draws one floor (or one floor and those below it).\n *\n * Indoor data stores the floor in many shapes. OpenStreetMap's `level` tag is\n * text and may name several floors (`\"0;1\"`) or a span (`\"-1-2\"`); IMDF stores\n * a numeric `ordinal`; other exports use a plain number. Rather than compile a\n * MapLibre expression that tries to parse all of these (string splitting has\n * no expression operator, and `to-number` throws on `\"0;1\"`), the picker\n * resolves the choice against the layer's features once and stores the raw\n * values that match. The compiled filter is then a plain `in` test, which\n * MapLibre, Mapbox, Cesium and ArcGIS all evaluate with the same style-spec\n * code.\n */\n\n/** One floor as the picker lists it. */\nexport interface LevelEntry {\n /** Canonical label: a number's shortest form (`\"1\"` for `\"01\"`), or the trimmed text. */\n label: string;\n /** The floor's number, or `null` for a named floor such as `\"G\"`. */\n ordinal: number | null;\n /** How many features sit on this floor (a multi-level feature counts once per floor). */\n count: number;\n}\n\n/** A feature as far as level parsing needs it. */\ninterface LevelFeature {\n properties?: Record<string, unknown> | null;\n}\n\n/** Field names that usually hold a floor, in preference order (compared case-insensitively). */\nconst LEVEL_FIELD_CANDIDATES = [\n \"level\",\n \"ordinal\",\n \"floor\",\n \"level_ordinal\",\n \"floor_number\",\n \"floornumber\",\n \"floor_no\",\n \"storey\",\n \"story\",\n \"etage\",\n \"level_name\",\n];\n\nconst NUMBER = /^-?\\d+(?:\\.\\d+)?$/;\nconst RANGE = /^(-?\\d+(?:\\.\\d+)?)\\s*-\\s*(-?\\d+(?:\\.\\d+)?)$/;\n/** A range wider than this is read as its two ends, not every floor between. */\nconst MAX_RANGE_STEPS = 200;\n\n/** A parsed floor token: its canonical label and number. */\ninterface LevelToken {\n label: string;\n ordinal: number | null;\n}\n\nfunction numericToken(value: number): LevelToken {\n return { label: String(value), ordinal: value };\n}\n\n/**\n * Parse one attribute value into the floors it names.\n *\n * Numbers are taken as they are. Text is split on `;` (OSM's list separator),\n * each part read as a number, an integer span such as `0-2` or `-2--1`\n * (expanded to every floor it covers), or kept as a named floor.\n *\n * @param raw - The attribute value.\n * @returns The floors it names, possibly empty (for `null`, booleans, blanks).\n */\nexport function parseLevelValue(raw: unknown): LevelToken[] {\n if (typeof raw === \"number\") return Number.isFinite(raw) ? [numericToken(raw)] : [];\n if (typeof raw !== \"string\") return [];\n const tokens: LevelToken[] = [];\n for (const part of raw.split(\";\")) {\n const text = part.trim();\n if (!text) continue;\n if (NUMBER.test(text)) {\n tokens.push(numericToken(Number(text)));\n continue;\n }\n const range = RANGE.exec(text);\n if (range) {\n const a = Number(range[1]);\n const b = Number(range[2]);\n const lo = Math.min(a, b);\n const hi = Math.max(a, b);\n // Safe integers only: past 2^53, `level += 1` no longer advances.\n if (Number.isSafeInteger(lo) && Number.isSafeInteger(hi) && hi - lo <= MAX_RANGE_STEPS) {\n for (let level = lo; level <= hi; level += 1) tokens.push(numericToken(level));\n } else {\n tokens.push(numericToken(lo), numericToken(hi));\n }\n continue;\n }\n tokens.push({ label: text, ordinal: null });\n }\n return tokens;\n}\n\n/** The ordinal of a picker label: its number, or `null` for a named floor. */\nfunction labelOrdinal(label: string): number | null {\n return NUMBER.test(label.trim()) ? Number(label) : null;\n}\n\n/**\n * Whether a feature's level value falls on the chosen floor.\n *\n * @param raw - The feature's level attribute.\n * @param level - The chosen floor's label.\n * @param mode - `only` matches that floor; `below` also matches every\n * numbered floor under it (a named floor matches only itself). `all`\n * matches everything.\n * @returns True when the feature should be drawn.\n */\nexport function levelValueMatches(raw: unknown, level: string, mode: LevelFilterMode): boolean {\n if (mode === \"all\") return true;\n const tokens = parseLevelValue(raw);\n const target = labelOrdinal(level);\n const label = target === null ? level.trim() : String(target);\n return tokens.some((token) => {\n if (token.label === label) return true;\n return mode === \"below\" && target !== null && token.ordinal !== null && token.ordinal <= target;\n });\n}\n\n/**\n * Order floors top down, the way a building directory or lift panel lists\n * them: numbered floors from highest to lowest, then named floors by name.\n */\nfunction compareLevels(a: LevelEntry, b: LevelEntry): number {\n if (a.ordinal !== null && b.ordinal !== null) return b.ordinal - a.ordinal;\n if (a.ordinal !== null) return -1;\n if (b.ordinal !== null) return 1;\n return a.label.localeCompare(b.label);\n}\n\n/**\n * The distinct floors a set of features holds in one field, top floor first.\n *\n * @param features - The layer's features.\n * @param field - The property holding the floor.\n * @returns One entry per floor with its feature count.\n */\nexport function collectLevels(\n features: readonly LevelFeature[] | null | undefined,\n field: string,\n): LevelEntry[] {\n const byLabel = new Map<string, LevelEntry>();\n for (const feature of features ?? []) {\n const seen = new Set<string>();\n for (const token of parseLevelValue(feature?.properties?.[field])) {\n if (seen.has(token.label)) continue;\n seen.add(token.label);\n const entry = byLabel.get(token.label);\n if (entry) entry.count += 1;\n else byLabel.set(token.label, { label: token.label, ordinal: token.ordinal, count: 1 });\n }\n }\n return [...byLabel.values()].sort(compareLevels);\n}\n\n/**\n * Merge the floor lists of several layers into one, top floor first, summing\n * the counts of a floor that appears in more than one layer.\n *\n * @param lists - One {@link collectLevels} result per linked layer.\n * @returns The combined floor list.\n */\nexport function mergeLevels(lists: readonly (readonly LevelEntry[])[]): LevelEntry[] {\n const byLabel = new Map<string, LevelEntry>();\n for (const list of lists) {\n for (const entry of list) {\n const existing = byLabel.get(entry.label);\n if (existing) existing.count += entry.count;\n else byLabel.set(entry.label, { ...entry });\n }\n }\n return [...byLabel.values()].sort(compareLevels);\n}\n\n/**\n * The distinct raw values of `field` that match a floor choice, in first-seen\n * order. These are what {@link compileLevelFilter} tests with `in`.\n *\n * @param features - The layer's features.\n * @param field - The property holding the floor.\n * @param level - The chosen floor's label.\n * @param mode - The floor picker mode.\n * @returns The matching values (strings and numbers kept as stored).\n */\nexport function matchingLevelValues(\n features: readonly LevelFeature[] | null | undefined,\n field: string,\n level: string,\n mode: LevelFilterMode,\n): (string | number)[] {\n const values: (string | number)[] = [];\n const checked = new Set<string | number>();\n for (const feature of features ?? []) {\n const raw = feature?.properties?.[field];\n if (typeof raw !== \"string\" && typeof raw !== \"number\") continue;\n if (checked.has(raw)) continue;\n checked.add(raw);\n if (levelValueMatches(raw, level, mode)) values.push(raw);\n }\n return values;\n}\n\n/**\n * Apply a floor choice to a layer's link, resolving the matching values from\n * the layer's features.\n *\n * Without features (a tile-backed layer, or data not loaded yet) the values\n * stay unresolved, which draws every floor, rather than resolving to an empty\n * list that would hide the layer once its data arrives.\n *\n * @param filter - The layer's current link.\n * @param features - The layer's features, or `null`/`undefined` when none are loaded.\n * @param level - The chosen floor's label, or `null` for every floor.\n * @param mode - The floor picker mode.\n * @returns The updated link.\n */\nexport function selectLevel(\n filter: LayerLevelFilter,\n features: readonly LevelFeature[] | null | undefined,\n level: string | null,\n mode: LevelFilterMode,\n): LayerLevelFilter {\n if (level === null || mode === \"all\") {\n return { field: filter.field, mode: \"all\", level: null };\n }\n if (!features) return { field: filter.field, mode, level };\n return {\n field: filter.field,\n mode,\n level,\n values: matchingLevelValues(features, filter.field, level, mode),\n };\n}\n\n/** The floor shown by the picker: a level (`null` for every floor) and a mode. */\nexport interface LevelChoice {\n level: string | null;\n mode: LevelFilterMode;\n}\n\n/** A layer as far as the floor picker reads it. */\ninterface LevelLinkedLayer {\n id: string;\n levelFilter?: LayerLevelFilter;\n geojson?: { features: readonly LevelFeature[] } | null;\n}\n\n/**\n * The floor choice the picker shows: the first linked layer that has a floor\n * chosen, so a layer linked later (still on `all`) does not reset the display.\n *\n * @param linked - The linked layers.\n * @returns The chosen level and mode, or every floor.\n */\nexport function currentLevelChoice(linked: readonly LevelLinkedLayer[]): LevelChoice {\n for (const layer of linked) {\n const link = layer.levelFilter;\n if (link && link.mode && link.mode !== \"all\" && typeof link.level === \"string\") {\n return { level: link.level, mode: link.mode };\n }\n }\n return { level: null, mode: \"all\" };\n}\n\n/**\n * Whether two links store the same choice, independent of key order (a\n * project written by the Python API may order the keys differently).\n *\n * @param a - One link.\n * @param b - The other.\n * @returns True when they draw the same features.\n */\nexport function sameLevelLink(a: LayerLevelFilter, b: LayerLevelFilter): boolean {\n if (a.field !== b.field) return false;\n if ((a.mode ?? \"all\") !== (b.mode ?? \"all\")) return false;\n if ((a.level ?? null) !== (b.level ?? null)) return false;\n const av = a.values;\n const bv = b.values;\n // A hand-edited project can hold anything in `values`; never throw on it.\n if (!Array.isArray(av) || !Array.isArray(bv)) return av === bv;\n return av.length === bv.length && av.every((value, index) => value === bv[index]);\n}\n\n/** Resolved links per feature array and choice, so an unrelated layer edit does not rescan. */\nconst resolvedLinks = new WeakMap<object, Map<string, LayerLevelFilter>>();\n\n/** {@link selectLevel}, memoized on the feature array (replaced whenever the data changes). */\nfunction cachedSelectLevel(\n link: LayerLevelFilter,\n features: readonly LevelFeature[],\n choice: LevelChoice,\n): LayerLevelFilter {\n const key = JSON.stringify([link.field, choice.level, choice.mode]);\n let byChoice = resolvedLinks.get(features);\n if (!byChoice) {\n byChoice = new Map();\n resolvedLinks.set(features, byChoice);\n }\n let next = byChoice.get(key);\n if (!next) {\n next = selectLevel(link, features, choice.level, choice.mode);\n byChoice.set(key, next);\n }\n return next;\n}\n\n/**\n * The link updates that put every linked layer on one floor choice, with the\n * matching values resolved from each layer's own features: a layer linked\n * after a floor was chosen, a project written without the values (the Python\n * API without inline data), or data that changed since the choice was made.\n *\n * @param linked - The linked layers.\n * @param choice - The floor to apply; defaults to {@link currentLevelChoice}.\n * @returns One update per layer whose stored link differs.\n */\nexport function resolveLevelLinks(\n linked: readonly LevelLinkedLayer[],\n choice: LevelChoice = currentLevelChoice(linked),\n): { id: string; levelFilter: LayerLevelFilter }[] {\n const updates: { id: string; levelFilter: LayerLevelFilter }[] = [];\n for (const layer of linked) {\n const link = layer.levelFilter;\n if (!link) continue;\n // Data still loading (a project re-reading its source file): keep the saved\n // link, values included, until the features arrive.\n const features = layer.geojson?.features;\n if (!features) continue;\n const next = cachedSelectLevel(link, features, choice);\n if (!sameLevelLink(link, next)) updates.push({ id: layer.id, levelFilter: next });\n }\n return updates;\n}\n\n/**\n * Whether a link narrows the layer: a floor is chosen and its values resolved.\n *\n * @param filter - The layer's link, if any.\n * @returns True when {@link compileLevelFilter} would return a filter.\n */\nexport function isLevelFilterActive(filter: LayerLevelFilter | null | undefined): boolean {\n return (\n !!filter &&\n typeof filter.field === \"string\" &&\n filter.field.length > 0 &&\n (filter.mode === \"only\" || filter.mode === \"below\") &&\n typeof filter.level === \"string\" &&\n Array.isArray(filter.values)\n );\n}\n\n/**\n * Compile a floor picker link into a MapLibre filter expression.\n *\n * @param filter - The layer's link, if any.\n * @returns An `in` test over the resolved values, or `null` when every floor\n * is drawn (no link, `all` mode, no floor chosen, or values unresolved).\n */\nexport function compileLevelFilter(filter: LayerLevelFilter | null | undefined): unknown[] | null {\n if (!isLevelFilterActive(filter)) return null;\n const values = (filter!.values ?? []).filter(\n (value) => typeof value === \"string\" || (typeof value === \"number\" && Number.isFinite(value)),\n );\n return [\"in\", [\"get\", filter!.field], [\"literal\", values]];\n}\n\n/**\n * The floor a feature is on, as the floor picker labels it: the first floor its\n * level value names that the picker lists (a room spanning floors `0;1` goes\n * to floor 0), or `null` when it names none of them.\n *\n * @param raw - The feature's level attribute.\n * @param levels - The floors the picker lists.\n * @returns The floor's label, or `null`.\n */\nexport function featureLevel(raw: unknown, levels: readonly LevelEntry[]): string | null {\n const listed = new Set(levels.map((entry) => entry.label));\n for (const token of parseLevelValue(raw)) {\n if (listed.has(token.label)) return token.label;\n }\n return null;\n}\n\n/**\n * Guess which field holds the floor.\n *\n * @param fields - The layer's field names.\n * @returns The best candidate, or `null` when none looks like a floor field.\n */\nexport function guessLevelField(fields: readonly string[]): string | null {\n const byLower = new Map<string, string>();\n for (const field of fields) {\n const lower = field.toLowerCase();\n if (!byLower.has(lower)) byLower.set(lower, field);\n }\n for (const candidate of LEVEL_FIELD_CANDIDATES) {\n const field = byLower.get(candidate);\n if (field) return field;\n }\n return null;\n}\n","import { featureFilter } from \"@maplibre/maplibre-gl-style-spec\";\nimport type { FeatureCollection } from \"geojson\";\nimport type { GeoLibreLayer } from \"./types\";\nimport { compileLevelFilter } from \"./level-filter\";\nimport { compileQuickFilters } from \"./quick-filters\";\n\n/** The layer fields needed to inspect project-authored feature filters. */\ntype LayerFilterSource = Pick<GeoLibreLayer, \"filterExpression\" | \"quickFilters\"> &\n Partial<Pick<GeoLibreLayer, \"levelFilter\">>;\n\n/**\n * Return a layer's persisted expression filter when it has a usable array\n * shape, otherwise return null.\n *\n * @param layer - The layer whose expression filter should be read.\n * @returns The active expression filter, or null when none is configured.\n */\nexport function activeLayerFilterExpression(layer: LayerFilterSource): unknown[] | null {\n const expression = layer.filterExpression;\n return Array.isArray(expression) && expression.length > 0 ? expression : null;\n}\n\n/**\n * Compile all project-authored filters for a layer into one MapLibre boolean\n * expression. An explicit expression, Quick Filter controls and the floor\n * picker's choice narrow the layer together.\n *\n * @param layer - The layer whose authored filters should be composed.\n * @returns A MapLibre filter expression, or null when no authored filter is active.\n */\nexport function compileLayerFilters(layer: LayerFilterSource): unknown[] | null {\n const filters = [\n activeLayerFilterExpression(layer),\n compileQuickFilters(layer.quickFilters),\n compileLevelFilter(layer.levelFilter),\n ].filter((filter): filter is unknown[] => filter !== null);\n if (filters.length === 0) return null;\n return filters.length === 1 ? filters[0] : [\"all\", ...filters];\n}\n\n/**\n * Whether a project-authored expression or Quick Filter currently narrows a\n * layer. This drives the filtered indicator and clear action in the layer UI.\n *\n * The floor picker's choice is left out on purpose: the picker is its own\n * on-map control that shows and resets the choice for every linked layer at\n * once, so a per-layer \"clear filters\" action must not desynchronize it.\n * Renderers still apply it through {@link compileLayerFilters}.\n *\n * @param layer - The layer to inspect.\n * @returns True when at least one persisted expression or Quick Filter is active.\n */\nexport function hasActiveLayerFilter(layer: LayerFilterSource): boolean {\n return (\n activeLayerFilterExpression(layer) !== null || compileQuickFilters(layer.quickFilters) !== null\n );\n}\n\n/** MapLibre's feature type codes, which `featureFilter` reads for `$type`. */\nconst FEATURE_TYPE_CODES: Record<string, 1 | 2 | 3> = {\n Point: 1,\n MultiPoint: 1,\n LineString: 2,\n MultiLineString: 2,\n Polygon: 3,\n MultiPolygon: 3,\n};\n\n/** The last filtered result per collection, so an unchanged filter reuses it. */\nconst filteredCollections = new WeakMap<object, { key: string; result: FeatureCollection }>();\n\n/**\n * A layer's features narrowed by its authored filters (an expression, Quick\n * Filters, the floor picker), for a renderer that draws GeoJSON itself rather\n * than through a MapLibre layer filter, such as the deck.gl 3D Z-value path.\n *\n * The same collection comes back while no filter is active, and the same\n * filtered collection while the filter is unchanged, so caches keyed on the\n * collection keep hitting. A filter that does not compile leaves every\n * feature in, as MapLibre would refuse it rather than hide the layer.\n *\n * @param layer - The layer whose filters apply.\n * @param geojson - The features to filter.\n * @param zoom - The zoom a `[\"zoom\"]` expression sees.\n * @returns The matching features.\n */\nexport function applyLayerFilters(\n layer: LayerFilterSource,\n geojson: FeatureCollection,\n zoom = 0,\n): FeatureCollection {\n const filter = compileLayerFilters(layer);\n if (!filter) return geojson;\n const key = `${zoom}|${JSON.stringify(filter)}`;\n const cached = filteredCollections.get(geojson);\n if (cached?.key === key) return cached.result;\n let compiled: ReturnType<typeof featureFilter>;\n try {\n compiled = featureFilter(filter as never, \"layers[0].filter\");\n } catch {\n return geojson;\n }\n const result: FeatureCollection = {\n ...geojson,\n features: geojson.features.filter((feature) =>\n compiled.filter(\n { zoom } as never,\n {\n type: FEATURE_TYPE_CODES[feature.geometry?.type ?? \"\"] ?? 1,\n properties: feature.properties ?? {},\n ...(feature.id !== undefined ? { id: feature.id } : {}),\n } as never,\n ),\n ),\n };\n filteredCollections.set(geojson, { key, result });\n return result;\n}\n","/**\n * List a zip archive's entry paths from its central directory, reading only\n * the end of the archive (a byte-range reader), never the whole file. Shared by\n * the plugin file-drop hints and the IMDF detection (issue #3341), so a large\n * shapefile zip is ruled out without being read.\n */\n\nexport type ReadRange = (offset: number, length: number) => Promise<Uint8Array>;\n\nconst EOCD_SIGNATURE = 0x06054b50;\nconst EOCD_SIZE = 22;\nconst ZIP64_LOCATOR_SIGNATURE = 0x07064b50;\nconst ZIP64_LOCATOR_SIZE = 20;\nconst ZIP64_EOCD_SIGNATURE = 0x06064b50;\nconst ZIP64_EOCD_SIZE = 56;\nconst CENTRAL_ENTRY_SIGNATURE = 0x02014b50;\nconst CENTRAL_ENTRY_SIZE = 46;\n/** General purpose flag bit 11: the entry name is UTF-8 rather than CP437. */\nconst UTF8_NAME_FLAG = 0x0800;\n/** The upper half (0x80-0xff) of code page 437, the zip default for names. */\nconst CP437_HIGH =\n \"ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■\\u00a0\";\n/** Longest possible zip comment plus the record that precedes it. */\nconst MAX_EOCD_SEARCH = 0xffff + EOCD_SIZE;\n/** End-record candidates tried before giving up, so a hostile comment stays cheap. */\nconst MAX_EOCD_CANDIDATES = 16;\nconst UTF8_DECODER = new TextDecoder(\"utf-8\", { ignoreBOM: true });\nconst STRICT_UTF8_DECODER = new TextDecoder(\"utf-8\", { fatal: true, ignoreBOM: true });\n/** A central directory beyond this is not a file a handler should inspect. */\nconst MAX_CENTRAL_DIRECTORY_BYTES = 16 * 1024 * 1024;\n\nfunction viewOf(bytes: Uint8Array): DataView {\n return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n}\n\nfunction decodeCp437(bytes: Uint8Array): string {\n let name = \"\";\n for (const byte of bytes)\n name += byte < 0x80 ? String.fromCharCode(byte) : CP437_HIGH[byte - 0x80];\n return name;\n}\n\n/**\n * Decode an entry name. Flag bit 11 says UTF-8. Without it the zip default is\n * CP437, but some writers (macOS \"Compress\") store UTF-8 without the flag, so\n * a name that is valid UTF-8 is read as such before falling back to CP437.\n */\nfunction decodeEntryName(name: Uint8Array, flags: number): string {\n if (flags & UTF8_NAME_FLAG) return UTF8_DECODER.decode(name);\n if (name.some((byte) => byte >= 0x80)) {\n try {\n return STRICT_UTF8_DECODER.decode(name);\n } catch {\n return decodeCp437(name);\n }\n }\n return decodeCp437(name);\n}\n\n/**\n * List the paths inside a zip from its central directory, reading only the tail\n * of the archive rather than the whole file.\n *\n * Deliberately not listed (the result is undefined, so a handler can still claim\n * by file name): archives with bytes in front of them (self-extracting zips),\n * and spanned or multi-disk archives. The entries are a hint for `claim`, not a full zip\n * parser; a handler that needs these cases unpacks the file itself in `open`.\n *\n * @param size - The archive's size in bytes.\n * @param readRange - Reads a byte range of the archive.\n * @returns The entry paths in archive order, or undefined when the file has no\n * readable central directory (not a zip, or truncated).\n */\nexport async function listZipEntryPaths(\n size: number,\n readRange: ReadRange,\n): Promise<string[] | undefined> {\n if (size < EOCD_SIZE) return undefined;\n const tailLength = Math.min(size, MAX_EOCD_SEARCH);\n const tailOffset = size - tailLength;\n const tail = await readRange(tailOffset, tailLength);\n const tailView = viewOf(tail);\n\n /** Whether a central directory entry starts at `offset`, with `length` bytes in range. */\n const startsDirectory = async (offset: number, length: number): Promise<boolean> => {\n if (length < CENTRAL_ENTRY_SIZE) return false;\n if (offset + length > size) return false;\n // A signature inside the tail already read needs no extra read.\n if (offset >= tailOffset && offset + 4 <= tailOffset + tail.byteLength) {\n return tailView.getUint32(offset - tailOffset, true) === CENTRAL_ENTRY_SIGNATURE;\n }\n const head = await readRange(offset, 4);\n return head.byteLength === 4 && viewOf(head).getUint32(0, true) === CENTRAL_ENTRY_SIGNATURE;\n };\n /** The directory the end record at `eocd` declares, if one really starts there. */\n const checkRecord = async (\n eocd: number,\n ): Promise<[offset: number, length: number] | undefined> => {\n // A spanned archive cannot be unpacked by a handler.\n const disk = tailView.getUint16(eocd + 4, true);\n const directoryDisk = tailView.getUint16(eocd + 6, true);\n if (disk !== 0 || directoryDisk !== 0) return undefined;\n let total = tailView.getUint16(eocd + 10, true);\n // An empty archive's directory \"starts\" where its own end record (ZIP64 or\n // classic) does; a lookalike elsewhere is not one.\n let recordOffset = tailOffset + eocd;\n let length = tailView.getUint32(eocd + 12, true);\n let offset = tailView.getUint32(eocd + 16, true);\n if (length === 0xffffffff || offset === 0xffffffff) {\n const locator = eocd - ZIP64_LOCATOR_SIZE;\n if (locator < 0 || tailView.getUint32(locator, true) !== ZIP64_LOCATOR_SIGNATURE) {\n return undefined;\n }\n // The locator names the disk of the ZIP64 record and the disk count.\n if (tailView.getUint32(locator + 4, true) !== 0) return undefined;\n if (tailView.getUint32(locator + 16, true) > 1) return undefined;\n const zip64Offset = Number(tailView.getBigUint64(locator + 8, true));\n if (zip64Offset + ZIP64_EOCD_SIZE > size) return undefined;\n const zip64Bytes = await readRange(zip64Offset, ZIP64_EOCD_SIZE);\n if (zip64Bytes.byteLength !== ZIP64_EOCD_SIZE) return undefined;\n const zip64 = viewOf(zip64Bytes);\n if (zip64.getUint32(0, true) !== ZIP64_EOCD_SIGNATURE) return undefined;\n if (zip64.getUint32(16, true) !== 0 || zip64.getUint32(20, true) !== 0) return undefined;\n recordOffset = zip64Offset;\n total = Number(zip64.getBigUint64(32, true));\n length = Number(zip64.getBigUint64(40, true));\n offset = Number(zip64.getBigUint64(48, true));\n }\n // An empty archive has no entry to find a signature at.\n if (length === 0) return total === 0 && offset === recordOffset ? [offset, 0] : undefined;\n return (await startsDirectory(offset, length)) ? [offset, length] : undefined;\n };\n\n // Scan back for an end record that points at a real directory: a signature\n // inside the archive comment is only a candidate. The record whose declared\n // comment ends exactly at the end of the file is the real one in a well-formed\n // zip, so it goes first and needs no extra read to be picked.\n const exact: number[] = [];\n const others: number[] = [];\n for (let eocd = tail.byteLength - EOCD_SIZE; eocd >= 0; eocd -= 1) {\n if (tailView.getUint32(eocd, true) !== EOCD_SIGNATURE) continue;\n const ends = eocd + EOCD_SIZE + tailView.getUint16(eocd + 20, true) === tail.byteLength;\n if (ends) exact.push(eocd);\n else others.push(eocd);\n }\n // The scan runs from the end backwards, so reverse the exact records to keep\n // them in ascending offset order, as prepending them used to.\n const candidates = [...exact.reverse(), ...others];\n let located: [offset: number, length: number] | undefined;\n for (const eocd of candidates.slice(0, MAX_EOCD_CANDIDATES)) {\n located = await checkRecord(eocd);\n if (located) break;\n }\n if (!located) return undefined;\n const [directoryOffset, directorySize] = located;\n if (directorySize > MAX_CENTRAL_DIRECTORY_BYTES || directoryOffset + directorySize > size) {\n return undefined;\n }\n\n const directory = await readRange(directoryOffset, directorySize);\n const view = viewOf(directory);\n const paths: string[] = [];\n let cursor = 0;\n while (cursor + CENTRAL_ENTRY_SIZE <= directory.byteLength) {\n if (view.getUint32(cursor, true) !== CENTRAL_ENTRY_SIGNATURE) return undefined;\n const flags = view.getUint16(cursor + 8, true);\n const nameLength = view.getUint16(cursor + 28, true);\n const extraLength = view.getUint16(cursor + 30, true);\n const commentLength = view.getUint16(cursor + 32, true);\n const nameStart = cursor + CENTRAL_ENTRY_SIZE;\n if (nameStart + nameLength > directory.byteLength) return undefined;\n const name = directory.subarray(nameStart, nameStart + nameLength);\n paths.push(decodeEntryName(name, flags));\n cursor = nameStart + nameLength + extraLength + commentLength;\n }\n return paths;\n}\n","import { listZipEntryPaths, type ReadRange } from \"./zip-directory\";\n\n/**\n * Recognizing an IMDF (Indoor Mapping Data Format) archive without unpacking it\n * (issue #3341). One implementation for every entry point: the app's file and\n * drop loaders and the Add Vector panel's picker and URL loads.\n *\n * Only the zip's central directory is read (its tail, through a byte-range\n * reader), so ruling out a large shapefile zip costs a few kilobytes.\n */\n\n/** Bytes read from the end of an archive: the longest zip comment plus its end record. */\nconst TAIL_BYTES = 0xffff + 22;\n/**\n * Largest archive downloaded whole just to look inside it, when a server will\n * not serve a byte range. IMDF venues are small; a larger zip is left to the\n * vector control, which downloads it once anyway.\n */\nconst MAX_PROBE_DOWNLOAD_BYTES = 32 * 1024 * 1024;\n\n/** Whether an archive entry is macOS resource-fork noise (`__MACOSX/`, `._name`). */\nexport function isMacOsZipMetadata(path: string): boolean {\n const base = path.split(\"/\").pop() ?? \"\";\n return path.startsWith(\"__MACOSX/\") || path.includes(\"/__MACOSX/\") || base.startsWith(\"._\");\n}\n\n/**\n * Whether a list of zip entry paths is an IMDF archive: a `manifest.json` beside\n * a `venue`, `level` or `unit` GeoJSON file, in any folder, ignoring case and\n * macOS metadata entries.\n *\n * @param paths - The archive's entry paths.\n * @returns True for an IMDF archive.\n */\nexport function isImdfArchiveEntries(paths: readonly string[]): boolean {\n let manifest = false;\n let core = false;\n for (const path of paths) {\n if (isMacOsZipMetadata(path)) continue;\n const name = path.split(\"/\").pop()?.toLowerCase() ?? \"\";\n if (name === \"manifest.json\") manifest = true;\n if (name === \"venue.geojson\" || name === \"level.geojson\" || name === \"unit.geojson\")\n core = true;\n }\n return manifest && core;\n}\n\n/** A byte-range reader over a `Blob` (or `File`), reading only the slices asked for. */\nexport function blobRangeReader(blob: Blob): ReadRange {\n return async (offset, length) =>\n new Uint8Array(await blob.slice(offset, offset + length).arrayBuffer());\n}\n\n/** A byte-range reader over bytes already in memory. */\nexport function bytesRangeReader(bytes: Uint8Array): ReadRange {\n return async (offset, length) => bytes.subarray(offset, offset + length);\n}\n\n/**\n * Whether an archive of `size` bytes is IMDF, reading only its central\n * directory through `readRange`.\n *\n * @param size - The archive's size in bytes.\n * @param readRange - Reads a byte range of the archive.\n * @returns True for an IMDF archive; false for any other zip or a non-zip.\n */\nexport async function isImdfZipRange(size: number, readRange: ReadRange): Promise<boolean> {\n try {\n const paths = await listZipEntryPaths(size, readRange);\n return paths !== undefined && isImdfArchiveEntries(paths);\n } catch {\n return false;\n }\n}\n\n/**\n * Whether a `Blob` (a picked or dropped file) is an IMDF zip, reading only its\n * central directory.\n *\n * @param blob - The file.\n * @returns True for an IMDF archive.\n */\nexport function isImdfZipBlob(blob: Blob): Promise<boolean> {\n return isImdfZipRange(blob.size, blobRangeReader(blob));\n}\n\n/** What probing a zip URL found. */\nexport interface ImdfUrlProbe {\n /** Whether the archive is IMDF. */\n imdf: boolean;\n /**\n * The whole archive when the probe had to download it (a server that ignores\n * `Range` requests). An IMDF archive is then not downloaded again; a zip that\n * is not IMDF is downloaded again by the caller's control.\n */\n bytes?: Uint8Array;\n}\n\n/**\n * Probe a zip URL for IMDF with HTTP range requests: the tail first (which\n * also gives the archive's size), then the central directory if it starts\n * before the tail.\n *\n * Two hosts need more. One that ignores `Range` answers with the whole file\n * (status 200); one that serves ranges cross-origin without exposing\n * `Content-Range` leaves the archive's size unknown. Either way the archive is\n * read whole instead, up to {@link MAX_PROBE_DOWNLOAD_BYTES}: a larger one is\n * reported as not IMDF without reading its body, so the control downloads it\n * once rather than twice. Below the cap, a non-IMDF archive read this way is\n * downloaded again by the control.\n *\n * Detection also needs the page to send `Range` cross-origin: a host whose\n * preflight refuses it makes the first request reject, which throws here. The\n * caller then passes the URL to the control, which loads an IMDF zip as a\n * zipped shapefile.\n *\n * @param url - The archive's URL.\n * @param fetchImpl - `fetch`, injectable for tests.\n * @returns Whether the archive is IMDF, and its bytes if they were downloaded.\n * @throws Error when the URL cannot be fetched at all.\n */\nexport async function probeImdfZipUrl(\n url: string,\n fetchImpl: typeof fetch = fetch,\n): Promise<ImdfUrlProbe> {\n const first = await fetchImpl(url, { headers: { Range: `bytes=-${TAIL_BYTES}` } });\n if (!first.ok) throw new Error(`HTTP ${first.status}`);\n if (first.status !== 206) return probeWholeResponse(first);\n const total = Number(/\\/(\\d+)\\s*$/.exec(first.headers.get(\"content-range\") ?? \"\")?.[1]);\n if (!Number.isFinite(total) || total <= 0) {\n // Ranges work but the size is hidden (no Access-Control-Expose-Headers):\n // not a reason to call an IMDF zip a shapefile.\n await first.body?.cancel().catch(() => {});\n const whole = await fetchImpl(url);\n if (!whole.ok) throw new Error(`HTTP ${whole.status}`);\n return probeWholeResponse(whole);\n }\n const tail = new Uint8Array(await first.arrayBuffer());\n const tailOffset = total - tail.byteLength;\n const readRange: ReadRange = async (offset, length) => {\n if (offset >= tailOffset && offset + length <= total) {\n return tail.subarray(offset - tailOffset, offset - tailOffset + length);\n }\n const response = await fetchImpl(url, {\n headers: { Range: `bytes=${offset}-${offset + length - 1}` },\n });\n if (response.status !== 206) throw new Error(\"range request not honoured\");\n return new Uint8Array(await response.arrayBuffer());\n };\n return { imdf: await isImdfZipRange(total, readRange) };\n}\n\n/**\n * Classify an archive from a response carrying all of it, reading the body\n * only when it is small enough to be an IMDF venue.\n *\n * @param response - A full (status 200) response.\n * @returns The probe, with the bytes when they were read.\n */\nasync function probeWholeResponse(response: Response): Promise<ImdfUrlProbe> {\n const bytes = await readBodyUpTo(response, MAX_PROBE_DOWNLOAD_BYTES);\n if (!bytes) return { imdf: false };\n return { imdf: await isImdfZipRange(bytes.byteLength, bytesRangeReader(bytes)), bytes };\n}\n\n/**\n * Read a response body, giving up (and cancelling the rest) once it passes\n * `limit` bytes. The size header cannot be trusted for this: it may be absent\n * (a chunked response) or hidden from script on a cross-origin one.\n *\n * @param response - The response.\n * @param limit - The most bytes to read.\n * @returns The body, or `null` when it is larger than `limit`.\n */\nasync function readBodyUpTo(response: Response, limit: number): Promise<Uint8Array | null> {\n const declared = response.headers.get(\"content-length\");\n if (declared !== null && Number(declared) > limit) {\n await response.body?.cancel().catch(() => {});\n return null;\n }\n if (!response.body) {\n const bytes = new Uint8Array(await response.arrayBuffer());\n return bytes.byteLength > limit ? null : bytes;\n }\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let length = 0;\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n length += value.byteLength;\n if (length > limit) {\n await reader.cancel().catch(() => {});\n return null;\n }\n chunks.push(value);\n }\n const bytes = new Uint8Array(length);\n let offset = 0;\n for (const chunk of chunks) {\n bytes.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return bytes;\n}\n","// The deployment capability model (issue #1673).\n//\n// A *deployment* — a hosted container, a kiosk terminal, a classroom instance,\n// an embed — declares which privileged actions the build it serves is allowed\n// to offer. This is deliberately coarse: it names whole capabilities (\"may add\n// data at all\"), not individual menu items, so a locked-down deployment cannot\n// be defeated by a menu item someone forgot to list.\n//\n// It is *not* the interface profile (`ui-profile.ts` in the desktop app), which\n// hides items to reduce clutter and which the user can turn back on. A denied\n// capability is a statement by whoever stood the deployment up, and the UI\n// never offers a way to grant it back.\n//\n// The client-side gate is one half of the model: it stops the app advertising\n// and invoking what the deployment forbids. The serving layer still has to\n// refuse the corresponding requests, because a client-side-only kiosk mode is a\n// suggestion that anyone with devtools declines.\n\n/** A privileged action a deployment can grant or withhold. */\nexport type DeploymentCapability =\n /** Create, save, or otherwise author the project itself. */\n | \"project:edit\"\n /** Bring new data into the map from files, URLs, or services. */\n | \"data:add\"\n /** Run processing/analysis tools (Whitebox, SQL, vector/raster, ML). */\n | \"processing:run\"\n /** Get data or renderings back out (export image, standalone HTML, …). */\n | \"export:data\"\n /** Install, load, or manage plugins. */\n | \"plugins:install\"\n /** Reach the application settings. */\n | \"settings:manage\";\n\n/** Every capability, in a stable order for docs and tests. */\nexport const DEPLOYMENT_CAPABILITIES: readonly DeploymentCapability[] = [\n \"project:edit\",\n \"data:add\",\n \"processing:run\",\n \"export:data\",\n \"plugins:install\",\n \"settings:manage\",\n];\n\n/**\n * The default grant: everything. `full` must stay the default so an existing\n * deployment that configures nothing behaves exactly as it did before.\n */\nexport const ALL_DEPLOYMENT_CAPABILITIES: ReadonlySet<DeploymentCapability> = new Set(\n DEPLOYMENT_CAPABILITIES,\n);\n\n/** Whether an arbitrary string names a capability we know how to enforce. */\nexport function isDeploymentCapability(value: string): value is DeploymentCapability {\n return (DEPLOYMENT_CAPABILITIES as readonly string[]).includes(value);\n}\n\n/**\n * The reserved token that grants nothing, for a deployment that wants the map\n * and nothing else.\n *\n * Needed because a blank value cannot mean it: the deployment env reader treats\n * an empty string as unset (a `-e VAR=` produces one), and unset has to keep\n * meaning \"full\" so existing deployments are unchanged. Without a spelling for\n * \"none\", the most locked-down configuration would be the one you cannot write.\n */\nexport const NO_DEPLOYMENT_CAPABILITIES = \"none\";\n\n/**\n * Parse a comma-separated capability list from deployment configuration.\n *\n * Unknown tokens are dropped rather than throwing: a newer container image may\n * be configured with a capability this build does not know about, and the safe\n * reading of an unrecognized grant is \"not granted\".\n *\n * Fails closed on purpose. An operator who set the variable at all meant to\n * restrict something, so a list that parses to nothing grants nothing — only an\n * absent (or blank) value falls back to the full set, which is the caller's job\n * to detect.\n *\n * @param raw - The configured value, e.g. `\"data:add, export:data\"`.\n * @returns The granted capabilities, with duplicates and unknown names removed.\n */\nexport function parseDeploymentCapabilities(raw: string): ReadonlySet<DeploymentCapability> {\n const granted = new Set<DeploymentCapability>();\n if (raw.trim().toLowerCase() === NO_DEPLOYMENT_CAPABILITIES) return granted;\n for (const token of raw.split(\",\")) {\n const name = token.trim();\n if (isDeploymentCapability(name)) granted.add(name);\n }\n return granted;\n}\n","/**\n * What this deployment and this session may do: deployment capabilities\n * (issue #1673) and the application role / privilege model (issue #1672).\n * Excluded from the project file and from undo history.\n */\nimport { ALL_DEPLOYMENT_CAPABILITIES, type DeploymentCapability } from \"../deployment-capabilities\";\nimport {\n createDefaultAppCapabilities,\n hasAppPrivilege,\n normalizeAppPrivileges,\n resolveRolePrivileges,\n} from \"../capabilities\";\nimport type { AppCapabilities, AppPrivilege, AppRole } from \"../types\";\nimport type { SliceCreator } from \"./types\";\n\nexport interface CapabilitiesSlice {\n /**\n * What this *deployment* is allowed to do (issue #1673). Set once at startup\n * from the deployment configuration; never from a project file, a URL\n * parameter, or anything else the visitor controls, and never edited from the\n * UI. Defaults to the full set so an unconfigured build behaves as before.\n *\n * Excluded from the project file and from undo history: it describes the\n * server that served the app, not the document being edited.\n */\n deploymentCapabilities: ReadonlySet<DeploymentCapability>;\n /**\n * Ephemeral application capability model (issue #1672). Gating role and\n * privileges for the current session/deployment. Excluded from the project file\n * and undo history.\n */\n capabilities: AppCapabilities;\n\n /**\n * Narrow what this deployment may do. Intended for the startup path only —\n * calling it later would leave already-rendered surfaces stale.\n */\n setDeploymentCapabilities: (capabilities: Iterable<DeploymentCapability>) => void;\n /**\n * Assign an application role (e.g. \"viewer\", \"editor\", \"publisher\", \"administrator\", \"custom\"),\n * deriving the effective privileges and optional reason.\n */\n setAppRole: (\n role: AppRole,\n options?: { customPrivileges?: AppPrivilege[]; reason?: string },\n ) => void;\n /** Set explicit custom privileges and an optional reason. */\n setAppPrivileges: (privileges: AppPrivilege[], reason?: string) => void;\n /** Grant an individual application privilege. */\n grantAppPrivilege: (privilege: AppPrivilege) => void;\n /** Revoke an individual application privilege with an optional reason. */\n revokeAppPrivilege: (privilege: AppPrivilege, reason?: string) => void;\n /** Reset application capabilities back to the default unconstrained Administrator role. */\n resetAppCapabilities: () => void;\n /** Check if the current capabilities grant the requested privilege. */\n hasAppPrivilege: (privilege: AppPrivilege) => boolean;\n}\n\nexport const createCapabilitiesSlice: SliceCreator<CapabilitiesSlice> = (set, get) => ({\n deploymentCapabilities: ALL_DEPLOYMENT_CAPABILITIES,\n capabilities: createDefaultAppCapabilities(),\n\n setDeploymentCapabilities: (capabilities) =>\n set({ deploymentCapabilities: new Set(capabilities) }),\n\n // A new role or privilege list is a new policy, so the per-privilege reasons\n // recorded against the old one go with it — carrying them forward would\n // explain a grant that is no longer withheld for that cause.\n setAppRole: (role, options) => {\n const privileges = resolveRolePrivileges(role, options?.customPrivileges);\n set({\n capabilities: {\n role,\n privileges,\n reason: options?.reason,\n },\n });\n },\n\n setAppPrivileges: (privileges, reason) => {\n set({\n capabilities: {\n role: \"custom\",\n privileges: normalizeAppPrivileges(privileges) ?? [],\n reason,\n },\n });\n },\n\n // An ad-hoc grant or revoke makes the set no longer the bundle its role\n // names, so the role becomes \"custom\" — the same thing setAppPrivileges\n // does for an explicit list. Leaving it as \"editor\" while the privileges\n // are not the editor bundle would mislead anything that branches on the\n // role rather than checking a privilege.\n grantAppPrivilege: (privilege) => {\n const current = get().capabilities;\n if (current.privileges.includes(privilege)) return;\n const { [privilege]: _granted, ...privilegeReasons } = current.privilegeReasons ?? {};\n set({\n capabilities: {\n ...current,\n role: \"custom\",\n privileges: [...current.privileges, privilege],\n privilegeReasons,\n },\n });\n },\n\n // The reason is filed against this privilege, not against the whole set:\n // revoking a second privilege for a different cause must not relabel the\n // first one's explanation. `reason` stays the fallback for the rest.\n //\n // Re-revoking an already-withheld privilege is not a no-op when it carries\n // a new reason: restating why something is denied is a real operation, and\n // an early return would silently keep the stale explanation on screen.\n revokeAppPrivilege: (privilege, reason) => {\n const current = get().capabilities;\n const held = current.privileges.includes(privilege);\n if (!held && !reason) return;\n set({\n capabilities: {\n ...current,\n role: held ? \"custom\" : current.role,\n privileges: held ? current.privileges.filter((p) => p !== privilege) : current.privileges,\n privilegeReasons: reason\n ? { ...current.privilegeReasons, [privilege]: reason }\n : current.privilegeReasons,\n },\n });\n },\n\n resetAppCapabilities: () => {\n set({ capabilities: createDefaultAppCapabilities() });\n },\n\n hasAppPrivilege: (privilege) => {\n return hasAppPrivilege(get().capabilities, privilege);\n },\n});\n","/**\n * Ephemeral live-collaboration session state (issue #307): never saved with the\n * project and never tracked by undo history.\n */\nimport type {\n CollabInvite,\n CollaborationChatMessage,\n CollaborationParticipant,\n CollaborationPresence,\n CollaborationState,\n} from \"../types\";\nimport type { SliceCreator } from \"./types\";\n\n/**\n * A fresh, inactive collaboration slice (no live session). Frozen (like\n * DEFAULT_LEGEND_CONFIG) to guard against accidental in-place mutation; store\n * actions always produce new objects via spread, so the frozen default is only\n * ever read.\n */\nexport const DEFAULT_COLLABORATION_STATE: CollaborationState = Object.freeze({\n isActive: false,\n connecting: false,\n sessionId: null,\n clientId: null,\n role: null,\n mode: \"co-edit\",\n selfName: \"\",\n selfColor: \"\",\n participants: Object.freeze([] as CollaborationParticipant[]) as CollaborationParticipant[],\n presence: Object.freeze({} as Record<string, CollaborationPresence>) as Record<\n string,\n CollaborationPresence\n >,\n followHost: false,\n chat: Object.freeze([] as CollaborationChatMessage[]) as CollaborationChatMessage[],\n requireIdentity: false,\n identitySupported: false,\n lockedLayerIds: Object.freeze([] as string[]) as string[],\n invites: Object.freeze([] as CollabInvite[]) as CollabInvite[],\n error: null,\n});\n\n// Cap the in-store chat log so a long session can't grow it without bound. This\n// is intentionally larger than the relay's persisted history (50): the live\n// session accumulates messages locally, while the relay only retains the tail\n// for late joiners.\nconst MAX_COLLABORATION_CHAT = 200;\n\nexport interface CollaborationSlice {\n // Ephemeral live-collaboration session state (issue #307). Deliberately\n // excluded from the project file (project.ts never reads it) and from undo\n // history (partialize never lists it).\n collaboration: CollaborationState;\n\n setCollaboration: (patch: Partial<CollaborationState>) => void;\n updateCollaborationPresence: (clientId: string, presence: CollaborationPresence | null) => void;\n /** Append a chat message to the session log (bounded; #754). */\n addCollaborationChat: (message: CollaborationChatMessage) => void;\n resetCollaboration: () => void;\n}\n\nexport const createCollaborationSlice: SliceCreator<CollaborationSlice> = (set) => ({\n collaboration: DEFAULT_COLLABORATION_STATE,\n\n setCollaboration: (patch) => set((s) => ({ collaboration: { ...s.collaboration, ...patch } })),\n // Add or remove a single remote participant's presence without rebuilding\n // the whole map on every cursor move. Passing `null` drops the entry (on\n // participant leave).\n updateCollaborationPresence: (clientId, presence) =>\n set((s) => {\n const next = { ...s.collaboration.presence };\n if (presence === null) {\n delete next[clientId];\n } else {\n next[clientId] = presence;\n }\n return { collaboration: { ...s.collaboration, presence: next } };\n }),\n addCollaborationChat: (message) =>\n set((s) => {\n // Default to [] in case an older relay left the slice undefined.\n const current = s.collaboration.chat ?? [];\n // Ignore a duplicate id: the server broadcasts to the sender too, and a\n // reconnect can replay recent history, so de-dupe defensively.\n if (current.some((m) => m.id === message.id)) return s;\n const chat = [...current, message].slice(-MAX_COLLABORATION_CHAT);\n return { collaboration: { ...s.collaboration, chat } };\n }),\n resetCollaboration: () =>\n // Also close the Collaborate dialog: an unexpected disconnect resets the\n // slice without a user-initiated leave(), and leaving the dialog open\n // would drop the user onto the start/join form with no context.\n set((s) => ({\n collaboration: DEFAULT_COLLABORATION_STATE,\n ui: { ...s.ui, collaborateDialogOpen: false },\n })),\n});\n","/**\n * Per-session selection and live readouts: the selected layer and features,\n * the Identify target, the attribute-table filter, and the pointer / camera /\n * GPS readouts the status bar shows. None of it is saved with the project or\n * tracked by undo history.\n */\nimport type { SliceCreator } from \"./types\";\n\n/**\n * Latest live device-GPS fix published by the GPS Tracking tool (issue #1316),\n * read by the status bar readout. Device state, not project state: excluded\n * from undo history (partialize never lists it) and from project files, and\n * deliberately left untouched on project switches.\n */\nexport interface GpsStatusFix {\n lng: number;\n lat: number;\n /** Horizontal accuracy radius in meters. */\n accuracy: number;\n /** Satellites used for the fix, or null when the provider does not report it. */\n satellites: number | null;\n /** Ground speed in m/s, or null when the device doesn't report one. */\n speed: number | null;\n /** Fix time in epoch milliseconds. */\n timestamp: number;\n}\n\n/** Reserved Identify target for querying every visible queryable layer at once. */\nexport const IDENTIFY_ALL_LAYERS_ID = \"__geolibre_identify_all_layers__\";\n\n/** The Identify fields of the store, as set together by an Identify change. */\nexport interface IdentifyState {\n identifyLayerId: string | null;\n identifyLayerIds: string[] | null;\n}\n\n/**\n * Resolve an Identify target against the layers that exist.\n *\n * Shared by the scripting `setIdentify` command and by project load, which\n * applies a project's saved `interaction.identify` (issue #2688).\n *\n * @param target - A layer id, `\"all\"`, a list of layer ids, or null/undefined.\n * @param layerIds - Ids of the layers currently in the project.\n * @returns The store fields to set. Ids that name no layer are dropped; a list\n * that keeps one id becomes a single-layer target, and one that keeps several\n * becomes the all-layers mode restricted to those ids.\n */\nexport function resolveIdentifyTarget(\n target: string | readonly string[] | null | undefined,\n layerIds: Iterable<string>,\n): IdentifyState {\n const off: IdentifyState = { identifyLayerId: null, identifyLayerIds: null };\n if (target === null || target === undefined) return off;\n if (target === \"all\") return { identifyLayerId: IDENTIFY_ALL_LAYERS_ID, identifyLayerIds: null };\n const known = new Set(layerIds);\n if (typeof target === \"string\") {\n return known.has(target) ? { identifyLayerId: target, identifyLayerIds: null } : off;\n }\n const ids = [...new Set(target)].filter((id) => known.has(id));\n if (ids.length === 0) return off;\n if (ids.length === 1) return { identifyLayerId: ids[0], identifyLayerIds: null };\n return { identifyLayerId: IDENTIFY_ALL_LAYERS_ID, identifyLayerIds: ids };\n}\n\n/**\n * Whether the all-layers Identify mode may query a layer.\n *\n * @param layerId - The candidate layer's id.\n * @param identifyLayerIds - The store's `identifyLayerIds` restriction.\n * @returns True when there is no restriction or the layer is in it.\n */\nexport function identifyAllIncludes(\n layerId: string,\n identifyLayerIds: readonly string[] | null | undefined,\n): boolean {\n return !identifyLayerIds || identifyLayerIds.includes(layerId);\n}\n\n/**\n * Drop removed layers from the Identify state.\n *\n * @param state - The current Identify fields.\n * @param removed - Ids of the layers being removed.\n * @returns The Identify fields with those layers gone. A restricted all-layers\n * mode left with one layer narrows to it, and with none turns Identify off.\n */\nexport function identifyStateWithoutLayers(\n state: IdentifyState,\n removed: ReadonlySet<string>,\n): IdentifyState {\n if (state.identifyLayerId !== null && removed.has(state.identifyLayerId)) {\n return { identifyLayerId: null, identifyLayerIds: null };\n }\n // Return fresh fields, never `state` itself: callers spread the result into\n // a store update and pass the whole store as `state`.\n const unchanged = {\n identifyLayerId: state.identifyLayerId,\n identifyLayerIds: state.identifyLayerIds,\n };\n if (!state.identifyLayerIds) return unchanged;\n const ids = state.identifyLayerIds.filter((id) => !removed.has(id));\n if (ids.length === state.identifyLayerIds.length) return unchanged;\n if (ids.length === 0) return { identifyLayerId: null, identifyLayerIds: null };\n if (ids.length === 1) return { identifyLayerId: ids[0], identifyLayerIds: null };\n return { identifyLayerId: state.identifyLayerId, identifyLayerIds: ids };\n}\n\nexport interface SessionSlice {\n /** Local viewing switch; saved layer popup settings are left untouched. */\n hoverTooltipsEnabled: boolean;\n setHoverTooltipsEnabled: (enabled: boolean) => void;\n selectedLayerId: string | null;\n selectedFeatureId: string | null;\n /**\n * Full set of selected feature ids. The attribute table extends the single\n * selection to many rows via Ctrl/Cmd (toggle) and Shift (range). The anchor\n * — `selectedFeatureId` — is the primary/last-clicked feature used for map\n * fit, DuckDB highlight, and scripting, and is always one of these ids (or\n * `null` when the set is empty). A single click leaves exactly one id here.\n */\n selectedFeatureIds: string[];\n /**\n * Store-layer id targeted by Identify, or {@link IDENTIFY_ALL_LAYERS_ID} for\n * the map-level mode that queries every visible queryable layer — vector,\n * DuckDB query, WMS, COG, NetCDF image and time-slider raster alike.\n */\n identifyLayerId: string | null;\n /**\n * Layers the all-layers Identify mode is limited to, or null for every\n * visible queryable layer. Set only alongside {@link IDENTIFY_ALL_LAYERS_ID},\n * by a script or project that names several layers (issue #2688), and\n * cleared by every `setIdentifyLayer` call so the in-app buttons keep their\n * plain meaning.\n */\n identifyLayerIds: string[] | null;\n /** Transient map-selection gesture activity, published by the primary renderer. */\n featureSelectionActive: boolean;\n setFeatureSelectionActive: (active: boolean) => void;\n pointerCoords: [number, number] | null;\n /**\n * Ground elevation in true metres under the pointer, for the status bar\n * (issue #1813). Null when it cannot be resolved — the pointer is off the\n * map, terrain is off and the remote lookup has not answered (or failed), or\n * the active body is not Earth. Set alongside `pointerCoords` by MapCanvas,\n * which owns the map instance the terrain sample comes from.\n */\n pointerElevation: number | null;\n /**\n * Camera height above sea level in metres — Google Earth Pro's \"Eye alt\"\n * (issue #1816). Derived from the camera, so deliberately *not* part of\n * `mapView`: that shape is persisted into the project file, and a stored\n * altitude could only drift from the center/zoom/pitch it is computed from.\n * Null before the map loads, or when MapLibre cannot report it.\n */\n cameraAltitude: number | null;\n /** Live GPS fix for the status bar, or null while GPS tracking is off. */\n gpsStatus: GpsStatusFix | null;\n attributeFilter: string;\n\n setPointerCoords: (coords: [number, number] | null) => void;\n setPointerElevation: (elevation: number | null) => void;\n setCameraAltitude: (altitude: number | null) => void;\n setGpsStatus: (fix: GpsStatusFix | null) => void;\n selectLayer: (id: string | null) => void;\n selectFeature: (id: string | null) => void;\n /**\n * Replace the multi-selection with `ids`. The anchor (`selectedFeatureId`)\n * becomes `anchorId` when provided, otherwise the last id in the list (or\n * `null` when the list is empty).\n */\n selectFeatures: (ids: string[], anchorId?: string | null) => void;\n setIdentifyLayer: (id: string | null) => void;\n /** Set both Identify fields at once (see {@link resolveIdentifyTarget}). */\n setIdentifyState: (state: IdentifyState) => void;\n setAttributeFilter: (filter: string) => void;\n}\n\nexport const createSessionSlice: SliceCreator<SessionSlice> = (set) => ({\n hoverTooltipsEnabled: true,\n setHoverTooltipsEnabled: (enabled) => set({ hoverTooltipsEnabled: enabled }),\n selectedLayerId: null,\n selectedFeatureId: null,\n selectedFeatureIds: [],\n identifyLayerId: null,\n identifyLayerIds: null,\n featureSelectionActive: false,\n setFeatureSelectionActive: (active) => set({ featureSelectionActive: active }),\n pointerCoords: null,\n pointerElevation: null,\n cameraAltitude: null,\n gpsStatus: null,\n attributeFilter: \"\",\n\n setPointerCoords: (coords) =>\n set(coords ? { pointerCoords: coords } : { pointerCoords: null, pointerElevation: null }),\n setPointerElevation: (elevation) => set({ pointerElevation: elevation }),\n setCameraAltitude: (altitude) => set({ cameraAltitude: altitude }),\n setGpsStatus: (fix) => set({ gpsStatus: fix }),\n\n selectLayer: (id) =>\n set({\n selectedLayerId: id,\n selectedFeatureId: null,\n selectedFeatureIds: [],\n }),\n // `\"\"` is a valid feature id; only `null` clears the selection.\n selectFeature: (id) =>\n set({ selectedFeatureId: id, selectedFeatureIds: id === null ? [] : [id] }),\n selectFeatures: (ids, anchorId) =>\n set({\n selectedFeatureIds: ids,\n // Enforce the documented invariant for every caller: the anchor is\n // always a member of the set (or null when empty). A supplied anchor\n // that isn't in `ids` falls back to the last id rather than pointing\n // the map fit / calculator sample at an unselected feature.\n selectedFeatureId:\n anchorId != null && ids.includes(anchorId) ? anchorId : (ids.at(-1) ?? null),\n }),\n setIdentifyLayer: (id) => set({ identifyLayerId: id, identifyLayerIds: null }),\n setIdentifyState: ({ identifyLayerId, identifyLayerIds }) =>\n set({ identifyLayerId, identifyLayerIds }),\n setAttributeFilter: (filter) => set({ attributeFilter: filter }),\n});\n","/**\n * Reference scrubbing shared by the actions that delete layers (`removeLayer`\n * and `removeLayerGroup` with `removeChildren`), so a deleted layer never\n * leaves a dangling id in a story map, a secondary pane, or a join.\n */\nimport { cascadeLayerJoinRefresh } from \"../joins\";\nimport { removedLayerIdSet } from \"../layer-ref-scrub\";\nimport type { GeoLibreLayer, SecondaryMapView, StoryMap } from \"../types\";\n\n/**\n * Strip storymap chapter enter/exit opacity rows that reference any of the\n * removed layer ids. Returns the same reference when nothing changes so\n * callers can avoid unnecessary storymap churn.\n */\nexport function scrubStorymapLayerRefs(\n storymap: StoryMap | null,\n layerIds: string | Iterable<string>,\n): StoryMap | null {\n if (!storymap) return null;\n const removed = removedLayerIdSet(layerIds);\n if (removed.size === 0) return storymap;\n let changed = false;\n const chapters = storymap.chapters.map((chapter) => {\n const onChapterEnter = chapter.onChapterEnter.filter((change) => !removed.has(change.layerId));\n const onChapterExit = chapter.onChapterExit.filter((change) => !removed.has(change.layerId));\n if (\n onChapterEnter.length === chapter.onChapterEnter.length &&\n onChapterExit.length === chapter.onChapterExit.length\n ) {\n return chapter;\n }\n changed = true;\n return { ...chapter, onChapterEnter, onChapterExit };\n });\n return changed ? { ...storymap, chapters } : storymap;\n}\n\n/**\n * Drop per-pane visibility overrides for removed layer ids so stale keys do\n * not accumulate (or serialize) in secondary panes after deletion.\n */\nexport function scrubSecondaryPaneLayerVisibility(\n panes: SecondaryMapView[],\n layerIds: string | Iterable<string>,\n): SecondaryMapView[] {\n const removed = removedLayerIdSet(layerIds);\n if (removed.size === 0) return panes;\n let anyChanged = false;\n const next = panes.map((pane) => {\n let changed = false;\n const layerVisibility: Record<string, boolean> = {};\n for (const [id, visible] of Object.entries(pane.layerVisibility)) {\n if (removed.has(id)) {\n changed = true;\n continue;\n }\n layerVisibility[id] = visible;\n }\n if (!changed) return pane;\n anyChanged = true;\n return { ...pane, layerVisibility };\n });\n return anyChanged ? next : panes;\n}\n\n/** Re-derive layers whose joins consumed any of the removed sources. */\nexport function cascadeJoinRefreshForRemoved(\n layers: GeoLibreLayer[],\n layerIds: string | Iterable<string>,\n): GeoLibreLayer[] {\n let current = layers;\n for (const id of removedLayerIdSet(layerIds)) {\n current = cascadeLayerJoinRefresh(current, id);\n }\n return current;\n}\n","/**\n * Layer-panel folders: create, rename, nest, reorder, sort and delete groups,\n * and move layers between them. `layerGroups` is tracked by undo history\n * except for the `collapsed` flag (see `undo-history.ts`).\n */\nimport { v4 as uuidv4 } from \"uuid\";\nimport {\n DEFAULT_LAYER_GROUP_OPACITY,\n normalizeGroupContiguity,\n reorderLayerGroupInPanel,\n sortLayerGroupInPanel,\n type LayerGroupSortOrder,\n} from \"../layer-groups\";\nimport {\n scrubCommentsForRemovedLayers,\n scrubLegendForRemovedLayers,\n scrubWidgetsForRemovedLayers,\n} from \"../layer-ref-scrub\";\nimport { scrubPrintLayoutForRemovedLayers } from \"../print-layout-config\";\nimport { identifyStateWithoutLayers } from \"./session-slice\";\nimport type { LayerGroup } from \"../types\";\nimport {\n cascadeJoinRefreshForRemoved,\n scrubSecondaryPaneLayerVisibility,\n scrubStorymapLayerRefs,\n} from \"./layer-removal\";\nimport type { SliceCreator } from \"./types\";\n\n/**\n * Pick the lowest `Group N` name not already taken, so default names stay\n * unique while still preferring small numbers — starting the search at 1 (not\n * `length + 1`) avoids skipping free low numbers when some groups carry custom\n * names. Group counts are small, so the linear scan is negligible.\n */\nfunction nextDefaultGroupName(groups: LayerGroup[]): string {\n const existing = new Set(groups.map((g) => g.name));\n let n = 1;\n while (existing.has(`Group ${n}`)) n++;\n return `Group ${n}`;\n}\n\nexport interface LayerGroupsSlice {\n layerGroups: LayerGroup[];\n\n addLayerGroup: (name?: string, layerIds?: string[]) => string;\n removeLayerGroup: (id: string, options?: { removeChildren?: boolean }) => void;\n renameLayerGroup: (id: string, name: string) => void;\n setLayerGroupVisibility: (id: string, visible: boolean) => void;\n setLayerGroupOpacity: (id: string, opacity: number) => void;\n toggleLayerGroupCollapsed: (id: string) => void;\n moveLayerToGroup: (\n layerId: string,\n groupId: string | null,\n beforeLayerId?: string | null,\n ) => void;\n moveLayersToGroup: (\n layerIds: string[],\n groupId: string | null,\n beforeLayerId?: string | null,\n ) => void;\n moveLayerGroupToGroup: (id: string, parentId: string | null) => void;\n reorderLayerGroup: (id: string, direction: \"up\" | \"down\") => void;\n /**\n * Sort a group's direct children by name, A to Z or Z to A (top of panel\n * first), collating by `locale` (the app's display language) when given.\n */\n sortLayerGroup: (id: string, order: LayerGroupSortOrder, locale?: string) => void;\n}\n\nexport const createLayerGroupsSlice: SliceCreator<LayerGroupsSlice> = (set, get) => ({\n layerGroups: [],\n\n addLayerGroup: (name, layerIds) => {\n const id = uuidv4();\n set((s) => {\n const group: LayerGroup = {\n id,\n name: name?.trim() || nextDefaultGroupName(s.layerGroups),\n collapsed: false,\n visible: true,\n opacity: DEFAULT_LAYER_GROUP_OPACITY,\n };\n const ids = new Set(layerIds ?? []);\n const layers =\n ids.size > 0\n ? normalizeGroupContiguity(\n s.layers.map((l) => (ids.has(l.id) ? { ...l, groupId: id } : l)),\n )\n : s.layers;\n return {\n layers,\n layerGroups: [...s.layerGroups, group],\n isDirty: true,\n };\n });\n return id;\n },\n\n removeLayerGroup: (id, options) =>\n set((s) => {\n const removeChildren = options?.removeChildren ?? false;\n const removedGroup = s.layerGroups.find((g) => g.id === id);\n if (!removedGroup) return s;\n const groupIds = new Set([id]);\n if (removeChildren) {\n let changed = true;\n while (changed) {\n changed = false;\n for (const group of s.layerGroups) {\n if (group.parentId && groupIds.has(group.parentId) && !groupIds.has(group.id)) {\n groupIds.add(group.id);\n changed = true;\n }\n }\n }\n }\n const removedIds = new Set(\n s.layers.filter((l) => l.groupId && groupIds.has(l.groupId)).map((l) => l.id),\n );\n let layers = removeChildren\n ? s.layers.filter((l) => !l.groupId || !groupIds.has(l.groupId))\n : s.layers.map((l) => (l.groupId === id ? { ...l, groupId: undefined } : l));\n // Match removeLayer: refreshing joins and scrubbing secondary-pane /\n // storymap refs for every deleted child so group delete cannot leave\n // stale joined columns or dangling visibility overrides behind.\n if (removeChildren && removedIds.size > 0) {\n layers = cascadeJoinRefreshForRemoved(layers, removedIds);\n }\n const selectionRemoved =\n removeChildren && s.selectedLayerId !== null && removedIds.has(s.selectedLayerId);\n return {\n layers,\n layerGroups: s.layerGroups\n .filter((g) => !groupIds.has(g.id))\n .map((g) => (g.parentId === id ? { ...g, parentId: removedGroup.parentId } : g)),\n secondaryMapViews: removeChildren\n ? scrubSecondaryPaneLayerVisibility(s.secondaryMapViews, removedIds)\n : s.secondaryMapViews,\n storymap: removeChildren ? scrubStorymapLayerRefs(s.storymap, removedIds) : s.storymap,\n widgets: removeChildren ? scrubWidgetsForRemovedLayers(s.widgets, removedIds) : s.widgets,\n comments: removeChildren\n ? scrubCommentsForRemovedLayers(s.comments, removedIds)\n : s.comments,\n legend: removeChildren ? scrubLegendForRemovedLayers(s.legend, removedIds) : s.legend,\n printLayout: removeChildren\n ? scrubPrintLayoutForRemovedLayers(s.printLayout, removedIds)\n : s.printLayout,\n selectedLayerId: selectionRemoved\n ? (layers[layers.length - 1]?.id ?? null)\n : s.selectedLayerId,\n selectedFeatureId: selectionRemoved ? null : s.selectedFeatureId,\n selectedFeatureIds: selectionRemoved ? [] : s.selectedFeatureIds,\n // Children kept at the top level are still in the project, so they\n // keep their place in the Identify target.\n ...(removeChildren\n ? identifyStateWithoutLayers(s, removedIds)\n : { identifyLayerId: s.identifyLayerId, identifyLayerIds: s.identifyLayerIds }),\n ui: removeChildren\n ? {\n ...s.ui,\n selectByExpressionLayerId:\n s.ui.selectByExpressionLayerId !== null &&\n removedIds.has(s.ui.selectByExpressionLayerId)\n ? null\n : s.ui.selectByExpressionLayerId,\n selectByLocationLayerId:\n s.ui.selectByLocationLayerId !== null &&\n removedIds.has(s.ui.selectByLocationLayerId)\n ? null\n : s.ui.selectByLocationLayerId,\n loadEditorFeaturesLayerId:\n s.ui.loadEditorFeaturesLayerId !== null &&\n removedIds.has(s.ui.loadEditorFeaturesLayerId)\n ? null\n : s.ui.loadEditorFeaturesLayerId,\n }\n : s.ui,\n isDirty: true,\n };\n }),\n\n renameLayerGroup: (id, name) =>\n set((s) => ({\n layerGroups: s.layerGroups.map((g) => (g.id === id ? { ...g, name } : g)),\n isDirty: true,\n })),\n\n setLayerGroupVisibility: (id, visible) =>\n set((s) => ({\n layerGroups: s.layerGroups.map((g) => (g.id === id ? { ...g, visible } : g)),\n isDirty: true,\n })),\n\n setLayerGroupOpacity: (id, opacity) =>\n set((s) => ({\n layerGroups: s.layerGroups.map((g) =>\n g.id === id ? { ...g, opacity: Math.min(Math.max(opacity, 0), 1) } : g,\n ),\n isDirty: true,\n })),\n\n // Collapsing/expanding a folder is a UI-panel preference, not a data\n // edit: it is still persisted in the project (folders reopen collapsed),\n // but it does not mark the project dirty and is excluded from undo (see\n // the equality comparator in undo-history.ts) so Ctrl-Z never toggles a\n // folder.\n toggleLayerGroupCollapsed: (id) =>\n set((s) => ({\n layerGroups: s.layerGroups.map((g) => (g.id === id ? { ...g, collapsed: !g.collapsed } : g)),\n })),\n\n moveLayerToGroup: (layerId, groupId, beforeLayerId = null) =>\n get().moveLayersToGroup([layerId], groupId, beforeLayerId),\n\n moveLayersToGroup: (layerIds, groupId, beforeLayerId = null) =>\n set((s) => {\n if (groupId && !s.layerGroups.some((g) => g.id === groupId)) return s;\n const requestedIds = new Set(layerIds);\n const moving = s.layers.filter(\n (layer) =>\n requestedIds.has(layer.id) &&\n (beforeLayerId !== null || (layer.groupId ?? null) !== groupId),\n );\n if (moving.length === 0) return s;\n const movingIds = new Set(moving.map((layer) => layer.id));\n const without = s.layers.filter((layer) => !movingIds.has(layer.id));\n const updated = moving.map((layer) => ({\n ...layer,\n groupId: groupId ?? undefined,\n }));\n let index: number;\n if (beforeLayerId && !movingIds.has(beforeLayerId)) {\n const at = without.findIndex((layer) => layer.id === beforeLayerId);\n index = at < 0 ? without.length : at;\n } else if (groupId) {\n let last = -1;\n without.forEach((layer, layerIndex) => {\n if (layer.groupId === groupId) last = layerIndex;\n });\n index = last < 0 ? without.length : last + 1;\n } else {\n index = without.length;\n }\n const next = [...without];\n next.splice(index, 0, ...updated);\n const normalized = normalizeGroupContiguity(next);\n const unchanged = normalized.every(\n (layer, layerIndex) =>\n layer.id === s.layers[layerIndex]?.id && layer.groupId === s.layers[layerIndex]?.groupId,\n );\n if (unchanged) return s;\n return { layers: normalized, isDirty: true };\n }),\n\n moveLayerGroupToGroup: (id, parentId) =>\n set((s) => {\n if (parentId === id) return s;\n const group = s.layerGroups.find((g) => g.id === id);\n if (!group) return s;\n if (parentId && !s.layerGroups.some((g) => g.id === parentId)) return s;\n // Walking upward from the proposed parent must never reach the group\n // being moved, otherwise the assignment would create a cycle.\n const byId = new Map(s.layerGroups.map((g) => [g.id, g]));\n let ancestorId = parentId ?? undefined;\n const visited = new Set<string>();\n while (ancestorId && !visited.has(ancestorId)) {\n if (ancestorId === id) return s;\n visited.add(ancestorId);\n ancestorId = byId.get(ancestorId)?.parentId;\n }\n const nextParentId = parentId ?? undefined;\n if (group.parentId === nextParentId) return s;\n return {\n layerGroups: s.layerGroups.map((g) => (g.id === id ? { ...g, parentId: nextParentId } : g)),\n isDirty: true,\n };\n }),\n\n // A folder that owns no layers has no position in `layers` to move, so\n // the panel order it takes part in lives across both arrays and the move\n // writes both (GeoLibre#1739).\n reorderLayerGroup: (id, direction) =>\n set((s) => {\n const moved = reorderLayerGroupInPanel(s.layers, s.layerGroups, id, direction);\n if (!moved) return s;\n return { layers: moved.layers, layerGroups: moved.groups, isDirty: true };\n }),\n\n sortLayerGroup: (id, order, locale) =>\n set((s) => {\n const sorted = sortLayerGroupInPanel(s.layers, s.layerGroups, id, order, locale);\n if (!sorted) return s;\n return { layers: sorted.layers, layerGroups: sorted.groups, isDirty: true };\n }),\n});\n","/**\n * A bare Android SAF document ID, as the browser `File.name` carries after a\n * native import: a known volume prefix (`primary:`, `home:`, `raw:`, `msf:`, or a\n * removable `XXXX-XXXX:` volume), or any provider prefix followed by a folder\n * separator. Ordinary encoded filenames such as `Report%3AQ1.geojson` do not match.\n */\nconst SAF_DOCUMENT_ID =\n /^(?:(?:primary|home|raw|msf|[0-9a-f]{4}-[0-9a-f]{4})%3a|[^%/\\\\:]+%3a.*%2f)/i;\n\n/** Extract a display filename without changing the source used for file access. */\nexport function localFileName(path: string): string {\n const isUri = /^(?:content|file):\\/\\//i.test(path);\n const value = isUri ? path.split(/[?#]/)[0] : path;\n const segment = value.split(/[/\\\\]/).pop() ?? value;\n // Do not decode ordinary filenames: percent escapes can be literal text.\n if (!isUri && (!SAF_DOCUMENT_ID.test(segment) || /[/\\\\]/.test(path))) {\n return segment;\n }\n let decoded = segment;\n try {\n decoded = decodeURIComponent(segment);\n } catch {\n return segment;\n }\n // Strip the provider's volume prefix before taking the final path component.\n return (\n decoded\n .replace(/^[^/\\\\:]+:/, \"\")\n .split(/[/\\\\]/)\n .pop() ?? decoded\n );\n}\n\n/**\n * Whether a layer name was derived from its local source filename (with or\n * without the extension) rather than supplied explicitly by a caller.\n */\nexport function isSourceDerivedLayerName(name: string, sourcePath: string): boolean {\n const fileName = localFileName(sourcePath);\n return name === fileName || name === fileName.replace(/\\.[^.]+$/, \"\");\n}\n\n/** Keep imported layers distinguishable, including within a multi-file import. */\nexport function uniqueImportedLayerName(name: string, names: Iterable<string>): string {\n const existing = new Set(names);\n let candidate = name;\n let suffix = 2;\n while (existing.has(candidate)) candidate = `${name}_${suffix++}`;\n return candidate;\n}\n","import type { FeatureCollection } from \"geojson\";\nimport { DEFAULT_LAYER_STYLE, type GeoLibreLayer, type LayerStyle } from \"./types\";\n\n/**\n * Initial styling for a newly added vector layer (issue #1519).\n *\n * {@link DEFAULT_LAYER_STYLE} stays exactly what it is — the schema fallback\n * that fills gaps when a partial style is read back from a project file. It is\n * deliberately NOT changed here: every load path spreads it under a saved\n * style, so editing it would silently restyle existing projects.\n *\n * What this module adds is a separate decision made only at *add* time: give\n * each new layer its own color, and pick sizes that suit the geometry it\n * actually contains, so a stack of freshly loaded layers is legible without\n * anyone opening the Style panel.\n */\n\n/**\n * Qualitative colors cycled across newly added layers, chosen to stay distinct\n * at low opacity over both light and dark basemaps.\n *\n * Distinct from the classification palette the Style panel uses for the classes\n * *within* one layer: this one separates layers from each other, and the two\n * are free to diverge.\n */\nexport const LAYER_PALETTE = [\n \"#3b82f6\", // blue — matches the historical default, so the first layer looks unchanged\n \"#ef4444\", // red\n \"#22c55e\", // green\n \"#a855f7\", // purple\n \"#f97316\", // orange\n \"#06b6d4\", // cyan\n \"#ec4899\", // pink\n \"#84cc16\", // lime\n] as const;\n\n/** How much darker an outline is than its fill (0 = black, 1 = unchanged). */\nconst STROKE_DARKEN = 0.55;\n\n/** Darken a `#rrggbb` color toward black by `factor`. */\nexport function darkenHex(hex: string, factor: number): string {\n const match = /^#([0-9a-f]{6})$/i.exec(hex.trim());\n if (!match) return hex;\n const value = Number.parseInt(match[1], 16);\n const channels = [(value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff];\n return `#${channels\n .map((channel) =>\n Math.max(0, Math.min(255, Math.round(channel * factor)))\n .toString(16)\n .padStart(2, \"0\"),\n )\n .join(\"\")}`;\n}\n\n/** Case-insensitive palette lookup, returning the canonical (lowercase) entry. */\nfunction paletteEntryOf(color: string | undefined): string | undefined {\n const normalized = (color ?? \"\").trim().toLowerCase();\n return LAYER_PALETTE.find((entry) => entry === normalized);\n}\n\n/**\n * True when a style's colors are a palette assignment {@link initialLayerStyle}\n * made: a palette fill *paired with* the outline derived from it.\n *\n * The pairing is what makes this precise. Testing the fill alone — or the layer\n * type — would miscount every layer built straight from\n * {@link DEFAULT_LAYER_STYLE}, whose `fillColor` happens to be\n * `LAYER_PALETTE[0]`: rasters, tilesets, video overlays, and the non-spatial\n * attribute tables the delimited-text source adds. None of those paint from a\n * collection, so none should reserve blue and push the next added layer to red.\n * They carry the schema's own `strokeColor`, not one derived from the fill, so\n * this test leaves them out.\n */\nfunction wearsPaletteColor(style: LayerStyle | undefined): boolean {\n const entry = paletteEntryOf(style?.fillColor);\n if (!entry) return false;\n return (style?.strokeColor ?? \"\").trim().toLowerCase() === darkenHex(entry, STROKE_DARKEN);\n}\n\n/**\n * Pick the next layer color: the first palette entry no current layer is\n * already using, so deleting a layer frees its color back up rather than\n * leaving the cycle permanently offset. Falls back to cycling by layer count\n * once every entry is in use.\n *\n * @param layers - The project's current layers. Only those actually wearing a\n * palette assignment count; see {@link wearsPaletteColor}.\n * @returns A `#rrggbb` color from {@link LAYER_PALETTE}.\n */\nexport function nextLayerPaletteColor(layers: readonly GeoLibreLayer[]): string {\n const styled = layers.filter((layer) => wearsPaletteColor(layer.style));\n const used = new Set(styled.map((layer) => layer.style.fillColor.trim().toLowerCase()));\n const free = LAYER_PALETTE.find((color) => !used.has(color));\n return free ?? LAYER_PALETTE[styled.length % LAYER_PALETTE.length];\n}\n\n/** The geometry family a layer is mostly made of. */\nexport type DominantGeometry = \"point\" | \"line\" | \"polygon\" | \"mixed\";\n\n/** Features sampled when deciding a collection's dominant geometry. */\nconst GEOMETRY_SAMPLE_SIZE = 500;\n\n/**\n * Classify a collection by the geometry family most of its features carry,\n * reading at most {@link GEOMETRY_SAMPLE_SIZE} features so a very large layer\n * does not pay a full scan for a styling default.\n *\n * @param geojson - The collection to inspect.\n * @returns The dominant family, or `\"mixed\"` when nothing has a clear majority\n * (or the collection is empty).\n */\nexport function dominantGeometry(geojson: FeatureCollection | undefined): DominantGeometry {\n const features = geojson?.features ?? [];\n if (!features.length) return \"mixed\";\n\n const counts = { point: 0, line: 0, polygon: 0 };\n const sampled = Math.min(features.length, GEOMETRY_SAMPLE_SIZE);\n for (let index = 0; index < sampled; index++) {\n switch (features[index]?.geometry?.type) {\n case \"Point\":\n case \"MultiPoint\":\n counts.point += 1;\n break;\n case \"LineString\":\n case \"MultiLineString\":\n counts.line += 1;\n break;\n case \"Polygon\":\n case \"MultiPolygon\":\n counts.polygon += 1;\n break;\n default:\n break;\n }\n }\n\n if (!counts.point && !counts.line && !counts.polygon) return \"mixed\";\n const [family, count] = Object.entries(counts).sort((a, b) => b[1] - a[1])[0] as [\n \"point\" | \"line\" | \"polygon\",\n number,\n ];\n // A clear majority of everything sampled, not just of the geometries this\n // switch understands: two points among three GeometryCollections is not a\n // point layer, so `sampled` is the denominator rather than the matched total.\n // And a majority, not merely the largest slice — a 40/35/25 split has no\n // geometry whose sizing suits the layer.\n return count / sampled > 0.5 ? family : \"mixed\";\n}\n\n/**\n * Per-geometry size and opacity overrides.\n *\n * One set of numbers cannot suit all three families: an opacity that lets a\n * basemap read through a polygon makes a point look washed out, and a stroke\n * heavy enough to see a line is a heavy outline on a polygon.\n */\nconst GEOMETRY_DEFAULTS: Record<DominantGeometry, Partial<LayerStyle>> = {\n // Solid and slightly smaller: a point is read as a dot, not a translucent blob.\n point: { circleRadius: 5, fillOpacity: 0.9, strokeWidth: 1 },\n // A line's stroke *is* the symbol, so it carries the weight.\n line: { strokeWidth: 2.5, fillOpacity: 1 },\n // Translucent enough that the basemap underneath stays readable.\n polygon: { fillOpacity: 0.45, strokeWidth: 1.5 },\n mixed: {},\n};\n\nexport interface InitialLayerStyleOptions {\n /** The layer's data, used to pick geometry-appropriate sizes. */\n geojson?: FeatureCollection;\n /** Current project layers, so the new layer gets an unused palette color. */\n layers?: readonly GeoLibreLayer[];\n /** Style values that win over the computed defaults (e.g. a restored style). */\n overrides?: Partial<LayerStyle>;\n}\n\n/**\n * Build the style a newly added vector layer starts with: the schema defaults,\n * plus its own palette color and geometry-appropriate sizing.\n *\n * @param options - Data and project context to derive the style from.\n * @returns A complete {@link LayerStyle}.\n */\nexport function initialLayerStyle(options: InitialLayerStyleOptions = {}): LayerStyle {\n const { geojson, layers = [], overrides } = options;\n const fillColor = nextLayerPaletteColor(layers);\n return {\n ...DEFAULT_LAYER_STYLE,\n fillColor,\n strokeColor: darkenHex(fillColor, STROKE_DARKEN),\n ...GEOMETRY_DEFAULTS[dominantGeometry(geojson)],\n ...overrides,\n };\n}\n\n/**\n * True when a layer still wears exactly what {@link initialLayerStyle} gave it.\n *\n * The renderer mode alone is too weak a test for \"untouched\": nudging the fill,\n * outline, or opacity leaves the mode on `\"single\"`, and a project restored\n * from disk can carry deliberate single-symbol styling. Both cases should stop\n * the Style panel volunteering suggestions, which is what this gates.\n *\n * The fill color cannot be compared against a fixed value — it is whichever\n * palette entry the layer drew — so the test is that it is *still a palette\n * entry* with the outline derived from it, which a hand-picked color will not\n * satisfy.\n *\n * @param style - The layer's current style.\n * @param geojson - Its data, so the geometry-derived sizes can be recomputed.\n * @returns `true` when nothing about the as-added look has been changed.\n */\nexport function isInitialLayerStyle(style: LayerStyle, geojson?: FeatureCollection): boolean {\n // Authored symbology, whatever the paint fields say.\n if (style.simpleStyleEnabled) return false;\n if (style.vectorStyleMode !== \"single\") return false;\n if (style.pointRenderer !== \"single\") return false;\n if ((style.vectorRules?.length ?? 0) > 0) return false;\n\n if (!wearsPaletteColor(style)) return false;\n\n const expected = { ...DEFAULT_LAYER_STYLE, ...GEOMETRY_DEFAULTS[dominantGeometry(geojson)] };\n return (\n style.fillOpacity === expected.fillOpacity &&\n style.strokeWidth === expected.strokeWidth &&\n style.circleRadius === expected.circleRadius\n );\n}\n","// Copy/paste layer symbology between layers (issue #1339), QGIS-style. The\n// transient clipboard slot and its two store actions live in `store.ts`; this\n// module holds the UI-free data layer so both the desktop app and tests consume\n// the same extract/apply logic.\n\nimport { DEFAULT_LAYER_STYLE, type GeoLibreLayer, type LayerStyle } from \"./types\";\nimport { isStyleLibraryTargetLayer } from \"./style-library\";\n\n/**\n * `metadata.sourceKind` marking a deck.gl COG raster managed by the\n * maplibre-gl-raster control. Mirrors `RASTER_SOURCE_KIND` in\n * `@geolibre/plugins` (which `@geolibre/core` must not depend on). The clipboard\n * test imports the real constant directly from the module that defines it\n * (`packages/plugins/src/plugins/raster-layer-sync.ts`, not the package barrel,\n * whose browser-only plugins crash under the Node test runner) and asserts a\n * layer tagged with it classifies as `\"raster\"`, so a rename of the upstream\n * constant makes the test fail here rather than silently dropping raster\n * copy/paste.\n */\nconst RASTER_SOURCE_KIND = \"maplibre-gl-raster\";\n\n/**\n * Which copy/paste style family a layer belongs to. Vector-styled layers and\n * deck.gl rasters store their symbology in different places (the {@link\n * LayerStyle} bag vs `metadata.rasterState`), so a paste only applies between\n * layers of the same family.\n */\nexport type LayerStyleClipboardKind = \"vector\" | \"raster\";\n\n/**\n * The `metadata.rasterState` keys a raster style paste carries over: the\n * appearance of the render (colormap, stretch range, gamma, nodata), but not\n * the data selection (`mode`/`bands`/`index`), which stays with the target so\n * pasting a style never points a raster at a band it does not have.\n */\nexport const RASTER_APPEARANCE_STATE_KEYS = [\n \"colormap\",\n \"reversed\",\n \"rescale\",\n \"nodata\",\n \"stretch\",\n \"gamma\",\n] as const;\n\n/**\n * Fields every clipboard entry carries, regardless of style family. Layer\n * `opacity` is deliberately excluded: like QGIS \"Paste Style\" and GeoLibre's\n * own Style Manager, copy/paste transfers symbology only and leaves the\n * target's opacity alone (opacity is a per-layer render setting, not style).\n *\n * {@link LayerStyle.blendMode} is deliberately *included*, even though the\n * Layers panel puts the Blend menu beside the opacity slider. The two differ in\n * kind despite sitting together: opacity is a transient \"turn this down so I\n * can see through it\" knob, while a blend mode is a cartographic decision about\n * how the layer reads against the map -- colour over a hillshade is a look, and\n * copying a look should bring it. QGIS agrees; its layer style, and so its\n * Paste Style, carries the blending mode. `tests/layer-style-clipboard.test.ts`\n * pins both halves of this so neither is changed by accident.\n */\ninterface CopiedLayerStyleBase {\n /** Source layer name, surfaced in the paste tooltip and status message. */\n sourceName: string;\n}\n\n/** A copied vector layer style: the whole {@link LayerStyle} bag. */\nexport interface CopiedVectorStyle extends CopiedLayerStyleBase {\n kind: \"vector\";\n /** Deep-cloned full vector layer style. */\n style: LayerStyle;\n}\n\n/**\n * A copied deck.gl raster style: the `metadata.rasterState` appearance and the\n * classification symbology. There is no `style` field — the vector\n * {@link LayerStyle} bag holds nothing a deck.gl raster renders from.\n */\nexport interface CopiedRasterStyle extends CopiedLayerStyleBase {\n kind: \"raster\";\n /** Raster visualization state (`metadata.rasterState`). */\n rasterState?: Record<string, unknown>;\n /** Whether the source carried a `metadata.rasterSymbology`. */\n hasRasterSymbology: boolean;\n /** Raster classification symbology (`metadata.rasterSymbology`). */\n rasterSymbology?: unknown;\n}\n\n/**\n * A layer's symbology captured for the paste clipboard. A discriminated union\n * on {@link LayerStyleClipboardKind} so raster-only fields cannot be read off a\n * vector entry (or vice versa); a paste only applies between layers of the same\n * family.\n */\nexport type CopiedLayerStyle = CopiedVectorStyle | CopiedRasterStyle;\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * The copy/paste style family of a layer, or `null` when the layer has no\n * copyable symbology. Deck.gl rasters are `\"raster\"`; the vector-styled layers\n * the Style Manager targets are `\"vector\"`.\n *\n * @param layer - The layer to classify.\n * @returns The style family, or `null`.\n */\nexport function copyableLayerStyleKind(layer: GeoLibreLayer): LayerStyleClipboardKind | null {\n if (layer.metadata.sourceKind === RASTER_SOURCE_KIND) return \"raster\";\n if (isStyleLibraryTargetLayer(layer.type)) return \"vector\";\n return null;\n}\n\n/**\n * Snapshot a layer's style for the clipboard, deep-cloned so later edits to the\n * live layer cannot mutate the copied entry.\n *\n * @param layer - The source layer to copy from.\n * @returns The clipboard entry, or `null` when the layer has no copyable\n * symbology.\n */\nexport function extractCopiedLayerStyle(layer: GeoLibreLayer): CopiedLayerStyle | null {\n const kind = copyableLayerStyleKind(layer);\n if (!kind) return null;\n if (kind === \"vector\") {\n return {\n kind,\n sourceName: layer.name,\n style: structuredClone({ ...DEFAULT_LAYER_STYLE, ...layer.style }),\n };\n }\n // A raster entry never carries the vector `style` bag, so the full-style\n // clone is skipped here.\n const rasterState = layer.metadata.rasterState;\n const rasterSymbology = layer.metadata.rasterSymbology;\n // Only a real symbology object counts as \"has symbology\". A missing or null\n // value (the latter only reachable via a hand-edited project file) both mean\n // \"none\", so the paste clears the target's symbology rather than writing a\n // literal null onto it — symmetric with the delete path in applyCopiedLayerStyle.\n const hasRasterSymbology = isPlainObject(rasterSymbology);\n return {\n kind,\n sourceName: layer.name,\n rasterState: isPlainObject(rasterState) ? structuredClone(rasterState) : undefined,\n hasRasterSymbology,\n ...(hasRasterSymbology ? { rasterSymbology: structuredClone(rasterSymbology) } : {}),\n };\n}\n\n/**\n * Build the {@link GeoLibreLayer} patch that pastes a copied style onto a\n * target layer, or `null` when the target's style family does not match the\n * clipboard entry's (so the caller can disable the paste action).\n *\n * @param target - The layer to paste onto.\n * @param copied - The clipboard entry.\n * @returns A partial layer patch to merge, or `null` when incompatible.\n */\nexport function applyCopiedLayerStyle(\n target: GeoLibreLayer,\n copied: CopiedLayerStyle,\n): Partial<GeoLibreLayer> | null {\n if (copyableLayerStyleKind(target) !== copied.kind) return null;\n if (copied.kind === \"vector\") {\n // The whole style is applied verbatim, including attribute-bound fields\n // (`vectorStyleProperty` and its stops/rules, `labels.field`,\n // `extrusionHeightProperty`, `proportionalSizeProperty`, `diagramFields`).\n // This is deliberate QGIS \"Paste Style\" behavior and matches the Style\n // Manager's full-\"style\" preset: a target lacking those attributes just\n // renders that facet inert (no classification / labels), which the user\n // re-points, rather than the paste silently dropping parts of the style.\n return { style: structuredClone(copied.style) };\n }\n // Merge the appearance keys onto the target's existing rasterState, keeping\n // its own `mode`/`bands`/`index` so the paste restyles the layer without\n // repointing it at the source's band selection.\n const targetState = isPlainObject(target.metadata.rasterState) ? target.metadata.rasterState : {};\n const source = copied.rasterState ?? {};\n const expectedRescaleLength = rescaleEntryCount(targetState);\n const mergedState: Record<string, unknown> = { ...targetState };\n for (const key of RASTER_APPEARANCE_STATE_KEYS) {\n if (!(key in source)) continue;\n // `rescale` is a per-channel min/max array whose length is set by the\n // render mode (one entry for single/index, one per band for rgb), not by\n // `bands.length` (index mode holds two operand bands but a single-entry\n // rescale). Copying a rescale whose length disagrees with the target's mode\n // — e.g. an RGB source's 3 entries onto a single-band target — would leave a\n // rescale that no longer matches the preserved render mode, the same \"band\n // it does not have\" hazard one level down. Skip it then and let the target\n // keep its own stretch. A null source rescale (auto) is safe to carry over.\n if (\n key === \"rescale\" &&\n Array.isArray(source.rescale) &&\n source.rescale.length !== expectedRescaleLength\n ) {\n continue;\n }\n mergedState[key] = structuredClone(source[key]);\n }\n const metadata: Record<string, unknown> = { ...target.metadata, rasterState: mergedState };\n if (copied.hasRasterSymbology) {\n metadata.rasterSymbology = structuredClone(copied.rasterSymbology);\n } else {\n delete metadata.rasterSymbology;\n }\n return { metadata };\n}\n\n/**\n * Number of `rescale` entries a rasterState of this render mode expects: one\n * per band for `\"rgb\"`, a single entry for `\"single\"` and `\"index\"` (the\n * latter stretches one derived index even though it reads two operand bands).\n */\nfunction rescaleEntryCount(state: Record<string, unknown>): number {\n if (state.mode === \"rgb\") return Array.isArray(state.bands) ? state.bands.length : 3;\n return 1;\n}\n","// Export every layer's style to one JSON file and apply such a file back by\n// layer name: Project → Export → Export Layer Styles, Project → Import →\n// Import Layer Styles, and the Startup setting that restyles each newly added\n// layer whose name matches an entry. A style here is exactly what copy/paste\n// style carries (see `layer-style-clipboard.ts`), so an exported entry restyles\n// a layer the same way pasting a copied style onto it would.\n\nimport {\n applyCopiedLayerStyle,\n type CopiedLayerStyle,\n copyableLayerStyleKind,\n extractCopiedLayerStyle,\n type LayerStyleClipboardKind,\n RASTER_APPEARANCE_STATE_KEYS,\n} from \"./layer-style-clipboard\";\nimport { sanitizeLayerStylePatch } from \"./style-library\";\nimport { DEFAULT_LAYER_STYLE, type GeoLibreLayer, type LayerStyle } from \"./types\";\n\n/** `type` tag identifying a layer styles file. */\nexport const LAYER_STYLES_FILE_TYPE = \"geolibre-layer-styles\";\n\n/** Format version written by {@link serializeLayerStylesFile}. */\nexport const LAYER_STYLES_FILE_VERSION = 1;\n\n/** One layer's style in a layer styles file, keyed by the layer's name. */\nexport type LayerStyleFileEntry =\n | {\n /** Name of the layer the style was exported from; the match key. */\n layerName: string;\n kind: \"vector\";\n /** The full vector {@link LayerStyle} bag. */\n style: LayerStyle;\n }\n | {\n layerName: string;\n kind: \"raster\";\n /** Raster visualization state (`metadata.rasterState`). */\n rasterState?: Record<string, unknown>;\n /** Raster classification symbology (`metadata.rasterSymbology`). */\n rasterSymbology?: Record<string, unknown>;\n };\n\n/** The on-disk layer styles document. */\nexport interface LayerStylesFile {\n type: typeof LAYER_STYLES_FILE_TYPE;\n version: typeof LAYER_STYLES_FILE_VERSION;\n styles: LayerStyleFileEntry[];\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Capture the style of every layer that has one. Layers without copyable\n * symbology (basemap tiles, 3D tiles, ...) are skipped. Layers that share a\n * name are all written, in stack order; the first one wins when the file is\n * applied (see {@link findLayerStyleEntryIndex}).\n *\n * @param layers - The layers to export, in store (bottom-to-top) order.\n * @returns The style entries.\n */\nexport function extractLayerStyleEntries(layers: readonly GeoLibreLayer[]): LayerStyleFileEntry[] {\n const entries: LayerStyleFileEntry[] = [];\n for (const layer of layers) {\n const copied = extractCopiedLayerStyle(layer);\n if (!copied) continue;\n if (copied.kind === \"vector\") {\n entries.push({ layerName: layer.name, kind: \"vector\", style: copied.style });\n } else {\n entries.push({\n layerName: layer.name,\n kind: \"raster\",\n ...(copied.rasterState ? { rasterState: copied.rasterState } : {}),\n ...(isPlainObject(copied.rasterSymbology)\n ? { rasterSymbology: copied.rasterSymbology }\n : {}),\n });\n }\n }\n return entries;\n}\n\n/**\n * Serialize style entries into the JSON written by Export Layer Styles.\n *\n * @param entries - The entries to write.\n * @returns Pretty-printed file content.\n */\nexport function serializeLayerStylesFile(entries: readonly LayerStyleFileEntry[]): string {\n const file: LayerStylesFile = {\n type: LAYER_STYLES_FILE_TYPE,\n version: LAYER_STYLES_FILE_VERSION,\n styles: [...entries],\n };\n return JSON.stringify(file, null, 2);\n}\n\nconst RASTER_STRETCHES: readonly unknown[] = [\"linear\", \"log\", \"sqrt\"];\nconst RASTER_NODATA_MODES: readonly unknown[] = [\"off\", \"auto\"];\n\nfunction isFiniteNumber(value: unknown): value is number {\n return typeof value === \"number\" && Number.isFinite(value);\n}\n\n/**\n * Validators for the raster appearance keys a style file may carry, by the\n * types `maplibre-gl-raster`'s `RasterLayerState` declares for them.\n */\nconst RASTER_APPEARANCE_VALIDATORS: Record<\n (typeof RASTER_APPEARANCE_STATE_KEYS)[number],\n (value: unknown) => boolean\n> = {\n colormap: (value) => typeof value === \"string\" && value.length > 0,\n reversed: (value) => typeof value === \"boolean\",\n rescale: (value) =>\n value === null ||\n (Array.isArray(value) &&\n // An empty array is not a meaningful rescale (as in savedRasterState).\n value.length > 0 &&\n value.every(\n (range) =>\n Array.isArray(range) &&\n range.length === 2 &&\n isFiniteNumber(range[0]) &&\n isFiniteNumber(range[1]),\n )),\n nodata: (value) => isFiniteNumber(value) || RASTER_NODATA_MODES.includes(value),\n stretch: (value) => RASTER_STRETCHES.includes(value),\n gamma: (value) => isFiniteNumber(value) && value > 0,\n};\n\n/**\n * Keep only the raster appearance keys a style applies, each with a value of\n * the type the raster renderer expects. The data selection (`mode`, `bands`,\n * ...) is dropped too: a style never applies it.\n */\nfunction sanitizeRasterAppearanceState(state: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const key of RASTER_APPEARANCE_STATE_KEYS) {\n if (key in state && RASTER_APPEARANCE_VALIDATORS[key](state[key])) {\n out[key] = structuredClone(state[key]);\n }\n }\n return out;\n}\n\n/**\n * Coerce untrusted entries (a hand-edited file, or a copy kept in settings)\n * into clean ones. An entry without a name or a known kind is dropped; a\n * vector style is sanitized and completed against the default style, and a\n * raster state keeps only well-typed appearance keys, so applying an entry\n * never hands a renderer an unknown, missing or wrong-typed field.\n *\n * @param value - The raw `styles` array.\n * @returns The usable entries.\n */\nexport function normalizeLayerStyleEntries(value: unknown): LayerStyleFileEntry[] {\n if (!Array.isArray(value)) return [];\n const entries: LayerStyleFileEntry[] = [];\n for (const item of value) {\n if (!isPlainObject(item)) continue;\n const layerName = typeof item.layerName === \"string\" ? item.layerName : \"\";\n if (!layerName.trim()) continue;\n if (item.kind === \"vector\") {\n if (!isPlainObject(item.style)) continue;\n entries.push({\n layerName,\n kind: \"vector\",\n style: { ...structuredClone(DEFAULT_LAYER_STYLE), ...sanitizeLayerStylePatch(item.style) },\n });\n } else if (item.kind === \"raster\") {\n entries.push({\n layerName,\n kind: \"raster\",\n ...(isPlainObject(item.rasterState)\n ? { rasterState: sanitizeRasterAppearanceState(item.rasterState) }\n : {}),\n // The symbology's schema lives in `@geolibre/plugins`, which validates\n // it on every read (`savedRasterSymbology`), as it does for a project\n // file; a malformed one renders as unclassified there.\n ...(isPlainObject(item.rasterSymbology)\n ? { rasterSymbology: structuredClone(item.rasterSymbology) }\n : {}),\n });\n }\n }\n return entries;\n}\n\n/**\n * Parse a file written by {@link serializeLayerStylesFile}.\n *\n * @param json - The file content.\n * @returns The normalized entries.\n * @throws Error when the content is not a layer styles file, comes from a\n * newer format version, or holds no usable entries.\n */\nexport function parseLayerStylesFile(json: string): LayerStyleFileEntry[] {\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n throw new Error(\"Not a valid layer styles file (invalid JSON).\");\n }\n if (!isPlainObject(parsed) || parsed.type !== LAYER_STYLES_FILE_TYPE) {\n throw new Error(\"Not a valid layer styles file.\");\n }\n // Refuse a newer format rather than misread it with this version's rules.\n if (parsed.version !== LAYER_STYLES_FILE_VERSION) {\n throw new Error(\"Unsupported layer styles file version.\");\n }\n const entries = normalizeLayerStyleEntries(parsed.styles);\n if (entries.length === 0) {\n throw new Error(\"The layer styles file holds no usable styles.\");\n }\n return entries;\n}\n\nfunction nameKey(name: string): string {\n return name.trim().toLocaleLowerCase();\n}\n\n/**\n * Find the entry for a layer name: an exact match first, then one that differs\n * only in case or surrounding whitespace, so `Roads` still picks up a style\n * saved for `roads`. The first match in file order wins.\n *\n * @param entries - The style entries.\n * @param layerName - The layer name to look up.\n * @param kind - When given, only entries of this style family are considered,\n * so a raster style saved under a shared name does not shadow the vector one.\n * @returns The matching entry's index in `entries`, or -1.\n */\nexport function findLayerStyleEntryIndex(\n entries: readonly LayerStyleFileEntry[],\n layerName: string,\n kind?: LayerStyleClipboardKind,\n): number {\n const ofKind = (entry: LayerStyleFileEntry) => !kind || entry.kind === kind;\n const exact = entries.findIndex((entry) => ofKind(entry) && entry.layerName === layerName);\n if (exact >= 0) return exact;\n const key = nameKey(layerName);\n return entries.findIndex((entry) => ofKind(entry) && nameKey(entry.layerName) === key);\n}\n\nfunction toCopiedLayerStyle(entry: LayerStyleFileEntry): CopiedLayerStyle {\n if (entry.kind === \"vector\") {\n return { kind: \"vector\", sourceName: entry.layerName, style: entry.style };\n }\n return {\n kind: \"raster\",\n sourceName: entry.layerName,\n rasterState: entry.rasterState,\n hasRasterSymbology: entry.rasterSymbology !== undefined,\n ...(entry.rasterSymbology !== undefined ? { rasterSymbology: entry.rasterSymbology } : {}),\n };\n}\n\n/** A layer restyled from a style entry, and the entry it took. */\nexport interface LayerStyleMatch {\n /** Index of the applied entry in the entries array. */\n entryIndex: number;\n /** The patch that applies it. */\n patch: Partial<GeoLibreLayer>;\n}\n\n/**\n * Build the patch that restyles a layer from the entry matching its name, with\n * the same semantics as pasting a copied style: a vector style replaces the\n * whole style bag, a raster style merges its appearance keys and keeps the\n * layer's own band selection, and layer opacity is left alone.\n *\n * @param layer - The layer to restyle.\n * @param entries - The style entries.\n * @returns The matched entry and patch, or `null` when no entry of the layer's\n * style family matches its name (or the layer has no stylable symbology).\n */\nexport function matchLayerStyleEntry(\n layer: GeoLibreLayer,\n entries: readonly LayerStyleFileEntry[],\n): LayerStyleMatch | null {\n const kind = copyableLayerStyleKind(layer);\n if (!kind) return null;\n const entryIndex = findLayerStyleEntryIndex(entries, layer.name, kind);\n if (entryIndex < 0) return null;\n const patch = applyCopiedLayerStyle(layer, toCopiedLayerStyle(entries[entryIndex]));\n return patch ? { entryIndex, patch } : null;\n}\n","/**\n * The layer stack: adding, removing, editing, restyling and reordering layer\n * records, plus the copy/paste style clipboard. `layers` is tracked by undo\n * history. `removeLayer` also scrubs every other slice's references to the\n * deleted layer, so it reads and writes the whole state through `set`.\n */\nimport type { FeatureCollection } from \"geojson\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport { isSourceDerivedLayerName, uniqueImportedLayerName } from \"../file-name\";\nimport { initialLayerStyle } from \"../layer-defaults\";\nimport { normalizeGroupContiguity } from \"../layer-groups\";\nimport {\n scrubCommentsForRemovedLayers,\n scrubLegendForRemovedLayers,\n scrubWidgetsForRemovedLayers,\n} from \"../layer-ref-scrub\";\nimport {\n applyCopiedLayerStyle,\n type CopiedLayerStyle,\n extractCopiedLayerStyle,\n} from \"../layer-style-clipboard\";\nimport { matchLayerStyleEntry, type LayerStyleFileEntry } from \"../layer-style-file\";\nimport { applyJoinsToLayer, cascadeLayerJoinRefresh } from \"../joins\";\nimport { normalizeLayerDescriptiveMetadata } from \"../layer-descriptive-metadata\";\nimport { withRasterSourceDefaults } from \"../raster-source-defaults\";\nimport { scrubPrintLayoutForRemovedLayers } from \"../print-layout-config\";\nimport { identifyStateWithoutLayers } from \"./session-slice\";\nimport {\n DEFAULT_LAYER_STYLE,\n shouldUseTiledRendering,\n type AddTileLayerOptions,\n type AttributeFormConfig,\n type EditorTrackingConfig,\n type GeoLibreLayer,\n type LayerJoin,\n type LayerDescriptiveMetadata,\n type LayerPopupConfig,\n type LayerLevelFilter,\n type LayerQuickFilter,\n type LayerStyle,\n type LevelFilterMode,\n type LayerVirtualField,\n} from \"../types\";\nimport { featureCollectionToGeoArrow, type GeoArrowBatch } from \"../geoarrow\";\nimport { sameLevelLink, selectLevel } from \"../level-filter\";\nimport { resolveLayerTileAddress } from \"../tile-address\";\nimport { hasSimpleStyleProperties } from \"../vector-color\";\nimport {\n cascadeJoinRefreshForRemoved,\n scrubSecondaryPaneLayerVisibility,\n scrubStorymapLayerRefs,\n} from \"./layer-removal\";\nimport type { SliceCreator } from \"./types\";\n\n/**\n * The layer types {@link AppState.addTileLayer} accepts. Each renders through\n * the raster tile path, which is why the layer's `source.type` is always\n * `\"raster\"`.\n */\nconst RASTER_TILE_LAYER_TYPES: ReadonlySet<string> = new Set<\n NonNullable<AddTileLayerOptions[\"type\"]>\n>([\"xyz\", \"wms\", \"wmts\", \"raster\"]);\n\nexport interface LayersSlice {\n layers: GeoLibreLayer[];\n\n addLayer: (layer: GeoLibreLayer, beforeLayerId?: string | null) => void;\n removeLayer: (id: string) => void;\n updateLayer: (id: string, patch: Partial<GeoLibreLayer>) => void;\n setLayerVisibility: (id: string, visible: boolean) => void;\n setLayerOpacity: (id: string, opacity: number) => void;\n setLayerStyle: (id: string, style: Partial<LayerStyle>) => void;\n /**\n * Transient clipboard holding a layer's symbology, captured by\n * {@link copyLayerStyle} and applied by {@link pasteLayerStyle} (copy/paste\n * styles, issue #1339). Runtime-only: excluded from undo history\n * (`partialize` never lists it) and from the saved project. `null` until a\n * style is copied this session. Cleared by `newProject`/`loadProject` so a\n * paste can't apply an entry from a different project; it deliberately\n * survives undo/redo within a project (it holds a deep snapshot, not a live\n * layer reference, so a paste stays valid even if the source layer is undone\n * away — only the displayed source name may then be stale).\n */\n copiedLayerStyle: CopiedLayerStyle | null;\n /**\n * Snapshot the given layer's style into {@link copiedLayerStyle} so it can be\n * pasted onto a compatible layer. No-op when the layer is missing or has no\n * copyable symbology (leaving any prior clipboard entry untouched). Returns\n * `true` when a style was captured, so callers can skip the confirmation on a\n * no-op.\n */\n copyLayerStyle: (id: string) => boolean;\n /**\n * Apply the {@link copiedLayerStyle} clipboard entry onto the given layer.\n * No-op when the clipboard is empty, the layer is missing, or the entry's\n * style family does not match the target layer's. Returns `true` when the\n * style was applied, so callers can skip the confirmation on a no-op.\n */\n pasteLayerStyle: (id: string) => boolean;\n /**\n * Restyle layers from layer styles file entries matched by layer name\n * (Import Layer Styles, and the Startup setting's auto-apply). Each matched\n * layer is patched the way {@link pasteLayerStyle} would patch it, all in a\n * single store update so the import is one undo step.\n *\n * @param entries - The parsed style entries.\n * @param layerIds - Restrict the restyle to these layers; every layer when\n * omitted.\n * @returns Each restyled layer's id with the index of the entry it took, in\n * stack order.\n */\n applyLayerStyleEntries: (\n entries: readonly LayerStyleFileEntry[],\n layerIds?: readonly string[],\n ) => { layerId: string; entryIndex: number }[];\n /**\n * Replace a layer's persistent attribute joins and immediately re-derive its\n * joined columns (strip what the previous joins added, apply the new list).\n * Pass an empty array to detach every join and restore the base attributes.\n */\n setLayerJoins: (id: string, joins: LayerJoin[]) => void;\n /**\n * Replace the layer's Attribute Form designer configuration (per-field edit\n * widgets, constraints, conditional visibility). Pass `undefined` to remove\n * the form config entirely.\n */\n setLayerAttributeForm: (id: string, attributeForm: AttributeFormConfig | undefined) => void;\n /**\n * Replace the layer's popup/tooltip design (which fields the Identify popup\n * shows, in what order and under what labels, plus the hover tooltip). Pass\n * `undefined` to restore the default full-property dump.\n */\n setLayerPopup: (id: string, popup: LayerPopupConfig | undefined) => void;\n /**\n * Replace the layer's user-authored catalog metadata (title, abstract,\n * keywords, license, contact, lineage, temporal extent, links). The record\n * is normalized first; one that cleans to nothing (or `undefined`) removes\n * the metadata entirely. One call is one undo step.\n */\n setLayerDescriptiveMetadata: (id: string, metadata: LayerDescriptiveMetadata | undefined) => void;\n /**\n * Replace the layer's editor tracking configuration (whether creation/edit\n * author and timestamp columns are maintained, and under which names). Pass\n * `undefined` to drop the configuration entirely.\n */\n setLayerEditorTracking: (id: string, editorTracking: EditorTrackingConfig | undefined) => void;\n /**\n * Replace a layer's virtual fields and immediately re-derive its computed\n * columns (strip what the previous fields added, evaluate the new list).\n * Pass an empty array to detach every virtual field.\n */\n setLayerVirtualFields: (id: string, fields: LayerVirtualField[]) => void;\n /**\n * Replace a layer's quick filters (issue #2114). The controls persist with\n * the project and are compiled to a MapLibre filter at sync time, so this\n * only stores state — nothing re-derives the layer's data. Pass an empty\n * array to remove every control.\n */\n setLayerQuickFilters: (id: string, filters: LayerQuickFilter[]) => void;\n /** Set or clear the project-persisted expression filter for a layer. */\n setLayerFilterExpression: (id: string, expression: unknown[] | null) => void;\n /**\n * Link a layer to the floor picker (issue #3341), or unlink it with `null`.\n * Linking a layer draws every floor until a floor is chosen.\n */\n setLayerLevelFilter: (id: string, filter: LayerLevelFilter | null) => void;\n /**\n * Choose a floor in the floor picker: every linked layer is narrowed to\n * `level` (`null` for every floor) under `mode`, in one update, with the\n * matching values resolved from each layer's own features.\n */\n setLevelSelection: (level: string | null, mode: LevelFilterMode) => void;\n /**\n * Write floor picker links the picker re-resolved on its own (values missing\n * from a saved project, or data that changed). Not a user edit: the project\n * stays clean and, called through `withoutHistory`, no undo step is added.\n */\n reconcileLevelLinks: (updates: { id: string; levelFilter: LayerLevelFilter }[]) => void;\n reorderLayer: (id: string, direction: \"up\" | \"down\") => void;\n moveLayer: (id: string, targetIndex: number) => void;\n moveLayersRelative: (\n layerIds: string[],\n targetLayerId: string,\n position: \"above\" | \"below\",\n ) => void;\n addGeoJsonLayer: (\n name: string,\n geojson: FeatureCollection,\n sourcePath?: string,\n beforeLayerId?: string | null,\n extra?: { geoarrow?: GeoArrowBatch; metadata?: Record<string, unknown> },\n ) => string;\n /**\n * Add a georeferenced image overlay (a MapLibre `image` source rendered as a\n * raster layer) from an image URL and its four corner coordinates, and return\n * its id. Used for KML/KMZ `<GroundOverlay>` imports; the layer persists and\n * renders exactly like a Raster Georeferencer overlay. Corners are `[lng,\n * lat]` in top-left, top-right, bottom-right, bottom-left order.\n */\n addImageOverlayLayer: (\n name: string,\n source: { url: string; coordinates: [number, number][] },\n options?: {\n opacity?: number;\n bounds?: [number, number, number, number];\n sourcePath?: string;\n /** Initial visibility (default true); a time-slider frame past the first\n * starts hidden. */\n visible?: boolean;\n /** Epoch-ms time bounds of a KML `<TimeSpan>`/`<TimeStamp>` frame; the\n * Time Slider toggles this frame's visibility by the current date. */\n timeSpan?: { begin: number | null; end: number | null };\n /**\n * What produced the overlay, e.g. a NetCDF grid baked to pixels. Defaults\n * to the KML ground overlay this was first written for; panels gate their\n * per-source controls on it.\n */\n sourceKind?: string;\n /** Extra metadata merged onto the layer (e.g. a symbology record). */\n metadata?: Record<string, unknown>;\n },\n beforeLayerId?: string | null,\n ) => string;\n /**\n * Add a native raster tile layer (XYZ, WMS, or WMTS) from one or more tile\n * URL templates and return its id. The layer appears in the Layers panel and\n * persists with the project exactly like a layer added through the Add Data\n * dialog, so callers (e.g. an external plugin) do not have to touch MapLibre\n * directly. See {@link AddTileLayerOptions}.\n */\n addTileLayer: (\n name: string,\n options: AddTileLayerOptions,\n beforeLayerId?: string | null,\n ) => string;\n}\n\nexport const createLayersSlice: SliceCreator<LayersSlice> = (set, get) => ({\n layers: [],\n copiedLayerStyle: null,\n\n addLayer: (inputLayer, beforeLayerId = null) =>\n set((s) => {\n let layer = withRasterSourceDefaults(inputLayer);\n const layers = [...s.layers];\n // Plugin source identifiers (for example pmtiles://) are not local files.\n const { sourcePath } = layer;\n const localSource =\n sourcePath &&\n (!/^[a-z][a-z0-9+.-]*:\\/\\//i.test(sourcePath) || /^(content|file):\\/\\//i.test(sourcePath));\n // Only filename-derived names are deduplicated; an explicit name (an\n // embedded document title, a tool output label, a user-typed name)\n // is kept as supplied.\n if (localSource && isSourceDerivedLayerName(layer.name, sourcePath)) {\n layer = {\n ...layer,\n name: uniqueImportedLayerName(\n layer.name,\n layers.map((item) => item.name),\n ),\n };\n }\n const beforeIndex = beforeLayerId ? layers.findIndex((l) => l.id === beforeLayerId) : -1;\n const layerWithBeforeId =\n beforeLayerId && beforeIndex < 0\n ? { ...layer, beforeId: beforeLayerId }\n : { ...layer, beforeId: layer.beforeId };\n if (beforeIndex >= 0) {\n layers.splice(beforeIndex, 0, layerWithBeforeId);\n } else {\n layers.push(layerWithBeforeId);\n }\n return {\n layers,\n selectedLayerId: layer.id,\n isDirty: true,\n };\n }),\n\n removeLayer: (id) =>\n set((s) => ({\n // Re-derive any layer whose joins consumed the removed layer: with\n // the source gone the join resolves to nothing, so its previously\n // materialized columns strip away instead of staying frozen (the\n // join definition itself stays, shown as missing in the Joins UI).\n layers: cascadeJoinRefreshForRemoved(\n s.layers.filter((l) => l.id !== id),\n id,\n ),\n secondaryMapViews: scrubSecondaryPaneLayerVisibility(s.secondaryMapViews, id),\n // Drop storymap chapter enter/exit opacity rows that pointed at the\n // removed layer so they do not keep a dangling id across save/reload.\n storymap: scrubStorymapLayerRefs(s.storymap, id),\n widgets: scrubWidgetsForRemovedLayers(s.widgets, id),\n comments: scrubCommentsForRemovedLayers(s.comments, id),\n legend: scrubLegendForRemovedLayers(s.legend, id),\n // Clear a Print Layout data/atlas block built on the removed layer,\n // so a save that follows the delete cannot write a dangling id.\n printLayout: scrubPrintLayoutForRemovedLayers(s.printLayout, id),\n selectedLayerId:\n s.selectedLayerId === id\n ? (s.layers.find((l) => l.id !== id)?.id ?? null)\n : s.selectedLayerId,\n selectedFeatureId: s.selectedLayerId === id ? null : s.selectedFeatureId,\n selectedFeatureIds: s.selectedLayerId === id ? [] : s.selectedFeatureIds,\n ...identifyStateWithoutLayers(s, new Set([id])),\n ui: {\n ...s.ui,\n selectByExpressionLayerId:\n s.ui.selectByExpressionLayerId === id ? null : s.ui.selectByExpressionLayerId,\n selectByLocationLayerId:\n s.ui.selectByLocationLayerId === id ? null : s.ui.selectByLocationLayerId,\n loadEditorFeaturesLayerId:\n s.ui.loadEditorFeaturesLayerId === id ? null : s.ui.loadEditorFeaturesLayerId,\n },\n isDirty: true,\n })),\n\n updateLayer: (id, patch) =>\n set((s) => {\n let layers = s.layers.map((l) => {\n if (l.id !== id) return l;\n const updated = { ...l, ...patch };\n // If a patch explicitly replaces geojson but does not explicitly supply geoarrow,\n // clear the existing GeoArrow batch to avoid retaining stale geometry.\n if (patch.geojson !== undefined && !(\"geoarrow\" in patch)) {\n delete updated.geoarrow;\n } else if (\"geoarrow\" in patch) {\n if (patch.geoarrow === null || patch.geoarrow === undefined) {\n delete updated.geoarrow;\n } else {\n updated.geoarrow = patch.geoarrow;\n }\n }\n return updated;\n });\n // A geojson replacement re-derives the layer's derived columns\n // (persistent joins, then virtual fields): on the layer itself\n // (file reload, attribute edits, processing writes — derived\n // columns stay derived, QGIS-style), then transitively on every\n // layer whose joins consume the updated one, so editing a join\n // table refreshes its targets and their dependents in turn. The\n // `patch.joins`/`patch.virtualFields` guard exists for external\n // callers of this public store API (plugins, programmatic loads):\n // a patch that carries `geojson` alongside the definitions is taken\n // verbatim as already-derived state — no in-repo caller does this\n // today.\n if (patch.geojson !== undefined) {\n if (patch.joins === undefined && patch.virtualFields === undefined) {\n layers = layers.map((l) =>\n l.id === id && (l.joins?.length || l.virtualFields?.length)\n ? applyJoinsToLayer(l, layers)\n : l,\n );\n }\n layers = cascadeLayerJoinRefresh(layers, id);\n }\n return { layers, isDirty: true };\n }),\n\n setLayerJoins: (id, joins) =>\n set((s) => {\n let layers = s.layers.map((l) => (l.id === id ? applyJoinsToLayer(l, s.layers, joins) : l));\n // Changing this layer's joins changes its materialized columns, so\n // layers joining against it (directly or transitively) re-derive too.\n layers = cascadeLayerJoinRefresh(layers, id);\n return { layers, isDirty: true };\n }),\n\n setLayerAttributeForm: (id, attributeForm) => get().updateLayer(id, { attributeForm }),\n setLayerPopup: (id, popup) => get().updateLayer(id, { popup }),\n setLayerDescriptiveMetadata: (id, metadata) =>\n get().updateLayer(id, { descriptiveMetadata: normalizeLayerDescriptiveMetadata(metadata) }),\n\n setLayerEditorTracking: (id, editorTracking) => get().updateLayer(id, { editorTracking }),\n\n setLayerVirtualFields: (id, fields) =>\n set((s) => {\n let layers = s.layers.map((l) =>\n l.id === id ? applyJoinsToLayer(l, s.layers, undefined, fields) : l,\n );\n // Virtual columns are ordinary materialized properties, so a layer\n // joining against this one (directly or transitively) sees them and\n // must re-derive too.\n layers = cascadeLayerJoinRefresh(layers, id);\n return { layers, isDirty: true };\n }),\n\n setLayerQuickFilters: (id, filters) =>\n get().updateLayer(id, { quickFilters: filters.length > 0 ? filters : undefined }),\n\n setLayerFilterExpression: (id, expression) =>\n get().updateLayer(id, { filterExpression: expression ?? undefined }),\n\n setLayerLevelFilter: (id, filter) => get().updateLayer(id, { levelFilter: filter ?? undefined }),\n\n setLevelSelection: (level, mode) =>\n set((s) => {\n let changed = false;\n const layers = s.layers.map((layer) => {\n if (!layer.levelFilter) return layer;\n const next = selectLevel(layer.levelFilter, layer.geojson?.features, level, mode);\n // Re-clicking the floor already shown is not an edit.\n if (sameLevelLink(layer.levelFilter, next)) return layer;\n changed = true;\n return { ...layer, levelFilter: next };\n });\n return changed ? { layers, isDirty: true } : {};\n }),\n\n reconcileLevelLinks: (updates) =>\n set((s) => {\n if (updates.length === 0) return {};\n const byId = new Map(updates.map((update) => [update.id, update.levelFilter]));\n return {\n layers: s.layers.map((layer) =>\n byId.has(layer.id) ? { ...layer, levelFilter: byId.get(layer.id) } : layer,\n ),\n };\n }),\n\n setLayerVisibility: (id, visible) => get().updateLayer(id, { visible }),\n\n setLayerOpacity: (id, opacity) => get().updateLayer(id, { opacity }),\n\n setLayerStyle: (id, style) =>\n set((s) => ({\n layers: s.layers.map((l) => (l.id === id ? { ...l, style: { ...l.style, ...style } } : l)),\n isDirty: true,\n })),\n\n copyLayerStyle: (id) => {\n const layer = get().layers.find((l) => l.id === id);\n if (!layer) return false;\n const copied = extractCopiedLayerStyle(layer);\n // Leave any prior clipboard entry in place when this layer is not\n // copyable, so opening a non-stylable layer's menu never clears it.\n if (!copied) return false;\n set({ copiedLayerStyle: copied });\n return true;\n },\n\n pasteLayerStyle: (id) => {\n const s = get();\n const copied = s.copiedLayerStyle;\n if (!copied) return false;\n const layer = s.layers.find((l) => l.id === id);\n if (!layer) return false;\n const patch = applyCopiedLayerStyle(layer, copied);\n if (!patch) return false;\n // Go through updateLayer so the paste picks up any cross-cutting layer\n // update logic (it also sets isDirty); the patch never carries geojson,\n // so the join-cascade branch is a no-op.\n get().updateLayer(id, patch);\n return true;\n },\n\n applyLayerStyleEntries: (entries, layerIds) => {\n if (entries.length === 0) return [];\n const scope = layerIds ? new Set(layerIds) : null;\n const applied: { layerId: string; entryIndex: number }[] = [];\n const layers = get().layers.map((layer) => {\n if (scope && !scope.has(layer.id)) return layer;\n const match = matchLayerStyleEntry(layer, entries);\n if (!match) return layer;\n applied.push({ layerId: layer.id, entryIndex: match.entryIndex });\n return { ...layer, ...match.patch };\n });\n // A style patch never carries geojson, so unlike updateLayer there is no\n // join cascade to run.\n if (applied.length > 0) set({ layers, isDirty: true });\n return applied;\n },\n\n reorderLayer: (id, direction) =>\n set((s) => {\n const idx = s.layers.findIndex((l) => l.id === id);\n if (idx < 0) return s;\n const target = direction === \"up\" ? idx + 1 : idx - 1;\n if (target < 0 || target >= s.layers.length) return s;\n const next = [...s.layers];\n const [item] = next.splice(idx, 1);\n next.splice(target, 0, item);\n return { layers: next, isDirty: true };\n }),\n\n moveLayer: (id, targetIndex) =>\n set((s) => {\n const currentIndex = s.layers.findIndex((layer) => layer.id === id);\n if (currentIndex < 0) return s;\n const next = [...s.layers];\n const [layer] = next.splice(currentIndex, 1);\n const nextIndex = Math.min(Math.max(targetIndex, 0), next.length);\n next.splice(nextIndex, 0, layer);\n if (next.every((item, index) => item.id === s.layers[index]?.id)) {\n return s;\n }\n return { layers: next, isDirty: true };\n }),\n\n moveLayersRelative: (layerIds, targetLayerId, position) =>\n set((s) => {\n const requestedIds = new Set(layerIds);\n if (requestedIds.has(targetLayerId)) return s;\n const target = s.layers.find((layer) => layer.id === targetLayerId);\n if (!target) return s;\n const targetGroupId = target.groupId ?? null;\n // This is a pure reorder — `groupId` is never touched — so a requested\n // layer from another group can only be lifted out of its own block,\n // and `normalizeGroupContiguity` would then drag that group's\n // untouched members along to reunite it. Move only the layers that\n // already sit in the target's group.\n const moving = s.layers.filter(\n (layer) => requestedIds.has(layer.id) && (layer.groupId ?? null) === targetGroupId,\n );\n if (moving.length === 0) return s;\n const movingIds = new Set(moving.map((layer) => layer.id));\n const without = s.layers.filter((layer) => !movingIds.has(layer.id));\n const targetIndex = without.findIndex((layer) => layer.id === targetLayerId);\n if (targetIndex < 0) return s;\n // Store order is the reverse of panel display order, so \"above\" is\n // immediately after the target in this array.\n const insertIndex = position === \"above\" ? targetIndex + 1 : targetIndex;\n const next = [...without];\n next.splice(insertIndex, 0, ...moving);\n const normalized = normalizeGroupContiguity(next);\n if (normalized.every((layer, index) => layer.id === s.layers[index]?.id)) return s;\n return { layers: normalized, isDirty: true };\n }),\n\n addGeoJsonLayer: (name, geojson, sourcePath, beforeLayerId = null, extra) => {\n const id = uuidv4();\n // GeoJSON remains authoritative for editing. The optional batch adds\n // coordinate storage; it does not reduce the memory held by this layer.\n let geoarrow = extra?.geoarrow;\n if (!geoarrow && shouldUseTiledRendering(geojson)) {\n try {\n geoarrow = featureCollectionToGeoArrow(geojson) ?? undefined;\n } catch (err) {\n console.warn(\"[GeoLibre] GeoArrow batch conversion failed; falling back to GeoJSON\", err);\n }\n }\n const layer: GeoLibreLayer = {\n id,\n name,\n type: \"geojson\",\n source: { type: \"geojson\" },\n visible: true,\n opacity: 1,\n // Its own palette color and geometry-appropriate sizing (#1519), so a\n // stack of freshly added layers is legible without restyling each one.\n style: initialLayerStyle({\n geojson,\n layers: get().layers,\n overrides: {\n simpleStyleEnabled: hasSimpleStyleProperties(geojson),\n },\n }),\n metadata: { ...extra?.metadata },\n geojson,\n ...(geoarrow ? { geoarrow } : {}),\n sourcePath,\n };\n get().addLayer(layer, beforeLayerId);\n return id;\n },\n\n addImageOverlayLayer: (name, source, options, beforeLayerId = null) => {\n const id = uuidv4();\n const layer: GeoLibreLayer = {\n id,\n name,\n type: \"image\",\n source: {\n type: \"image\",\n url: source.url,\n coordinates: source.coordinates,\n },\n visible: options?.visible ?? true,\n opacity: options?.opacity ?? 1,\n style: { ...DEFAULT_LAYER_STYLE },\n metadata: {\n sourceKind: options?.sourceKind ?? \"kml-ground-overlay\",\n ...(options?.bounds ? { bounds: options.bounds } : {}),\n ...(options?.timeSpan ? { timeSpan: options.timeSpan } : {}),\n ...(options?.metadata ?? {}),\n },\n ...(options?.sourcePath ? { sourcePath: options.sourcePath } : {}),\n };\n get().addLayer(layer, beforeLayerId);\n return id;\n },\n\n addTileLayer: (name, options, beforeLayerId = null) => {\n // Every layer this builds carries a raster source, so a non-raster\n // `type` from an untyped JS caller (e.g. \"vector-tiles\") would persist\n // a layer whose `type` and `source.type` disagree. Reject it instead of\n // silently mislabelling the source; vector tiles need their own style\n // layers and go through the vector-tile path, not this one.\n const type = options.type ?? \"xyz\";\n if (!RASTER_TILE_LAYER_TYPES.has(type)) {\n throw new Error(\n `addTileLayer: unsupported type \"${String(type)}\"; expected one of ` +\n `${[...RASTER_TILE_LAYER_TYPES].join(\", \")}. Only raster tile layers are supported.`,\n );\n }\n const id = uuidv4();\n // Trim each template and drop blanks, then reject a registration that\n // sanitizes down to nothing: syncRasterTileLayer returns early on an\n // empty tile list, so without this the store would persist and select a\n // layer that can never render.\n const tiles = options.tiles\n .filter((tile): tile is string => typeof tile === \"string\")\n .map((tile) => tile.trim())\n .filter((tile) => tile !== \"\");\n if (tiles.length === 0) {\n throw new Error(\"addTileLayer requires at least one non-empty tile URL template.\");\n }\n // {-y} is accepted on XYZ templates. Reject a template that names both\n // row tokens before the layer is stored.\n resolveLayerTileAddress({ type, source: { scheme: options.scheme } }, tiles);\n // MapLibre's addSource throws synchronously when maxzoom < minzoom, and\n // syncRasterTileLayer does not catch it, which would strand a layer in\n // the store with no rendered source. Reject the bad range up front.\n if (\n options.minzoom !== undefined &&\n options.maxzoom !== undefined &&\n options.minzoom > options.maxzoom\n ) {\n throw new Error(\n `addTileLayer: minzoom (${options.minzoom}) must be <= maxzoom (${options.maxzoom}).`,\n );\n }\n const layer: GeoLibreLayer = {\n id,\n name,\n type,\n source: {\n // Extra source fields (e.g. WMS layers/styles) merge first so the\n // required raster descriptor below always wins. `type` above is\n // validated as a raster kind, so \"raster\" here always agrees with it.\n ...(options.source ?? {}),\n type: \"raster\",\n tiles,\n tileSize: options.tileSize ?? 256,\n ...(options.url !== undefined ? { url: options.url } : {}),\n ...(options.attribution !== undefined ? { attribution: options.attribution } : {}),\n ...(options.bounds !== undefined ? { bounds: options.bounds } : {}),\n ...(options.minzoom !== undefined ? { minzoom: options.minzoom } : {}),\n ...(options.maxzoom !== undefined ? { maxzoom: options.maxzoom } : {}),\n ...(options.scheme !== undefined ? { scheme: options.scheme } : {}),\n },\n visible: options.visible ?? true,\n opacity: options.opacity ?? 1,\n style: { ...DEFAULT_LAYER_STYLE },\n metadata: { ...(options.metadata ?? {}) },\n };\n get().addLayer(layer, beforeLayerId);\n return id;\n },\n});\n","// The Layer Library's data layer (issue #1520): capturing a configured layer as\n// a reusable entry, planning how to re-add one to a project, the shareable JSON\n// bundle format, and untrusted-input normalization. UI-free so both the desktop\n// app and tests consume it directly. Mirrors the shape of `style-library.ts`,\n// which does the same job for the Style Manager.\n//\n// The guiding rule is that an entry stores the layer's **source specification,\n// not its data**: the library stays small and re-adding an entry always shows\n// the current contents of its source. Layers whose features exist only in\n// memory (drawn features, processing output) or only in a local file have no\n// re-fetchable source, so those embed their features behind a size cap.\n\nimport { cesiumIonAssetId } from \"./cesium-ion\";\nimport type { FeatureCollection } from \"geojson\";\nimport {\n DEFAULT_LAYER_STYLE,\n LAYER_TYPES,\n type AttributeFormConfig,\n type GeoLibreLayer,\n type LayerDescriptiveMetadata,\n type LayerJoin,\n type LayerLibraryEntry,\n type LayerPopupConfig,\n type LayerStyle,\n type LayerType,\n type LayerVirtualField,\n} from \"./types\";\nimport { sanitizeLayerStylePatch } from \"./style-library\";\nimport { normalizeLayerDescriptiveMetadata } from \"./layer-descriptive-metadata\";\nimport { hasPathTraversal, isAbsoluteFilesystemPath } from \"./paths\";\nimport { isCredentialFieldName, redactUrlCredentials } from \"./credentials\";\n\n/** `type` discriminator of an exported Layer Library bundle file. */\nexport const LAYER_LIBRARY_BUNDLE_TYPE = \"geolibre-layer-library\";\n\n/** Current bundle format version, for forward-compatible readers. */\nexport const LAYER_LIBRARY_BUNDLE_VERSION = 1;\n\n/**\n * Size ceiling for one library entry's serialized JSON, in bytes. The library\n * lives in IndexedDB and is meant to be a small, shareable index of *sources*,\n * so an entry that can only be stored by embedding a large feature set is\n * refused rather than silently turning the library into a data store.\n *\n * Applied to the whole entry (not just `geojson`), because an Add Vector Layer\n * entry can also carry a `metadata.embeddedGeoJSON` copy of its features.\n */\nexport const MAX_LAYER_LIBRARY_ENTRY_BYTES = 5 * 1024 * 1024;\n\n/**\n * Cap on how many entries a single normalization pass keeps. Enforced on the way\n * *in* — an imported bundle or a hand-edited database is untrusted, and without\n * a bound one file could push an unlimited number of records through this\n * synchronous pass and into IndexedDB. Mirrors `MAX_FAVORITES`, which caps the\n * Browser panel's other user-grown list on read as well as write.\n */\nexport const MAX_LAYER_LIBRARY_ENTRIES = 500;\n\n/**\n * Metadata keys dropped when capturing an entry. `resolvedUrl` is the dev-server\n * proxy rewrite of an XYZ template — a per-session artifact that must not be\n * baked into a saved source (the same reason `prepareLayerForSave` strips it).\n * `geometryEdited` tracks changes to the live layer, not a library source.\n */\nconst TRANSIENT_METADATA_KEYS = [\"resolvedUrl\", \"geometryEdited\"] as const;\n\nfunction nonEmptyString(value: unknown): value is string {\n return typeof value === \"string\" && value.trim() !== \"\";\n}\n\n/**\n * Whether a layer's features can be re-fetched from its source specification\n * alone, so an entry need not embed them: a tile/service template, a remote\n * file URL, or a GeoJSON source pointing at a URL. A `geojson` source whose\n * `data` is an inline FeatureCollection (not a URL string) deliberately does\n * not count.\n *\n * @param layer - The layer to inspect (source + metadata only).\n * @returns True when the source alone is enough to recreate the layer's data.\n */\nexport function hasRestorableLayerSource(\n layer: Pick<GeoLibreLayer, \"source\" | \"metadata\">,\n): boolean {\n const source = layer.source ?? {};\n if (nonEmptyString(source.url)) return true;\n if (nonEmptyString(source.data)) return true;\n if (Array.isArray(source.tiles) && source.tiles.some(nonEmptyString)) return true;\n // A Cesium Ion asset is re-fetched from its id alone (under the cesium-ion\n // source kind, the same contract the globe loads it by).\n if (cesiumIonAssetId(layer) !== null) return true;\n return nonEmptyString((layer.metadata ?? {}).originalUrl);\n}\n\n/**\n * The absolute local path a layer's data can be **re-read** from, or undefined.\n *\n * A non-empty `sourcePath` is not enough on its own: a file picked in the\n * browser has no path, and both the vector and raster controls still record the\n * bare file *name* there for display. Each control marks a genuinely re-readable\n * absolute path differently, so both conventions are honored:\n *\n * - Vector / plain local GeoJSON: `metadata.localFileReloadable` alongside an\n * absolute `sourcePath` (`prepareLayerForSave`, `needsLocalFileReload`,\n * `restoreVectorLayers`).\n * - Raster / COG: `metadata.localFilePath` holds the absolute path outright,\n * and it is what `restoreRasterLayers` re-reads on restore. Without this\n * branch a locally-opened COG could not be saved at all, even though its\n * restore pass is fully able to replay it.\n *\n * Every candidate is re-validated, because a hand-edited project or an imported\n * bundle can set these fields to anything.\n */\nexport function layerLocalPath(\n layer: Pick<GeoLibreLayer, \"sourcePath\" | \"metadata\">,\n): string | undefined {\n const metadata = layer.metadata ?? {};\n if (isReReadablePath(metadata.localFilePath)) return metadata.localFilePath;\n if (metadata.localFileReloadable !== true) return undefined;\n return isReReadablePath(layer.sourcePath) ? layer.sourcePath : undefined;\n}\n\n/**\n * Whether a value is a path safe to hand to a host file read: a non-empty\n * string, absolute, with no `..` traversal. Takes `unknown` and does the\n * non-empty check itself so every path-like field — `sourcePath`,\n * `metadata.localFilePath`, and any added later — goes through one predicate;\n * splitting the two halves is what let those two fields drift apart in the\n * first place.\n *\n * Applied to captured *and* imported entries, because a shared bundle is\n * untrusted input: a crafted `needsLocalFile` entry could otherwise point at any\n * file on the importing user's disk, and clicking it in My Data would read that\n * file. Mirrors the client-side re-validation the project-restore paths already\n * do before calling into Tauri.\n */\nfunction isReReadablePath(value: unknown): value is string {\n return nonEmptyString(value) && isAbsoluteFilesystemPath(value) && !hasPathTraversal(value);\n}\n\nfunction featureCount(geojson: FeatureCollection | undefined): number {\n return Array.isArray(geojson?.features) ? geojson.features.length : 0;\n}\n\n/**\n * Whether \"Save to library\" applies to a layer at all — the cheap predicate the\n * layer actions menu gates on. Two things have to hold:\n *\n * 1. There is something to re-add *from*: a re-fetchable source, a local file\n * path, or features to embed. A control-painted layer's features live in its\n * control rather than the store record — a tiles-mode Add Vector Layer layer\n * has no `layer.geojson` at all — so the caller can answer for that case with\n * `hasMaterializableFeatures`, keeping this gate consistent with the capture\n * path, which reads those features through `options.features`.\n * 2. The host can actually render it again. For a control-painted layer\n * ({@link isExternalNativeLayerRecord}) that means either the map's own sync\n * rebuilds it from the record or the owning plugin's restore pass can be\n * re-run, which only the host knows — so the caller supplies\n * `canRestoreControlPainted`. Without it, control-painted layers are refused\n * rather than saved into an entry that would re-add unrendered.\n *\n * A layer that qualifies here can still fail to save because its features are\n * too large to embed ({@link MAX_LAYER_LIBRARY_ENTRY_BYTES}); that outcome needs\n * the full capture, so it is reported by {@link captureLayerLibraryEntry} rather\n * than hidden behind a disabled menu item.\n *\n * @param layer - The layer to test.\n * @param options - `canRestoreControlPainted` answers whether the host can\n * re-render a control-painted layer (the desktop app passes its\n * `canRestoreLibraryLayer`); `hasMaterializableFeatures` answers whether it can\n * read the layer's features out of its control (the app passes\n * `isEmbeddableLocalVectorLayer`, the same predicate its save handler uses).\n * @returns Whether the layer can be offered to the library.\n */\nexport function canSaveLayerToLibrary(\n layer: GeoLibreLayer,\n options: {\n canRestoreControlPainted?: (layer: GeoLibreLayer) => boolean;\n hasMaterializableFeatures?: (layer: GeoLibreLayer) => boolean;\n } = {},\n): boolean {\n const hasSomethingToReAddFrom =\n hasRestorableLayerSource(layer) ||\n layerLocalPath(layer) !== undefined ||\n featureCount(layer.geojson) > 0 ||\n options.hasMaterializableFeatures?.(layer) === true;\n if (!hasSomethingToReAddFrom) return false;\n if (!isExternalNativeLayerRecord(layer)) return true;\n return options.canRestoreControlPainted?.(layer) === true;\n}\n\n/**\n * The `source` to store for a layer, with a live XYZ layer's session-specific\n * endpoint rewound to the template it was resolved from.\n *\n * `prepareLayerForSave` does the same thing when writing a project file: an XYZ\n * layer's `source.tiles`/`source.url` can hold a *resolved* endpoint while\n * `metadata.originalUrl` keeps the template, and baking the resolved value into\n * a saved record makes it replay a stale host instead of re-resolving. A library\n * entry outlives a session by design, so it needs the same rewind — stripping\n * `metadata.resolvedUrl` alone would leave the resolved endpoint sitting in\n * `source`.\n */\nfunction sourceForCapture(layer: GeoLibreLayer): Record<string, unknown> {\n const source = layer.source ?? {};\n if (layer.type !== \"xyz\") return source;\n const originalUrl = nonEmptyString((layer.metadata ?? {}).originalUrl)\n ? layer.metadata.originalUrl\n : nonEmptyString(source.url)\n ? source.url\n : null;\n if (!originalUrl) return source;\n // A TileJSON layer keeps its own tile templates — see `prepareLayerForSave`.\n // The library re-adds a captured source verbatim, with no re-resolution step,\n // so collapsing `tiles` onto the document URL here would never load a tile.\n if (typeof (layer.metadata ?? {}).tilejsonUrl === \"string\") {\n return { ...source, url: originalUrl };\n }\n return { ...source, tiles: [originalUrl], url: originalUrl };\n}\n\n/** Why a layer could not be captured into the library. */\nexport type LayerLibraryCaptureFailure =\n /** Nothing to re-add from: no source URL, no local path, no features. */\n | \"no-source\"\n /** Its features are the only copy, and they exceed the per-entry size cap. */\n | \"features-too-large\"\n /**\n * The entry exceeds the cap without embedding any features — its style,\n * attribute form, joins, or virtual fields are simply that large. Reported\n * separately so the message does not blame the layer's data.\n */\n | \"config-too-large\";\n\n/** Outcome of {@link captureLayerLibraryEntry}. */\nexport type LayerLibraryCaptureResult =\n | { ok: true; entry: LayerLibraryEntry }\n | { ok: false; reason: LayerLibraryCaptureFailure };\n\n/**\n * Serialized size of a value in UTF-8 bytes — what IndexedDB and an exported\n * bundle actually cost. Measured with {@link TextEncoder} rather than\n * `JSON.stringify().length`, which counts UTF-16 code units: CJK, Arabic, and\n * Cyrillic text is 1 unit but 2-3 bytes per character, so a length-based check\n * would let an entry through at 2-3x the cap it is supposed to enforce.\n */\nfunction jsonByteLength(value: unknown): number {\n try {\n const json = JSON.stringify(value);\n return json === undefined ? 0 : new TextEncoder().encode(json).length;\n } catch {\n // A source or metadata value with a circular reference cannot be stored at\n // all, so treat it as over any cap.\n return Number.POSITIVE_INFINITY;\n }\n}\n\n/**\n * Whether a layer is painted by a plugin control rather than by GeoLibre's own\n * layer sync (Add Vector Layer, the deck.gl raster/COG control, 3D Tiles,\n * LiDAR, …). These recreate their map output from the layer record through\n * their plugin's restore pass, so an entry for one must carry the metadata that\n * pass reads — and re-adding it has to run that pass.\n *\n * @param layer - The layer to test (metadata only).\n * @returns True when a plugin control owns the layer's rendering.\n */\nexport function isExternalNativeLayerRecord(layer: Pick<GeoLibreLayer, \"metadata\">): boolean {\n return (layer.metadata ?? {}).externalNativeLayer === true;\n}\n\n/**\n * Capture a configured layer as a reusable Layer Library entry: its source\n * spec, layer type, full style (labels included), opacity, metadata, and the\n * per-layer configuration that makes it \"fully configured\" — joins, virtual\n * fields, and the attribute form. Project-specific placement (`id`,\n * `groupId`, `beforeId`) is deliberately not captured.\n *\n * Features are embedded only when the source cannot supply them (an in-memory\n * or local-file layer). Where they land depends on who paints the layer:\n * GeoLibre-rendered layers keep them in {@link LayerLibraryEntry.geojson},\n * while a control-painted layer gets them in `metadata.embeddedGeoJSON` — the\n * same field the project format's Embed/Share flow writes and the plugin's\n * restore pass reads.\n *\n * If the result exceeds {@link MAX_LAYER_LIBRARY_ENTRY_BYTES} the capture\n * degrades rather than failing outright: it falls back to a path-only entry\n * (`needsLocalFile`) when a local file can supply the features.\n *\n * @param layer - The live layer to save.\n * @param options - Fresh entry `id`, ISO `addedAt`, an optional `name` override\n * (defaults to the layer's name), and optional `features` to embed instead of\n * the layer's own `geojson` (the caller reads a control-painted layer's\n * current data from its control).\n * @returns The entry, or the reason it could not be captured.\n */\nexport function captureLayerLibraryEntry(\n layer: GeoLibreLayer,\n options: { id: string; addedAt: string; name?: string; features?: FeatureCollection },\n): LayerLibraryCaptureResult {\n const localPath = layerLocalPath(layer);\n const hasSource = hasRestorableLayerSource(layer);\n const supplied = featureCount(options.features) > 0 ? options.features : undefined;\n const features = supplied ?? (featureCount(layer.geojson) > 0 ? layer.geojson : undefined);\n if (!hasSource && !localPath && !features) {\n return { ok: false, reason: \"no-source\" };\n }\n\n const metadata: Record<string, unknown> = { ...(layer.metadata ?? {}) };\n for (const key of TRANSIENT_METADATA_KEYS) delete metadata[key];\n // A stale embedded copy from the project this layer was loaded from: the\n // capture below re-embeds current data (or drops the copy when the source can\n // re-fetch it), so never carry the old blob through.\n delete metadata.embeddedGeoJSON;\n\n const controlPainted = isExternalNativeLayerRecord(layer);\n // Embed features only when the source cannot re-fetch them; a layer with a\n // restorable source stays data-free so its entry keeps reflecting the source.\n const embed = !hasSource && features ? structuredClone(features) : undefined;\n if (embed && controlPainted) {\n metadata.embeddedGeoJSON = embed;\n // Mirrors the project Embed flow: with the data embedded, the restore pass\n // must replay it rather than a file path that may not exist on this machine.\n delete metadata.localFileReloadable;\n }\n\n const base: LayerLibraryEntry = {\n id: options.id,\n name: options.name?.trim() || layer.name,\n addedAt: options.addedAt,\n layerType: layer.type,\n source: structuredClone(sourceForCapture(layer)),\n style: structuredClone({ ...DEFAULT_LAYER_STYLE, ...layer.style }),\n opacity:\n typeof layer.opacity === \"number\" && Number.isFinite(layer.opacity) ? layer.opacity : 1,\n metadata: structuredClone(metadata),\n ...(localPath ? { sourcePath: localPath } : {}),\n ...(layer.joins?.length ? { joins: structuredClone(layer.joins) } : {}),\n ...(layer.virtualFields?.length ? { virtualFields: structuredClone(layer.virtualFields) } : {}),\n ...(layer.attributeForm ? { attributeForm: structuredClone(layer.attributeForm) } : {}),\n ...(layer.popup ? { popup: structuredClone(layer.popup) } : {}),\n ...(layer.descriptiveMetadata\n ? { descriptiveMetadata: structuredClone(layer.descriptiveMetadata) }\n : {}),\n };\n const withFeatures = embed && !controlPainted ? { ...base, geojson: embed } : base;\n\n if (jsonByteLength(withFeatures) <= MAX_LAYER_LIBRARY_ENTRY_BYTES) {\n return { ok: true, entry: withFeatures };\n }\n if (embed && localPath) {\n // Last resort for an oversized local file: keep the path and let the\n // desktop host re-read it at add time.\n const { embeddedGeoJSON: _dropped, ...leanMetadata } = withFeatures.metadata;\n const pathOnly: LayerLibraryEntry = {\n ...withFeatures,\n metadata: leanMetadata,\n needsLocalFile: true,\n };\n delete pathOnly.geojson;\n if (jsonByteLength(pathOnly) <= MAX_LAYER_LIBRARY_ENTRY_BYTES) {\n return { ok: true, entry: pathOnly };\n }\n }\n // Blame the right thing: an entry can also exceed the cap with no embedded\n // features at all (a rule-based style with hundreds of rules, a large\n // attribute form), and telling that user their \"features are the only copy\"\n // would send them looking in the wrong place.\n return { ok: false, reason: embed ? \"features-too-large\" : \"config-too-large\" };\n}\n\n/**\n * The presentation state a re-added layer takes from its entry. For the\n * `local-file` plan the host imports the file first (producing a default-styled\n * layer) and then applies this patch, so a degraded entry still comes back\n * \"fully configured\" rather than losing the styling the entry preserved.\n */\nexport interface LayerLibraryConfigPatch {\n name: string;\n style: LayerStyle;\n opacity: number;\n joins?: LayerJoin[];\n virtualFields?: LayerVirtualField[];\n attributeForm?: AttributeFormConfig;\n popup?: LayerPopupConfig;\n descriptiveMetadata?: LayerDescriptiveMetadata;\n}\n\n/** How an entry should be re-added to the current project. */\nexport type LayerLibraryAddPlan =\n /** Add this layer record to the store; the map sync renders it. */\n | { kind: \"layer\"; layer: GeoLibreLayer }\n /**\n * The entry's features live only in a local file the host must re-read (its\n * data was too large to embed), so the caller runs its local-file add path for\n * `path` and then applies `config` to the layer that import produced. Only\n * reachable on a host with filesystem access.\n */\n | { kind: \"local-file\"; path: string; config: LayerLibraryConfigPatch };\n\n/**\n * Plan how to re-add a library entry to the current project.\n *\n * @param entry - The library entry to add.\n * @param options - The fresh layer `id` to create it under.\n * @returns Either the layer record to add, or the local file to re-read.\n */\nexport function planLayerLibraryAdd(\n entry: LayerLibraryEntry,\n options: { id: string },\n): LayerLibraryAddPlan {\n if (layerLibraryEntryNeedsLocalFile(entry)) {\n return {\n kind: \"local-file\",\n // Non-null by the guard above, which also re-validates the path.\n path: entry.sourcePath as string,\n config: layerLibraryConfigPatch(entry),\n };\n }\n return {\n kind: \"layer\",\n layer: {\n id: options.id,\n name: entry.name,\n type: entry.layerType,\n source: structuredClone(entry.source),\n // Always added visible: the user just asked for this layer.\n visible: true,\n opacity: entry.opacity,\n style: structuredClone(entry.style),\n metadata: structuredClone(entry.metadata),\n ...(entry.sourcePath ? { sourcePath: entry.sourcePath } : {}),\n ...(entry.geojson ? { geojson: structuredClone(entry.geojson) } : {}),\n ...(entry.joins ? { joins: structuredClone(entry.joins) } : {}),\n ...(entry.virtualFields ? { virtualFields: structuredClone(entry.virtualFields) } : {}),\n ...(entry.attributeForm ? { attributeForm: structuredClone(entry.attributeForm) } : {}),\n ...(entry.popup ? { popup: structuredClone(entry.popup) } : {}),\n ...(entry.descriptiveMetadata\n ? { descriptiveMetadata: structuredClone(entry.descriptiveMetadata) }\n : {}),\n },\n };\n}\n\n/**\n * The presentation state to apply to a layer the host imported from an entry's\n * file, so the `local-file` plan preserves everything the entry captured.\n *\n * @param entry - The entry being re-added.\n * @returns The patch to merge onto the imported layer.\n */\nexport function layerLibraryConfigPatch(entry: LayerLibraryEntry): LayerLibraryConfigPatch {\n return {\n name: entry.name,\n style: structuredClone(entry.style),\n opacity: entry.opacity,\n ...(entry.joins ? { joins: structuredClone(entry.joins) } : {}),\n ...(entry.virtualFields ? { virtualFields: structuredClone(entry.virtualFields) } : {}),\n ...(entry.attributeForm ? { attributeForm: structuredClone(entry.attributeForm) } : {}),\n ...(entry.popup ? { popup: structuredClone(entry.popup) } : {}),\n ...(entry.descriptiveMetadata\n ? { descriptiveMetadata: structuredClone(entry.descriptiveMetadata) }\n : {}),\n };\n}\n\n/**\n * The saved joins whose source layer is not present in the target project.\n *\n * A {@link LayerJoin} points at another layer by its **project-local id**, so a\n * join travels only as far as the project it was captured in: re-adding an entry\n * elsewhere leaves those joins referring to layers that do not exist. The join\n * engine degrades quietly (`applyLayerJoins` just drops an unresolved join from\n * its active set), which is the right runtime behavior but a silent one — so the\n * caller uses this to tell the user which joins did not come back, instead of\n * letting them find a column missing later.\n *\n * The joins are still captured and reapplied rather than dropped: they resolve\n * whenever the source layer *is* there, which covers re-adding into the project\n * the entry came from, or into one restored from the same saved project.\n *\n * @param entry - The entry being re-added.\n * @param presentLayerIds - Ids of the layers in the target project.\n * @returns Ids of the join-target layers that cannot resolve ({@link LayerJoin}\n * has no display name).\n */\nexport function unresolvedLayerLibraryJoins(\n entry: Pick<LayerLibraryEntry, \"joins\">,\n presentLayerIds: Iterable<string>,\n): string[] {\n if (!entry.joins?.length) return [];\n const present = new Set(presentLayerIds);\n return entry.joins\n .filter((join) => join.enabled !== false && !present.has(join.joinLayerId))\n .map((join) => join.joinLayerId);\n}\n\n/**\n * Whether re-adding an entry needs a host that can read local files — used to\n * badge the entry in the Browser panel and to explain the failure in the\n * browser build, where there is no filesystem to re-read from.\n *\n * @param entry - The entry to test.\n * @returns True when only a filesystem-capable host can add it.\n */\nexport function layerLibraryEntryNeedsLocalFile(entry: LayerLibraryEntry): boolean {\n return entry.needsLocalFile === true && isReReadablePath(entry.sourcePath);\n}\n\n/** A plain JSON object (not an array, not null), or undefined. */\nfunction plainObject(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\n/** Whether every listed key on an object holds a non-empty string. */\nfunction hasStringKeys(object: Record<string, unknown>, keys: readonly string[]): boolean {\n return keys.every((key) => nonEmptyString(object[key]));\n}\n\n/**\n * Keep only the {@link LayerJoin} members a hand-edited or imported bundle got\n * right. A join missing any of its required fields resolves to nothing and would\n * sit in the Joins UI as a permanently-broken definition, so drop it rather than\n * the whole entry.\n */\nfunction validJoins(value: unknown): LayerJoin[] | undefined {\n const items = objectArray(value)?.filter((join) =>\n hasStringKeys(join, [\"id\", \"joinLayerId\", \"targetField\", \"joinField\"]),\n );\n return items && items.length > 0 ? (structuredClone(items) as unknown as LayerJoin[]) : undefined;\n}\n\n/** Keep only {@link LayerVirtualField} members that carry an id, name, and expression. */\nfunction validVirtualFields(value: unknown): LayerVirtualField[] | undefined {\n const items = objectArray(value)?.filter((field) =>\n hasStringKeys(field, [\"id\", \"name\", \"expression\"]),\n );\n return items && items.length > 0\n ? (structuredClone(items) as unknown as LayerVirtualField[])\n : undefined;\n}\n\n/**\n * Keep an {@link AttributeFormConfig} only when every field entry names the\n * feature property it configures; a field with no `field` key would apply to\n * nothing.\n */\nfunction validAttributeForm(value: unknown): AttributeFormConfig | undefined {\n const config = plainObject(value);\n if (!config || !Array.isArray(config.fields)) return undefined;\n const fields = config.fields.filter(\n (field) =>\n plainObject(field) !== undefined && nonEmptyString((field as { field?: unknown }).field),\n );\n if (fields.length === 0) return undefined;\n return structuredClone({ ...config, fields }) as unknown as AttributeFormConfig;\n}\n\n/**\n * A structurally plausible popup design, or undefined. `fields` is optional (a\n * config may carry only a title or the hover flag), so unlike the attribute\n * form an empty field list does not disqualify the block — but a config with\n * nothing set at all is dropped rather than stored as an empty object.\n */\nfunction validPopup(value: unknown): LayerPopupConfig | undefined {\n const config = plainObject(value);\n if (!config) return undefined;\n const fields = Array.isArray(config.fields)\n ? config.fields.filter(\n (field) =>\n plainObject(field) !== undefined && nonEmptyString((field as { field?: unknown }).field),\n )\n : undefined;\n const rest = { ...config };\n delete rest.fields;\n if (!fields?.length && Object.keys(rest).length === 0) return undefined;\n return structuredClone({\n ...rest,\n ...(fields?.length ? { fields } : {}),\n }) as unknown as LayerPopupConfig;\n}\n\n/** An array of plain JSON objects, or undefined when nothing usable is present. */\nfunction objectArray(value: unknown): Record<string, unknown>[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const items = value.filter((item): item is Record<string, unknown> => Boolean(plainObject(item)));\n return items.length > 0 ? items : undefined;\n}\n\n/** A GeoJSON FeatureCollection shape, or undefined. */\nfunction featureCollection(value: unknown): FeatureCollection | undefined {\n const object = plainObject(value);\n if (!object || object.type !== \"FeatureCollection\" || !Array.isArray(object.features)) {\n return undefined;\n }\n return object as unknown as FeatureCollection;\n}\n\n/**\n * Coerce an untrusted entries array (an imported bundle, or a record written by\n * an older version) into valid {@link LayerLibraryEntry} records. Entries\n * without a usable id, name, or layer type are dropped, ids are de-duplicated,\n * the style is sanitized through the Style Manager's\n * {@link sanitizeLayerStylePatch} and completed against the defaults, and each\n * optional block is kept only when structurally plausible.\n *\n * Entries that cannot be re-added at all (no source, no path, no features) are\n * dropped rather than shown as permanently-failing rows.\n *\n * @param value - The raw entries value.\n * @returns Normalized, de-duplicated entries (empty when none survive).\n */\nexport function normalizeLayerLibraryEntries(value: unknown): LayerLibraryEntry[] {\n if (!Array.isArray(value)) return [];\n const entries: LayerLibraryEntry[] = [];\n const seen = new Set<string>();\n for (const raw of value) {\n const candidate = plainObject(raw);\n if (!candidate) continue;\n const id = typeof candidate.id === \"string\" ? candidate.id.trim() : \"\";\n const name = typeof candidate.name === \"string\" ? candidate.name.trim() : \"\";\n // An unknown layer type is dropped rather than coerced: every downstream\n // renderer branches on it, so a made-up value would render nothing.\n const layerType = LAYER_TYPES.includes(candidate.layerType as LayerType)\n ? (candidate.layerType as LayerType)\n : undefined;\n if (!id || !name || !layerType || seen.has(id)) continue;\n const source = plainObject(candidate.source) ?? {};\n const metadata = { ...(plainObject(candidate.metadata) ?? {}) };\n // Strip the same per-session keys the capture path drops, so a stale one on\n // an older record or an imported bundle is cleaned up on load instead of\n // replaying a dev-server proxy rewrite indefinitely.\n for (const key of TRANSIENT_METADATA_KEYS) delete metadata[key];\n // `metadata.localFilePath` is the *second* path in an entry, and the one\n // `restoreRasterLayers` re-reads — so it needs the same validation as\n // `sourcePath` below, not a free pass because it happens to live in\n // metadata. Without this a shared bundle could keep a valid-looking\n // `sourcePath` while pointing the raster restore at any file on the\n // importing user's disk.\n if (\"localFilePath\" in metadata && !isReReadablePath(metadata.localFilePath)) {\n delete metadata.localFilePath;\n }\n // A bundle is shareable, so its `sourcePath` is untrusted: keep it only when\n // it is an absolute, traversal-free path a host could legitimately re-read.\n // Without this a crafted entry could point `onAddFilePath` at any file on the\n // importing user's disk.\n const sourcePath = isReReadablePath(candidate.sourcePath) ? candidate.sourcePath : undefined;\n const geojson = featureCollection(candidate.geojson);\n // Same \"is there anything to re-add from\" gate as the capture path, so a\n // hand-edited bundle cannot introduce an entry that always fails. Both path\n // conventions count (a local raster's only path is `metadata.localFilePath`,\n // already validated above), and so does a control-painted entry's\n // `metadata.embeddedGeoJSON`, which holds its features instead of `geojson`.\n if (\n !hasRestorableLayerSource({ source, metadata }) &&\n !sourcePath &&\n !nonEmptyString(metadata.localFilePath) &&\n featureCount(geojson) === 0 &&\n featureCount(featureCollection(metadata.embeddedGeoJSON)) === 0\n ) {\n continue;\n }\n const opacity =\n typeof candidate.opacity === \"number\" &&\n Number.isFinite(candidate.opacity) &&\n candidate.opacity >= 0 &&\n candidate.opacity <= 1\n ? candidate.opacity\n : 1;\n const normalized: LayerLibraryEntry = {\n id,\n name,\n addedAt: typeof candidate.addedAt === \"string\" ? candidate.addedAt : \"\",\n layerType,\n source: structuredClone(source),\n style: { ...DEFAULT_LAYER_STYLE, ...sanitizeLayerStylePatch(candidate.style) },\n opacity,\n metadata: structuredClone(metadata),\n ...(sourcePath ? { sourcePath } : {}),\n ...(() => {\n // Each block is kept only when its members carry the fields their engine\n // needs; a malformed member is dropped rather than the whole entry.\n const joins = validJoins(candidate.joins);\n const virtualFields = validVirtualFields(candidate.virtualFields);\n const attributeForm = validAttributeForm(candidate.attributeForm);\n const popup = validPopup(candidate.popup);\n const descriptiveMetadata = normalizeLayerDescriptiveMetadata(\n candidate.descriptiveMetadata,\n );\n return {\n ...(joins ? { joins } : {}),\n ...(virtualFields ? { virtualFields } : {}),\n ...(attributeForm ? { attributeForm } : {}),\n ...(popup ? { popup } : {}),\n ...(descriptiveMetadata ? { descriptiveMetadata } : {}),\n };\n })(),\n ...(geojson ? { geojson: structuredClone(geojson) } : {}),\n // `needsLocalFile` only means anything with a path to read.\n ...(candidate.needsLocalFile === true && sourcePath ? { needsLocalFile: true } : {}),\n };\n // The per-entry size ceiling is enforced here too, not only on capture: an\n // imported bundle is produced elsewhere, so without this a crafted (or just\n // hand-assembled) file could push tens of MB of embedded features per entry\n // into IndexedDB, exactly the \"library as a data store\" outcome the cap\n // exists to prevent.\n if (jsonByteLength(normalized) > MAX_LAYER_LIBRARY_ENTRY_BYTES) continue;\n seen.add(id);\n entries.push(normalized);\n if (entries.length >= MAX_LAYER_LIBRARY_ENTRIES) break;\n }\n return entries;\n}\n\n/**\n * Copy of an entry with per-user credentials removed from its source.\n *\n * `source` fields that can hold a per-user credential are removed on export.\n * `requestHeaders`, for example, carries the bearer token / API key an\n * authenticated 3D Tiles layer was added with (see\n * `persistedThreeDTilesRequestHeaders`, which keeps non-Google auth headers when\n * persisting into a project file).\n *\n * Kept in the local IndexedDB entry so re-adding on this machine still works,\n * and dropped only from the bundle: Export is pitched as a one-click \"share with\n * your team\" action, which makes it a far more direct exposure path than handing\n * someone a whole project file. A recipient re-enters their own credentials,\n * which is the correct outcome.\n *\n * The names come from `isCredentialFieldName`, so this path and project export\n * share one registry instead of drifting apart.\n */\nfunction redactEntryForExport(entry: LayerLibraryEntry): LayerLibraryEntry {\n const source = { ...entry.source };\n let changed = false;\n for (const key of Object.keys(source)) {\n if (isCredentialFieldName(key)) {\n delete source[key];\n changed = true;\n }\n }\n // Sweep every URL-shaped value in `source` rather than naming the fields that\n // hold one. Different layer types put their URL in different places — `url`,\n // `data` (a GeoJSON source pointing at a URL, which\n // `hasRestorableLayerSource` already treats as re-fetchable), `tiles[]` — and\n // a per-field list has to be extended for each new one, with a silent\n // credential leak as the cost of forgetting.\n for (const [key, value] of Object.entries(source)) {\n const redacted = redactSourceValue(value);\n if (redacted !== value) {\n source[key] = redacted;\n changed = true;\n }\n }\n // `metadata.originalUrl` is the template a tile layer restores from, so it\n // carries the same credential as `source.tiles`.\n let metadata = entry.metadata;\n if (typeof metadata.originalUrl === \"string\") {\n const redacted = redactUrlCredentials(metadata.originalUrl);\n if (redacted !== metadata.originalUrl) {\n metadata = { ...metadata, originalUrl: redacted };\n changed = true;\n }\n }\n return changed ? { ...entry, source, metadata } : entry;\n}\n\n/** Depth limit for the export sweep, so a pathological source cannot recurse far. */\nconst MAX_REDACT_DEPTH = 6;\n\n/**\n * Whether a value is GeoJSON data rather than source configuration. The sweep\n * stops at these: `source.data` can hold an inline FeatureCollection, and\n * descending into it would both walk every feature and *rewrite* any feature\n * property that happens to look like a URL with a `key=` parameter — corrupting\n * the user's data in the name of protecting it. Credentials live in\n * configuration, not in features.\n */\nfunction isGeoJsonPayload(value: Record<string, unknown>): boolean {\n return (\n value.type === \"FeatureCollection\" ||\n value.type === \"Feature\" ||\n value.type === \"GeometryCollection\"\n );\n}\n\n/**\n * Redact a `source` value of any shape: strings are treated as possible URLs,\n * arrays as lists of them (tile templates), and nested objects are swept\n * recursively — a layer type that groups its endpoint and credentials as\n * `{ store: { url, headers } }` must not slip a token past the export just\n * because it is one level deeper. Nested `requestHeaders`-style keys are dropped\n * the same way the top level drops them.\n *\n * GeoJSON payloads are left alone (see {@link isGeoJsonPayload}) and recursion is\n * depth-capped. Returns the original reference when nothing changed, so callers\n * can detect that by identity.\n */\nfunction redactSourceValue(value: unknown, depth = 0): unknown {\n if (typeof value === \"string\") return redactUrlCredentials(value);\n if (depth >= MAX_REDACT_DEPTH) {\n // Fail closed, matching `redactConfigurationValue` in credentials.ts: no\n // built-in layer needs configuration nested this deep to be re-addable, and\n // handing the value back unswept would let a credential ride out of the\n // bundle merely by sitting past the traversal cap. Only the exported bundle\n // loses it; the local IndexedDB entry is untouched.\n return undefined;\n }\n if (Array.isArray(value)) {\n const redacted = value.map((item) => redactSourceValue(item, depth + 1));\n return redacted.some((item, index) => item !== value[index]) ? redacted : value;\n }\n const object = plainObject(value);\n if (!object || isGeoJsonPayload(object)) return value;\n let changed = false;\n const out: Record<string, unknown> = {};\n for (const [key, nested] of Object.entries(object)) {\n if (isCredentialFieldName(key)) {\n changed = true;\n continue;\n }\n const redacted = redactSourceValue(nested, depth + 1);\n if (redacted !== nested) changed = true;\n out[key] = redacted;\n }\n return changed ? out : value;\n}\n\n/**\n * Serialize Layer Library entries into the shareable bundle JSON written by the\n * Export action and read back by {@link parseLayerLibrary}. Per-user credentials\n * are redacted first (see {@link EXPORT_REDACTED_SOURCE_KEYS}).\n *\n * @param entries - The entries to export.\n * @returns Pretty-printed bundle JSON.\n */\nexport function serializeLayerLibrary(entries: LayerLibraryEntry[]): string {\n return JSON.stringify(\n {\n type: LAYER_LIBRARY_BUNDLE_TYPE,\n version: LAYER_LIBRARY_BUNDLE_VERSION,\n entries: entries.map(redactEntryForExport),\n },\n null,\n 2,\n );\n}\n\n/**\n * Parse a Layer Library bundle produced by {@link serializeLayerLibrary} (a\n * bare entries array is also accepted, so hand-authored files work). Entries\n * are normalized through {@link normalizeLayerLibraryEntries}.\n *\n * @param json - The bundle file content.\n * @returns The normalized entries.\n * @throws Error when the JSON is not a layer-library bundle or holds no usable\n * entries.\n */\nexport function parseLayerLibrary(json: string): LayerLibraryEntry[] {\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n throw new Error(\"Not a valid layer library file (invalid JSON).\");\n }\n let rawEntries: unknown = null;\n if (Array.isArray(parsed)) {\n rawEntries = parsed;\n } else if (plainObject(parsed)?.type === LAYER_LIBRARY_BUNDLE_TYPE) {\n // Refuse a bundle from a newer format rather than misreading it with v1\n // semantics. Bare arrays stay accepted for hand-authored files.\n if (plainObject(parsed)?.version !== LAYER_LIBRARY_BUNDLE_VERSION) {\n throw new Error(\"Unsupported layer library version.\");\n }\n rawEntries = plainObject(parsed)?.entries;\n }\n if (rawEntries === null) {\n throw new Error(\"Not a valid layer library file.\");\n }\n const entries = normalizeLayerLibraryEntries(rawEntries);\n if (entries.length === 0) {\n throw new Error(\"The layer library file holds no usable entries.\");\n }\n return entries;\n}\n\n/**\n * Create a fresh unique id for a new library entry.\n *\n * @returns A random id (UUID when available).\n */\nexport function createLayerLibraryEntryId(): string {\n if (typeof crypto !== \"undefined\" && \"randomUUID\" in crypto) {\n return crypto.randomUUID();\n }\n return `layer-lib-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n","/**\n * The Style Manager, Layer Library and Template Library. The app-level lists\n * live outside the project lifecycle (persisted by the desktop app); only the\n * project-scoped style list is saved with the project.\n */\nimport { MAX_LAYER_LIBRARY_ENTRIES } from \"../layer-library\";\nimport type { LayerLibraryEntry, ProjectTemplateEntry, StyleLibraryEntry } from \"../types\";\nimport type { SliceCreator } from \"./types\";\n\nexport interface LibrariesSlice {\n /**\n * App-level Style Manager library (issue #1294). Lives outside the project\n * lifecycle: never serialized into the project file, untouched by\n * newProject/loadProject, and persisted by the desktop app (IndexedDB).\n */\n styleLibrary: StyleLibraryEntry[];\n /**\n * App-level Layer Library (issue #1520) — the Browser panel's My Data\n * section. Like {@link styleLibrary} it lives outside the project lifecycle:\n * never serialized into the project file, untouched by newProject/loadProject,\n * and persisted by the desktop app (IndexedDB). Most recently saved first.\n */\n layerLibrary: LayerLibraryEntry[];\n /**\n * App-level Template Library. Persisted by the desktop app (IndexedDB).\n */\n templateLibrary: ProjectTemplateEntry[];\n /**\n * Project-scoped Style Manager entries (issue #1294), serialized into the\n * `.geolibre.json` `styleLibrary` array and replaced on project load.\n */\n projectStyleLibrary: StyleLibraryEntry[];\n\n /**\n * Replace the app-level style library wholesale. Used by the persistence\n * layer on startup and by bundle imports.\n */\n setStyleLibrary: (entries: StyleLibraryEntry[]) => void;\n /**\n * Insert or replace (matching by `id`) a Style Manager entry in the given\n * scope. Scope-local, like {@link deleteStyleLibraryEntry}: the other\n * scope's list is never touched, since a same id there can belong to an\n * unrelated entry after loading a project authored elsewhere. Project-scope\n * saves mark the project dirty.\n */\n saveStyleLibraryEntry: (entry: StyleLibraryEntry, scope?: \"app\" | \"project\") => void;\n /**\n * Remove a Style Manager entry by id. When `scope` is given only that list\n * is touched — the two scopes can legitimately hold the same id after\n * loading a project authored elsewhere, and deleting a project entry must\n * not erase a local app-library style (or vice versa). Omitting `scope`\n * removes the id from both lists.\n */\n deleteStyleLibraryEntry: (id: string, scope?: \"app\" | \"project\") => void;\n\n /**\n * Replace the app-level Layer Library wholesale. Used by the persistence\n * layer on startup and by bundle imports.\n */\n setLayerLibrary: (entries: LayerLibraryEntry[]) => void;\n /**\n * Insert or replace (matching by `id`) a Layer Library entry. New entries go\n * to the front so the most recently saved layer leads the My Data section.\n */\n saveLayerLibraryEntry: (entry: LayerLibraryEntry) => void;\n /** Rename a Layer Library entry; a blank name is ignored. */\n renameLayerLibraryEntry: (id: string, name: string) => void;\n /** Remove a Layer Library entry by id. */\n deleteLayerLibraryEntry: (id: string) => void;\n\n /** Replace the app-level template library wholesale. */\n setTemplateLibrary: (templates: ProjectTemplateEntry[]) => void;\n /** Insert or replace a template in the Template Library. */\n saveTemplateEntry: (entry: ProjectTemplateEntry) => void;\n /** Remove a template entry by id from the Template Library. */\n deleteTemplateEntry: (id: string) => void;\n}\n\nexport const createLibrariesSlice: SliceCreator<LibrariesSlice> = (set) => ({\n styleLibrary: [],\n layerLibrary: [],\n templateLibrary: [],\n projectStyleLibrary: [],\n\n setStyleLibrary: (entries) => set({ styleLibrary: entries }),\n saveStyleLibraryEntry: (entry, scope = \"app\") =>\n set((s) => {\n const upsert = (list: StyleLibraryEntry[]) =>\n list.some((e) => e.id === entry.id)\n ? list.map((e) => (e.id === entry.id ? entry : e))\n : [...list, entry];\n if (scope === \"project\") {\n return {\n projectStyleLibrary: upsert(s.projectStyleLibrary),\n isDirty: true,\n };\n }\n // App-level saves don't touch the project file, so no dirty flag.\n return { styleLibrary: upsert(s.styleLibrary) };\n }),\n deleteStyleLibraryEntry: (id, scope) =>\n set((s) => {\n const inProject = scope !== \"app\" && s.projectStyleLibrary.some((e) => e.id === id);\n const inLibrary = scope !== \"project\" && s.styleLibrary.some((e) => e.id === id);\n // Keep untouched scopes reference-stable so the IndexedDB\n // persistence (which watches the styleLibrary reference) does not\n // rewrite an unchanged library.\n return {\n styleLibrary: inLibrary ? s.styleLibrary.filter((e) => e.id !== id) : s.styleLibrary,\n projectStyleLibrary: inProject\n ? s.projectStyleLibrary.filter((e) => e.id !== id)\n : s.projectStyleLibrary,\n isDirty: s.isDirty || inProject,\n };\n }),\n\n // The entry cap is applied on every write, not just when reading\n // untrusted input, so ordinary use (repeated saves, importing several\n // bundles over time) cannot grow the library past it. Mirrors\n // `writeBrowserFavorites`, which slices to MAX_FAVORITES on each write.\n setLayerLibrary: (entries) => set({ layerLibrary: entries.slice(0, MAX_LAYER_LIBRARY_ENTRIES) }),\n saveLayerLibraryEntry: (entry) =>\n set((s) => ({\n // App-level saves don't touch the project file, so no dirty flag\n // (mirrors saveStyleLibraryEntry's \"app\" scope). A new entry goes to\n // the front, so at the cap the oldest falls off the end.\n layerLibrary: s.layerLibrary.some((e) => e.id === entry.id)\n ? s.layerLibrary.map((e) => (e.id === entry.id ? entry : e))\n : [entry, ...s.layerLibrary].slice(0, MAX_LAYER_LIBRARY_ENTRIES),\n })),\n renameLayerLibraryEntry: (id, name) =>\n set((s) => {\n const trimmed = name.trim();\n // Keep the array reference stable for a no-op rename so the\n // IndexedDB persistence (which watches the reference) skips a write.\n if (!trimmed || !s.layerLibrary.some((e) => e.id === id && e.name !== trimmed)) {\n return {};\n }\n return {\n layerLibrary: s.layerLibrary.map((e) => (e.id === id ? { ...e, name: trimmed } : e)),\n };\n }),\n deleteLayerLibraryEntry: (id) =>\n set((s) =>\n s.layerLibrary.some((e) => e.id === id)\n ? { layerLibrary: s.layerLibrary.filter((e) => e.id !== id) }\n : {},\n ),\n\n setTemplateLibrary: (templates) => set({ templateLibrary: templates }),\n saveTemplateEntry: (entry) =>\n set((s) => ({\n templateLibrary: s.templateLibrary.some((t) => t.id === entry.id)\n ? s.templateLibrary.map((t) => (t.id === entry.id ? entry : t))\n : [...s.templateLibrary, entry],\n })),\n deleteTemplateEntry: (id) =>\n set((s) => ({\n templateLibrary: s.templateLibrary.filter((t) => t.id !== id),\n })),\n});\n","/**\n * Runtime-settable window (ms) used to coalesce rapid history-producing changes\n * (e.g. a continuous opacity/style slider drag) into a single undo entry. Tests\n * set this to 0 for deterministic one-entry-per-action behavior.\n */\nlet historyCoalesceMs = 400;\n\nexport function setHistoryCoalesceMs(ms: number): void {\n historyCoalesceMs = ms;\n}\n\nexport function getHistoryCoalesceMs(): number {\n return historyCoalesceMs;\n}\n\n/**\n * Soft budget (in total feature count) for the geometry/attribute payload\n * retained across undo snapshots. Each in-app edit of a vector layer pushes a\n * new snapshot that holds the layer's full `geojson`; without a bound, editing\n * a large layer many times pins several copies of its feature set in memory\n * (issue #341). When the distinct features across snapshots exceed this budget,\n * the oldest snapshots are dropped (see {@link trimHistoryBySize}).\n *\n * Feature count is a cheap proxy for payload size; it avoids serializing\n * geometry on every edit. The default is generous enough that ordinary\n * multi-step editing of small/medium layers keeps the full history depth, while\n * very large layers trade history depth for a bounded footprint.\n */\nlet maxHistoryFeatureCount = 500_000;\n\nexport function setMaxHistoryFeatureCount(count: number): void {\n if (!Number.isFinite(count) || count < 0) {\n throw new RangeError(\n `maxHistoryFeatureCount must be a non-negative finite number, got ${count}`,\n );\n }\n maxHistoryFeatureCount = count;\n}\n\nexport function getMaxHistoryFeatureCount(): number {\n return maxHistoryFeatureCount;\n}\n\n/** Structural shape of a partialized undo snapshot, for size accounting. */\ninterface HistorySnapshot {\n layers?: {\n geojson?: { features?: unknown[] } | null;\n source?: { url?: unknown } | null;\n }[];\n}\n\n/**\n * Bytes of inline payload charged as one feature-equivalent, so a `data:` URL\n * competes for the same budget as geometry. A rough vector feature costs a few\n * hundred bytes of live JS objects, which is the order this matches.\n */\nconst BYTES_PER_FEATURE_EQUIVALENT = 200;\n\n/**\n * The size a snapshot adds to the budget, skipping any payload already in\n * `seen` so payloads shared across snapshots (unchanged layers keep the same\n * reference) are counted once. Mutates `seen` with each newly counted payload.\n *\n * Two payload kinds count. Vector `geojson` contributes its feature count. A\n * layer whose `source.url` is an inline `data:` URL contributes its length as\n * feature-equivalents: a NetCDF grid baked to pixels holds a whole PNG there,\n * and re-symbolizing it writes a fresh multi-megabyte string on every change, so\n * without this the pruner sees a \"free\" snapshot and history grows unbounded.\n */\nfunction distinctFeatureCount(snapshot: HistorySnapshot, seen: Set<unknown>): number {\n let count = 0;\n for (const layer of snapshot.layers ?? []) {\n const geojson = layer?.geojson;\n // Dedup the reference first so a payload shared across snapshots is visited\n // once even when its `features` is missing/malformed (it then contributes 0).\n if (geojson && !seen.has(geojson)) {\n seen.add(geojson);\n if (Array.isArray(geojson.features)) count += geojson.features.length;\n }\n const url = layer?.source?.url;\n // Strings have no stable identity to dedup by reference, so `seen` holds the\n // value itself; an unchanged layer repeats the same URL across snapshots and\n // must be charged once, exactly as a shared feature payload is.\n if (typeof url === \"string\" && url.startsWith(\"data:\") && !seen.has(url)) {\n seen.add(url);\n count += Math.ceil(url.length / BYTES_PER_FEATURE_EQUIVALENT);\n }\n }\n return count;\n}\n\n/**\n * Bound the memory held by undo history by dropping the oldest snapshots whose\n * combined feature payload exceeds `maxFeatures`.\n *\n * Walks newest-to-oldest and keeps the most recent snapshots whose cumulative\n * distinct feature count fits the budget. The single newest snapshot is always\n * kept regardless of size, so one-step undo of even a huge edit still works.\n * Feature sets shared by reference across snapshots (unchanged layers) are\n * counted once, so retaining many snapshots of small layers stays cheap.\n *\n * Returns the original array when nothing needs trimming, or a trimmed slice\n * (oldest entries removed) otherwise. Pure: never mutates its input.\n */\nexport function trimHistoryBySize<T extends HistorySnapshot>(\n pastStates: T[],\n maxFeatures: number,\n): T[] {\n if (!Number.isFinite(maxFeatures) || maxFeatures < 0) {\n throw new RangeError(`maxFeatures must be a non-negative finite number, got ${maxFeatures}`);\n }\n if (pastStates.length <= 1) return pastStates;\n const seen = new Set<unknown>();\n const lastIndex = pastStates.length - 1;\n // The newest snapshot is always retained, even if it alone exceeds the budget.\n let total = distinctFeatureCount(pastStates[lastIndex], seen);\n let keepFrom = lastIndex;\n for (let i = lastIndex - 1; i >= 0; i--) {\n const added = distinctFeatureCount(pastStates[i], seen);\n if (total + added > maxFeatures) break;\n total += added;\n keepFrom = i;\n }\n return keepFrom === 0 ? pastStates : pastStates.slice(keepFrom);\n}\n\n/** A debounced function with a `cancel` to clear its in-flight burst window. */\nexport type DebouncedFn<A extends unknown[]> = ((...args: A) => void) & {\n /** Clear the active burst timer so the next call fires on its leading edge. */\n cancel: () => void;\n};\n\n/**\n * Leading-edge debounce. Fires `fn` immediately on the first call of a burst,\n * then suppresses further calls until `getWait()` ms of quiet have elapsed.\n * When the wait is <= 0, every call is passed straight through (used in tests).\n *\n * Used as zundo's `handleSet`: `fn` is zundo's \"save previous state to history\"\n * function, so firing only on the leading edge records the pre-burst state once.\n * `cancel()` resets the window so that clearing history mid-burst doesn't\n * suppress the next edit's save.\n */\nexport function leadingDebounce<A extends unknown[]>(\n fn: (...args: A) => void,\n getWait: () => number,\n): DebouncedFn<A> {\n let timer: ReturnType<typeof setTimeout> | null = null;\n const wrapped = (...args: A) => {\n const wait = getWait();\n if (wait <= 0) {\n fn(...args);\n return;\n }\n const atBurstStart = timer === null;\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n }, wait);\n if (atBurstStart) fn(...args);\n };\n wrapped.cancel = () => {\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n };\n return wrapped;\n}\n","/**\n * Undo/redo for the app store. zundo's `temporal` middleware tracks only the\n * project-data fields that {@link partializeHistory} lists (layers, groups,\n * basemap, story map, comments, dashboard widgets, legend); every other field,\n * including the whole `ui`\n * dialog sub-state, selection and the camera, is outside the history, so\n * changing it never records an undo step and undo/redo never reverts it.\n *\n * The functions here act on the store bound by {@link bindHistoryStore}\n * rather than importing it, so the slices (whose `newProject`/`loadProject`\n * call {@link clearHistory}) can depend on this module without a cycle.\n */\nimport { shallow } from \"zustand/shallow\";\nimport type { StoreApi } from \"zustand\";\nimport type { TemporalState, ZundoOptions } from \"zundo\";\nimport { DEFAULT_ELLIPSOID_ID, getPlanetaryBasemapByStyleUrl } from \"../ellipsoids\";\nimport {\n getHistoryCoalesceMs,\n getMaxHistoryFeatureCount,\n leadingDebounce,\n trimHistoryBySize,\n} from \"../history\";\nimport { scrubPrintLayoutForLayers } from \"../print-layout-config\";\nimport { getRegionalBasemapByStyleUrl } from \"../regional-basemaps\";\nimport { DEFAULT_BASEMAP, type GeoLibreProject, type LayerGroup } from \"../types\";\nimport { scrubSecondaryPaneLayerVisibility } from \"./layer-removal\";\nimport type { AppState } from \"./types\";\n\n/** The fields undo/redo snapshots, restores and compares. */\nexport type HistoryState = Pick<\n AppState,\n | \"layers\"\n | \"layerGroups\"\n | \"basemapStyleUrl\"\n | \"basemapVisible\"\n | \"basemapOpacity\"\n | \"blankBackgroundColor\"\n | \"storymap\"\n | \"comments\"\n | \"widgets\"\n | \"legend\"\n>;\n\n/** The parts of the bound store the history functions use. */\ninterface HistoryStore {\n getState: () => AppState;\n setState: (partial: Partial<AppState>) => void;\n temporal: StoreApi<TemporalState<HistoryState>>;\n}\n\nlet historyStore: HistoryStore | null = null;\n\n/** Point the history functions at the app store; called once by `store.ts`. */\nexport function bindHistoryStore(store: HistoryStore): void {\n historyStore = store;\n}\n\nfunction boundStore(): HistoryStore {\n if (!historyStore) throw new Error(\"The app store has not been created yet.\");\n return historyStore;\n}\n\n/**\n * Compare two `layerGroups` arrays for undo-history purposes, ignoring the\n * `collapsed` flag so expand/collapse (a UI-panel preference) never records a\n * history entry. Every other field — order, name, visibility, opacity — is\n * still compared, so real edits are tracked.\n */\nfunction layerGroupsEqualForHistory(a: LayerGroup[], b: LayerGroup[]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n const x = a[i];\n const y = b[i];\n if (x === y) continue;\n if (x.id !== y.id || x.name !== y.name || x.visible !== y.visible || x.opacity !== y.opacity) {\n return false;\n }\n }\n return true;\n}\n\n/** Cancels the active history coalesce window (assigned by zundo's handleSet). */\nlet cancelHistoryCoalesce: () => void = () => {};\n\ninterface ProjectRestoreHistoryEntry {\n before: GeoLibreProject;\n beforePath: string | null;\n after: GeoLibreProject;\n afterPath: string | null;\n}\n\nlet projectRestoreUndo: ProjectRestoreHistoryEntry | null = null;\nlet projectRestoreRedo: ProjectRestoreHistoryEntry | null = null;\nlet applyingProjectRestoreHistory = false;\nconst projectRestoreHistoryListeners = new Set<() => void>();\n\nfunction notifyProjectRestoreHistory(): void {\n projectRestoreHistoryListeners.forEach((listener) => listener());\n}\n\nexport function subscribeProjectRestoreHistory(listener: () => void): () => void {\n projectRestoreHistoryListeners.add(listener);\n return () => projectRestoreHistoryListeners.delete(listener);\n}\n\nexport function canUndoProjectRestore(): boolean {\n return projectRestoreUndo !== null;\n}\n\nexport function canRedoProjectRestore(): boolean {\n return projectRestoreRedo !== null;\n}\n\n/**\n * Register a whole-project restore as one undoable operation. The regular\n * temporal history intentionally tracks only editing fields, while restoring a\n * history snapshot also changes project metadata, camera, plugins, and other\n * serialized state, so it needs a full canonical project pair.\n */\nexport function registerProjectRestoreHistory(\n before: GeoLibreProject,\n beforePath: string | null,\n after: GeoLibreProject,\n afterPath: string | null = null,\n): void {\n projectRestoreUndo = { before, beforePath, after, afterPath };\n projectRestoreRedo = null;\n notifyProjectRestoreHistory();\n}\n\n/**\n * Drop the oldest undo snapshots once their combined feature payload exceeds the\n * configured budget, bounding the memory held by history when a large vector\n * layer is edited repeatedly (issue #341). Called after a snapshot is appended.\n * Operates on the live temporal store directly; this never touches the main\n * store, so it does not itself record a history entry.\n */\nfunction pruneHistoryBySize(): void {\n const temporalStore = boundStore().temporal;\n const { pastStates } = temporalStore.getState();\n const trimmed = trimHistoryBySize(pastStates, getMaxHistoryFeatureCount());\n if (trimmed.length !== pastStates.length) {\n temporalStore.setState({ pastStates: trimmed });\n }\n}\n\n/**\n * Only these fields participate in undo/redo; everything else (selection,\n * ui flags, mapView/camera, pointerCoords, project metadata, isDirty, ...)\n * is excluded, so changing them never creates a history entry.\n */\nexport function partializeHistory(s: AppState): HistoryState {\n return {\n layers: s.layers,\n layerGroups: s.layerGroups,\n basemapStyleUrl: s.basemapStyleUrl,\n basemapVisible: s.basemapVisible,\n basemapOpacity: s.basemapOpacity,\n blankBackgroundColor: s.blankBackgroundColor,\n storymap: s.storymap,\n comments: s.comments,\n // Widgets and legend entries reference layers, and deleting a layer\n // scrubs them; tracking them makes undoing a delete (or a snapshot layer\n // restore) bring them back with the layer instead of leaving them lost or\n // dangling. Secondary panes and the Print Layout are deliberately not\n // tracked (see finishHistoryStep).\n widgets: s.widgets,\n legend: s.legend,\n };\n}\n\n/** zundo options for the app store's undo history. */\nexport function createHistoryOptions(): ZundoOptions<AppState, HistoryState> {\n return {\n partialize: partializeHistory,\n // Records a history entry only when the tracked slice really changed.\n // Basemap fields compare with ===; `layers` is compared element-by-element\n // (Object.is per element) via shallow. Every mutating action creates new\n // layer/group objects, so real changes differ; two distinct empty arrays\n // compare equal, so resetting them (e.g. newProject) records nothing.\n // `storymap` is compared by reference: every authoring action creates a\n // new object, so real edits differ while an unchanged null stays equal.\n // `layerGroups` is compared ignoring `collapsed`, which is a UI preference\n // excluded from undo (see toggleLayerGroupCollapsed).\n // `comments` and `widgets` are compared shallowly by reference, and\n // `legend` by reference: setLegend always stores a new object.\n equality: (a, b) =>\n a.basemapStyleUrl === b.basemapStyleUrl &&\n a.basemapVisible === b.basemapVisible &&\n a.basemapOpacity === b.basemapOpacity &&\n a.blankBackgroundColor === b.blankBackgroundColor &&\n a.storymap === b.storymap &&\n shallow(a.layers, b.layers) &&\n shallow(a.comments, b.comments) &&\n shallow(a.widgets, b.widgets) &&\n a.legend === b.legend &&\n layerGroupsEqualForHistory(a.layerGroups, b.layerGroups),\n limit: 100,\n // Group rapid bursts (slider drags) into one entry; window is 0 in tests.\n // Keep the debounced wrapper so clearHistory can reset an in-flight burst.\n handleSet: (baseHandleSet) => {\n const debounced = leadingDebounce(baseHandleSet, getHistoryCoalesceMs);\n cancelHistoryCoalesce = debounced.cancel;\n // Trim history back under the feature-payload budget so editing large\n // layers can't pin unbounded copies of their feature sets in memory\n // (issue #341). Only the leading-edge call actually pushes a snapshot;\n // burst-suppressed calls leave `pastStates` untouched, so skip the scan\n // unless a new snapshot was appended.\n return (...args: Parameters<typeof debounced>) => {\n const before = boundStore().temporal.getState().pastStates;\n debounced(...args);\n if (boundStore().temporal.getState().pastStates !== before) {\n if (!applyingProjectRestoreHistory && projectRestoreRedo) {\n projectRestoreRedo = null;\n notifyProjectRestoreHistory();\n }\n pruneHistoryBySize();\n }\n };\n },\n };\n}\n\n/**\n * After an undo/redo restores the tracked slice, mark the project dirty and\n * drop references the step left pointing at a layer that no longer exists:\n * the selection, per-pane layer visibility, and Print Layout data/atlas\n * blocks. None of those is tracked in history (selection is UI state; panes\n * carry their own camera and must stay in step with the untracked grid\n * layout; the Print Layout composer keeps a local copy it writes back), so\n * they are scrubbed the way deleting the layer would have scrubbed them.\n */\nfunction finishHistoryStep(previousBasemapStyleUrl: string): void {\n const s = boundStore().getState();\n const layerIds = new Set(s.layers.map((layer) => layer.id));\n const danglingPaneIds = new Set<string>();\n for (const pane of s.secondaryMapViews) {\n for (const id of Object.keys(pane.layerVisibility)) {\n if (!layerIds.has(id)) danglingPaneIds.add(id);\n }\n }\n const secondaryMapViews = scrubSecondaryPaneLayerVisibility(s.secondaryMapViews, danglingPaneIds);\n const printLayout = scrubPrintLayoutForLayers(s.printLayout, layerIds);\n const referencePatch = {\n ...(secondaryMapViews !== s.secondaryMapViews ? { secondaryMapViews } : {}),\n ...(printLayout !== s.printLayout ? { printLayout } : {}),\n };\n const selectionDangling =\n s.selectedLayerId !== null && !s.layers.some((layer) => layer.id === s.selectedLayerId);\n // The basemap is in the undo history but the ellipsoid preference is not, so a\n // step that restores a *different* basemap can leave the two out of sync (e.g.\n // undoing a switch to Mars would keep the Mars radius under an Earth basemap).\n // Re-derive the ellipsoid from the restored basemap's body — Earth for a\n // non-planetary basemap — but only when this step actually changed the\n // basemap. Steps that leave the basemap untouched must not touch the ellipsoid,\n // which the user can set independently of the basemap in Settings.\n const restoredEllipsoidId =\n getPlanetaryBasemapByStyleUrl(s.basemapStyleUrl)?.ellipsoidId ?? DEFAULT_ELLIPSOID_ID;\n const ellipsoidPatch =\n s.basemapStyleUrl !== previousBasemapStyleUrl &&\n s.preferences.map.ellipsoidId !== restoredEllipsoidId\n ? {\n preferences: {\n ...s.preferences,\n map: { ...s.preferences.map, ellipsoidId: restoredEllipsoidId },\n },\n }\n : {};\n // A step recorded before the deployment disabled regional basemaps must not\n // bring one back.\n const basemapPatch: { basemapStyleUrl?: string } =\n s.regionalBasemapPolicy === \"block\" && getRegionalBasemapByStyleUrl(s.basemapStyleUrl)\n ? { basemapStyleUrl: DEFAULT_BASEMAP }\n : {};\n boundStore().setState(\n selectionDangling\n ? {\n isDirty: true,\n selectedLayerId: null,\n selectedFeatureId: null,\n selectedFeatureIds: [],\n ...ellipsoidPatch,\n ...referencePatch,\n }\n : { isDirty: true, ...ellipsoidPatch, ...referencePatch },\n );\n // The basemap is tracked, so recording this correction as an edit would\n // clear the redo stack and leave the regional basemap one undo step back.\n if (basemapPatch.basemapStyleUrl) withoutHistory(() => boundStore().setState(basemapPatch));\n // The setState above must not leave a coalesce window open for the next edit.\n cancelHistoryCoalesce();\n}\n\n/**\n * Step the layer/basemap history back one entry and mark the project dirty.\n * zundo restores the partialized slice via the store's set; the resulting new\n * `layers`/basemap refs drive MapCanvas's existing effects, so the map\n * reconciles through MapController.syncLayers (never mutated directly here).\n */\nexport function undo(): void {\n const temporal = boundStore().temporal.getState();\n if (temporal.pastStates.length === 0 && projectRestoreUndo) {\n const entry = projectRestoreUndo;\n applyingProjectRestoreHistory = true;\n try {\n boundStore().getState().loadProject(entry.before, entry.beforePath, {\n rememberRecent: false,\n presenting: false,\n // A snapshot of the store, whose basemap already passed the gate.\n trustBasemap: true,\n });\n projectRestoreUndo = null;\n boundStore().setState({ isDirty: true });\n projectRestoreRedo = entry;\n notifyProjectRestoreHistory();\n } catch (error) {\n console.error(\"Could not undo the project snapshot restore.\", error);\n } finally {\n applyingProjectRestoreHistory = false;\n }\n return;\n }\n if (temporal.pastStates.length === 0) return; // nothing to undo; stay clean\n cancelHistoryCoalesce(); // break any in-flight burst so the next edit records\n const previousBasemapStyleUrl = boundStore().getState().basemapStyleUrl;\n temporal.undo();\n finishHistoryStep(previousBasemapStyleUrl);\n}\n\n/** Step the history forward one entry and mark the project dirty. */\nexport function redo(): void {\n const temporal = boundStore().temporal.getState();\n if (temporal.futureStates.length === 0 && projectRestoreRedo) {\n const entry = projectRestoreRedo;\n applyingProjectRestoreHistory = true;\n try {\n boundStore().getState().loadProject(entry.after, entry.afterPath, {\n rememberRecent: false,\n presenting: false,\n // A snapshot of the store, whose basemap already passed the gate.\n trustBasemap: true,\n });\n projectRestoreRedo = null;\n boundStore().setState({ isDirty: true });\n projectRestoreUndo = entry;\n notifyProjectRestoreHistory();\n } catch (error) {\n console.error(\"Could not redo the project snapshot restore.\", error);\n } finally {\n applyingProjectRestoreHistory = false;\n }\n return;\n }\n if (temporal.futureStates.length === 0) return; // nothing to redo; stay clean\n cancelHistoryCoalesce(); // break any in-flight burst so the next edit records\n const previousBasemapStyleUrl = boundStore().getState().basemapStyleUrl;\n temporal.redo();\n finishHistoryStep(previousBasemapStyleUrl);\n}\n\n/**\n * Apply a store change that is not the user's (data loading in behind a\n * project) without recording an undo step for it. The history stays as it\n * was, so undo neither takes the change back out nor loses earlier steps.\n *\n * @param change - The store updates to apply.\n */\nexport function withoutHistory(change: () => void): void {\n cancelHistoryCoalesce(); // a burst left open would record this change\n const temporal = boundStore().temporal.getState();\n temporal.pause();\n try {\n change();\n } finally {\n temporal.resume();\n }\n}\n\n/**\n * Rewrite the basemap recorded in every undo/redo snapshot from `from` to `to`.\n *\n * Used when the user accepts a regional basemap a loaded project asked for:\n * snapshots taken while the confirmation was open recorded the fallback, which\n * is not a basemap anyone chose, so undo must not bring it back.\n *\n * @param from - The basemap style URL to replace (the fallback).\n * @param to - The basemap style URL to record instead.\n */\nexport function replaceHistoryBasemap(from: string, to: string): void {\n const temporal = boundStore().temporal;\n const swap = (states: Partial<HistoryState>[]) =>\n states.map((s) => (s.basemapStyleUrl === from ? { ...s, basemapStyleUrl: to } : s));\n const { pastStates, futureStates } = temporal.getState();\n if (pastStates.length === 0 && futureStates.length === 0) return;\n temporal.setState({ pastStates: swap(pastStates), futureStates: swap(futureStates) });\n}\n\n/** Empty both the undo and redo stacks (e.g. on new/loaded project). */\nexport function clearHistory(): void {\n cancelHistoryCoalesce(); // reset any in-flight burst so the next edit records\n boundStore().temporal.getState().clear();\n if (!applyingProjectRestoreHistory) {\n projectRestoreUndo = null;\n projectRestoreRedo = null;\n notifyProjectRestoreHistory();\n }\n}\n","/**\n * The map workspace: the primary camera, the basemap, project preferences, the\n * multi-map grid and its secondary panes, and which engine draws the primary\n * map. The basemap fields are tracked by undo history; the camera is not.\n */\nimport { v4 as uuidv4 } from \"uuid\";\nimport { createDefaultMapView, normalizeBlankBackgroundColor } from \"../project\";\nimport { DEFAULT_ELLIPSOID_ID } from \"../ellipsoids\";\nimport type { PlanetaryBasemap } from \"../ellipsoids\";\nimport {\n gateRegionalBasemap,\n getRegionalBasemapByStyleUrl,\n type RegionalBasemapHold,\n type RegionalBasemapPolicy,\n} from \"../regional-basemaps\";\nimport {\n DEFAULT_BASEMAP,\n DEFAULT_MAP_GRID_LAYOUT,\n DEFAULT_PRIMARY_RENDERER,\n DEFAULT_PROJECT_PREFERENCES,\n MAX_MAP_GRID_DIM,\n type MapGridLayout,\n type MapRendererKind,\n type MapViewState,\n type ProjectPreferences,\n type SecondaryMapView,\n} from \"../types\";\nimport type { AppState, SliceCreator } from \"./types\";\nimport { replaceHistoryBasemap, withoutHistory } from \"./undo-history\";\n\n/** An explicit background choice replaces the active renderer's override. */\nfunction preferencesForBasemap(state: AppState, ellipsoidId = state.preferences.map.ellipsoidId) {\n // Each engine keeps its own persisted basemap override, so this names them.\n /* eslint-disable local/no-renderer-kind-checks -- per-engine persisted basemap settings */\n const clearMapbox =\n state.primaryRenderer === \"mapbox\" && state.preferences.map.mapboxStyleUrl !== undefined;\n // Any Cesium or ArcGIS pane, not only a primary one: split panes pick the\n // renderer independently, and a pinned globe imagery or Esri style would\n // otherwise ignore the picker.\n const clearCesium =\n state.preferences.map.cesiumBasemap !== \"project\" &&\n (state.primaryRenderer === \"cesium\" ||\n state.secondaryMapViews.some((pane) => pane.viewKind === \"cesium\"));\n const clearArcgis =\n state.preferences.map.arcgisBasemap !== undefined &&\n (state.primaryRenderer === \"arcgis\" ||\n state.secondaryMapViews.some((pane) => pane.viewKind === \"arcgis\"));\n /* eslint-enable local/no-renderer-kind-checks */\n if (\n !clearMapbox &&\n !clearCesium &&\n !clearArcgis &&\n ellipsoidId === state.preferences.map.ellipsoidId\n )\n return state.preferences;\n return {\n ...state.preferences,\n map: {\n ...state.preferences.map,\n ...(clearMapbox ? { mapboxStyleUrl: undefined } : {}),\n ...(clearCesium ? { cesiumBasemap: \"project\" as const } : {}),\n ...(clearArcgis ? { arcgisBasemap: undefined } : {}),\n ellipsoidId,\n },\n };\n}\n\n/**\n * The basemap fields for a basemap the user (or code acting for them) picked.\n *\n * A deployment that disabled regional basemaps refuses them from every caller\n * (plugins, scripts, the assistant), not only the hidden picker. An explicit\n * pick is the user's own choice, so `\"prompt\"` never applies, and it supersedes\n * a pending prompt: answering Load afterwards must not replace it.\n *\n * Plugins, scripts and the assistant go through here too. They are code the\n * user installed or ran to act for them, like the picker, so they are trusted\n * the same way; only a project file (data someone else may have authored) is\n * gated by `\"prompt\"`.\n *\n * @param state - The current store state.\n * @param url - The requested basemap style URL.\n * @returns The `basemapStyleUrl` to apply, the updated hold and choice counter.\n */\nfunction explicitBasemapChoice(\n state: AppState,\n url: string,\n): Pick<\n AppState,\n \"basemapStyleUrl\" | \"regionalBasemapHold\" | \"basemapChoiceGeneration\" | \"regionalBasemapAnswers\"\n> {\n const gated = gateRegionalBasemap(url, state.regionalBasemapPolicy, DEFAULT_BASEMAP, true);\n if (gated.held) {\n console.warn(`Regional basemaps are disabled by this deployment; ignoring \"${url}\".`);\n }\n // Picking a regional basemap answers the prompt for it too: a project (or an\n // embed host re-sending one) that names it later loads it without asking,\n // even when the user turned it down earlier in the session.\n const pickedRegional = !gated.held && getRegionalBasemapByStyleUrl(url) !== undefined;\n return {\n regionalBasemapAnswers: pickedRegional\n ? { ...state.regionalBasemapAnswers, [url]: true }\n : state.regionalBasemapAnswers,\n basemapStyleUrl: gated.styleUrl,\n regionalBasemapHold:\n state.regionalBasemapHold?.reason === \"prompt\" ? null : state.regionalBasemapHold,\n basemapChoiceGeneration: state.basemapChoiceGeneration + 1,\n };\n}\n\n/** True when two camera states have identical center/zoom/bearing/pitch. */\nfunction sameCamera(a: MapViewState, b: MapViewState): boolean {\n return (\n a.center[0] === b.center[0] &&\n a.center[1] === b.center[1] &&\n a.zoom === b.zoom &&\n a.bearing === b.bearing &&\n a.pitch === b.pitch\n );\n}\n\n/** Clamp a requested grid row/column count into the supported [1, MAX] range. */\nfunction clampGridDim(value: number): number {\n if (!Number.isFinite(value)) return 1;\n return Math.max(1, Math.min(MAX_MAP_GRID_DIM, Math.floor(value)));\n}\n\n/**\n * Pick a grid that holds at least `total` panes within the supported\n * `MAX_MAP_GRID_DIM x MAX_MAP_GRID_DIM` bound, minimizing empty cells first and\n * then preferring a column count close to (and, on ties, no smaller than)\n * `preferredCols` so removing a pane keeps the layout's orientation. Used when\n * collapsing the grid after a pane is removed.\n *\n * Bounding both dimensions matters: a prime `total` (e.g. 5 panes left after\n * removing one from a 2x3 grid) has no gap-free factor pair inside the bound, so\n * the only gap-free options (1x5 / 5x1) would exceed `MAX_MAP_GRID_DIM`. In that\n * case we accept the smallest bounded grid with one empty trailing cell (2x3)\n * rather than returning an out-of-range dimension.\n */\nfunction fitGrid(total: number, preferredCols: number): { rows: number; cols: number } {\n if (total <= 1) return { rows: 1, cols: 1 };\n let best: {\n rows: number;\n cols: number;\n empty: number;\n score: number;\n } | null = null;\n for (let rows = 1; rows <= MAX_MAP_GRID_DIM; rows++) {\n for (let cols = 1; cols <= MAX_MAP_GRID_DIM; cols++) {\n const capacity = rows * cols;\n if (capacity < total) continue;\n const empty = capacity - total;\n const score = Math.abs(cols - preferredCols);\n // Fewest empty cells wins; then the column count closest to\n // preferredCols; then, on a tie, the larger column count (favoring wider,\n // side-by-side layouts over tall ones).\n const better =\n best === null ||\n empty < best.empty ||\n (empty === best.empty &&\n (score < best.score || (score === best.score && cols > best.cols)));\n if (better) best = { rows, cols, empty, score };\n }\n }\n return best ? { rows: best.rows, cols: best.cols } : { rows: 1, cols: 1 };\n}\n\nexport interface MapViewSlice {\n mapView: MapViewState;\n basemapStyleUrl: string;\n basemapVisible: boolean;\n basemapOpacity: number;\n blankBackgroundColor: string | null;\n preferences: ProjectPreferences;\n /**\n * Multi-map grid layout (issue: split/grid view). A 1x1 grid is the normal\n * single-map workspace. `rows * cols` panes are shown; pane 0 is the primary\n * map driven by `mapView` / `basemap*`, panes 1.. are `secondaryMapViews`.\n */\n mapLayout: MapGridLayout;\n /**\n * Secondary map panes (everything past the primary pane). The store keeps\n * exactly `rows * cols - 1` entries in sync with `mapLayout`.\n */\n secondaryMapViews: SecondaryMapView[];\n /** User-entered label for the primary pane (shown only in multi-map mode). */\n primaryMapLabel: string;\n /**\n * Which engine draws the primary map area (issue #2217): the 2D MapLibre map\n * or the 3D Cesium globe. Both render the same store state, so switching\n * keeps the camera, basemap, layers, groups, visibility, and opacity — it\n * only changes what draws them. Independent of `mapLayout`: switching never\n * adds or removes panes.\n */\n primaryRenderer: MapRendererKind;\n\n setMapView: (view: Partial<MapViewState>, markDirty?: boolean) => void;\n /**\n * Resize the map grid. Clamps `rows`/`cols` into range and grows/shrinks\n * `secondaryMapViews` so it always holds `rows * cols - 1` panes; new panes\n * clone the primary map's current camera and basemap.\n */\n setMapGrid: (rows: number, cols: number) => void;\n /** Toggle synchronized camera across all panes. */\n setSyncView: (syncView: boolean) => void;\n /** Patch one secondary pane's camera by id (no-op if the id is unknown). */\n setSecondaryMapView: (id: string, view: Partial<MapViewState>, markDirty?: boolean) => void;\n /**\n * Override a layer's visibility in one secondary pane (no-op if the pane id is\n * unknown). The override forces the layer visible/hidden in that pane only,\n * independent of the primary map's visibility.\n */\n setSecondaryLayerVisibility: (id: string, layerId: string, visible: boolean) => void;\n /** Set the primary pane's custom label. */\n setPrimaryMapLabel: (label: string) => void;\n /**\n * Switch the primary map area between the 2D map and the 3D globe (no-op if\n * unchanged). Touches nothing else in the store, so the shared camera, layer,\n * and basemap state carries straight across the swap.\n */\n setPrimaryRenderer: (renderer: MapRendererKind) => void;\n /** Set one secondary pane's custom label (no-op if the id is unknown). */\n setSecondaryMapLabel: (id: string, label: string) => void;\n /**\n * Switch one secondary pane between the 2D map and the 3D globe (no-op if the\n * id is unknown or the kind is unchanged).\n */\n setSecondaryViewKind: (id: string, viewKind: NonNullable<SecondaryMapView[\"viewKind\"]>) => void;\n /** Remove one secondary pane and collapse the grid back toward 1x1. */\n removeSecondaryMapView: (id: string) => void;\n setBasemapStyleUrl: (url: string) => void;\n /**\n * Apply a planetary basemap and sync the project's ellipsoid to the body it\n * depicts, so measurements and the globe control use that body's radius. Used\n * by both the basemap picker and the Layers-panel planet switcher.\n */\n applyPlanetaryBasemap: (basemap: PlanetaryBasemap) => void;\n /**\n * Return to Earth: apply `styleUrl` (typically the Earth basemap that was\n * active before a planet was selected, e.g. Liberty) and reset the ellipsoid\n * to Earth. Used when a planet is deselected in the switcher.\n */\n restoreEarthBasemap: (styleUrl: string) => void;\n setBasemapVisible: (visible: boolean) => void;\n setBasemapOpacity: (opacity: number) => void;\n setBlankBackgroundColor: (color: string | null) => void;\n setPreferences: (preferences: ProjectPreferences) => void;\n /**\n * How a regional basemap (served from mainland China) that a loaded project\n * names is treated; see {@link RegionalBasemapPolicy}. Not project data: the\n * app sets it once at startup from the deployment policy.\n */\n regionalBasemapPolicy: RegionalBasemapPolicy;\n /**\n * The regional basemap the last project load set aside in favour of the\n * default basemap, or null. The app shell renders a confirmation (or, when\n * blocked, a notice) from it. Not project data and not in undo history.\n */\n regionalBasemapHold: RegionalBasemapHold | null;\n /**\n * Bumped whenever the basemap changes because the user (or code acting for\n * them) chose it: a basemap setter, or Load on the regional prompt. Not\n * bumped by project loads, collaboration snapshots or undo. Lets observers\n * tell a chosen basemap from one that arrived with data, e.g. so a regional\n * basemap is only remembered for the next launch when it was chosen.\n */\n basemapChoiceGeneration: number;\n /**\n * The user's answer to the regional-basemap prompt this session, per\n * regional style URL (true for Load). A later load naming the same basemap\n * reuses it instead of asking again, so a host that re-sends its project\n * (the Jupyter widget on every Python-side change) does not re-prompt.\n * Session-only: never persisted or saved into a project.\n */\n regionalBasemapAnswers: Record<string, boolean>;\n /** Set the {@link regionalBasemapPolicy}. Switching to `\"block\"` drops a pending prompt. */\n setRegionalBasemapPolicy: (policy: RegionalBasemapPolicy) => void;\n /**\n * Answer the held regional basemap. With `load` true and the hold still\n * belonging to the open project, the regional basemap is applied without\n * marking the project dirty or recording an undo step (it is what the file\n * already says). Otherwise the default basemap stays. Either way the hold is\n * cleared. The answer is remembered for the session unless `remember` is\n * false (the dialog was dismissed rather than answered).\n */\n resolveRegionalBasemapHold: (load: boolean, options?: { remember?: boolean }) => void;\n}\n\nexport const createMapViewSlice: SliceCreator<MapViewSlice> = (set, get) => ({\n mapView: createDefaultMapView(),\n basemapStyleUrl: DEFAULT_BASEMAP,\n basemapVisible: true,\n basemapOpacity: 1,\n blankBackgroundColor: null,\n preferences: DEFAULT_PROJECT_PREFERENCES,\n mapLayout: { ...DEFAULT_MAP_GRID_LAYOUT },\n secondaryMapViews: [],\n primaryMapLabel: \"\",\n primaryRenderer: DEFAULT_PRIMARY_RENDERER,\n regionalBasemapPolicy: \"allow\",\n regionalBasemapHold: null,\n basemapChoiceGeneration: 0,\n regionalBasemapAnswers: {},\n\n setMapView: (view, markDirty = false) =>\n set((s) => ({\n mapView: { ...s.mapView, ...view },\n isDirty: markDirty || s.isDirty,\n })),\n setMapGrid: (rows, cols) =>\n set((s) => {\n const clampedRows = clampGridDim(rows);\n const clampedCols = clampGridDim(cols);\n const desiredSecondary = clampedRows * clampedCols - 1;\n let secondaryMapViews = s.secondaryMapViews;\n if (desiredSecondary < secondaryMapViews.length) {\n secondaryMapViews = secondaryMapViews.slice(0, desiredSecondary);\n } else if (desiredSecondary > secondaryMapViews.length) {\n const additions: SecondaryMapView[] = [];\n for (let i = secondaryMapViews.length; i < desiredSecondary; i++) {\n // New panes start as a clone of the primary map's camera and (by\n // having no overrides) inherit its layer visibility, so the\n // comparison begins from the same view the user is looking at.\n additions.push({\n id: uuidv4(),\n view: { ...s.mapView },\n layerVisibility: {},\n });\n }\n secondaryMapViews = [...secondaryMapViews, ...additions];\n }\n return {\n mapLayout: {\n ...s.mapLayout,\n rows: clampedRows,\n cols: clampedCols,\n },\n secondaryMapViews,\n isDirty: true,\n };\n }),\n setSyncView: (syncView) =>\n set((s) => ({\n mapLayout: { ...s.mapLayout, syncView },\n isDirty: true,\n })),\n setSecondaryMapView: (id, view, markDirty = false) =>\n set((s) => {\n let changed = false;\n const secondaryMapViews = s.secondaryMapViews.map((pane) => {\n if (pane.id !== id) return pane;\n const merged = { ...pane.view, ...view };\n // Skip value-identical writes: a programmatic `applyView` (camera\n // sync, initial load) fires \"moveend\" too, so without this guard\n // each pane re-stores the same camera it was just given, churning a\n // new `secondaryMapViews` array and re-rendering every subscriber.\n if (sameCamera(pane.view, merged)) return pane;\n changed = true;\n return { ...pane, view: merged };\n });\n if (!changed) return s;\n return {\n secondaryMapViews,\n isDirty: markDirty || s.isDirty,\n };\n }),\n setSecondaryLayerVisibility: (id, layerId, visible) =>\n set((s) => {\n let changed = false;\n const secondaryMapViews = s.secondaryMapViews.map((pane) => {\n if (pane.id !== id) return pane;\n changed = true;\n return {\n ...pane,\n layerVisibility: { ...pane.layerVisibility, [layerId]: visible },\n };\n });\n if (!changed) return s;\n return { secondaryMapViews, isDirty: true };\n }),\n setPrimaryMapLabel: (label) => set({ primaryMapLabel: label, isDirty: true }),\n setPrimaryRenderer: (renderer) =>\n set((s) => (s.primaryRenderer === renderer ? s : { primaryRenderer: renderer, isDirty: true })),\n setSecondaryMapLabel: (id, label) =>\n set((s) => {\n let changed = false;\n const secondaryMapViews = s.secondaryMapViews.map((pane) => {\n if (pane.id !== id) return pane;\n changed = true;\n return { ...pane, label };\n });\n if (!changed) return s;\n return { secondaryMapViews, isDirty: true };\n }),\n setSecondaryViewKind: (id, viewKind) =>\n set((s) => {\n let changed = false;\n const secondaryMapViews = s.secondaryMapViews.map((pane) => {\n if (pane.id !== id) return pane;\n // Treat an absent viewKind as \"maplibre\" so switching a legacy pane\n // to maplibre is a no-op rather than a churned array.\n if ((pane.viewKind ?? \"maplibre\") === viewKind) return pane;\n changed = true;\n return { ...pane, viewKind };\n });\n if (!changed) return s;\n return { secondaryMapViews, isDirty: true };\n }),\n removeSecondaryMapView: (id) =>\n set((s) => {\n const secondaryMapViews = s.secondaryMapViews.filter((pane) => pane.id !== id);\n if (secondaryMapViews.length === s.secondaryMapViews.length) {\n return s;\n }\n // Collapse to a gap-free grid that fits the remaining panes, keeping\n // the layout's orientation as close as possible to the current one.\n const total = secondaryMapViews.length + 1;\n const { rows, cols } = fitGrid(total, s.mapLayout.cols);\n return {\n secondaryMapViews,\n mapLayout: { ...s.mapLayout, rows, cols },\n isDirty: true,\n };\n }),\n setBasemapStyleUrl: (url) =>\n set((state) => ({\n ...explicitBasemapChoice(state, url),\n preferences: preferencesForBasemap(state),\n isDirty: true,\n })),\n applyPlanetaryBasemap: (basemap) =>\n set((state) => ({\n ...explicitBasemapChoice(state, basemap.styleUrl),\n preferences: preferencesForBasemap(state, basemap.ellipsoidId),\n isDirty: true,\n })),\n restoreEarthBasemap: (styleUrl) =>\n set((state) => ({\n ...explicitBasemapChoice(state, styleUrl),\n preferences: preferencesForBasemap(state, DEFAULT_ELLIPSOID_ID),\n isDirty: true,\n })),\n setBasemapVisible: (visible) => set({ basemapVisible: visible, isDirty: true }),\n setBasemapOpacity: (opacity) => set({ basemapOpacity: opacity, isDirty: true }),\n setBlankBackgroundColor: (color) =>\n set({ blankBackgroundColor: normalizeBlankBackgroundColor(color), isDirty: true }),\n setPreferences: (preferences) => set({ preferences, isDirty: true }),\n setRegionalBasemapPolicy: (policy) => {\n const state = get();\n const hold =\n policy === \"block\" && state.regionalBasemapHold?.reason === \"prompt\"\n ? null\n : state.regionalBasemapHold;\n // Blocking while a regional basemap is on screen takes it down too, so the\n // map stops requesting its tiles. Not the user's edit: no undo step.\n if (policy === \"block\" && getRegionalBasemapByStyleUrl(state.basemapStyleUrl)) {\n withoutHistory(() =>\n set({\n regionalBasemapPolicy: policy,\n regionalBasemapHold: hold,\n basemapStyleUrl: DEFAULT_BASEMAP,\n }),\n );\n return;\n }\n set({ regionalBasemapPolicy: policy, regionalBasemapHold: hold });\n },\n resolveRegionalBasemapHold: (load, options = {}) => {\n const state = get();\n const hold = state.regionalBasemapHold;\n if (!hold) return;\n // Every project load replaces or clears the hold, so the hold read here is\n // always the one the latest load set. Not comparing `projectGeneration`\n // here: a collaboration snapshot bumps it without a load, and that must not\n // drop a pending answer.\n const answered = hold.reason === \"prompt\" && state.regionalBasemapPolicy !== \"block\";\n // Remember a real answer for the session, so the next load of the same\n // basemap does not ask again.\n const regionalBasemapAnswers =\n answered && options.remember !== false\n ? { ...state.regionalBasemapAnswers, [hold.styleUrl]: load }\n : state.regionalBasemapAnswers;\n const applies =\n load &&\n answered &&\n // Only replace the fallback the load put in. An explicit pick already\n // clears the hold; this also covers a direct setState.\n state.basemapStyleUrl === DEFAULT_BASEMAP;\n if (!applies) {\n set({ regionalBasemapHold: null, regionalBasemapAnswers });\n return;\n }\n withoutHistory(() =>\n set({\n basemapStyleUrl: hold.styleUrl,\n regionalBasemapHold: null,\n regionalBasemapAnswers,\n basemapChoiceGeneration: state.basemapChoiceGeneration + 1,\n }),\n );\n // Edits made while the prompt was open recorded the fallback in their undo\n // snapshots. No explicit basemap pick happened since the load (that would\n // have cleared the hold), so every such snapshot gets the accepted basemap,\n // and undoing one of those edits does not take it away again.\n replaceHistoryBasemap(DEFAULT_BASEMAP, hold.styleUrl);\n },\n});\n","/**\n * Saved processing models (issue #344) and the Processing History log\n * (issue #1292). Both are saved with the project.\n */\nimport { MAX_PROCESSING_HISTORY, type ProcessingModel, type ProcessingRun } from \"../types\";\nimport type { SliceCreator } from \"./types\";\n\nexport interface ProcessingSlice {\n /** Saved processing pipelines (batch/model chaining; issue #344). */\n models: ProcessingModel[];\n /** Recorded processing tool runs, oldest first (Processing History; #1292). */\n processingHistory: ProcessingRun[];\n /**\n * Object-Based Analysis workbench state saved with the project (#3053), an\n * app-owned JSON object; null until the workbench is used.\n */\n obiaWorkbench: Record<string, unknown> | null;\n\n /** Insert a new model or replace an existing one matching by `id`. */\n saveModel: (model: ProcessingModel) => void;\n /** Remove a saved model by id. */\n deleteModel: (id: string) => void;\n\n /** Append a processing run to the history (bounded, de-duped by id; #1292). */\n addProcessingRun: (run: ProcessingRun) => void;\n /** Patch a recorded run by id (no-op if absent), e.g. to add output layers. */\n updateProcessingRun: (id: string, patch: Partial<Omit<ProcessingRun, \"id\">>) => void;\n /** Drop all recorded processing runs. */\n clearProcessingHistory: () => void;\n /**\n * Replace the saved workbench state. `markDirty` is false when the app is\n * only mirroring state it just restored, so opening a project stays clean.\n */\n setObiaWorkbench: (value: Record<string, unknown> | null, markDirty?: boolean) => void;\n}\n\nexport const createProcessingSlice: SliceCreator<ProcessingSlice> = (set) => ({\n models: [],\n processingHistory: [],\n obiaWorkbench: null,\n\n saveModel: (model) =>\n set((s) => {\n const exists = s.models.some((m) => m.id === model.id);\n const models = exists\n ? s.models.map((m) => (m.id === model.id ? model : m))\n : [...s.models, model];\n return { models, isDirty: true };\n }),\n deleteModel: (id) =>\n set((s) => ({\n models: s.models.filter((m) => m.id !== id),\n isDirty: true,\n })),\n\n addProcessingRun: (run) =>\n set((s) => {\n // Ignore a duplicate id so updateProcessingRun stays unambiguous.\n if (s.processingHistory.some((r) => r.id === run.id)) return s;\n const processingHistory = [...s.processingHistory, run].slice(-MAX_PROCESSING_HISTORY);\n return { processingHistory, isDirty: true };\n }),\n updateProcessingRun: (id, patch) =>\n set((s) => {\n if (!s.processingHistory.some((r) => r.id === id)) return s;\n return {\n processingHistory: s.processingHistory.map((r) =>\n r.id === id ? { ...r, ...patch, id: r.id } : r,\n ),\n isDirty: true,\n };\n }),\n clearProcessingHistory: () =>\n set((s) => (s.processingHistory.length === 0 ? s : { processingHistory: [], isDirty: true })),\n setObiaWorkbench: (obiaWorkbench, markDirty = true) =>\n set(markDirty ? { obiaWorkbench, isDirty: true } : { obiaWorkbench }),\n});\n","/**\n * Project content that sits beside the layers: review comments (issue #1518),\n * Dashboard widgets (issue #401) and the story map. All of it is saved with the\n * project; `comments` and `storymap` are also tracked by undo history.\n */\nimport {\n DEFAULT_DASHBOARD_COLUMNS,\n DEFAULT_STORY_MAP,\n MAX_DASHBOARD_COLUMNS,\n MIN_DASHBOARD_COLUMNS,\n type CommentReply,\n type DashboardWidget,\n type ProjectBookmark,\n type ProjectBookmarkGroup,\n type ProjectComment,\n type ProjectInteraction,\n type StoryChapter,\n type StoryMap,\n} from \"../types\";\nimport type { SliceCreator } from \"./types\";\n\nexport interface ProjectContentSlice {\n storymap: StoryMap | null;\n /** Saved Dashboard panel chart widgets (issue #401). */\n widgets: DashboardWidget[];\n /** Number of columns in the Dashboard widget grid. */\n dashboardColumns: number;\n /** Saved map views from the Bookmarks panel, in panel order (#2869). */\n bookmarks: ProjectBookmark[];\n /** Folders the bookmarks are organized into. */\n bookmarkGroups: ProjectBookmarkGroup[];\n /** Anchored review comments on map points or features (issue #1518). */\n comments: ProjectComment[];\n /**\n * The loaded project's startup `interaction` block (issue #2688). Applied\n * once on load and written back unchanged on save; live Identify and control\n * changes do not edit it.\n */\n projectInteraction: ProjectInteraction | null;\n\n /** Append a new dashboard widget. */\n addWidget: (widget: DashboardWidget) => void;\n /** Patch an existing dashboard widget by id (no-op if absent). Merges, so an\n * omitted key keeps its current value; use replaceWidget to clear one. */\n updateWidget: (id: string, patch: Partial<Omit<DashboardWidget, \"id\">>) => void;\n /** Swap an existing dashboard widget for a complete new record, keeping its\n * id and position (no-op if absent). Unlike updateWidget this does not merge,\n * so fields the caller omits are cleared — what the widget editor needs to\n * persist an emptied title, color, prefix, or suffix. */\n replaceWidget: (id: string, widget: Omit<DashboardWidget, \"id\">) => void;\n /** Remove a dashboard widget by id. */\n removeWidget: (id: string) => void;\n /** Move a widget to a new index, clamped into range, preserving the rest. */\n moveWidget: (id: string, toIndex: number) => void;\n /** Set the Dashboard widget-grid column count (clamped into range). */\n setDashboardColumns: (columns: number) => void;\n\n setStorymap: (storymap: StoryMap | null) => void;\n updateStorymapSettings: (patch: Partial<Omit<StoryMap, \"chapters\">>) => void;\n addStoryChapter: (chapter: StoryChapter, atIndex?: number) => void;\n updateStoryChapter: (id: string, patch: Partial<StoryChapter>) => void;\n removeStoryChapter: (id: string) => void;\n moveStoryChapter: (id: string, targetIndex: number) => void;\n addComment: (comment: ProjectComment) => void;\n replyToComment: (commentId: string, reply: CommentReply) => void;\n toggleResolveComment: (commentId: string, resolved?: boolean) => void;\n deleteComment: (commentId: string) => void;\n setComments: (comments: ProjectComment[]) => void;\n /**\n * Replace every bookmark and folder (the Bookmarks panel writes its whole\n * state back after each edit). Marks the project dirty.\n */\n setBookmarks: (bookmarks: ProjectBookmark[], bookmarkGroups: ProjectBookmarkGroup[]) => void;\n}\n\nexport const createProjectContentSlice: SliceCreator<ProjectContentSlice> = (set) => ({\n storymap: null,\n widgets: [],\n dashboardColumns: DEFAULT_DASHBOARD_COLUMNS,\n bookmarks: [],\n bookmarkGroups: [],\n comments: [],\n projectInteraction: null,\n\n setBookmarks: (bookmarks, bookmarkGroups) => set({ bookmarks, bookmarkGroups, isDirty: true }),\n\n addComment: (comment) =>\n set((s) => {\n // Ignore a duplicate id: the WebSocket relay echoes our own\n // comment-mutation back to the sender, and a reconnect can replay\n // recent history, so de-dupe defensively (mirrors addCollaborationChat).\n if (s.comments.some((c) => c.id === comment.id)) return s;\n return { comments: [...s.comments, comment], isDirty: true };\n }),\n replyToComment: (commentId, reply) =>\n set((s) => {\n let appended = false;\n const nextComments = s.comments.map((c) => {\n if (c.id !== commentId) return c;\n if (c.replies.some((r) => r.id === reply.id)) return c;\n appended = true;\n return { ...c, replies: [...c.replies, reply] };\n });\n if (!appended) return s;\n return { comments: nextComments, isDirty: true };\n }),\n toggleResolveComment: (commentId, resolved) =>\n set((s) => ({\n comments: s.comments.map((c) =>\n c.id === commentId\n ? { ...c, resolved: resolved !== undefined ? resolved : !c.resolved }\n : c,\n ),\n isDirty: true,\n })),\n deleteComment: (commentId) =>\n set((s) => ({\n comments: s.comments.filter((c) => c.id !== commentId),\n isDirty: true,\n })),\n setComments: (comments) => set({ comments, isDirty: true }),\n\n addWidget: (widget) =>\n set((s) => {\n // Ignore a duplicate id so updateWidget/removeWidget stay unambiguous\n // and a later entry isn't silently dropped when normalized on save.\n if (s.widgets.some((w) => w.id === widget.id)) return s;\n return { widgets: [...s.widgets, widget], isDirty: true };\n }),\n updateWidget: (id, patch) =>\n set((s) => {\n const exists = s.widgets.some((w) => w.id === id);\n if (!exists) return s;\n return {\n widgets: s.widgets.map((w) => (w.id === id ? { ...w, ...patch, id: w.id } : w)),\n isDirty: true,\n };\n }),\n replaceWidget: (id, widget) =>\n set((s) => {\n if (!s.widgets.some((w) => w.id === id)) return s;\n return {\n widgets: s.widgets.map((w) => (w.id === id ? { ...widget, id } : w)),\n isDirty: true,\n };\n }),\n removeWidget: (id) =>\n set((s) => ({\n widgets: s.widgets.filter((w) => w.id !== id),\n isDirty: true,\n })),\n moveWidget: (id, toIndex) =>\n set((s) => {\n const from = s.widgets.findIndex((w) => w.id === id);\n if (from < 0) return s;\n const target = Math.max(0, Math.min(s.widgets.length - 1, toIndex));\n if (target === from) return s;\n const widgets = [...s.widgets];\n const [moved] = widgets.splice(from, 1);\n widgets.splice(target, 0, moved);\n return { widgets, isDirty: true };\n }),\n setDashboardColumns: (columns) =>\n set((s) => {\n // Guard non-finite input so the clamp can't yield NaN columns.\n if (!Number.isFinite(columns)) return s;\n return {\n dashboardColumns: Math.max(\n MIN_DASHBOARD_COLUMNS,\n Math.min(MAX_DASHBOARD_COLUMNS, Math.trunc(columns)),\n ),\n isDirty: true,\n };\n }),\n\n setStorymap: (storymap) => set({ storymap, isDirty: true }),\n updateStorymapSettings: (patch) =>\n set((s) => ({\n storymap: { ...(s.storymap ?? DEFAULT_STORY_MAP), ...patch },\n isDirty: true,\n })),\n addStoryChapter: (chapter, atIndex) =>\n set((s) => {\n const base = s.storymap ?? DEFAULT_STORY_MAP;\n const chapters = [...base.chapters];\n const index =\n atIndex === undefined ? chapters.length : Math.min(Math.max(atIndex, 0), chapters.length);\n chapters.splice(index, 0, chapter);\n return { storymap: { ...base, chapters }, isDirty: true };\n }),\n updateStoryChapter: (id, patch) =>\n set((s) => {\n if (!s.storymap) return s;\n return {\n storymap: {\n ...s.storymap,\n chapters: s.storymap.chapters.map((chapter) =>\n chapter.id === id ? { ...chapter, ...patch } : chapter,\n ),\n },\n isDirty: true,\n };\n }),\n removeStoryChapter: (id) =>\n set((s) => {\n if (!s.storymap) return s;\n return {\n storymap: {\n ...s.storymap,\n chapters: s.storymap.chapters.filter((chapter) => chapter.id !== id),\n },\n isDirty: true,\n };\n }),\n moveStoryChapter: (id, targetIndex) =>\n set((s) => {\n if (!s.storymap) return s;\n const current = s.storymap.chapters.findIndex((chapter) => chapter.id === id);\n if (current < 0) return s;\n const chapters = [...s.storymap.chapters];\n const [chapter] = chapters.splice(current, 1);\n if (!chapter) return s;\n const next = Math.min(Math.max(targetIndex, 0), chapters.length);\n chapters.splice(next, 0, chapter);\n if (chapters.every((item, i) => item.id === s.storymap?.chapters[i]?.id)) {\n return s;\n }\n return { storymap: { ...s.storymap, chapters }, isDirty: true };\n }),\n});\n","/**\n * Project identity and lifecycle: name, path, dirty flag, recent projects, and\n * the project-level settings that have no richer home (plugins, legend, print\n * layout, metadata). `newProject` and `loadProject` replace the whole document\n * (every slice's project fields) and clear undo history.\n */\nimport {\n applyProjectToStore,\n type CreateProjectOptions,\n createEmptyProject,\n DEFAULT_PROJECT_NAME,\n} from \"../project\";\nimport {\n createDefaultPrintLayout,\n printLayoutConfigsEqual,\n type PrintLayoutConfig,\n} from \"../print-layout-config\";\nimport { reapplyLayerJoins } from \"../joins\";\nimport { gateRegionalBasemap } from \"../regional-basemaps\";\nimport {\n DEFAULT_BASEMAP,\n DEFAULT_LEGEND_CONFIG,\n type GeoLibreLayer,\n type GeoLibreProject,\n type LayerGroup,\n type LegendConfig,\n type ProjectPluginState,\n type RecentProjectEntry,\n} from \"../types\";\nimport { resolveIdentifyTarget } from \"./session-slice\";\nimport type { SliceCreator } from \"./types\";\nimport { clearHistory } from \"./undo-history\";\n\nconst MAX_RECENT_PROJECTS = 10;\n\n/** Derive a human-friendly display name from a file path or URL. */\nexport function projectPathLabel(path: string): string {\n return path.split(/[/\\\\]/).pop() || path;\n}\n\nfunction normalizeRecentProjects(projects: RecentProjectEntry[]): RecentProjectEntry[] {\n const seen = new Set<string>();\n const normalized: RecentProjectEntry[] = [];\n\n for (const project of projects) {\n const path = project.path.trim();\n if (!path || seen.has(path)) continue;\n\n const name = project.name.trim() || projectPathLabel(path);\n normalized.push({\n path,\n name,\n openedAt: project.openedAt || new Date().toISOString(),\n });\n seen.add(path);\n }\n\n return normalized.slice(0, MAX_RECENT_PROJECTS);\n}\n\nexport interface ProjectSlice {\n projectName: string;\n projectPath: string | null;\n projectGeneration: number;\n projectLoaded: boolean;\n isDirty: boolean;\n projectPlugins: ProjectPluginState | null;\n legend: LegendConfig;\n /** Print Layout composer settings for the open project (discussion #1992). */\n printLayout: PrintLayoutConfig;\n metadata: Record<string, unknown>;\n recentProjects: RecentProjectEntry[];\n\n setLegend: (legend: LegendConfig) => void;\n /**\n * Replace the Print Layout composer settings. A config equal to the current\n * one is ignored, so re-opening the composer (or a project load seeding the\n * dialog) never marks the project dirty.\n */\n setPrintLayout: (printLayout: PrintLayoutConfig) => void;\n setProjectPlugins: (projectPlugins: ProjectPluginState | null, shouldMarkDirty?: boolean) => void;\n /**\n * Start a new, untitled project. `layers` and `layerGroups` seed it with\n * layers (the Startup setting's default layers).\n */\n newProject: (\n options?: CreateProjectOptions & {\n name?: string;\n layers?: GeoLibreLayer[];\n layerGroups?: LayerGroup[];\n },\n ) => void;\n /**\n * Replace the open project with `project`.\n *\n * A regional basemap the project names is gated by `regionalBasemapPolicy`:\n * under `\"prompt\"` the default basemap is applied and the regional one is\n * held in `regionalBasemapHold` for the user to confirm, under `\"block\"` it is\n * always replaced. Pass `trustBasemap: true` for a load whose basemap the user\n * already chose in this session (an undo step, a duplicate, a template, a\n * host-driven embed load), which skips the prompt but not the block.\n */\n loadProject: (\n project: GeoLibreProject,\n path?: string | null,\n options?: { rememberRecent?: boolean; presenting?: boolean; trustBasemap?: boolean },\n ) => void;\n setProjectPath: (path: string | null) => void;\n setProjectName: (name: string) => void;\n setRecentProjects: (projects: RecentProjectEntry[]) => void;\n rememberRecentProject: (entry: RecentProjectEntry) => void;\n forgetRecentProject: (path: string) => void;\n clearRecentProjects: () => void;\n markSaved: () => void;\n}\n\nexport const createProjectSlice: SliceCreator<ProjectSlice> = (set, get) => ({\n projectName: DEFAULT_PROJECT_NAME,\n projectPath: null,\n projectGeneration: 0,\n projectLoaded: false,\n isDirty: false,\n projectPlugins: null,\n legend: { ...DEFAULT_LEGEND_CONFIG },\n printLayout: createDefaultPrintLayout(),\n metadata: {},\n recentProjects: [],\n\n setLegend: (legend) => set({ legend, isDirty: true }),\n\n setPrintLayout: (printLayout) =>\n set((s) =>\n printLayoutConfigsEqual(s.printLayout, printLayout) ? s : { printLayout, isDirty: true },\n ),\n // When shouldMarkDirty is false the existing dirty flag is preserved rather\n // than set; it cannot clear the flag (only markSaved() does that).\n setProjectPlugins: (projectPlugins, shouldMarkDirty = true) =>\n set((s) => ({\n projectPlugins,\n isDirty: shouldMarkDirty || s.isDirty,\n })),\n\n setProjectPath: (path) => set({ projectPath: path }),\n setProjectName: (name) => set({ projectName: name, isDirty: true }),\n setRecentProjects: (projects) => set({ recentProjects: normalizeRecentProjects(projects) }),\n rememberRecentProject: (entry) =>\n set((s) => ({\n recentProjects: normalizeRecentProjects([entry, ...s.recentProjects]),\n })),\n forgetRecentProject: (path) => {\n // Compare with separators normalized so a backslash/forward-slash mismatch\n // on Windows does not leave a stale entry behind.\n const normalized = path.replace(/\\\\/g, \"/\");\n set((s) => ({\n recentProjects: s.recentProjects.filter(\n (project) => project.path.replace(/\\\\/g, \"/\") !== normalized,\n ),\n }));\n },\n clearRecentProjects: () => set({ recentProjects: [] }),\n markSaved: () => set({ isDirty: false }),\n\n newProject: (options = {}) => {\n const project = {\n ...createEmptyProject(options.name, options),\n ...(options.layers ? { layers: options.layers } : {}),\n ...(options.layerGroups ? { layerGroups: options.layerGroups } : {}),\n };\n const applied = applyProjectToStore(project);\n // A new project's basemap is the user's own pick in the New Project dialog,\n // so only a deployment block replaces it.\n applied.basemapStyleUrl = gateRegionalBasemap(\n applied.basemapStyleUrl,\n get().regionalBasemapPolicy,\n DEFAULT_BASEMAP,\n true,\n ).styleUrl;\n set((s) => ({\n ...applied,\n regionalBasemapHold: null,\n // The New Project dialog's basemap is the user's pick.\n basemapChoiceGeneration: s.basemapChoiceGeneration + 1,\n hoverTooltipsEnabled: true,\n projectPath: null,\n projectGeneration: s.projectGeneration + 1,\n projectLoaded: false,\n isDirty: false,\n selectedLayerId: null,\n selectedFeatureId: null,\n selectedFeatureIds: [],\n identifyLayerId: null,\n identifyLayerIds: null,\n featureSelectionActive: false,\n // The copied style names a layer from the previous project, so a\n // paste in the new one would apply an orphaned entry.\n copiedLayerStyle: null,\n pointerCoords: null,\n pointerElevation: null,\n cameraAltitude: null,\n attributeFilter: \"\",\n // Don't carry an active story presentation into a different project.\n ui: {\n ...s.ui,\n storymapPresenting: false,\n storymapReturnToEditor: false,\n storymapLayerOpacity: {},\n storymapPanelOpen: false,\n storymapComposingId: null,\n // An open selection dialog (and its preselected layer id) belongs\n // to the previous project's layers.\n selectByExpressionOpen: false,\n selectByExpressionLayerId: null,\n selectByLocationOpen: false,\n selectByLocationLayerId: null,\n loadEditorFeaturesOpen: false,\n loadEditorFeaturesLayerId: null,\n // A pending assistant-requested Model Builder load names a model in\n // the previous project's `savedModels`.\n modelBuilderRequestedModelId: null,\n },\n }));\n clearHistory();\n },\n\n loadProject: (project, path = null, options = {}) => {\n const applied = applyProjectToStore(project);\n // Re-resolve persistent attribute joins against the loaded layer set,\n // so joined columns reflect the join tables as saved (and a stale\n // saved copy of the joined output self-heals).\n applied.layers = reapplyLayerJoins(applied.layers);\n // A project file that names a regional basemap would otherwise load tiles\n // from servers in mainland China without the user choosing that, so the\n // default basemap renders until the user confirms (or for good, when the\n // deployment disabled regional basemaps).\n const requestedBasemap = applied.basemapStyleUrl;\n // An answer the user already gave this session for the same basemap\n // stands: Load trusts it, Use default basemap keeps the fallback quietly.\n const priorAnswer = get().regionalBasemapAnswers[requestedBasemap];\n const gated = gateRegionalBasemap(\n requestedBasemap,\n get().regionalBasemapPolicy,\n DEFAULT_BASEMAP,\n options.trustBasemap === true || priorAnswer === true,\n );\n if (gated.held === \"prompt\" && priorAnswer === false) delete gated.held;\n applied.basemapStyleUrl = gated.styleUrl;\n // A project that ships a story map opens straight into the presentation\n // so the reader sees the story, not the editor. Projects without a story\n // (or with an empty one) open normally. Callers that open a project for\n // authoring rather than viewing can pass `presenting: false` to override.\n const presentStory = options.presenting ?? (applied.storymap?.chapters.length ?? 0) > 0;\n const selectedLayerId =\n project.selectedLayerId === null\n ? null\n : typeof project.selectedLayerId === \"string\" &&\n applied.layers.some((layer) => layer.id === project.selectedLayerId)\n ? project.selectedLayerId\n : (applied.layers[0]?.id ?? null);\n set((s) => ({\n ...applied,\n regionalBasemapHold: gated.held\n ? {\n styleUrl: requestedBasemap,\n reason: gated.held,\n }\n : null,\n hoverTooltipsEnabled: true,\n projectPath: path,\n projectGeneration: s.projectGeneration + 1,\n projectLoaded: true,\n isDirty: false,\n selectedLayerId,\n selectedFeatureId: null,\n selectedFeatureIds: [],\n featureSelectionActive: false,\n // A project that saved an Identify target (issue #2688) opens with it\n // armed; any other load disarms Identify, since the previous target\n // named a layer of the project being replaced.\n ...resolveIdentifyTarget(\n applied.projectInteraction?.identify,\n applied.layers.map((layer) => layer.id),\n ),\n // The copied style names a layer from the previous project, so a\n // paste in the loaded one would apply an orphaned entry.\n copiedLayerStyle: null,\n // Ephemeral readouts describe the previous project's map. The\n // elevation and altitude especially: a project that switches to a\n // planetary body would otherwise keep showing Earth-scaled values\n // until the next hover or camera move.\n pointerCoords: null,\n pointerElevation: null,\n cameraAltitude: null,\n // Present a bundled story on load; otherwise drop any presentation\n // carried over from the previous project.\n ui: {\n ...s.ui,\n storymapPresenting: presentStory,\n // A bundled story auto-presents for viewing, so exiting it should\n // not pop open the editor (#918).\n storymapReturnToEditor: false,\n storymapLayerOpacity: {},\n storymapPanelOpen: false,\n storymapComposingId: null,\n // An open selection dialog (and its preselected layer id) belongs\n // to the previous project's layers.\n selectByExpressionOpen: false,\n selectByExpressionLayerId: null,\n selectByLocationOpen: false,\n selectByLocationLayerId: null,\n loadEditorFeaturesOpen: false,\n loadEditorFeaturesLayerId: null,\n // A pending assistant-requested Model Builder load names a model in\n // the previous project's `savedModels`.\n modelBuilderRequestedModelId: null,\n },\n }));\n clearHistory();\n if (path && options.rememberRecent !== false) {\n get().rememberRecentProject({\n path,\n name: project.name,\n openedAt: new Date().toISOString(),\n });\n }\n },\n});\n","/**\n * UI-only state: which dialogs and panels are open, and the one-shot requests\n * they consume. Lives in the `ui` sub-state, which the undo history never\n * tracks (see `undo-history.ts`), so opening or closing a dialog never records\n * an undo step and undo/redo never toggles one.\n */\nimport type { ProcessingRerunRequest } from \"../types\";\nimport type { SliceCreator } from \"./types\";\n\nexport type ConversionToolKind =\n | \"vector-to-vector\"\n | \"vector-to-geoparquet\"\n | \"vector-to-flatgeobuf\"\n | \"vector-to-shapefile\"\n | \"vector-to-geopackage\"\n | \"csv-to-geoparquet\"\n | \"vector-to-pmtiles\"\n | \"raster-to-pmtiles\"\n | \"raster-to-cog\";\n\n/**\n * Identifiers of the vector processing tools. Kept in sync by hand with the\n * `id` fields of `VECTOR_TOOLS` in `@geolibre/processing` (`vector-tools.ts`);\n * deriving the type there would create a core -> processing circular import.\n */\nexport type VectorToolKind =\n | \"buffer\"\n | \"centroids\"\n | \"convex-hull\"\n | \"dissolve\"\n | \"bounding-box\"\n | \"simplify\"\n | \"clip\"\n | \"intersection\"\n | \"difference\"\n | \"union\"\n | \"spatial-join\"\n | \"attribute-join\"\n | \"select-by-value\"\n | \"select-by-location\"\n | \"random-extract\"\n | \"reproject\"\n | \"explode\"\n | \"aggregate\"\n | \"smooth\"\n | \"extract-vertices\"\n | \"points-along-geometry\"\n | \"grid\"\n | \"voronoi\"\n | \"cell-sectors\"\n | \"dggs-grid\"\n | \"dggs-bin\"\n | \"dggs-compact\"\n | \"trajectory-speed\"\n | \"detect-stops\"\n | \"space-time-proximity\"\n | \"decode-polyline\"\n | \"encode-polyline\"\n | \"merge-layers\"\n | \"check-validity\"\n | \"fix-geometries\"\n | \"check-topology-rules\"\n | \"fix-topology\";\n\n/** Identifiers of the network-analysis tools (`NETWORK_TOOLS` ids). */\nexport type NetworkToolKind = \"isochrone\" | \"od-matrix\" | \"sequential-route\";\n\n/** Identifiers of the spatial-statistics tools (`STATISTICS_TOOLS` ids). */\nexport type StatisticsToolKind =\n | \"global-morans-i\"\n | \"local-morans-i\"\n | \"getis-ord-gi\"\n | \"average-nearest-neighbor\"\n | \"kernel-density\"\n | \"emerging-hot-spot\"\n | \"composite-score\";\n\n/**\n * Identifiers of the raster processing tools. Kept in sync by hand with the\n * `id` fields of `RASTER_TOOLS` in `@geolibre/processing` (`raster-tools.ts`);\n * deriving the type there would create a core -> processing circular import.\n */\nexport type RasterToolKind =\n | \"hillshade\"\n | \"slope\"\n | \"aspect\"\n | \"reproject\"\n | \"resample\"\n | \"clip-extent\"\n | \"clip-mask\"\n | \"polygonize\"\n | \"contour\"\n | \"interpolate\"\n | \"zonal\"\n | \"raster-calc\"\n | \"spectral-index\"\n | \"reclassify\"\n | \"mosaic\"\n | \"focal\";\n\nexport interface UiSlice {\n ui: {\n processingOpen: boolean;\n /**\n * Tool id to preselect when the Whitebox toolbox dialog opens, set when the\n * user picks a specific tool from the Processing menu's category submenus.\n * Consumed and cleared by ProcessingDialog. Null means \"no preselection\".\n */\n processingInitialTool: string | null;\n conversionOpen: ConversionToolKind | null;\n vectorToolOpen: VectorToolKind | null;\n networkToolOpen: NetworkToolKind | null;\n statisticsToolOpen: StatisticsToolKind | null;\n rasterToolOpen: RasterToolKind | null;\n segmentationOpen: boolean;\n objectDetectionOpen: boolean;\n segmentEverythingOpen: boolean;\n geocodeOpen: boolean;\n sqlWorkspaceOpen: boolean;\n loadEditorFeaturesOpen: boolean;\n // Store layer preselected in the \"Load Features into Editor\" dialog when it\n // is opened from a layer's context menu, or null when opened without a target.\n loadEditorFeaturesLayerId: string | null;\n pythonConsoleOpen: boolean;\n notebookOpen: boolean;\n assistantOpen: boolean;\n attributeTableOpen: boolean;\n /**\n * Pending \"open the attribute table in edit mode\" request for this layer,\n * written by Identify's Edit attributes action (#2932). The table consumes\n * and clears it. Null when idle.\n */\n attributeTableEditLayerId: string | null;\n /** Whether the Raster Attribute Table bottom panel is open (issue #1307). */\n rasterAttributeTableOpen: boolean;\n dashboardOpen: boolean;\n storymapPanelOpen: boolean;\n storymapPresenting: boolean;\n // True when the active presentation was launched from the editor, so exiting\n // it reopens the Story Map editor instead of dropping to the bare map\n // (#918). Auto-presented projects (opened for viewing) leave this false.\n storymapReturnToEditor: boolean;\n /**\n * Layer opacities the active story presentation has applied so far, keyed\n * by store layer id. Playback fades layers by writing MapLibre paint\n * properties directly (never the persisted `layers[].opacity`), so this is\n * how renderers outside MapLibre's paint model (deck.gl diagrams, 3D\n * Z-value geometry) and the on-map Legend follow a chapter's fades. Empty\n * while not presenting; never saved with the project.\n */\n storymapLayerOpacity: Record<string, number>;\n // Id of the chapter currently being composed on the live map. When set, the\n // Story Map dialog is hidden so the user can pan/zoom/tilt the real map and\n // save the resulting camera back into this chapter (issue #775).\n storymapComposingId: string | null;\n /** The Batch tools dialog (run one tool across many layers). */\n batchToolsOpen: boolean;\n /** The Model Builder canvas panel (author a processing graph). */\n modelBuilderOpen: boolean;\n /** One-shot request for Model Builder to load a saved model. */\n modelBuilderRequestedModelId: string | null;\n /** Style Manager dialog visibility (issue #1294). */\n styleManagerOpen: boolean;\n /** Processing History panel visibility (#1292). */\n processingHistoryOpen: boolean;\n /** Select by Expression dialog visibility (#1314). */\n selectByExpressionOpen: boolean;\n // Layer preselected in the Select by Expression dialog when it is opened\n // from a layer's context menu, or null when opened without a target.\n // Deliberately not selectLayer(): that would clear the live selection the\n // dialog's add/remove/intersect modes need to combine with.\n selectByExpressionLayerId: string | null;\n /** Select by Location dialog visibility (#1314). */\n selectByLocationOpen: boolean;\n /** Same contract as `selectByExpressionLayerId`, for Select by Location. */\n selectByLocationLayerId: string | null;\n /**\n * Pending \"re-run from History\" request. Written by the History panel just\n * before it opens the target processing dialog; consumed and cleared by\n * that dialog once it has pre-filled its parameter form. Null when idle.\n */\n processingRerun: ProcessingRerunRequest | null;\n zoomToSelectedFeature: boolean;\n // Live-collaboration dialog visibility. Lifted into the store (rather than\n // local toolbar state) so the on-canvas session-status badge can reopen the\n // Collaborate dialog from outside the toolbar's component tree (#754).\n collaborateDialogOpen: boolean;\n };\n\n setProcessingOpen: (open: boolean) => void;\n setProcessingInitialTool: (toolId: string | null) => void;\n setConversionOpen: (kind: ConversionToolKind | null) => void;\n setVectorToolOpen: (kind: VectorToolKind | null) => void;\n setNetworkToolOpen: (kind: NetworkToolKind | null) => void;\n setStatisticsToolOpen: (kind: StatisticsToolKind | null) => void;\n setRasterToolOpen: (kind: RasterToolKind | null) => void;\n setSegmentationOpen: (open: boolean) => void;\n setObjectDetectionOpen: (open: boolean) => void;\n setSegmentEverythingOpen: (open: boolean) => void;\n setGeocodeOpen: (open: boolean) => void;\n setSqlWorkspaceOpen: (open: boolean) => void;\n setLoadEditorFeaturesOpen: (open: boolean, layerId?: string | null) => void;\n setPythonConsoleOpen: (open: boolean) => void;\n setNotebookOpen: (open: boolean) => void;\n setAssistantOpen: (open: boolean) => void;\n setAttributeTableOpen: (open: boolean) => void;\n /**\n * Ask the attribute table to open in edit mode on `layerId` (opening the\n * table too), or clear a pending request with `null`.\n */\n requestAttributeTableEdit: (layerId: string | null) => void;\n setRasterAttributeTableOpen: (open: boolean) => void;\n setDashboardOpen: (open: boolean) => void;\n setStorymapPanelOpen: (open: boolean) => void;\n setStorymapPresenting: (presenting: boolean, returnToEditor?: boolean) => void;\n /**\n * Record the layer opacities a story chapter applied, merging into the\n * presentation's running map (see `ui.storymapLayerOpacity`).\n */\n setStorymapLayerOpacity: (changes: Record<string, number>) => void;\n setStorymapComposing: (chapterId: string | null) => void;\n setBatchToolsOpen: (open: boolean) => void;\n setModelBuilderOpen: (open: boolean) => void;\n setModelBuilderRequestedModelId: (id: string | null) => void;\n setProcessingHistoryOpen: (open: boolean) => void;\n /** Open/close Select by Expression, optionally preselecting a target layer. */\n setSelectByExpressionOpen: (open: boolean, layerId?: string | null) => void;\n /** Open/close Select by Location, optionally preselecting a target layer. */\n setSelectByLocationOpen: (open: boolean, layerId?: string | null) => void;\n setProcessingRerun: (request: ProcessingRerunRequest | null) => void;\n setCollaborateDialogOpen: (open: boolean) => void;\n setZoomToSelectedFeature: (enabled: boolean) => void;\n setStyleManagerOpen: (open: boolean) => void;\n}\n\nexport const createUiSlice: SliceCreator<UiSlice> = (set) => ({\n ui: {\n processingOpen: false,\n processingInitialTool: null,\n conversionOpen: null,\n vectorToolOpen: null,\n networkToolOpen: null,\n statisticsToolOpen: null,\n rasterToolOpen: null,\n segmentationOpen: false,\n objectDetectionOpen: false,\n segmentEverythingOpen: false,\n geocodeOpen: false,\n sqlWorkspaceOpen: false,\n loadEditorFeaturesOpen: false,\n loadEditorFeaturesLayerId: null,\n pythonConsoleOpen: false,\n notebookOpen: false,\n assistantOpen: false,\n attributeTableOpen: false,\n attributeTableEditLayerId: null,\n rasterAttributeTableOpen: false,\n dashboardOpen: false,\n storymapPanelOpen: false,\n storymapPresenting: false,\n storymapReturnToEditor: false,\n storymapLayerOpacity: {},\n storymapComposingId: null,\n batchToolsOpen: false,\n modelBuilderOpen: false,\n modelBuilderRequestedModelId: null,\n styleManagerOpen: false,\n processingHistoryOpen: false,\n selectByExpressionOpen: false,\n selectByExpressionLayerId: null,\n selectByLocationOpen: false,\n selectByLocationLayerId: null,\n processingRerun: null,\n zoomToSelectedFeature: false,\n collaborateDialogOpen: false,\n },\n\n setProcessingOpen: (open) => set((s) => ({ ui: { ...s.ui, processingOpen: open } })),\n setProcessingInitialTool: (toolId) =>\n set((s) => ({ ui: { ...s.ui, processingInitialTool: toolId } })),\n setConversionOpen: (kind) => set((s) => ({ ui: { ...s.ui, conversionOpen: kind } })),\n setVectorToolOpen: (kind) =>\n set((s) => ({\n ui: {\n ...s.ui,\n // Pre-DGGS projects / callers may still pass h3-grid / h3-bin-points.\n vectorToolOpen:\n (kind as string | null) === \"h3-grid\"\n ? \"dggs-grid\"\n : (kind as string | null) === \"h3-bin-points\"\n ? \"dggs-bin\"\n : kind,\n },\n })),\n setNetworkToolOpen: (kind) => set((s) => ({ ui: { ...s.ui, networkToolOpen: kind } })),\n setStatisticsToolOpen: (kind) => set((s) => ({ ui: { ...s.ui, statisticsToolOpen: kind } })),\n setRasterToolOpen: (kind) => set((s) => ({ ui: { ...s.ui, rasterToolOpen: kind } })),\n setSegmentationOpen: (open) => set((s) => ({ ui: { ...s.ui, segmentationOpen: open } })),\n setObjectDetectionOpen: (open) => set((s) => ({ ui: { ...s.ui, objectDetectionOpen: open } })),\n setSegmentEverythingOpen: (open) =>\n set((s) => ({ ui: { ...s.ui, segmentEverythingOpen: open } })),\n setGeocodeOpen: (open) => set((s) => ({ ui: { ...s.ui, geocodeOpen: open } })),\n setSqlWorkspaceOpen: (open) => set((s) => ({ ui: { ...s.ui, sqlWorkspaceOpen: open } })),\n setLoadEditorFeaturesOpen: (open, layerId) =>\n set((s) => ({\n ui: {\n ...s.ui,\n loadEditorFeaturesOpen: open,\n loadEditorFeaturesLayerId: open ? (layerId ?? null) : null,\n },\n })),\n setPythonConsoleOpen: (open) => set((s) => ({ ui: { ...s.ui, pythonConsoleOpen: open } })),\n setNotebookOpen: (open) => set((s) => ({ ui: { ...s.ui, notebookOpen: open } })),\n setAssistantOpen: (open) => set((s) => ({ ui: { ...s.ui, assistantOpen: open } })),\n setAttributeTableOpen: (open) => set((s) => ({ ui: { ...s.ui, attributeTableOpen: open } })),\n requestAttributeTableEdit: (layerId) =>\n set((s) => ({\n ui: {\n ...s.ui,\n attributeTableEditLayerId: layerId,\n attributeTableOpen: layerId === null ? s.ui.attributeTableOpen : true,\n },\n })),\n setRasterAttributeTableOpen: (open) =>\n set((s) => ({ ui: { ...s.ui, rasterAttributeTableOpen: open } })),\n setDashboardOpen: (open) => set((s) => ({ ui: { ...s.ui, dashboardOpen: open } })),\n setStorymapPanelOpen: (open) =>\n set((s) => ({\n ui: {\n ...s.ui,\n storymapPanelOpen: open,\n // Opening the editor must leave compose mode, or the menu item could\n // re-open the dialog while the compose bar is still active over a\n // now-hidden map (#775). Closing (entering compose) leaves it as-is.\n ...(open ? { storymapComposingId: null } : {}),\n },\n })),\n setStorymapPresenting: (presenting, returnToEditor = false) =>\n set((s) => ({\n ui: {\n ...s.ui,\n storymapPresenting: presenting,\n // Track whether exiting should reopen the editor; only meaningful\n // while presenting, so it clears once the presentation ends (#918).\n storymapReturnToEditor: presenting ? returnToEditor : false,\n // A presentation starts from (and leaves behind) a clean slate; the\n // fades it applies are replayed from chapter 0 on the next run.\n storymapLayerOpacity: {},\n },\n })),\n setStorymapLayerOpacity: (changes) =>\n set((s) => {\n const next = { ...s.ui.storymapLayerOpacity };\n let changed = false;\n for (const [layerId, opacity] of Object.entries(changes)) {\n const clamped = Math.min(1, Math.max(0, opacity));\n if (next[layerId] === clamped) continue;\n next[layerId] = clamped;\n changed = true;\n }\n // Return the current state untouched when nothing moved: Zustand only\n // skips the listener broadcast for the same state reference, and a\n // chapter re-entering the same opacities would otherwise rebuild\n // every store subscriber's view.\n return changed ? { ui: { ...s.ui, storymapLayerOpacity: next } } : s;\n }),\n setStorymapComposing: (chapterId) =>\n set((s) => ({ ui: { ...s.ui, storymapComposingId: chapterId } })),\n setBatchToolsOpen: (open) => set((s) => ({ ui: { ...s.ui, batchToolsOpen: open } })),\n setModelBuilderOpen: (open) => set((s) => ({ ui: { ...s.ui, modelBuilderOpen: open } })),\n setModelBuilderRequestedModelId: (id) =>\n set((s) => ({ ui: { ...s.ui, modelBuilderRequestedModelId: id } })),\n setProcessingHistoryOpen: (open) =>\n set((s) => ({ ui: { ...s.ui, processingHistoryOpen: open } })),\n setProcessingRerun: (request) => set((s) => ({ ui: { ...s.ui, processingRerun: request } })),\n setSelectByExpressionOpen: (open, layerId) =>\n set((s) => ({\n ui: {\n ...s.ui,\n selectByExpressionOpen: open,\n selectByExpressionLayerId: open ? (layerId ?? null) : null,\n },\n })),\n setSelectByLocationOpen: (open, layerId) =>\n set((s) => ({\n ui: {\n ...s.ui,\n selectByLocationOpen: open,\n selectByLocationLayerId: open ? (layerId ?? null) : null,\n },\n })),\n setCollaborateDialogOpen: (open) =>\n set((s) => ({ ui: { ...s.ui, collaborateDialogOpen: open } })),\n setZoomToSelectedFeature: (enabled) =>\n set((s) => ({ ui: { ...s.ui, zoomToSelectedFeature: enabled } })),\n\n setStyleManagerOpen: (open) => set((s) => ({ ui: { ...s.ui, styleManagerOpen: open } })),\n});\n","/**\n * The app store: one Zustand store wrapped in zundo's undo history, composed\n * from the slices under `./store/` (zustand's slices pattern). The split is\n * internal — the hook, the state shape and every export are unchanged, and\n * consumers keep importing from here (or `@geolibre/core`).\n *\n * Slices: project (identity and lifecycle), map-view (camera, basemap, grid),\n * layers, layer-groups, project-content (comments, dashboard, story map),\n * processing (models, run history), libraries (style / layer / template),\n * session (selection and live readouts), collaboration, capabilities, and ui\n * (dialog and panel flags). Undo history lives in `./store/undo-history.ts`.\n */\nimport { create } from \"zustand\";\nimport { temporal } from \"zundo\";\nimport { appPrivilegeReason } from \"./capabilities\";\nimport { setActiveEllipsoidId } from \"./ellipsoids\";\nimport type { AppPrivilege } from \"./types\";\nimport { createCapabilitiesSlice } from \"./store/capabilities-slice\";\nimport { createCollaborationSlice } from \"./store/collaboration-slice\";\nimport { createLayerGroupsSlice } from \"./store/layer-groups-slice\";\nimport { createLayersSlice } from \"./store/layers-slice\";\nimport { createLibrariesSlice } from \"./store/libraries-slice\";\nimport { createMapViewSlice } from \"./store/map-view-slice\";\nimport { createProcessingSlice } from \"./store/processing-slice\";\nimport { createProjectContentSlice } from \"./store/project-content-slice\";\nimport { createProjectSlice } from \"./store/project-slice\";\nimport { createSessionSlice } from \"./store/session-slice\";\nimport { createUiSlice } from \"./store/ui-slice\";\nimport type { AppState } from \"./store/types\";\nimport { bindHistoryStore, createHistoryOptions } from \"./store/undo-history\";\n\nexport type { AppState } from \"./store/types\";\nexport type {\n ConversionToolKind,\n NetworkToolKind,\n RasterToolKind,\n StatisticsToolKind,\n VectorToolKind,\n} from \"./store/ui-slice\";\nexport {\n type GpsStatusFix,\n IDENTIFY_ALL_LAYERS_ID,\n type IdentifyState,\n identifyAllIncludes,\n identifyStateWithoutLayers,\n resolveIdentifyTarget,\n} from \"./store/session-slice\";\nexport { DEFAULT_COLLABORATION_STATE } from \"./store/collaboration-slice\";\nexport { projectPathLabel } from \"./store/project-slice\";\nexport {\n canRedoProjectRestore,\n canUndoProjectRestore,\n clearHistory,\n withoutHistory,\n redo,\n registerProjectRestoreHistory,\n subscribeProjectRestoreHistory,\n undo,\n} from \"./store/undo-history\";\n\nexport const useAppStore = create<AppState>()(\n temporal(\n (set, get) => ({\n ...createProjectSlice(set, get),\n ...createMapViewSlice(set, get),\n ...createLayersSlice(set, get),\n ...createLayerGroupsSlice(set, get),\n ...createProjectContentSlice(set, get),\n ...createProcessingSlice(set, get),\n ...createLibrariesSlice(set, get),\n ...createSessionSlice(set, get),\n ...createCollaborationSlice(set, get),\n ...createCapabilitiesSlice(set, get),\n ...createUiSlice(set, get),\n }),\n createHistoryOptions(),\n ),\n);\n\nbindHistoryStore(useAppStore);\n\n// Mirror the project's ellipsoid into the module-level singleton the\n// measurement helpers read. One subscription covers every path that changes\n// preferences (setPreferences, load/new project, undo/redo) without threading\n// the value through each call site. The subscription runs on every store\n// mutation (e.g. setPointerCoords on each mousemove), so guard on the id to skip\n// the redundant work for a value that changes at most once per session.\nlet lastEllipsoidId = useAppStore.getState().preferences.map.ellipsoidId;\nsetActiveEllipsoidId(lastEllipsoidId);\nuseAppStore.subscribe((state) => {\n const id = state.preferences.map.ellipsoidId;\n if (id === lastEllipsoidId) return;\n lastEllipsoidId = id;\n setActiveEllipsoidId(id);\n});\n\n/**\n * Whether the map should fit to data the user just added, per the project's\n * Map Preferences. Add-data paths consult this before an automatic fit; an\n * explicit \"Zoom to layer\" request ignores it.\n *\n * @returns `true` unless the user turned off zooming to new layers.\n */\nexport function shouldZoomToNewLayers(): boolean {\n return useAppStore.getState().preferences.map.zoomToNewLayers !== false;\n}\n\n/**\n * React hook for consuming application capability state for a specific privilege.\n *\n * @param privilege - The privilege to check.\n * @returns `{ granted: boolean, reason?: string }`\n */\nexport function useAppCapability(privilege: AppPrivilege): { granted: boolean; reason?: string } {\n const capabilities = useAppStore((state) => state.capabilities);\n return {\n granted: capabilities.privileges.includes(privilege),\n reason: appPrivilegeReason(capabilities, privilege),\n };\n}\n","import { useAppStore } from \"./store\";\nimport { applySelectionMode, type SelectionMode } from \"./selection\";\n\n/**\n * Store-touching selection helpers. Kept out of `selection.ts` so that module\n * stays a pure, directly testable set of functions over id arrays.\n */\n\n/**\n * Applies a matched id set to the live selection under the given mode and\n * returns the resulting selection size. Combines with the current selection\n * only when the target layer already holds it (ids are per-layer, so a\n * cross-layer combine would mix unrelated features). Otherwise `current` is\n * empty: \"new\"/\"add\" start a fresh selection on the target layer, while\n * \"remove\"/\"intersect\" necessarily yield an empty one — the dialogs guard\n * this by forcing mode \"new\" when the target layer does not hold the\n * selection (SelectionModeField's disableCombineModes). `selectLayer` runs\n * before `selectFeatures` because it clears the selection as a side effect.\n *\n * Lives in `@geolibre/core` rather than the app so the layer-panel dialogs and\n * the map's drawing gestures (`@geolibre/map`) share one copy of this rule\n * instead of two that can drift apart.\n */\nexport function applyMatchedSelection(\n targetLayerId: string,\n matchedIds: string[],\n mode: SelectionMode,\n): number {\n const store = useAppStore.getState();\n const current = store.selectedLayerId === targetLayerId ? store.selectedFeatureIds : [];\n const next = applySelectionMode(current, matchedIds, mode);\n if (store.selectedLayerId !== targetLayerId) store.selectLayer(targetLayerId);\n store.selectFeatures(next);\n return next.length;\n}\n","/**\n * Shared scale-bar unit maths.\n *\n * The on-screen scale bar (`PlanetaryScaleControl` in `@geolibre/map`) and the\n * Print Layout scale bar (`apps/geolibre-desktop/src/lib/print-layout.ts`) both\n * need to pick a \"nice\" round distance in the user's chosen unit system\n * (metric / imperial / nautical). Keeping that logic in one place here means the\n * two bars round a given ground span to the same value and share the same unit\n * constants, so a change to one can never silently diverge from the other.\n */\n\nimport type { MapScaleUnit } from \"./types\";\n\n/** Conversion constants shared by every scale-bar denomination. */\nexport const FEET_PER_METER = 3.2808398950131235; // 1 / 0.3048\nexport const METERS_PER_FOOT = 0.3048;\nexport const FEET_PER_MILE = 5280;\nexport const METERS_PER_MILE = 1609.344; // 5280 ft\nexport const METERS_PER_NAUTICAL_MILE = 1852;\n\n/**\n * The largest `1 / 2 / 3 / 5 / 10 × 10ⁿ` number that does not exceed `num`, so\n * the bar lands on a readable round value. Uses log10 for the magnitude (unlike\n * MapLibre's digit-count trick, which returns 1 for any `0 < num < 1` and so\n * rounds *up* — producing a bar wider than the max width for sub-unit spans).\n * Returns 0 for a non-positive input so callers can clamp or fall back.\n */\nexport function getRoundNum(num: number): number {\n if (!(num > 0)) return 0;\n const pow10 = Math.pow(10, Math.floor(Math.log10(num)));\n const d = num / pow10;\n const nice = d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : 1;\n return pow10 * nice;\n}\n\n/** Trim trailing-zero noise from a rounded value (e.g. 0.5, 2, 300). */\nexport function formatRoundNum(value: number): string {\n return Number.parseFloat(value.toPrecision(12)).toString();\n}\n\n/**\n * The denomination a scale bar rounds and labels distances with for a ground\n * span of `maxMeters` in the requested unit system: km/m for metric, mi/ft for\n * imperial, and nautical miles for nautical. The unit crosses to the larger\n * denomination once the span exceeds one of it. `metersPerUnit` is the size of\n * one unit of that denomination in metres, so a caller can round the span in\n * unit space and convert the result back to metres (and thus pixels).\n */\nexport function scaleDenomination(\n maxMeters: number,\n unit: MapScaleUnit,\n): { metersPerUnit: number; label: string } {\n if (unit === \"imperial\") {\n const feet = maxMeters * FEET_PER_METER;\n return feet >= FEET_PER_MILE\n ? { metersPerUnit: METERS_PER_MILE, label: \"mi\" }\n : { metersPerUnit: METERS_PER_FOOT, label: \"ft\" };\n }\n if (unit === \"nautical\") {\n return { metersPerUnit: METERS_PER_NAUTICAL_MILE, label: \"nmi\" };\n }\n return maxMeters >= 1000\n ? { metersPerUnit: 1000, label: \"km\" }\n : { metersPerUnit: 1, label: \"m\" };\n}\n\n/**\n * Convert a ground distance in metres into the span (already divided into the\n * bar's denomination) and its label — the shape the on-screen control consumes.\n * Derived from {@link scaleDenomination} so the two never diverge.\n */\nexport function scaleSpan(maxMeters: number, unit: MapScaleUnit): { span: number; label: string } {\n const { metersPerUnit, label } = scaleDenomination(maxMeters, unit);\n return { span: maxMeters / metersPerUnit, label };\n}\n","/**\n * Shared ground-elevation resolution.\n *\n * Several places in the app need \"how high is the ground at this coordinate\":\n * the Elevation Profile plugin (along a drawn line), the Measure tool's\n * terrain-aware readout (along a measured geometry), and the status bar (under\n * the cursor). They all resolve it the same way, from two sources tried in\n * order:\n *\n * 1. The map's own terrain (`map.queryTerrainElevation`) when 3D terrain is\n * enabled — instant and offline, but only where DEM tiles are loaded.\n * MapLibre returns elevations multiplied by the terrain exaggeration, so\n * values are divided back to true meters.\n * 2. The keyless Open-Meteo elevation API — works without terrain enabled and\n * anywhere on Earth, but costs a network round-trip.\n *\n * This module lives in `@geolibre/core` rather than beside the Elevation\n * Profile plugin because `@geolibre/map` (which owns the pointer readout)\n * cannot import from `@geolibre/plugins` without a dependency cycle — plugins\n * already depends on map. Core is the only package all three can share.\n *\n * Source 2 is Earth-only: Open-Meteo has no data for the planetary basemaps, so\n * callers must not reach for it when the active body is not Earth.\n */\n\n/** A coordinate as `[longitude, latitude]` in degrees. */\nexport type LngLat = [number, number];\n\n/** Open-Meteo accepts at most 100 coordinates per elevation request. */\nexport const MAX_POINTS_PER_REQUEST = 100;\n\n/** Abort an elevation request that has not responded within this window. */\nexport const ELEVATION_REQUEST_TIMEOUT_MS = 15000;\n\nconst ENDPOINT = \"https://api.open-meteo.com/v1/elevation\";\n\n/** Error thrown when an elevation request cannot be completed or parsed. */\nexport class ElevationFetchError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ElevationFetchError\";\n }\n}\n\n/** A `fetch`-compatible function, so tests can inject a stub. */\nexport type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;\n\ninterface ElevationResponse {\n elevation?: number[];\n}\n\n/**\n * Normalize longitude into [-180, 180] degrees (handles unwrapped coordinates\n * from antimeridian cross-overs and world-copy pannings).\n */\nexport function normalizeLongitude(lng: number): number {\n if (!Number.isFinite(lng)) return 0;\n return ((((lng + 180) % 360) + 360) % 360) - 180;\n}\n\n/**\n * Clamp latitude into [-90, 90] degrees.\n */\nexport function clampLatitude(lat: number): number {\n if (!Number.isFinite(lat)) return 0;\n return Math.min(90, Math.max(-90, lat));\n}\n\n/**\n * Fetch elevations (in meters) for an ordered list of coordinates.\n *\n * @param points - Coordinates as `[lng, lat]`, at most {@link MAX_POINTS_PER_REQUEST}\n * @param fetchImpl - Optional `fetch` implementation; defaults to the global `fetch`\n * @returns The elevation in meters for each input coordinate, in the same order\n * @throws {ElevationFetchError} On too many points, a non-finite coordinate, a\n * network error, a non-2xx response, a malformed body, or a length mismatch\n */\nexport async function fetchElevations(points: LngLat[], fetchImpl?: FetchLike): Promise<number[]> {\n if (points.length === 0) return [];\n if (points.length > MAX_POINTS_PER_REQUEST) {\n throw new ElevationFetchError(\n `Too many points: ${points.length} (max ${MAX_POINTS_PER_REQUEST}).`,\n );\n }\n // Normalizing would turn NaN/Infinity into 0 and query (0, 0) as if it were\n // the requested point, so reject them instead of returning a wrong elevation.\n if (points.some(([lng, lat]) => !Number.isFinite(lng) || !Number.isFinite(lat))) {\n throw new ElevationFetchError(\"Invalid coordinate: longitude and latitude must be finite.\");\n }\n\n const doFetch: FetchLike = fetchImpl ?? ((url, init) => fetch(url, init));\n const latitudes = points.map((p) => clampLatitude(p[1]).toFixed(6)).join(\",\");\n const longitudes = points.map((p) => normalizeLongitude(p[0]).toFixed(6)).join(\",\");\n const url = `${ENDPOINT}?latitude=${latitudes}&longitude=${longitudes}`;\n\n // A default fetch never times out, so a hung request would leave the control's\n // busy state stuck with no recovery. Abort after ELEVATION_REQUEST_TIMEOUT_MS\n // and surface it as a normal fetch error the caller already handles.\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), ELEVATION_REQUEST_TIMEOUT_MS);\n let response: Response;\n try {\n response = await doFetch(url, { signal: controller.signal });\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw new ElevationFetchError(\"Elevation request timed out.\");\n }\n const detail = error instanceof Error ? error.message : \"unknown error\";\n throw new ElevationFetchError(`Could not reach the elevation service: ${detail}`);\n } finally {\n clearTimeout(timeoutId);\n }\n\n if (!response.ok) {\n throw new ElevationFetchError(`Elevation request failed (HTTP ${response.status}).`);\n }\n\n let data: ElevationResponse;\n try {\n data = (await response.json()) as ElevationResponse;\n } catch {\n throw new ElevationFetchError(\"Could not parse the elevation response.\");\n }\n\n if (!data || !Array.isArray(data.elevation)) {\n throw new ElevationFetchError(\"Malformed elevation response.\");\n }\n if (data.elevation.length !== points.length) {\n throw new ElevationFetchError(\n `Expected ${points.length} elevations but received ${data.elevation.length}.`,\n );\n }\n\n return data.elevation;\n}\n\n/** The slice of the MapLibre map the terrain sampler needs (stubbed in tests). */\nexport interface TerrainMapLike {\n getTerrain?: () => { exaggeration?: number } | null | undefined;\n queryTerrainElevation?: (lngLat: LngLat) => number | null;\n}\n\n/**\n * Sample elevations from the map's enabled 3D terrain, in true meters\n * (MapLibre's `queryTerrainElevation` bakes the exaggeration in, so it is\n * divided back out). Returns null when terrain is not enabled.\n */\nexport function sampleMapTerrain(\n map: TerrainMapLike | null | undefined,\n points: LngLat[],\n): (number | null)[] | null {\n if (!map?.getTerrain || !map.queryTerrainElevation) return null;\n const terrain = map.getTerrain();\n if (!terrain) return null;\n const exaggeration =\n typeof terrain.exaggeration === \"number\" && terrain.exaggeration > 0 ? terrain.exaggeration : 1;\n return points.map((point) => {\n const elevation = map.queryTerrainElevation!(point);\n return typeof elevation === \"number\" && Number.isFinite(elevation)\n ? elevation / exaggeration\n : null;\n });\n}\n\n/**\n * Sample a single point's elevation from enabled 3D terrain, in true meters.\n * Returns null when terrain is off or the DEM tile under the point has not\n * loaded — the caller decides whether that is worth a network round-trip.\n */\nexport function sampleMapTerrainPoint(\n map: TerrainMapLike | null | undefined,\n point: LngLat,\n): number | null {\n return sampleMapTerrain(map, [point])?.[0] ?? null;\n}\n\n/**\n * Sample elevations from the Open-Meteo API, chunked to its 100-point request\n * limit. A failed chunk yields nulls for its points rather than throwing, so a\n * flaky network degrades the readout instead of breaking the caller.\n */\nexport async function sampleRemoteElevations(\n points: LngLat[],\n fetchImpl?: FetchLike,\n): Promise<(number | null)[]> {\n const chunks: LngLat[][] = [];\n for (let i = 0; i < points.length; i += MAX_POINTS_PER_REQUEST) {\n chunks.push(points.slice(i, i + MAX_POINTS_PER_REQUEST));\n }\n // The chunks are independent, so fire them concurrently; Promise.all\n // preserves their order for reassembly.\n const chunkResults = await Promise.all(\n chunks.map(async (chunk) => {\n try {\n const elevations = await fetchElevations(chunk, fetchImpl);\n return elevations.map((elevation) => (Number.isFinite(elevation) ? elevation : null));\n } catch {\n return chunk.map(() => null);\n }\n }),\n );\n return chunkResults.flat();\n}\n\n// --- Pointer elevation resolver --------------------------------------------\n\n/** Round a coordinate to ~11 m so nearby hovers share one cache entry. */\nfunction cacheKey(point: LngLat): string {\n return `${point[0].toFixed(4)},${point[1].toFixed(4)}`;\n}\n\n/** How long the pointer must sit still before a remote lookup is worth it. */\nexport const POINTER_ELEVATION_DEBOUNCE_MS = 500;\n\n/** Remote results kept per resolver; bounded so a long session cannot grow it without limit. */\nexport const POINTER_ELEVATION_CACHE_LIMIT = 500;\n\nexport interface PointerElevationResolverOptions {\n /** The live map, for the terrain sample. May return null before init. */\n getMap: () => TerrainMapLike | null | undefined;\n /**\n * Whether the readout is switched on (Controls -> Elevation). Read per call\n * rather than captured, and rechecked before publishing a remote result, so\n * turning it off mid-request neither shows a late value nor leaves the last\n * one on screen. Defaults to always-on for callers that do not gate it.\n */\n isEnabled?: () => boolean;\n /**\n * Whether the *remote* Open-Meteo fallback may run. The terrain path sends\n * nothing anywhere, so it is deliberately not gated by this — a user who has\n * declined the network lookup still gets a live readout wherever 3D terrain\n * is on. Defaults to allowed for callers that do not gate it.\n */\n canUseRemote?: () => boolean;\n /** Whether the active body is Earth — Open-Meteo has no data for anything else. */\n isEarth: () => boolean;\n /** Called with the resolved elevation in true metres, or null when unknown. */\n emit: (elevation: number | null) => void;\n fetchImpl?: FetchLike;\n debounceMs?: number;\n}\n\nexport interface PointerElevationResolver {\n /** Report a new pointer position, or null when the pointer leaves the map. */\n update: (point: LngLat | null) => void;\n /**\n * Drop any pending *and* in-flight lookup without tearing the resolver down.\n * Used when the map's context changes under it — loading another project\n * resets the readout, and a response for the previous project must not\n * repaint it.\n */\n invalidate: () => void;\n /** Cancel any pending lookup. */\n dispose: () => void;\n}\n\n/**\n * Resolves the ground elevation under the pointer for the status bar.\n *\n * Two paths, deliberately asymmetric:\n *\n * - **Terrain enabled** — `queryTerrainElevation` is synchronous and free, so\n * it runs on every move and the readout tracks the cursor live.\n * - **Terrain disabled** — falls back to Open-Meteo, but only after the\n * pointer has been still for {@link POINTER_ELEVATION_DEBOUNCE_MS}, and only\n * on Earth. Results are cached per ~11 m cell, so sweeping back over ground\n * already looked up costs nothing. Without both guards a hover readout would\n * fire a request per mousemove at a free, keyless public service.\n *\n * While a remote lookup is outstanding the readout reads null rather than the\n * previous point's value: showing a stale height next to fresh coordinates\n * would be worse than showing nothing.\n */\nexport function createPointerElevationResolver(\n options: PointerElevationResolverOptions,\n): PointerElevationResolver {\n const { getMap, isEarth, emit, fetchImpl } = options;\n const isEnabled = options.isEnabled ?? (() => true);\n const canUseRemote = options.canUseRemote ?? (() => true);\n const debounceMs = options.debounceMs ?? POINTER_ELEVATION_DEBOUNCE_MS;\n const cache = new Map<string, number | null>();\n let timer: ReturnType<typeof setTimeout> | null = null;\n // Bumped on every update so a slow response for an abandoned point is dropped\n // rather than overwriting the readout for wherever the pointer is now.\n let generation = 0;\n\n const cancelPending = () => {\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n };\n\n const update = (point: LngLat | null) => {\n generation += 1;\n cancelPending();\n\n if (!point || !isEnabled()) {\n emit(null);\n return;\n }\n\n const fromTerrain = sampleMapTerrainPoint(getMap(), point);\n if (fromTerrain !== null) {\n emit(fromTerrain);\n return;\n }\n\n // Open-Meteo has no data off Earth, and the remote path needs consent.\n if (!isEarth() || !canUseRemote()) {\n emit(null);\n return;\n }\n\n const key = cacheKey(point);\n if (cache.has(key)) {\n const cached = cache.get(key) ?? null;\n // Re-insert to move this entry to the end: Map iterates in insertion\n // order, so the first key is always the least recently used.\n cache.delete(key);\n cache.set(key, cached);\n emit(cached);\n return;\n }\n\n emit(null);\n const requested = generation;\n timer = setTimeout(() => {\n timer = null;\n void (async () => {\n const [elevation] = await sampleRemoteElevations([point], fetchImpl);\n const value = elevation ?? null;\n // `sampleRemoteElevations` turns a fetch failure into a null, which is\n // indistinguishable here from \"no data for this cell\". Caching that\n // would blackhole the readout for this ~11 m cell for the rest of the\n // session, so only successes are remembered and a transient network\n // blip gets another chance on the next hover.\n if (value !== null) {\n // Evict just the oldest entry. Clearing the whole cache at the cap\n // would make every 500th distinct cell trigger a burst of re-fetches\n // for cells that may still be under active hovering.\n if (cache.size >= POINTER_ELEVATION_CACHE_LIMIT) {\n const oldest = cache.keys().next();\n if (!oldest.done) cache.delete(oldest.value);\n }\n cache.set(key, value);\n }\n // The pointer moved, left, or the resolver was disposed while the\n // request was in flight. Also recheck the body: a switch to a planetary\n // basemap mid-request must not publish an Earth elevation over it.\n if (requested !== generation || !isEarth() || !isEnabled() || !canUseRemote()) return;\n emit(value);\n })();\n }, debounceMs);\n };\n\n // Bumping the generation is what actually invalidates an *in-flight*\n // request; cancelPending alone only clears a timer that has not fired yet, so\n // a fetch already in progress would still emit into a torn-down map.\n const invalidate = (): void => {\n generation += 1;\n cancelPending();\n };\n\n return { update, invalidate, dispose: invalidate };\n}\n","/**\n * Camera altitude (\"Eye alt\") for the status bar — issue #1816.\n *\n * MapLibre's `transform.getCameraAltitude()` returns the camera height above\n * sea level in metres, which is exactly what Google Earth Pro's `Eye alt`\n * reports. But it derives that height from the Web-Mercator scale, which is\n * built on Earth's radius — so on a Moon / Mars / Mercury basemap it is wrong by\n * the ratio of that body's circumference to Earth's, the same way MapLibre's\n * built-in ScaleControl is (see `PlanetaryScaleControl`, which fixes the\n * horizontal case).\n *\n * {@link scaleAltitudeToActiveBody} applies the matching correction so the\n * readout is right on every body, using the same active-ellipsoid singleton the\n * scale bar and the measurement tools read.\n */\n\nimport { getActiveBodyRadiusRatio } from \"./ellipsoids\";\nimport { scaleDenomination } from \"./scale-units\";\nimport type { MapScaleUnit } from \"./types\";\n\n/**\n * Convert a MapLibre camera altitude (computed against Earth's radius) into the\n * active body's true altitude. A no-op on Earth.\n *\n * @param earthAltitudeMeters Altitude in metres as MapLibre reports it\n * @returns Altitude in metres above the active body's datum, or null when the\n * input is not a usable number (no map, degenerate transform, globe edge case)\n */\nexport function scaleAltitudeToActiveBody(earthAltitudeMeters: number | null): number | null {\n if (earthAltitudeMeters === null || !Number.isFinite(earthAltitudeMeters)) return null;\n return earthAltitudeMeters * getActiveBodyRadiusRatio();\n}\n\n/**\n * Format a camera altitude for the status bar.\n *\n * Altitude spans metres (standing on a ridge) to tens of thousands of\n * kilometres (whole-globe view), so it borrows the scale bar's denomination\n * switching rather than printing \"12,742,000 m\". Metric crosses to km past\n * 1 km, imperial to miles past a mile, and nautical stays in nautical miles —\n * the same thresholds {@link scaleDenomination} gives the scale bar, so the two\n * always agree about which unit this view is being described in.\n */\nexport function formatCameraAltitude(meters: number, unit: MapScaleUnit): string {\n const { metersPerUnit, label } = scaleDenomination(Math.abs(meters), unit);\n const value = meters / metersPerUnit;\n // Sub-unit and single-digit values need a decimal to say anything; past that\n // the extra digit is noise on a number that moves with every zoom step.\n const digits = Math.abs(value) < 10 ? 1 : 0;\n return `${value.toLocaleString(undefined, {\n minimumFractionDigits: digits,\n maximumFractionDigits: digits,\n })} ${label}`;\n}\n","// A layers file holds a set of layer records that point at their data rather\n// than carry it: tile and service URLs, remote files, and absolute local paths\n// the desktop app can re-read. Project → Export → Export Layers writes one, and\n// the Startup setting adds its layers to every untitled workspace (at launch\n// and on File → New). Layers whose features exist only in memory are left out,\n// so the file stays small enough to keep a copy of in the desktop settings.\n\nimport { hasRestorableLayerSource, layerLocalPath } from \"./layer-library\";\nimport { createDefaultMapView, isSessionOnlyLayer, parseProject } from \"./project\";\nimport {\n LAYER_TYPES,\n PROJECT_VERSION,\n type GeoLibreLayer,\n type GeoLibreProject,\n type LayerGroup,\n} from \"./types\";\n\n/** `type` tag identifying a layers file. */\nexport const LAYERS_FILE_TYPE = \"geolibre-layers\";\n\n/** Format version written by {@link serializeLayersFile}. */\nexport const LAYERS_FILE_VERSION = 1;\n\n/**\n * Size ceiling for a layers file, in UTF-8 bytes. A file of references is a few\n * kilobytes per layer, and the Startup setting keeps a parsed copy in the\n * desktop settings (localStorage), so a file past this is refused rather than\n * crowding out the rest of the settings.\n */\nexport const MAX_LAYERS_FILE_BYTES = 1024 * 1024;\n\n/** The layers and the folders they sit in. */\nexport interface LayersFileContent {\n /** Layer records in store (bottom-to-top) order. */\n layers: GeoLibreLayer[];\n /** The folders the layers belong to, with their ancestors. */\n layerGroups: LayerGroup[];\n}\n\n/** The on-disk layers document. */\nexport interface LayersFile extends LayersFileContent {\n type: typeof LAYERS_FILE_TYPE;\n version: typeof LAYERS_FILE_VERSION;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isHttpUrl(value: unknown): value is string {\n if (typeof value !== \"string\") return false;\n try {\n const url = new URL(value.trim());\n return url.protocol === \"http:\" || url.protocol === \"https:\";\n } catch {\n return false;\n }\n}\n\n/** A `blob:` or `data:` URL is the data itself, not a reference to it. */\nfunction isInlineUrl(value: unknown): boolean {\n return typeof value === \"string\" && /^\\s*(blob|data):/i.test(value);\n}\n\nfunction hasInlineUrl(layer: GeoLibreLayer): boolean {\n const source = layer.source ?? {};\n const tiles = Array.isArray(source.tiles) ? source.tiles : [];\n return (\n isInlineUrl(source.url) ||\n isInlineUrl(source.data) ||\n tiles.some(isInlineUrl) ||\n isInlineUrl(layer.sourcePath) ||\n isInlineUrl((layer.metadata ?? {}).originalUrl)\n );\n}\n\n/**\n * Whether a layer (in its saved form) points at its data rather than carrying\n * it: it has a re-fetchable source, an http(s) source path, or an absolute\n * local path the desktop app re-reads, and holds no inline features.\n *\n * @param layer - A layer as `projectFromStore` writes it.\n * @returns True when the layer can go in a layers file.\n */\nexport function isReferencedLayer(layer: GeoLibreLayer): boolean {\n if (isSessionOnlyLayer(layer)) return false;\n if (layer.geojson) return false;\n const source = layer.source ?? {};\n const metadata = layer.metadata ?? {};\n if (source.data !== undefined && typeof source.data !== \"string\") return false;\n if (source.czmlData !== undefined) return false;\n if (metadata.embeddedGeoJSON !== undefined) return false;\n if (hasInlineUrl(layer)) return false;\n return (\n hasRestorableLayerSource(layer) ||\n layerLocalPath(layer) !== undefined ||\n isHttpUrl(layer.sourcePath)\n );\n}\n\n/** Folders the given layers sit in, plus every ancestor, in their original order. */\nfunction groupsForLayers(\n layers: readonly GeoLibreLayer[],\n groups: readonly LayerGroup[],\n): LayerGroup[] {\n const byId = new Map(groups.map((group) => [group.id, group]));\n const keep = new Set<string>();\n for (const layer of layers) {\n let id = layer.groupId;\n // The `keep` check also stops a hand-edited parent cycle.\n while (id && !keep.has(id) && byId.has(id)) {\n keep.add(id);\n id = byId.get(id)?.parentId;\n }\n }\n return groups.filter((group) => keep.has(group.id));\n}\n\n/**\n * Pick the layers of a saved project that can go in a layers file.\n *\n * @param project - A project as `projectFromStore` writes it. A host that can\n * re-fetch a layer's features (a GeoJSON layer loaded from a URL) strips its\n * `geojson` first, so the layer counts as a reference here.\n * @returns The file content, and the names of the layers left out because they\n * carry their data inline.\n */\nexport function extractLayersFileContent(\n project: Pick<GeoLibreProject, \"layers\" | \"layerGroups\">,\n): { content: LayersFileContent; skipped: string[] } {\n const layers: GeoLibreLayer[] = [];\n const skipped: string[] = [];\n for (const layer of project.layers) {\n if (isReferencedLayer(layer)) layers.push(layer);\n else if (!isSessionOnlyLayer(layer)) skipped.push(layer.name);\n }\n return {\n content: { layers, layerGroups: groupsForLayers(layers, project.layerGroups ?? []) },\n skipped,\n };\n}\n\n/**\n * Serialize layers into the JSON written by Export Layers.\n *\n * @param content - The layers and their folders.\n * @returns Pretty-printed file content.\n */\nexport function serializeLayersFile(content: LayersFileContent): string {\n const file: LayersFile = {\n type: LAYERS_FILE_TYPE,\n version: LAYERS_FILE_VERSION,\n layers: [...content.layers],\n layerGroups: [...content.layerGroups],\n };\n return JSON.stringify(file, null, 2);\n}\n\n/**\n * Coerce untrusted layers (a hand-edited file, or the copy kept in settings)\n * into clean ones. Each layer goes through the same normalization a project\n * file's layers do; one without an id, a name, a known type, or a reference to\n * its data is dropped, as is a repeated id.\n *\n * @param value - An object with `layers` and optional `layerGroups` arrays.\n * @returns The usable layers and the folders they sit in.\n */\nexport function normalizeLayersFileContent(value: unknown): LayersFileContent {\n const empty: LayersFileContent = { layers: [], layerGroups: [] };\n if (!isPlainObject(value) || !Array.isArray(value.layers)) return empty;\n try {\n return normalizeLayers(value.layers, value.layerGroups);\n } catch {\n // A corrupt copy in settings must not break loading the rest of them.\n return empty;\n }\n}\n\n/**\n * The body of {@link normalizeLayersFileContent}, which catches what it throws.\n *\n * @param rawLayers - The raw `layers` array.\n * @param rawGroups - The raw `layerGroups` value.\n * @returns The usable layers and the folders they sit in, or nothing when the\n * layers are past {@link MAX_LAYERS_FILE_BYTES}.\n */\nfunction normalizeLayers(rawLayers: unknown[], rawGroups: unknown): LayersFileContent {\n const seen = new Set<string>();\n const candidates = rawLayers.filter((layer): layer is Record<string, unknown> => {\n if (!isPlainObject(layer)) return false;\n if (typeof layer.id !== \"string\" || !layer.id.trim() || seen.has(layer.id)) return false;\n if (typeof layer.name !== \"string\") return false;\n if (!(LAYER_TYPES as readonly unknown[]).includes(layer.type)) return false;\n if (!isPlainObject(layer.source)) return false;\n seen.add(layer.id);\n return true;\n });\n // Borrow the project parser rather than duplicate its per-layer rules: the\n // layers below are exactly what a project file with only these layers holds.\n // The round trip through JSON also bounds the size of what is kept.\n const json = JSON.stringify({\n version: PROJECT_VERSION,\n name: \"layers\",\n mapView: createDefaultMapView(),\n layers: candidates.map((layer) => ({\n ...layer,\n metadata: isPlainObject(layer.metadata) ? layer.metadata : {},\n style: isPlainObject(layer.style) ? layer.style : {},\n })),\n layerGroups: Array.isArray(rawGroups) ? rawGroups : [],\n });\n if (new TextEncoder().encode(json).length > MAX_LAYERS_FILE_BYTES) {\n return { layers: [], layerGroups: [] };\n }\n const parsed = parseProject(json);\n const layers = parsed.layers.filter(isReferencedLayer);\n return { layers, layerGroups: groupsForLayers(layers, parsed.layerGroups ?? []) };\n}\n\n/**\n * Parse a file written by {@link serializeLayersFile}.\n *\n * @param json - The file content.\n * @returns The normalized layers and folders.\n * @throws Error when the content is too large, is not a layers file, comes\n * from a newer format version, or holds no usable layers.\n */\nexport function parseLayersFile(json: string): LayersFileContent {\n if (new TextEncoder().encode(json).length > MAX_LAYERS_FILE_BYTES) {\n throw new Error(\"The layers file is too large. Keep it under 1 MB.\");\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n throw new Error(\"Not a valid layers file (invalid JSON).\");\n }\n if (!isPlainObject(parsed) || parsed.type !== LAYERS_FILE_TYPE) {\n throw new Error(\"Not a valid layers file.\");\n }\n // Refuse a newer format rather than misread it with this version's rules.\n if (parsed.version !== LAYERS_FILE_VERSION) {\n throw new Error(\"Unsupported layers file version.\");\n }\n const content = normalizeLayersFileContent(parsed);\n if (content.layers.length === 0) {\n throw new Error(\"The layers file holds no usable layers.\");\n }\n return content;\n}\n\n/**\n * Add a layers file's layers on top of a project's own, keeping the project's\n * layers where an id repeats.\n *\n * @param project - The project to add to (usually a new, empty one).\n * @param content - The layers and folders to add.\n * @returns A new project with the layers added.\n */\nexport function addLayersToProject(\n project: GeoLibreProject,\n content: LayersFileContent,\n): GeoLibreProject {\n const layerIds = new Set(project.layers.map((layer) => layer.id));\n const groups = project.layerGroups ?? [];\n const groupIds = new Set(groups.map((group) => group.id));\n const layers = content.layers.filter((layer) => !layerIds.has(layer.id));\n const layerGroups = content.layerGroups.filter((group) => !groupIds.has(group.id));\n return {\n ...project,\n layers: [...project.layers, ...structuredClone(layers)],\n layerGroups: [...groups, ...structuredClone(layerGroups)],\n };\n}\n","/**\n * Trim a float sample to something readable. A 32-bit raster value decoded to a\n * JS double prints all 17 digits of its binary representation\n * (`48.11851119995117`), which is noise past the sensor's precision — six\n * significant digits is more than any COG carries. Integers are left alone so a\n * classification code is never shown in exponential form.\n *\n * @param value A decoded raster sample.\n * @returns The sample rendered for display.\n */\nexport function formatPixelValue(value: number): string {\n if (!Number.isFinite(value)) return String(value);\n if (Number.isInteger(value)) return String(value);\n return String(Number(value.toPrecision(6)));\n}\n","/**\n * Amazon S3 (and S3-compatible) object URLs: parsing, public HTTPS mapping,\n * SigV4 query-string presigning, and the registry the app uses to plug\n * credentialed signing into every reader.\n *\n * Readers in GeoLibre take a plain HTTPS URL (geotiff.js, cog-tiler-wasm,\n * pmtiles, DuckDB-WASM's HTTP reader, the vector control). A presigned URL is\n * the one form of authenticated S3 access all of them accept without a custom\n * fetch hook, so private objects are read by signing the URL first and handing\n * the reader the result. Layers keep the unsigned source URL (`s3://…` or the\n * plain object URL); the signed one is minted again on every load, so a saved\n * project never carries a signature or a session token.\n */\n\n/** A bucket and object key, with the endpoint the URL named, if any. */\nexport interface S3ObjectLocation {\n bucket: string;\n /** The raw (decoded) object key, without a leading slash. */\n key: string;\n /** The region an AWS HTTPS URL named in its host, when it named one. */\n region?: string;\n}\n\n/** Where and how a bucket is addressed. */\nexport interface S3EndpointConfig {\n /** Signing region, e.g. `us-east-1`. S3-compatible stores often accept `auto`. */\n region: string;\n /**\n * Custom endpoint origin for S3-compatible stores (MinIO, Cloudflare R2,\n * Wasabi, Ceph), e.g. `https://minio.example.com:9000`. Empty for AWS.\n */\n endpoint?: string;\n /** Address the bucket in the path instead of the host name. */\n pathStyle?: boolean;\n}\n\n/** AWS credentials used to sign a request. */\nexport interface S3Credentials {\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken?: string;\n /** Epoch milliseconds at which temporary credentials stop working. */\n expiresAt?: number;\n}\n\n/** A presigned URL and the moment it stops working. */\nexport interface S3SignedUrl {\n href: string;\n /** Epoch milliseconds. */\n expiresAt: number;\n}\n\n/** SigV4 allows a presigned URL to live at most seven days. */\nexport const S3_MAX_PRESIGN_SECONDS = 7 * 24 * 60 * 60;\n\nconst S3_SCHEME = /^s3a?:\\/\\//i;\n// bucket.s3.amazonaws.com, bucket.s3.us-west-2.amazonaws.com,\n// bucket.s3-us-west-2.amazonaws.com, bucket.s3.dualstack.us-west-2.amazonaws.com\nconst VIRTUAL_HOST = /^(.+)\\.s3(?:[.-](?:dualstack\\.)?([a-z0-9-]+))?\\.amazonaws\\.com(?:\\.cn)?$/i;\n// s3.amazonaws.com, s3.us-west-2.amazonaws.com, s3-us-west-2.amazonaws.com\nconst PATH_HOST = /^s3(?:[.-](?:dualstack\\.)?([a-z0-9-]+))?\\.amazonaws\\.com(?:\\.cn)?$/i;\n\nfunction regionFromHost(value: string | undefined): string | undefined {\n // `s3-external-1` and `s3.dualstack` carry no region of their own.\n if (!value || value === \"external-1\" || value === \"dualstack\") return undefined;\n return value.toLowerCase();\n}\n\n/** Static-website endpoints serve HTML, not the REST API a signature targets. */\nfunction isWebsiteRegion(value: string | undefined): boolean {\n return Boolean(value && /^website/i.test(value));\n}\n\nfunction decodeKey(path: string): string {\n try {\n return decodeURIComponent(path);\n } catch {\n return path;\n }\n}\n\n/**\n * Parses an `s3://bucket/key` URI or an AWS S3 HTTPS object URL\n * (virtual-hosted or path-style, any region) into its bucket and key.\n * Query strings and fragments are ignored, so a presigned URL parses to the\n * object it signs.\n *\n * @param url The URL to parse.\n * @returns The bucket and key, or null when the URL is not an S3 object URL.\n */\nexport function parseS3Url(url: string): S3ObjectLocation | null {\n const trimmed = url.trim();\n if (S3_SCHEME.test(trimmed)) {\n const rest = trimmed.replace(S3_SCHEME, \"\").split(/[?#]/)[0];\n const slash = rest.indexOf(\"/\");\n const bucket = slash === -1 ? rest : rest.slice(0, slash);\n const key = slash === -1 ? \"\" : rest.slice(slash + 1);\n return isSafeBucketName(bucket) ? { bucket, key } : null;\n }\n let parsed: URL;\n try {\n parsed = new URL(trimmed);\n } catch {\n return null;\n }\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") return null;\n const host = parsed.hostname.toLowerCase();\n const path = parsed.pathname.replace(/^\\//, \"\");\n const virtual = host.match(VIRTUAL_HOST);\n if (virtual && !PATH_HOST.test(host)) {\n if (isWebsiteRegion(virtual[2])) return null;\n const region = regionFromHost(virtual[2]);\n return { bucket: virtual[1], key: decodeKey(path), ...(region ? { region } : {}) };\n }\n const pathStyle = host.match(PATH_HOST);\n if (pathStyle) {\n if (isWebsiteRegion(pathStyle[1])) return null;\n const slash = path.indexOf(\"/\");\n const bucket = slash === -1 ? path : path.slice(0, slash);\n if (!bucket) return null;\n const region = regionFromHost(pathStyle[1]);\n return {\n bucket: decodeKey(bucket),\n key: slash === -1 ? \"\" : decodeKey(path.slice(slash + 1)),\n ...(region ? { region } : {}),\n };\n }\n return null;\n}\n\n/**\n * Bucket names are letters, digits, dots, dashes, and underscores (the last\n * two outside AWS's own rules, for S3-compatible stores). Anything else, a\n * backslash or `@` say, could move a virtual-hosted URL's authority to\n * another host before a signature is attached, so it is refused outright.\n */\nconst SAFE_BUCKET_NAME = /^[A-Za-z0-9._-]{1,255}$/;\n\nfunction isSafeBucketName(bucket: string): boolean {\n return SAFE_BUCKET_NAME.test(bucket);\n}\n\n/** Whether `url` is an `s3://` URI (as opposed to an HTTPS object URL). */\nexport function isS3Uri(url: string): boolean {\n return S3_SCHEME.test(url.trim());\n}\n\n/** Formats a location as an `s3://bucket/key` URI. */\nexport function formatS3Uri(location: S3ObjectLocation): string {\n return `s3://${location.bucket}/${location.key}`;\n}\n\n/**\n * URI-encodes a key the way SigV4 canonicalizes an S3 path: every byte except\n * the unreserved set and `/` is percent-encoded, once.\n */\nexport function encodeS3Key(key: string): string {\n return key\n .split(\"/\")\n .map((segment) =>\n encodeURIComponent(segment).replace(\n /[!'()*]/g,\n (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`,\n ),\n )\n .join(\"/\");\n}\n\n/** A custom endpoint's path, without a trailing slash (\"\" for none). */\nfunction endpointPathPrefix(base: URL): string {\n return base.pathname.replace(/\\/+$/, \"\");\n}\n\n/**\n * Builds the HTTPS URL of an object.\n *\n * AWS buckets whose name contains a dot are addressed path-style, since the\n * `*.s3.amazonaws.com` wildcard certificate covers one label only.\n *\n * @param location The bucket and key.\n * @param config The endpoint; without one the global AWS endpoint is used.\n * @returns The unsigned object URL.\n */\nexport function s3ObjectHttpsUrl(\n location: S3ObjectLocation,\n config: Partial<S3EndpointConfig> = {},\n): string {\n if (location.bucket && !isSafeBucketName(location.bucket)) {\n throw new Error(`Invalid S3 bucket name: ${location.bucket}`);\n }\n // Every URL parser drops \".\" and \"..\" path segments (encoded or not), so such\n // a key would address another object, or with path style another bucket,\n // and a signer would sign that read with the covered bucket's credentials.\n if (location.key.split(\"/\").some((segment) => segment === \".\" || segment === \"..\")) {\n throw new Error(\n `S3 object key has a \".\" or \"..\" segment a URL cannot address: ${location.key}`,\n );\n }\n const key = encodeS3Key(location.key);\n const endpoint = config.endpoint?.trim().replace(/\\/+$/, \"\");\n // No bucket addresses the service itself (ListBuckets).\n if (!location.bucket) {\n if (endpoint) {\n const base = new URL(endpoint.includes(\"://\") ? endpoint : `https://${endpoint}`);\n return `${base.protocol}//${base.host}${endpointPathPrefix(base)}/`;\n }\n const serviceRegion = config.region || location.region;\n return serviceRegion && serviceRegion !== \"us-east-1\"\n ? `https://s3.${serviceRegion}.amazonaws.com/`\n : \"https://s3.amazonaws.com/\";\n }\n if (endpoint) {\n const base = new URL(endpoint.includes(\"://\") ? endpoint : `https://${endpoint}`);\n // An S3-compatible gateway may sit under a path (`https://host/s3`).\n const prefix = endpointPathPrefix(base);\n return config.pathStyle\n ? `${base.protocol}//${base.host}${prefix}/${encodeS3Key(location.bucket)}/${key}`\n : `${base.protocol}//${location.bucket}.${base.host}${prefix}/${key}`;\n }\n const region = config.region || location.region;\n const host = region && region !== \"us-east-1\" ? `s3.${region}.amazonaws.com` : \"s3.amazonaws.com\";\n if (config.pathStyle || location.bucket.includes(\".\")) {\n return `https://${host}/${encodeS3Key(location.bucket)}/${key}`;\n }\n return `https://${location.bucket}.${host}/${key}`;\n}\n\n// ---------------------------------------------------------------------------\n// SigV4 query-string signing\n// ---------------------------------------------------------------------------\n\nconst encoder = new TextEncoder();\n\nfunction toHex(buffer: ArrayBuffer): string {\n return Array.from(new Uint8Array(buffer), (byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nasync function sha256Hex(value: string): Promise<string> {\n return toHex(await crypto.subtle.digest(\"SHA-256\", encoder.encode(value)));\n}\n\nasync function hmac(key: ArrayBuffer | Uint8Array, value: string): Promise<ArrayBuffer> {\n const cryptoKey = await crypto.subtle.importKey(\n \"raw\",\n key as BufferSource,\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n return crypto.subtle.sign(\"HMAC\", cryptoKey, encoder.encode(value));\n}\n\n/** RFC 3986 encoding, as SigV4 requires for query names and values. */\nfunction encodeRfc3986(value: string): string {\n return encodeURIComponent(value).replace(\n /[!'()*]/g,\n (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`,\n );\n}\n\nfunction amzDate(date: Date): { stamp: string; day: string } {\n const stamp = date\n .toISOString()\n .replace(/[-:]/g, \"\")\n .replace(/\\.\\d{3}/, \"\");\n return { stamp, day: stamp.slice(0, 8) };\n}\n\nexport interface PresignS3Options {\n /** The unsigned HTTPS object URL (see {@link s3ObjectHttpsUrl}). */\n url: string;\n region: string;\n credentials: S3Credentials;\n /** Lifetime in seconds, clamped to 1…{@link S3_MAX_PRESIGN_SECONDS}. */\n expiresIn: number;\n /** HTTP method the URL authorizes. Defaults to GET. */\n method?: string;\n /** Signing time; defaults to now. Exposed for tests. */\n now?: Date;\n}\n\n/**\n * Presigns an S3 request with AWS Signature Version 4 in the query string.\n * Runs on WebCrypto, so it works in the browser, the Tauri webview, and Node.\n *\n * The URL authorizes exactly one method: a GET URL cannot answer a HEAD, so\n * readers that probe with HEAD need a GET range request instead.\n *\n * @param options The object URL, region, credentials, and lifetime.\n * @returns The presigned URL.\n */\nexport async function presignS3Url(options: PresignS3Options): Promise<string> {\n const { credentials, region } = options;\n const method = (options.method ?? \"GET\").toUpperCase();\n const url = new URL(options.url);\n const { stamp, day } = amzDate(options.now ?? new Date());\n const scope = `${day}/${region}/s3/aws4_request`;\n const expiresIn = Math.max(1, Math.min(S3_MAX_PRESIGN_SECONDS, Math.floor(options.expiresIn)));\n\n const params: Array<[string, string]> = [];\n url.searchParams.forEach((value, name) => {\n if (!name.toLowerCase().startsWith(\"x-amz-\")) params.push([name, value]);\n });\n params.push(\n [\"X-Amz-Algorithm\", \"AWS4-HMAC-SHA256\"],\n [\"X-Amz-Credential\", `${credentials.accessKeyId}/${scope}`],\n [\"X-Amz-Date\", stamp],\n [\"X-Amz-Expires\", String(expiresIn)],\n [\"X-Amz-SignedHeaders\", \"host\"],\n );\n if (credentials.sessionToken) params.push([\"X-Amz-Security-Token\", credentials.sessionToken]);\n const canonicalQuery = params\n .map(([name, value]) => [encodeRfc3986(name), encodeRfc3986(value)] as const)\n .sort(([a, av], [b, bv]) => (a < b ? -1 : a > b ? 1 : av < bv ? -1 : av > bv ? 1 : 0))\n .map(([name, value]) => `${name}=${value}`)\n .join(\"&\");\n\n // The URL constructor already percent-encoded the path; S3 signs it as sent.\n const canonicalPath = url.pathname || \"/\";\n const canonicalRequest = [\n method,\n canonicalPath,\n canonicalQuery,\n `host:${url.host}\\n`,\n \"host\",\n \"UNSIGNED-PAYLOAD\",\n ].join(\"\\n\");\n const stringToSign = [\"AWS4-HMAC-SHA256\", stamp, scope, await sha256Hex(canonicalRequest)].join(\n \"\\n\",\n );\n\n const dateKey = await hmac(encoder.encode(`AWS4${credentials.secretAccessKey}`), day);\n const regionKey = await hmac(dateKey, region);\n const serviceKey = await hmac(regionKey, \"s3\");\n const signingKey = await hmac(serviceKey, \"aws4_request\");\n const signature = toHex(await hmac(signingKey, stringToSign));\n\n return `${url.origin}${canonicalPath}?${canonicalQuery}&X-Amz-Signature=${signature}`;\n}\n\n// ---------------------------------------------------------------------------\n// Signer registry\n// ---------------------------------------------------------------------------\n\n/** One signed S3 GET: an object read, or a bucket-level call such as ListObjectsV2. */\nexport interface S3PresignRequest {\n bucket: string;\n /** Object key; empty for a bucket-level request. */\n key: string;\n /** Extra query parameters to sign (e.g. `list-type=2`). */\n query?: Record<string, string>;\n /** Region an AWS URL named, used ahead of the connection's default. */\n region?: string;\n /**\n * Sign with this connection instead of the one matched by bucket name (the\n * S3 browser listing a chosen connection's buckets, where `bucket` is empty).\n */\n connectionId?: string;\n}\n\n/** A configured connection, as the S3 browser lists it. No secrets. */\nexport interface S3ConnectionSummary {\n id: string;\n name: string;\n /** Bucket patterns the connection covers; empty means every bucket. */\n buckets: string[];\n}\n\n/**\n * Presigns S3 requests with the app's configured credentials. `presign`\n * resolves to null when no configured connection covers the bucket, in which\n * case the object is read anonymously.\n */\nexport interface S3UrlSigner {\n /** Whether a configured connection covers `bucket`. Must be synchronous. */\n covers(bucket: string): boolean;\n /** Presigns a GET; rejects when the connection's credentials cannot be resolved. */\n presign(request: S3PresignRequest, signal?: AbortSignal): Promise<S3SignedUrl | null>;\n /** The configured connections, for pickers. */\n connections(): S3ConnectionSummary[];\n /**\n * Fetches a presigned bucket-level URL (listing). The desktop app routes it\n * through its native HTTP client, which needs no bucket CORS rule.\n */\n fetchText?(url: string, signal?: AbortSignal): Promise<{ status: number; body: string }>;\n /**\n * Drops the cached credentials and signed URLs of the connection that\n * serves `target`, so the next `presign` resolves them afresh. Called when S3\n * refuses a request as expired.\n */\n invalidateCredentials?(target: { bucket?: string; connectionId?: string }): void;\n /** The persisted location (`s3://bucket/prefix/`) the S3 browser opens at, or \"\". */\n defaultLocation?(): string;\n /** Persists the S3 browser's default location; \"\" clears it. */\n setDefaultLocation?(location: string): void;\n}\n\nlet activeSigner: S3UrlSigner | null = null;\n/** Presigned URL → the unsigned URL it was minted for. Bounded. */\nconst sourceBySignedHref = new Map<string, string>();\nconst SIGNED_SOURCE_LIMIT = 500;\n\n/** Installs (or with null, removes) the app's S3 signer. */\nexport function registerS3UrlSigner(signer: S3UrlSigner | null): void {\n activeSigner = signer;\n}\n\n/**\n * Whether reading `url` needs the S3 signer: it names an S3 object in a bucket\n * a configured connection covers.\n */\nexport function isCredentialedS3Url(url: string): boolean {\n if (!activeSigner) return false;\n const location = parseS3Url(url);\n return Boolean(location && activeSigner.covers(location.bucket));\n}\n\n/** Records that `signedHref` reads `sourceUrl`, so a layer can keep the latter. */\nfunction rememberSignedSource(signedHref: string, sourceUrl: string): void {\n // Least recently used goes first: deleting before setting moves a URL that\n // is signed (handed out) again to the back of the eviction order.\n sourceBySignedHref.delete(signedHref);\n if (sourceBySignedHref.size >= SIGNED_SOURCE_LIMIT) {\n const oldest = sourceBySignedHref.keys().next().value;\n if (oldest !== undefined) sourceBySignedHref.delete(oldest);\n }\n sourceBySignedHref.set(signedHref, sourceUrl);\n}\n\n/** Removes the SigV4 query parameters (credential, token, signature) from a URL. */\nfunction stripPresignParameters(url: string): string {\n try {\n const parsed = new URL(url);\n for (const name of [...parsed.searchParams.keys()]) {\n if (name.toLowerCase().startsWith(\"x-amz-\")) parsed.searchParams.delete(name);\n }\n return parsed.href;\n } catch {\n return url;\n }\n}\n\n/**\n * The unsigned URL a presigned one was minted for by\n * {@link resolveReadableUrl}, or `url` itself. Layer sync runs every URL a\n * control reports through this, so the store (and a saved project) keeps the\n * `s3://` source rather than an expiring signature.\n */\nexport function unsignedSourceUrl(url: string): string;\nexport function unsignedSourceUrl(url: string | undefined): string | undefined;\nexport function unsignedSourceUrl(url: string | undefined): string | undefined {\n if (url === undefined) return undefined;\n const known = sourceBySignedHref.get(url);\n if (known !== undefined) return known;\n // A presigned URL whose mapping was evicted must still never reach a saved\n // project: fall back to the object it signs, without the signature.\n if (!/[?&]x-amz-signature=/i.test(url)) return url;\n const location = parseS3Url(url);\n // An `s3://` URI cannot carry a key with `?` or `#` (they would be read as a\n // query or fragment), so such keys keep the object URL form.\n return location && !/[?#]/.test(location.key)\n ? formatS3Uri(location)\n : stripPresignParameters(url);\n}\n\n/**\n * Resolves a data URL into one a plain HTTP reader can fetch:\n *\n * - an S3 object in a bucket a configured connection covers → a presigned URL;\n * - any other `s3://` URI → the public HTTPS object URL (anonymous read);\n * - anything else → unchanged.\n *\n * @param url The URL a layer or query names.\n * @param signal Aborts a pending credential lookup.\n * @returns The URL to hand the reader.\n * @throws When the bucket is covered but its credentials cannot be resolved.\n */\nexport async function resolveReadableUrl(url: string, signal?: AbortSignal): Promise<string> {\n const location = parseS3Url(url);\n if (!location) return url;\n if (activeSigner?.covers(location.bucket)) {\n const signed = await activeSigner.presign(\n { bucket: location.bucket, key: location.key, region: location.region },\n signal,\n );\n if (signed) {\n rememberSignedSource(signed.href, url);\n return signed.href;\n }\n }\n return isS3Uri(url) ? s3ObjectHttpsUrl(location) : url;\n}\n\n/** The installed signer, for the S3 browser. */\nexport function getS3UrlSigner(): S3UrlSigner | null {\n return activeSigner;\n}\n\n// ---------------------------------------------------------------------------\n// ListObjectsV2\n// ---------------------------------------------------------------------------\n\n/** One page of a ListObjectsV2 response. */\nexport interface S3ListPage {\n /** Common prefixes (\"folders\") under the requested prefix. */\n prefixes: string[];\n objects: Array<{ key: string; size: number; lastModified?: string }>;\n /** Present when more results follow. */\n nextContinuationToken?: string;\n}\n\nfunction decodeXmlText(value: string): string {\n return value\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/&#(\\d+);/g, (_, code: string) => String.fromCodePoint(Number(code)))\n .replace(/&#x([0-9a-f]+);/gi, (_, code: string) => String.fromCodePoint(parseInt(code, 16)))\n .replace(/&/g, \"&\");\n}\n\nfunction xmlTag(block: string, tag: string): string | undefined {\n const match = block.match(new RegExp(`<${tag}>([\\\\s\\\\S]*?)</${tag}>`));\n return match ? decodeXmlText(match[1]) : undefined;\n}\n\nfunction xmlBlocks(xml: string, tag: string): string[] {\n return Array.from(xml.matchAll(new RegExp(`<${tag}>([\\\\s\\\\S]*?)</${tag}>`, \"g\")), (m) => m[1]);\n}\n\n/**\n * Parses a ListObjectsV2 XML body. S3's listing XML is flat and predictable,\n * so this reads it with patterns rather than an XML DOM, which keeps it usable\n * in workers and Node tests.\n */\nexport function parseS3ListObjects(xml: string): S3ListPage {\n const prefixes = xmlBlocks(xml, \"CommonPrefixes\")\n .map((block) => xmlTag(block, \"Prefix\"))\n .filter((value): value is string => Boolean(value));\n const objects = xmlBlocks(xml, \"Contents\").flatMap((block) => {\n const key = xmlTag(block, \"Key\");\n if (key === undefined) return [];\n const lastModified = xmlTag(block, \"LastModified\");\n return [\n { key, size: Number(xmlTag(block, \"Size\") ?? 0), ...(lastModified ? { lastModified } : {}) },\n ];\n });\n const truncated = xmlTag(xml, \"IsTruncated\") === \"true\";\n const token = xmlTag(xml, \"NextContinuationToken\");\n return { prefixes, objects, ...(truncated && token ? { nextContinuationToken: token } : {}) };\n}\n\n/** The error code and message of an S3 XML error body, when it is one. */\nexport function parseS3Error(\n xml: string,\n): { code: string; message: string; region?: string } | null {\n const block = xmlBlocks(xml, \"Error\")[0];\n if (block === undefined) return null;\n // A wrong-region signature names the region; an unsigned request to the\n // wrong endpoint (301 PermanentRedirect) names the right endpoint instead.\n const endpoint = xmlTag(block, \"Endpoint\");\n const region =\n xmlTag(block, \"Region\") ?? (endpoint ? parseS3Url(`https://${endpoint}/`)?.region : undefined);\n return {\n code: xmlTag(block, \"Code\") ?? \"Error\",\n message: xmlTag(block, \"Message\") ?? \"\",\n ...(region ? { region } : {}),\n };\n}\n\n/** Bucket names from a ListBuckets (`GET /` on the service) XML body. */\nexport function parseS3BucketList(xml: string): string[] {\n return xmlBlocks(xml, \"Bucket\")\n .map((block) => xmlTag(block, \"Name\"))\n .filter((name): name is string => Boolean(name));\n}\n\n// ---------------------------------------------------------------------------\n// CORS diagnosis\n// ---------------------------------------------------------------------------\n\n/**\n * How browsers word a request they blocked or could not complete, plus the\n * readers that already guess at CORS themselves (maplibre-gl-lidar's message\n * tells the user to use a file picker, which is wrong for a bucket).\n */\nconst NETWORK_FAILURE =\n /failed to fetch|networkerror|load failed|network request failed|err_failed|cross-origin|\\bcors\\b/i;\n\nfunction errorText(error: unknown): string {\n const parts: string[] = [];\n let current: unknown = error;\n for (let depth = 0; current && depth < 4; depth += 1) {\n parts.push(current instanceof Error ? `${current.name}: ${current.message}` : String(current));\n current = current instanceof Error ? (current as Error & { cause?: unknown }).cause : null;\n }\n return parts.join(\" | \");\n}\n\n/** A read the bucket's CORS configuration blocked. */\nexport class S3CorsError extends Error {\n readonly bucket: string;\n readonly origin: string;\n\n constructor(message: string, bucket: string, origin: string, cause: unknown) {\n super(message, { cause });\n this.name = \"S3CorsError\";\n this.bucket = bucket;\n this.origin = origin;\n }\n}\n\n/** Translates `key`, falling back to `defaultValue` (an app API's `translate`). */\nexport type S3ErrorTranslate = (\n key: string,\n defaultValue: string,\n params?: Record<string, string | number>,\n) => string;\n\n/**\n * Explains why reading an S3 object failed when the browser only said\n * \"Failed to fetch\". A browser reports a request the bucket's CORS rules\n * blocked exactly like a network outage, so this repeats the request twice:\n * normally, where success means the failure was transient; then in `no-cors`\n * mode, which CORS cannot block. Only when the first fails and the second gets\n * an answer is the bucket's CORS configuration the cause.\n *\n * @param url The `s3://` URI or object URL that was being read.\n * @param error What the reader threw.\n * @param translate Turns the message into the UI language.\n * @returns An {@link S3CorsError} naming the bucket and the origin to allow,\n * or `error` unchanged when it is not a CORS refusal of an S3 read.\n */\nexport async function explainS3ReadError(\n url: string,\n error: unknown,\n translate?: S3ErrorTranslate,\n): Promise<unknown> {\n const location = parseS3Url(url);\n if (!location || error instanceof S3CorsError) return error;\n if (!NETWORK_FAILURE.test(errorText(error))) return error;\n if (typeof fetch !== \"function\" || typeof window === \"undefined\") return error;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 10_000);\n try {\n const href = await resolveReadableUrl(url);\n const probe = { signal: controller.signal, cache: \"no-store\" as const };\n // A normal (CORS) read first: if that works now, the failure was transient\n // and CORS is not the cause. The body is never needed, so each request is\n // cut off once its headers arrive.\n const corsAllowed = await fetch(href, probe).then(\n () => true,\n () => false,\n );\n if (corsAllowed) return error;\n // Blocked under CORS, yet S3 answers a no-cors request, which CORS cannot\n // block: the bucket's CORS configuration is what refuses this origin.\n await fetch(href, { ...probe, mode: \"no-cors\" });\n } catch {\n // Unreachable under no-cors too: a real network failure, not CORS.\n return error;\n } finally {\n controller.abort();\n clearTimeout(timer);\n }\n const origin = window.location.origin;\n const fallback =\n `The bucket \"${location.bucket}\" does not allow reads from ${origin}: its CORS configuration ` +\n \"is missing or does not list this origin. Add a CORS rule that allows GET and HEAD from this \" +\n \"origin and exposes Content-Length, Content-Range, and ETag (see Settings > Cloud Storage).\";\n const message = translate\n ? translate(\"s3Browser.corsError\", fallback, { bucket: location.bucket, origin })\n : fallback;\n return new S3CorsError(message, location.bucket, origin, error);\n}\n","import type { StoryMap } from \"./types\";\n\nconst SAMPLE_ASSET_BASE = \"https://opengeos.org/maplibre-gl-storymaps/assets\";\n\n/**\n * Build a ready-to-play sample story map.\n *\n * Mirrors the chapters of the `opengeos/maplibre-gl-storymaps` demo (five world\n * cities) so users can explore the feature instantly without authoring content.\n * A fresh object is returned on each call so callers can mutate it freely.\n *\n * @returns A populated {@link StoryMap} with five sample chapters.\n */\nexport function createSampleStoryMap(): StoryMap {\n return {\n title: \"A Tour of Five Cities\",\n subtitle: \"A scrollytelling map experience built with GeoLibre\",\n byline: \"By GeoLibre\",\n footer:\n 'Source: Wikipedia. Built with <a href=\"https://github.com/opengeos/GeoLibre\" target=\"_blank\">GeoLibre</a>, inspired by <a href=\"https://github.com/opengeos/maplibre-gl-storymaps\" target=\"_blank\">MapLibre Storytelling</a>.',\n theme: \"dark\",\n showMarkers: true,\n markerColor: \"#3fb1ce\",\n inset: true,\n insetPosition: \"bottom-left\",\n hideChapterNav: false,\n startSlide: \"none\",\n endSlide: \"none\",\n chapters: [\n {\n id: \"sample-san-francisco\",\n title: \"San Francisco, California\",\n image: `${SAMPLE_ASSET_BASE}/san-francisco.jpg`,\n description:\n \"San Francisco, a hilly city on the tip of a peninsula surrounded by the Pacific Ocean and San Francisco Bay, is known for its year-round fog, iconic Golden Gate Bridge, cable cars and colorful Victorian houses. <br><br>The city is also known for its vibrant tech industry, diverse neighborhoods, and rich cultural scene.\",\n alignment: \"left\",\n hidden: false,\n location: {\n center: [-122.4194, 37.7749],\n zoom: 11,\n pitch: 45,\n bearing: 0,\n },\n mapAnimation: \"flyTo\",\n rotateAnimation: false,\n onChapterEnter: [],\n onChapterExit: [],\n },\n {\n id: \"sample-new-york\",\n title: \"New York City, New York\",\n image: `${SAMPLE_ASSET_BASE}/new-york.jpg`,\n description:\n \"New York City comprises 5 boroughs sitting where the Hudson River meets the Atlantic Ocean. At its core is Manhattan, a densely populated borough that's among the world's major commercial, financial and cultural centers. <br><br>Its iconic sites include skyscrapers such as the Empire State Building and sprawling Central Park.\",\n alignment: \"right\",\n hidden: false,\n location: {\n center: [-74.006, 40.7128],\n zoom: 11,\n pitch: 60,\n bearing: -43.2,\n },\n mapAnimation: \"flyTo\",\n rotateAnimation: true,\n onChapterEnter: [],\n onChapterExit: [],\n },\n {\n id: \"sample-tokyo\",\n title: \"Tokyo, Japan\",\n image: `${SAMPLE_ASSET_BASE}/tokyo.jpg`,\n description:\n \"Tokyo, Japan's busy capital, mixes the ultramodern and the traditional, from neon-lit skyscrapers to historic temples. The opulent Meiji Shinto Shrine is known for its towering gate and surrounding woods. <br><br>The Imperial Palace sits amid large public gardens. The city's many museums offer exhibits ranging from classical art to a reconstructed kabuki theater.\",\n alignment: \"left\",\n hidden: false,\n location: {\n center: [139.6917, 35.6895],\n zoom: 10,\n pitch: 30,\n bearing: 20,\n },\n mapAnimation: \"flyTo\",\n rotateAnimation: false,\n onChapterEnter: [],\n onChapterExit: [],\n },\n {\n id: \"sample-sydney\",\n title: \"Sydney, Australia\",\n image: `${SAMPLE_ASSET_BASE}/sydney.jpg`,\n description:\n \"Sydney, capital of New South Wales and one of Australia's largest cities, is best known for its harbourfront Sydney Opera House, with a distinctive sail-like design. <br><br>Massive Darling Harbour and the smaller Circular Quay port are hubs of waterside life, with the arched Harbour Bridge and esteemed Royal Botanic Garden nearby.\",\n alignment: \"right\",\n hidden: false,\n location: {\n center: [151.2093, -33.8688],\n zoom: 11,\n pitch: 45,\n bearing: 0,\n },\n mapAnimation: \"flyTo\",\n rotateAnimation: false,\n onChapterEnter: [],\n onChapterExit: [],\n },\n {\n id: \"sample-cape-town\",\n title: \"Cape Town, South Africa\",\n image: `${SAMPLE_ASSET_BASE}/cape-town.jpg`,\n description:\n \"Cape Town is a port city on South Africa's southwest coast, on a peninsula beneath the imposing Table Mountain. Slowly rotating cable cars climb to the mountain's flat top, from which there are sweeping views of the city, the busy harbor and boats headed for Robben Island, the infamous prison that once held Nelson Mandela. <br><br>You can add as many chapters as you need to tell your story.\",\n alignment: \"left\",\n hidden: false,\n location: {\n center: [18.4241, -33.9249],\n zoom: 10,\n pitch: 50,\n bearing: 30,\n },\n mapAnimation: \"flyTo\",\n rotateAnimation: false,\n onChapterEnter: [],\n onChapterExit: [],\n },\n ],\n };\n}\n","/**\n * Helpers for following a story-map presentation's layer fades outside the\n * MapLibre paint model.\n *\n * Story playback fades layers by writing MapLibre paint properties directly\n * (see `MapController.setStoryLayerOpacity`) and records the applied values in\n * `ui.storymapLayerOpacity`. Anything that renders a layer some other way, such\n * as deck.gl feature diagrams and 3D Z-value geometry, or that lists layers\n * for the reader, such as the on-map Legend, reads that record through these\n * helpers so a faded-out layer disappears everywhere at once.\n */\nimport type { GeoLibreLayer, MapViewState, StoryChapterLocation } from \"./types\";\n\n/** The clamped opacity recorded for a layer, or undefined when untouched. */\nfunction recordedStoryOpacity(\n opacities: Record<string, number> | undefined,\n layerId: string,\n): number | undefined {\n const value = opacities?.[layerId];\n if (typeof value !== \"number\" || !Number.isFinite(value)) return undefined;\n return Math.min(1, Math.max(0, value));\n}\n\n/**\n * The opacity a story presentation has applied to a layer.\n *\n * @param opacities The store's `ui.storymapLayerOpacity` record.\n * @param layerId Store layer id.\n * @returns The recorded opacity in `[0, 1]`, or `1` when the presentation has\n * not touched the layer (or nothing is presenting).\n */\nexport function storyLayerOpacityFactor(\n opacities: Record<string, number> | undefined,\n layerId: string,\n): number {\n return recordedStoryOpacity(opacities, layerId) ?? 1;\n}\n\n/**\n * A layer whose opacity reflects the story presentation's fade.\n *\n * A chapter's opacity *replaces* the layer's own opacity rather than scaling\n * it, matching `MapController.setStoryLayerOpacity`, which writes the chapter\n * value as the absolute paint opacity; otherwise a layer set to 0.7 in the\n * Layers panel and faded to 0.4 by a chapter would draw its diagrams at 0.28\n * while its MapLibre layers draw at 0.4.\n *\n * Returns the same object when the presentation has not touched the layer (or\n * recorded the opacity it already has), so identity-keyed caches downstream\n * keep hitting.\n *\n * @param layer Store layer.\n * @param opacities The store's `ui.storymapLayerOpacity` record.\n */\nexport function applyStoryLayerOpacity(\n layer: GeoLibreLayer,\n opacities: Record<string, number> | undefined,\n): GeoLibreLayer {\n const opacity = recordedStoryOpacity(opacities, layer.id);\n if (opacity === undefined || opacity === layer.opacity) return layer;\n return { ...layer, opacity };\n}\n\n/**\n * Whether a story presentation has faded a layer fully out.\n *\n * @param opacities The store's `ui.storymapLayerOpacity` record.\n * @param layerId Store layer id.\n */\nexport function isStoryHiddenLayer(\n opacities: Record<string, number> | undefined,\n layerId: string,\n): boolean {\n return storyLayerOpacityFactor(opacities, layerId) === 0;\n}\n\n/**\n * The layers a reader can currently see during a story presentation: the\n * input minus those the active chapter has faded fully out. Outside a\n * presentation the input is returned as-is (same array identity).\n *\n * @param layers Store layers in panel order.\n * @param presenting The store's `ui.storymapPresenting` flag.\n * @param opacities The store's `ui.storymapLayerOpacity` record.\n */\nexport function storyVisibleLayers(\n layers: GeoLibreLayer[],\n presenting: boolean,\n opacities: Record<string, number> | undefined,\n): GeoLibreLayer[] {\n if (!presenting) return layers;\n if (!layers.some((layer) => isStoryHiddenLayer(opacities, layer.id))) return layers;\n return layers.filter((layer) => !isStoryHiddenLayer(opacities, layer.id));\n}\n\n/**\n * Resolve a story chapter location to a full camera view.\n *\n * A location's `pitch` and `bearing` are optional in memory (a project file\n * fills them with 0 on load). An absent value keeps the camera's current one,\n * the same as MapLibre's camera methods do for an omitted option, so engines\n * whose camera API needs every field (Cesium, `MapEngine.applyView`) behave\n * like the MapLibre map.\n *\n * @param location The chapter's camera target.\n * @param current The camera's current view, used for an absent pitch/bearing.\n * @returns The location with `pitch` and `bearing` filled in.\n */\nexport function storyLocationView(\n location: StoryChapterLocation,\n current: Pick<MapViewState, \"pitch\" | \"bearing\">,\n): MapViewState {\n return {\n center: location.center,\n zoom: location.zoom,\n pitch: location.pitch ?? current.pitch,\n bearing: location.bearing ?? current.bearing,\n };\n}\n","import { normalizeStoryMap } from \"./project\";\nimport { DEFAULT_STORY_MAP, type StoryMap } from \"./types\";\n\n/**\n * Serialize a story map to pretty-printed JSON for export.\n *\n * @param storymap The story map to serialize.\n * @returns A JSON string (settings and chapters).\n */\nexport function serializeStoryMapJson(storymap: StoryMap): string {\n return JSON.stringify(storymap, null, 2);\n}\n\n/**\n * Parse a story map from JSON authored outside GeoLibre.\n *\n * Accepts either a bare story map object or a full `.geolibre.json` project with\n * a `storymap` field, then validates it through the same normalizer used when\n * loading projects.\n *\n * @param text JSON text to import.\n * @returns A normalized story map.\n * @throws If the JSON is invalid or contains no usable chapters.\n */\nexport function parseStoryMapJson(text: string): StoryMap {\n const data = JSON.parse(text) as unknown;\n const candidate =\n data && typeof data === \"object\" && \"storymap\" in (data as object)\n ? (data as { storymap: unknown }).storymap\n : data;\n const normalized = normalizeStoryMap(candidate);\n if (!normalized) {\n throw new Error(\"The imported story map has no chapters.\");\n }\n return normalized;\n}\n\n// Flat, spreadsheet-friendly chapter columns. Layer opacity effects are stored\n// as JSON in their own columns so the CSV round-trips, but hand-authors can\n// simply leave them blank.\nconst CSV_HEADERS = [\n \"id\",\n \"title\",\n \"description\",\n \"image\",\n \"alignment\",\n \"hidden\",\n \"lng\",\n \"lat\",\n \"zoom\",\n \"pitch\",\n \"bearing\",\n \"mapAnimation\",\n \"rotateAnimation\",\n \"onChapterEnter\",\n \"onChapterExit\",\n \"durationSeconds\",\n] as const;\n\n/**\n * Serialize a story map's chapters to CSV (one row per chapter).\n *\n * Story-level settings are not represented in CSV; they are kept in the project\n * and preserved on CSV import.\n *\n * @param storymap The story map whose chapters to serialize.\n * @returns A CSV string with a header row.\n */\nexport function serializeStoryMapCsv(storymap: StoryMap): string {\n const rows = [CSV_HEADERS.join(\",\")];\n for (const c of storymap.chapters) {\n rows.push(\n [\n c.id,\n c.title,\n c.description,\n c.image ?? \"\",\n c.alignment,\n String(c.hidden),\n String(c.location.center[0]),\n String(c.location.center[1]),\n String(c.location.zoom),\n // An absent pitch/bearing is an empty cell, not the string \"undefined\".\n String(c.location.pitch ?? \"\"),\n String(c.location.bearing ?? \"\"),\n c.mapAnimation,\n String(c.rotateAnimation),\n JSON.stringify(c.onChapterEnter),\n JSON.stringify(c.onChapterExit),\n String(c.durationSeconds ?? \"\"),\n ]\n .map(csvEscape)\n .join(\",\"),\n );\n }\n return rows.join(\"\\n\") + \"\\n\";\n}\n\n/**\n * Parse chapters from a CSV authored outside GeoLibre.\n *\n * Columns are matched by header name (case-insensitive) so column order is\n * flexible; `lng`/`lat` set the chapter center. Story-level settings come from\n * `base` (the current story) since CSV only carries chapters; missing ids are\n * generated.\n *\n * @param text CSV text to import.\n * @param base Existing story map whose settings should be preserved.\n * @returns A normalized story map.\n * @throws If the CSV has no data rows or no valid chapters.\n */\nexport function parseStoryMapCsv(text: string, base?: StoryMap | null): StoryMap {\n const rows = parseCsvRows(text);\n if (rows.length < 2) {\n throw new Error(\"The CSV has no chapter rows.\");\n }\n const header = rows[0].map((h) => h.trim().toLowerCase());\n const col = (name: string) => header.indexOf(name.toLowerCase());\n\n const chapters = rows\n .slice(1)\n .filter((row) => row.some((cell) => cell.trim() !== \"\"))\n .map((row, index) => {\n const get = (name: string) => {\n const j = col(name);\n return j >= 0 ? (row[j] ?? \"\") : \"\";\n };\n const num = (name: string, fallback: number) => {\n const value = Number(get(name));\n return Number.isFinite(value) ? value : fallback;\n };\n const bool = (name: string) => /^(true|1|yes)$/i.test(get(name).trim());\n const jsonArray = (name: string) => {\n try {\n const parsed = JSON.parse(get(name) || \"[]\");\n return Array.isArray(parsed) ? parsed : [];\n } catch {\n return [];\n }\n };\n return {\n id: get(\"id\").trim() || `chapter-${index + 1}`,\n title: get(\"title\"),\n description: get(\"description\"),\n image: get(\"image\").trim() || undefined,\n alignment: get(\"alignment\").trim() || \"left\",\n hidden: bool(\"hidden\"),\n location: {\n center: [num(\"lng\", 0), num(\"lat\", 0)],\n zoom: num(\"zoom\", 2),\n pitch: num(\"pitch\", 0),\n bearing: num(\"bearing\", 0),\n },\n mapAnimation: get(\"mapanimation\").trim() || \"flyTo\",\n rotateAnimation: bool(\"rotateanimation\"),\n // An absent/blank cell means the default, not Number(\"\") === 0.\n durationSeconds: get(\"durationseconds\").trim() ? Number(get(\"durationseconds\")) : undefined,\n onChapterEnter: jsonArray(\"onchapterenter\"),\n onChapterExit: jsonArray(\"onchapterexit\"),\n };\n });\n\n const normalized = normalizeStoryMap({\n ...(base ?? DEFAULT_STORY_MAP),\n chapters,\n });\n if (!normalized) {\n throw new Error(\"The CSV contained no valid chapters.\");\n }\n return normalized;\n}\n\n/** Quote a CSV field when it contains a comma, quote, or newline. */\nfunction csvEscape(value: string): string {\n if (/[\",\\n\\r]/.test(value)) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n }\n return value;\n}\n\n/** Parse CSV text into rows of fields, honoring quotes and escaped quotes. */\nfunction parseCsvRows(text: string): string[][] {\n const rows: string[][] = [];\n let row: string[] = [];\n let field = \"\";\n let inQuotes = false;\n // Normalize line endings so \\r\\n and \\r behave like \\n.\n const input = text.replace(/\\r\\n?/g, \"\\n\");\n\n for (let i = 0; i < input.length; i++) {\n const ch = input[i];\n if (inQuotes) {\n if (ch === '\"') {\n if (input[i + 1] === '\"') {\n field += '\"';\n i++;\n } else {\n inQuotes = false;\n }\n } else {\n field += ch;\n }\n } else if (ch === '\"') {\n inQuotes = true;\n } else if (ch === \",\") {\n row.push(field);\n field = \"\";\n } else if (ch === \"\\n\") {\n row.push(field);\n rows.push(row);\n row = [];\n field = \"\";\n } else {\n field += ch;\n }\n }\n // Flush the final field/row unless the input ended with a trailing newline.\n if (field !== \"\" || row.length > 0) {\n row.push(field);\n rows.push(row);\n }\n return rows;\n}\n","/**\n * Narrow subscriptions to the store's `layers` array.\n *\n * `updateLayer` (and every other layer action) replaces `layers` with a new\n * array on each change, so a component that subscribes with\n * `useAppStore((s) => s.layers)` re-renders on every opacity tweak, rename or\n * style edit of *any* layer. The hooks here select only what a component reads\n * and compare it so unrelated layer changes do not re-render it:\n *\n * - {@link useLayer} — one layer record, by id. Layer actions copy only the\n * layer they change and keep every other record's identity, so this is\n * stable while other layers change.\n * - {@link useLayerIds} — the ordered id list, shallow-compared.\n * - {@link useLayerSummaries} — `{ id, name, type, visible, groupId }` per\n * layer, for components that only list layers.\n * - {@link useLayersWhen} — the full array, but only while `active` (a dialog\n * is open, a search has a query); a constant empty array otherwise.\n *\n * The plain `select*` functions are exported so the selection semantics can be\n * tested without React.\n */\nimport { useShallow } from \"zustand/react/shallow\";\nimport { type AppState, useAppStore } from \"./store\";\nimport type { GeoLibreLayer, LayerType } from \"./types\";\n\n/**\n * The fields a layer list needs: enough to draw a row, a `<select>` option or\n * a visibility checkbox. It deliberately leaves out opacity, style, source,\n * metadata and feature data, which change far more often.\n */\nexport interface LayerSummary {\n /** The layer id. */\n readonly id: string;\n /** The display name. */\n readonly name: string;\n /** The layer kind. */\n readonly type: LayerType;\n /** Whether the layer is shown on the primary map. */\n readonly visible: boolean;\n /** The layer panel group the layer belongs to, if any. */\n readonly groupId: string | undefined;\n}\n\n/**\n * The array {@link useLayersWhen} returns while inactive. Frozen, and shared so\n * the inactive selection never changes identity.\n */\nexport const NO_LAYERS: GeoLibreLayer[] = Object.freeze([]) as unknown as GeoLibreLayer[];\n\n/**\n * Select one layer record by id.\n *\n * @param state - The app state.\n * @param id - The layer id; `null`/`undefined` selects nothing.\n * @returns The layer record, or `undefined` when there is no such layer.\n */\nexport function selectLayerById(\n state: Pick<AppState, \"layers\">,\n id: string | null | undefined,\n): GeoLibreLayer | undefined {\n if (!id) return undefined;\n return state.layers.find((layer) => layer.id === id);\n}\n\n/**\n * Select the layer ids in store order (bottom to top).\n *\n * Returns a new array on every call; pair it with a shallow comparison (as\n * {@link useLayerIds} does) so an unchanged id list keeps its identity.\n *\n * @param state - The app state.\n * @returns The layer ids.\n */\nexport function selectLayerIds(state: Pick<AppState, \"layers\">): string[] {\n return state.layers.map((layer) => layer.id);\n}\n\n// Summaries from the previous call, by layer id. Reusing an entry whose fields\n// are unchanged keeps each summary's identity across unrelated edits (an\n// opacity change replaces the layer object but not its summary), which is what\n// lets a shallow comparison of the summary array succeed. Rebuilt on every call\n// so removed layers drop out. Summaries are immutable values, so sharing them\n// between subscribers is safe.\n//\n// The cache is module-level on purpose: every caller reads the one app store,\n// and an entry is reused only after `sameSummary` re-checks it against the\n// current layer. Another subscriber, or a React render that is discarded,\n// can therefore only cause a cache miss (a new summary object), never a stale\n// summary. Tests that care about identity should not rely on a clean cache.\nlet summaryCache = new Map<string, LayerSummary>();\n\nfunction sameSummary(summary: LayerSummary, layer: GeoLibreLayer): boolean {\n return (\n summary.name === layer.name &&\n summary.type === layer.type &&\n summary.visible === layer.visible &&\n summary.groupId === layer.groupId\n );\n}\n\n/**\n * Select a {@link LayerSummary} per layer, in store order (bottom to top).\n *\n * Each summary keeps its identity while its own fields are unchanged, so the\n * returned array is shallow-equal to the previous one after an edit that\n * touches none of those fields (opacity, style, data, metadata).\n *\n * @param state - The app state.\n * @returns One summary per layer.\n */\nexport function selectLayerSummaries(state: Pick<AppState, \"layers\">): LayerSummary[] {\n const next = new Map<string, LayerSummary>();\n const summaries = state.layers.map((layer) => {\n const previous = summaryCache.get(layer.id);\n const summary =\n previous && sameSummary(previous, layer)\n ? previous\n : Object.freeze({\n id: layer.id,\n name: layer.name,\n type: layer.type,\n visible: layer.visible,\n groupId: layer.groupId,\n });\n next.set(layer.id, summary);\n return summary;\n });\n summaryCache = next;\n return summaries;\n}\n\n/**\n * Build a selector for the full `layers` array that is live only while\n * `active`.\n *\n * @param active - Whether the caller currently needs the layers.\n * @returns A selector returning `state.layers` while active, and the constant\n * {@link NO_LAYERS} otherwise.\n */\nexport function selectLayersWhen(\n active: boolean,\n): (state: Pick<AppState, \"layers\">) => GeoLibreLayer[] {\n return active ? (state) => state.layers : () => NO_LAYERS;\n}\n\n/**\n * Subscribe to one layer record.\n *\n * Re-renders only when that layer changes (or appears/disappears), not when\n * another layer is edited.\n *\n * @param id - The layer id; `null`/`undefined` subscribes to nothing.\n * @returns The layer record, or `undefined` when there is no such layer.\n */\nexport function useLayer(id: string | null | undefined): GeoLibreLayer | undefined {\n return useAppStore((state) => selectLayerById(state, id));\n}\n\n/**\n * Subscribe to the ordered list of layer ids.\n *\n * Re-renders only when a layer is added, removed or reordered.\n *\n * @returns The layer ids in store order (bottom to top).\n */\nexport function useLayerIds(): string[] {\n return useAppStore(useShallow(selectLayerIds));\n}\n\n/**\n * Subscribe to a {@link LayerSummary} per layer.\n *\n * Re-renders only when a layer is added, removed, reordered, renamed, shown or\n * hidden, retyped or regrouped; opacity, style, data and metadata edits do not\n * re-render the caller.\n *\n * @returns One summary per layer, in store order (bottom to top).\n */\nexport function useLayerSummaries(): LayerSummary[] {\n return useAppStore(useShallow(selectLayerSummaries));\n}\n\n/**\n * Subscribe to the full `layers` array only while `active`.\n *\n * For always-mounted surfaces (dialogs, floating panels) that need every layer\n * record while open but keep local state across closes and so cannot simply be\n * unmounted. While inactive the caller gets the constant {@link NO_LAYERS} and\n * layer edits do not re-render it.\n *\n * @param active - Whether the caller currently needs the layers (for example,\n * whether its dialog is open).\n * @returns `layers` while active, otherwise an empty, frozen array.\n */\nexport function useLayersWhen(active: boolean): GeoLibreLayer[] {\n return useAppStore(selectLayersWhen(active));\n}\n","/**\n * Platform detection shared across the workspace. Carries no React/MapLibre\n * dependency so callers stay unit-testable.\n */\n\n/**\n * Whether a \"Macintosh\" user agent is really an iPad.\n *\n * iPadOS 13+ requests desktop sites by default and reports a macOS user agent,\n * so the UA string alone cannot tell an iPad from a Mac. Multi-touch does: a\n * real Mac reports `maxTouchPoints` 0 or 1, an iPad reports more.\n *\n * This lives in `@geolibre/core` because two packages need it and the rule is\n * an Apple behaviour that could change: the desktop app's `isMobile` (which\n * hides sidecar-backed tools on mobile) and `@geolibre/plugins`'\n * `isEarthEngineAvailable` (which hides Earth Engine sign-in on the Apple App\n * Store builds). Keeping one copy means a future correction lands in both.\n *\n * @param userAgent - The user agent string to test.\n * @param maxTouchPoints - `navigator.maxTouchPoints`, or undefined where the\n * runtime does not provide it (Node exposes a `navigator` without it).\n * @returns True when the UA claims macOS but the device reports multi-touch.\n */\nexport function isIpadDesktopUserAgent(\n userAgent: string,\n maxTouchPoints: number | undefined,\n): boolean {\n return /Macintosh/.test(userAgent) && (maxTouchPoints ?? 0) > 1;\n}\n","import { getGoogleMapsApiKey } from \"./runtime-env\";\n\n// Shared request-header resolution for 3D Tiles layers. Google Photorealistic\n// 3D Tiles authenticate with a Maps API key sent in the `X-GOOG-API-KEY` header,\n// but the key is deliberately stripped from the store/project record (so shared\n// projects never carry it in plain text) and re-injected at render time from\n// runtime env. Both the MapLibre/deck.gl render path (packages/plugins) and the\n// Cesium globe path (packages/map) must resolve headers the same way, so the\n// logic lives here in core rather than being duplicated per renderer.\n\n/** Google Photorealistic 3D Tiles root host + path. */\nconst GOOGLE_PHOTOREALISTIC_TILES_HOST = \"tile.googleapis.com\";\nconst GOOGLE_PHOTOREALISTIC_TILES_PATH = \"/v1/3dtiles/root.json\";\n/** Header Google's tile server expects the Maps API key in. */\nexport const GOOGLE_MAPS_API_KEY_HEADER = \"X-GOOG-API-KEY\";\n\n/** Whether `url` points at Google's Photorealistic 3D Tiles root tileset. */\nexport function isGooglePhotorealisticTilesetUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n return (\n parsed.hostname === GOOGLE_PHOTOREALISTIC_TILES_HOST &&\n parsed.pathname === GOOGLE_PHOTOREALISTIC_TILES_PATH\n );\n } catch {\n return false;\n }\n}\n\n/** A masked placeholder (all asterisks) is a UI stand-in, not a real key. */\nfunction isMaskedKey(value: string): boolean {\n return /^\\*+$/.test(value.trim());\n}\n\n/** Drop the `X-GOOG-API-KEY` header (case-insensitive), keeping the rest. */\nexport function stripGoogleMapsApiKeyHeader(\n headers: Record<string, string> | undefined,\n): Record<string, string> | undefined {\n if (!headers) return undefined;\n const entries = Object.entries(headers).filter(\n ([name]) => name.toLowerCase() !== GOOGLE_MAPS_API_KEY_HEADER.toLowerCase(),\n );\n return entries.length > 0 ? Object.fromEntries(entries) : undefined;\n}\n\n/**\n * The real `X-GOOG-API-KEY` value from `headers`, or `undefined` when absent,\n * blank, or a masked placeholder.\n */\nexport function googleMapsApiKeyHeaderValue(\n headers: Record<string, string> | undefined,\n): string | undefined {\n if (!headers) return undefined;\n const entry = Object.entries(headers).find(\n ([name]) => name.toLowerCase() === GOOGLE_MAPS_API_KEY_HEADER.toLowerCase(),\n );\n const value = entry?.[1].trim();\n if (!value || isMaskedKey(value)) return undefined;\n return value;\n}\n\n/** Collapse an empty record to `undefined`, otherwise pass it through. */\nexport function nonEmptyRecord(\n value: Record<string, string> | undefined,\n): Record<string, string> | undefined {\n return value && Object.keys(value).length > 0 ? value : undefined;\n}\n\n/**\n * Resolve the request headers a 3D Tiles layer should load with. For non-Google\n * tilesets the headers pass through unchanged. For Google Photorealistic tiles\n * the `X-GOOG-API-KEY` header is (re)built from, in order: an explicit key in\n * the given headers, the `googleMapsApiKey` argument, then `getGoogleMapsApiKey()`\n * (runtime env). Returns `undefined` when there is nothing to send.\n */\nexport function resolveThreeDTilesRequestHeaders(\n url: string,\n headers: Record<string, string> | undefined,\n googleMapsApiKey?: string,\n): Record<string, string> | undefined {\n if (!isGooglePhotorealisticTilesetUrl(url)) return headers;\n const nonGoogleHeaders = stripGoogleMapsApiKeyHeader(headers);\n const apiKey = googleMapsApiKeyHeaderValue(headers) ?? googleMapsApiKey ?? getGoogleMapsApiKey();\n if (!apiKey) return nonEmptyRecord(nonGoogleHeaders);\n return {\n ...(nonGoogleHeaders ?? {}),\n [GOOGLE_MAPS_API_KEY_HEADER]: apiKey,\n };\n}\n\n/**\n * The request headers to persist on a 3D Tiles layer record. Non-Google\n * tilesets keep their headers; Google Photorealistic tiles keep any non-key\n * custom headers but never persist the `X-GOOG-API-KEY` value, so shared\n * projects do not carry the key in plain text (it is re-injected at render\n * time by `resolveThreeDTilesRequestHeaders`).\n */\nexport function persistedThreeDTilesRequestHeaders(\n url: string,\n headers: Record<string, string> | undefined,\n): Record<string, string> | undefined {\n if (!isGooglePhotorealisticTilesetUrl(url)) return headers;\n return nonEmptyRecord(stripGoogleMapsApiKeyHeader(headers));\n}\n","/**\n * Credentials a project refers to by name rather than carrying inline.\n *\n * On the desktop app the geocoding API keys and secret environment variables\n * of a project live in the OS keychain, keyed device-wide by provider id /\n * variable name. The project file keeps the names with empty values. Request\n * headers never go there: they reference variables as `${NAME}` instead (see\n * `resolveProjectHeaderReferences`). This module is the pure half of that\n * scheme: the account names, the overlay that fills empty values from the\n * stored ones, and the split that moves values out of a project before it is\n * written. The app installs the lookup; without one (web, Jupyter) every\n * function here is the identity, so values stay in the project file.\n *\n * A non-empty value in the project always wins over the stored one: a file\n * that still carries plaintext (older files, the web build) is used as-is for\n * the session.\n */\n\nimport { GEOCODING_PROVIDERS } from \"./geocoding\";\nimport { resolveHeaderReferences } from \"./header-references\";\nimport { useAppStore } from \"./store\";\nimport type {\n GeocodingPreferences,\n GeoLibreProject,\n ProjectPreferences,\n RuntimeEnvironmentVariable,\n} from \"./types\";\n\n// Account names are stable keychain data: renaming one orphans every value a\n// user already stored under it.\nexport function geocodingApiKeyAccount(providerId: string): string {\n return `project.geocoding.apiKey.${providerId}`;\n}\n\nexport function environmentVariableAccount(key: string): string {\n return `project.env.${key}`;\n}\n\n/** Rows are secret unless explicitly marked `secret: false`. */\nexport function isSecretEnvironmentVariable(variable: RuntimeEnvironmentVariable): boolean {\n return variable.secret !== false;\n}\n\nexport type ProjectCredentialLookup = (account: string) => string | undefined;\n\nlet projectCredentialLookup: ProjectCredentialLookup | null = null;\n\n/** Install (or clear, with `null`) the source of stored project credentials. */\nexport function setProjectCredentialLookup(lookup: ProjectCredentialLookup | null): void {\n projectCredentialLookup = lookup;\n}\n\n/** The stored value for `account`, or `undefined` when none (or no lookup). */\nexport function lookupProjectCredential(account: string): string | undefined {\n const value = projectCredentialLookup?.(account);\n return value ? value : undefined;\n}\n\n/** Fill empty geocoding API keys from the stored ones. */\nexport function overlayStoredGeocodingApiKeys(\n geocoding: GeocodingPreferences,\n): GeocodingPreferences {\n let apiKeys: Record<string, string> | null = null;\n for (const { id } of GEOCODING_PROVIDERS) {\n if (geocoding.apiKeys[id]?.trim()) continue;\n const stored = lookupProjectCredential(geocodingApiKeyAccount(id));\n if (!stored) continue;\n apiKeys ??= { ...geocoding.apiKeys };\n apiKeys[id] = stored;\n }\n return apiKeys ? { ...geocoding, apiKeys } : geocoding;\n}\n\n/** Fill empty secret environment variable values from the stored ones. */\nexport function overlayStoredEnvironmentVariables(\n variables: RuntimeEnvironmentVariable[],\n): RuntimeEnvironmentVariable[] {\n let changed = false;\n const overlaid = variables.map((variable) => {\n if (!isSecretEnvironmentVariable(variable) || variable.value !== \"\") return variable;\n const key = variable.key.trim();\n const stored = key ? lookupProjectCredential(environmentVariableAccount(key)) : undefined;\n if (!stored) return variable;\n changed = true;\n return { ...variable, value: stored };\n });\n return changed ? overlaid : variables;\n}\n\n/** Preferences with every empty stored credential filled in. */\nexport function overlayStoredPreferenceCredentials(\n preferences: ProjectPreferences,\n): ProjectPreferences {\n const geocoding = overlayStoredGeocodingApiKeys(preferences.geocoding);\n const environmentVariables = overlayStoredEnvironmentVariables(preferences.environmentVariables);\n if (\n geocoding === preferences.geocoding &&\n environmentVariables === preferences.environmentVariables\n ) {\n return preferences;\n }\n return { ...preferences, geocoding, environmentVariables };\n}\n\n/**\n * The credential changes an edited Settings draft makes, keyed by account\n * (`\"\"` deletes).\n *\n * - `seeded`: the overlaid preferences the dialog opened with.\n * - `next`: the normalized draft being saved.\n *\n * Only values the user changed are returned, so an untouched session override\n * (plaintext from the opened file) stays out of the keychain and opening a\n * file never writes it. Removing, renaming, or un-secreting a variable never\n * deletes its stored value: names are shared by every project on the device.\n * Clearing a field deletes the stored value only when the field showed that\n * stored value; clearing a file override that differed from it just drops the\n * override, so the shared value applies again and other projects keep it.\n */\nexport function changedPreferenceCredentials(\n seeded: ProjectPreferences,\n next: ProjectPreferences,\n): Record<string, string> {\n const changes: Record<string, string> = {};\n const record = (account: string, seededValue: string, nextValue: string) => {\n if (nextValue === seededValue) return;\n if (nextValue === \"\" && seededValue !== lookupProjectCredential(account)) return;\n changes[account] = nextValue;\n };\n\n const providerIds = new Set([\n ...Object.keys(seeded.geocoding.apiKeys),\n ...Object.keys(next.geocoding.apiKeys),\n ]);\n for (const providerId of providerIds) {\n record(\n geocodingApiKeyAccount(providerId),\n seeded.geocoding.apiKeys[providerId]?.trim() ?? \"\",\n next.geocoding.apiKeys[providerId]?.trim() ?? \"\",\n );\n }\n\n const seededSecrets = new Map<string, string>();\n for (const variable of seeded.environmentVariables) {\n if (!isSecretEnvironmentVariable(variable) || seededSecrets.has(variable.key)) continue;\n seededSecrets.set(variable.key, variable.value);\n }\n for (const variable of next.environmentVariables) {\n if (!isSecretEnvironmentVariable(variable)) continue;\n const seededValue = seededSecrets.get(variable.key);\n // A new blank row falls back to any value stored under the name; it\n // never deletes it.\n if (seededValue === undefined && variable.value === \"\") continue;\n record(environmentVariableAccount(variable.key), seededValue ?? \"\", variable.value);\n }\n\n return changes;\n}\n\n/**\n * Move unambiguous geocoding API keys and secret environment variable values\n * out of `project` (a copy; the input is untouched) into `secrets`, keyed by\n * account. Secret environment rows that have no unique name remain in the\n * project for an explicit keep/strip choice. Layers pass through: their\n * request headers reference variables as `${NAME}` rather than holding\n * secrets that need moving.\n */\nexport function splitProjectCredentials(project: GeoLibreProject): {\n project: GeoLibreProject;\n secrets: Record<string, string>;\n} {\n const secrets: Record<string, string> = {};\n\n for (const [providerId, apiKey] of Object.entries(project.preferences.geocoding.apiKeys)) {\n const trimmed = apiKey.trim();\n if (trimmed) secrets[geocodingApiKeyAccount(providerId)] = trimmed;\n }\n // A hand-edited file can contain duplicate names even though Settings\n // rejects them. One keychain account cannot preserve two different values.\n const nameCounts = new Map<string, number>();\n for (const variable of project.preferences.environmentVariables) {\n const key = variable.key.trim();\n if (key) nameCounts.set(key, (nameCounts.get(key) ?? 0) + 1);\n }\n const environmentVariables = project.preferences.environmentVariables.map((variable) => {\n if (!isSecretEnvironmentVariable(variable) || variable.value === \"\") return variable;\n const key = variable.key.trim();\n // Keep ambiguous or nameless rows intact for the local keep/strip prompt.\n if (!key || (nameCounts.get(key) ?? 0) > 1) return variable;\n secrets[environmentVariableAccount(key)] = variable.value;\n return { ...variable, value: \"\" };\n });\n\n return {\n project: {\n ...project,\n preferences: {\n ...project.preferences,\n geocoding: { ...project.preferences.geocoding, apiKeys: {} },\n environmentVariables,\n },\n },\n secrets,\n };\n}\n\n/**\n * `headers` with every `${NAME}` resolved from the project's enabled\n * Environment Variables (stored secret values filled in). Only those rows\n * count, not the OS environment or build-time values that share the runtime\n * env. A header referencing an unset variable is dropped.\n */\nexport function resolveProjectHeaderReferences(\n headers: Record<string, string> | undefined,\n): Record<string, string> | undefined {\n if (!headers) return headers;\n const values: Record<string, string> = {};\n const variables = overlayStoredEnvironmentVariables(\n useAppStore.getState().preferences.environmentVariables,\n );\n for (const variable of variables) {\n const key = variable.key.trim();\n if (variable.enabled && key) values[key] = variable.value;\n }\n return resolveHeaderReferences(headers, values);\n}\n","import type { FeatureCollection } from \"geojson\";\nimport type { GeoLibreProject, FieldVisibility } from \"./types\";\n\n/**\n * Returns a new FeatureCollection with properties marked as \"excluded\" removed.\n */\nexport function excludeHiddenFieldsFromGeojson(\n geojson: FeatureCollection,\n fieldVisibility?: Record<string, FieldVisibility>,\n): FeatureCollection {\n const excludedKeys = new Set(\n Object.entries(fieldVisibility || {})\n .filter(([_, visibility]) => visibility === \"excluded\")\n .map(([key]) => key),\n );\n\n if (excludedKeys.size === 0) {\n return geojson;\n }\n\n // Deep clone to avoid mutating the live store state\n const stripped: FeatureCollection = {\n ...geojson,\n features: geojson.features.map((feature) => {\n const properties = { ...feature.properties };\n for (const key of excludedKeys) {\n delete properties[key];\n }\n return { ...feature, properties };\n }),\n };\n\n return stripped;\n}\n\n/**\n * Returns a new GeoLibreProject where all layers have their excluded fields\n * physically removed from their inline GeoJSON.\n */\nexport function excludeHiddenFieldsFromProject(project: GeoLibreProject): GeoLibreProject {\n let changed = false;\n const layers = project.layers.map((layer) => {\n if (!layer.fieldVisibility) return layer;\n\n let updatedLayer = layer;\n\n if (layer.geojson) {\n const strippedGeojson = excludeHiddenFieldsFromGeojson(layer.geojson, layer.fieldVisibility);\n if (strippedGeojson !== layer.geojson) {\n changed = true;\n const { geoarrow: _staleGeoarrow, ...rest } = updatedLayer;\n updatedLayer = { ...rest, geojson: strippedGeojson };\n }\n }\n\n if (layer.metadata?.embeddedGeoJSON) {\n const strippedEmbedded = excludeHiddenFieldsFromGeojson(\n layer.metadata.embeddedGeoJSON as FeatureCollection,\n layer.fieldVisibility,\n );\n if (strippedEmbedded !== layer.metadata.embeddedGeoJSON) {\n changed = true;\n updatedLayer = {\n ...updatedLayer,\n metadata: {\n ...updatedLayer.metadata,\n embeddedGeoJSON: strippedEmbedded,\n },\n };\n }\n }\n\n return updatedLayer;\n });\n\n return changed ? { ...project, layers } : project;\n}\n","import type { Feature, FeatureCollection } from \"geojson\";\nimport type { EditorTrackingConfig } from \"./types\";\n\n/** Default field names for maintained editor tracking columns. */\nexport const DEFAULT_EDITOR_TRACKING_CONFIG: Required<EditorTrackingConfig> = {\n enabled: false,\n createdByField: \"created_by\",\n createdAtField: \"created_at\",\n editedByField: \"edited_by\",\n editedAtField: \"edited_at\",\n};\n\n/**\n * Identity written to the author fields when nothing better is known.\n *\n * Deliberately NOT translated: it is written into the user's data, where a\n * value that changes with the UI language would make the same editor appear as\n * several different people across a project's history.\n */\nexport const DEFAULT_EDITOR_IDENTITY = \"local-user\";\n\n/**\n * `localStorage` key holding the display name this browser edits under.\n *\n * Shared with the Comments panel, which asks for the same name and stores it\n * here, so a user who has introduced themselves once is recognized by both\n * features instead of being prompted twice.\n */\nexport const EDITOR_IDENTITY_STORAGE_KEY = \"geolibre_author_name\";\n\n/**\n * Choose the identity to stamp, most authoritative source first.\n *\n * A live collaboration session names its participants, so that name wins: it is\n * the one other people in the session see attached to the same edits. Otherwise\n * fall back to the locally configured display name, then to\n * {@link DEFAULT_EDITOR_IDENTITY}.\n *\n * @param collabName Display name from an active collaboration session.\n * @param storedName Locally configured display name.\n * @returns A non-empty identity string.\n */\nexport function pickEditorIdentity(collabName?: string | null, storedName?: string | null): string {\n return collabName?.trim() || storedName?.trim() || DEFAULT_EDITOR_IDENTITY;\n}\n\n/**\n * The four maintained column names, or `null` when tracking is off or the\n * configuration is unusable. Ordered created-then-edited so callers that render\n * the columns get a stable, meaningful order.\n */\nexport function editorTrackingFieldNames(config?: EditorTrackingConfig): string[] | null {\n const resolved = resolveEditorTrackingConfigForQuery(config);\n if (!resolved?.enabled) return null;\n return [\n resolved.createdByField,\n resolved.createdAtField,\n resolved.editedByField,\n resolved.editedAtField,\n ];\n}\n\n/**\n * Options for editor tracking stamping functions.\n */\nexport interface EditorTrackingStampOptions {\n /** Optional custom editor tracking field configuration. */\n config?: EditorTrackingConfig;\n /**\n * Identity string of the author/editor (e.g. username, email, or client ID).\n * Defaults to {@link DEFAULT_EDITOR_IDENTITY}.\n */\n userIdentity?: string;\n /** ISO timestamp override for deterministic testing or batch operations. Defaults to `new Date().toISOString()`. */\n timestamp?: string;\n}\n\n/**\n * Fully resolve an {@link EditorTrackingConfig} with fallback default field names.\n */\nexport function resolveEditorTrackingConfig(\n config?: EditorTrackingConfig,\n): Required<EditorTrackingConfig> {\n // Trimmed on the way in, because these become literal GeoJSON property keys:\n // a name typed as `\" created_by \"` in the settings form would otherwise pass\n // validation and then write a column whose name has invisible padding,\n // matching neither what the user typed nor the default it looks like.\n const resolved = {\n enabled: config?.enabled ?? DEFAULT_EDITOR_TRACKING_CONFIG.enabled,\n createdByField: (\n config?.createdByField ?? DEFAULT_EDITOR_TRACKING_CONFIG.createdByField\n ).trim(),\n createdAtField: (\n config?.createdAtField ?? DEFAULT_EDITOR_TRACKING_CONFIG.createdAtField\n ).trim(),\n editedByField: (config?.editedByField ?? DEFAULT_EDITOR_TRACKING_CONFIG.editedByField).trim(),\n editedAtField: (config?.editedAtField ?? DEFAULT_EDITOR_TRACKING_CONFIG.editedAtField).trim(),\n };\n\n // Tracking that is off writes nothing, so the names cannot matter — and a\n // half-filled or hand-edited config left behind on a disabled layer must not\n // make every save on that layer throw. Validate only what will be used.\n if (!resolved.enabled) return resolved;\n\n const fields = [\n resolved.createdByField,\n resolved.createdAtField,\n resolved.editedByField,\n resolved.editedAtField,\n ];\n if (fields.some((field) => field === \"\") || new Set(fields).size !== fields.length) {\n throw new Error(\"Editor tracking field names must be non-empty and unique\");\n }\n\n return resolved;\n}\n\n/**\n * Resolve a config for the query helpers, yielding `null` rather than throwing when\n * the configured field names are blank or collide.\n *\n * The query helpers run once per field on every Attribute Table / Field Calculator\n * render, and the field names are user-editable strings, so a half-filled settings\n * form or a hand-edited `.geolibre.json` must degrade to \"tracking off\" instead of\n * crashing the panel. The stamping helpers keep the throw: a write that silently\n * skipped tracking would leave a layer looking tracked while recording nothing.\n */\nfunction resolveEditorTrackingConfigForQuery(\n config?: EditorTrackingConfig,\n): Required<EditorTrackingConfig> | null {\n try {\n return resolveEditorTrackingConfig(config);\n } catch {\n return null;\n }\n}\n\n/**\n * Check whether a field name corresponds to one of the maintained editor tracking columns.\n *\n * Returns `false` for an invalid config, so callers can use this as a plain predicate.\n */\nexport function isMaintainedEditorTrackingField(\n fieldName: string,\n config?: EditorTrackingConfig,\n): boolean {\n const resolved = resolveEditorTrackingConfigForQuery(config);\n if (!resolved?.enabled) {\n return false;\n }\n return (\n fieldName === resolved.createdByField ||\n fieldName === resolved.createdAtField ||\n fieldName === resolved.editedByField ||\n fieldName === resolved.editedAtField\n );\n}\n\n/**\n * Ensure all configured editor tracking field names are included in a field list.\n *\n * Returns the list unchanged for an invalid config, matching\n * {@link isMaintainedEditorTrackingField}.\n */\nexport function ensureEditorTrackingFields(\n fields: string[],\n config?: EditorTrackingConfig,\n): string[] {\n const trackingFields = editorTrackingFieldNames(config);\n if (!trackingFields) {\n return fields;\n }\n const result = [...fields];\n for (const tf of trackingFields) {\n if (!result.includes(tf)) {\n result.push(tf);\n }\n }\n return result;\n}\n\n/**\n * Stamp editor tracking metadata onto a feature's properties object.\n *\n * On `\"create\"`: sets all four columns to this editor and this timestamp.\n * `\"create\"` states that the feature is being created here and now, so it is\n * authoritative — a value already sitting in one of these columns describes\n * some other feature, not this one. That case is reachable: the geometry\n * editor's copy action clones a tracked feature's properties, and a Field\n * Collection form can define a capture field whose key happens to be a tracking\n * column. Both would otherwise credit a brand-new feature to whoever created\n * the thing it was copied from.\n *\n * On `\"update\"`: sets `edited_by`/`edited_at` and preserves `created_by` /\n * `created_at`, which record something this edit did not change.\n */\nexport function stampFeaturePropertiesEditorTracking(\n properties: Record<string, unknown> | null | undefined,\n action: \"create\" | \"update\",\n options?: EditorTrackingStampOptions,\n): Record<string, unknown> {\n const resolved = resolveEditorTrackingConfig(options?.config);\n if (!resolved.enabled) {\n return properties ? { ...properties } : {};\n }\n\n const result: Record<string, unknown> = properties ? { ...properties } : {};\n const now = options?.timestamp ?? new Date().toISOString();\n const actor = options?.userIdentity || DEFAULT_EDITOR_IDENTITY;\n\n if (action === \"create\") {\n result[resolved.createdByField] = actor;\n result[resolved.createdAtField] = now;\n }\n\n result[resolved.editedByField] = actor;\n result[resolved.editedAtField] = now;\n\n return result;\n}\n\n/**\n * Stamp editor tracking metadata onto a GeoJSON {@link Feature}.\n */\nexport function stampFeatureEditorTracking<T extends Feature>(\n feature: T,\n action: \"create\" | \"update\",\n options?: EditorTrackingStampOptions,\n): T {\n const resolved = resolveEditorTrackingConfig(options?.config);\n if (!resolved.enabled) {\n return feature;\n }\n\n return {\n ...feature,\n properties: stampFeaturePropertiesEditorTracking(feature.properties, action, options),\n };\n}\n\n/**\n * Stamp editor tracking metadata onto all features in a {@link FeatureCollection}.\n */\nexport function stampFeatureCollectionEditorTracking(\n collection: FeatureCollection,\n action: \"create\" | \"update\",\n options?: EditorTrackingStampOptions,\n): FeatureCollection {\n const resolved = resolveEditorTrackingConfig(options?.config);\n if (!resolved.enabled) {\n return collection;\n }\n\n const timestamp = options?.timestamp ?? new Date().toISOString();\n const opts = { ...options, timestamp };\n\n return {\n ...collection,\n features: collection.features.map((feat) => stampFeatureEditorTracking(feat, action, opts)),\n };\n}\n","import { EDITOR_IDENTITY_STORAGE_KEY, pickEditorIdentity } from \"./editor-tracking\";\nimport { useAppStore } from \"./store\";\n\n/**\n * Runtime resolution of \"who is editing\", for the editor tracking stamps.\n *\n * Split from the pure `editor-tracking.ts` helpers because it reads two ambient\n * sources — the collaboration session in the store and `localStorage` — which\n * the stamping functions must stay free of so they remain unit-testable and\n * usable from Node (the MCP server authors projects without either).\n */\n\n/**\n * The display name this browser edits under, or `\"\"` when none is set.\n *\n * `localStorage` throws rather than returning null in a partitioned or\n * storage-blocked context (Safari private browsing, an embed in a third-party\n * iframe), so a failure here has to read as \"no name set\" — an editor whose\n * browser refuses storage must still be able to edit.\n */\nexport function readStoredAuthorName(): string {\n try {\n if (typeof localStorage === \"undefined\") return \"\";\n return localStorage.getItem(EDITOR_IDENTITY_STORAGE_KEY)?.trim() ?? \"\";\n } catch {\n return \"\";\n }\n}\n\n/**\n * Persist the display name to edit under. A blank name clears it, so the user\n * can go back to the anonymous default rather than being stuck with a typo.\n */\nexport function setStoredAuthorName(name: string): void {\n try {\n if (typeof localStorage === \"undefined\") return;\n const trimmed = name.trim();\n if (trimmed) {\n localStorage.setItem(EDITOR_IDENTITY_STORAGE_KEY, trimmed);\n } else {\n localStorage.removeItem(EDITOR_IDENTITY_STORAGE_KEY);\n }\n } catch {\n // Storage unavailable: the name applies to this session only.\n }\n}\n\n/**\n * The identity to stamp on edits made right now: the collaboration session's\n * name when one is active, else the locally configured name, else the anonymous\n * default.\n *\n * @returns A non-empty identity string.\n */\nexport function currentEditorIdentity(): string {\n const { isActive, selfName } = useAppStore.getState().collaboration;\n return pickEditorIdentity(isActive ? selfName : undefined, readStoredAuthorName());\n}\n","/**\n * Datum shifts (proj4 `+towgs84` Helmert parameters) for coordinate systems\n * whose datum is not WGS84-compatible, by EPSG code.\n *\n * The offline EPSG tables GeoLibre resolves codes with\n * (`geotiff-geokeys-to-proj4`) leave the shift out for many national grids, and\n * epsg.io's PROJJSON for a projected CRS carries no transformation either, so\n * proj4 would treat those datums as WGS84 and place data tens to hundreds of\n * metres off (about 100 m for the British National Grid). The parameters are\n * EPSG's (the transformation epsg.io's proj4 strings use), within a few\n * metres of PROJ's best transformation for each (15 m for the NAD27 mean);\n * grid-based transformations (OSTN15, NTv2, NADCON) are finer but need grid\n * files proj4 does not ship. Datums the tables already shift (ED50, AGD66,\n * AGD84, Tokyo, NZGD49, TM75 and others) are not listed.\n */\n\ninterface DatumShift {\n /** Datum name, for the record. */\n datum: string;\n /** EPSG datum codes (`GeogGeodeticDatumGeoKey`). */\n datums: number[];\n /**\n * The datum's names in a WKT `.prj`, without ESRI's `D_` prefix: the ESRI\n * name, and the OGC one where it differs. Matched ignoring case.\n */\n names: string[];\n /** EPSG geographic and projected CRS codes on the datum. */\n crs: number[];\n towgs84: string;\n}\n\n/** Inclusive ranges of codes, as a list. */\nconst range = (from: number, to: number) =>\n Array.from({ length: to - from + 1 }, (_, i) => from + i);\n\nconst SHIFTS: DatumShift[] = [\n {\n datum: \"OSGB 1936\",\n datums: [6277],\n names: [\"OSGB_1936\", \"Ordnance_Survey_of_Great_Britain_1936\"],\n crs: [4277, 27700],\n towgs84: \"446.448,-125.157,542.06,0.15,0.247,0.842,-20.489\",\n },\n {\n datum: \"TM65 (Ireland)\",\n datums: [6299],\n names: [\"TM65\"],\n crs: [4299, 29902],\n towgs84: \"482.5,-130.6,564.6,-1.042,-0.214,-0.631,8.15\",\n },\n {\n datum: \"DHDN\",\n datums: [6314],\n names: [\"Deutsches_Hauptdreiecksnetz\"],\n // 5676-5679: the Gauss-Kruger zones 2 to 5 with east-north axes.\n crs: [4314, 3068, ...range(31466, 31469), ...range(5676, 5679)],\n towgs84: \"598.1,73.7,418.2,0.202,0.045,-2.455,6.7\",\n },\n {\n datum: \"Amersfoort\",\n datums: [6289],\n names: [\"Amersfoort\"],\n crs: [4289, 28991, 28992],\n towgs84: \"565.417,50.3319,465.552,-0.398957,0.343988,-1.8774,4.0725\",\n },\n {\n datum: \"CH1903\",\n datums: [6149],\n names: [\"CH1903\"],\n crs: [4149, 21781, 21782],\n towgs84: \"674.4,15.1,405.3,0,0,0,0\",\n },\n {\n datum: \"CH1903+\",\n datums: [6150],\n names: [\"CH1903+\"],\n crs: [4150, 2056],\n towgs84: \"674.374,15.056,405.346,0,0,0,0\",\n },\n {\n datum: \"Belge 1972\",\n datums: [6313],\n names: [\"Belge_1972\", \"Reseau_National_Belge_1972\"],\n crs: [4313, 31300, 31370],\n towgs84: \"-106.8686,52.2978,-103.7239,0.3366,-0.457,1.8422,-1.2747\",\n },\n {\n datum: \"S-JTSK\",\n datums: [6156],\n names: [\"S_JTSK\", \"System_of_the_Unified_Trigonometrical_Cadastral_Network\"],\n crs: [4156, 5514],\n towgs84: \"589,76,480,0,0,0,0\",\n },\n {\n datum: \"Monte Mario\",\n datums: [6265],\n names: [\"Monte_Mario\"],\n // 5659: TM Emilia-Romagna, the region's own grid.\n crs: [4265, 3003, 3004, 5659],\n towgs84: \"-104.1,-49.1,-9.9,0.971,-2.917,0.714,-11.68\",\n },\n {\n datum: \"HD72\",\n datums: [6237],\n names: [\"Hungarian_1972\", \"Hungarian_Datum_1972\"],\n crs: [4237, 23700],\n towgs84: \"52.17,-71.82,-14.9,0,0,0,0\",\n },\n {\n datum: \"GGRS87\",\n datums: [6121],\n names: [\"GGRS_1987\", \"Greek_Geodetic_Reference_System_1987\"],\n crs: [4121, 2100],\n towgs84: \"-199.87,74.79,246.62,0,0,0,0\",\n },\n {\n datum: \"Hong Kong 1980\",\n datums: [6611],\n names: [\"Hong_Kong_1980\"],\n crs: [4611, 2326],\n towgs84: \"-162.619,-276.959,-161.764,0.067753,-2.24365,-1.15883,-1.09425\",\n },\n {\n // A conterminous-US mean, so only the UTM zones over it (10 N to 19 N),\n // not the geographic CRS or the datum, which span all of North America.\n datum: \"NAD27 (conterminous US mean)\",\n datums: [],\n names: [],\n crs: range(26710, 26719),\n towgs84: \"-8,160,176,0,0,0,0\",\n },\n];\n\nconst BY_CRS = new Map<number, string>();\nconst BY_DATUM = new Map<number, string>();\nconst BY_NAME = new Map<string, string>();\nfor (const shift of SHIFTS) {\n for (const code of shift.crs) BY_CRS.set(code, shift.towgs84);\n for (const code of shift.datums) BY_DATUM.set(code, shift.towgs84);\n for (const name of shift.names) BY_NAME.set(name.toLowerCase(), shift.towgs84);\n}\n\n/**\n * The `+towgs84` parameters for an EPSG CRS or datum code, or null when the\n * code is not one whose shift GeoLibre adds.\n *\n * @param crs An EPSG geographic or projected CRS code.\n * @param datum An EPSG datum code, used when the CRS code is unknown.\n */\nexport function datumShiftFor(\n crs: number | null | undefined,\n datum?: number | null,\n): string | null {\n return (\n (crs != null ? BY_CRS.get(crs) : undefined) ??\n (datum != null ? BY_DATUM.get(datum) : undefined) ??\n null\n );\n}\n\n/**\n * Definitions the EPSG tables get wrong, replaced whole, by CRS code. EPSG:31300\n * (Belge Lambert 72) uses Lambert Conic Conformal (2SP Belgium), which rotates the\n * grid by 29.2985\" (EPSG Guidance Note 7-2, §1.3.1.4). proj4 has no such method and\n * the tables give a plain LCC 2SP, about 745 m off. EPSG:31370 is the same grid\n * written as a plain LCC 2SP, so its parameters (from the same tables) stand in.\n */\nconst REPLACED = new Map<number, string>([\n [\n 31300,\n \"+proj=lcc +lat_0=90 +lon_0=4.367486666667 +lat_1=51.166667233333 +lat_2=49.8333339 +x_0=150000.013 +y_0=5400088.438 +ellps=intl +a=6378388 +b=6356911.9461279465 +pm=0 +no_defs\",\n ],\n]);\n\n/** GeoTIFF's \"user-defined\" geokey value. */\nconst USER_DEFINED = 32767;\n\n/** A geokey's numeric value, or null for a missing or user-defined one. */\nfunction keyCode(value: unknown): number | null {\n const code = Number(value);\n return Number.isInteger(code) && code > 0 && code !== USER_DEFINED ? code : null;\n}\n\n/**\n * Add the datum shift to a proj4 definition built for an EPSG CRS (or from a\n * GeoTIFF's geokeys), when the definition has none and the CRS is one whose\n * shift is known; a definition the tables get wrong (see `REPLACED`) is replaced\n * first. A definition that already names a datum transformation (`+towgs84`,\n * `+datum=`, `+nadgrids=`) is returned unchanged.\n *\n * @param definition A proj4 definition string.\n * @param crs The EPSG CRS code it was built for.\n * @param datum The EPSG datum code, for a user-defined CRS.\n */\nexport function withDatumShift(\n definition: string,\n crs: number | null | undefined,\n datum?: number | null,\n): string {\n if (/\\+(towgs84|datum|nadgrids)=/.test(definition)) return definition;\n const base = (crs != null ? REPLACED.get(crs) : undefined) ?? definition;\n const shift = datumShiftFor(crs, datum);\n return shift ? `${base.trim()} +towgs84=${shift}` : base;\n}\n\n/**\n * Add the datum shift to a proj4 definition built from GeoTIFF geokeys, from\n * the projected or geographic CRS code, else the datum code.\n *\n * @param definition The proj4 definition built from `geoKeys`.\n * @param geoKeys The GeoTIFF's geokeys (geotiff.js names).\n */\nexport function withGeoKeysDatumShift(\n definition: string,\n geoKeys: Record<string, unknown> | null | undefined,\n): string {\n if (!geoKeys) return definition;\n const crs =\n keyCode(geoKeys.ProjectedCSTypeGeoKey) ??\n keyCode(geoKeys.ProjectedCRSGeoKey) ??\n keyCode(geoKeys.GeographicTypeGeoKey) ??\n keyCode(geoKeys.GeodeticCRSGeoKey);\n const datum = keyCode(geoKeys.GeogGeodeticDatumGeoKey) ?? keyCode(geoKeys.GeodeticDatumGeoKey);\n return withDatumShift(definition, crs, datum);\n}\n\n/** The index just past the bracketed node that opens at `open` (a `[`), or -1. */\nfunction closingBracket(text: string, open: number): number {\n let depth = 0;\n for (let i = open; i < text.length; i += 1) {\n if (text[i] === \"[\") depth += 1;\n else if (text[i] === \"]\") {\n depth -= 1;\n if (depth === 0) return i + 1;\n }\n }\n return -1;\n}\n\n/**\n * Add the datum shift to a WKT 1 CRS (a shapefile's `.prj`) whose datum is one\n * whose shift is known, as a `TOWGS84` node after the datum's spheroid. An ESRI\n * `.prj` never carries one, and proj4 knows only a few datums by name, so\n * without it most national datums are read as WGS84 and the data lands 70 to\n * 325 m off. WKT that already has a `TOWGS84` is returned unchanged.\n *\n * @param wkt A WKT 1 CRS, as in a `.prj` file.\n */\nexport function withWktDatumShift(wkt: string): string {\n if (/\\bTOWGS84\\s*\\[/i.test(wkt)) return wkt;\n const datum = /\\bDATUM\\s*\\[\\s*\"([^\"]*)\"/i.exec(wkt);\n const shift = datum && BY_NAME.get(datum[1].replace(/^D_/i, \"\").toLowerCase());\n if (!datum || !shift) return wkt;\n const spheroid = /\\bSPHEROID\\s*\\[/i.exec(wkt.slice(datum.index));\n if (!spheroid) return wkt;\n const end = closingBracket(wkt, datum.index + spheroid.index + spheroid[0].length - 1);\n return end < 0 ? wkt : `${wkt.slice(0, end)},TOWGS84[${shift}]${wkt.slice(end)}`;\n}\n\n/** The part of `cog-tiler-wasm` the datum-shift hook needs (0.5 and later). */\ninterface CogTilerCrsHook {\n setSourceCrsResolver?: (\n fn: ((def: string, geoKeys: Record<string, unknown>) => string | null | undefined) | null,\n ) => void;\n}\n\n/**\n * Have `cog-tiler-wasm` add the datum shift to each COG's source CRS, so a\n * raster it tiles lands where the rest of GeoLibre puts it. Idempotent; a\n * tiler without the hook is left as it is. WKT definitions are left alone.\n *\n * @param module The imported `cog-tiler-wasm` module.\n */\nexport function installCogTilerDatumShift(module: CogTilerCrsHook): void {\n module.setSourceCrsResolver?.((def, geoKeys) =>\n def.trimStart().startsWith(\"+\") ? withGeoKeysDatumShift(def, geoKeys) : def,\n );\n}\n","// JPEG-in-TIFF tiles that omit their own tables (TIFF tag 347 holds them), which\n// cog-tiler-wasm's decoder rejects as \"use of unset quantization table\". Every\n// engine that opens a COG through the tiler (the MapLibre raster control, the\n// Cesium globe, the ArcGIS view) wraps its `openCog` with {@link\n// withJpegTablesPatch} so those tiles decode through geotiff.js instead.\n\nconst DEFAULT_COEFFICIENTS = [0.299, 0.587, 0.114] as const;\nconst DEFAULT_REFERENCE_BLACK_WHITE = [0, 255, 128, 255, 128, 255] as const;\n\ntype NumericArray = ArrayLike<number> | undefined;\n\nfunction finiteValues(values: NumericArray, length: number): number[] | null {\n if (!values || values.length < length) return null;\n const result = Array.from({ length }, (_, index) => Number(values[index]));\n return result.every(Number.isFinite) ? result : null;\n}\n\nfunction validCoefficients(values: NumericArray): number[] | null {\n const coefficients = finiteValues(values, 3);\n if (!coefficients) return null;\n const [kr, kg, kb] = coefficients;\n const sum = kr + kg + kb;\n return kr >= 0 && kg > 0 && kb >= 0 && kr <= 1 && kg <= 1 && kb <= 1 && Math.abs(sum - 1) <= 1e-6\n ? coefficients\n : null;\n}\n\nfunction validReference(values: NumericArray): number[] | null {\n const reference = finiteValues(values, 6);\n if (!reference) return null;\n return reference[1] > reference[0] && reference[3] > reference[2] && reference[5] > reference[4]\n ? reference\n : null;\n}\n\n/** Convert separate TIFF Y/Cb/Cr planes to RGB using tags 529 and 532. */\nexport function convertTiffYCbCrToRgb(\n yPlane: ArrayLike<number>,\n cbPlane: ArrayLike<number>,\n crPlane: ArrayLike<number>,\n coefficients?: NumericArray,\n referenceBlackWhite?: NumericArray,\n): [Float64Array, Float64Array, Float64Array] {\n const [kr, kg, kb] = validCoefficients(coefficients) ?? DEFAULT_COEFFICIENTS;\n const reference = validReference(referenceBlackWhite) ?? DEFAULT_REFERENCE_BLACK_WHITE;\n const [blackY, whiteY, blackCb, whiteCb, blackCr, whiteCr] = reference;\n const yRange = whiteY - blackY;\n const cbRange = whiteCb - blackCb;\n const crRange = whiteCr - blackCr;\n const size = Math.min(yPlane.length, cbPlane.length, crPlane.length);\n const red = new Float64Array(size);\n const green = new Float64Array(size);\n const blue = new Float64Array(size);\n\n for (let index = 0; index < size; index += 1) {\n const y = ((Number(yPlane[index]) - blackY) * 255) / yRange;\n const cb = ((Number(cbPlane[index]) - blackCb) * 127) / cbRange;\n const cr = ((Number(crPlane[index]) - blackCr) * 127) / crRange;\n const r = y + (2 - 2 * kr) * cr;\n const b = y + (2 - 2 * kb) * cb;\n const g = (y - kr * r - kb * b) / kg;\n red[index] = Math.max(0, Math.min(255, r));\n green[index] = Math.max(0, Math.min(255, g));\n blue[index] = Math.max(0, Math.min(255, b));\n }\n\n return [red, green, blue];\n}\n\n/**\n * Whether a cog-tiler-wasm level's `compression` is baseline JPEG, the only\n * codec whose tiles may omit their tables (TIFF tag 347) and so need the\n * geotiff.js window read in {@link patchJpegCogSource}. The string is whitebox-wasm's\n * enum name, so match `Jpeg`/`OldJpeg` exactly: a looser `/jpeg/i` would also\n * catch `JpegXl`, which the wasm decoder handles and geotiff.js cannot decode\n * at all (#2339).\n */\nexport function isAbbreviatedJpegCompression(compression: string | undefined): boolean {\n return /^(old)?jpeg$/i.test(compression ?? \"\");\n}\n\ntype GeoTiffImage = {\n fileDirectory?: {\n PhotometricInterpretation?: number;\n getValue?: (tag: number) => unknown;\n hasTag?: (tag: number) => boolean;\n loadValue?: (tag: number) => Promise<unknown>;\n };\n readRasters: (options: {\n window: [number, number, number, number];\n }) => Promise<ArrayLike<ArrayLike<number>>>;\n};\ntype CogSourceInternals = {\n levels?: Array<{ compression?: string }>;\n tiff?: unknown;\n _tiffImage?: (level: number) => Promise<GeoTiffImage>;\n _assembleWindow?: (\n level: number,\n x: number,\n y: number,\n width: number,\n height: number,\n band?: number,\n ) => Promise<ArrayLike<number>>;\n geolibreJpegTablesPatched?: boolean;\n};\n\n/**\n * Route an abbreviated-JPEG COG's tile windows through geotiff.js, which reads\n * the shared tables, converting Y/Cb/Cr to the RGB bands the renderers expect.\n * Any other source is returned unchanged.\n *\n * @param source - A cog-tiler-wasm source.\n * @returns The same source, patched in place when it needs it.\n */\nexport function patchJpegCogSource(source: unknown): unknown {\n const cog = source as CogSourceInternals;\n if (\n cog.geolibreJpegTablesPatched ||\n !isAbbreviatedJpegCompression(cog.levels?.[0]?.compression) ||\n !cog.tiff ||\n !cog._tiffImage ||\n !cog._assembleWindow\n ) {\n return source;\n }\n\n const windowCache = new Map<string, Promise<ArrayLike<ArrayLike<number>>>>();\n cog._assembleWindow = async (level, x, y, width, height, band = 0) => {\n const key = `${level}/${x}/${y}/${width}/${height}`;\n let decoded = windowCache.get(key);\n if (decoded) {\n // Re-insert on a hit so the size cap below evicts the least recently used.\n windowCache.delete(key);\n windowCache.set(key, decoded);\n } else {\n decoded = cog._tiffImage!(level).then(async (image) => {\n const rasters = await image.readRasters({\n window: [x, y, x + width, y + height],\n });\n // geotiff.js expands the chroma subsampling but deliberately returns\n // the TIFF's native Y/Cb/Cr samples. The renderer expects RGB bands,\n // like the GPU engine, so perform the TIFF/JPEG color transform once\n // for the shared three-band window.\n const photometric =\n image.fileDirectory?.PhotometricInterpretation ?? image.fileDirectory?.getValue?.(262);\n if (photometric !== 6 || rasters.length < 3) {\n return rasters;\n }\n const directory = image.fileDirectory;\n const readTag = async (tag: number): Promise<ArrayLike<number> | undefined> => {\n if (directory?.hasTag?.(tag) === false) return undefined;\n const value = directory?.loadValue\n ? await directory.loadValue(tag)\n : directory?.getValue?.(tag);\n return value as ArrayLike<number> | undefined;\n };\n const [coefficients, referenceBlackWhite] = await Promise.all([readTag(529), readTag(532)]);\n // Baseline JPEG is 8-bit, so narrow the clamped float planes to one\n // byte per sample before caching: a window held as Float64Array costs\n // 8x the memory across the whole cache.\n return convertTiffYCbCrToRgb(\n rasters[0],\n rasters[1],\n rasters[2],\n coefficients,\n referenceBlackWhite,\n ).map((plane) => Uint8ClampedArray.from(plane));\n });\n windowCache.set(key, decoded);\n void decoded.catch(() => {\n if (windowCache.get(key) === decoded) windowCache.delete(key);\n });\n if (windowCache.size > 32) windowCache.delete(windowCache.keys().next().value!);\n }\n const rasters = await decoded;\n return rasters[band] ?? rasters[0];\n };\n cog.geolibreJpegTablesPatched = true;\n return source;\n}\n\n/**\n * A cog-tiler module whose `openCog` returns {@link patchJpegCogSource}-patched\n * sources; the rest of the module is passed through.\n *\n * @param module - The cog-tiler-wasm module (or a test double).\n * @returns The wrapped module.\n */\nexport function withJpegTablesPatch<M extends { openCog: (input: never) => Promise<unknown> }>(\n module: M,\n): M {\n return {\n ...module,\n openCog: (async (input: never) =>\n patchJpegCogSource(await module.openCog(input))) as M[\"openCog\"],\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["toDate","uuidv4","isPlainObject","isPlainObject","uuidv4","MAX_REDACT_DEPTH","uuidv4"],"sources":["../src/tile-address.ts","../src/diagram.ts","../src/marker-shape.ts","../src/hyperlink.ts","../src/photo.ts","../src/paths.ts","../src/routing.ts","../src/style-layer-evaluator.ts","../src/document-locale.ts","../src/label-number-format.ts","../src/external-native-paint.ts","../src/qgis-expression.ts","../src/attribute-form.ts","../src/map-click-claims.ts","../src/popup.ts","../src/quick-filters.ts","../src/level-filter.ts","../src/layer-filters.ts","../src/zip-directory.ts","../src/imdf-detect.ts","../src/deployment-capabilities.ts","../src/store/capabilities-slice.ts","../src/store/collaboration-slice.ts","../src/store/session-slice.ts","../src/store/layer-removal.ts","../src/store/layer-groups-slice.ts","../src/file-name.ts","../src/layer-defaults.ts","../src/layer-style-clipboard.ts","../src/layer-style-file.ts","../src/store/layers-slice.ts","../src/layer-library.ts","../src/store/libraries-slice.ts","../src/history.ts","../src/store/undo-history.ts","../src/store/map-view-slice.ts","../src/store/processing-slice.ts","../src/store/project-content-slice.ts","../src/store/project-slice.ts","../src/store/ui-slice.ts","../src/store.ts","../src/selection-actions.ts","../src/scale-units.ts","../src/elevation.ts","../src/camera-altitude.ts","../src/i3s-building-state.ts","../src/layers-file.ts","../src/pixel-format.ts","../src/s3.ts","../src/storymap-sample.ts","../src/storymap-playback.ts","../src/storymap-io.ts","../src/layer-selectors.ts","../src/platform.ts","../src/three-d-tiles.ts","../src/project-credential-refs.ts","../src/visibility.ts","../src/editor-tracking.ts","../src/editor-identity.ts","../src/datum-shift.ts","../src/cog-jpeg-tables.ts"],"sourcesContent":["export type TileScheme = \"xyz\" | \"tms\";\n\n/** A layer record that carries a tile scheme. Only XYZ templates are rewritten. */\nexport interface TileAddressLayer {\n type?: string;\n source?: { scheme?: unknown };\n}\n\n/** `\"tms\"` is the only scheme that flips a row. Anything else is XYZ. */\nexport function asTileScheme(scheme: unknown): TileScheme {\n return scheme === \"tms\" ? \"tms\" : \"xyz\";\n}\n\n/**\n * Express a `{-y}` row flip in the form MapLibre understands.\n *\n * `{-y}` flips the row. It does not select TMS. `scheme: \"tms\"` is its own\n * flip, so a template that has both is requested with an XYZ row. The stored\n * template and scheme stay as the user wrote them; callers apply this when\n * they build a render source. A template may use `{y}` or `{-y}`, not both,\n * and every template in one list must use the same row token. One shared\n * scheme applies to the whole list, so a plain `{y}` template cannot sit\n * beside a `{-y}` template.\n */\nexport function resolveTileAddress(\n tiles: readonly string[],\n scheme: TileScheme = \"xyz\",\n): { tiles: string[]; scheme: TileScheme } {\n let flipped = false;\n let plain = false;\n const next = tiles.map((tile) => {\n const flipY = tile.includes(\"{-y}\");\n const plainY = tile.includes(\"{y}\");\n if (flipY && plainY) {\n throw new Error(\"A tile template can use {y} or {-y}, not both.\");\n }\n if (flipY) flipped = true;\n if (plainY) plain = true;\n return flipY ? tile.replaceAll(\"{-y}\", \"{y}\") : tile;\n });\n if (flipped && plain) {\n throw new Error(\"A tile list can use {y} or {-y}, not both.\");\n }\n const nextScheme: TileScheme = flipped ? (scheme === \"tms\" ? \"xyz\" : \"tms\") : scheme;\n return { tiles: next, scheme: nextScheme };\n}\n\n/**\n * The row a layer's templates should request.\n *\n * XYZ layers run through {@link resolveTileAddress}. Every other layer keeps\n * its templates and only normalizes `scheme`.\n */\nexport function resolveLayerTileAddress(\n layer: TileAddressLayer,\n tiles: readonly string[],\n): { tiles: string[]; scheme: TileScheme } {\n const scheme = asTileScheme(layer.source?.scheme);\n if (layer.type !== \"xyz\") return { tiles: [...tiles], scheme };\n return resolveTileAddress(tiles, scheme);\n}\n\n/** True when {@link resolveTileAddress} rejected a mixed `{y}` / `{-y}` template. */\nexport function isTileAddressError(error: unknown): boolean {\n return error instanceof Error && error.message.includes(\"{y} or {-y}\");\n}\n","/**\n * Pure helpers for the diagram renderer (per-feature pie/donut/bar charts,\n * QGIS-style diagram symbology). Everything here is DOM-free so the desktop\n * app, the deck.gl overlay, and the node test suite share one implementation:\n * anchor placement, value extraction, and size scaling. The canvas drawing and\n * deck.gl layer construction live in `@geolibre/plugins`.\n */\nimport type { Feature, FeatureCollection, Geometry, Position } from \"geojson\";\nimport { styleValue, type LayerStyle } from \"./types\";\n\n/**\n * Hard cap on how many features get a diagram. Each diagram is rasterized into\n * an icon atlas, so an unbounded layer (e.g. a million-point parquet) would\n * hang the UI; typical thematic layers (counties, districts) sit well below\n * this.\n */\nexport const MAX_DIAGRAM_FEATURES = 2000;\n\n/**\n * Hard cap on how many raw features {@link collectDiagramData} visits while\n * looking for drawable diagrams. Without it, a layer whose drawable ratio is\n * low (sparse attributes, degenerate geometry) degenerates to a full scan of\n * an arbitrarily large collection on every rebuild. Features past this bound\n * are treated as truncation, which the Style Panel surfaces.\n */\nexport const MAX_DIAGRAM_SCAN_FEATURES = 100_000;\n\n/** Smallest rendered diagram size in pixels under scaled sizing. */\nexport const MIN_DIAGRAM_SIZE = 8;\n\n/** One feature's chart, ready for drawing. */\nexport interface DiagramDatum {\n /** Anchor position: the point location or polygon/line midpoint centroid. */\n position: [number, number];\n /** One value per configured diagram field (negatives clamped to 0). */\n values: number[];\n /** Sum of {@link values}. */\n total: number;\n /** The value driving scaled sizing (total, attribute value, or 1 for fixed). */\n sizeValue: number;\n}\n\n/** The per-layer diagram dataset plus the maxima needed for scaling. */\nexport interface DiagramData {\n data: DiagramDatum[];\n /** Largest {@link DiagramDatum.sizeValue} (0 when there is no data). */\n maxSizeValue: number;\n /** Largest single field value across all features (for bar axis scaling). */\n maxFieldValue: number;\n /** Largest {@link DiagramDatum.total} across all features. */\n maxTotal: number;\n /** True when the feature cap truncated the dataset. */\n truncated: boolean;\n}\n\n/**\n * Whether a layer style has a drawable diagram configuration: a chart type and\n * at least one mapped attribute.\n *\n * @param style - The layer style to test.\n */\nexport function isDiagramStyleEnabled(style: LayerStyle): boolean {\n return (\n styleValue(style, \"diagramType\") !== \"none\" &&\n styleValue(style, \"diagramFields\").some((field) => field.property !== \"\")\n );\n}\n\n/**\n * Whether a heatmap/cluster point renderer suppresses the layer's diagrams.\n * The point renderer only applies (and is only user-editable) on point-only\n * layers, so a stale `pointRenderer` value on a layer that has since gained\n * non-point geometry must not block diagrams — this mirrors the Style Panel's\n * own `!supportsPointRenderer || pointRenderer === \"single\"` visibility gate,\n * keeping the render and legend gates consistent with what the panel lets the\n * user configure. The full feature list is scanned (matching the panel's\n * isPointOnlyGeoJsonLayer, which uses an uncapped every()) so the two gates\n * can never disagree on layers whose non-point features sit past a sampling\n * window; the scan only runs for the uncommon non-\"single\" renderer values.\n *\n * @param geojson - The layer's feature collection (undefined suppresses nothing).\n * @param style - The layer style carrying `pointRenderer`.\n */\nexport function diagramsSuppressedByPointRenderer(\n geojson: FeatureCollection | undefined,\n style: LayerStyle,\n): boolean {\n if (styleValue(style, \"pointRenderer\") === \"single\") return false;\n const features = geojson?.features ?? [];\n if (features.length === 0) return false;\n return features.every((feature) => {\n const type = feature?.geometry?.type;\n return type === \"Point\" || type === \"MultiPoint\";\n });\n}\n\n/** Shoelace area of a linear ring (planar, in degree space; sign = winding). */\nfunction ringArea(ring: Position[]): number {\n let area = 0;\n for (let i = 0, j = ring.length - 1; i < ring.length; j = i, i += 1) {\n // Same sign convention as ringCentroid's cross terms, so centroid / area\n // divisions stay consistent for either winding.\n area += ring[j][0] * ring[i][1] - ring[i][0] * ring[j][1];\n }\n return area / 2;\n}\n\n/**\n * Area-weighted centroid of a polygon's outer ring, falling back to the ring's\n * vertex average when the area is degenerate. Planar math in degree space is\n * fine at diagram-placement accuracy.\n */\nfunction ringCentroid(ring: Position[]): [number, number] | null {\n if (ring.length === 0) return null;\n const area = ringArea(ring);\n if (Math.abs(area) < 1e-12) {\n let x = 0;\n let y = 0;\n for (const [lng, lat] of ring) {\n x += lng;\n y += lat;\n }\n return [x / ring.length, y / ring.length];\n }\n let cx = 0;\n let cy = 0;\n for (let i = 0, j = ring.length - 1; i < ring.length; j = i, i += 1) {\n const cross = ring[j][0] * ring[i][1] - ring[i][0] * ring[j][1];\n cx += (ring[j][0] + ring[i][0]) * cross;\n cy += (ring[j][1] + ring[i][1]) * cross;\n }\n return [cx / (6 * area), cy / (6 * area)];\n}\n\nfunction finitePosition(position: Position | undefined): [number, number] | null {\n if (!position) return null;\n const [lng, lat] = position;\n return Number.isFinite(lng) && Number.isFinite(lat) ? [lng as number, lat as number] : null;\n}\n\n/**\n * Where a feature's diagram is anchored: the point itself, a line's middle\n * vertex, or the polygon centroid (largest part of a multi-polygon). Returns\n * null for empty/degenerate geometry so the feature is skipped.\n *\n * @param geometry - The feature geometry (may be null for null-geometry rows).\n */\nexport function diagramAnchor(geometry: Geometry | null | undefined): [number, number] | null {\n if (!geometry) return null;\n switch (geometry.type) {\n case \"Point\":\n return finitePosition(geometry.coordinates);\n case \"MultiPoint\":\n return finitePosition(geometry.coordinates[0]);\n case \"LineString\": {\n const coords = geometry.coordinates;\n return finitePosition(coords[Math.floor(coords.length / 2)]);\n }\n case \"MultiLineString\": {\n // Anchor on the longest part (by vertex count) so the diagram sits on\n // the feature's dominant line.\n let longest: Position[] = [];\n for (const part of geometry.coordinates) {\n if (part.length > longest.length) longest = part;\n }\n return finitePosition(longest[Math.floor(longest.length / 2)]);\n }\n case \"Polygon\":\n return ringCentroid(geometry.coordinates[0] ?? []);\n case \"MultiPolygon\": {\n let best: Position[] = [];\n let bestArea = -1;\n for (const polygon of geometry.coordinates) {\n const ring = polygon[0] ?? [];\n const area = Math.abs(ringArea(ring));\n if (area > bestArea) {\n bestArea = area;\n best = ring;\n }\n }\n return ringCentroid(best);\n }\n case \"GeometryCollection\": {\n for (const child of geometry.geometries) {\n const anchor = diagramAnchor(child);\n if (anchor) return anchor;\n }\n return null;\n }\n default:\n return null;\n }\n}\n\n/** Reads a feature property as a non-negative finite number (else 0). */\nfunction readValue(feature: Feature, property: string): number {\n const raw = feature.properties?.[property];\n const num = typeof raw === \"number\" ? raw : Number(raw);\n return Number.isFinite(num) && num > 0 ? num : 0;\n}\n\n/**\n * Extract the per-feature diagram dataset from a layer's GeoJSON and style:\n * one {@link DiagramDatum} per feature that has an anchor and at least one\n * positive value, capped at {@link MAX_DIAGRAM_FEATURES}, plus the global\n * maxima scaled sizing and bar axes need.\n *\n * @param geojson - The layer's feature collection.\n * @param style - The layer style carrying the diagram configuration.\n */\nexport function collectDiagramData(geojson: FeatureCollection, style: LayerStyle): DiagramData {\n const properties = styleValue(style, \"diagramFields\")\n .map((field) => field.property)\n .filter((property) => property !== \"\");\n const sizeMode = styleValue(style, \"diagramSizeMode\");\n const sizeProperty = styleValue(style, \"diagramSizeProperty\");\n\n const data: DiagramDatum[] = [];\n let maxSizeValue = 0;\n let maxFieldValue = 0;\n let maxTotal = 0;\n let truncated = false;\n let visited = 0;\n\n for (const feature of geojson.features) {\n if (data.length >= MAX_DIAGRAM_FEATURES || visited >= MAX_DIAGRAM_SCAN_FEATURES) {\n truncated = true;\n break;\n }\n visited += 1;\n const position = diagramAnchor(feature.geometry);\n if (!position) continue;\n const values = properties.map((property) => readValue(feature, property));\n const total = values.reduce((sum, value) => sum + value, 0);\n if (total <= 0) continue;\n // Attribute sizing with no attribute chosen yet falls back to a constant\n // (full-size diagrams) instead of collapsing everything to the floor.\n const sizeValue =\n sizeMode === \"sum\"\n ? total\n : sizeMode === \"attribute\" && sizeProperty !== \"\"\n ? readValue(feature, sizeProperty)\n : 1;\n if (sizeValue > maxSizeValue) maxSizeValue = sizeValue;\n if (total > maxTotal) maxTotal = total;\n for (const value of values) {\n if (value > maxFieldValue) maxFieldValue = value;\n }\n data.push({ position, values, total, sizeValue });\n }\n\n return { data, maxSizeValue, maxFieldValue, maxTotal, truncated };\n}\n\n/**\n * The rendered size (diameter for pie/donut, box height/width for bars) of one\n * diagram in pixels. Fixed sizing returns the configured size; scaled sizing\n * maps the datum's size value onto the configured size by square root, so\n * diagram *area* tracks the value, with a small floor to keep every drawn\n * diagram legible.\n *\n * @param datum - The feature's diagram datum.\n * @param style - The layer style carrying the diagram configuration.\n * @param maxSizeValue - The dataset's largest size value (from\n * {@link collectDiagramData}).\n */\nexport function diagramPixelSize(\n datum: DiagramDatum,\n style: LayerStyle,\n maxSizeValue: number,\n): number {\n const size = Math.max(4, styleValue(style, \"diagramSize\"));\n if (styleValue(style, \"diagramSizeMode\") === \"fixed\") return size;\n if (maxSizeValue <= 0 || datum.sizeValue <= 0) return MIN_DIAGRAM_SIZE;\n return Math.max(MIN_DIAGRAM_SIZE, size * Math.sqrt(datum.sizeValue / maxSizeValue));\n}\n","import type { MarkerShape } from \"./types\";\n\n/**\n * Trace the outline of a built-in marker {@link MarkerShape} onto a 2D canvas\n * path, centered in a `size`×`size` box. The caller supplies the fill/stroke;\n * this only builds the path (via `beginPath`/`closePath`), so it stays free of\n * any DOM other than the passed context and can be shared by the map's sprite\n * baker (`@geolibre/map`) and the Print Layout legend renderer.\n *\n * `\"custom\"` (SVG) markers have no built-in path and are handled by the caller\n * (rasterized separately); passing one draws the default circle so the marker\n * never vanishes silently.\n *\n * @param ctx - Any 2D path-drawing context (a real `CanvasRenderingContext2D`\n * satisfies this structurally).\n * @param shape - The built-in marker shape to trace.\n * @param size - The box edge length in pixels; the shape is inset slightly so a\n * stroke is not clipped at the edge.\n */\nexport function drawMarkerPath(\n ctx: CanvasRenderingContext2D,\n shape: MarkerShape,\n size: number,\n): void {\n const c = size / 2;\n // Leave a small inset so the stroke is not clipped at the tile edge.\n const r = c * 0.82;\n ctx.beginPath();\n switch (shape) {\n case \"square\":\n ctx.rect(c - r, c - r, r * 2, r * 2);\n break;\n case \"triangle\":\n ctx.moveTo(c, c - r);\n ctx.lineTo(c + r, c + r);\n ctx.lineTo(c - r, c + r);\n ctx.closePath();\n break;\n case \"diamond\":\n ctx.moveTo(c, c - r);\n ctx.lineTo(c + r, c);\n ctx.lineTo(c, c + r);\n ctx.lineTo(c - r, c);\n ctx.closePath();\n break;\n case \"star\": {\n const outer = r;\n const inner = r * 0.42;\n for (let point = 0; point < 10; point += 1) {\n const radius = point % 2 === 0 ? outer : inner;\n const angle = (Math.PI / 5) * point - Math.PI / 2;\n const x = c + radius * Math.cos(angle);\n const y = c + radius * Math.sin(angle);\n if (point === 0) ctx.moveTo(x, y);\n else ctx.lineTo(x, y);\n }\n ctx.closePath();\n break;\n }\n case \"cross\": {\n const arm = r * 0.42;\n ctx.moveTo(c - arm, c - r);\n ctx.lineTo(c + arm, c - r);\n ctx.lineTo(c + arm, c - arm);\n ctx.lineTo(c + r, c - arm);\n ctx.lineTo(c + r, c + arm);\n ctx.lineTo(c + arm, c + arm);\n ctx.lineTo(c + arm, c + r);\n ctx.lineTo(c - arm, c + r);\n ctx.lineTo(c - arm, c + arm);\n ctx.lineTo(c - r, c + arm);\n ctx.lineTo(c - r, c - arm);\n ctx.lineTo(c - arm, c - arm);\n ctx.closePath();\n break;\n }\n case \"pin\": {\n // A teardrop: a circle bowl with a point at the bottom.\n const bowlR = r * 0.7;\n const bowlY = c - r * 0.2;\n ctx.moveTo(c, c + r);\n ctx.quadraticCurveTo(c - bowlR, bowlY + bowlR * 0.4, c - bowlR, bowlY);\n ctx.arc(c, bowlY, bowlR, Math.PI, Math.PI * 2);\n ctx.quadraticCurveTo(c + bowlR, bowlY + bowlR * 0.4, c, c + r);\n ctx.closePath();\n break;\n }\n case \"circle\":\n default:\n ctx.arc(c, c, r, 0, Math.PI * 2);\n }\n}\n\nconst RASTER_IMAGE_EXTENSION = /\\.(?:png|jpe?g|gif|webp|bmp|avif)$/i;\n\n/**\n * Whether a custom marker source is a raster image (PNG, JPEG, GIF, ...) rather\n * than SVG: a non-SVG `data:image/...` URL, or an `http(s)` URL whose path ends\n * in a raster extension. Raster markers cannot be recolored, so the marker\n * color and per-class colors only apply to SVG sources. A remote URL without a\n * telling extension is not classified here; the map sniffs its content type.\n *\n * @param markup - The `markerSvg` value.\n * @returns `true` for a recognizably raster source.\n */\nexport function isRasterMarkerSource(markup: string): boolean {\n const trimmed = markup.trim();\n if (/^data:image\\//i.test(trimmed)) return !/^data:image\\/svg\\+xml/i.test(trimmed);\n if (!/^https?:\\/\\//i.test(trimmed)) return false;\n return RASTER_IMAGE_EXTENSION.test(urlPath(trimmed));\n}\n\n/**\n * Whether a remote marker response holds SVG text, judged by its\n * `content-type` header: `image/svg+xml`, or a text/XML type (a raw GitHub\n * file is served as `text/plain`), reads as SVG and any other `image/*` does\n * not. A missing or generic header falls back to a `.svg` path.\n *\n * @param url - The requested URL.\n * @param contentType - The response's `content-type` header, if any.\n * @returns `true` when the body should be read as SVG markup.\n */\nexport function isSvgMarkerResponse(url: string, contentType: string | null): boolean {\n const type = (contentType ?? \"\").split(\";\")[0].trim().toLowerCase();\n if (type === \"image/svg+xml\") return true;\n if (type.startsWith(\"image/\")) return false;\n if (type.startsWith(\"text/\") || type.endsWith(\"/xml\") || type.endsWith(\"+xml\")) return true;\n return /\\.svg$/i.test(urlPath(url));\n}\n\nfunction urlPath(url: string): string {\n try {\n return new URL(url).pathname;\n } catch {\n return url.split(/[?#]/)[0];\n }\n}\n\n// Remote SVG sources we have already warned about, so the console message below\n// fires once per distinct URL instead of on every image regeneration.\nconst warnedRemoteSvgSources = new Set<string>();\n\n/**\n * Resolve user-supplied SVG input to an `Image.src`: inline markup (starting\n * with `<`) is encoded as a data URL; otherwise only `data:` and `http(s):`\n * URLs are accepted. Returns null for empty input or an unsupported scheme\n * (e.g. `file:`), which the caller treats as \"no image\" rather than letting an\n * arbitrary URL be loaded.\n *\n * Remote `http(s):` URLs are supported intentionally (custom marker/pattern\n * SVGs) but trigger a cross-origin request when rendered. Because a shared\n * `.geolibre.json` can carry such a URL, we log a one-time warning so the\n * outbound request is visible; prefer inline `<svg>` or `data:` in shared\n * projects.\n *\n * Lives here (rather than beside the map's sprite baker) because every surface\n * that previews a custom marker — the map, the Print Layout legend, the on-map\n * Legend panel — must resolve the same input the same way. A second, local\n * `data:image/svg+xml,…` wrapper is exactly what left URL and `data:` markers\n * blank in the on-map legend while the map drew them fine.\n *\n * @param markup - Raw SVG markup, a `data:` URL, or an `http(s)` URL.\n * @returns An `Image.src` value, or `null` when nothing loadable was given.\n */\nexport function resolveSvgSource(markup: string): string | null {\n const trimmed = markup.trim();\n if (!trimmed) return null;\n if (trimmed.startsWith(\"<\")) {\n return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(trimmed)}`;\n }\n if (/^https?:\\/\\//i.test(trimmed)) {\n if (!warnedRemoteSvgSources.has(trimmed)) {\n warnedRemoteSvgSources.add(trimmed);\n console.warn(\n `[geolibre] Loading a custom image from a remote URL triggers a ` +\n `cross-origin request: ${trimmed}. Prefer inline <svg> markup or a ` +\n `data: URL in shared projects.`,\n );\n }\n return trimmed;\n }\n if (trimmed.startsWith(\"data:\")) {\n return trimmed;\n }\n return null;\n}\n","/**\n * Return a whole-value HTTP(S) URL that is safe to expose as an attribute link.\n *\n * Attribute values may contain arbitrary user data, so only explicit web URLs\n * are accepted. Substrings in prose and schemes such as `javascript:` and\n * `file:` remain plain text.\n */\nexport function attributeLinkUrl(value: unknown): string | null {\n if (typeof value !== \"string\") return null;\n const trimmed = value.trim();\n if (!trimmed || /\\s/.test(trimmed)) return null;\n // Bidirectional controls can make an untrusted hostname appear to read as a\n // different domain even though the browser opens the original URL.\n if (/[\\u061c\\u200e\\u200f\\u202a-\\u202e\\u2066-\\u2069]/u.test(trimmed)) return null;\n if (!/^https?:\\/\\/[^/?#]/i.test(trimmed)) return null;\n\n try {\n const { protocol } = new URL(trimmed);\n return protocol === \"http:\" || protocol === \"https:\" ? trimmed : null;\n } catch {\n return null;\n }\n}\n","/**\n * Feature-property keys for inline photo images, shared by the app and the map\n * package so neither hardcodes a second copy that could drift. These keys are\n * part of the project schema: they are serialized into `.geolibre.json` on a\n * geotagged-photo or field-collection layer.\n */\n\n/** Property key under which a photo's downscaled thumbnail (data URL) is stored. */\nexport const PHOTO_PROPERTY = \"photo\";\n\n/**\n * Property key under which a geotagged photo's full-resolution image (a data URL\n * of the original, un-re-encoded bytes) is stored. {@link PHOTO_PROPERTY} holds\n * the small thumbnail shown on the map marker/popup; this holds the\n * native-resolution original used by the enlarged/fullscreen viewer and a \"Save\n * image\". Absent when the source is already at or below the thumbnail cap, or a\n * format a browser cannot display at native size (TIFF/HEIC).\n */\nexport const PHOTO_FULL_PROPERTY = \"photo_full\";\n","// Matches a `..` directory-traversal segment (bounded by separators or the\n// string ends), without rejecting a `..` inside a filename like `v1..2.gpkg`.\nconst PATH_TRAVERSAL = /(?:^|[/\\\\])\\.\\.(?:[/\\\\]|$)/;\n\n/**\n * Whether a path contains a `..` directory-traversal segment. Used to reject a\n * (possibly hand-edited) project's `sourcePath` before re-reading it off disk.\n *\n * @param path - The path to check.\n * @returns True when the path contains a traversal segment.\n */\nexport function hasPathTraversal(path: string): boolean {\n return PATH_TRAVERSAL.test(path);\n}\n\n/**\n * Whether a path is an absolute local filesystem path a host could read.\n *\n * Matches the raw path (not a trimmed copy): a whitespace-padded value would\n * pass a trimmed check but reach the file read unchanged and fail there, so it\n * is rejected up front. Accepts POSIX paths and Windows drive-letter paths only.\n * UNC paths (`\\\\server\\share`) are deliberately rejected: reading one can make\n * Windows auto-authenticate against a remote host (NTLM hash capture), and a\n * remote share is not a supported local data source.\n *\n * Lives in core so validation of a persisted path is available wherever a\n * record is normalized — the desktop app's `isAbsoluteLocalPath` re-exports the\n * same rule for its Tauri call sites.\n *\n * @param path - The path to check.\n * @returns True when the path is an absolute POSIX or Windows path.\n */\nexport function isAbsoluteFilesystemPath(path: string): boolean {\n return path.startsWith(\"/\") || /^[a-z]:[\\\\/]/i.test(path);\n}\n","import type { Feature, FeatureCollection, LineString, MultiPolygon, Polygon } from \"geojson\";\nimport { decodePolyline } from \"./polyline\";\nimport { getRuntimeEnvironment } from \"./runtime-env\";\n\n/**\n * Routing / network-analysis client and pure helpers backing the Processing →\n * Network tools (isochrones / service areas, OD cost matrices).\n *\n * Provider: Valhalla. OSRM (the Directions backend) has no isochrone endpoint,\n * so isochrones require Valhalla, which also serves the OD matrix\n * (`/sources_to_targets`) — one consistent API. The default is the shared\n * public FOSSGIS server, overridable via runtime env (`VITE_ROUTING_ENDPOINT`)\n * so a self-hosted Valhalla can be used instead. The pure request builders and\n * response parsers carry no React/MapLibre dependency so they can be\n * unit-tested without a browser or network.\n */\n\n/** Default public Valhalla server (FOSSGIS). CORS-enabled; usage limits apply. */\nexport const DEFAULT_ROUTING_ENDPOINT = \"https://valhalla1.openstreetmap.de\";\n\n/** Valhalla costing models exposed in the UI. */\nexport type RoutingMode = \"auto\" | \"pedestrian\" | \"bicycle\";\n\n/** Isochrone contour metric: travel time (minutes) or distance (km). */\nexport type RoutingMetric = \"time\" | \"distance\";\n\nexport interface RoutingConfig {\n /** Base URL of the Valhalla server (no trailing slash). */\n endpoint: string;\n}\n\n/** A point with a stable id, used as an isochrone origin or matrix source/target. */\nexport interface RoutingPoint {\n id: string | number;\n lon: number;\n lat: number;\n}\n\n/**\n * Resolves the routing configuration from runtime env, defaulting to the public\n * Valhalla server. `VITE_ROUTING_ENDPOINT` overrides the endpoint.\n *\n * @returns The resolved routing configuration.\n */\nexport function getRoutingConfig(): RoutingConfig {\n const env = getRuntimeEnvironment();\n return {\n endpoint: stripTrailingSlash(env.VITE_ROUTING_ENDPOINT?.trim()) || DEFAULT_ROUTING_ENDPOINT,\n };\n}\n\nfunction stripTrailingSlash(value: string | undefined): string | undefined {\n return value ? value.replace(/\\/+$/, \"\") : value;\n}\n\n/**\n * Parses a comma/space-separated contour string (\"5, 10, 15\") into a sorted,\n * de-duplicated list of positive numbers.\n *\n * @param value - The raw contour string.\n * @returns Ascending unique positive contour values.\n */\nexport function parseContours(value: string): number[] {\n const seen = new Set<number>();\n for (const token of value.split(/[\\s,]+/)) {\n if (!token) continue;\n const n = Number(token);\n if (Number.isFinite(n) && n > 0) seen.add(n);\n }\n return [...seen].sort((a, b) => a - b);\n}\n\nexport interface IsochroneRequestBody {\n locations: { lon: number; lat: number }[];\n costing: RoutingMode;\n contours: ({ time: number } | { distance: number })[];\n polygons: true;\n}\n\n/**\n * Builds a Valhalla `/isochrone` request body for a single origin.\n *\n * @param point - The origin `[lon, lat]`.\n * @param opts - Travel mode, metric, and contour values (minutes for time, km for distance).\n * @returns The request body.\n */\nexport function buildIsochroneRequest(\n point: [number, number],\n opts: { mode: RoutingMode; metric: RoutingMetric; contours: number[] },\n): IsochroneRequestBody {\n const [lon, lat] = point;\n return {\n locations: [{ lon, lat }],\n costing: opts.mode,\n contours: opts.contours.map((value) =>\n opts.metric === \"time\" ? { time: value } : { distance: value },\n ),\n polygons: true,\n };\n}\n\ntype IsochroneFeatureProps = {\n source_id: string | number;\n mode: RoutingMode;\n metric: RoutingMetric;\n contour: number | null;\n};\n\n/**\n * Converts a Valhalla isochrone GeoJSON response into polygon features tagged\n * with their origin, mode, metric, and contour value. Non-polygon features\n * (Valhalla can also emit contour LineStrings) are dropped.\n *\n * @param response - The Valhalla `/isochrone` GeoJSON FeatureCollection.\n * @param ctx - Origin id, travel mode, and metric to tag onto each polygon.\n * @returns The tagged polygon features.\n */\nexport function isochroneResponseToFeatures(\n response: unknown,\n ctx: { sourceId: string | number; mode: RoutingMode; metric: RoutingMetric },\n): Feature<Polygon | MultiPolygon, IsochroneFeatureProps>[] {\n const features = (response as FeatureCollection | null)?.features;\n if (!Array.isArray(features)) return [];\n const out: Feature<Polygon | MultiPolygon, IsochroneFeatureProps>[] = [];\n for (const feature of features) {\n const type = feature?.geometry?.type;\n if (type !== \"Polygon\" && type !== \"MultiPolygon\") continue;\n const contour = (feature.properties as { contour?: unknown } | null)?.contour;\n out.push({\n type: \"Feature\",\n geometry: feature.geometry as Polygon | MultiPolygon,\n properties: {\n source_id: ctx.sourceId,\n mode: ctx.mode,\n metric: ctx.metric,\n contour: typeof contour === \"number\" ? contour : null,\n },\n });\n }\n return out;\n}\n\nexport interface MatrixRequestBody {\n sources: { lon: number; lat: number }[];\n targets: { lon: number; lat: number }[];\n costing: RoutingMode;\n}\n\n/**\n * Builds a Valhalla `/sources_to_targets` request body.\n *\n * @param origins - The source points.\n * @param targets - The target points.\n * @param mode - The travel mode.\n * @returns The request body.\n */\nexport function buildMatrixRequest(\n origins: RoutingPoint[],\n targets: RoutingPoint[],\n mode: RoutingMode,\n): MatrixRequestBody {\n return {\n sources: origins.map((p) => ({ lon: p.lon, lat: p.lat })),\n targets: targets.map((p) => ({ lon: p.lon, lat: p.lat })),\n costing: mode,\n };\n}\n\ntype MatrixCell = {\n from_index: number;\n to_index: number;\n /** Travel time in seconds, or null when unreachable. */\n time: number | null;\n /** Travel distance in km, or null when unreachable. */\n distance: number | null;\n};\n\ntype MatrixFeatureProps = {\n origin_id: string | number;\n dest_id: string | number;\n time_s: number;\n distance_km: number;\n mode: RoutingMode;\n};\n\n/**\n * Converts a Valhalla `/sources_to_targets` response into one LineString per\n * reachable origin→destination pair, carrying the travel time and distance.\n * Unreachable pairs (null time) are dropped.\n *\n * @param response - The Valhalla matrix response.\n * @param origins - The origin points (indexed by `from_index`).\n * @param targets - The target points (indexed by `to_index`).\n * @param ctx - The travel mode to tag onto each pair.\n * @returns The OD-pair LineString features.\n */\nexport function matrixResponseToFeatures(\n response: unknown,\n origins: RoutingPoint[],\n targets: RoutingPoint[],\n ctx: { mode: RoutingMode },\n): Feature<LineString, MatrixFeatureProps>[] {\n const rows = (response as { sources_to_targets?: MatrixCell[][] } | null)?.sources_to_targets;\n if (!Array.isArray(rows)) return [];\n const out: Feature<LineString, MatrixFeatureProps>[] = [];\n for (const row of rows) {\n if (!Array.isArray(row)) continue;\n for (const cell of row) {\n if (cell?.time == null || cell.distance == null) continue;\n const origin = origins[cell.from_index];\n const target = targets[cell.to_index];\n if (!origin || !target) continue;\n out.push({\n type: \"Feature\",\n geometry: {\n type: \"LineString\",\n coordinates: [\n [origin.lon, origin.lat],\n [target.lon, target.lat],\n ],\n },\n properties: {\n origin_id: origin.id,\n dest_id: target.id,\n time_s: cell.time,\n distance_km: cell.distance,\n mode: ctx.mode,\n },\n });\n }\n }\n return out;\n}\n\nexport interface RouteRequestBody {\n locations: { lon: number; lat: number }[];\n costing: RoutingMode;\n directions_options: { units: \"kilometers\" };\n}\n\n/**\n * Builds a Valhalla `/route` request body that visits the points in the given\n * order. Valhalla snaps each location to the nearest routable edge, so points\n * that are off the road network (cell sites, sensors) still route cleanly.\n *\n * @param points - The waypoints in visiting order (at least two).\n * @param mode - The travel mode (costing model).\n * @returns The request body.\n */\nexport function buildRouteRequest(points: RoutingPoint[], mode: RoutingMode): RouteRequestBody {\n return {\n locations: points.map((p) => ({ lon: p.lon, lat: p.lat })),\n costing: mode,\n directions_options: { units: \"kilometers\" },\n };\n}\n\ntype RouteLeg = {\n shape?: string;\n summary?: { time?: number; length?: number };\n};\n\ntype RouteFeatureProps = {\n leg_index: number;\n from_id: string | number;\n to_id: string | number;\n time_s: number;\n distance_km: number;\n mode: RoutingMode;\n};\n\n/**\n * Converts a Valhalla `/route` response into one LineString per leg (the road\n * path between two consecutive waypoints), carrying the leg's travel time and\n * distance. Legs without a decodable shape are dropped.\n *\n * @param response - The Valhalla `/route` response.\n * @param points - The waypoints passed to the request, indexing the legs.\n * @param ctx - The travel mode to tag onto each leg.\n * @returns One LineString feature per routed leg.\n */\nexport function routeResponseToFeatures(\n response: unknown,\n points: RoutingPoint[],\n ctx: { mode: RoutingMode },\n): Feature<LineString, RouteFeatureProps>[] {\n const legs = (response as { trip?: { legs?: RouteLeg[] } } | null)?.trip?.legs;\n if (!Array.isArray(legs)) return [];\n const out: Feature<LineString, RouteFeatureProps>[] = [];\n legs.forEach((leg, index) => {\n if (typeof leg?.shape !== \"string\") return;\n const coordinates = decodePolyline(leg.shape, 6);\n if (coordinates.length < 2) return;\n const from = points[index];\n const to = points[index + 1];\n out.push({\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates },\n properties: {\n leg_index: index,\n from_id: from?.id ?? index,\n to_id: to?.id ?? index + 1,\n time_s: typeof leg.summary?.time === \"number\" ? leg.summary.time : 0,\n distance_km: typeof leg.summary?.length === \"number\" ? leg.summary.length : 0,\n mode: ctx.mode,\n },\n });\n });\n return out;\n}\n\n/**\n * Comparator for ordering route waypoints by a chosen attribute value. Numeric\n * strings and timestamps (anything `Date.parse` understands, e.g. ISO 8601)\n * sort chronologically/numerically; values that parse this way sort before\n * free-form text, which falls back to a locale string comparison. Empty/missing\n * values sort last so unlabeled points trail the ordered ones.\n *\n * Numeric parsing is tried before date parsing, so a single column is expected\n * to use one convention: a column mixing plain integers with ISO timestamps\n * would compare them on the same numeric axis (epoch millis dwarf small\n * integers), which is not a meaningful ordering. Real sequence/time columns use\n * one convention, so this is acceptable in practice.\n *\n * @param a - The first value.\n * @param b - The second value.\n * @returns A negative, zero, or positive number for ascending order.\n */\nexport function compareSequenceValues(a: unknown, b: unknown): number {\n const aMissing = isMissingValue(a);\n const bMissing = isMissingValue(b);\n if (aMissing && bMissing) return 0;\n if (aMissing) return 1;\n if (bMissing) return -1;\n const an = toComparableNumber(a);\n const bn = toComparableNumber(b);\n if (an !== null && bn !== null) return an - bn;\n if (an !== null) return -1;\n if (bn !== null) return 1;\n return String(a).localeCompare(String(b));\n}\n\nfunction isMissingValue(value: unknown): boolean {\n return value == null || (typeof value === \"string\" && value.trim() === \"\");\n}\n\nfunction toComparableNumber(value: unknown): number | null {\n if (typeof value === \"number\") return Number.isFinite(value) ? value : null;\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (trimmed === \"\") return null;\n // Only treat plain decimal/scientific notation as numeric, so hex (\"0x1A\")\n // and other Number()-coercible forms sort as text rather than by their\n // surprising numeric value.\n if (/^[+-]?\\d+(\\.\\d+)?([eE][+-]?\\d+)?$/.test(trimmed)) {\n return Number(trimmed);\n }\n const parsed = Date.parse(trimmed);\n if (Number.isFinite(parsed)) return parsed;\n }\n return null;\n}\n\n/**\n * Error thrown when a Valhalla request returns a non-2xx response. Carries the\n * HTTP status so callers can branch on it (e.g. a 4xx rejection) without\n * pattern-matching the message string.\n */\nexport class RoutingRequestError extends Error {\n readonly status: number;\n\n constructor(status: number, statusText: string) {\n super(`Routing request failed (${status} ${statusText})`);\n this.name = \"RoutingRequestError\";\n this.status = status;\n }\n}\n\nasync function postJson(url: string, body: unknown, signal?: AbortSignal): Promise<unknown> {\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n signal,\n });\n if (!response.ok) {\n throw new RoutingRequestError(response.status, response.statusText);\n }\n return response.json();\n}\n\n/**\n * Requests an isochrone from the Valhalla server.\n *\n * @param endpoint - The Valhalla base URL.\n * @param body - The request body from {@link buildIsochroneRequest}.\n * @param signal - Optional abort signal.\n * @returns The Valhalla GeoJSON response.\n */\nexport function requestIsochrone(\n endpoint: string,\n body: IsochroneRequestBody,\n signal?: AbortSignal,\n): Promise<unknown> {\n return postJson(`${stripTrailingSlash(endpoint)}/isochrone`, body, signal);\n}\n\n/**\n * Requests an OD cost matrix from the Valhalla server.\n *\n * @param endpoint - The Valhalla base URL.\n * @param body - The request body from {@link buildMatrixRequest}.\n * @param signal - Optional abort signal.\n * @returns The Valhalla matrix response.\n */\nexport function requestMatrix(\n endpoint: string,\n body: MatrixRequestBody,\n signal?: AbortSignal,\n): Promise<unknown> {\n return postJson(`${stripTrailingSlash(endpoint)}/sources_to_targets`, body, signal);\n}\n\n/**\n * Requests a route through ordered waypoints from the Valhalla server.\n *\n * @param endpoint - The Valhalla base URL.\n * @param body - The request body from {@link buildRouteRequest}.\n * @param signal - Optional abort signal.\n * @returns The Valhalla `/route` response.\n */\nexport function requestRoute(\n endpoint: string,\n body: RouteRequestBody,\n signal?: AbortSignal,\n): Promise<unknown> {\n return postJson(`${stripTrailingSlash(endpoint)}/route`, body, signal);\n}\n\n/**\n * Requests a turn-by-turn route from the Valhalla server. Unlike\n * {@link requestRoute}, the body is passed through as is, so the caller can ask\n * for Valhalla's OSRM-compatible output with voice and banner instructions,\n * alternates, a start heading, and costing options (the navigation tool).\n *\n * @param endpoint - The Valhalla base URL.\n * @param body - The `/route` request body.\n * @param signal - Optional abort signal.\n * @returns The Valhalla `/route` response.\n */\nexport function requestNavigationRoute(\n endpoint: string,\n body: Record<string, unknown>,\n signal?: AbortSignal,\n): Promise<unknown> {\n return postJson(`${stripTrailingSlash(endpoint)}/route`, body, signal);\n}\n","import {\n Color,\n featureFilter,\n latest,\n normalizePropertyExpression,\n} from \"@maplibre/maplibre-gl-style-spec\";\n\n/** The geometry kinds a MapLibre style expression tells apart (`[\"geometry-type\"]`). */\nexport type StyleGeometryKind = \"Point\" | \"LineString\" | \"Polygon\";\n\n/** A feature as a style layer's expressions read it. */\nexport interface StyleFeatureInput {\n id?: string | number;\n properties: Record<string, unknown> | null | undefined;\n kind: StyleGeometryKind;\n}\n\n/** A style layer's filter and property values, evaluated as MapLibre would. */\nexport interface StyleLayerEvaluator {\n /** Whether the layer's `filter` keeps the feature at `zoom`. */\n passes: (zoom: number, feature: StyleFeatureInput) => boolean;\n /**\n * A paint or layout property's value for a feature at `zoom`: the layer's\n * value (a literal, a legacy function or an expression) or the spec's\n * default. A color comes back as `[r, g, b, a]` with channels 0-255 and\n * alpha 0-1; formatted text as its plain string. `undefined` for a property\n * the spec does not know or an expression that does not compile.\n */\n value: (\n group: \"paint\" | \"layout\",\n name: string,\n zoom: number,\n feature: StyleFeatureInput,\n ) => unknown;\n}\n\ninterface StyleLayerInput {\n id?: string;\n type: string;\n filter?: unknown;\n paint?: Record<string, unknown>;\n layout?: Record<string, unknown>;\n}\n\nconst GEOMETRY_TYPE: Record<StyleGeometryKind, number> = { Point: 1, LineString: 2, Polygon: 3 };\n\ntype Evaluate = (globals: { zoom: number }, feature: unknown) => unknown;\n\n/**\n * Compile a MapLibre style layer's filter and properties for evaluation\n * outside MapLibre, for a renderer that draws the layer itself (the ArcGIS\n * renderer drawing a plugin's own GeoJSON overlay, say). Uses the style spec's\n * own evaluator, so data-driven and zoom expressions, legacy functions and\n * defaults behave as they do on MapLibre. Each property compiles once.\n *\n * @param layer - The style layer (its `type`, `filter`, `paint`, `layout`).\n * @returns The layer's evaluator.\n */\nexport function createStyleLayerEvaluator(layer: StyleLayerInput): StyleLayerEvaluator {\n const root = `layers[${layer.id ?? \"\"}]`;\n let filter: ((zoom: number, feature: unknown) => boolean) | null;\n try {\n const compiled = featureFilter(layer.filter as never, `${root}.filter`);\n filter = (zoom, feature) => compiled.filter({ zoom } as never, feature as never);\n } catch {\n // MapLibre refuses the layer outright; draw nothing rather than everything.\n filter = () => false;\n }\n const compiled = new Map<string, Evaluate | null>();\n const compile = (group: \"paint\" | \"layout\", name: string): Evaluate | null => {\n const key = `${group}/${name}`;\n if (compiled.has(key)) return compiled.get(key)!;\n const spec = (latest as unknown as Record<string, Record<string, unknown>>)[\n `${group}_${layer.type}`\n ]?.[name] as { default?: unknown } | undefined;\n let evaluate: Evaluate | null = null;\n if (spec) {\n const raw = layer[group]?.[name];\n try {\n const expression = normalizePropertyExpression(\n (raw ?? spec.default) as never,\n `${root}.${group}.${name}`,\n spec as never,\n );\n evaluate = (globals, feature) => expression.evaluate(globals as never, feature as never);\n } catch {\n evaluate = null;\n }\n }\n compiled.set(key, evaluate);\n return evaluate;\n };\n const input = (feature: StyleFeatureInput) => ({\n type: GEOMETRY_TYPE[feature.kind],\n id: feature.id,\n properties: feature.properties ?? {},\n });\n return {\n passes: (zoom, feature) => {\n try {\n return filter(zoom, input(feature));\n } catch {\n return false;\n }\n },\n value: (group, name, zoom, feature) => {\n const evaluate = compile(group, name);\n if (!evaluate) return undefined;\n let result: unknown;\n try {\n result = evaluate({ zoom }, input(feature));\n } catch {\n return undefined;\n }\n if (result instanceof Color) {\n const [r, g, b, a] = result.rgb;\n return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255), a];\n }\n if (result && typeof result === \"object\" && \"sections\" in result) return String(result);\n return result;\n },\n };\n}\n","/**\n * The document language, so formatted numbers and dates follow the UI locale.\n *\n * Shared by the Identify popup's value formatting and by label number\n * formatting, which both fall back to it when the author has not pinned an\n * explicit locale. Returns `undefined` outside a browser (tests, the headless\n * project authoring paths), which leaves `Intl` on its own default.\n */\nexport function documentLocale(): string | undefined {\n const lang = typeof document !== \"undefined\" ? document.documentElement.lang.trim() : \"\";\n return lang || undefined;\n}\n","import type { LabelStyle } from \"./types\";\n\n/**\n * Locale-aware number formatting for map labels (issue #2336).\n *\n * A numeric attribute rendered straight into a label reads as `1234567.5`,\n * which is hard to compare at a glance. QGIS solves this with\n * `format_number(field, places, language)`; the equivalent here is three\n * {@link LabelStyle} fields — {@link LabelStyle.numberFormatEnabled},\n * {@link LabelStyle.numberDecimals} and {@link LabelStyle.numberLocale} —\n * whose effect this module renders two ways:\n *\n * - {@link labelFieldTextField} builds the MapLibre `text-field` expression the\n * 2D map and the Mapbox/MapLibre style export both use, so the map and the\n * exported style agree.\n * - {@link formatLabelNumber} does the same in JavaScript for the paths that\n * resolve label text themselves: the Cesium globe and the duplicate-label\n * collapsing modes.\n *\n * Formatting applies to {@link LabelStyle.field} only. A label\n * {@link LabelStyle.expression} formats its own output (with `number-format`,\n * offered in the Expression Builder), so wrapping it here would fight the\n * author.\n */\n\n/**\n * Locales offered for {@link LabelStyle.numberLocale}, alongside the default\n * empty value that follows the app's own language.\n *\n * Deliberately short, and every entry groups with a character the map's glyph\n * stack can draw: ASCII `,`/`.` or U+00A0. Locales whose CLDR grouping uses\n * the narrow no-break space U+202F (`fr-FR`) or non-Latin digits (`ar-EG`)\n * are left out because a missing glyph renders as a blank box on the map\n * rather than as a separator. An author who needs one of those can still\n * write `[\"number-format\", …]` in the label expression.\n */\nexport const LABEL_NUMBER_LOCALES = [\"en-US\", \"de-DE\", \"ru-RU\", \"hi-IN\"] as const;\n\n/**\n * Magnitude at which the text-field stops treating a value as a finite number.\n *\n * JSON carries no `Infinity`, so the expression cannot test against it\n * directly; this is the largest power of ten a double holds, which doubles as\n * the `to-number` fallback so an unconvertible value fails the same test. A\n * real attribute at or above it labels as plain text instead of a formatted\n * number -- a trade worth making for values already past 1e308.\n */\nconst NON_FINITE_SENTINEL = 1e308;\n\n/** Sample value used to preview a locale's separators in the Style panel. */\nexport const LABEL_NUMBER_SAMPLE = 1234567.5;\n\n/**\n * Cached `Intl.NumberFormat`s, keyed by locale + decimals. `formatLabelNumber`\n * runs once per labeled feature (per Cesium entity, per deduplicated point), so\n * constructing a formatter on every call is the wrong order of magnitude.\n *\n * The key space is small: {@link LABEL_NUMBER_LOCALES} plus the app's own\n * language, times eleven decimal settings, times whatever a hand-edited project\n * pins.\n */\nconst formatterCache = new Map<string, Intl.NumberFormat>();\n\n/** Locale tags already accepted or rejected, so each is tested once. */\nconst localeSupport = new Map<string, boolean>();\n\n/**\n * A formatter for this locale/decimals pair. Grouping is always on: it is the\n * whole point of the setting. The locale must already have been vetted by\n * {@link resolveLabelNumberLocale}, so this never throws.\n */\nfunction labelNumberFormatter(locale: string | undefined, decimals: number): Intl.NumberFormat {\n const digits = clampLabelDecimals(decimals);\n const key = `${locale ?? \"\"}\\u0000${digits}`;\n const cached = formatterCache.get(key);\n if (cached) return cached;\n const formatter = new Intl.NumberFormat(locale, {\n useGrouping: true,\n minimumFractionDigits: digits,\n maximumFractionDigits: digits,\n });\n formatterCache.set(key, formatter);\n return formatter;\n}\n\n/**\n * Whether every character a locale's grouping produces is one the map's glyph\n * stack can draw: ASCII, or the no-break space U+00A0.\n *\n * {@link LABEL_NUMBER_LOCALES} is curated on exactly this basis, but the picker\n * is not the only way a locale reaches the renderer. A hand-edited project can\n * pin any tag, and the default (`numberLocale: \"\"`) follows the app's own\n * language, so a French or Arabic UI would otherwise put U+202F or Arabic-Indic\n * digits into the `text-field` and label the map with blank boxes. Testing the\n * rendered sample rather than the tag covers both routes and needs no list to\n * keep in sync.\n */\nfunction glyphSafeLocale(tag: string): boolean {\n try {\n const format = new Intl.NumberFormat(tag, {\n useGrouping: true,\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n });\n // Both signs: sv-SE, fi-FI and nb-NO group with U+00A0 (safe) but write\n // negatives with U+2212 MINUS SIGN, so a positive-only check would pass\n // them and then blank-box every negative label.\n for (const sample of [\n format.format(LABEL_NUMBER_SAMPLE),\n format.format(-LABEL_NUMBER_SAMPLE),\n ]) {\n for (const char of sample) {\n const code = char.codePointAt(0) ?? 0;\n if (code >= 0x80 && code !== 0x00a0) return false;\n }\n }\n return true;\n } catch {\n // A malformed BCP 47 tag (`\"en_US\"`, `\"not a locale\"`) makes Intl throw a\n // RangeError. It would propagate out of the Cesium labeler and the dedup\n // builder and take down the layer's labels, and MapLibre evaluates its own\n // Intl.NumberFormat per feature, so the expression path is exposed too.\n return false;\n }\n}\n\n/** Clamp a decimal-places setting to the range the UI and `Intl` both accept. */\nexport function clampLabelDecimals(decimals: number): number {\n if (!Number.isFinite(decimals)) return 0;\n return Math.max(0, Math.min(10, Math.trunc(decimals)));\n}\n\n/**\n * The first locale tag that both `Intl` accepts and the map can draw, or\n * `undefined` to leave `Intl` and MapLibre on the runtime default.\n *\n * Every path resolves its locale through here -- the JavaScript formatter, the\n * MapLibre expression, and the dedup cache key -- so all three agree on which\n * locale is actually in effect.\n */\nexport function resolveLabelNumberLocale(...tags: (string | undefined)[]): string | undefined {\n for (const tag of tags) {\n if (!tag) continue;\n let usable = localeSupport.get(tag);\n if (usable === undefined) {\n usable = glyphSafeLocale(tag);\n localeSupport.set(tag, usable);\n }\n if (usable) return tag;\n }\n return undefined;\n}\n\n/**\n * Format one label value as a number, or `null` when the setting is off or the\n * value is not a finite number (a text attribute keeps its own rendering).\n *\n * @param value - The raw attribute value.\n * @param labels - The layer's label configuration.\n * @param fallbackLocale - Locale used when {@link LabelStyle.numberLocale} is\n * empty; the caller passes the app's language. Omitted means the runtime\n * default, matching how popups format their numbers.\n */\nexport function formatLabelNumber(\n value: unknown,\n labels: Pick<LabelStyle, \"numberFormatEnabled\" | \"numberDecimals\" | \"numberLocale\">,\n fallbackLocale?: string,\n): string | null {\n if (!labels.numberFormatEnabled) return null;\n if (typeof value !== \"number\" || !Number.isFinite(value)) return null;\n const locale = resolveLabelNumberLocale(labels.numberLocale, fallbackLocale);\n return labelNumberFormatter(locale, labels.numberDecimals).format(value);\n}\n\n/**\n * The MapLibre expression that renders {@link LabelStyle.field} as text, with\n * number formatting applied when it is on. Returns `\"\"` when no field is set,\n * which callers treat as \"no label text\".\n *\n * Number formatting is guarded by a `typeof` test rather than applied blindly\n * so a mixed or text column still labels normally; only real numbers take the\n * `number-format` branch.\n */\nexport function labelFieldTextField(\n labels: Pick<LabelStyle, \"field\" | \"numberFormatEnabled\" | \"numberDecimals\" | \"numberLocale\">,\n fallbackLocale?: string,\n): unknown[] | \"\" {\n const field = labels.field;\n if (!field) return \"\";\n const asText = [\"to-string\", [\"coalesce\", [\"get\", field], \"\"]];\n if (!labels.numberFormatEnabled) return asText;\n const locale = resolveLabelNumberLocale(labels.numberLocale, fallbackLocale);\n const digits = clampLabelDecimals(labels.numberDecimals);\n const options: Record<string, unknown> = {};\n if (locale) options.locale = locale;\n // MapLibre's `number-format` ignores a falsy option, so zero fraction digits\n // cannot be requested that way — it would silently fall back to the spec\n // default of up to three. Rounding first gives an integer, which then prints\n // with no fraction digits at all.\n // `to-number` carries a fallback so it can never throw: MapLibre hands a NaN\n // property through as null, and an unguarded conversion raised \"Could not\n // convert null to number\" from inside the render. The fallback is the same\n // sentinel the finiteness test rejects, so a value that could not convert\n // takes the plain-text branch rather than labelling as the fallback itself.\n const numeric = [\"to-number\", [\"get\", field], NON_FINITE_SENTINEL];\n const number = digits > 0 ? numeric : [\"round\", numeric];\n if (digits > 0) {\n options[\"min-fraction-digits\"] = digits;\n options[\"max-fraction-digits\"] = digits;\n }\n return [\n \"case\",\n [\n \"all\",\n [\"==\", [\"typeof\", [\"get\", field]], \"number\"],\n // Finite only, matching formatLabelNumber's Number.isFinite guard.\n // Without it the map rendered Infinity as U+221E -- a glyph the label\n // font need not carry, so a tofu box -- while the JavaScript path fell\n // back to the plain string, which is the disagreement the parity tests\n // exist to prevent. NaN was worse: it reached the render as null and\n // threw out of the conversion.\n [\"<\", [\"abs\", numeric], NON_FINITE_SENTINEL],\n ],\n [\"number-format\", number, options],\n asText,\n ];\n}\n\n/**\n * Render {@link LABEL_NUMBER_SAMPLE} the way a locale/decimals pair would, so\n * the Style panel can name each choice by the separators it actually produces\n * (\"1,234,567.50\") instead of by an opaque language tag.\n *\n * A tag `Intl` rejects (or one the map cannot draw) falls back to the runtime\n * locale's formatting rather than to an unformatted number, matching what\n * {@link formatLabelNumber} and the emitted expression do with the same tag.\n */\nexport function formatLabelNumberSample(locale: string, decimals: number): string {\n return labelNumberFormatter(resolveLabelNumberLocale(locale), decimals).format(\n LABEL_NUMBER_SAMPLE,\n );\n}\n","/**\n * Paint ownership for plugin-registered native layers.\n *\n * A plugin can register a MapLibre `CustomLayerInterface` (a WebGL layer that\n * draws itself, e.g. a Zarr renderer that reprojects chunks on the GPU) through\n * `registerExternalNativeLayer`. Such a layer has **no MapLibre paint\n * properties**, so GeoLibre's Style panel editors (raster opacity/brightness/\n * saturation/contrast/hue, or the vector paint editors) cannot reach it: every\n * `setPaintProperty` call is dropped and the control looks functional while\n * doing nothing (opengeos/GeoLibre#1445).\n *\n * The registration therefore declares `paintMode: \"plugin\"`, which lands in the\n * store layer as `metadata.paintMode` so the UI can hide the editors it cannot\n * apply, and may additionally supply a {@link ExternalNativePaintBridge} so the\n * generic controls GeoLibre keeps (opacity, visibility) are forwarded to the\n * custom layer's own API.\n *\n * The bridge holds live functions, so it cannot live in `metadata` (that is\n * serialized into `.geolibre.json`). It is kept in this module-level registry\n * instead, keyed by layer id, and the layer record only carries the\n * serializable `paintMode` flag.\n */\nexport interface ExternalNativePaintBridge {\n /**\n * Apply an opacity in [0, 1] to the plugin's layer (e.g.\n * `zarrLayer.setOpacity(value)`). When supplied, GeoLibre keeps its Opacity\n * sliders live for the layer and calls this whenever the value changes.\n */\n setOpacity?: (opacity: number) => void;\n /**\n * Apply the panel's show/hide state. MapLibre already honors `visibility` on\n * a custom layer, so this is only needed by layers that also want to release\n * resources or stop fetching while hidden.\n */\n setVisibility?: (visible: boolean) => void;\n}\n\n/** Serializable paint-ownership marker stored in `GeoLibreLayer.metadata`. */\nexport type ExternalNativePaintMode = \"geolibre\" | \"plugin\";\n\n/**\n * Whether a layer is drawn by a **custom render layer** its control created — a\n * deck.gl overlay, 3D Tiles, or Add Vector Layer's own MapLibre layers — rather\n * than by GeoLibre's layer sync. `metadata.customLayerType` is what marks one,\n * and it is the flag `syncExternalNativeLayer` branches on: for these layers the\n * sync only *moves and filters* native layers that already exist, so nothing\n * appears on the map unless the owning control (re)creates them.\n *\n * Lives here, in core, so the map's dispatch and the consumers that need to\n * reason about it — the Layer Library's \"can this be re-added and rendered?\"\n * gate (issue #1520) — share one definition and cannot drift.\n *\n * Distinct from {@link pluginOwnsPaint}: that asks who applies *paint* to an\n * existing layer, this asks who creates the layer at all.\n *\n * @param layer - A store layer (metadata only).\n * @returns True when only the owning control can create the layer's map output.\n */\nexport function controlRendersLayer(layer: { metadata: Record<string, unknown> }): boolean {\n return typeof layer.metadata.customLayerType === \"string\";\n}\n\n/**\n * True when the plugin that registered this layer paints it itself, so GeoLibre\n * must not offer (or apply) MapLibre paint properties for it.\n */\nexport function pluginOwnsPaint(layer: { metadata: Record<string, unknown> }): boolean {\n return layer.metadata.paintMode === \"plugin\";\n}\n\nconst paintBridges = new Map<string, ExternalNativePaintBridge>();\n\n/** Register (or replace) the paint bridge a plugin supplied for a layer. */\nexport function setExternalNativePaintBridge(\n layerId: string,\n bridge: ExternalNativePaintBridge | undefined,\n): void {\n if (bridge && (bridge.setOpacity || bridge.setVisibility)) {\n paintBridges.set(layerId, bridge);\n } else {\n paintBridges.delete(layerId);\n }\n}\n\n/** Drop a layer's paint bridge (on unregister, or when the layer is removed). */\nexport function clearExternalNativePaintBridge(layerId: string): void {\n paintBridges.delete(layerId);\n}\n\n/** The paint bridge for a layer, if its plugin supplied one. */\nexport function getExternalNativePaintBridge(\n layerId: string,\n): ExternalNativePaintBridge | undefined {\n return paintBridges.get(layerId);\n}\n\n/**\n * True when a plugin-painted layer bridged `setOpacity`, so an Opacity slider\n * would actually reach the renderer. It says nothing about GeoLibre-painted\n * layers, which have no bridge: the Style and Layers panels combine it with\n * `!pluginOwnsPaint(layer)` to decide whether to show the slider at all.\n */\nexport function supportsBridgedOpacity(layerId: string): boolean {\n return typeof paintBridges.get(layerId)?.setOpacity === \"function\";\n}\n\n/**\n * True when the primary renderer draws this plugin-painted layer itself, from\n * the store record, and so applies `layer.opacity` without any plugin bridge.\n *\n * A Zarr layer is a MapLibre custom layer owned by the Zarr control on the 2D\n * map, but the ArcGIS view and the Cesium globe render it natively (an ArcGIS\n * layer's `opacity`, a Cesium `ImageryLayer.alpha`) and mount no control to\n * register a bridge. Without this the Opacity slider would be hidden there\n * although it works (opengeos/GeoLibre#2261).\n *\n * @param layer - A store layer.\n * @param renderer - The primary renderer (`useAppStore().primaryRenderer`).\n * @returns Whether an Opacity slider reaches the layer through the renderer.\n */\nexport function rendererAppliesOpacity(\n layer: { type: string },\n renderer: string | undefined,\n): boolean {\n // @geolibre/core sits below @geolibre/map and cannot read its capabilities;\n // these are the renderers whose `nativeZarr` is set, which a test pins.\n // eslint-disable-next-line local/no-renderer-kind-checks -- see above\n return (renderer === \"arcgis\" || renderer === \"cesium\") && layer.type === \"zarr\";\n}\n\n/**\n * Whether `layer` is an Add Vector Layer layer GeoLibre adopted\n * (`maplibre-gl-vector-adopted`, ADOPTED_VECTOR_SOURCE_KIND in\n * @geolibre/plugins) that has no features yet. A project saves a URL- or\n * path-backed adopted layer without them, and the vector control reads them\n * again on reopen. Until then its `source.url` may name GeoParquet or\n * GeoPackage, so a renderer must wait rather than load that URL as GeoJSON.\n *\n * @param layer - A store layer.\n * @returns True while the adopted layer awaits its features.\n */\nexport function isAdoptedVectorAwaitingFeatures(layer: {\n metadata: Record<string, unknown>;\n geojson?: unknown;\n}): boolean {\n return layer.metadata.sourceKind === \"maplibre-gl-vector-adopted\" && !layer.geojson;\n}\n","/**\n * A small, safe interpreter for the subset of the QGIS expression language\n * that attribute-form setups actually use (issue #3398): default values such\n * as `now()`, `uuid()`, `\"site\" || '_' || format_date(now(), 'yyyyMMdd')` or\n * `coalesce(@position_horizontal_accuracy, @gnss_horizontal_accuracy)`, and\n * simple constraints such as `length(beekeeper) > 1`.\n *\n * Expressions are tokenized and parsed into an AST, then walked: nothing is\n * ever handed to `eval`/`Function`, so an expression that arrives inside an\n * imported QGIS project (untrusted input) can only call the functions listed\n * in {@link FUNCTIONS}. Anything outside the subset (aggregates, layer\n * lookups such as `get_feature`, geometry processing) fails with an error\n * the caller can surface instead of producing a wrong value.\n *\n * Values follow QGIS semantics where it matters for forms: an unknown\n * `@variable` is NULL rather than an error (so `coalesce(@a, @b)` works when\n * neither is set), NULL propagates through arithmetic, comparison and `||`,\n * and `AND`/`OR` use three-valued logic.\n */\nimport type { Geometry } from \"geojson\";\n\n/** Parsed QGIS expression. */\nexport type QgisExpressionNode =\n | { type: \"literal\"; value: string | number | boolean | null }\n | { type: \"field\"; name: string }\n | { type: \"variable\"; name: string }\n | { type: \"special\"; name: string }\n | { type: \"call\"; name: string; args: QgisExpressionNode[] }\n | { type: \"unary\"; op: \"-\" | \"not\"; arg: QgisExpressionNode }\n | { type: \"binary\"; op: QgisBinaryOperator; left: QgisExpressionNode; right: QgisExpressionNode }\n | { type: \"isNull\"; arg: QgisExpressionNode; negated: boolean }\n | { type: \"in\"; arg: QgisExpressionNode; list: QgisExpressionNode[]; negated: boolean }\n | {\n type: \"case\";\n branches: { when: QgisExpressionNode; then: QgisExpressionNode }[];\n otherwise: QgisExpressionNode | null;\n };\n\nexport type QgisBinaryOperator =\n | \"or\"\n | \"and\"\n | \"=\"\n | \"<>\"\n | \"<\"\n | \"<=\"\n | \">\"\n | \">=\"\n | \"like\"\n | \"ilike\"\n | \"+\"\n | \"-\"\n | \"*\"\n | \"/\"\n | \"%\"\n | \"^\"\n | \"||\";\n\n/** What an expression is evaluated against. */\nexport interface QgisExpressionContext {\n /** Attribute values of the feature being edited (`\"field\"` references). */\n properties?: Record<string, unknown>;\n /**\n * `@variable` values. Names are given without the `@`. A variable that is\n * absent (or `undefined`) evaluates to NULL, as in QGIS.\n */\n variables?: Record<string, unknown>;\n /** The feature's geometry in WGS84, for `$x`, `$y` and `$geometry`. */\n geometry?: Geometry | null;\n /** Clock for `now()`; injectable so tests are deterministic. */\n now?: () => Date;\n /** Generator for `uuid()` (bare, without braces); injectable for tests. */\n uuid?: () => string;\n /**\n * Reprojects a `[x, y]` pair for `transform(point, from, to)`. Returning\n * `null` (or leaving this unset) makes `transform` fail, so a default that\n * needs a CRS the host cannot resolve is skipped instead of storing\n * unprojected coordinates under a projected field.\n */\n transform?: (point: [number, number], from: string, to: string) => [number, number] | null;\n}\n\nexport type QgisExpressionResult = { ok: true; value: unknown } | { ok: false; error: string };\n\nexport type QgisExpressionParseResult =\n | { ok: true; node: QgisExpressionNode }\n | { ok: false; error: string };\n\ntype Token =\n | { kind: \"number\"; value: number }\n | { kind: \"string\"; value: string }\n | { kind: \"identifier\"; value: string; quoted: boolean }\n | { kind: \"variable\"; value: string }\n | { kind: \"special\"; value: string }\n | { kind: \"operator\"; value: string };\n\n/** Longest expression the parser accepts; form expressions are one-liners. */\nconst MAX_EXPRESSION_LENGTH = 4000;\n/** Nesting bound so a crafted project cannot overflow the stack. */\nconst MAX_DEPTH = 64;\n\nclass QgisExpressionError extends Error {}\n\n/** Backslash escapes QGIS string literals define. */\nconst STRING_ESCAPES = new Map([\n [\"n\", \"\\n\"],\n [\"t\", \"\\t\"],\n [\"r\", \"\\r\"],\n [\"\\\\\", \"\\\\\"],\n [\"'\", \"'\"],\n]);\n\nfunction tokenize(source: string): Token[] {\n const tokens: Token[] = [];\n let i = 0;\n const readQuoted = (quote: string): string => {\n let value = \"\";\n i += 1;\n for (;;) {\n if (i >= source.length) throw new QgisExpressionError(\"Unterminated quoted text\");\n const char = source[i];\n if (char === quote) {\n if (source[i + 1] === quote) {\n value += quote;\n i += 2;\n continue;\n }\n i += 1;\n return value;\n }\n if (char === \"\\\\\" && quote === \"'\" && i + 1 < source.length) {\n // Only the escapes QGIS defines are consumed; any other backslash is\n // kept, so a LIKE pattern such as '50\\%' still reaches the matcher\n // with its escape intact.\n const next = source[i + 1];\n const escaped = STRING_ESCAPES.get(next);\n if (escaped !== undefined) {\n value += escaped;\n i += 2;\n continue;\n }\n }\n value += char;\n i += 1;\n }\n };\n while (i < source.length) {\n const char = source[i];\n if (/\\s/.test(char)) {\n i += 1;\n continue;\n }\n if (char === \"-\" && source[i + 1] === \"-\") {\n while (i < source.length && source[i] !== \"\\n\") i += 1;\n continue;\n }\n if (char === \"/\" && source[i + 1] === \"*\") {\n const end = source.indexOf(\"*/\", i + 2);\n if (end < 0) throw new QgisExpressionError(\"Unterminated comment\");\n i = end + 2;\n continue;\n }\n if (char === \"'\") {\n tokens.push({ kind: \"string\", value: readQuoted(\"'\") });\n continue;\n }\n if (char === '\"') {\n tokens.push({ kind: \"identifier\", value: readQuoted('\"'), quoted: true });\n continue;\n }\n const number = /^(?:\\d+\\.?\\d*(?:[eE][+-]?\\d+)?|\\.\\d+(?:[eE][+-]?\\d+)?)/.exec(source.slice(i));\n if (number) {\n tokens.push({ kind: \"number\", value: Number(number[0]) });\n i += number[0].length;\n continue;\n }\n if (char === \"@\" || char === \"$\") {\n const name = /^[A-Za-z_][A-Za-z0-9_]*/.exec(source.slice(i + 1));\n if (!name) throw new QgisExpressionError(`Unexpected \"${char}\"`);\n tokens.push({ kind: char === \"@\" ? \"variable\" : \"special\", value: name[0] });\n i += 1 + name[0].length;\n continue;\n }\n const word = /^[A-Za-z_À-][A-Za-z0-9_À-]*/.exec(source.slice(i));\n if (word) {\n tokens.push({ kind: \"identifier\", value: word[0], quoted: false });\n i += word[0].length;\n continue;\n }\n const two = source.slice(i, i + 2);\n if (two === \"||\" || two === \"<>\" || two === \"!=\" || two === \"<=\" || two === \">=\") {\n tokens.push({ kind: \"operator\", value: two === \"!=\" ? \"<>\" : two });\n i += 2;\n continue;\n }\n if (\"+-*/%^=<>(),\".includes(char)) {\n tokens.push({ kind: \"operator\", value: char });\n i += 1;\n continue;\n }\n throw new QgisExpressionError(`Unexpected \"${char}\"`);\n }\n return tokens;\n}\n\nconst KEYWORDS = new Set([\n \"and\",\n \"or\",\n \"not\",\n \"is\",\n \"null\",\n \"in\",\n \"like\",\n \"ilike\",\n \"true\",\n \"false\",\n \"case\",\n \"when\",\n \"then\",\n \"else\",\n \"end\",\n]);\n\nclass Parser {\n private index = 0;\n private depth = 0;\n\n constructor(private readonly tokens: Token[]) {}\n\n parse(): QgisExpressionNode {\n const node = this.parseOr();\n if (this.index < this.tokens.length) {\n throw new QgisExpressionError(`Unexpected \"${this.describe(this.tokens[this.index])}\"`);\n }\n return node;\n }\n\n private describe(token: Token): string {\n return String(token.value);\n }\n\n private peekKeyword(): string | null {\n const token = this.tokens[this.index];\n if (token?.kind !== \"identifier\" || token.quoted) return null;\n const lower = token.value.toLowerCase();\n return KEYWORDS.has(lower) ? lower : null;\n }\n\n private acceptKeyword(keyword: string): boolean {\n if (this.peekKeyword() !== keyword) return false;\n this.index += 1;\n return true;\n }\n\n private expectKeyword(keyword: string): void {\n if (!this.acceptKeyword(keyword)) {\n throw new QgisExpressionError(`Expected ${keyword.toUpperCase()}`);\n }\n }\n\n private acceptOperator(...operators: string[]): string | null {\n const token = this.tokens[this.index];\n if (token?.kind !== \"operator\" || !operators.includes(token.value)) return null;\n this.index += 1;\n return token.value;\n }\n\n private expectOperator(operator: string): void {\n if (!this.acceptOperator(operator)) throw new QgisExpressionError(`Expected \"${operator}\"`);\n }\n\n private nested<T>(parse: () => T): T {\n this.depth += 1;\n if (this.depth > MAX_DEPTH) throw new QgisExpressionError(\"Expression is nested too deeply\");\n try {\n return parse();\n } finally {\n this.depth -= 1;\n }\n }\n\n private parseOr(): QgisExpressionNode {\n return this.nested(() => {\n let left = this.parseAnd();\n while (this.acceptKeyword(\"or\")) {\n left = { type: \"binary\", op: \"or\", left, right: this.parseAnd() };\n }\n return left;\n });\n }\n\n private parseAnd(): QgisExpressionNode {\n let left = this.parseNot();\n while (this.acceptKeyword(\"and\")) {\n left = { type: \"binary\", op: \"and\", left, right: this.parseNot() };\n }\n return left;\n }\n\n private parseNot(): QgisExpressionNode {\n if (this.acceptKeyword(\"not\")) {\n return this.nested(() => ({ type: \"unary\", op: \"not\", arg: this.parseNot() }));\n }\n return this.parseComparison();\n }\n\n private parseComparison(): QgisExpressionNode {\n let left = this.parseAdditive();\n for (;;) {\n const operator = this.acceptOperator(\"=\", \"<>\", \"<\", \"<=\", \">\", \">=\");\n if (operator) {\n left = {\n type: \"binary\",\n op: operator as QgisBinaryOperator,\n left,\n right: this.parseAdditive(),\n };\n continue;\n }\n if (this.acceptKeyword(\"is\")) {\n const negated = this.acceptKeyword(\"not\");\n this.expectKeyword(\"null\");\n left = { type: \"isNull\", arg: left, negated };\n continue;\n }\n const start = this.index;\n const negated = this.acceptKeyword(\"not\");\n if (this.acceptKeyword(\"in\")) {\n this.expectOperator(\"(\");\n const list: QgisExpressionNode[] = [];\n if (!this.acceptOperator(\")\")) {\n do list.push(this.parseOr());\n while (this.acceptOperator(\",\"));\n this.expectOperator(\")\");\n }\n left = { type: \"in\", arg: left, list, negated };\n continue;\n }\n const like = this.acceptKeyword(\"like\") ? \"like\" : this.acceptKeyword(\"ilike\") ? \"ilike\" : \"\";\n if (like) {\n const node: QgisExpressionNode = {\n type: \"binary\",\n op: like,\n left,\n right: this.parseAdditive(),\n };\n left = negated ? { type: \"unary\", op: \"not\", arg: node } : node;\n continue;\n }\n this.index = start;\n return left;\n }\n }\n\n private parseAdditive(): QgisExpressionNode {\n let left = this.parseMultiplicative();\n for (;;) {\n const operator = this.acceptOperator(\"+\", \"-\");\n if (!operator) return left;\n left = {\n type: \"binary\",\n op: operator as QgisBinaryOperator,\n left,\n right: this.parseMultiplicative(),\n };\n }\n }\n\n private parseMultiplicative(): QgisExpressionNode {\n let left = this.parsePower();\n for (;;) {\n const operator = this.acceptOperator(\"*\", \"/\", \"%\");\n if (!operator) return left;\n left = {\n type: \"binary\",\n op: operator as QgisBinaryOperator,\n left,\n right: this.parsePower(),\n };\n }\n }\n\n private parsePower(): QgisExpressionNode {\n const left = this.parseConcat();\n if (this.acceptOperator(\"^\")) {\n return this.nested(() => ({ type: \"binary\", op: \"^\", left, right: this.parsePower() }));\n }\n return left;\n }\n\n private parseConcat(): QgisExpressionNode {\n let left = this.parseUnary();\n while (this.acceptOperator(\"||\")) {\n left = { type: \"binary\", op: \"||\", left, right: this.parseUnary() };\n }\n return left;\n }\n\n private parseUnary(): QgisExpressionNode {\n if (this.acceptOperator(\"-\")) {\n return this.nested(() => ({ type: \"unary\", op: \"-\", arg: this.parseUnary() }));\n }\n if (this.acceptOperator(\"+\")) return this.nested(() => this.parseUnary());\n return this.parsePrimary();\n }\n\n private parsePrimary(): QgisExpressionNode {\n const token = this.tokens[this.index];\n if (!token) throw new QgisExpressionError(\"Unexpected end of expression\");\n if (token.kind === \"number\" || token.kind === \"string\") {\n this.index += 1;\n return { type: \"literal\", value: token.value };\n }\n if (token.kind === \"variable\") {\n this.index += 1;\n return { type: \"variable\", name: token.value };\n }\n if (token.kind === \"special\") {\n this.index += 1;\n return { type: \"special\", name: token.value.toLowerCase() };\n }\n if (token.kind === \"operator\") {\n if (token.value !== \"(\") throw new QgisExpressionError(`Unexpected \"${token.value}\"`);\n this.index += 1;\n const node = this.parseOr();\n this.expectOperator(\")\");\n return node;\n }\n if (token.quoted) {\n this.index += 1;\n return { type: \"field\", name: token.value };\n }\n const keyword = this.peekKeyword();\n if (keyword === \"true\" || keyword === \"false\") {\n this.index += 1;\n return { type: \"literal\", value: keyword === \"true\" };\n }\n if (keyword === \"null\") {\n this.index += 1;\n return { type: \"literal\", value: null };\n }\n if (keyword === \"case\") {\n this.index += 1;\n const branches: { when: QgisExpressionNode; then: QgisExpressionNode }[] = [];\n while (this.acceptKeyword(\"when\")) {\n const when = this.parseOr();\n this.expectKeyword(\"then\");\n branches.push({ when, then: this.parseOr() });\n }\n if (branches.length === 0) throw new QgisExpressionError(\"Expected WHEN\");\n const otherwise = this.acceptKeyword(\"else\") ? this.parseOr() : null;\n this.expectKeyword(\"end\");\n return { type: \"case\", branches, otherwise };\n }\n if (keyword) throw new QgisExpressionError(`Unexpected \"${token.value}\"`);\n this.index += 1;\n if (this.acceptOperator(\"(\")) {\n const args: QgisExpressionNode[] = [];\n if (!this.acceptOperator(\")\")) {\n do args.push(this.parseOr());\n while (this.acceptOperator(\",\"));\n this.expectOperator(\")\");\n }\n return { type: \"call\", name: token.value.toLowerCase(), args };\n }\n // A bare word that is not a call is a field reference, as in QGIS.\n return { type: \"field\", name: token.value };\n }\n}\n\n/**\n * Parse a QGIS expression into an AST.\n *\n * @param source - The expression text.\n * @returns The AST, or the first syntax error.\n */\nexport function parseQgisExpression(source: string): QgisExpressionParseResult {\n if (source.length > MAX_EXPRESSION_LENGTH) return { ok: false, error: \"Expression is too long\" };\n try {\n const tokens = tokenize(source);\n if (tokens.length === 0) return { ok: false, error: \"Expression is empty\" };\n return { ok: true, node: new Parser(tokens).parse() };\n } catch (error) {\n return { ok: false, error: error instanceof Error ? error.message : String(error) };\n }\n}\n\ninterface PointValue {\n type: \"Point\";\n coordinates: number[];\n}\n\nfunction isPoint(value: unknown): value is PointValue {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (value as { type?: unknown }).type === \"Point\" &&\n Array.isArray((value as { coordinates?: unknown }).coordinates)\n );\n}\n\nfunction isNull(value: unknown): boolean {\n return (\n value === null || value === undefined || (typeof value === \"number\" && Number.isNaN(value))\n );\n}\n\nfunction pad(value: number, width: number): string {\n return String(Math.trunc(Math.abs(value))).padStart(width, \"0\");\n}\n\n/** The local-time ISO form QGIS writes for a datetime (`2026-11-11T16:05:09`). */\nexport function formatQgisDateTime(date: Date): string {\n return `${formatQgisDate(date)}T${pad(date.getHours(), 2)}:${pad(date.getMinutes(), 2)}:${pad(date.getSeconds(), 2)}`;\n}\n\n/** The local-time ISO date (`2026-11-11`). */\nexport function formatQgisDate(date: Date): string {\n return `${pad(date.getFullYear(), 4)}-${pad(date.getMonth() + 1, 2)}-${pad(date.getDate(), 2)}`;\n}\n\nfunction toText(value: unknown): string {\n if (value instanceof Date) return formatQgisDateTime(value);\n if (typeof value === \"boolean\") return value ? \"true\" : \"false\";\n if (isPoint(value)) return `POINT(${value.coordinates.join(\" \")})`;\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n}\n\nfunction toNumber(value: unknown): number {\n if (typeof value === \"number\") return value;\n if (typeof value === \"boolean\") return value ? 1 : 0;\n if (typeof value === \"string\" && value.trim() !== \"\") {\n const parsed = Number(value);\n if (Number.isFinite(parsed)) return parsed;\n }\n throw new QgisExpressionError(`Cannot convert \"${toText(value)}\" to a number`);\n}\n\nfunction toDate(value: unknown): Date {\n if (value instanceof Date) return value;\n if (typeof value === \"string\") {\n // `new Date(\"2026-11-11\")` is midnight UTC, which is the previous day in\n // any zone west of Greenwich once read back with local getters. A value\n // without a zone is a local calendar date/time, as it is in QGIS.\n const local =\n /^\\s*(\\d{4})-(\\d{2})-(\\d{2})(?:[T ](\\d{2}):(\\d{2})(?::(\\d{2})(?:\\.(\\d{1,3}))?)?)?\\s*$/.exec(\n value,\n );\n if (local) {\n const [, year, month, day, hour = \"0\", minute = \"0\", second = \"0\", ms = \"0\"] = local;\n const parsed = new Date(\n Number(year),\n Number(month) - 1,\n Number(day),\n Number(hour),\n Number(minute),\n Number(second),\n Number(ms.padEnd(3, \"0\")),\n );\n if (!Number.isNaN(parsed.getTime())) return parsed;\n }\n }\n if (typeof value === \"string\" || typeof value === \"number\") {\n const parsed = new Date(value);\n if (!Number.isNaN(parsed.getTime())) return parsed;\n }\n throw new QgisExpressionError(`Cannot convert \"${toText(value)}\" to a date`);\n}\n\n/** Three-valued truthiness: `null` stays unknown. */\nfunction toBoolean(value: unknown): boolean | null {\n if (isNull(value)) return null;\n if (typeof value === \"boolean\") return value;\n if (typeof value === \"number\") return value !== 0;\n if (typeof value === \"string\") {\n const lower = value.trim().toLowerCase();\n if (lower === \"\" || lower === \"false\" || lower === \"0\") return false;\n return true;\n }\n return true;\n}\n\nconst MONTHS = [\n \"January\",\n \"February\",\n \"March\",\n \"April\",\n \"May\",\n \"June\",\n \"July\",\n \"August\",\n \"September\",\n \"October\",\n \"November\",\n \"December\",\n];\nconst DAYS = [\"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\"];\n\n/** Render a date with a Qt format string (`yyyy-MM-dd HH:mm:ss`, …). */\nfunction formatQtDate(date: Date, format: string): string {\n const hours = date.getHours();\n const twelveHour = /(?:ap|a)/i.test(format.replace(/'[^']*'/g, \"\"));\n const replacements: Record<string, () => string> = {\n yyyy: () => pad(date.getFullYear(), 4),\n yy: () => pad(date.getFullYear() % 100, 2),\n MMMM: () => MONTHS[date.getMonth()],\n MMM: () => MONTHS[date.getMonth()].slice(0, 3),\n MM: () => pad(date.getMonth() + 1, 2),\n M: () => String(date.getMonth() + 1),\n dddd: () => DAYS[date.getDay()],\n ddd: () => DAYS[date.getDay()].slice(0, 3),\n dd: () => pad(date.getDate(), 2),\n d: () => String(date.getDate()),\n HH: () => pad(hours, 2),\n H: () => String(hours),\n hh: () => pad(twelveHour ? hours % 12 || 12 : hours, 2),\n h: () => String(twelveHour ? hours % 12 || 12 : hours),\n mm: () => pad(date.getMinutes(), 2),\n m: () => String(date.getMinutes()),\n ss: () => pad(date.getSeconds(), 2),\n s: () => String(date.getSeconds()),\n zzz: () => pad(date.getMilliseconds(), 3),\n z: () => String(date.getMilliseconds()),\n AP: () => (hours < 12 ? \"AM\" : \"PM\"),\n ap: () => (hours < 12 ? \"am\" : \"pm\"),\n A: () => (hours < 12 ? \"AM\" : \"PM\"),\n a: () => (hours < 12 ? \"am\" : \"pm\"),\n };\n return format.replace(\n /'([^']*)'|yyyy|yy|MMMM|MMM|MM|M|dddd|ddd|dd|d|HH|H|hh|h|mm|m|ss|s|zzz|z|AP|ap|A|a/g,\n (match, quoted: string | undefined) =>\n quoted !== undefined ? quoted || \"'\" : replacements[match](),\n );\n}\n\nfunction pointOf(value: unknown, name: string): PointValue {\n if (isPoint(value)) return value;\n throw new QgisExpressionError(`${name}() needs a point`);\n}\n\nfunction defaultUuid(): string {\n const cryptoApi = (globalThis as { crypto?: Crypto }).crypto;\n if (cryptoApi?.randomUUID) return cryptoApi.randomUUID();\n // Non-secure contexts (plain-HTTP self-hosting) have no randomUUID.\n const bytes = new Uint8Array(16);\n if (cryptoApi?.getRandomValues) cryptoApi.getRandomValues(bytes);\n else for (let i = 0; i < 16; i += 1) bytes[i] = Math.floor(Math.random() * 256);\n bytes[6] = (bytes[6] & 0x0f) | 0x40;\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\n/** Longest text a function may build; a default value is a short string. */\nconst MAX_TEXT_LENGTH = 10_000;\n\n/**\n * Every text a function or `||` produces passes through here, so nested\n * calls (`replace(replace(...))`, repeated concatenation) cannot multiply a\n * short expression into an enormous string.\n */\nfunction boundedText<T>(value: T): T {\n if (typeof value === \"string\" && value.length > MAX_TEXT_LENGTH) {\n throw new QgisExpressionError(\"The text is too long\");\n }\n return value;\n}\n\nfunction padWidth(value: unknown): number {\n const width = Math.max(0, Math.trunc(toNumber(value)));\n if (width > MAX_TEXT_LENGTH) throw new QgisExpressionError(\"The padded text is too long\");\n return width;\n}\n\ntype QgisFunction = (args: unknown[], context: QgisExpressionContext) => unknown;\n\ninterface FunctionSpec {\n min: number;\n max: number;\n /** When false (the default) any NULL argument makes the result NULL. */\n handlesNull?: boolean;\n run: QgisFunction;\n}\n\nconst text = (run: (value: string, args: unknown[]) => unknown, max = 1): FunctionSpec => ({\n min: 1,\n max,\n run: (args) => run(toText(args[0]), args),\n});\n\nconst numeric = (run: (value: number) => number): FunctionSpec => ({\n min: 1,\n max: 1,\n run: (args) => run(toNumber(args[0])),\n});\n\n/** Every callable function; the whole surface an imported expression can reach. */\nconst FUNCTIONS: Record<string, FunctionSpec> = {\n now: { min: 0, max: 0, run: (_args, context) => context.now?.() ?? new Date() },\n uuid: {\n min: 0,\n max: 1,\n run: (args, context) => {\n const id = context.uuid?.() ?? defaultUuid();\n const format = args.length > 0 ? toText(args[0]).toLowerCase() : \"withbraces\";\n if (format === \"withoutbraces\") return id;\n if (format === \"id128\") return id.replaceAll(\"-\", \"\");\n return `{${id}}`;\n },\n },\n coalesce: {\n min: 0,\n max: Infinity,\n handlesNull: true,\n run: (args) => args.find((arg) => !isNull(arg)) ?? null,\n },\n concat: {\n min: 0,\n max: Infinity,\n handlesNull: true,\n run: (args) => args.map((arg) => (isNull(arg) ? \"\" : toText(arg))).join(\"\"),\n },\n if: {\n min: 3,\n max: 3,\n handlesNull: true,\n run: (args) => (toBoolean(args[0]) === true ? args[1] : args[2]),\n },\n nullif: {\n min: 2,\n max: 2,\n handlesNull: true,\n run: (args) => (compare(args[0], args[1]) === 0 ? null : args[0]),\n },\n to_string: { min: 1, max: 1, run: (args) => toText(args[0]) },\n to_int: { min: 1, max: 1, run: (args) => Math.trunc(toNumber(args[0])) },\n to_real: { min: 1, max: 1, run: (args) => toNumber(args[0]) },\n to_date: { min: 1, max: 1, run: (args) => formatQgisDate(toDate(args[0])) },\n to_datetime: { min: 1, max: 1, run: (args) => toDate(args[0]) },\n upper: text((value) => value.toUpperCase()),\n lower: text((value) => value.toLowerCase()),\n trim: text((value) => value.trim()),\n title: text((value) =>\n value.replace(/\\S+/g, (word) => word[0].toUpperCase() + word.slice(1).toLowerCase()),\n ),\n length: text((value) => value.length),\n char_length: text((value) => value.length),\n left: text((value, args) => value.slice(0, Math.max(0, toNumber(args[1]))), 2),\n right: text((value, args) => {\n const count = Math.max(0, toNumber(args[1]));\n return count === 0 ? \"\" : value.slice(-count);\n }, 2),\n substr: {\n min: 2,\n max: 3,\n run: (args) => {\n const value = toText(args[0]);\n // 1-based start; a negative start counts from the end, as in QGIS.\n const position = Math.trunc(toNumber(args[1]));\n const start = position > 0 ? position - 1 : Math.max(0, value.length + position);\n if (args.length < 3) return value.slice(start);\n return value.slice(start, start + Math.max(0, Math.trunc(toNumber(args[2]))));\n },\n },\n replace: {\n min: 3,\n max: 3,\n run: (args) => {\n // Splitting on \"\" would insert the replacement between every character;\n // QGIS leaves the text as it is.\n const search = toText(args[1]);\n return search ? toText(args[0]).split(search).join(toText(args[2])) : toText(args[0]);\n },\n },\n lpad: {\n min: 3,\n max: 3,\n run: (args) => {\n const width = padWidth(args[1]);\n return toText(args[0])\n .slice(0, width)\n .padStart(width, toText(args[2]) || \" \");\n },\n },\n rpad: {\n min: 3,\n max: 3,\n run: (args) => {\n const width = padWidth(args[1]);\n return toText(args[0])\n .slice(0, width)\n .padEnd(width, toText(args[2]) || \" \");\n },\n },\n abs: numeric(Math.abs),\n floor: numeric(Math.floor),\n ceil: numeric(Math.ceil),\n sqrt: numeric(Math.sqrt),\n round: {\n min: 1,\n max: 2,\n run: (args) => {\n // Half away from zero, as QGIS rounds; Math.round sends -2.5 to -2.\n const factor = 10 ** (args.length > 1 ? Math.trunc(toNumber(args[1])) : 0);\n const scaled = toNumber(args[0]) * factor;\n return (Math.sign(scaled) * Math.round(Math.abs(scaled))) / factor;\n },\n },\n min: { min: 1, max: Infinity, run: (args) => Math.min(...args.map(toNumber)) },\n max: { min: 1, max: Infinity, run: (args) => Math.max(...args.map(toNumber)) },\n format_number: {\n min: 1,\n max: 2,\n run: (args) => toNumber(args[0]).toFixed(args.length > 1 ? Math.trunc(toNumber(args[1])) : 0),\n },\n format_date: {\n min: 2,\n max: 2,\n run: (args) => formatQtDate(toDate(args[0]), toText(args[1])),\n },\n year: { min: 1, max: 1, run: (args) => toDate(args[0]).getFullYear() },\n month: { min: 1, max: 1, run: (args) => toDate(args[0]).getMonth() + 1 },\n day: { min: 1, max: 1, run: (args) => toDate(args[0]).getDate() },\n hour: { min: 1, max: 1, run: (args) => toDate(args[0]).getHours() },\n minute: { min: 1, max: 1, run: (args) => toDate(args[0]).getMinutes() },\n second: { min: 1, max: 1, run: (args) => toDate(args[0]).getSeconds() },\n epoch: { min: 1, max: 1, run: (args) => toDate(args[0]).getTime() },\n x: { min: 1, max: 1, run: (args) => pointOf(args[0], \"x\").coordinates[0] ?? null },\n y: { min: 1, max: 1, run: (args) => pointOf(args[0], \"y\").coordinates[1] ?? null },\n z: { min: 1, max: 1, run: (args) => pointOf(args[0], \"z\").coordinates[2] ?? null },\n make_point: {\n min: 2,\n max: 3,\n run: (args): PointValue => ({ type: \"Point\", coordinates: args.map(toNumber) }),\n },\n transform: {\n min: 3,\n max: 3,\n run: (args, context): PointValue => {\n const point = pointOf(args[0], \"transform\");\n const [x, y, ...rest] = point.coordinates;\n const from = toText(args[1]);\n const to = toText(args[2]);\n const projected =\n from.toUpperCase() === to.toUpperCase()\n ? ([x, y] as [number, number])\n : (context.transform?.([x, y], from, to) ?? null);\n if (!projected || !projected.every(Number.isFinite)) {\n throw new QgisExpressionError(`Cannot transform from ${from} to ${to}`);\n }\n return { type: \"Point\", coordinates: [...projected, ...rest] };\n },\n },\n // attribute('name'): the current feature's own attribute. The two-argument\n // form takes a feature fetched from another layer, which is out of scope.\n attribute: {\n min: 1,\n max: 1,\n run: (args, context) => {\n const name = toText(args[0]);\n return context.properties && Object.prototype.hasOwnProperty.call(context.properties, name)\n ? (context.properties[name] ?? null)\n : null;\n },\n },\n};\n\n/** Alternate spellings QGIS accepts for the same function. */\nconst FUNCTION_ALIASES: Record<string, string> = {\n tostring: \"to_string\",\n toint: \"to_int\",\n toreal: \"to_real\",\n todate: \"to_date\",\n todatetime: \"to_datetime\",\n};\n\nfunction resolveFunction(name: string): FunctionSpec | undefined {\n const canonical = FUNCTION_ALIASES[name] ?? name;\n return Object.prototype.hasOwnProperty.call(FUNCTIONS, canonical)\n ? FUNCTIONS[canonical]\n : undefined;\n}\n\n/** -1/0/1 ordering; numbers numerically when both sides are numeric. */\nfunction compare(left: unknown, right: unknown): number {\n const a = left instanceof Date ? left.getTime() : left;\n const b = right instanceof Date ? right.getTime() : right;\n if (typeof a === \"boolean\" || typeof b === \"boolean\") {\n const x = toBoolean(a) ? 1 : 0;\n const y = toBoolean(b) ? 1 : 0;\n return x === y ? 0 : x < y ? -1 : 1;\n }\n const numericPair =\n (typeof a === \"number\" ||\n (typeof a === \"string\" && a.trim() !== \"\" && Number.isFinite(Number(a)))) &&\n (typeof b === \"number\" ||\n (typeof b === \"string\" && b.trim() !== \"\" && Number.isFinite(Number(b)))) &&\n (typeof a === \"number\" || typeof b === \"number\");\n if (numericPair) {\n const x = Number(a);\n const y = Number(b);\n return x === y ? 0 : x < y ? -1 : 1;\n }\n const x = toText(a);\n const y = toText(b);\n return x === y ? 0 : x < y ? -1 : 1;\n}\n\n/**\n * SQL LIKE: `%` matches any run of characters, `_` exactly one, and a\n * backslash makes the next character literal. Matched with the two-pointer\n * wildcard walk rather than a generated `RegExp`, so a pattern from an\n * imported project cannot trigger catastrophic backtracking.\n */\nfunction matchesLike(text: string, pattern: string, caseInsensitive: boolean): boolean {\n const tokens: { kind: \"any\" | \"one\" | \"char\"; char?: string }[] = [];\n for (let i = 0; i < pattern.length; i += 1) {\n const char = pattern[i];\n if (char === \"\\\\\" && i + 1 < pattern.length) {\n i += 1;\n tokens.push({ kind: \"char\", char: pattern[i] });\n } else if (char === \"%\") {\n if (tokens[tokens.length - 1]?.kind !== \"any\") tokens.push({ kind: \"any\" });\n } else if (char === \"_\") tokens.push({ kind: \"one\" });\n else tokens.push({ kind: \"char\", char });\n }\n const subject = caseInsensitive ? text.toLowerCase() : text;\n const same = (token: { char?: string }, char: string) =>\n (caseInsensitive ? token.char!.toLowerCase() : token.char) === char;\n let t = 0;\n let p = 0;\n let star = -1;\n let mark = 0;\n while (t < subject.length) {\n const token = tokens[p];\n if (token && (token.kind === \"one\" || (token.kind === \"char\" && same(token, subject[t])))) {\n t += 1;\n p += 1;\n } else if (token?.kind === \"any\") {\n star = p;\n mark = t;\n p += 1;\n } else if (star >= 0) {\n p = star + 1;\n mark += 1;\n t = mark;\n } else {\n return false;\n }\n }\n while (tokens[p]?.kind === \"any\") p += 1;\n return p === tokens.length;\n}\n\nfunction evaluateNode(node: QgisExpressionNode, context: QgisExpressionContext): unknown {\n switch (node.type) {\n case \"literal\":\n return node.value;\n case \"field\": {\n const properties = context.properties;\n if (properties && Object.prototype.hasOwnProperty.call(properties, node.name)) {\n return properties[node.name] ?? null;\n }\n return null;\n }\n case \"variable\": {\n const variables = context.variables;\n if (variables && Object.prototype.hasOwnProperty.call(variables, node.name)) {\n return variables[node.name] ?? null;\n }\n return null;\n }\n case \"special\": {\n const geometry = context.geometry ?? null;\n if (node.name === \"geometry\") return geometry;\n if (node.name === \"x\" || node.name === \"y\") {\n if (!isPoint(geometry)) return null;\n return geometry.coordinates[node.name === \"x\" ? 0 : 1] ?? null;\n }\n if (node.name === \"now\") return context.now?.() ?? new Date();\n throw new QgisExpressionError(`$${node.name} is not supported`);\n }\n case \"call\": {\n const spec = resolveFunction(node.name);\n if (!spec) throw new QgisExpressionError(`The function ${node.name}() is not supported`);\n if (node.args.length < spec.min || node.args.length > spec.max) {\n throw new QgisExpressionError(`Wrong number of arguments for ${node.name}()`);\n }\n // Only the chosen branch runs, as in QGIS, so a guarded branch that\n // would fail (`if(\"a\" is null, 0, to_int(\"a\"))`) cannot sink the result.\n if (node.name === \"if\") {\n const chosen = toBoolean(evaluateNode(node.args[0], context)) === true ? 1 : 2;\n return evaluateNode(node.args[chosen], context);\n }\n const args = node.args.map((arg) => evaluateNode(arg, context));\n if (!spec.handlesNull && args.some(isNull)) return null;\n return boundedText(spec.run(args, context));\n }\n case \"unary\": {\n const value = evaluateNode(node.arg, context);\n if (node.op === \"not\") {\n const truth = toBoolean(value);\n return truth === null ? null : !truth;\n }\n return isNull(value) ? null : -toNumber(value);\n }\n case \"isNull\":\n return isNull(evaluateNode(node.arg, context)) !== node.negated;\n case \"in\": {\n const value = evaluateNode(node.arg, context);\n if (isNull(value)) return null;\n const found = node.list.some((entry) => {\n const candidate = evaluateNode(entry, context);\n return !isNull(candidate) && compare(value, candidate) === 0;\n });\n return found !== node.negated;\n }\n case \"case\": {\n for (const branch of node.branches) {\n if (toBoolean(evaluateNode(branch.when, context)) === true) {\n return evaluateNode(branch.then, context);\n }\n }\n return node.otherwise ? evaluateNode(node.otherwise, context) : null;\n }\n case \"binary\":\n return evaluateBinary(node, context);\n }\n}\n\nfunction evaluateBinary(\n node: Extract<QgisExpressionNode, { type: \"binary\" }>,\n context: QgisExpressionContext,\n): unknown {\n if (node.op === \"and\" || node.op === \"or\") {\n const left = toBoolean(evaluateNode(node.left, context));\n if (node.op === \"and\" && left === false) return false;\n if (node.op === \"or\" && left === true) return true;\n const right = toBoolean(evaluateNode(node.right, context));\n if (node.op === \"and\") return right === false ? false : left === null ? null : right;\n return right === true ? true : left === null ? null : right;\n }\n const left = evaluateNode(node.left, context);\n const right = evaluateNode(node.right, context);\n if (isNull(left) || isNull(right)) return null;\n switch (node.op) {\n case \"||\":\n return boundedText(toText(left) + toText(right));\n case \"=\":\n return compare(left, right) === 0;\n case \"<>\":\n return compare(left, right) !== 0;\n case \"<\":\n return compare(left, right) < 0;\n case \"<=\":\n return compare(left, right) <= 0;\n case \">\":\n return compare(left, right) > 0;\n case \">=\":\n return compare(left, right) >= 0;\n case \"like\":\n case \"ilike\":\n return matchesLike(toText(left), toText(right), node.op === \"ilike\");\n case \"+\":\n // QGIS concatenates when either side is text that is not a number.\n if (\n (typeof left === \"string\" && !Number.isFinite(Number(left))) ||\n (typeof right === \"string\" && !Number.isFinite(Number(right)))\n ) {\n return toText(left) + toText(right);\n }\n return toNumber(left) + toNumber(right);\n case \"-\":\n return toNumber(left) - toNumber(right);\n case \"*\":\n return toNumber(left) * toNumber(right);\n case \"/\": {\n const divisor = toNumber(right);\n return divisor === 0 ? null : toNumber(left) / divisor;\n }\n case \"%\": {\n const divisor = toNumber(right);\n return divisor === 0 ? null : toNumber(left) % divisor;\n }\n case \"^\":\n return toNumber(left) ** toNumber(right);\n }\n}\n\n/**\n * Evaluate a QGIS expression.\n *\n * @param source - Expression text, or an AST from {@link parseQgisExpression}.\n * @param context - Field values, variables, geometry and injectable clock.\n * @returns The value (possibly `null`), or why it could not be computed.\n */\nexport function evaluateQgisExpression(\n source: string | QgisExpressionNode,\n context: QgisExpressionContext = {},\n): QgisExpressionResult {\n let node: QgisExpressionNode;\n if (typeof source === \"string\") {\n const parsed = parseQgisExpression(source);\n if (!parsed.ok) return parsed;\n node = parsed.node;\n } else {\n node = source;\n }\n try {\n const value = evaluateNode(node, context);\n return { ok: true, value: value === undefined ? null : value };\n } catch (error) {\n return { ok: false, error: error instanceof Error ? error.message : String(error) };\n }\n}\n\n/** MapLibre operators that map one-to-one onto a QGIS binary operator. */\nconst MAPLIBRE_BINARY: Partial<Record<QgisBinaryOperator, string>> = {\n \"=\": \"==\",\n \"<>\": \"!=\",\n \"<\": \"<\",\n \"<=\": \"<=\",\n \">\": \">\",\n \">=\": \">=\",\n \"+\": \"+\",\n \"-\": \"-\",\n \"*\": \"*\",\n \"/\": \"/\",\n \"%\": \"%\",\n \"^\": \"^\",\n and: \"all\",\n or: \"any\",\n};\n\ntype OperandKind = \"number\" | \"string\" | \"boolean\" | \"unknown\";\n\nfunction operandKind(node: QgisExpressionNode): OperandKind {\n if (node.type === \"literal\") {\n if (typeof node.value === \"number\") return \"number\";\n if (typeof node.value === \"string\") return \"string\";\n if (typeof node.value === \"boolean\") return \"boolean\";\n return \"unknown\";\n }\n if (node.type === \"unary\") return node.op === \"-\" ? \"number\" : \"boolean\";\n if (node.type === \"binary\") {\n if ([\"+\", \"-\", \"*\", \"/\", \"%\", \"^\"].includes(node.op)) return \"number\";\n if (node.op === \"||\") return \"string\";\n return \"boolean\";\n }\n if (node.type === \"call\") {\n if ([\"length\", \"char_length\", \"abs\", \"floor\", \"ceil\", \"round\", \"sqrt\"].includes(node.name)) {\n return \"number\";\n }\n if ([\"upper\", \"lower\", \"trim\", \"concat\", \"to_string\", \"tostring\"].includes(node.name)) {\n return \"string\";\n }\n }\n return \"unknown\";\n}\n\nfunction translate(node: QgisExpressionNode): unknown {\n switch (node.type) {\n case \"literal\":\n return node.value;\n case \"field\":\n return [\"get\", node.name];\n case \"unary\":\n return node.op === \"not\" ? [\"!\", translate(node.arg)] : [\"-\", translate(node.arg)];\n case \"isNull\": {\n // A missing property and an explicit null are both NULL to QGIS.\n const test = [\"==\", translate(node.arg), null];\n return node.negated ? [\"!\", test] : test;\n }\n case \"in\": {\n if (!node.list.every((entry) => entry.type === \"literal\" && entry.value !== null)) {\n throw new QgisExpressionError(\"IN needs literal values\");\n }\n const test = [\n \"in\",\n translate(node.arg),\n [\"literal\", node.list.map((entry) => (entry as { value: unknown }).value)],\n ];\n return node.negated ? [\"!\", test] : test;\n }\n case \"binary\": {\n if (node.op === \"||\") {\n return [\"concat\", translate(node.left), translate(node.right)];\n }\n const operator = MAPLIBRE_BINARY[node.op];\n if (!operator) throw new QgisExpressionError(`${node.op.toUpperCase()} is not supported`);\n if (operator === \"all\" || operator === \"any\") {\n return [operator, translate(node.left), translate(node.right)];\n }\n // MapLibre comparisons and arithmetic are strictly typed, while a\n // feature property is untyped: coerce a field reference to the type of\n // the literal it is compared with so `\"boxes\" <= 10` type-checks.\n const leftKind = operandKind(node.left);\n const rightKind = operandKind(node.right);\n const arithmetic = [\"+\", \"-\", \"*\", \"/\", \"%\", \"^\"].includes(operator);\n const target: OperandKind = arithmetic\n ? \"number\"\n : leftKind !== \"unknown\"\n ? leftKind\n : rightKind;\n const coerce = (side: QgisExpressionNode, kind: OperandKind): unknown => {\n const translated = translate(side);\n if (kind !== \"unknown\" || target === \"unknown\" || target === \"boolean\") return translated;\n return [target === \"number\" ? \"to-number\" : \"to-string\", translated];\n };\n return [operator, coerce(node.left, leftKind), coerce(node.right, rightKind)];\n }\n case \"call\": {\n const args = node.args.map(translate);\n const arity = (count: number): void => {\n if (args.length !== count) {\n throw new QgisExpressionError(`Wrong number of arguments for ${node.name}()`);\n }\n };\n switch (FUNCTION_ALIASES[node.name] ?? node.name) {\n case \"length\":\n case \"char_length\":\n arity(1);\n return [\"length\", [\"to-string\", [\"coalesce\", args[0], \"\"]]];\n case \"upper\":\n arity(1);\n return [\"upcase\", [\"to-string\", args[0]]];\n case \"lower\":\n arity(1);\n return [\"downcase\", [\"to-string\", args[0]]];\n case \"to_string\":\n arity(1);\n return [\"to-string\", args[0]];\n // to_int() truncates, which `to-number` does not, so it is left to the\n // default case and reported as not convertible.\n case \"to_real\":\n arity(1);\n return [\"to-number\", args[0]];\n case \"abs\":\n case \"floor\":\n case \"ceil\":\n case \"sqrt\":\n arity(1);\n return [node.name, [\"to-number\", args[0]]];\n case \"round\":\n arity(1);\n return [\"round\", [\"to-number\", args[0]]];\n case \"coalesce\":\n return [\"coalesce\", ...args];\n case \"concat\":\n return [\"concat\", ...args];\n default:\n throw new QgisExpressionError(`The function ${node.name}() is not supported`);\n }\n }\n default:\n throw new QgisExpressionError(\"This expression cannot be converted\");\n }\n}\n\n/**\n * Convert a boolean QGIS expression (a form constraint) into the MapLibre\n * expression source an {@link AttributeFormFieldConfig.constraintExpression}\n * stores. Only field references, literals, comparison, boolean logic,\n * arithmetic and a few string/number functions convert; anything else\n * (variables, dates, geometry) returns `null` so the importer can report the\n * constraint as not carried over instead of installing a wrong rule.\n *\n * @param source - QGIS constraint expression.\n * @returns JSON source of an equivalent MapLibre expression, or `null`.\n */\nexport function qgisExpressionToMapLibre(source: string): string | null {\n const parsed = parseQgisExpression(source);\n if (!parsed.ok) return null;\n try {\n const expression = translate(parsed.node);\n return Array.isArray(expression) ? JSON.stringify(expression) : null;\n } catch {\n return null;\n }\n}\n\nfunction collectUnsupported(node: QgisExpressionNode, found: Set<string>): void {\n switch (node.type) {\n case \"literal\":\n case \"field\":\n case \"variable\":\n return;\n case \"special\":\n if (![\"geometry\", \"x\", \"y\", \"now\"].includes(node.name)) found.add(`$${node.name}`);\n return;\n case \"call\":\n if (!resolveFunction(node.name)) found.add(`${node.name}()`);\n else if (\n node.args.length < resolveFunction(node.name)!.min ||\n node.args.length > resolveFunction(node.name)!.max\n ) {\n found.add(`${node.name}()`);\n }\n node.args.forEach((arg) => collectUnsupported(arg, found));\n return;\n case \"unary\":\n case \"isNull\":\n collectUnsupported(node.arg, found);\n return;\n case \"in\":\n collectUnsupported(node.arg, found);\n node.list.forEach((entry) => collectUnsupported(entry, found));\n return;\n case \"case\":\n for (const branch of node.branches) {\n collectUnsupported(branch.when, found);\n collectUnsupported(branch.then, found);\n }\n if (node.otherwise) collectUnsupported(node.otherwise, found);\n return;\n case \"binary\":\n collectUnsupported(node.left, found);\n collectUnsupported(node.right, found);\n }\n}\n\n/**\n * Check, without evaluating, whether an expression stays inside the supported\n * subset. An importer uses this to decide up front whether a default value\n * can be carried over, rather than installing one that can never produce a\n * value.\n *\n * @param source - QGIS expression text.\n * @returns `null` when the expression is usable; otherwise the syntax error,\n * or the functions and `$` variables it uses that are not supported.\n */\nexport function qgisExpressionProblem(source: string): string | null {\n const parsed = parseQgisExpression(source);\n if (!parsed.ok) return parsed.error;\n const found = new Set<string>();\n collectUnsupported(parsed.node, found);\n return found.size > 0 ? [...found].join(\", \") : null;\n}\n\n/** Bit flags {@link collectReferences} returns. */\nconst USES_NOW = 1;\nconst USES_UUID = 2;\n\nfunction collectReferences(node: QgisExpressionNode, fields: Set<string>): number {\n switch (node.type) {\n case \"literal\":\n case \"variable\":\n return 0;\n case \"special\":\n return node.name === \"now\" ? USES_NOW : 0;\n case \"field\":\n fields.add(node.name);\n return 0;\n case \"call\": {\n let volatile = node.name === \"now\" ? USES_NOW : node.name === \"uuid\" ? USES_UUID : 0;\n // attribute('name') reads a field by a name only known when it runs.\n if (node.name === \"attribute\") {\n const [name] = node.args;\n if (name?.type === \"literal\" && typeof name.value === \"string\") fields.add(name.value);\n else fields.add(\"*\");\n }\n for (const arg of node.args) volatile |= collectReferences(arg, fields);\n return volatile;\n }\n case \"unary\":\n case \"isNull\":\n return collectReferences(node.arg, fields);\n case \"in\": {\n let volatile = collectReferences(node.arg, fields);\n for (const entry of node.list) volatile |= collectReferences(entry, fields);\n return volatile;\n }\n case \"case\": {\n let volatile = 0;\n for (const branch of node.branches) {\n volatile |= collectReferences(branch.when, fields);\n volatile |= collectReferences(branch.then, fields);\n }\n if (node.otherwise) volatile |= collectReferences(node.otherwise, fields);\n return volatile;\n }\n case \"binary\":\n return collectReferences(node.left, fields) | collectReferences(node.right, fields);\n }\n}\n\n/** What an expression reads, for deciding when its value needs recomputing. */\nexport interface QgisExpressionReferences {\n /** Field names it reads; `\"*\"` when a name is only known at run time. */\n fields: string[];\n /** Whether it reads the clock (`now()`), which a caller can hold still. */\n usesNow: boolean;\n /** Whether it calls `uuid()`, whose value differs on every run. */\n usesUuid: boolean;\n}\n\n/**\n * List the fields an expression reads and whether its result changes between\n * runs with the same inputs.\n *\n * @param source - QGIS expression text.\n * @returns The references, or `null` when the expression does not parse.\n */\nexport function qgisExpressionReferences(source: string): QgisExpressionReferences | null {\n const parsed = parseQgisExpression(source);\n if (!parsed.ok) return null;\n const fields = new Set<string>();\n const flags = collectReferences(parsed.node, fields);\n return {\n fields: [...fields],\n usesNow: (flags & USES_NOW) !== 0,\n usesUuid: (flags & USES_UUID) !== 0,\n };\n}\n","/**\n * Pure helpers for the Attribute Form designer (#1322): coercing raw form\n * strings into widget-typed values, validating values against per-field rules\n * (required, numeric bounds, value maps, boolean constraint expressions), and\n * evaluating conditional field visibility.\n *\n * The designer UI (layer properties → Attributes Form) authors an\n * {@link AttributeFormConfig} stored on the layer; the attribute editing\n * surfaces (attribute table inline editor, Field Collection capture form) call\n * these helpers so both enforce the same rules. Everything here is side-effect\n * free so it can be unit tested without a DOM or the app store.\n *\n * Constraint and visibility expressions are MapLibre expressions compiled and\n * evaluated through `expressions.ts` (the same engine as the Expression\n * Builder), always with `expectedType: \"boolean\"`.\n */\nimport type { Feature } from \"geojson\";\nimport { evaluateMapExpression } from \"./expressions\";\nimport {\n evaluateQgisExpression,\n parseQgisExpression,\n formatQgisDate,\n formatQgisDateTime,\n type QgisExpressionContext,\n} from \"./qgis-expression\";\nimport type {\n AttributeFormConfig,\n AttributeFormFieldConfig,\n AttributeFormValueMapEntry,\n AttributeFormValueRelation,\n} from \"./types\";\n\n/** Options threaded to expression evaluation (constraints, visibility). */\nexport interface AttributeFormEvalOptions {\n /** Map zoom for `[\"zoom\"]` in expressions; defaults to 0. */\n zoom?: number;\n /**\n * Constraints a data service enforces regardless of the form's\n * presentation (ArcGIS domains). A field the form hides is still checked\n * against its entry here, so a visibility rule cannot let an invalid value\n * reach the service.\n */\n serviceForm?: AttributeFormConfig;\n /**\n * Real feature backing the properties, when the caller has one — its\n * geometry feeds `[\"geometry-type\"]` and geometry-aware operators. Its\n * `properties` are ignored in favor of the candidate record being\n * validated.\n */\n feature?: Feature;\n}\n\n/** Why a field's value was rejected; codes map to localized messages. */\nexport interface AttributeFormFieldError {\n code: \"required\" | \"number\" | \"integer\" | \"text\" | \"range\" | \"valueMap\" | \"constraint\";\n /**\n * For `constraint`: the author's `constraintDescription`, or the\n * expression's own error text when it failed to evaluate.\n */\n message?: string;\n /** Bounds echoed back for `range` error messages. */\n min?: number;\n max?: number;\n}\n\n/** Outcome of validating a whole properties record against a form config. */\nexport interface AttributeFormValidation {\n ok: boolean;\n /** Field name → error for every failing, visible, configured field. */\n errors: Record<string, AttributeFormFieldError>;\n}\n\n/** Look up the configuration for a field, if any. */\nexport function getAttributeFormField(\n form: AttributeFormConfig | undefined,\n field: string,\n): AttributeFormFieldConfig | undefined {\n return form?.fields.find((entry) => entry.field === field);\n}\n\n/** The label attribute forms should show for a configured field. */\nexport function attributeFormFieldLabel(config: AttributeFormFieldConfig): string {\n return config.alias?.trim() || config.field;\n}\n\n/**\n * Parse the designer's value-map text — one entry per line, `value=label` or\n * just `value` — into entries. Blank lines and duplicate values are dropped.\n */\nexport function parseValueMapText(text: string): AttributeFormValueMapEntry[] {\n const seen = new Set<string>();\n const entries: AttributeFormValueMapEntry[] = [];\n for (const line of text.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n const eq = trimmed.indexOf(\"=\");\n const value = (eq >= 0 ? trimmed.slice(0, eq) : trimmed).trim();\n const label = eq >= 0 ? trimmed.slice(eq + 1).trim() : \"\";\n if (!value || seen.has(value)) continue;\n seen.add(value);\n entries.push(label && label !== value ? { value, label } : { value });\n }\n return entries;\n}\n\n/** Serialize value-map entries back into the designer's editable text form. */\nexport function valueMapToText(entries: readonly AttributeFormValueMapEntry[] | undefined): string {\n return (entries ?? [])\n .map((entry) =>\n entry.label && entry.label !== entry.value ? `${entry.value}=${entry.label}` : entry.value,\n )\n .join(\"\\n\");\n}\n\n/** Display label for a stored value under a value-map widget. */\nexport function valueMapLabelFor(config: AttributeFormFieldConfig, value: unknown): string {\n const text = value == null ? \"\" : String(value);\n const entry = config.valueMap?.find((candidate) => candidate.value === text);\n return entry?.label ?? text;\n}\n\n/**\n * Coerce a raw form string into the typed value the widget stores on the\n * feature. An empty/blank string means \"no value\" (`null`). A `number`/`range`\n * string that does not parse is returned verbatim so validation can flag it\n * instead of silently dropping the input.\n */\nexport function coerceAttributeFormValue(config: AttributeFormFieldConfig, raw: string): unknown {\n const trimmed = raw.trim();\n if (trimmed === \"\") return null;\n if (config.valueType) {\n // A declared type wins over the widget heuristics below. A value-map pick\n // is kept verbatim so a code with surrounding spaces still matches.\n const picked =\n config.widget === \"valueMap\" && config.valueMap?.some((entry) => entry.value === raw)\n ? raw\n : trimmed;\n if (config.valueType === \"string\") return picked;\n const parsed = Number(picked);\n return Number.isFinite(parsed) ? parsed : raw;\n }\n if (config.widget === \"number\" || config.widget === \"range\") {\n const parsed = Number(trimmed);\n return Number.isFinite(parsed) ? parsed : raw;\n }\n if (config.widget === \"checkbox\") {\n const normalized = trimmed.toLowerCase();\n if (normalized === \"true\") return true;\n if (normalized === \"false\") return false;\n return raw;\n }\n // A value map whose entries are all canonical numeric codes stores numbers,\n // so an edited row keeps the same property type as untouched rows (strict-\n // equality style/filter expressions would otherwise silently miss edited\n // features). Canonical means the string round-trips through Number (\"1\",\n // \"-2\", \"3.5\") — zero-padded or exotic forms like \"01\" or \"1e3\" are\n // identifiers, not numbers, and stay strings so the membership check in\n // validateAttributeFormField keeps matching the entry verbatim.\n if (\n config.widget === \"valueMap\" &&\n config.valueMap?.length &&\n config.valueMap.every((entry) => String(Number(entry.value)) === entry.value)\n ) {\n const parsed = Number(trimmed);\n if (Number.isFinite(parsed)) return parsed;\n }\n // text, date (ISO yyyy-mm-dd string), and valueMap store the string verbatim.\n return trimmed;\n}\n\n/**\n * Whether a configured field is currently shown, given the form's candidate\n * properties. An empty, invalid, or erroring visibility expression fails open\n * (visible) so a typo cannot hide data entry.\n */\nexport function isAttributeFormFieldVisible(\n config: AttributeFormFieldConfig,\n properties: Record<string, unknown>,\n options: AttributeFormEvalOptions = {},\n): boolean {\n const source = config.visibilityExpression?.trim();\n if (!source) return true;\n const result = evaluateMapExpression(source, {\n feature: featureForEvaluation(properties, options.feature),\n zoom: options.zoom,\n expectedType: \"boolean\",\n });\n return result.kind === \"value\" ? result.value === true : true;\n}\n\n/**\n * Render a computed default as the raw string a form input holds, matching\n * what {@link coerceAttributeFormValue} parses back for the field's widget.\n */\nfunction defaultValueText(config: AttributeFormFieldConfig, value: unknown): string | null {\n if (value == null) return null;\n if (config.widget === \"checkbox\") {\n if (typeof value === \"boolean\") return value ? \"true\" : \"false\";\n if (typeof value === \"number\") return value !== 0 ? \"true\" : \"false\";\n const lower = String(value).trim().toLowerCase();\n return lower === \"true\" || lower === \"1\"\n ? \"true\"\n : lower === \"false\" || lower === \"0\"\n ? \"false\"\n : null;\n }\n if (value instanceof Date) {\n return config.widget === \"date\" ? formatQgisDate(value) : formatQgisDateTime(value);\n }\n if (typeof value === \"number\") return Number.isFinite(value) ? String(value) : null;\n if (typeof value === \"object\") return null;\n const text = String(value);\n // A date input only accepts yyyy-mm-dd, so trim a datetime default to it.\n if (config.widget === \"date\") return /^\\d{4}-\\d{2}-\\d{2}/.exec(text)?.[0] ?? null;\n return text;\n}\n\n/**\n * Compute the default values a form pre-fills for a new feature. Fields are\n * evaluated in form order and each result is visible to later expressions, so\n * a default can build on an earlier one (a photo name built from an ID that\n * is itself defaulted).\n *\n * @param form - The layer's form config.\n * @param context - Variables, geometry and clock for the expressions; its\n * `properties` seed the record (values the user already entered).\n * @param options.skip - Fields to leave alone, e.g. ones the user has edited.\n * @returns Field name to raw form string, only for fields whose default\n * evaluated to a non-null value.\n */\nexport function computeAttributeFormDefaults(\n form: AttributeFormConfig | undefined,\n context: QgisExpressionContext = {},\n options: { skip?: ReadonlySet<string> } = {},\n): Record<string, string> {\n const defaults: Record<string, string> = {};\n const properties: Record<string, unknown> = { ...context.properties };\n for (const config of form?.fields ?? []) {\n const source = config.defaultValue?.trim();\n if (!source || options.skip?.has(config.field)) continue;\n const result = evaluateQgisExpression(source, { ...context, properties });\n if (!result.ok) continue;\n const text = defaultValueText(config, result.value);\n if (text === null) continue;\n defaults[config.field] = text;\n properties[config.field] = coerceAttributeFormValue(config, text);\n }\n return defaults;\n}\n\n/**\n * Validate one configured field against the candidate properties record (the\n * field's own candidate value lives at `properties[config.field]`). Returns\n * `null` when the value passes. Checks run in order: required, widget typing,\n * numeric bounds, value-map membership, then the constraint expression — the\n * constraint sees the whole record, so cross-field rules work.\n */\nexport function validateAttributeFormField(\n config: AttributeFormFieldConfig,\n properties: Record<string, unknown>,\n options: AttributeFormEvalOptions = {},\n): AttributeFormFieldError | null {\n const value = properties[config.field];\n const empty = value == null || value === \"\";\n\n if (empty) {\n // An unchecked checkbox is a valid false-like state, not a missing value —\n // requiring it would block every save with a confusing \"value required\".\n if (config.required && config.widget !== \"checkbox\") return { code: \"required\" };\n } else {\n if (config.valueType === \"string\" && typeof value !== \"string\") return { code: \"text\" };\n if (config.valueType === \"number\" || config.valueType === \"integer\") {\n if (typeof value !== \"number\" || !Number.isFinite(value)) return { code: \"number\" };\n if (config.valueType === \"integer\" && !Number.isSafeInteger(value))\n return { code: \"integer\" };\n }\n if (config.widget === \"number\" || config.widget === \"range\") {\n const numeric = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(numeric)) return { code: \"number\" };\n if (\n (config.min != null && numeric < config.min) ||\n (config.max != null && numeric > config.max)\n ) {\n return { code: \"range\", min: config.min, max: config.max };\n }\n }\n if (\n config.widget === \"valueMap\" &&\n config.valueMap?.length &&\n !config.valueMap.some((entry) => entry.value === String(value))\n ) {\n return { code: \"valueMap\" };\n }\n }\n\n const source = config.constraintExpression?.trim();\n if (source) {\n const result = evaluateMapExpression(source, {\n feature: featureForEvaluation(properties, options.feature),\n zoom: options.zoom,\n expectedType: \"boolean\",\n });\n if (result.kind === \"error\") {\n // An unevaluable constraint cannot be verified, so the value is\n // rejected — surfacing the compile/runtime message rather than\n // silently accepting data the author meant to gate.\n return { code: \"constraint\", message: (result.errors ?? []).join(\"; \") };\n }\n if (result.kind === \"value\" && result.value !== true) {\n return {\n code: \"constraint\",\n ...(config.constraintDescription ? { message: config.constraintDescription } : {}),\n };\n }\n }\n\n return null;\n}\n\n/**\n * Validate a whole candidate properties record against a form config. Hidden\n * fields (visibility expression currently false) skip the form's own rules —\n * a field the form does not show cannot block a save — but are still checked\n * against `options.serviceForm`, whose constraints the data service enforces.\n */\nexport function validateAttributeFormValues(\n form: AttributeFormConfig | undefined,\n properties: Record<string, unknown>,\n options: AttributeFormEvalOptions = {},\n): AttributeFormValidation {\n const errors: Record<string, AttributeFormFieldError> = {};\n const checked = new Set<string>();\n for (const config of form?.fields ?? []) {\n if (!isAttributeFormFieldVisible(config, properties, options)) continue;\n checked.add(config.field);\n const error = validateAttributeFormField(config, properties, options);\n if (error) errors[config.field] = error;\n }\n for (const config of options.serviceForm?.fields ?? []) {\n if (checked.has(config.field)) continue;\n const error = validateAttributeFormField(config, properties, options);\n if (error) errors[config.field] = error;\n }\n return { ok: Object.keys(errors).length === 0, errors };\n}\n\n/**\n * Combine a layer's designer-authored form with the constraints its data\n * service publishes (for example ArcGIS coded-value and range domains) into\n * the form the editing surfaces use. The author's presentation choices\n * (alias, visibility, constraint expression, a narrower value map or bounds)\n * survive, but never widen what the service accepts: a service value map\n * forces the value-map widget and keeps only author entries the service also\n * lists (all service entries when none overlap), bounds take the tighter of\n * each side, and the service's declared value type always applies.\n *\n * @param form - The layer's designer-authored config, if any.\n * @param service - Service-derived configs, if the layer has any.\n * @returns The effective config, or `form` itself when there is no service form.\n */\nexport function mergeAttributeFormConfigs(\n form: AttributeFormConfig | undefined,\n service: AttributeFormConfig | undefined,\n): AttributeFormConfig | undefined {\n if (!service?.fields.length) return form;\n const serviceFields = new Map(service.fields.map((entry) => [entry.field, entry]));\n const fields: AttributeFormFieldConfig[] = [];\n for (const own of form?.fields ?? []) {\n const constraint = serviceFields.get(own.field);\n serviceFields.delete(own.field);\n fields.push(constraint ? mergeAttributeFormField(own, constraint) : own);\n }\n fields.push(...serviceFields.values());\n return { fields };\n}\n\nfunction mergeAttributeFormField(\n own: AttributeFormFieldConfig,\n service: AttributeFormFieldConfig,\n): AttributeFormFieldConfig {\n const merged: AttributeFormFieldConfig = { ...own, valueType: service.valueType };\n if (!own.alias?.trim() && service.alias) merged.alias = service.alias;\n if (service.required) merged.required = true;\n if (service.widget === \"valueMap\" && service.valueMap) {\n const allowed = new Map(service.valueMap.map((entry) => [entry.value, entry]));\n const narrowed =\n own.widget === \"valueMap\"\n ? (own.valueMap ?? [])\n .filter((entry) => allowed.has(entry.value))\n .map((entry) => ({\n value: entry.value,\n label: entry.label ?? allowed.get(entry.value)!.label,\n }))\n : [];\n merged.widget = \"valueMap\";\n merged.valueMap = narrowed.length ? narrowed : service.valueMap;\n delete merged.min;\n delete merged.max;\n delete merged.step;\n return merged;\n }\n if (own.widget !== \"number\" && own.widget !== \"range\") {\n merged.widget = service.widget;\n delete merged.valueMap;\n }\n merged.min = tighter(own.min, service.min, Math.max);\n merged.max = tighter(own.max, service.max, Math.min);\n if (merged.min === undefined) delete merged.min;\n if (merged.max === undefined) delete merged.max;\n return merged;\n}\n\nfunction tighter(\n a: number | undefined,\n b: number | undefined,\n pick: (a: number, b: number) => number,\n): number | undefined {\n if (a == null) return b;\n if (b == null) return a;\n return pick(a, b);\n}\n\n/**\n * Build the feature handed to expression evaluation: the caller's real\n * feature geometry when available (so `[\"geometry-type\"]` works), always with\n * the candidate properties being validated.\n */\nfunction featureForEvaluation(\n properties: Record<string, unknown>,\n feature: Feature | undefined,\n): Feature {\n return {\n type: \"Feature\",\n geometry: feature?.geometry ?? { type: \"Point\", coordinates: [0, 0] },\n ...(feature?.id !== undefined ? { id: feature.id } : {}),\n properties: properties as Feature[\"properties\"],\n };\n}\n\n/** Entries read from a referenced layer, and how their stored values are typed. */\nexport interface RelationValueMap {\n entries: AttributeFormValueMapEntry[];\n /**\n * `\"string\"` when the key column holds text, so a code such as `\"01\"` is\n * stored as text like the rows already in the layer; `\"number\"` when every\n * key is numeric.\n */\n valueType: \"string\" | \"number\";\n}\n\n/**\n * Turn a referenced layer's rows into dropdown entries.\n *\n * @param rows - The referenced rows, as plain property records.\n * @param relation - Which properties hold the stored value and the label.\n * @param options.maxEntries - Stop once more than this many distinct entries\n * are found; the result is then longer than the limit and unsorted.\n * @returns Every distinct entry, sorted by label. The caller decides what to\n * do with a very long list.\n */\nexport function buildRelationValueMap(\n rows: Iterable<Record<string, unknown> | null | undefined>,\n relation: Omit<AttributeFormValueRelation, \"layerId\">,\n options: { maxEntries?: number } = {},\n): RelationValueMap {\n const seen = new Set<string>();\n const entries: AttributeFormValueMapEntry[] = [];\n // Parsed once: the same expression labels every row.\n // Also the fallback for a row whose label column is empty.\n const source = relation.labelExpression?.trim();\n const parsed = source ? parseQgisExpression(source) : null;\n const expression = parsed?.ok ? parsed.node : null;\n let numeric = true;\n for (const row of rows) {\n const raw = row?.[relation.keyField];\n if (!row || raw == null || raw === \"\") continue;\n const value = String(raw);\n if (seen.has(value)) continue;\n seen.add(value);\n if (typeof raw !== \"number\" && typeof raw !== \"bigint\") numeric = false;\n let label: unknown = relation.labelField ? row[relation.labelField] : undefined;\n if (label == null && expression) {\n const result = evaluateQgisExpression(expression, { properties: row });\n if (result.ok) label = result.value;\n }\n const text = label == null ? \"\" : String(label).trim();\n entries.push(text && text !== value ? { value, label: text } : { value });\n // Past the caller's limit the list is not going to be used: stop before\n // labelling and sorting the rest of a very long table.\n if (options.maxEntries !== undefined && entries.length > options.maxEntries) {\n return { entries, valueType: numeric ? \"number\" : \"string\" };\n }\n }\n entries.sort((a, b) =>\n (a.label ?? a.value).localeCompare(b.label ?? b.value, undefined, { numeric: true }),\n );\n return { entries, valueType: numeric ? \"number\" : \"string\" };\n}\n\n/** Most entries a live relation dropdown shows; a longer list stays as it was. */\nexport const MAX_RELATION_ENTRIES = 1000;\n\n/**\n * Fill relation dropdowns from the layers they reference.\n *\n * @param form - The layer's form config.\n * @param rowsOf - The current feature properties of a layer by id, or\n * `undefined` when the layer is absent or its features have not loaded.\n * @returns A form whose relation fields carry entries read from the\n * referenced layer. A field whose layer is unavailable, or too long for a\n * list, keeps the entries it already had; one whose layer is loaded but\n * empty has none. Returns `form` itself when\n * nothing was resolved.\n */\nexport function resolveAttributeFormRelations(\n form: AttributeFormConfig | undefined,\n rowsOf: (layerId: string) => Iterable<Record<string, unknown> | null> | undefined,\n): AttributeFormConfig | undefined {\n if (!form?.fields.some((field) => field.valueRelation)) return form;\n let changed = false;\n const fields = form.fields.map((field) => {\n const relation = field.valueRelation;\n const rows = relation ? rowsOf(relation.layerId) : undefined;\n if (!relation || !rows) return field;\n const { entries, valueType } = buildRelationValueMap(rows, relation, {\n maxEntries: MAX_RELATION_ENTRIES,\n });\n if (entries.length > MAX_RELATION_ENTRIES) return field;\n changed = true;\n // The layer is loaded and has nothing to offer: the import-time snapshot\n // would list keys that no longer exist, so it is dropped rather than kept.\n if (entries.length === 0) return { ...field, valueMap: [] };\n return {\n ...field,\n widget: \"valueMap\" as const,\n valueMap: entries,\n valueType: field.valueType ?? valueType,\n };\n });\n return changed ? { ...form, fields } : form;\n}\n\n/**\n * `metadata` flag on a layer that is a table: its features have attributes\n * but no geometry (a lookup list, or child records linked to another layer's\n * features). It draws nothing; its rows show in the attribute table.\n */\nexport const ATTRIBUTE_TABLE_FLAG = \"attributeTable\";\n\n/** Whether a layer is a geometry-less table (see {@link ATTRIBUTE_TABLE_FLAG}). */\nexport function isAttributeTableLayer(layer: {\n metadata?: Record<string, unknown> | null;\n}): boolean {\n return layer.metadata?.[ATTRIBUTE_TABLE_FLAG] === true;\n}\n","/**\n * Map tools that own the map's clicks while they draw (the Measure control\n * placing points, the Geo Editor sketching a shape) register a claim here, and\n * the Identify click handlers stand down while any claim holds. Without it a\n * click meant as a measure vertex also opens an Identify popup over the layer\n * underneath (issue #3237).\n *\n * A claim is a predicate read at click time rather than a flag the tool sets,\n * so a tool whose drawing state changes without an event (Measure stops\n * drawing silently when its panel collapses) never leaves a stale claim.\n */\nconst claims = new Set<() => boolean>();\n\n/**\n * Register a tool whose drawing state claims the map's clicks.\n *\n * Args:\n * isClaiming: Returns true while the tool is drawing and owns map clicks.\n *\n * Returns:\n * A function that removes the claim.\n */\nexport function registerMapClickClaim(isClaiming: () => boolean): () => void {\n // A fresh entry per registration, so two tools sharing one predicate keep\n // independent claims and releasing one leaves the other in place.\n const entry = () => isClaiming();\n claims.add(entry);\n return () => {\n claims.delete(entry);\n };\n}\n\n/**\n * Whether a registered map tool currently owns the map's clicks.\n *\n * Returns:\n * True when any registered claim reports that its tool is drawing.\n */\nexport function isMapClickClaimed(): boolean {\n for (const isClaiming of claims) {\n try {\n if (isClaiming()) return true;\n } catch (error) {\n // A tool torn down mid-read reports no claim rather than breaking\n // Identify for every other click; warn so a broken predicate (an\n // upstream API change, say) does not fail silently.\n console.warn(\"[GeoLibre] A map click claim could not be read\", error);\n }\n }\n return false;\n}\n","/**\n * Pure helpers for the popup and tooltip designer (#2113): resolving which\n * fields a layer's Identify popup (or hover tooltip) shows and in what order,\n * and formatting each value the way the author asked for.\n *\n * The designer UI (Style panel → Popup) authors a {@link LayerPopupConfig} on\n * the layer; `MapCanvas` renders the popup from these helpers so the click\n * popup and the hover tooltip agree on labels, order and formatting.\n * Everything here is side-effect free so it can be unit tested without a DOM\n * or the app store.\n *\n * Two rules the rest of the app depends on:\n *\n * - {@link GeoLibreLayer.fieldVisibility} is authoritative. A field marked\n * `\"hidden\"` or `\"excluded\"` never reaches a popup, even when a popup config\n * names it explicitly — the config selects from what is visible, it cannot\n * re-expose what the author hid.\n * - A layer with no config behaves exactly as it did before the designer\n * existed: every property, in the feature's own key order, unformatted.\n */\nimport type { Feature } from \"geojson\";\nimport { compileFeatureExpression, type CompiledFeatureExpression } from \"./expressions\";\nimport { PHOTO_FULL_PROPERTY } from \"./photo\";\nimport type {\n FieldVisibility,\n LayerPopupConfig,\n PopupDateFormat,\n PopupFieldConfig,\n PopupFieldFormat,\n PopupFieldKind,\n} from \"./types\";\n\n/** Match an inline base64 raster image (excludes SVG, which can carry scripts). */\nconst INLINE_IMAGE_DATA_URL = /^data:image\\/(?!svg)[\\w.+-]+;base64,/i;\n\n/** One resolved popup row, ready for the renderer. */\nexport interface PopupRow {\n /** Feature property key. */\n field: string;\n /** Label to print in the key column. */\n label: string;\n /** The raw property value, for kinds that need it (image `src`, link `href`). */\n value: unknown;\n /** The formatted value text. */\n text: string;\n /** How the renderer should draw the value. */\n kind: PopupFieldKind;\n /** Link text for a `\"link\"` row; falls back to the value. */\n linkLabel?: string;\n}\n\n/**\n * A trimmed non-empty string, or `undefined` for anything else.\n *\n * Popup configs arrive from untrusted JSON — a hand-edited `.geolibre.json`, an\n * imported layer-library bundle, an MCP-authored project — so a field typed as\n * `string` may hold a number, an object or null. `value.trim()` on one of those\n * throws and takes the whole popup render with it, so every string read here\n * goes through this.\n */\nfunction trimmedString(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const trimmed = value.trim();\n return trimmed === \"\" ? undefined : trimmed;\n}\n\n/** Whether the Identify popup should open for a layer. Default: yes. */\nexport function isPopupClickEnabled(popup: LayerPopupConfig | undefined): boolean {\n return popup?.click !== false;\n}\n\n/** Whether a hover tooltip should follow the pointer. Default: no. */\nexport function isPopupHoverEnabled(popup: LayerPopupConfig | undefined): boolean {\n return popup?.hover === true;\n}\n\n/**\n * Bounds for {@link LayerPopupConfig.maxWidth}, in CSS pixels. The floor is\n * the popup's own minimum width (`min-w-[18rem]`), below which the setting\n * would be overruled by the layout and read as broken; the ceiling keeps a\n * hand-edited project from authoring a popup that blankets the map.\n */\nexport const POPUP_MAX_WIDTH_RANGE = { min: 288, max: 1200 } as const;\n\n/**\n * Bounds for {@link LayerPopupConfig.imageHeight}, in CSS pixels. Below the\n * floor a thumbnail carries no information; the ceiling matches\n * {@link POPUP_MAX_WIDTH_RANGE}, since a taller picture than that is what the\n * lightbox is for.\n */\nexport const POPUP_IMAGE_HEIGHT_RANGE = { min: 40, max: 1200 } as const;\n\n/**\n * Coerce an author-supplied pixel size to a usable number, or `undefined`.\n *\n * Popup configs arrive from untrusted JSON, so the value may be a string, null\n * or NaN. Anything that is not a finite positive number means \"unset\" (keep\n * the default) rather than an error that would take the popup render down,\n * and a number outside `range` is clamped rather than dropped — a project\n * asking for a 4000px popup wants the widest one it can have.\n */\nfunction pixelSize(\n value: unknown,\n range: { readonly min: number; readonly max: number },\n): number | undefined {\n const size = typeof value === \"number\" ? value : Number(trimmedString(value) ?? NaN);\n if (!Number.isFinite(size) || size <= 0) return undefined;\n return Math.round(Math.max(range.min, Math.min(range.max, size)));\n}\n\n/**\n * The widest the click popup may draw for a layer, in CSS pixels, or\n * `undefined` to keep the renderer's default.\n */\nexport function resolvePopupMaxWidth(popup: LayerPopupConfig | undefined): number | undefined {\n return pixelSize(popup?.maxWidth, POPUP_MAX_WIDTH_RANGE);\n}\n\n/**\n * The tallest an `\"image\"` field may draw inside the popup, in CSS pixels, or\n * `undefined` to keep the stylesheet's default.\n */\nexport function resolvePopupImageHeight(popup: LayerPopupConfig | undefined): number | undefined {\n return pixelSize(popup?.imageHeight, POPUP_IMAGE_HEIGHT_RANGE);\n}\n\n/** The label a popup shows for a configured field. */\nexport function popupFieldLabel(config: PopupFieldConfig): string {\n return trimmedString(config.label) ?? config.field;\n}\n\n/**\n * Property keys that never belong in a popup: GeoLibre's own internal columns\n * and the full-resolution companion of a photo thumbnail (an internal twin of\n * a value already shown, and often multiple megabytes of data URL).\n */\nexport function isInternalPopupField(key: string): boolean {\n return key === PHOTO_FULL_PROPERTY || key.startsWith(\"__geolibre_\");\n}\n\n/**\n * The property keys a popup may show, before any popup config narrows them:\n * the feature's own keys minus the internal ones and anything the author\n * marked `\"hidden\"` or `\"excluded\"`.\n */\nexport function visiblePopupFields(\n properties: Record<string, unknown>,\n fieldVisibility?: Record<string, FieldVisibility>,\n): string[] {\n return Object.keys(properties).filter(\n (key) => !isInternalPopupField(key) && !fieldVisibility?.[key],\n );\n}\n\n/** Options for {@link formatPopupValue} and {@link resolvePopupRows}. */\nexport interface PopupFormatOptions {\n /**\n * BCP 47 tag for number/date formatting. Omitted means the runtime default,\n * which is what the browser uses for every other locale-aware rendering.\n */\n locale?: string;\n}\n\nfunction formatNumber(value: number, format: PopupFieldFormat, locale?: string): string {\n const options: Intl.NumberFormatOptions = {\n useGrouping: format.thousands === true,\n };\n if (format.decimals != null && Number.isFinite(format.decimals)) {\n const digits = Math.max(0, Math.min(20, Math.trunc(format.decimals)));\n options.minimumFractionDigits = digits;\n options.maximumFractionDigits = digits;\n }\n return new Intl.NumberFormat(locale, options).format(value);\n}\n\n/**\n * Coerce a property value to a Date. A number is epoch milliseconds (what a\n * GeoJSON timestamp column normally holds); a string goes through `Date`'s own\n * parser. Anything that does not parse comes back as `null` so the caller can\n * fall back to the raw text rather than printing \"Invalid Date\".\n */\nfunction toDate(value: unknown): Date | null {\n if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;\n if (typeof value === \"number\" && Number.isFinite(value)) {\n const fromEpoch = new Date(value);\n return Number.isNaN(fromEpoch.getTime()) ? null : fromEpoch;\n }\n if (typeof value === \"string\" && value.trim() !== \"\") {\n const parsed = new Date(value);\n return Number.isNaN(parsed.getTime()) ? null : parsed;\n }\n return null;\n}\n\nfunction formatDate(date: Date, dateFormat: PopupDateFormat, locale?: string): string {\n switch (dateFormat) {\n case \"datetime\":\n return date.toLocaleString(locale);\n case \"time\":\n return date.toLocaleTimeString(locale);\n case \"iso\":\n return date.toISOString();\n case \"year\":\n // Local, like the date/datetime/time cases above — only `iso` is\n // deliberately UTC, because that is what the format itself means. Reading\n // the year in UTC while the neighbouring formats read local would make\n // \"Year\" disagree with \"Date\" for the same timestamp near a year\n // boundary (2026-01-01T02:00:00Z is Dec 31 2025 in UTC-8).\n return String(date.getFullYear());\n case \"date\":\n default:\n return date.toLocaleDateString(locale);\n }\n}\n\n/** Stringify a value the way the untyped popup always has. */\nexport function stringifyPopupValue(value: unknown): string {\n if (value == null) return \"\";\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n}\n\n/**\n * Render one value as text under a field's kind and format. `\"auto\"` and\n * `\"image\"` values are returned verbatim (the renderer draws them itself);\n * every other kind is stringified, formatted, then wrapped in the configured\n * prefix/suffix.\n */\nexport function formatPopupValue(\n value: unknown,\n config: Pick<PopupFieldConfig, \"kind\" | \"format\"> = {},\n options: PopupFormatOptions = {},\n): string {\n const kind = config.kind ?? \"auto\";\n const format = config.format ?? {};\n if (kind === \"auto\" || kind === \"image\") return stringifyPopupValue(value);\n\n // A missing value stays missing under every kind. Without this a `number`\n // field would coerce null (and \"\") through `Number(\"\")` to a confident 0,\n // reporting a population of zero for a city that simply has no figure.\n if (value == null || (typeof value === \"string\" && value.trim() === \"\")) return \"\";\n\n let body: string;\n if (kind === \"number\") {\n const numeric = typeof value === \"number\" ? value : Number(stringifyPopupValue(value));\n body = Number.isFinite(numeric)\n ? formatNumber(numeric, format, options.locale)\n : stringifyPopupValue(value);\n } else if (kind === \"date\") {\n const date = toDate(value);\n body = date\n ? formatDate(date, format.dateFormat ?? \"date\", options.locale)\n : stringifyPopupValue(value);\n } else {\n body = stringifyPopupValue(value);\n }\n\n // An empty value gets no affixes: \"$\" alone, or a bare \" km\", reads as data\n // that is present when it is not.\n if (body === \"\") return \"\";\n return `${format.prefix ?? \"\"}${body}${format.suffix ?? \"\"}`;\n}\n\n/** Whether a row would draw as a picture rather than as text. */\nfunction rendersAsImage(kind: PopupFieldKind, value: unknown): boolean {\n return kind === \"image\" || (kind === \"auto\" && isInlineImageValue(value));\n}\n\n/**\n * The properties a popup may read, with the author's hidden and excluded\n * fields and GeoLibre's internal columns removed.\n *\n * Expressions get this rather than the raw record. `[\"get\", \"ssn\"]` in a title\n * or body would otherwise reach a column the author hid, printing it into the\n * popup — and onto the hover tooltip, which needs no click at all. The rest of\n * this module already honors `fieldVisibility` field by field; an expression\n * has no field list to filter, so the filtering has to happen to its input.\n */\nexport function visiblePopupProperties(\n properties: Record<string, unknown>,\n fieldVisibility?: Record<string, FieldVisibility>,\n): Record<string, unknown> {\n const visible: Record<string, unknown> = {};\n for (const key of visiblePopupFields(properties, fieldVisibility)) {\n visible[key] = properties[key];\n }\n return visible;\n}\n\n/** Options for {@link resolvePopupRows}. */\nexport interface ResolvePopupRowsOptions extends PopupFormatOptions {\n popup?: LayerPopupConfig;\n fieldVisibility?: Record<string, FieldVisibility>;\n /** Resolve the hover subset (fields flagged `hover`) instead of the full list. */\n hover?: boolean;\n}\n\n/**\n * The rows a popup (or hover tooltip) should render for one feature's\n * properties.\n *\n * With no `popup.fields`, every visible property is returned in the feature's\n * own key order and left untyped, which is exactly what the popup did before\n * the designer existed. With a list, only the listed fields appear, in the\n * listed order, under their labels and formats — and a listed field the\n * feature does not carry is skipped rather than printed empty, so one config\n * can cover a layer whose features have ragged properties.\n *\n * `hover: true` narrows the list to fields flagged `hover`. A hover request\n * with no flagged field returns nothing: an author who turned the tooltip on\n * without choosing a field gets no tip rather than the whole table following\n * the pointer.\n */\nexport function resolvePopupRows(\n properties: Record<string, unknown>,\n options: ResolvePopupRowsOptions = {},\n): PopupRow[] {\n const { popup, fieldVisibility, hover = false, locale } = options;\n // Untrusted JSON again: `fields` may be absent, a non-array, or hold entries\n // whose `field` is not a string. Anything unusable is dropped here so the\n // loop below can treat every entry as a real field name.\n const configured = (Array.isArray(popup?.fields) ? popup.fields : []).filter(\n (config): config is PopupFieldConfig =>\n Boolean(config) && typeof (config as PopupFieldConfig).field === \"string\",\n );\n\n if (configured.length === 0) {\n if (hover) return [];\n return visiblePopupFields(properties, fieldVisibility).map((field) => ({\n field,\n label: field,\n value: properties[field],\n text: stringifyPopupValue(properties[field]),\n kind: \"auto\" as const,\n }));\n }\n\n const rows: PopupRow[] = [];\n const seen = new Set<string>();\n for (const config of configured) {\n if (hover && config.hover !== true) continue;\n // fieldVisibility wins: a popup config can order and relabel what is\n // visible, it can never re-expose a field the author hid or excluded.\n if (fieldVisibility?.[config.field]) continue;\n if (isInternalPopupField(config.field)) continue;\n if (!(config.field in properties)) continue;\n if (seen.has(config.field)) continue;\n const value = properties[config.field];\n // A hover tip is a glance, and it re-renders on every animation frame the\n // pointer is over the feature. An image row has no useful text form — its\n // value is a multi-kilobyte data URL — so it would print that URL as the\n // tip's body, over and over. Skip it and let the click popup show the\n // picture.\n if (hover && rendersAsImage(config.kind ?? \"auto\", value)) continue;\n seen.add(config.field);\n rows.push({\n field: config.field,\n label: popupFieldLabel(config),\n value,\n text: formatPopupValue(value, config, { locale }),\n kind: config.kind ?? \"auto\",\n ...(trimmedString(config.format?.linkLabel)\n ? { linkLabel: trimmedString(config.format?.linkLabel) }\n : {}),\n });\n }\n return rows;\n}\n\n/**\n * Compiled title/body expressions, keyed by zoom and source.\n *\n * `createHoverTooltipElement` calls {@link resolvePopupTitle} once per\n * animation frame for as long as the pointer sits on a hovered feature, and\n * compiling means a `JSON.parse` plus a full style-spec validation. Without\n * this the same unchanged source string would be re-parsed sixty times a\n * second. Zoom is part of the key because a compiled expression bakes it in\n * for `[\"zoom\"]`; it is constant through a hover, so the cache still hits.\n */\nconst EXPRESSION_CACHE = new Map<string, CompiledFeatureExpression>();\n/** Bound on the cache, so a project full of layers cannot grow it without end. */\nconst EXPRESSION_CACHE_LIMIT = 64;\n\nfunction compiledPopupExpression(source: string, zoom: number): CompiledFeatureExpression {\n const key = `${zoom}\\u0000${source}`;\n const cached = EXPRESSION_CACHE.get(key);\n if (cached) return cached;\n const compiled = compileFeatureExpression(source, { zoom });\n // Map iterates in insertion order, so the first key is the oldest entry.\n if (EXPRESSION_CACHE.size >= EXPRESSION_CACHE_LIMIT) {\n const oldest = EXPRESSION_CACHE.keys().next().value;\n if (oldest !== undefined) EXPRESSION_CACHE.delete(oldest);\n }\n EXPRESSION_CACHE.set(key, compiled);\n return compiled;\n}\n\n/**\n * Evaluate a popup expression against a feature's properties, or return\n * `undefined` when it does not compile or throws. Both failures mean the same\n * thing to every caller here: fall back to what the popup would show without\n * the expression.\n */\nfunction evaluatePopupExpression(\n source: string,\n properties: Record<string, unknown>,\n options: PopupExpressionOptions,\n): unknown {\n const compiled = compiledPopupExpression(source, options.zoom ?? 0);\n if (!compiled.ok || !compiled.evaluate) return undefined;\n try {\n return compiled.evaluate(\n featureFor(visiblePopupProperties(properties, options.fieldVisibility), options.feature),\n );\n } catch {\n return undefined;\n }\n}\n\n/** Build the synthetic feature an expression is evaluated against. */\nfunction featureFor(properties: Record<string, unknown>, feature?: Feature | null): Feature {\n if (feature) return { ...feature, properties };\n return { type: \"Feature\", geometry: null as never, properties };\n}\n\n/** Options for {@link resolvePopupTitle} and {@link resolvePopupBody}. */\nexport interface PopupExpressionOptions {\n /**\n * The author's field visibility, so an expression cannot read a hidden or\n * excluded column. Omitting it means nothing is hidden.\n */\n fieldVisibility?: Record<string, FieldVisibility>;\n /** The real feature, when the caller has one — feeds `[\"geometry-type\"]`. */\n feature?: Feature | null;\n /** Map zoom for `[\"zoom\"]`; defaults to 0. */\n zoom?: number;\n}\n\n/**\n * The heading a popup should show: the title expression's value, else the\n * title field's value, else the layer name. An expression that fails to\n * compile or evaluate, and a title field that is empty or hidden, both fall\n * back to the layer name rather than leaving the popup headless.\n */\nexport function resolvePopupTitle(\n layerName: string,\n properties: Record<string, unknown>,\n popup: LayerPopupConfig | undefined,\n options: PopupExpressionOptions = {},\n): string {\n return resolveConfiguredPopupTitle(properties, popup, options) ?? layerName;\n}\n\n/**\n * The title the author's own configuration produced, or `null` when there is\n * none — no title field or expression, or one that evaluated to nothing, or a\n * field the author hid.\n *\n * {@link resolvePopupTitle} is this plus the layer-name fallback. The hover\n * tooltip needs the two apart: a tip with no fields and no configured title\n * would be a box repeating the layer name, so it is suppressed — but that\n * decision has to rest on whether a title was *configured*, not on whether the\n * resolved text happens to equal the layer name. A city layer named \"Olympia\"\n * whose feature is also called \"Olympia\" is a real title, not a fallback.\n */\nexport function resolveConfiguredPopupTitle(\n properties: Record<string, unknown>,\n popup: LayerPopupConfig | undefined,\n options: PopupExpressionOptions = {},\n): string | null {\n const source = trimmedString(popup?.titleExpression);\n if (source) {\n const text = stringifyPopupValue(evaluatePopupExpression(source, properties, options)).trim();\n if (text) return text;\n }\n const field = trimmedString(popup?.titleField);\n if (field && !options.fieldVisibility?.[field] && !isInternalPopupField(field)) {\n const text = stringifyPopupValue(properties[field]).trim();\n if (text) return text;\n }\n return null;\n}\n\n/**\n * The popup body text when the author supplied a body expression, or `null`\n * when there is none (or it failed) and the field rows should render instead.\n */\nexport function resolvePopupBody(\n properties: Record<string, unknown>,\n popup: LayerPopupConfig | undefined,\n options: PopupExpressionOptions = {},\n): string | null {\n const source = trimmedString(popup?.bodyExpression);\n if (!source) return null;\n const preview = evaluatePopupExpression(source, properties, options);\n if (preview === undefined) return null;\n const text = stringifyPopupValue(preview);\n return text.trim() ? text : null;\n}\n\n/**\n * Whether a value should render as an inline image thumbnail under `\"auto\"`.\n * Kept here so the renderer and the designer's preview agree on what counts.\n */\nexport function isInlineImageValue(value: unknown): value is string {\n return typeof value === \"string\" && INLINE_IMAGE_DATA_URL.test(value);\n}\n\n/**\n * Whether a URL is safe to put in a popup `href`/`src`. Only http(s) and\n * inline raster data URLs: a `javascript:` or `data:text/html` value coming\n * from an untrusted GeoJSON must never become a live link.\n */\nexport function isSafePopupUrl(value: unknown, allowDataImage = false): value is string {\n if (typeof value !== \"string\") return false;\n const trimmed = value.trim();\n if (/^https?:\\/\\//i.test(trimmed)) return true;\n return allowDataImage && INLINE_IMAGE_DATA_URL.test(trimmed);\n}\n","import type { GeoLibreLayer, LayerQuickFilter } from \"./types\";\n\n/**\n * Quick filters (issue #2114): per-layer, data-driven filter controls that\n * compile down to the MapLibre filter a layer already supports.\n *\n * The persisted record is the *control state* (field, kind, chosen values),\n * never the compiled expression. Storing the compiled output would make a\n * filter unreadable and un-editable the moment it is saved, which is exactly\n * the wall this feature exists to remove: a rule filter, Select by Expression,\n * and the attribute table's filter box all already accept a hand-written\n * MapLibre expression.\n *\n * Compilation happens at sync time in `@geolibre/map`'s layer sync, which\n * combines the result with the transient `timeFilter`, the embed API's\n * `embedFilter`, and the rule-based visibility filter under one `[\"all\", …]`,\n * so a host page's filter and a user's filter narrow the layer together\n * instead of clobbering each other.\n *\n * Every clause reads the property in place, so the same expression is correct\n * for a GeoJSON source and for a tile-backed source whose tiles decode later.\n */\n\n/** Day length in milliseconds, used to make a date filter's end day inclusive. */\nconst DAY_MS = 86_400_000;\n\n/** The `YYYY-MM-DD` prefix length every ISO date comparison slices to. */\nconst ISO_DAY_LENGTH = 10;\n\n/** Whether a filter is switched on (the field defaults to enabled). */\nfunction isEnabled(filter: LayerQuickFilter): boolean {\n return filter.enabled !== false;\n}\n\n/** A validated date bound: its canonical `YYYY-MM-DD` text and UTC midnight. */\ninterface IsoDayBound {\n day: string;\n ms: number;\n}\n\n/**\n * Validate a `YYYY-MM-DD` bound, returning its canonical text and UTC midnight,\n * or `null` when the text is blank, malformed, or not a real calendar day.\n *\n * The round-trip check is what rejects a day that does not exist: `Date.parse`\n * happily reads `2026-02-30` as March 2, so shape-checking alone would compile\n * a bound the user never chose. Rejecting here means a half-typed or impossible\n * bound compiles to no clause at all, leaving that side unconstrained rather\n * than filtering on a date nobody asked for.\n */\nfunction parseIsoDayBound(day: string | null | undefined): IsoDayBound | null {\n if (typeof day !== \"string\") return null;\n const trimmed = day.trim();\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(trimmed)) return null;\n const ms = Date.parse(`${trimmed}T00:00:00Z`);\n if (!Number.isFinite(ms)) return null;\n return new Date(ms).toISOString().slice(0, ISO_DAY_LENGTH) === trimmed\n ? { day: trimmed, ms }\n : null;\n}\n\n/** A finite number, or `null` for every other input (including `NaN`). */\nfunction finite(value: number | null | undefined): number | null {\n return typeof value === \"number\" && Number.isFinite(value) ? value : null;\n}\n\n/**\n * `[\"get\", field]` coerced for comparison, guarded by `[\"has\", field]` where a\n * missing property would otherwise coerce into range (`to-number` turns a\n * missing property into `0`, and `to-string` into `\"\"`, either of which can sit\n * inside a one-sided bound).\n */\nfunction presenceGuard(field: string): unknown[] {\n return [\"has\", field];\n}\n\nfunction compileCategorical(filter: LayerQuickFilter): unknown[] | null {\n const values = filter.values ?? [];\n if (values.length === 0) return null;\n // `in` keeps the values at their original type, so a numeric code and its\n // string spelling never collapse into one another the way `match` labels do.\n return [\"in\", [\"get\", filter.field], [\"literal\", values]];\n}\n\nfunction compileRange(filter: LayerQuickFilter): unknown[] | null {\n const first = finite(filter.min);\n const second = finite(filter.max);\n if (first === null && second === null) return null;\n // Order the bounds rather than trusting which box they were typed into. The\n // slider's thumbs cannot cross, but the two number inputs commit\n // independently, so a bound is briefly inverted on the way to any value whose\n // first digit is smaller (typing `200000` into a max already showing `800000`\n // passes through `2`). Compiling that literally yields an unsatisfiable\n // filter, which empties the layer with nothing on screen to explain why.\n const min = first !== null && second !== null ? Math.min(first, second) : first;\n const max = first !== null && second !== null ? Math.max(first, second) : second;\n const value = [\"to-number\", [\"get\", filter.field]];\n const clauses: unknown[] = [presenceGuard(filter.field)];\n if (min !== null) clauses.push([\">=\", value, min]);\n if (max !== null) clauses.push([\"<=\", value, max]);\n return [\"all\", ...clauses];\n}\n\nfunction compileDate(filter: LayerQuickFilter): unknown[] | null {\n // Both branches read the *validated* bounds, never the raw strings: with one\n // bound valid and the other malformed the filter still compiles, and pushing\n // the raw text would compare against a value no real date can satisfy — which\n // hides every feature instead of leaving that side open.\n const first = parseIsoDayBound(filter.start);\n const second = parseIsoDayBound(filter.end);\n if (first === null && second === null) return null;\n // Ordered for the same reason as `compileRange`: a date typed into the wrong\n // box (or a from/to pair edited out of order) should read as the interval\n // between them, not as an empty layer.\n const inverted = first !== null && second !== null && first.ms > second.ms;\n const start = inverted ? second : first;\n const end = inverted ? first : second;\n const clauses: unknown[] = [presenceGuard(filter.field)];\n\n if ((filter.dateKind ?? \"iso\") === \"iso\") {\n // Compare the leading `YYYY-MM-DD` slice on both sides: ISO text sorts\n // chronologically, and slicing makes a datetime comparable to a plain day\n // bound without parsing. Both bounds are inclusive because the slice drops\n // the time of day.\n const value = [\"slice\", [\"to-string\", [\"get\", filter.field]], 0, ISO_DAY_LENGTH];\n if (start !== null) clauses.push([\">=\", value, start.day]);\n if (end !== null) clauses.push([\"<=\", value, end.day]);\n return [\"all\", ...clauses];\n }\n\n // Epoch numbers: the end bound is exclusive at the *next* day's midnight, so\n // the chosen end day is kept in full rather than only its midnight instant.\n const scale = filter.dateKind === \"epochS\" ? 0.001 : 1;\n const value = [\"to-number\", [\"get\", filter.field]];\n if (start !== null) clauses.push([\">=\", value, start.ms * scale]);\n if (end !== null) clauses.push([\"<\", value, (end.ms + DAY_MS) * scale]);\n return [\"all\", ...clauses];\n}\n\nfunction compileText(filter: LayerQuickFilter): unknown[] | null {\n const needle = filter.text?.trim().toLowerCase() ?? \"\";\n if (needle === \"\") return null;\n // Case-insensitive on both sides: a viewer typing \"portland\" should not have\n // to guess how the data capitalizes it.\n const haystack = [\"downcase\", [\"to-string\", [\"get\", filter.field]]];\n switch (filter.operator ?? \"contains\") {\n case \"equals\":\n return [\"==\", haystack, needle];\n case \"startsWith\":\n return [\"==\", [\"index-of\", needle, haystack], 0];\n default:\n return [\"!=\", [\"index-of\", needle, haystack], -1];\n }\n}\n\n/**\n * Compile one quick filter to a MapLibre filter expression, or `null` when it\n * places no constraint (switched off, no field, or nothing chosen yet). A\n * control that has been added but not yet answered must never hide features.\n *\n * @param filter - The persisted control state.\n * @returns A MapLibre filter expression, or `null`.\n */\nexport function compileQuickFilter(filter: LayerQuickFilter): unknown[] | null {\n if (!isEnabled(filter) || typeof filter.field !== \"string\" || filter.field === \"\") return null;\n switch (filter.kind) {\n case \"categorical\":\n return compileCategorical(filter);\n case \"range\":\n return compileRange(filter);\n case \"date\":\n return compileDate(filter);\n case \"text\":\n return compileText(filter);\n default:\n return null;\n }\n}\n\n/**\n * Compile a layer's quick filters into a single MapLibre filter expression.\n * Multiple controls narrow the data together (`[\"all\", …]`), matching how a\n * viewer reads a stack of filters. Returns `null` when no control constrains\n * anything, so the common path adds no filter at all.\n *\n * @param filters - The layer's persisted quick filters.\n * @returns A MapLibre filter expression, or `null`.\n */\nexport function compileQuickFilters(\n filters: readonly LayerQuickFilter[] | undefined,\n): unknown[] | null {\n if (!filters || filters.length === 0) return null;\n const clauses: unknown[] = [];\n for (const filter of filters) {\n const compiled = compileQuickFilter(filter);\n if (compiled) clauses.push(compiled);\n }\n if (clauses.length === 0) return null;\n return clauses.length === 1 ? (clauses[0] as unknown[]) : [\"all\", ...clauses];\n}\n\n/**\n * The quick filters that actually constrain the layer right now. Drives the\n * \"filtered\" badge on the layer row and its count, so an active filter is\n * never mistaken for missing data.\n *\n * @param layer - The layer to inspect.\n * @returns The constraining filters, in their configured order.\n */\nexport function activeQuickFilters(layer: GeoLibreLayer): LayerQuickFilter[] {\n return (layer.quickFilters ?? []).filter((filter) => compileQuickFilter(filter) !== null);\n}\n\n/**\n * Whether the layer is currently narrowed by at least one quick filter.\n *\n * @param layer - The layer to inspect.\n * @returns `true` when a quick filter constrains the rendered features.\n */\nexport function hasActiveQuickFilter(layer: GeoLibreLayer): boolean {\n return (layer.quickFilters ?? []).some((filter) => compileQuickFilter(filter) !== null);\n}\n\n/**\n * Clear every chosen value while keeping the controls themselves, so \"Clear\n * filters\" is one action that leaves the layer's configured controls in place\n * for the next question.\n *\n * @param filters - The layer's persisted quick filters.\n * @returns The same controls with their selections emptied.\n */\nexport function clearQuickFilterValues(\n filters: readonly LayerQuickFilter[] | undefined,\n): LayerQuickFilter[] {\n return (filters ?? []).map((filter) => ({\n ...filter,\n values: filter.kind === \"categorical\" ? [] : filter.values,\n min: filter.kind === \"range\" ? null : filter.min,\n max: filter.kind === \"range\" ? null : filter.max,\n start: filter.kind === \"date\" ? null : filter.start,\n end: filter.kind === \"date\" ? null : filter.end,\n text: filter.kind === \"text\" ? \"\" : filter.text,\n }));\n}\n","import type { LayerLevelFilter, LevelFilterMode } from \"./types\";\n\n/**\n * Floor picker (issue #3341): the levels a multi-floor layer holds, and the\n * filter that draws one floor (or one floor and those below it).\n *\n * Indoor data stores the floor in many shapes. OpenStreetMap's `level` tag is\n * text and may name several floors (`\"0;1\"`) or a span (`\"-1-2\"`); IMDF stores\n * a numeric `ordinal`; other exports use a plain number. Rather than compile a\n * MapLibre expression that tries to parse all of these (string splitting has\n * no expression operator, and `to-number` throws on `\"0;1\"`), the picker\n * resolves the choice against the layer's features once and stores the raw\n * values that match. The compiled filter is then a plain `in` test, which\n * MapLibre, Mapbox, Cesium and ArcGIS all evaluate with the same style-spec\n * code.\n */\n\n/** One floor as the picker lists it. */\nexport interface LevelEntry {\n /** Canonical label: a number's shortest form (`\"1\"` for `\"01\"`), or the trimmed text. */\n label: string;\n /** The floor's number, or `null` for a named floor such as `\"G\"`. */\n ordinal: number | null;\n /** How many features sit on this floor (a multi-level feature counts once per floor). */\n count: number;\n}\n\n/** A feature as far as level parsing needs it. */\ninterface LevelFeature {\n properties?: Record<string, unknown> | null;\n}\n\n/** Field names that usually hold a floor, in preference order (compared case-insensitively). */\nconst LEVEL_FIELD_CANDIDATES = [\n \"level\",\n \"ordinal\",\n \"floor\",\n \"level_ordinal\",\n \"floor_number\",\n \"floornumber\",\n \"floor_no\",\n \"storey\",\n \"story\",\n \"etage\",\n \"level_name\",\n];\n\nconst NUMBER = /^-?\\d+(?:\\.\\d+)?$/;\nconst RANGE = /^(-?\\d+(?:\\.\\d+)?)\\s*-\\s*(-?\\d+(?:\\.\\d+)?)$/;\n/** A range wider than this is read as its two ends, not every floor between. */\nconst MAX_RANGE_STEPS = 200;\n\n/** A parsed floor token: its canonical label and number. */\ninterface LevelToken {\n label: string;\n ordinal: number | null;\n}\n\nfunction numericToken(value: number): LevelToken {\n return { label: String(value), ordinal: value };\n}\n\n/**\n * Parse one attribute value into the floors it names.\n *\n * Numbers are taken as they are. Text is split on `;` (OSM's list separator),\n * each part read as a number, an integer span such as `0-2` or `-2--1`\n * (expanded to every floor it covers), or kept as a named floor.\n *\n * @param raw - The attribute value.\n * @returns The floors it names, possibly empty (for `null`, booleans, blanks).\n */\nexport function parseLevelValue(raw: unknown): LevelToken[] {\n if (typeof raw === \"number\") return Number.isFinite(raw) ? [numericToken(raw)] : [];\n if (typeof raw !== \"string\") return [];\n const tokens: LevelToken[] = [];\n for (const part of raw.split(\";\")) {\n const text = part.trim();\n if (!text) continue;\n if (NUMBER.test(text)) {\n tokens.push(numericToken(Number(text)));\n continue;\n }\n const range = RANGE.exec(text);\n if (range) {\n const a = Number(range[1]);\n const b = Number(range[2]);\n const lo = Math.min(a, b);\n const hi = Math.max(a, b);\n // Safe integers only: past 2^53, `level += 1` no longer advances.\n if (Number.isSafeInteger(lo) && Number.isSafeInteger(hi) && hi - lo <= MAX_RANGE_STEPS) {\n for (let level = lo; level <= hi; level += 1) tokens.push(numericToken(level));\n } else {\n tokens.push(numericToken(lo), numericToken(hi));\n }\n continue;\n }\n tokens.push({ label: text, ordinal: null });\n }\n return tokens;\n}\n\n/** The ordinal of a picker label: its number, or `null` for a named floor. */\nfunction labelOrdinal(label: string): number | null {\n return NUMBER.test(label.trim()) ? Number(label) : null;\n}\n\n/**\n * Whether a feature's level value falls on the chosen floor.\n *\n * @param raw - The feature's level attribute.\n * @param level - The chosen floor's label.\n * @param mode - `only` matches that floor; `below` also matches every\n * numbered floor under it (a named floor matches only itself). `all`\n * matches everything.\n * @returns True when the feature should be drawn.\n */\nexport function levelValueMatches(raw: unknown, level: string, mode: LevelFilterMode): boolean {\n if (mode === \"all\") return true;\n const tokens = parseLevelValue(raw);\n const target = labelOrdinal(level);\n const label = target === null ? level.trim() : String(target);\n return tokens.some((token) => {\n if (token.label === label) return true;\n return mode === \"below\" && target !== null && token.ordinal !== null && token.ordinal <= target;\n });\n}\n\n/**\n * Order floors top down, the way a building directory or lift panel lists\n * them: numbered floors from highest to lowest, then named floors by name.\n */\nfunction compareLevels(a: LevelEntry, b: LevelEntry): number {\n if (a.ordinal !== null && b.ordinal !== null) return b.ordinal - a.ordinal;\n if (a.ordinal !== null) return -1;\n if (b.ordinal !== null) return 1;\n return a.label.localeCompare(b.label);\n}\n\n/**\n * The distinct floors a set of features holds in one field, top floor first.\n *\n * @param features - The layer's features.\n * @param field - The property holding the floor.\n * @returns One entry per floor with its feature count.\n */\nexport function collectLevels(\n features: readonly LevelFeature[] | null | undefined,\n field: string,\n): LevelEntry[] {\n const byLabel = new Map<string, LevelEntry>();\n for (const feature of features ?? []) {\n const seen = new Set<string>();\n for (const token of parseLevelValue(feature?.properties?.[field])) {\n if (seen.has(token.label)) continue;\n seen.add(token.label);\n const entry = byLabel.get(token.label);\n if (entry) entry.count += 1;\n else byLabel.set(token.label, { label: token.label, ordinal: token.ordinal, count: 1 });\n }\n }\n return [...byLabel.values()].sort(compareLevels);\n}\n\n/**\n * Merge the floor lists of several layers into one, top floor first, summing\n * the counts of a floor that appears in more than one layer.\n *\n * @param lists - One {@link collectLevels} result per linked layer.\n * @returns The combined floor list.\n */\nexport function mergeLevels(lists: readonly (readonly LevelEntry[])[]): LevelEntry[] {\n const byLabel = new Map<string, LevelEntry>();\n for (const list of lists) {\n for (const entry of list) {\n const existing = byLabel.get(entry.label);\n if (existing) existing.count += entry.count;\n else byLabel.set(entry.label, { ...entry });\n }\n }\n return [...byLabel.values()].sort(compareLevels);\n}\n\n/**\n * The distinct raw values of `field` that match a floor choice, in first-seen\n * order. These are what {@link compileLevelFilter} tests with `in`.\n *\n * @param features - The layer's features.\n * @param field - The property holding the floor.\n * @param level - The chosen floor's label.\n * @param mode - The floor picker mode.\n * @returns The matching values (strings and numbers kept as stored).\n */\nexport function matchingLevelValues(\n features: readonly LevelFeature[] | null | undefined,\n field: string,\n level: string,\n mode: LevelFilterMode,\n): (string | number)[] {\n const values: (string | number)[] = [];\n const checked = new Set<string | number>();\n for (const feature of features ?? []) {\n const raw = feature?.properties?.[field];\n if (typeof raw !== \"string\" && typeof raw !== \"number\") continue;\n if (checked.has(raw)) continue;\n checked.add(raw);\n if (levelValueMatches(raw, level, mode)) values.push(raw);\n }\n return values;\n}\n\n/**\n * Apply a floor choice to a layer's link, resolving the matching values from\n * the layer's features.\n *\n * Without features (a tile-backed layer, or data not loaded yet) the values\n * stay unresolved, which draws every floor, rather than resolving to an empty\n * list that would hide the layer once its data arrives.\n *\n * @param filter - The layer's current link.\n * @param features - The layer's features, or `null`/`undefined` when none are loaded.\n * @param level - The chosen floor's label, or `null` for every floor.\n * @param mode - The floor picker mode.\n * @returns The updated link.\n */\nexport function selectLevel(\n filter: LayerLevelFilter,\n features: readonly LevelFeature[] | null | undefined,\n level: string | null,\n mode: LevelFilterMode,\n): LayerLevelFilter {\n if (level === null || mode === \"all\") {\n return { field: filter.field, mode: \"all\", level: null };\n }\n if (!features) return { field: filter.field, mode, level };\n return {\n field: filter.field,\n mode,\n level,\n values: matchingLevelValues(features, filter.field, level, mode),\n };\n}\n\n/** The floor shown by the picker: a level (`null` for every floor) and a mode. */\nexport interface LevelChoice {\n level: string | null;\n mode: LevelFilterMode;\n}\n\n/** A layer as far as the floor picker reads it. */\ninterface LevelLinkedLayer {\n id: string;\n levelFilter?: LayerLevelFilter;\n geojson?: { features: readonly LevelFeature[] } | null;\n}\n\n/**\n * The floor choice the picker shows: the first linked layer that has a floor\n * chosen, so a layer linked later (still on `all`) does not reset the display.\n *\n * @param linked - The linked layers.\n * @returns The chosen level and mode, or every floor.\n */\nexport function currentLevelChoice(linked: readonly LevelLinkedLayer[]): LevelChoice {\n for (const layer of linked) {\n const link = layer.levelFilter;\n if (link && link.mode && link.mode !== \"all\" && typeof link.level === \"string\") {\n return { level: link.level, mode: link.mode };\n }\n }\n return { level: null, mode: \"all\" };\n}\n\n/**\n * Whether two links store the same choice, independent of key order (a\n * project written by the Python API may order the keys differently).\n *\n * @param a - One link.\n * @param b - The other.\n * @returns True when they draw the same features.\n */\nexport function sameLevelLink(a: LayerLevelFilter, b: LayerLevelFilter): boolean {\n if (a.field !== b.field) return false;\n if ((a.mode ?? \"all\") !== (b.mode ?? \"all\")) return false;\n if ((a.level ?? null) !== (b.level ?? null)) return false;\n const av = a.values;\n const bv = b.values;\n // A hand-edited project can hold anything in `values`; never throw on it.\n if (!Array.isArray(av) || !Array.isArray(bv)) return av === bv;\n return av.length === bv.length && av.every((value, index) => value === bv[index]);\n}\n\n/** Resolved links per feature array and choice, so an unrelated layer edit does not rescan. */\nconst resolvedLinks = new WeakMap<object, Map<string, LayerLevelFilter>>();\n\n/** {@link selectLevel}, memoized on the feature array (replaced whenever the data changes). */\nfunction cachedSelectLevel(\n link: LayerLevelFilter,\n features: readonly LevelFeature[],\n choice: LevelChoice,\n): LayerLevelFilter {\n const key = JSON.stringify([link.field, choice.level, choice.mode]);\n let byChoice = resolvedLinks.get(features);\n if (!byChoice) {\n byChoice = new Map();\n resolvedLinks.set(features, byChoice);\n }\n let next = byChoice.get(key);\n if (!next) {\n next = selectLevel(link, features, choice.level, choice.mode);\n byChoice.set(key, next);\n }\n return next;\n}\n\n/**\n * The link updates that put every linked layer on one floor choice, with the\n * matching values resolved from each layer's own features: a layer linked\n * after a floor was chosen, a project written without the values (the Python\n * API without inline data), or data that changed since the choice was made.\n *\n * @param linked - The linked layers.\n * @param choice - The floor to apply; defaults to {@link currentLevelChoice}.\n * @returns One update per layer whose stored link differs.\n */\nexport function resolveLevelLinks(\n linked: readonly LevelLinkedLayer[],\n choice: LevelChoice = currentLevelChoice(linked),\n): { id: string; levelFilter: LayerLevelFilter }[] {\n const updates: { id: string; levelFilter: LayerLevelFilter }[] = [];\n for (const layer of linked) {\n const link = layer.levelFilter;\n if (!link) continue;\n // Data still loading (a project re-reading its source file): keep the saved\n // link, values included, until the features arrive.\n const features = layer.geojson?.features;\n if (!features) continue;\n const next = cachedSelectLevel(link, features, choice);\n if (!sameLevelLink(link, next)) updates.push({ id: layer.id, levelFilter: next });\n }\n return updates;\n}\n\n/**\n * Whether a link narrows the layer: a floor is chosen and its values resolved.\n *\n * @param filter - The layer's link, if any.\n * @returns True when {@link compileLevelFilter} would return a filter.\n */\nexport function isLevelFilterActive(filter: LayerLevelFilter | null | undefined): boolean {\n return (\n !!filter &&\n typeof filter.field === \"string\" &&\n filter.field.length > 0 &&\n (filter.mode === \"only\" || filter.mode === \"below\") &&\n typeof filter.level === \"string\" &&\n Array.isArray(filter.values)\n );\n}\n\n/**\n * Compile a floor picker link into a MapLibre filter expression.\n *\n * @param filter - The layer's link, if any.\n * @returns An `in` test over the resolved values, or `null` when every floor\n * is drawn (no link, `all` mode, no floor chosen, or values unresolved).\n */\nexport function compileLevelFilter(filter: LayerLevelFilter | null | undefined): unknown[] | null {\n if (!isLevelFilterActive(filter)) return null;\n const values = (filter!.values ?? []).filter(\n (value) => typeof value === \"string\" || (typeof value === \"number\" && Number.isFinite(value)),\n );\n return [\"in\", [\"get\", filter!.field], [\"literal\", values]];\n}\n\n/**\n * The floor a feature is on, as the floor picker labels it: the first floor its\n * level value names that the picker lists (a room spanning floors `0;1` goes\n * to floor 0), or `null` when it names none of them.\n *\n * @param raw - The feature's level attribute.\n * @param levels - The floors the picker lists.\n * @returns The floor's label, or `null`.\n */\nexport function featureLevel(raw: unknown, levels: readonly LevelEntry[]): string | null {\n const listed = new Set(levels.map((entry) => entry.label));\n for (const token of parseLevelValue(raw)) {\n if (listed.has(token.label)) return token.label;\n }\n return null;\n}\n\n/**\n * Guess which field holds the floor.\n *\n * @param fields - The layer's field names.\n * @returns The best candidate, or `null` when none looks like a floor field.\n */\nexport function guessLevelField(fields: readonly string[]): string | null {\n const byLower = new Map<string, string>();\n for (const field of fields) {\n const lower = field.toLowerCase();\n if (!byLower.has(lower)) byLower.set(lower, field);\n }\n for (const candidate of LEVEL_FIELD_CANDIDATES) {\n const field = byLower.get(candidate);\n if (field) return field;\n }\n return null;\n}\n","import { featureFilter } from \"@maplibre/maplibre-gl-style-spec\";\nimport type { FeatureCollection } from \"geojson\";\nimport type { GeoLibreLayer } from \"./types\";\nimport { compileLevelFilter } from \"./level-filter\";\nimport { compileQuickFilters } from \"./quick-filters\";\n\n/** The layer fields needed to inspect project-authored feature filters. */\ntype LayerFilterSource = Pick<GeoLibreLayer, \"filterExpression\" | \"quickFilters\"> &\n Partial<Pick<GeoLibreLayer, \"levelFilter\">>;\n\n/**\n * Return a layer's persisted expression filter when it has a usable array\n * shape, otherwise return null.\n *\n * @param layer - The layer whose expression filter should be read.\n * @returns The active expression filter, or null when none is configured.\n */\nexport function activeLayerFilterExpression(layer: LayerFilterSource): unknown[] | null {\n const expression = layer.filterExpression;\n return Array.isArray(expression) && expression.length > 0 ? expression : null;\n}\n\n/**\n * Compile all project-authored filters for a layer into one MapLibre boolean\n * expression. An explicit expression, Quick Filter controls and the floor\n * picker's choice narrow the layer together.\n *\n * @param layer - The layer whose authored filters should be composed.\n * @returns A MapLibre filter expression, or null when no authored filter is active.\n */\nexport function compileLayerFilters(layer: LayerFilterSource): unknown[] | null {\n const filters = [\n activeLayerFilterExpression(layer),\n compileQuickFilters(layer.quickFilters),\n compileLevelFilter(layer.levelFilter),\n ].filter((filter): filter is unknown[] => filter !== null);\n if (filters.length === 0) return null;\n return filters.length === 1 ? filters[0] : [\"all\", ...filters];\n}\n\n/**\n * Whether a project-authored expression or Quick Filter currently narrows a\n * layer. This drives the filtered indicator and clear action in the layer UI.\n *\n * The floor picker's choice is left out on purpose: the picker is its own\n * on-map control that shows and resets the choice for every linked layer at\n * once, so a per-layer \"clear filters\" action must not desynchronize it.\n * Renderers still apply it through {@link compileLayerFilters}.\n *\n * @param layer - The layer to inspect.\n * @returns True when at least one persisted expression or Quick Filter is active.\n */\nexport function hasActiveLayerFilter(layer: LayerFilterSource): boolean {\n return (\n activeLayerFilterExpression(layer) !== null || compileQuickFilters(layer.quickFilters) !== null\n );\n}\n\n/** MapLibre's feature type codes, which `featureFilter` reads for `$type`. */\nconst FEATURE_TYPE_CODES: Record<string, 1 | 2 | 3> = {\n Point: 1,\n MultiPoint: 1,\n LineString: 2,\n MultiLineString: 2,\n Polygon: 3,\n MultiPolygon: 3,\n};\n\n/** The last filtered result per collection, so an unchanged filter reuses it. */\nconst filteredCollections = new WeakMap<object, { key: string; result: FeatureCollection }>();\n\n/**\n * A layer's features narrowed by its authored filters (an expression, Quick\n * Filters, the floor picker), for a renderer that draws GeoJSON itself rather\n * than through a MapLibre layer filter, such as the deck.gl 3D Z-value path.\n *\n * The same collection comes back while no filter is active, and the same\n * filtered collection while the filter is unchanged, so caches keyed on the\n * collection keep hitting. A filter that does not compile leaves every\n * feature in, as MapLibre would refuse it rather than hide the layer.\n *\n * @param layer - The layer whose filters apply.\n * @param geojson - The features to filter.\n * @param zoom - The zoom a `[\"zoom\"]` expression sees.\n * @returns The matching features.\n */\nexport function applyLayerFilters(\n layer: LayerFilterSource,\n geojson: FeatureCollection,\n zoom = 0,\n): FeatureCollection {\n const filter = compileLayerFilters(layer);\n if (!filter) return geojson;\n const key = `${zoom}|${JSON.stringify(filter)}`;\n const cached = filteredCollections.get(geojson);\n if (cached?.key === key) return cached.result;\n let compiled: ReturnType<typeof featureFilter>;\n try {\n compiled = featureFilter(filter as never, \"layers[0].filter\");\n } catch {\n return geojson;\n }\n const result: FeatureCollection = {\n ...geojson,\n features: geojson.features.filter((feature) =>\n compiled.filter(\n { zoom } as never,\n {\n type: FEATURE_TYPE_CODES[feature.geometry?.type ?? \"\"] ?? 1,\n properties: feature.properties ?? {},\n ...(feature.id !== undefined ? { id: feature.id } : {}),\n } as never,\n ),\n ),\n };\n filteredCollections.set(geojson, { key, result });\n return result;\n}\n","/**\n * List a zip archive's entry paths from its central directory, reading only\n * the end of the archive (a byte-range reader), never the whole file. Shared by\n * the plugin file-drop hints and the IMDF detection (issue #3341), so a large\n * shapefile zip is ruled out without being read.\n */\n\nexport type ReadRange = (offset: number, length: number) => Promise<Uint8Array>;\n\nconst EOCD_SIGNATURE = 0x06054b50;\nconst EOCD_SIZE = 22;\nconst ZIP64_LOCATOR_SIGNATURE = 0x07064b50;\nconst ZIP64_LOCATOR_SIZE = 20;\nconst ZIP64_EOCD_SIGNATURE = 0x06064b50;\nconst ZIP64_EOCD_SIZE = 56;\nconst CENTRAL_ENTRY_SIGNATURE = 0x02014b50;\nconst CENTRAL_ENTRY_SIZE = 46;\n/** General purpose flag bit 11: the entry name is UTF-8 rather than CP437. */\nconst UTF8_NAME_FLAG = 0x0800;\n/** The upper half (0x80-0xff) of code page 437, the zip default for names. */\nconst CP437_HIGH =\n \"ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■\\u00a0\";\n/** Longest possible zip comment plus the record that precedes it. */\nconst MAX_EOCD_SEARCH = 0xffff + EOCD_SIZE;\n/** End-record candidates tried before giving up, so a hostile comment stays cheap. */\nconst MAX_EOCD_CANDIDATES = 16;\nconst UTF8_DECODER = new TextDecoder(\"utf-8\", { ignoreBOM: true });\nconst STRICT_UTF8_DECODER = new TextDecoder(\"utf-8\", { fatal: true, ignoreBOM: true });\n/** A central directory beyond this is not a file a handler should inspect. */\nconst MAX_CENTRAL_DIRECTORY_BYTES = 16 * 1024 * 1024;\n\nfunction viewOf(bytes: Uint8Array): DataView {\n return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n}\n\nfunction decodeCp437(bytes: Uint8Array): string {\n let name = \"\";\n for (const byte of bytes)\n name += byte < 0x80 ? String.fromCharCode(byte) : CP437_HIGH[byte - 0x80];\n return name;\n}\n\n/**\n * Decode an entry name. Flag bit 11 says UTF-8. Without it the zip default is\n * CP437, but some writers (macOS \"Compress\") store UTF-8 without the flag, so\n * a name that is valid UTF-8 is read as such before falling back to CP437.\n */\nfunction decodeEntryName(name: Uint8Array, flags: number): string {\n if (flags & UTF8_NAME_FLAG) return UTF8_DECODER.decode(name);\n if (name.some((byte) => byte >= 0x80)) {\n try {\n return STRICT_UTF8_DECODER.decode(name);\n } catch {\n return decodeCp437(name);\n }\n }\n return decodeCp437(name);\n}\n\n/**\n * List the paths inside a zip from its central directory, reading only the tail\n * of the archive rather than the whole file.\n *\n * Deliberately not listed (the result is undefined, so a handler can still claim\n * by file name): archives with bytes in front of them (self-extracting zips),\n * and spanned or multi-disk archives. The entries are a hint for `claim`, not a full zip\n * parser; a handler that needs these cases unpacks the file itself in `open`.\n *\n * @param size - The archive's size in bytes.\n * @param readRange - Reads a byte range of the archive.\n * @returns The entry paths in archive order, or undefined when the file has no\n * readable central directory (not a zip, or truncated).\n */\nexport async function listZipEntryPaths(\n size: number,\n readRange: ReadRange,\n): Promise<string[] | undefined> {\n if (size < EOCD_SIZE) return undefined;\n const tailLength = Math.min(size, MAX_EOCD_SEARCH);\n const tailOffset = size - tailLength;\n const tail = await readRange(tailOffset, tailLength);\n const tailView = viewOf(tail);\n\n /** Whether a central directory entry starts at `offset`, with `length` bytes in range. */\n const startsDirectory = async (offset: number, length: number): Promise<boolean> => {\n if (length < CENTRAL_ENTRY_SIZE) return false;\n if (offset + length > size) return false;\n // A signature inside the tail already read needs no extra read.\n if (offset >= tailOffset && offset + 4 <= tailOffset + tail.byteLength) {\n return tailView.getUint32(offset - tailOffset, true) === CENTRAL_ENTRY_SIGNATURE;\n }\n const head = await readRange(offset, 4);\n return head.byteLength === 4 && viewOf(head).getUint32(0, true) === CENTRAL_ENTRY_SIGNATURE;\n };\n /** The directory the end record at `eocd` declares, if one really starts there. */\n const checkRecord = async (\n eocd: number,\n ): Promise<[offset: number, length: number] | undefined> => {\n // A spanned archive cannot be unpacked by a handler.\n const disk = tailView.getUint16(eocd + 4, true);\n const directoryDisk = tailView.getUint16(eocd + 6, true);\n if (disk !== 0 || directoryDisk !== 0) return undefined;\n let total = tailView.getUint16(eocd + 10, true);\n // An empty archive's directory \"starts\" where its own end record (ZIP64 or\n // classic) does; a lookalike elsewhere is not one.\n let recordOffset = tailOffset + eocd;\n let length = tailView.getUint32(eocd + 12, true);\n let offset = tailView.getUint32(eocd + 16, true);\n if (length === 0xffffffff || offset === 0xffffffff) {\n const locator = eocd - ZIP64_LOCATOR_SIZE;\n if (locator < 0 || tailView.getUint32(locator, true) !== ZIP64_LOCATOR_SIGNATURE) {\n return undefined;\n }\n // The locator names the disk of the ZIP64 record and the disk count.\n if (tailView.getUint32(locator + 4, true) !== 0) return undefined;\n if (tailView.getUint32(locator + 16, true) > 1) return undefined;\n const zip64Offset = Number(tailView.getBigUint64(locator + 8, true));\n if (zip64Offset + ZIP64_EOCD_SIZE > size) return undefined;\n const zip64Bytes = await readRange(zip64Offset, ZIP64_EOCD_SIZE);\n if (zip64Bytes.byteLength !== ZIP64_EOCD_SIZE) return undefined;\n const zip64 = viewOf(zip64Bytes);\n if (zip64.getUint32(0, true) !== ZIP64_EOCD_SIGNATURE) return undefined;\n if (zip64.getUint32(16, true) !== 0 || zip64.getUint32(20, true) !== 0) return undefined;\n recordOffset = zip64Offset;\n total = Number(zip64.getBigUint64(32, true));\n length = Number(zip64.getBigUint64(40, true));\n offset = Number(zip64.getBigUint64(48, true));\n }\n // An empty archive has no entry to find a signature at.\n if (length === 0) return total === 0 && offset === recordOffset ? [offset, 0] : undefined;\n return (await startsDirectory(offset, length)) ? [offset, length] : undefined;\n };\n\n // Scan back for an end record that points at a real directory: a signature\n // inside the archive comment is only a candidate. The record whose declared\n // comment ends exactly at the end of the file is the real one in a well-formed\n // zip, so it goes first and needs no extra read to be picked.\n const exact: number[] = [];\n const others: number[] = [];\n for (let eocd = tail.byteLength - EOCD_SIZE; eocd >= 0; eocd -= 1) {\n if (tailView.getUint32(eocd, true) !== EOCD_SIGNATURE) continue;\n const ends = eocd + EOCD_SIZE + tailView.getUint16(eocd + 20, true) === tail.byteLength;\n if (ends) exact.push(eocd);\n else others.push(eocd);\n }\n // The scan runs from the end backwards, so reverse the exact records to keep\n // them in ascending offset order, as prepending them used to.\n const candidates = [...exact.reverse(), ...others];\n let located: [offset: number, length: number] | undefined;\n for (const eocd of candidates.slice(0, MAX_EOCD_CANDIDATES)) {\n located = await checkRecord(eocd);\n if (located) break;\n }\n if (!located) return undefined;\n const [directoryOffset, directorySize] = located;\n if (directorySize > MAX_CENTRAL_DIRECTORY_BYTES || directoryOffset + directorySize > size) {\n return undefined;\n }\n\n const directory = await readRange(directoryOffset, directorySize);\n const view = viewOf(directory);\n const paths: string[] = [];\n let cursor = 0;\n while (cursor + CENTRAL_ENTRY_SIZE <= directory.byteLength) {\n if (view.getUint32(cursor, true) !== CENTRAL_ENTRY_SIGNATURE) return undefined;\n const flags = view.getUint16(cursor + 8, true);\n const nameLength = view.getUint16(cursor + 28, true);\n const extraLength = view.getUint16(cursor + 30, true);\n const commentLength = view.getUint16(cursor + 32, true);\n const nameStart = cursor + CENTRAL_ENTRY_SIZE;\n if (nameStart + nameLength > directory.byteLength) return undefined;\n const name = directory.subarray(nameStart, nameStart + nameLength);\n paths.push(decodeEntryName(name, flags));\n cursor = nameStart + nameLength + extraLength + commentLength;\n }\n return paths;\n}\n","import { listZipEntryPaths, type ReadRange } from \"./zip-directory\";\n\n/**\n * Recognizing an IMDF (Indoor Mapping Data Format) archive without unpacking it\n * (issue #3341). One implementation for every entry point: the app's file and\n * drop loaders and the Add Vector panel's picker and URL loads.\n *\n * Only the zip's central directory is read (its tail, through a byte-range\n * reader), so ruling out a large shapefile zip costs a few kilobytes.\n */\n\n/** Bytes read from the end of an archive: the longest zip comment plus its end record. */\nconst TAIL_BYTES = 0xffff + 22;\n/**\n * Largest archive downloaded whole just to look inside it, when a server will\n * not serve a byte range. IMDF venues are small; a larger zip is left to the\n * vector control, which downloads it once anyway.\n */\nconst MAX_PROBE_DOWNLOAD_BYTES = 32 * 1024 * 1024;\n\n/** Whether an archive entry is macOS resource-fork noise (`__MACOSX/`, `._name`). */\nexport function isMacOsZipMetadata(path: string): boolean {\n const base = path.split(\"/\").pop() ?? \"\";\n return path.startsWith(\"__MACOSX/\") || path.includes(\"/__MACOSX/\") || base.startsWith(\"._\");\n}\n\n/**\n * Whether a list of zip entry paths is an IMDF archive: a `manifest.json` beside\n * a `venue`, `level` or `unit` GeoJSON file, in any folder, ignoring case and\n * macOS metadata entries.\n *\n * @param paths - The archive's entry paths.\n * @returns True for an IMDF archive.\n */\nexport function isImdfArchiveEntries(paths: readonly string[]): boolean {\n let manifest = false;\n let core = false;\n for (const path of paths) {\n if (isMacOsZipMetadata(path)) continue;\n const name = path.split(\"/\").pop()?.toLowerCase() ?? \"\";\n if (name === \"manifest.json\") manifest = true;\n if (name === \"venue.geojson\" || name === \"level.geojson\" || name === \"unit.geojson\")\n core = true;\n }\n return manifest && core;\n}\n\n/** A byte-range reader over a `Blob` (or `File`), reading only the slices asked for. */\nexport function blobRangeReader(blob: Blob): ReadRange {\n return async (offset, length) =>\n new Uint8Array(await blob.slice(offset, offset + length).arrayBuffer());\n}\n\n/** A byte-range reader over bytes already in memory. */\nexport function bytesRangeReader(bytes: Uint8Array): ReadRange {\n return async (offset, length) => bytes.subarray(offset, offset + length);\n}\n\n/**\n * Whether an archive of `size` bytes is IMDF, reading only its central\n * directory through `readRange`.\n *\n * @param size - The archive's size in bytes.\n * @param readRange - Reads a byte range of the archive.\n * @returns True for an IMDF archive; false for any other zip or a non-zip.\n */\nexport async function isImdfZipRange(size: number, readRange: ReadRange): Promise<boolean> {\n try {\n const paths = await listZipEntryPaths(size, readRange);\n return paths !== undefined && isImdfArchiveEntries(paths);\n } catch {\n return false;\n }\n}\n\n/**\n * Whether a `Blob` (a picked or dropped file) is an IMDF zip, reading only its\n * central directory.\n *\n * @param blob - The file.\n * @returns True for an IMDF archive.\n */\nexport function isImdfZipBlob(blob: Blob): Promise<boolean> {\n return isImdfZipRange(blob.size, blobRangeReader(blob));\n}\n\n/** What probing a zip URL found. */\nexport interface ImdfUrlProbe {\n /** Whether the archive is IMDF. */\n imdf: boolean;\n /**\n * The whole archive when the probe had to download it (a server that ignores\n * `Range` requests). An IMDF archive is then not downloaded again; a zip that\n * is not IMDF is downloaded again by the caller's control.\n */\n bytes?: Uint8Array;\n}\n\n/**\n * Probe a zip URL for IMDF with HTTP range requests: the tail first (which\n * also gives the archive's size), then the central directory if it starts\n * before the tail.\n *\n * Two hosts need more. One that ignores `Range` answers with the whole file\n * (status 200); one that serves ranges cross-origin without exposing\n * `Content-Range` leaves the archive's size unknown. Either way the archive is\n * read whole instead, up to {@link MAX_PROBE_DOWNLOAD_BYTES}: a larger one is\n * reported as not IMDF without reading its body, so the control downloads it\n * once rather than twice. Below the cap, a non-IMDF archive read this way is\n * downloaded again by the control.\n *\n * Detection also needs the page to send `Range` cross-origin: a host whose\n * preflight refuses it makes the first request reject, which throws here. The\n * caller then passes the URL to the control, which loads an IMDF zip as a\n * zipped shapefile.\n *\n * @param url - The archive's URL.\n * @param fetchImpl - `fetch`, injectable for tests.\n * @returns Whether the archive is IMDF, and its bytes if they were downloaded.\n * @throws Error when the URL cannot be fetched at all.\n */\nexport async function probeImdfZipUrl(\n url: string,\n fetchImpl: typeof fetch = fetch,\n): Promise<ImdfUrlProbe> {\n const first = await fetchImpl(url, { headers: { Range: `bytes=-${TAIL_BYTES}` } });\n if (!first.ok) throw new Error(`HTTP ${first.status}`);\n if (first.status !== 206) return probeWholeResponse(first);\n const total = Number(/\\/(\\d+)\\s*$/.exec(first.headers.get(\"content-range\") ?? \"\")?.[1]);\n if (!Number.isFinite(total) || total <= 0) {\n // Ranges work but the size is hidden (no Access-Control-Expose-Headers):\n // not a reason to call an IMDF zip a shapefile.\n await first.body?.cancel().catch(() => {});\n const whole = await fetchImpl(url);\n if (!whole.ok) throw new Error(`HTTP ${whole.status}`);\n return probeWholeResponse(whole);\n }\n const tail = new Uint8Array(await first.arrayBuffer());\n const tailOffset = total - tail.byteLength;\n const readRange: ReadRange = async (offset, length) => {\n if (offset >= tailOffset && offset + length <= total) {\n return tail.subarray(offset - tailOffset, offset - tailOffset + length);\n }\n const response = await fetchImpl(url, {\n headers: { Range: `bytes=${offset}-${offset + length - 1}` },\n });\n if (response.status !== 206) throw new Error(\"range request not honoured\");\n return new Uint8Array(await response.arrayBuffer());\n };\n return { imdf: await isImdfZipRange(total, readRange) };\n}\n\n/**\n * Classify an archive from a response carrying all of it, reading the body\n * only when it is small enough to be an IMDF venue.\n *\n * @param response - A full (status 200) response.\n * @returns The probe, with the bytes when they were read.\n */\nasync function probeWholeResponse(response: Response): Promise<ImdfUrlProbe> {\n const bytes = await readBodyUpTo(response, MAX_PROBE_DOWNLOAD_BYTES);\n if (!bytes) return { imdf: false };\n return { imdf: await isImdfZipRange(bytes.byteLength, bytesRangeReader(bytes)), bytes };\n}\n\n/**\n * Read a response body, giving up (and cancelling the rest) once it passes\n * `limit` bytes. The size header cannot be trusted for this: it may be absent\n * (a chunked response) or hidden from script on a cross-origin one.\n *\n * @param response - The response.\n * @param limit - The most bytes to read.\n * @returns The body, or `null` when it is larger than `limit`.\n */\nasync function readBodyUpTo(response: Response, limit: number): Promise<Uint8Array | null> {\n const declared = response.headers.get(\"content-length\");\n if (declared !== null && Number(declared) > limit) {\n await response.body?.cancel().catch(() => {});\n return null;\n }\n if (!response.body) {\n const bytes = new Uint8Array(await response.arrayBuffer());\n return bytes.byteLength > limit ? null : bytes;\n }\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let length = 0;\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n length += value.byteLength;\n if (length > limit) {\n await reader.cancel().catch(() => {});\n return null;\n }\n chunks.push(value);\n }\n const bytes = new Uint8Array(length);\n let offset = 0;\n for (const chunk of chunks) {\n bytes.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return bytes;\n}\n","// The deployment capability model (issue #1673).\n//\n// A *deployment* — a hosted container, a kiosk terminal, a classroom instance,\n// an embed — declares which privileged actions the build it serves is allowed\n// to offer. This is deliberately coarse: it names whole capabilities (\"may add\n// data at all\"), not individual menu items, so a locked-down deployment cannot\n// be defeated by a menu item someone forgot to list.\n//\n// It is *not* the interface profile (`ui-profile.ts` in the desktop app), which\n// hides items to reduce clutter and which the user can turn back on. A denied\n// capability is a statement by whoever stood the deployment up, and the UI\n// never offers a way to grant it back.\n//\n// The client-side gate is one half of the model: it stops the app advertising\n// and invoking what the deployment forbids. The serving layer still has to\n// refuse the corresponding requests, because a client-side-only kiosk mode is a\n// suggestion that anyone with devtools declines.\n\n/** A privileged action a deployment can grant or withhold. */\nexport type DeploymentCapability =\n /** Create, save, or otherwise author the project itself. */\n | \"project:edit\"\n /** Bring new data into the map from files, URLs, or services. */\n | \"data:add\"\n /** Run processing/analysis tools (Whitebox, SQL, vector/raster, ML). */\n | \"processing:run\"\n /** Get data or renderings back out (export image, standalone HTML, …). */\n | \"export:data\"\n /** Install, load, or manage plugins. */\n | \"plugins:install\"\n /** Reach the application settings. */\n | \"settings:manage\";\n\n/** Every capability, in a stable order for docs and tests. */\nexport const DEPLOYMENT_CAPABILITIES: readonly DeploymentCapability[] = [\n \"project:edit\",\n \"data:add\",\n \"processing:run\",\n \"export:data\",\n \"plugins:install\",\n \"settings:manage\",\n];\n\n/**\n * The default grant: everything. `full` must stay the default so an existing\n * deployment that configures nothing behaves exactly as it did before.\n */\nexport const ALL_DEPLOYMENT_CAPABILITIES: ReadonlySet<DeploymentCapability> = new Set(\n DEPLOYMENT_CAPABILITIES,\n);\n\n/** Whether an arbitrary string names a capability we know how to enforce. */\nexport function isDeploymentCapability(value: string): value is DeploymentCapability {\n return (DEPLOYMENT_CAPABILITIES as readonly string[]).includes(value);\n}\n\n/**\n * The reserved token that grants nothing, for a deployment that wants the map\n * and nothing else.\n *\n * Needed because a blank value cannot mean it: the deployment env reader treats\n * an empty string as unset (a `-e VAR=` produces one), and unset has to keep\n * meaning \"full\" so existing deployments are unchanged. Without a spelling for\n * \"none\", the most locked-down configuration would be the one you cannot write.\n */\nexport const NO_DEPLOYMENT_CAPABILITIES = \"none\";\n\n/**\n * Parse a comma-separated capability list from deployment configuration.\n *\n * Unknown tokens are dropped rather than throwing: a newer container image may\n * be configured with a capability this build does not know about, and the safe\n * reading of an unrecognized grant is \"not granted\".\n *\n * Fails closed on purpose. An operator who set the variable at all meant to\n * restrict something, so a list that parses to nothing grants nothing — only an\n * absent (or blank) value falls back to the full set, which is the caller's job\n * to detect.\n *\n * @param raw - The configured value, e.g. `\"data:add, export:data\"`.\n * @returns The granted capabilities, with duplicates and unknown names removed.\n */\nexport function parseDeploymentCapabilities(raw: string): ReadonlySet<DeploymentCapability> {\n const granted = new Set<DeploymentCapability>();\n if (raw.trim().toLowerCase() === NO_DEPLOYMENT_CAPABILITIES) return granted;\n for (const token of raw.split(\",\")) {\n const name = token.trim();\n if (isDeploymentCapability(name)) granted.add(name);\n }\n return granted;\n}\n","/**\n * What this deployment and this session may do: deployment capabilities\n * (issue #1673) and the application role / privilege model (issue #1672).\n * Excluded from the project file and from undo history.\n */\nimport { ALL_DEPLOYMENT_CAPABILITIES, type DeploymentCapability } from \"../deployment-capabilities\";\nimport {\n createDefaultAppCapabilities,\n hasAppPrivilege,\n normalizeAppPrivileges,\n resolveRolePrivileges,\n} from \"../capabilities\";\nimport type { AppCapabilities, AppPrivilege, AppRole } from \"../types\";\nimport type { SliceCreator } from \"./types\";\n\nexport interface CapabilitiesSlice {\n /**\n * What this *deployment* is allowed to do (issue #1673). Set once at startup\n * from the deployment configuration; never from a project file, a URL\n * parameter, or anything else the visitor controls, and never edited from the\n * UI. Defaults to the full set so an unconfigured build behaves as before.\n *\n * Excluded from the project file and from undo history: it describes the\n * server that served the app, not the document being edited.\n */\n deploymentCapabilities: ReadonlySet<DeploymentCapability>;\n /**\n * Ephemeral application capability model (issue #1672). Gating role and\n * privileges for the current session/deployment. Excluded from the project file\n * and undo history.\n */\n capabilities: AppCapabilities;\n\n /**\n * Narrow what this deployment may do. Intended for the startup path only —\n * calling it later would leave already-rendered surfaces stale.\n */\n setDeploymentCapabilities: (capabilities: Iterable<DeploymentCapability>) => void;\n /**\n * Assign an application role (e.g. \"viewer\", \"editor\", \"publisher\", \"administrator\", \"custom\"),\n * deriving the effective privileges and optional reason.\n */\n setAppRole: (\n role: AppRole,\n options?: { customPrivileges?: AppPrivilege[]; reason?: string },\n ) => void;\n /** Set explicit custom privileges and an optional reason. */\n setAppPrivileges: (privileges: AppPrivilege[], reason?: string) => void;\n /** Grant an individual application privilege. */\n grantAppPrivilege: (privilege: AppPrivilege) => void;\n /** Revoke an individual application privilege with an optional reason. */\n revokeAppPrivilege: (privilege: AppPrivilege, reason?: string) => void;\n /** Reset application capabilities back to the default unconstrained Administrator role. */\n resetAppCapabilities: () => void;\n /** Check if the current capabilities grant the requested privilege. */\n hasAppPrivilege: (privilege: AppPrivilege) => boolean;\n}\n\nexport const createCapabilitiesSlice: SliceCreator<CapabilitiesSlice> = (set, get) => ({\n deploymentCapabilities: ALL_DEPLOYMENT_CAPABILITIES,\n capabilities: createDefaultAppCapabilities(),\n\n setDeploymentCapabilities: (capabilities) =>\n set({ deploymentCapabilities: new Set(capabilities) }),\n\n // A new role or privilege list is a new policy, so the per-privilege reasons\n // recorded against the old one go with it — carrying them forward would\n // explain a grant that is no longer withheld for that cause.\n setAppRole: (role, options) => {\n const privileges = resolveRolePrivileges(role, options?.customPrivileges);\n set({\n capabilities: {\n role,\n privileges,\n reason: options?.reason,\n },\n });\n },\n\n setAppPrivileges: (privileges, reason) => {\n set({\n capabilities: {\n role: \"custom\",\n privileges: normalizeAppPrivileges(privileges) ?? [],\n reason,\n },\n });\n },\n\n // An ad-hoc grant or revoke makes the set no longer the bundle its role\n // names, so the role becomes \"custom\" — the same thing setAppPrivileges\n // does for an explicit list. Leaving it as \"editor\" while the privileges\n // are not the editor bundle would mislead anything that branches on the\n // role rather than checking a privilege.\n grantAppPrivilege: (privilege) => {\n const current = get().capabilities;\n if (current.privileges.includes(privilege)) return;\n const { [privilege]: _granted, ...privilegeReasons } = current.privilegeReasons ?? {};\n set({\n capabilities: {\n ...current,\n role: \"custom\",\n privileges: [...current.privileges, privilege],\n privilegeReasons,\n },\n });\n },\n\n // The reason is filed against this privilege, not against the whole set:\n // revoking a second privilege for a different cause must not relabel the\n // first one's explanation. `reason` stays the fallback for the rest.\n //\n // Re-revoking an already-withheld privilege is not a no-op when it carries\n // a new reason: restating why something is denied is a real operation, and\n // an early return would silently keep the stale explanation on screen.\n revokeAppPrivilege: (privilege, reason) => {\n const current = get().capabilities;\n const held = current.privileges.includes(privilege);\n if (!held && !reason) return;\n set({\n capabilities: {\n ...current,\n role: held ? \"custom\" : current.role,\n privileges: held ? current.privileges.filter((p) => p !== privilege) : current.privileges,\n privilegeReasons: reason\n ? { ...current.privilegeReasons, [privilege]: reason }\n : current.privilegeReasons,\n },\n });\n },\n\n resetAppCapabilities: () => {\n set({ capabilities: createDefaultAppCapabilities() });\n },\n\n hasAppPrivilege: (privilege) => {\n return hasAppPrivilege(get().capabilities, privilege);\n },\n});\n","/**\n * Ephemeral live-collaboration session state (issue #307): never saved with the\n * project and never tracked by undo history.\n */\nimport type {\n CollabInvite,\n CollaborationChatMessage,\n CollaborationParticipant,\n CollaborationPresence,\n CollaborationState,\n} from \"../types\";\nimport type { SliceCreator } from \"./types\";\n\n/**\n * A fresh, inactive collaboration slice (no live session). Frozen (like\n * DEFAULT_LEGEND_CONFIG) to guard against accidental in-place mutation; store\n * actions always produce new objects via spread, so the frozen default is only\n * ever read.\n */\nexport const DEFAULT_COLLABORATION_STATE: CollaborationState = Object.freeze({\n isActive: false,\n connecting: false,\n sessionId: null,\n clientId: null,\n role: null,\n mode: \"co-edit\",\n selfName: \"\",\n selfColor: \"\",\n participants: Object.freeze([] as CollaborationParticipant[]) as CollaborationParticipant[],\n presence: Object.freeze({} as Record<string, CollaborationPresence>) as Record<\n string,\n CollaborationPresence\n >,\n followHost: false,\n chat: Object.freeze([] as CollaborationChatMessage[]) as CollaborationChatMessage[],\n requireIdentity: false,\n identitySupported: false,\n lockedLayerIds: Object.freeze([] as string[]) as string[],\n invites: Object.freeze([] as CollabInvite[]) as CollabInvite[],\n error: null,\n});\n\n// Cap the in-store chat log so a long session can't grow it without bound. This\n// is intentionally larger than the relay's persisted history (50): the live\n// session accumulates messages locally, while the relay only retains the tail\n// for late joiners.\nconst MAX_COLLABORATION_CHAT = 200;\n\nexport interface CollaborationSlice {\n // Ephemeral live-collaboration session state (issue #307). Deliberately\n // excluded from the project file (project.ts never reads it) and from undo\n // history (partialize never lists it).\n collaboration: CollaborationState;\n\n setCollaboration: (patch: Partial<CollaborationState>) => void;\n updateCollaborationPresence: (clientId: string, presence: CollaborationPresence | null) => void;\n /** Append a chat message to the session log (bounded; #754). */\n addCollaborationChat: (message: CollaborationChatMessage) => void;\n resetCollaboration: () => void;\n}\n\nexport const createCollaborationSlice: SliceCreator<CollaborationSlice> = (set) => ({\n collaboration: DEFAULT_COLLABORATION_STATE,\n\n setCollaboration: (patch) => set((s) => ({ collaboration: { ...s.collaboration, ...patch } })),\n // Add or remove a single remote participant's presence without rebuilding\n // the whole map on every cursor move. Passing `null` drops the entry (on\n // participant leave).\n updateCollaborationPresence: (clientId, presence) =>\n set((s) => {\n const next = { ...s.collaboration.presence };\n if (presence === null) {\n delete next[clientId];\n } else {\n next[clientId] = presence;\n }\n return { collaboration: { ...s.collaboration, presence: next } };\n }),\n addCollaborationChat: (message) =>\n set((s) => {\n // Default to [] in case an older relay left the slice undefined.\n const current = s.collaboration.chat ?? [];\n // Ignore a duplicate id: the server broadcasts to the sender too, and a\n // reconnect can replay recent history, so de-dupe defensively.\n if (current.some((m) => m.id === message.id)) return s;\n const chat = [...current, message].slice(-MAX_COLLABORATION_CHAT);\n return { collaboration: { ...s.collaboration, chat } };\n }),\n resetCollaboration: () =>\n // Also close the Collaborate dialog: an unexpected disconnect resets the\n // slice without a user-initiated leave(), and leaving the dialog open\n // would drop the user onto the start/join form with no context.\n set((s) => ({\n collaboration: DEFAULT_COLLABORATION_STATE,\n ui: { ...s.ui, collaborateDialogOpen: false },\n })),\n});\n","/**\n * Per-session selection and live readouts: the selected layer and features,\n * the Identify target, the attribute-table filter, and the pointer / camera /\n * GPS readouts the status bar shows. None of it is saved with the project or\n * tracked by undo history.\n */\nimport type { SliceCreator } from \"./types\";\n\n/**\n * Latest live device-GPS fix published by the GPS Tracking tool (issue #1316),\n * read by the status bar readout. Device state, not project state: excluded\n * from undo history (partialize never lists it) and from project files, and\n * deliberately left untouched on project switches.\n */\nexport interface GpsStatusFix {\n lng: number;\n lat: number;\n /** Horizontal accuracy radius in meters. */\n accuracy: number;\n /** Satellites used for the fix, or null when the provider does not report it. */\n satellites: number | null;\n /** Ground speed in m/s, or null when the device doesn't report one. */\n speed: number | null;\n /** Fix time in epoch milliseconds. */\n timestamp: number;\n}\n\n/** Reserved Identify target for querying every visible queryable layer at once. */\nexport const IDENTIFY_ALL_LAYERS_ID = \"__geolibre_identify_all_layers__\";\n\n/** The Identify fields of the store, as set together by an Identify change. */\nexport interface IdentifyState {\n identifyLayerId: string | null;\n identifyLayerIds: string[] | null;\n}\n\n/**\n * Resolve an Identify target against the layers that exist.\n *\n * Shared by the scripting `setIdentify` command and by project load, which\n * applies a project's saved `interaction.identify` (issue #2688).\n *\n * @param target - A layer id, `\"all\"`, a list of layer ids, or null/undefined.\n * @param layerIds - Ids of the layers currently in the project.\n * @returns The store fields to set. Ids that name no layer are dropped; a list\n * that keeps one id becomes a single-layer target, and one that keeps several\n * becomes the all-layers mode restricted to those ids.\n */\nexport function resolveIdentifyTarget(\n target: string | readonly string[] | null | undefined,\n layerIds: Iterable<string>,\n): IdentifyState {\n const off: IdentifyState = { identifyLayerId: null, identifyLayerIds: null };\n if (target === null || target === undefined) return off;\n if (target === \"all\") return { identifyLayerId: IDENTIFY_ALL_LAYERS_ID, identifyLayerIds: null };\n const known = new Set(layerIds);\n if (typeof target === \"string\") {\n return known.has(target) ? { identifyLayerId: target, identifyLayerIds: null } : off;\n }\n const ids = [...new Set(target)].filter((id) => known.has(id));\n if (ids.length === 0) return off;\n if (ids.length === 1) return { identifyLayerId: ids[0], identifyLayerIds: null };\n return { identifyLayerId: IDENTIFY_ALL_LAYERS_ID, identifyLayerIds: ids };\n}\n\n/**\n * Whether the all-layers Identify mode may query a layer.\n *\n * @param layerId - The candidate layer's id.\n * @param identifyLayerIds - The store's `identifyLayerIds` restriction.\n * @returns True when there is no restriction or the layer is in it.\n */\nexport function identifyAllIncludes(\n layerId: string,\n identifyLayerIds: readonly string[] | null | undefined,\n): boolean {\n return !identifyLayerIds || identifyLayerIds.includes(layerId);\n}\n\n/**\n * Drop removed layers from the Identify state.\n *\n * @param state - The current Identify fields.\n * @param removed - Ids of the layers being removed.\n * @returns The Identify fields with those layers gone. A restricted all-layers\n * mode left with one layer narrows to it, and with none turns Identify off.\n */\nexport function identifyStateWithoutLayers(\n state: IdentifyState,\n removed: ReadonlySet<string>,\n): IdentifyState {\n if (state.identifyLayerId !== null && removed.has(state.identifyLayerId)) {\n return { identifyLayerId: null, identifyLayerIds: null };\n }\n // Return fresh fields, never `state` itself: callers spread the result into\n // a store update and pass the whole store as `state`.\n const unchanged = {\n identifyLayerId: state.identifyLayerId,\n identifyLayerIds: state.identifyLayerIds,\n };\n if (!state.identifyLayerIds) return unchanged;\n const ids = state.identifyLayerIds.filter((id) => !removed.has(id));\n if (ids.length === state.identifyLayerIds.length) return unchanged;\n if (ids.length === 0) return { identifyLayerId: null, identifyLayerIds: null };\n if (ids.length === 1) return { identifyLayerId: ids[0], identifyLayerIds: null };\n return { identifyLayerId: state.identifyLayerId, identifyLayerIds: ids };\n}\n\nexport interface SessionSlice {\n /** Local viewing switch; saved layer popup settings are left untouched. */\n hoverTooltipsEnabled: boolean;\n setHoverTooltipsEnabled: (enabled: boolean) => void;\n selectedLayerId: string | null;\n selectedFeatureId: string | null;\n /**\n * Full set of selected feature ids. The attribute table extends the single\n * selection to many rows via Ctrl/Cmd (toggle) and Shift (range). The anchor\n * — `selectedFeatureId` — is the primary/last-clicked feature used for map\n * fit, DuckDB highlight, and scripting, and is always one of these ids (or\n * `null` when the set is empty). A single click leaves exactly one id here.\n */\n selectedFeatureIds: string[];\n /**\n * Store-layer id targeted by Identify, or {@link IDENTIFY_ALL_LAYERS_ID} for\n * the map-level mode that queries every visible queryable layer — vector,\n * DuckDB query, WMS, COG, NetCDF image and time-slider raster alike.\n */\n identifyLayerId: string | null;\n /**\n * Layers the all-layers Identify mode is limited to, or null for every\n * visible queryable layer. Set only alongside {@link IDENTIFY_ALL_LAYERS_ID},\n * by a script or project that names several layers (issue #2688), and\n * cleared by every `setIdentifyLayer` call so the in-app buttons keep their\n * plain meaning.\n */\n identifyLayerIds: string[] | null;\n /** Transient map-selection gesture activity, published by the primary renderer. */\n featureSelectionActive: boolean;\n setFeatureSelectionActive: (active: boolean) => void;\n pointerCoords: [number, number] | null;\n /**\n * Ground elevation in true metres under the pointer, for the status bar\n * (issue #1813). Null when it cannot be resolved — the pointer is off the\n * map, terrain is off and the remote lookup has not answered (or failed), or\n * the active body is not Earth. Set alongside `pointerCoords` by MapCanvas,\n * which owns the map instance the terrain sample comes from.\n */\n pointerElevation: number | null;\n /**\n * Camera height above sea level in metres — Google Earth Pro's \"Eye alt\"\n * (issue #1816). Derived from the camera, so deliberately *not* part of\n * `mapView`: that shape is persisted into the project file, and a stored\n * altitude could only drift from the center/zoom/pitch it is computed from.\n * Null before the map loads, or when MapLibre cannot report it.\n */\n cameraAltitude: number | null;\n /** Live GPS fix for the status bar, or null while GPS tracking is off. */\n gpsStatus: GpsStatusFix | null;\n attributeFilter: string;\n\n setPointerCoords: (coords: [number, number] | null) => void;\n setPointerElevation: (elevation: number | null) => void;\n setCameraAltitude: (altitude: number | null) => void;\n setGpsStatus: (fix: GpsStatusFix | null) => void;\n selectLayer: (id: string | null) => void;\n selectFeature: (id: string | null) => void;\n /**\n * Replace the multi-selection with `ids`. The anchor (`selectedFeatureId`)\n * becomes `anchorId` when provided, otherwise the last id in the list (or\n * `null` when the list is empty).\n */\n selectFeatures: (ids: string[], anchorId?: string | null) => void;\n setIdentifyLayer: (id: string | null) => void;\n /** Set both Identify fields at once (see {@link resolveIdentifyTarget}). */\n setIdentifyState: (state: IdentifyState) => void;\n setAttributeFilter: (filter: string) => void;\n}\n\nexport const createSessionSlice: SliceCreator<SessionSlice> = (set) => ({\n hoverTooltipsEnabled: true,\n setHoverTooltipsEnabled: (enabled) => set({ hoverTooltipsEnabled: enabled }),\n selectedLayerId: null,\n selectedFeatureId: null,\n selectedFeatureIds: [],\n identifyLayerId: null,\n identifyLayerIds: null,\n featureSelectionActive: false,\n setFeatureSelectionActive: (active) => set({ featureSelectionActive: active }),\n pointerCoords: null,\n pointerElevation: null,\n cameraAltitude: null,\n gpsStatus: null,\n attributeFilter: \"\",\n\n setPointerCoords: (coords) =>\n set(coords ? { pointerCoords: coords } : { pointerCoords: null, pointerElevation: null }),\n setPointerElevation: (elevation) => set({ pointerElevation: elevation }),\n setCameraAltitude: (altitude) => set({ cameraAltitude: altitude }),\n setGpsStatus: (fix) => set({ gpsStatus: fix }),\n\n selectLayer: (id) =>\n set({\n selectedLayerId: id,\n selectedFeatureId: null,\n selectedFeatureIds: [],\n }),\n // `\"\"` is a valid feature id; only `null` clears the selection.\n selectFeature: (id) =>\n set({ selectedFeatureId: id, selectedFeatureIds: id === null ? [] : [id] }),\n selectFeatures: (ids, anchorId) =>\n set({\n selectedFeatureIds: ids,\n // Enforce the documented invariant for every caller: the anchor is\n // always a member of the set (or null when empty). A supplied anchor\n // that isn't in `ids` falls back to the last id rather than pointing\n // the map fit / calculator sample at an unselected feature.\n selectedFeatureId:\n anchorId != null && ids.includes(anchorId) ? anchorId : (ids.at(-1) ?? null),\n }),\n setIdentifyLayer: (id) => set({ identifyLayerId: id, identifyLayerIds: null }),\n setIdentifyState: ({ identifyLayerId, identifyLayerIds }) =>\n set({ identifyLayerId, identifyLayerIds }),\n setAttributeFilter: (filter) => set({ attributeFilter: filter }),\n});\n","/**\n * Reference scrubbing shared by the actions that delete layers (`removeLayer`\n * and `removeLayerGroup` with `removeChildren`), so a deleted layer never\n * leaves a dangling id in a story map, a secondary pane, or a join.\n */\nimport { cascadeLayerJoinRefresh } from \"../joins\";\nimport { removedLayerIdSet } from \"../layer-ref-scrub\";\nimport type { GeoLibreLayer, SecondaryMapView, StoryMap } from \"../types\";\n\n/**\n * Strip storymap chapter enter/exit opacity rows that reference any of the\n * removed layer ids. Returns the same reference when nothing changes so\n * callers can avoid unnecessary storymap churn.\n */\nexport function scrubStorymapLayerRefs(\n storymap: StoryMap | null,\n layerIds: string | Iterable<string>,\n): StoryMap | null {\n if (!storymap) return null;\n const removed = removedLayerIdSet(layerIds);\n if (removed.size === 0) return storymap;\n let changed = false;\n const chapters = storymap.chapters.map((chapter) => {\n const onChapterEnter = chapter.onChapterEnter.filter((change) => !removed.has(change.layerId));\n const onChapterExit = chapter.onChapterExit.filter((change) => !removed.has(change.layerId));\n if (\n onChapterEnter.length === chapter.onChapterEnter.length &&\n onChapterExit.length === chapter.onChapterExit.length\n ) {\n return chapter;\n }\n changed = true;\n return { ...chapter, onChapterEnter, onChapterExit };\n });\n return changed ? { ...storymap, chapters } : storymap;\n}\n\n/**\n * Drop per-pane visibility overrides for removed layer ids so stale keys do\n * not accumulate (or serialize) in secondary panes after deletion.\n */\nexport function scrubSecondaryPaneLayerVisibility(\n panes: SecondaryMapView[],\n layerIds: string | Iterable<string>,\n): SecondaryMapView[] {\n const removed = removedLayerIdSet(layerIds);\n if (removed.size === 0) return panes;\n let anyChanged = false;\n const next = panes.map((pane) => {\n let changed = false;\n const layerVisibility: Record<string, boolean> = {};\n for (const [id, visible] of Object.entries(pane.layerVisibility)) {\n if (removed.has(id)) {\n changed = true;\n continue;\n }\n layerVisibility[id] = visible;\n }\n if (!changed) return pane;\n anyChanged = true;\n return { ...pane, layerVisibility };\n });\n return anyChanged ? next : panes;\n}\n\n/** Re-derive layers whose joins consumed any of the removed sources. */\nexport function cascadeJoinRefreshForRemoved(\n layers: GeoLibreLayer[],\n layerIds: string | Iterable<string>,\n): GeoLibreLayer[] {\n let current = layers;\n for (const id of removedLayerIdSet(layerIds)) {\n current = cascadeLayerJoinRefresh(current, id);\n }\n return current;\n}\n","/**\n * Layer-panel folders: create, rename, nest, reorder, sort and delete groups,\n * and move layers between them. `layerGroups` is tracked by undo history\n * except for the `collapsed` flag (see `undo-history.ts`).\n */\nimport { v4 as uuidv4 } from \"uuid\";\nimport {\n DEFAULT_LAYER_GROUP_OPACITY,\n normalizeGroupContiguity,\n reorderLayerGroupInPanel,\n sortLayerGroupInPanel,\n type LayerGroupSortOrder,\n} from \"../layer-groups\";\nimport {\n scrubCommentsForRemovedLayers,\n scrubLegendForRemovedLayers,\n scrubWidgetsForRemovedLayers,\n} from \"../layer-ref-scrub\";\nimport { scrubPrintLayoutForRemovedLayers } from \"../print-layout-config\";\nimport { identifyStateWithoutLayers } from \"./session-slice\";\nimport type { LayerGroup } from \"../types\";\nimport {\n cascadeJoinRefreshForRemoved,\n scrubSecondaryPaneLayerVisibility,\n scrubStorymapLayerRefs,\n} from \"./layer-removal\";\nimport type { SliceCreator } from \"./types\";\n\n/**\n * Pick the lowest `Group N` name not already taken, so default names stay\n * unique while still preferring small numbers — starting the search at 1 (not\n * `length + 1`) avoids skipping free low numbers when some groups carry custom\n * names. Group counts are small, so the linear scan is negligible.\n */\nfunction nextDefaultGroupName(groups: LayerGroup[]): string {\n const existing = new Set(groups.map((g) => g.name));\n let n = 1;\n while (existing.has(`Group ${n}`)) n++;\n return `Group ${n}`;\n}\n\nexport interface LayerGroupsSlice {\n layerGroups: LayerGroup[];\n\n addLayerGroup: (name?: string, layerIds?: string[]) => string;\n removeLayerGroup: (id: string, options?: { removeChildren?: boolean }) => void;\n renameLayerGroup: (id: string, name: string) => void;\n setLayerGroupVisibility: (id: string, visible: boolean) => void;\n setLayerGroupOpacity: (id: string, opacity: number) => void;\n toggleLayerGroupCollapsed: (id: string) => void;\n moveLayerToGroup: (\n layerId: string,\n groupId: string | null,\n beforeLayerId?: string | null,\n ) => void;\n moveLayersToGroup: (\n layerIds: string[],\n groupId: string | null,\n beforeLayerId?: string | null,\n ) => void;\n moveLayerGroupToGroup: (id: string, parentId: string | null) => void;\n reorderLayerGroup: (id: string, direction: \"up\" | \"down\") => void;\n /**\n * Sort a group's direct children by name, A to Z or Z to A (top of panel\n * first), collating by `locale` (the app's display language) when given.\n */\n sortLayerGroup: (id: string, order: LayerGroupSortOrder, locale?: string) => void;\n}\n\nexport const createLayerGroupsSlice: SliceCreator<LayerGroupsSlice> = (set, get) => ({\n layerGroups: [],\n\n addLayerGroup: (name, layerIds) => {\n const id = uuidv4();\n set((s) => {\n const group: LayerGroup = {\n id,\n name: name?.trim() || nextDefaultGroupName(s.layerGroups),\n collapsed: false,\n visible: true,\n opacity: DEFAULT_LAYER_GROUP_OPACITY,\n };\n const ids = new Set(layerIds ?? []);\n const layers =\n ids.size > 0\n ? normalizeGroupContiguity(\n s.layers.map((l) => (ids.has(l.id) ? { ...l, groupId: id } : l)),\n )\n : s.layers;\n return {\n layers,\n layerGroups: [...s.layerGroups, group],\n isDirty: true,\n };\n });\n return id;\n },\n\n removeLayerGroup: (id, options) =>\n set((s) => {\n const removeChildren = options?.removeChildren ?? false;\n const removedGroup = s.layerGroups.find((g) => g.id === id);\n if (!removedGroup) return s;\n const groupIds = new Set([id]);\n if (removeChildren) {\n let changed = true;\n while (changed) {\n changed = false;\n for (const group of s.layerGroups) {\n if (group.parentId && groupIds.has(group.parentId) && !groupIds.has(group.id)) {\n groupIds.add(group.id);\n changed = true;\n }\n }\n }\n }\n const removedIds = new Set(\n s.layers.filter((l) => l.groupId && groupIds.has(l.groupId)).map((l) => l.id),\n );\n let layers = removeChildren\n ? s.layers.filter((l) => !l.groupId || !groupIds.has(l.groupId))\n : s.layers.map((l) => (l.groupId === id ? { ...l, groupId: undefined } : l));\n // Match removeLayer: refreshing joins and scrubbing secondary-pane /\n // storymap refs for every deleted child so group delete cannot leave\n // stale joined columns or dangling visibility overrides behind.\n if (removeChildren && removedIds.size > 0) {\n layers = cascadeJoinRefreshForRemoved(layers, removedIds);\n }\n const selectionRemoved =\n removeChildren && s.selectedLayerId !== null && removedIds.has(s.selectedLayerId);\n return {\n layers,\n layerGroups: s.layerGroups\n .filter((g) => !groupIds.has(g.id))\n .map((g) => (g.parentId === id ? { ...g, parentId: removedGroup.parentId } : g)),\n secondaryMapViews: removeChildren\n ? scrubSecondaryPaneLayerVisibility(s.secondaryMapViews, removedIds)\n : s.secondaryMapViews,\n storymap: removeChildren ? scrubStorymapLayerRefs(s.storymap, removedIds) : s.storymap,\n widgets: removeChildren ? scrubWidgetsForRemovedLayers(s.widgets, removedIds) : s.widgets,\n comments: removeChildren\n ? scrubCommentsForRemovedLayers(s.comments, removedIds)\n : s.comments,\n legend: removeChildren ? scrubLegendForRemovedLayers(s.legend, removedIds) : s.legend,\n printLayout: removeChildren\n ? scrubPrintLayoutForRemovedLayers(s.printLayout, removedIds)\n : s.printLayout,\n selectedLayerId: selectionRemoved\n ? (layers[layers.length - 1]?.id ?? null)\n : s.selectedLayerId,\n selectedFeatureId: selectionRemoved ? null : s.selectedFeatureId,\n selectedFeatureIds: selectionRemoved ? [] : s.selectedFeatureIds,\n // Children kept at the top level are still in the project, so they\n // keep their place in the Identify target.\n ...(removeChildren\n ? identifyStateWithoutLayers(s, removedIds)\n : { identifyLayerId: s.identifyLayerId, identifyLayerIds: s.identifyLayerIds }),\n ui: removeChildren\n ? {\n ...s.ui,\n selectByExpressionLayerId:\n s.ui.selectByExpressionLayerId !== null &&\n removedIds.has(s.ui.selectByExpressionLayerId)\n ? null\n : s.ui.selectByExpressionLayerId,\n selectByLocationLayerId:\n s.ui.selectByLocationLayerId !== null &&\n removedIds.has(s.ui.selectByLocationLayerId)\n ? null\n : s.ui.selectByLocationLayerId,\n loadEditorFeaturesLayerId:\n s.ui.loadEditorFeaturesLayerId !== null &&\n removedIds.has(s.ui.loadEditorFeaturesLayerId)\n ? null\n : s.ui.loadEditorFeaturesLayerId,\n }\n : s.ui,\n isDirty: true,\n };\n }),\n\n renameLayerGroup: (id, name) =>\n set((s) => ({\n layerGroups: s.layerGroups.map((g) => (g.id === id ? { ...g, name } : g)),\n isDirty: true,\n })),\n\n setLayerGroupVisibility: (id, visible) =>\n set((s) => ({\n layerGroups: s.layerGroups.map((g) => (g.id === id ? { ...g, visible } : g)),\n isDirty: true,\n })),\n\n setLayerGroupOpacity: (id, opacity) =>\n set((s) => ({\n layerGroups: s.layerGroups.map((g) =>\n g.id === id ? { ...g, opacity: Math.min(Math.max(opacity, 0), 1) } : g,\n ),\n isDirty: true,\n })),\n\n // Collapsing/expanding a folder is a UI-panel preference, not a data\n // edit: it is still persisted in the project (folders reopen collapsed),\n // but it does not mark the project dirty and is excluded from undo (see\n // the equality comparator in undo-history.ts) so Ctrl-Z never toggles a\n // folder.\n toggleLayerGroupCollapsed: (id) =>\n set((s) => ({\n layerGroups: s.layerGroups.map((g) => (g.id === id ? { ...g, collapsed: !g.collapsed } : g)),\n })),\n\n moveLayerToGroup: (layerId, groupId, beforeLayerId = null) =>\n get().moveLayersToGroup([layerId], groupId, beforeLayerId),\n\n moveLayersToGroup: (layerIds, groupId, beforeLayerId = null) =>\n set((s) => {\n if (groupId && !s.layerGroups.some((g) => g.id === groupId)) return s;\n const requestedIds = new Set(layerIds);\n const moving = s.layers.filter(\n (layer) =>\n requestedIds.has(layer.id) &&\n (beforeLayerId !== null || (layer.groupId ?? null) !== groupId),\n );\n if (moving.length === 0) return s;\n const movingIds = new Set(moving.map((layer) => layer.id));\n const without = s.layers.filter((layer) => !movingIds.has(layer.id));\n const updated = moving.map((layer) => ({\n ...layer,\n groupId: groupId ?? undefined,\n }));\n let index: number;\n if (beforeLayerId && !movingIds.has(beforeLayerId)) {\n const at = without.findIndex((layer) => layer.id === beforeLayerId);\n index = at < 0 ? without.length : at;\n } else if (groupId) {\n let last = -1;\n without.forEach((layer, layerIndex) => {\n if (layer.groupId === groupId) last = layerIndex;\n });\n index = last < 0 ? without.length : last + 1;\n } else {\n index = without.length;\n }\n const next = [...without];\n next.splice(index, 0, ...updated);\n const normalized = normalizeGroupContiguity(next);\n const unchanged = normalized.every(\n (layer, layerIndex) =>\n layer.id === s.layers[layerIndex]?.id && layer.groupId === s.layers[layerIndex]?.groupId,\n );\n if (unchanged) return s;\n return { layers: normalized, isDirty: true };\n }),\n\n moveLayerGroupToGroup: (id, parentId) =>\n set((s) => {\n if (parentId === id) return s;\n const group = s.layerGroups.find((g) => g.id === id);\n if (!group) return s;\n if (parentId && !s.layerGroups.some((g) => g.id === parentId)) return s;\n // Walking upward from the proposed parent must never reach the group\n // being moved, otherwise the assignment would create a cycle.\n const byId = new Map(s.layerGroups.map((g) => [g.id, g]));\n let ancestorId = parentId ?? undefined;\n const visited = new Set<string>();\n while (ancestorId && !visited.has(ancestorId)) {\n if (ancestorId === id) return s;\n visited.add(ancestorId);\n ancestorId = byId.get(ancestorId)?.parentId;\n }\n const nextParentId = parentId ?? undefined;\n if (group.parentId === nextParentId) return s;\n return {\n layerGroups: s.layerGroups.map((g) => (g.id === id ? { ...g, parentId: nextParentId } : g)),\n isDirty: true,\n };\n }),\n\n // A folder that owns no layers has no position in `layers` to move, so\n // the panel order it takes part in lives across both arrays and the move\n // writes both (GeoLibre#1739).\n reorderLayerGroup: (id, direction) =>\n set((s) => {\n const moved = reorderLayerGroupInPanel(s.layers, s.layerGroups, id, direction);\n if (!moved) return s;\n return { layers: moved.layers, layerGroups: moved.groups, isDirty: true };\n }),\n\n sortLayerGroup: (id, order, locale) =>\n set((s) => {\n const sorted = sortLayerGroupInPanel(s.layers, s.layerGroups, id, order, locale);\n if (!sorted) return s;\n return { layers: sorted.layers, layerGroups: sorted.groups, isDirty: true };\n }),\n});\n","/**\n * A bare Android SAF document ID, as the browser `File.name` carries after a\n * native import: a known volume prefix (`primary:`, `home:`, `raw:`, `msf:`, or a\n * removable `XXXX-XXXX:` volume), or any provider prefix followed by a folder\n * separator. Ordinary encoded filenames such as `Report%3AQ1.geojson` do not match.\n */\nconst SAF_DOCUMENT_ID =\n /^(?:(?:primary|home|raw|msf|[0-9a-f]{4}-[0-9a-f]{4})%3a|[^%/\\\\:]+%3a.*%2f)/i;\n\n/** Extract a display filename without changing the source used for file access. */\nexport function localFileName(path: string): string {\n const isUri = /^(?:content|file):\\/\\//i.test(path);\n const value = isUri ? path.split(/[?#]/)[0] : path;\n const segment = value.split(/[/\\\\]/).pop() ?? value;\n // Do not decode ordinary filenames: percent escapes can be literal text.\n if (!isUri && (!SAF_DOCUMENT_ID.test(segment) || /[/\\\\]/.test(path))) {\n return segment;\n }\n let decoded = segment;\n try {\n decoded = decodeURIComponent(segment);\n } catch {\n return segment;\n }\n // Strip the provider's volume prefix before taking the final path component.\n return (\n decoded\n .replace(/^[^/\\\\:]+:/, \"\")\n .split(/[/\\\\]/)\n .pop() ?? decoded\n );\n}\n\n/**\n * Whether a layer name was derived from its local source filename (with or\n * without the extension) rather than supplied explicitly by a caller.\n */\nexport function isSourceDerivedLayerName(name: string, sourcePath: string): boolean {\n const fileName = localFileName(sourcePath);\n return name === fileName || name === fileName.replace(/\\.[^.]+$/, \"\");\n}\n\n/** Keep imported layers distinguishable, including within a multi-file import. */\nexport function uniqueImportedLayerName(name: string, names: Iterable<string>): string {\n const existing = new Set(names);\n let candidate = name;\n let suffix = 2;\n while (existing.has(candidate)) candidate = `${name}_${suffix++}`;\n return candidate;\n}\n","import type { FeatureCollection } from \"geojson\";\nimport { DEFAULT_LAYER_STYLE, type GeoLibreLayer, type LayerStyle } from \"./types\";\n\n/**\n * Initial styling for a newly added vector layer (issue #1519).\n *\n * {@link DEFAULT_LAYER_STYLE} stays exactly what it is — the schema fallback\n * that fills gaps when a partial style is read back from a project file. It is\n * deliberately NOT changed here: every load path spreads it under a saved\n * style, so editing it would silently restyle existing projects.\n *\n * What this module adds is a separate decision made only at *add* time: give\n * each new layer its own color, and pick sizes that suit the geometry it\n * actually contains, so a stack of freshly loaded layers is legible without\n * anyone opening the Style panel.\n */\n\n/**\n * Qualitative colors cycled across newly added layers, chosen to stay distinct\n * at low opacity over both light and dark basemaps.\n *\n * Distinct from the classification palette the Style panel uses for the classes\n * *within* one layer: this one separates layers from each other, and the two\n * are free to diverge.\n */\nexport const LAYER_PALETTE = [\n \"#3b82f6\", // blue — matches the historical default, so the first layer looks unchanged\n \"#ef4444\", // red\n \"#22c55e\", // green\n \"#a855f7\", // purple\n \"#f97316\", // orange\n \"#06b6d4\", // cyan\n \"#ec4899\", // pink\n \"#84cc16\", // lime\n] as const;\n\n/** How much darker an outline is than its fill (0 = black, 1 = unchanged). */\nconst STROKE_DARKEN = 0.55;\n\n/** Darken a `#rrggbb` color toward black by `factor`. */\nexport function darkenHex(hex: string, factor: number): string {\n const match = /^#([0-9a-f]{6})$/i.exec(hex.trim());\n if (!match) return hex;\n const value = Number.parseInt(match[1], 16);\n const channels = [(value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff];\n return `#${channels\n .map((channel) =>\n Math.max(0, Math.min(255, Math.round(channel * factor)))\n .toString(16)\n .padStart(2, \"0\"),\n )\n .join(\"\")}`;\n}\n\n/** Case-insensitive palette lookup, returning the canonical (lowercase) entry. */\nfunction paletteEntryOf(color: string | undefined): string | undefined {\n const normalized = (color ?? \"\").trim().toLowerCase();\n return LAYER_PALETTE.find((entry) => entry === normalized);\n}\n\n/**\n * True when a style's colors are a palette assignment {@link initialLayerStyle}\n * made: a palette fill *paired with* the outline derived from it.\n *\n * The pairing is what makes this precise. Testing the fill alone — or the layer\n * type — would miscount every layer built straight from\n * {@link DEFAULT_LAYER_STYLE}, whose `fillColor` happens to be\n * `LAYER_PALETTE[0]`: rasters, tilesets, video overlays, and the non-spatial\n * attribute tables the delimited-text source adds. None of those paint from a\n * collection, so none should reserve blue and push the next added layer to red.\n * They carry the schema's own `strokeColor`, not one derived from the fill, so\n * this test leaves them out.\n */\nfunction wearsPaletteColor(style: LayerStyle | undefined): boolean {\n const entry = paletteEntryOf(style?.fillColor);\n if (!entry) return false;\n return (style?.strokeColor ?? \"\").trim().toLowerCase() === darkenHex(entry, STROKE_DARKEN);\n}\n\n/**\n * Pick the next layer color: the first palette entry no current layer is\n * already using, so deleting a layer frees its color back up rather than\n * leaving the cycle permanently offset. Falls back to cycling by layer count\n * once every entry is in use.\n *\n * @param layers - The project's current layers. Only those actually wearing a\n * palette assignment count; see {@link wearsPaletteColor}.\n * @returns A `#rrggbb` color from {@link LAYER_PALETTE}.\n */\nexport function nextLayerPaletteColor(layers: readonly GeoLibreLayer[]): string {\n const styled = layers.filter((layer) => wearsPaletteColor(layer.style));\n const used = new Set(styled.map((layer) => layer.style.fillColor.trim().toLowerCase()));\n const free = LAYER_PALETTE.find((color) => !used.has(color));\n return free ?? LAYER_PALETTE[styled.length % LAYER_PALETTE.length];\n}\n\n/** The geometry family a layer is mostly made of. */\nexport type DominantGeometry = \"point\" | \"line\" | \"polygon\" | \"mixed\";\n\n/** Features sampled when deciding a collection's dominant geometry. */\nconst GEOMETRY_SAMPLE_SIZE = 500;\n\n/**\n * Classify a collection by the geometry family most of its features carry,\n * reading at most {@link GEOMETRY_SAMPLE_SIZE} features so a very large layer\n * does not pay a full scan for a styling default.\n *\n * @param geojson - The collection to inspect.\n * @returns The dominant family, or `\"mixed\"` when nothing has a clear majority\n * (or the collection is empty).\n */\nexport function dominantGeometry(geojson: FeatureCollection | undefined): DominantGeometry {\n const features = geojson?.features ?? [];\n if (!features.length) return \"mixed\";\n\n const counts = { point: 0, line: 0, polygon: 0 };\n const sampled = Math.min(features.length, GEOMETRY_SAMPLE_SIZE);\n for (let index = 0; index < sampled; index++) {\n switch (features[index]?.geometry?.type) {\n case \"Point\":\n case \"MultiPoint\":\n counts.point += 1;\n break;\n case \"LineString\":\n case \"MultiLineString\":\n counts.line += 1;\n break;\n case \"Polygon\":\n case \"MultiPolygon\":\n counts.polygon += 1;\n break;\n default:\n break;\n }\n }\n\n if (!counts.point && !counts.line && !counts.polygon) return \"mixed\";\n const [family, count] = Object.entries(counts).sort((a, b) => b[1] - a[1])[0] as [\n \"point\" | \"line\" | \"polygon\",\n number,\n ];\n // A clear majority of everything sampled, not just of the geometries this\n // switch understands: two points among three GeometryCollections is not a\n // point layer, so `sampled` is the denominator rather than the matched total.\n // And a majority, not merely the largest slice — a 40/35/25 split has no\n // geometry whose sizing suits the layer.\n return count / sampled > 0.5 ? family : \"mixed\";\n}\n\n/**\n * Per-geometry size and opacity overrides.\n *\n * One set of numbers cannot suit all three families: an opacity that lets a\n * basemap read through a polygon makes a point look washed out, and a stroke\n * heavy enough to see a line is a heavy outline on a polygon.\n */\nconst GEOMETRY_DEFAULTS: Record<DominantGeometry, Partial<LayerStyle>> = {\n // Solid and slightly smaller: a point is read as a dot, not a translucent blob.\n point: { circleRadius: 5, fillOpacity: 0.9, strokeWidth: 1 },\n // A line's stroke *is* the symbol, so it carries the weight.\n line: { strokeWidth: 2.5, fillOpacity: 1 },\n // Translucent enough that the basemap underneath stays readable.\n polygon: { fillOpacity: 0.45, strokeWidth: 1.5 },\n mixed: {},\n};\n\nexport interface InitialLayerStyleOptions {\n /** The layer's data, used to pick geometry-appropriate sizes. */\n geojson?: FeatureCollection;\n /** Current project layers, so the new layer gets an unused palette color. */\n layers?: readonly GeoLibreLayer[];\n /**\n * The layer's geometry family when it is known without `geojson`, as for a\n * layer whose features live in an external renderer. Wins over `geojson`.\n */\n geometry?: DominantGeometry;\n /** Style values that win over the computed defaults (e.g. a restored style). */\n overrides?: Partial<LayerStyle>;\n}\n\n/**\n * Build the style a newly added vector layer starts with: the schema defaults,\n * plus its own palette color and geometry-appropriate sizing.\n *\n * @param options - Data and project context to derive the style from.\n * @returns A complete {@link LayerStyle}.\n */\nexport function initialLayerStyle(options: InitialLayerStyleOptions = {}): LayerStyle {\n const { geojson, layers = [], overrides } = options;\n const fillColor = nextLayerPaletteColor(layers);\n return {\n ...DEFAULT_LAYER_STYLE,\n fillColor,\n strokeColor: darkenHex(fillColor, STROKE_DARKEN),\n ...GEOMETRY_DEFAULTS[options.geometry ?? dominantGeometry(geojson)],\n ...overrides,\n };\n}\n\n/**\n * True when a layer still wears exactly what {@link initialLayerStyle} gave it.\n *\n * The renderer mode alone is too weak a test for \"untouched\": nudging the fill,\n * outline, or opacity leaves the mode on `\"single\"`, and a project restored\n * from disk can carry deliberate single-symbol styling. Both cases should stop\n * the Style panel volunteering suggestions, which is what this gates.\n *\n * The fill color cannot be compared against a fixed value — it is whichever\n * palette entry the layer drew — so the test is that it is *still a palette\n * entry* with the outline derived from it, which a hand-picked color will not\n * satisfy.\n *\n * @param style - The layer's current style.\n * @param geojson - Its data, so the geometry-derived sizes can be recomputed.\n * @returns `true` when nothing about the as-added look has been changed.\n */\nexport function isInitialLayerStyle(style: LayerStyle, geojson?: FeatureCollection): boolean {\n // Authored symbology, whatever the paint fields say.\n if (style.simpleStyleEnabled) return false;\n if (style.vectorStyleMode !== \"single\") return false;\n if (style.pointRenderer !== \"single\") return false;\n if ((style.vectorRules?.length ?? 0) > 0) return false;\n\n if (!wearsPaletteColor(style)) return false;\n\n const expected = { ...DEFAULT_LAYER_STYLE, ...GEOMETRY_DEFAULTS[dominantGeometry(geojson)] };\n return (\n style.fillOpacity === expected.fillOpacity &&\n style.strokeWidth === expected.strokeWidth &&\n style.circleRadius === expected.circleRadius\n );\n}\n","// Copy/paste layer symbology between layers (issue #1339), QGIS-style. The\n// transient clipboard slot and its two store actions live in `store.ts`; this\n// module holds the UI-free data layer so both the desktop app and tests consume\n// the same extract/apply logic.\n\nimport { DEFAULT_LAYER_STYLE, type GeoLibreLayer, type LayerStyle } from \"./types\";\nimport { isStyleLibraryTargetLayer } from \"./style-library\";\n\n/**\n * `metadata.sourceKind` marking a deck.gl COG raster managed by the\n * maplibre-gl-raster control. Mirrors `RASTER_SOURCE_KIND` in\n * `@geolibre/plugins` (which `@geolibre/core` must not depend on). The clipboard\n * test imports the real constant directly from the module that defines it\n * (`packages/plugins/src/plugins/raster-layer-sync.ts`, not the package barrel,\n * whose browser-only plugins crash under the Node test runner) and asserts a\n * layer tagged with it classifies as `\"raster\"`, so a rename of the upstream\n * constant makes the test fail here rather than silently dropping raster\n * copy/paste.\n */\nconst RASTER_SOURCE_KIND = \"maplibre-gl-raster\";\n\n/**\n * Which copy/paste style family a layer belongs to. Vector-styled layers and\n * deck.gl rasters store their symbology in different places (the {@link\n * LayerStyle} bag vs `metadata.rasterState`), so a paste only applies between\n * layers of the same family.\n */\nexport type LayerStyleClipboardKind = \"vector\" | \"raster\";\n\n/**\n * The `metadata.rasterState` keys a raster style paste carries over: the\n * appearance of the render (colormap, stretch range, gamma, nodata), but not\n * the data selection (`mode`/`bands`/`index`), which stays with the target so\n * pasting a style never points a raster at a band it does not have.\n */\nexport const RASTER_APPEARANCE_STATE_KEYS = [\n \"colormap\",\n \"reversed\",\n \"rescale\",\n \"nodata\",\n \"stretch\",\n \"gamma\",\n] as const;\n\n/**\n * Fields every clipboard entry carries, regardless of style family. Layer\n * `opacity` is deliberately excluded: like QGIS \"Paste Style\" and GeoLibre's\n * own Style Manager, copy/paste transfers symbology only and leaves the\n * target's opacity alone (opacity is a per-layer render setting, not style).\n *\n * {@link LayerStyle.blendMode} is deliberately *included*, even though the\n * Layers panel puts the Blend menu beside the opacity slider. The two differ in\n * kind despite sitting together: opacity is a transient \"turn this down so I\n * can see through it\" knob, while a blend mode is a cartographic decision about\n * how the layer reads against the map -- colour over a hillshade is a look, and\n * copying a look should bring it. QGIS agrees; its layer style, and so its\n * Paste Style, carries the blending mode. `tests/layer-style-clipboard.test.ts`\n * pins both halves of this so neither is changed by accident.\n */\ninterface CopiedLayerStyleBase {\n /** Source layer name, surfaced in the paste tooltip and status message. */\n sourceName: string;\n}\n\n/** A copied vector layer style: the whole {@link LayerStyle} bag. */\nexport interface CopiedVectorStyle extends CopiedLayerStyleBase {\n kind: \"vector\";\n /** Deep-cloned full vector layer style. */\n style: LayerStyle;\n}\n\n/**\n * A copied deck.gl raster style: the `metadata.rasterState` appearance and the\n * classification symbology. There is no `style` field — the vector\n * {@link LayerStyle} bag holds nothing a deck.gl raster renders from.\n */\nexport interface CopiedRasterStyle extends CopiedLayerStyleBase {\n kind: \"raster\";\n /** Raster visualization state (`metadata.rasterState`). */\n rasterState?: Record<string, unknown>;\n /** Whether the source carried a `metadata.rasterSymbology`. */\n hasRasterSymbology: boolean;\n /** Raster classification symbology (`metadata.rasterSymbology`). */\n rasterSymbology?: unknown;\n}\n\n/**\n * A layer's symbology captured for the paste clipboard. A discriminated union\n * on {@link LayerStyleClipboardKind} so raster-only fields cannot be read off a\n * vector entry (or vice versa); a paste only applies between layers of the same\n * family.\n */\nexport type CopiedLayerStyle = CopiedVectorStyle | CopiedRasterStyle;\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * The copy/paste style family of a layer, or `null` when the layer has no\n * copyable symbology. Deck.gl rasters are `\"raster\"`; the vector-styled layers\n * the Style Manager targets are `\"vector\"`.\n *\n * @param layer - The layer to classify.\n * @returns The style family, or `null`.\n */\nexport function copyableLayerStyleKind(layer: GeoLibreLayer): LayerStyleClipboardKind | null {\n if (layer.metadata.sourceKind === RASTER_SOURCE_KIND) return \"raster\";\n if (isStyleLibraryTargetLayer(layer.type)) return \"vector\";\n return null;\n}\n\n/**\n * Snapshot a layer's style for the clipboard, deep-cloned so later edits to the\n * live layer cannot mutate the copied entry.\n *\n * @param layer - The source layer to copy from.\n * @returns The clipboard entry, or `null` when the layer has no copyable\n * symbology.\n */\nexport function extractCopiedLayerStyle(layer: GeoLibreLayer): CopiedLayerStyle | null {\n const kind = copyableLayerStyleKind(layer);\n if (!kind) return null;\n if (kind === \"vector\") {\n return {\n kind,\n sourceName: layer.name,\n style: structuredClone({ ...DEFAULT_LAYER_STYLE, ...layer.style }),\n };\n }\n // A raster entry never carries the vector `style` bag, so the full-style\n // clone is skipped here.\n const rasterState = layer.metadata.rasterState;\n const rasterSymbology = layer.metadata.rasterSymbology;\n // Only a real symbology object counts as \"has symbology\". A missing or null\n // value (the latter only reachable via a hand-edited project file) both mean\n // \"none\", so the paste clears the target's symbology rather than writing a\n // literal null onto it — symmetric with the delete path in applyCopiedLayerStyle.\n const hasRasterSymbology = isPlainObject(rasterSymbology);\n return {\n kind,\n sourceName: layer.name,\n rasterState: isPlainObject(rasterState) ? structuredClone(rasterState) : undefined,\n hasRasterSymbology,\n ...(hasRasterSymbology ? { rasterSymbology: structuredClone(rasterSymbology) } : {}),\n };\n}\n\n/**\n * Build the {@link GeoLibreLayer} patch that pastes a copied style onto a\n * target layer, or `null` when the target's style family does not match the\n * clipboard entry's (so the caller can disable the paste action).\n *\n * @param target - The layer to paste onto.\n * @param copied - The clipboard entry.\n * @returns A partial layer patch to merge, or `null` when incompatible.\n */\nexport function applyCopiedLayerStyle(\n target: GeoLibreLayer,\n copied: CopiedLayerStyle,\n): Partial<GeoLibreLayer> | null {\n if (copyableLayerStyleKind(target) !== copied.kind) return null;\n if (copied.kind === \"vector\") {\n // The whole style is applied verbatim, including attribute-bound fields\n // (`vectorStyleProperty` and its stops/rules, `labels.field`,\n // `extrusionHeightProperty`, `proportionalSizeProperty`, `diagramFields`).\n // This is deliberate QGIS \"Paste Style\" behavior and matches the Style\n // Manager's full-\"style\" preset: a target lacking those attributes just\n // renders that facet inert (no classification / labels), which the user\n // re-points, rather than the paste silently dropping parts of the style.\n return { style: structuredClone(copied.style) };\n }\n // Merge the appearance keys onto the target's existing rasterState, keeping\n // its own `mode`/`bands`/`index` so the paste restyles the layer without\n // repointing it at the source's band selection.\n const targetState = isPlainObject(target.metadata.rasterState) ? target.metadata.rasterState : {};\n const source = copied.rasterState ?? {};\n const expectedRescaleLength = rescaleEntryCount(targetState);\n const mergedState: Record<string, unknown> = { ...targetState };\n for (const key of RASTER_APPEARANCE_STATE_KEYS) {\n if (!(key in source)) continue;\n // `rescale` is a per-channel min/max array whose length is set by the\n // render mode (one entry for single/index, one per band for rgb), not by\n // `bands.length` (index mode holds two operand bands but a single-entry\n // rescale). Copying a rescale whose length disagrees with the target's mode\n // — e.g. an RGB source's 3 entries onto a single-band target — would leave a\n // rescale that no longer matches the preserved render mode, the same \"band\n // it does not have\" hazard one level down. Skip it then and let the target\n // keep its own stretch. A null source rescale (auto) is safe to carry over.\n if (\n key === \"rescale\" &&\n Array.isArray(source.rescale) &&\n source.rescale.length !== expectedRescaleLength\n ) {\n continue;\n }\n mergedState[key] = structuredClone(source[key]);\n }\n const metadata: Record<string, unknown> = { ...target.metadata, rasterState: mergedState };\n if (copied.hasRasterSymbology) {\n metadata.rasterSymbology = structuredClone(copied.rasterSymbology);\n } else {\n delete metadata.rasterSymbology;\n }\n return { metadata };\n}\n\n/**\n * Number of `rescale` entries a rasterState of this render mode expects: one\n * per band for `\"rgb\"`, a single entry for `\"single\"` and `\"index\"` (the\n * latter stretches one derived index even though it reads two operand bands).\n */\nfunction rescaleEntryCount(state: Record<string, unknown>): number {\n if (state.mode === \"rgb\") return Array.isArray(state.bands) ? state.bands.length : 3;\n return 1;\n}\n","// Export every layer's style to one JSON file and apply such a file back by\n// layer name: Project → Export → Export Layer Styles, Project → Import →\n// Import Layer Styles, and the Startup setting that restyles each newly added\n// layer whose name matches an entry. A style here is exactly what copy/paste\n// style carries (see `layer-style-clipboard.ts`), so an exported entry restyles\n// a layer the same way pasting a copied style onto it would.\n\nimport {\n applyCopiedLayerStyle,\n type CopiedLayerStyle,\n copyableLayerStyleKind,\n extractCopiedLayerStyle,\n type LayerStyleClipboardKind,\n RASTER_APPEARANCE_STATE_KEYS,\n} from \"./layer-style-clipboard\";\nimport { sanitizeLayerStylePatch } from \"./style-library\";\nimport { DEFAULT_LAYER_STYLE, type GeoLibreLayer, type LayerStyle } from \"./types\";\n\n/** `type` tag identifying a layer styles file. */\nexport const LAYER_STYLES_FILE_TYPE = \"geolibre-layer-styles\";\n\n/** Format version written by {@link serializeLayerStylesFile}. */\nexport const LAYER_STYLES_FILE_VERSION = 1;\n\n/** One layer's style in a layer styles file, keyed by the layer's name. */\nexport type LayerStyleFileEntry =\n | {\n /** Name of the layer the style was exported from; the match key. */\n layerName: string;\n kind: \"vector\";\n /** The full vector {@link LayerStyle} bag. */\n style: LayerStyle;\n }\n | {\n layerName: string;\n kind: \"raster\";\n /** Raster visualization state (`metadata.rasterState`). */\n rasterState?: Record<string, unknown>;\n /** Raster classification symbology (`metadata.rasterSymbology`). */\n rasterSymbology?: Record<string, unknown>;\n };\n\n/** The on-disk layer styles document. */\nexport interface LayerStylesFile {\n type: typeof LAYER_STYLES_FILE_TYPE;\n version: typeof LAYER_STYLES_FILE_VERSION;\n styles: LayerStyleFileEntry[];\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Capture the style of every layer that has one. Layers without copyable\n * symbology (basemap tiles, 3D tiles, ...) are skipped. Layers that share a\n * name are all written, in stack order; the first one wins when the file is\n * applied (see {@link findLayerStyleEntryIndex}).\n *\n * @param layers - The layers to export, in store (bottom-to-top) order.\n * @returns The style entries.\n */\nexport function extractLayerStyleEntries(layers: readonly GeoLibreLayer[]): LayerStyleFileEntry[] {\n const entries: LayerStyleFileEntry[] = [];\n for (const layer of layers) {\n const copied = extractCopiedLayerStyle(layer);\n if (!copied) continue;\n if (copied.kind === \"vector\") {\n entries.push({ layerName: layer.name, kind: \"vector\", style: copied.style });\n } else {\n entries.push({\n layerName: layer.name,\n kind: \"raster\",\n ...(copied.rasterState ? { rasterState: copied.rasterState } : {}),\n ...(isPlainObject(copied.rasterSymbology)\n ? { rasterSymbology: copied.rasterSymbology }\n : {}),\n });\n }\n }\n return entries;\n}\n\n/**\n * Serialize style entries into the JSON written by Export Layer Styles.\n *\n * @param entries - The entries to write.\n * @returns Pretty-printed file content.\n */\nexport function serializeLayerStylesFile(entries: readonly LayerStyleFileEntry[]): string {\n const file: LayerStylesFile = {\n type: LAYER_STYLES_FILE_TYPE,\n version: LAYER_STYLES_FILE_VERSION,\n styles: [...entries],\n };\n return JSON.stringify(file, null, 2);\n}\n\nconst RASTER_STRETCHES: readonly unknown[] = [\"linear\", \"log\", \"sqrt\"];\nconst RASTER_NODATA_MODES: readonly unknown[] = [\"off\", \"auto\"];\n\nfunction isFiniteNumber(value: unknown): value is number {\n return typeof value === \"number\" && Number.isFinite(value);\n}\n\n/**\n * Validators for the raster appearance keys a style file may carry, by the\n * types `maplibre-gl-raster`'s `RasterLayerState` declares for them.\n */\nconst RASTER_APPEARANCE_VALIDATORS: Record<\n (typeof RASTER_APPEARANCE_STATE_KEYS)[number],\n (value: unknown) => boolean\n> = {\n colormap: (value) => typeof value === \"string\" && value.length > 0,\n reversed: (value) => typeof value === \"boolean\",\n rescale: (value) =>\n value === null ||\n (Array.isArray(value) &&\n // An empty array is not a meaningful rescale (as in savedRasterState).\n value.length > 0 &&\n value.every(\n (range) =>\n Array.isArray(range) &&\n range.length === 2 &&\n isFiniteNumber(range[0]) &&\n isFiniteNumber(range[1]),\n )),\n nodata: (value) => isFiniteNumber(value) || RASTER_NODATA_MODES.includes(value),\n stretch: (value) => RASTER_STRETCHES.includes(value),\n gamma: (value) => isFiniteNumber(value) && value > 0,\n};\n\n/**\n * Keep only the raster appearance keys a style applies, each with a value of\n * the type the raster renderer expects. The data selection (`mode`, `bands`,\n * ...) is dropped too: a style never applies it.\n */\nfunction sanitizeRasterAppearanceState(state: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const key of RASTER_APPEARANCE_STATE_KEYS) {\n if (key in state && RASTER_APPEARANCE_VALIDATORS[key](state[key])) {\n out[key] = structuredClone(state[key]);\n }\n }\n return out;\n}\n\n/**\n * Coerce untrusted entries (a hand-edited file, or a copy kept in settings)\n * into clean ones. An entry without a name or a known kind is dropped; a\n * vector style is sanitized and completed against the default style, and a\n * raster state keeps only well-typed appearance keys, so applying an entry\n * never hands a renderer an unknown, missing or wrong-typed field.\n *\n * @param value - The raw `styles` array.\n * @returns The usable entries.\n */\nexport function normalizeLayerStyleEntries(value: unknown): LayerStyleFileEntry[] {\n if (!Array.isArray(value)) return [];\n const entries: LayerStyleFileEntry[] = [];\n for (const item of value) {\n if (!isPlainObject(item)) continue;\n const layerName = typeof item.layerName === \"string\" ? item.layerName : \"\";\n if (!layerName.trim()) continue;\n if (item.kind === \"vector\") {\n if (!isPlainObject(item.style)) continue;\n entries.push({\n layerName,\n kind: \"vector\",\n style: { ...structuredClone(DEFAULT_LAYER_STYLE), ...sanitizeLayerStylePatch(item.style) },\n });\n } else if (item.kind === \"raster\") {\n entries.push({\n layerName,\n kind: \"raster\",\n ...(isPlainObject(item.rasterState)\n ? { rasterState: sanitizeRasterAppearanceState(item.rasterState) }\n : {}),\n // The symbology's schema lives in `@geolibre/plugins`, which validates\n // it on every read (`savedRasterSymbology`), as it does for a project\n // file; a malformed one renders as unclassified there.\n ...(isPlainObject(item.rasterSymbology)\n ? { rasterSymbology: structuredClone(item.rasterSymbology) }\n : {}),\n });\n }\n }\n return entries;\n}\n\n/**\n * Parse a file written by {@link serializeLayerStylesFile}.\n *\n * @param json - The file content.\n * @returns The normalized entries.\n * @throws Error when the content is not a layer styles file, comes from a\n * newer format version, or holds no usable entries.\n */\nexport function parseLayerStylesFile(json: string): LayerStyleFileEntry[] {\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n throw new Error(\"Not a valid layer styles file (invalid JSON).\");\n }\n if (!isPlainObject(parsed) || parsed.type !== LAYER_STYLES_FILE_TYPE) {\n throw new Error(\"Not a valid layer styles file.\");\n }\n // Refuse a newer format rather than misread it with this version's rules.\n if (parsed.version !== LAYER_STYLES_FILE_VERSION) {\n throw new Error(\"Unsupported layer styles file version.\");\n }\n const entries = normalizeLayerStyleEntries(parsed.styles);\n if (entries.length === 0) {\n throw new Error(\"The layer styles file holds no usable styles.\");\n }\n return entries;\n}\n\nfunction nameKey(name: string): string {\n return name.trim().toLocaleLowerCase();\n}\n\n/**\n * Find the entry for a layer name: an exact match first, then one that differs\n * only in case or surrounding whitespace, so `Roads` still picks up a style\n * saved for `roads`. The first match in file order wins.\n *\n * @param entries - The style entries.\n * @param layerName - The layer name to look up.\n * @param kind - When given, only entries of this style family are considered,\n * so a raster style saved under a shared name does not shadow the vector one.\n * @returns The matching entry's index in `entries`, or -1.\n */\nexport function findLayerStyleEntryIndex(\n entries: readonly LayerStyleFileEntry[],\n layerName: string,\n kind?: LayerStyleClipboardKind,\n): number {\n const ofKind = (entry: LayerStyleFileEntry) => !kind || entry.kind === kind;\n const exact = entries.findIndex((entry) => ofKind(entry) && entry.layerName === layerName);\n if (exact >= 0) return exact;\n const key = nameKey(layerName);\n return entries.findIndex((entry) => ofKind(entry) && nameKey(entry.layerName) === key);\n}\n\nfunction toCopiedLayerStyle(entry: LayerStyleFileEntry): CopiedLayerStyle {\n if (entry.kind === \"vector\") {\n return { kind: \"vector\", sourceName: entry.layerName, style: entry.style };\n }\n return {\n kind: \"raster\",\n sourceName: entry.layerName,\n rasterState: entry.rasterState,\n hasRasterSymbology: entry.rasterSymbology !== undefined,\n ...(entry.rasterSymbology !== undefined ? { rasterSymbology: entry.rasterSymbology } : {}),\n };\n}\n\n/** A layer restyled from a style entry, and the entry it took. */\nexport interface LayerStyleMatch {\n /** Index of the applied entry in the entries array. */\n entryIndex: number;\n /** The patch that applies it. */\n patch: Partial<GeoLibreLayer>;\n}\n\n/**\n * Build the patch that restyles a layer from the entry matching its name, with\n * the same semantics as pasting a copied style: a vector style replaces the\n * whole style bag, a raster style merges its appearance keys and keeps the\n * layer's own band selection, and layer opacity is left alone.\n *\n * @param layer - The layer to restyle.\n * @param entries - The style entries.\n * @returns The matched entry and patch, or `null` when no entry of the layer's\n * style family matches its name (or the layer has no stylable symbology).\n */\nexport function matchLayerStyleEntry(\n layer: GeoLibreLayer,\n entries: readonly LayerStyleFileEntry[],\n): LayerStyleMatch | null {\n const kind = copyableLayerStyleKind(layer);\n if (!kind) return null;\n const entryIndex = findLayerStyleEntryIndex(entries, layer.name, kind);\n if (entryIndex < 0) return null;\n const patch = applyCopiedLayerStyle(layer, toCopiedLayerStyle(entries[entryIndex]));\n return patch ? { entryIndex, patch } : null;\n}\n","/**\n * The layer stack: adding, removing, editing, restyling and reordering layer\n * records, plus the copy/paste style clipboard. `layers` is tracked by undo\n * history. `removeLayer` also scrubs every other slice's references to the\n * deleted layer, so it reads and writes the whole state through `set`.\n */\nimport type { FeatureCollection } from \"geojson\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport { isSourceDerivedLayerName, uniqueImportedLayerName } from \"../file-name\";\nimport { initialLayerStyle } from \"../layer-defaults\";\nimport { normalizeGroupContiguity } from \"../layer-groups\";\nimport {\n scrubCommentsForRemovedLayers,\n scrubLegendForRemovedLayers,\n scrubWidgetsForRemovedLayers,\n} from \"../layer-ref-scrub\";\nimport {\n applyCopiedLayerStyle,\n type CopiedLayerStyle,\n extractCopiedLayerStyle,\n} from \"../layer-style-clipboard\";\nimport { matchLayerStyleEntry, type LayerStyleFileEntry } from \"../layer-style-file\";\nimport { applyJoinsToLayer, cascadeLayerJoinRefresh } from \"../joins\";\nimport { normalizeLayerDescriptiveMetadata } from \"../layer-descriptive-metadata\";\nimport { withRasterSourceDefaults } from \"../raster-source-defaults\";\nimport { scrubPrintLayoutForRemovedLayers } from \"../print-layout-config\";\nimport { identifyStateWithoutLayers } from \"./session-slice\";\nimport {\n DEFAULT_LAYER_STYLE,\n shouldUseTiledRendering,\n type AddTileLayerOptions,\n type AttributeFormConfig,\n type EditorTrackingConfig,\n type GeoLibreLayer,\n type LayerJoin,\n type LayerDescriptiveMetadata,\n type LayerPopupConfig,\n type LayerLevelFilter,\n type LayerQuickFilter,\n type LayerStyle,\n type LevelFilterMode,\n type LayerVirtualField,\n} from \"../types\";\nimport { featureCollectionToGeoArrow, type GeoArrowBatch } from \"../geoarrow\";\nimport { sameLevelLink, selectLevel } from \"../level-filter\";\nimport { resolveLayerTileAddress } from \"../tile-address\";\nimport { hasSimpleStyleProperties } from \"../vector-color\";\nimport {\n cascadeJoinRefreshForRemoved,\n scrubSecondaryPaneLayerVisibility,\n scrubStorymapLayerRefs,\n} from \"./layer-removal\";\nimport type { SliceCreator } from \"./types\";\n\n/**\n * The layer types {@link AppState.addTileLayer} accepts. Each renders through\n * the raster tile path, which is why the layer's `source.type` is always\n * `\"raster\"`.\n */\nconst RASTER_TILE_LAYER_TYPES: ReadonlySet<string> = new Set<\n NonNullable<AddTileLayerOptions[\"type\"]>\n>([\"xyz\", \"wms\", \"wmts\", \"raster\"]);\n\nexport interface LayersSlice {\n layers: GeoLibreLayer[];\n\n addLayer: (layer: GeoLibreLayer, beforeLayerId?: string | null) => void;\n removeLayer: (id: string) => void;\n updateLayer: (id: string, patch: Partial<GeoLibreLayer>) => void;\n setLayerVisibility: (id: string, visible: boolean) => void;\n setLayerOpacity: (id: string, opacity: number) => void;\n setLayerStyle: (id: string, style: Partial<LayerStyle>) => void;\n /**\n * Transient clipboard holding a layer's symbology, captured by\n * {@link copyLayerStyle} and applied by {@link pasteLayerStyle} (copy/paste\n * styles, issue #1339). Runtime-only: excluded from undo history\n * (`partialize` never lists it) and from the saved project. `null` until a\n * style is copied this session. Cleared by `newProject`/`loadProject` so a\n * paste can't apply an entry from a different project; it deliberately\n * survives undo/redo within a project (it holds a deep snapshot, not a live\n * layer reference, so a paste stays valid even if the source layer is undone\n * away — only the displayed source name may then be stale).\n */\n copiedLayerStyle: CopiedLayerStyle | null;\n /**\n * Snapshot the given layer's style into {@link copiedLayerStyle} so it can be\n * pasted onto a compatible layer. No-op when the layer is missing or has no\n * copyable symbology (leaving any prior clipboard entry untouched). Returns\n * `true` when a style was captured, so callers can skip the confirmation on a\n * no-op.\n */\n copyLayerStyle: (id: string) => boolean;\n /**\n * Apply the {@link copiedLayerStyle} clipboard entry onto the given layer.\n * No-op when the clipboard is empty, the layer is missing, or the entry's\n * style family does not match the target layer's. Returns `true` when the\n * style was applied, so callers can skip the confirmation on a no-op.\n */\n pasteLayerStyle: (id: string) => boolean;\n /**\n * Restyle layers from layer styles file entries matched by layer name\n * (Import Layer Styles, and the Startup setting's auto-apply). Each matched\n * layer is patched the way {@link pasteLayerStyle} would patch it, all in a\n * single store update so the import is one undo step.\n *\n * @param entries - The parsed style entries.\n * @param layerIds - Restrict the restyle to these layers; every layer when\n * omitted.\n * @returns Each restyled layer's id with the index of the entry it took, in\n * stack order.\n */\n applyLayerStyleEntries: (\n entries: readonly LayerStyleFileEntry[],\n layerIds?: readonly string[],\n ) => { layerId: string; entryIndex: number }[];\n /**\n * Replace a layer's persistent attribute joins and immediately re-derive its\n * joined columns (strip what the previous joins added, apply the new list).\n * Pass an empty array to detach every join and restore the base attributes.\n */\n setLayerJoins: (id: string, joins: LayerJoin[]) => void;\n /**\n * Replace the layer's Attribute Form designer configuration (per-field edit\n * widgets, constraints, conditional visibility). Pass `undefined` to remove\n * the form config entirely.\n */\n setLayerAttributeForm: (id: string, attributeForm: AttributeFormConfig | undefined) => void;\n /**\n * Replace the layer's popup/tooltip design (which fields the Identify popup\n * shows, in what order and under what labels, plus the hover tooltip). Pass\n * `undefined` to restore the default full-property dump.\n */\n setLayerPopup: (id: string, popup: LayerPopupConfig | undefined) => void;\n /**\n * Replace the layer's user-authored catalog metadata (title, abstract,\n * keywords, license, contact, lineage, temporal extent, links). The record\n * is normalized first; one that cleans to nothing (or `undefined`) removes\n * the metadata entirely. One call is one undo step.\n */\n setLayerDescriptiveMetadata: (id: string, metadata: LayerDescriptiveMetadata | undefined) => void;\n /**\n * Replace the layer's editor tracking configuration (whether creation/edit\n * author and timestamp columns are maintained, and under which names). Pass\n * `undefined` to drop the configuration entirely.\n */\n setLayerEditorTracking: (id: string, editorTracking: EditorTrackingConfig | undefined) => void;\n /**\n * Replace a layer's virtual fields and immediately re-derive its computed\n * columns (strip what the previous fields added, evaluate the new list).\n * Pass an empty array to detach every virtual field.\n */\n setLayerVirtualFields: (id: string, fields: LayerVirtualField[]) => void;\n /**\n * Replace a layer's quick filters (issue #2114). The controls persist with\n * the project and are compiled to a MapLibre filter at sync time, so this\n * only stores state — nothing re-derives the layer's data. Pass an empty\n * array to remove every control.\n */\n setLayerQuickFilters: (id: string, filters: LayerQuickFilter[]) => void;\n /** Set or clear the project-persisted expression filter for a layer. */\n setLayerFilterExpression: (id: string, expression: unknown[] | null) => void;\n /**\n * Link a layer to the floor picker (issue #3341), or unlink it with `null`.\n * Linking a layer draws every floor until a floor is chosen.\n */\n setLayerLevelFilter: (id: string, filter: LayerLevelFilter | null) => void;\n /**\n * Choose a floor in the floor picker: every linked layer is narrowed to\n * `level` (`null` for every floor) under `mode`, in one update, with the\n * matching values resolved from each layer's own features.\n */\n setLevelSelection: (level: string | null, mode: LevelFilterMode) => void;\n /**\n * Write floor picker links the picker re-resolved on its own (values missing\n * from a saved project, or data that changed). Not a user edit: the project\n * stays clean and, called through `withoutHistory`, no undo step is added.\n */\n reconcileLevelLinks: (updates: { id: string; levelFilter: LayerLevelFilter }[]) => void;\n reorderLayer: (id: string, direction: \"up\" | \"down\") => void;\n moveLayer: (id: string, targetIndex: number) => void;\n moveLayersRelative: (\n layerIds: string[],\n targetLayerId: string,\n position: \"above\" | \"below\",\n ) => void;\n addGeoJsonLayer: (\n name: string,\n geojson: FeatureCollection,\n sourcePath?: string,\n beforeLayerId?: string | null,\n extra?: { geoarrow?: GeoArrowBatch; metadata?: Record<string, unknown> },\n ) => string;\n /**\n * Add a georeferenced image overlay (a MapLibre `image` source rendered as a\n * raster layer) from an image URL and its four corner coordinates, and return\n * its id. Used for KML/KMZ `<GroundOverlay>` imports; the layer persists and\n * renders exactly like a Raster Georeferencer overlay. Corners are `[lng,\n * lat]` in top-left, top-right, bottom-right, bottom-left order.\n */\n addImageOverlayLayer: (\n name: string,\n source: { url: string; coordinates: [number, number][] },\n options?: {\n opacity?: number;\n bounds?: [number, number, number, number];\n sourcePath?: string;\n /** Initial visibility (default true); a time-slider frame past the first\n * starts hidden. */\n visible?: boolean;\n /** Epoch-ms time bounds of a KML `<TimeSpan>`/`<TimeStamp>` frame; the\n * Time Slider toggles this frame's visibility by the current date. */\n timeSpan?: { begin: number | null; end: number | null };\n /**\n * What produced the overlay, e.g. a NetCDF grid baked to pixels. Defaults\n * to the KML ground overlay this was first written for; panels gate their\n * per-source controls on it.\n */\n sourceKind?: string;\n /** Extra metadata merged onto the layer (e.g. a symbology record). */\n metadata?: Record<string, unknown>;\n },\n beforeLayerId?: string | null,\n ) => string;\n /**\n * Add a native raster tile layer (XYZ, WMS, or WMTS) from one or more tile\n * URL templates and return its id. The layer appears in the Layers panel and\n * persists with the project exactly like a layer added through the Add Data\n * dialog, so callers (e.g. an external plugin) do not have to touch MapLibre\n * directly. See {@link AddTileLayerOptions}.\n */\n addTileLayer: (\n name: string,\n options: AddTileLayerOptions,\n beforeLayerId?: string | null,\n ) => string;\n}\n\nexport const createLayersSlice: SliceCreator<LayersSlice> = (set, get) => ({\n layers: [],\n copiedLayerStyle: null,\n\n addLayer: (inputLayer, beforeLayerId = null) =>\n set((s) => {\n let layer = withRasterSourceDefaults(inputLayer);\n const layers = [...s.layers];\n // Plugin source identifiers (for example pmtiles://) are not local files.\n const { sourcePath } = layer;\n const localSource =\n sourcePath &&\n (!/^[a-z][a-z0-9+.-]*:\\/\\//i.test(sourcePath) || /^(content|file):\\/\\//i.test(sourcePath));\n // Only filename-derived names are deduplicated; an explicit name (an\n // embedded document title, a tool output label, a user-typed name)\n // is kept as supplied.\n if (localSource && isSourceDerivedLayerName(layer.name, sourcePath)) {\n layer = {\n ...layer,\n name: uniqueImportedLayerName(\n layer.name,\n layers.map((item) => item.name),\n ),\n };\n }\n const beforeIndex = beforeLayerId ? layers.findIndex((l) => l.id === beforeLayerId) : -1;\n const layerWithBeforeId =\n beforeLayerId && beforeIndex < 0\n ? { ...layer, beforeId: beforeLayerId }\n : { ...layer, beforeId: layer.beforeId };\n if (beforeIndex >= 0) {\n layers.splice(beforeIndex, 0, layerWithBeforeId);\n } else {\n layers.push(layerWithBeforeId);\n }\n return {\n layers,\n selectedLayerId: layer.id,\n isDirty: true,\n };\n }),\n\n removeLayer: (id) =>\n set((s) => ({\n // Re-derive any layer whose joins consumed the removed layer: with\n // the source gone the join resolves to nothing, so its previously\n // materialized columns strip away instead of staying frozen (the\n // join definition itself stays, shown as missing in the Joins UI).\n layers: cascadeJoinRefreshForRemoved(\n s.layers.filter((l) => l.id !== id),\n id,\n ),\n secondaryMapViews: scrubSecondaryPaneLayerVisibility(s.secondaryMapViews, id),\n // Drop storymap chapter enter/exit opacity rows that pointed at the\n // removed layer so they do not keep a dangling id across save/reload.\n storymap: scrubStorymapLayerRefs(s.storymap, id),\n widgets: scrubWidgetsForRemovedLayers(s.widgets, id),\n comments: scrubCommentsForRemovedLayers(s.comments, id),\n legend: scrubLegendForRemovedLayers(s.legend, id),\n // Clear a Print Layout data/atlas block built on the removed layer,\n // so a save that follows the delete cannot write a dangling id.\n printLayout: scrubPrintLayoutForRemovedLayers(s.printLayout, id),\n selectedLayerId:\n s.selectedLayerId === id\n ? (s.layers.find((l) => l.id !== id)?.id ?? null)\n : s.selectedLayerId,\n selectedFeatureId: s.selectedLayerId === id ? null : s.selectedFeatureId,\n selectedFeatureIds: s.selectedLayerId === id ? [] : s.selectedFeatureIds,\n ...identifyStateWithoutLayers(s, new Set([id])),\n ui: {\n ...s.ui,\n selectByExpressionLayerId:\n s.ui.selectByExpressionLayerId === id ? null : s.ui.selectByExpressionLayerId,\n selectByLocationLayerId:\n s.ui.selectByLocationLayerId === id ? null : s.ui.selectByLocationLayerId,\n loadEditorFeaturesLayerId:\n s.ui.loadEditorFeaturesLayerId === id ? null : s.ui.loadEditorFeaturesLayerId,\n },\n isDirty: true,\n })),\n\n updateLayer: (id, patch) =>\n set((s) => {\n let layers = s.layers.map((l) => {\n if (l.id !== id) return l;\n const updated = { ...l, ...patch };\n // If a patch explicitly replaces geojson but does not explicitly supply geoarrow,\n // clear the existing GeoArrow batch to avoid retaining stale geometry.\n if (patch.geojson !== undefined && !(\"geoarrow\" in patch)) {\n delete updated.geoarrow;\n } else if (\"geoarrow\" in patch) {\n if (patch.geoarrow === null || patch.geoarrow === undefined) {\n delete updated.geoarrow;\n } else {\n updated.geoarrow = patch.geoarrow;\n }\n }\n return updated;\n });\n // A geojson replacement re-derives the layer's derived columns\n // (persistent joins, then virtual fields): on the layer itself\n // (file reload, attribute edits, processing writes — derived\n // columns stay derived, QGIS-style), then transitively on every\n // layer whose joins consume the updated one, so editing a join\n // table refreshes its targets and their dependents in turn. The\n // `patch.joins`/`patch.virtualFields` guard exists for external\n // callers of this public store API (plugins, programmatic loads):\n // a patch that carries `geojson` alongside the definitions is taken\n // verbatim as already-derived state — no in-repo caller does this\n // today.\n if (patch.geojson !== undefined) {\n if (patch.joins === undefined && patch.virtualFields === undefined) {\n layers = layers.map((l) =>\n l.id === id && (l.joins?.length || l.virtualFields?.length)\n ? applyJoinsToLayer(l, layers)\n : l,\n );\n }\n layers = cascadeLayerJoinRefresh(layers, id);\n }\n return { layers, isDirty: true };\n }),\n\n setLayerJoins: (id, joins) =>\n set((s) => {\n let layers = s.layers.map((l) => (l.id === id ? applyJoinsToLayer(l, s.layers, joins) : l));\n // Changing this layer's joins changes its materialized columns, so\n // layers joining against it (directly or transitively) re-derive too.\n layers = cascadeLayerJoinRefresh(layers, id);\n return { layers, isDirty: true };\n }),\n\n setLayerAttributeForm: (id, attributeForm) => get().updateLayer(id, { attributeForm }),\n setLayerPopup: (id, popup) => get().updateLayer(id, { popup }),\n setLayerDescriptiveMetadata: (id, metadata) =>\n get().updateLayer(id, { descriptiveMetadata: normalizeLayerDescriptiveMetadata(metadata) }),\n\n setLayerEditorTracking: (id, editorTracking) => get().updateLayer(id, { editorTracking }),\n\n setLayerVirtualFields: (id, fields) =>\n set((s) => {\n let layers = s.layers.map((l) =>\n l.id === id ? applyJoinsToLayer(l, s.layers, undefined, fields) : l,\n );\n // Virtual columns are ordinary materialized properties, so a layer\n // joining against this one (directly or transitively) sees them and\n // must re-derive too.\n layers = cascadeLayerJoinRefresh(layers, id);\n return { layers, isDirty: true };\n }),\n\n setLayerQuickFilters: (id, filters) =>\n get().updateLayer(id, { quickFilters: filters.length > 0 ? filters : undefined }),\n\n setLayerFilterExpression: (id, expression) =>\n get().updateLayer(id, { filterExpression: expression ?? undefined }),\n\n setLayerLevelFilter: (id, filter) => get().updateLayer(id, { levelFilter: filter ?? undefined }),\n\n setLevelSelection: (level, mode) =>\n set((s) => {\n let changed = false;\n const layers = s.layers.map((layer) => {\n if (!layer.levelFilter) return layer;\n const next = selectLevel(layer.levelFilter, layer.geojson?.features, level, mode);\n // Re-clicking the floor already shown is not an edit.\n if (sameLevelLink(layer.levelFilter, next)) return layer;\n changed = true;\n return { ...layer, levelFilter: next };\n });\n return changed ? { layers, isDirty: true } : {};\n }),\n\n reconcileLevelLinks: (updates) =>\n set((s) => {\n if (updates.length === 0) return {};\n const byId = new Map(updates.map((update) => [update.id, update.levelFilter]));\n return {\n layers: s.layers.map((layer) =>\n byId.has(layer.id) ? { ...layer, levelFilter: byId.get(layer.id) } : layer,\n ),\n };\n }),\n\n setLayerVisibility: (id, visible) => get().updateLayer(id, { visible }),\n\n setLayerOpacity: (id, opacity) => get().updateLayer(id, { opacity }),\n\n setLayerStyle: (id, style) =>\n set((s) => ({\n layers: s.layers.map((l) => (l.id === id ? { ...l, style: { ...l.style, ...style } } : l)),\n isDirty: true,\n })),\n\n copyLayerStyle: (id) => {\n const layer = get().layers.find((l) => l.id === id);\n if (!layer) return false;\n const copied = extractCopiedLayerStyle(layer);\n // Leave any prior clipboard entry in place when this layer is not\n // copyable, so opening a non-stylable layer's menu never clears it.\n if (!copied) return false;\n set({ copiedLayerStyle: copied });\n return true;\n },\n\n pasteLayerStyle: (id) => {\n const s = get();\n const copied = s.copiedLayerStyle;\n if (!copied) return false;\n const layer = s.layers.find((l) => l.id === id);\n if (!layer) return false;\n const patch = applyCopiedLayerStyle(layer, copied);\n if (!patch) return false;\n // Go through updateLayer so the paste picks up any cross-cutting layer\n // update logic (it also sets isDirty); the patch never carries geojson,\n // so the join-cascade branch is a no-op.\n get().updateLayer(id, patch);\n return true;\n },\n\n applyLayerStyleEntries: (entries, layerIds) => {\n if (entries.length === 0) return [];\n const scope = layerIds ? new Set(layerIds) : null;\n const applied: { layerId: string; entryIndex: number }[] = [];\n const layers = get().layers.map((layer) => {\n if (scope && !scope.has(layer.id)) return layer;\n const match = matchLayerStyleEntry(layer, entries);\n if (!match) return layer;\n applied.push({ layerId: layer.id, entryIndex: match.entryIndex });\n return { ...layer, ...match.patch };\n });\n // A style patch never carries geojson, so unlike updateLayer there is no\n // join cascade to run.\n if (applied.length > 0) set({ layers, isDirty: true });\n return applied;\n },\n\n reorderLayer: (id, direction) =>\n set((s) => {\n const idx = s.layers.findIndex((l) => l.id === id);\n if (idx < 0) return s;\n const target = direction === \"up\" ? idx + 1 : idx - 1;\n if (target < 0 || target >= s.layers.length) return s;\n const next = [...s.layers];\n const [item] = next.splice(idx, 1);\n next.splice(target, 0, item);\n return { layers: next, isDirty: true };\n }),\n\n moveLayer: (id, targetIndex) =>\n set((s) => {\n const currentIndex = s.layers.findIndex((layer) => layer.id === id);\n if (currentIndex < 0) return s;\n const next = [...s.layers];\n const [layer] = next.splice(currentIndex, 1);\n const nextIndex = Math.min(Math.max(targetIndex, 0), next.length);\n next.splice(nextIndex, 0, layer);\n if (next.every((item, index) => item.id === s.layers[index]?.id)) {\n return s;\n }\n return { layers: next, isDirty: true };\n }),\n\n moveLayersRelative: (layerIds, targetLayerId, position) =>\n set((s) => {\n const requestedIds = new Set(layerIds);\n if (requestedIds.has(targetLayerId)) return s;\n const target = s.layers.find((layer) => layer.id === targetLayerId);\n if (!target) return s;\n const targetGroupId = target.groupId ?? null;\n // This is a pure reorder — `groupId` is never touched — so a requested\n // layer from another group can only be lifted out of its own block,\n // and `normalizeGroupContiguity` would then drag that group's\n // untouched members along to reunite it. Move only the layers that\n // already sit in the target's group.\n const moving = s.layers.filter(\n (layer) => requestedIds.has(layer.id) && (layer.groupId ?? null) === targetGroupId,\n );\n if (moving.length === 0) return s;\n const movingIds = new Set(moving.map((layer) => layer.id));\n const without = s.layers.filter((layer) => !movingIds.has(layer.id));\n const targetIndex = without.findIndex((layer) => layer.id === targetLayerId);\n if (targetIndex < 0) return s;\n // Store order is the reverse of panel display order, so \"above\" is\n // immediately after the target in this array.\n const insertIndex = position === \"above\" ? targetIndex + 1 : targetIndex;\n const next = [...without];\n next.splice(insertIndex, 0, ...moving);\n const normalized = normalizeGroupContiguity(next);\n if (normalized.every((layer, index) => layer.id === s.layers[index]?.id)) return s;\n return { layers: normalized, isDirty: true };\n }),\n\n addGeoJsonLayer: (name, geojson, sourcePath, beforeLayerId = null, extra) => {\n const id = uuidv4();\n // GeoJSON remains authoritative for editing. The optional batch adds\n // coordinate storage; it does not reduce the memory held by this layer.\n let geoarrow = extra?.geoarrow;\n if (!geoarrow && shouldUseTiledRendering(geojson)) {\n try {\n geoarrow = featureCollectionToGeoArrow(geojson) ?? undefined;\n } catch (err) {\n console.warn(\"[GeoLibre] GeoArrow batch conversion failed; falling back to GeoJSON\", err);\n }\n }\n const layer: GeoLibreLayer = {\n id,\n name,\n type: \"geojson\",\n source: { type: \"geojson\" },\n visible: true,\n opacity: 1,\n // Its own palette color and geometry-appropriate sizing (#1519), so a\n // stack of freshly added layers is legible without restyling each one.\n style: initialLayerStyle({\n geojson,\n layers: get().layers,\n overrides: {\n simpleStyleEnabled: hasSimpleStyleProperties(geojson),\n },\n }),\n metadata: { ...extra?.metadata },\n geojson,\n ...(geoarrow ? { geoarrow } : {}),\n sourcePath,\n };\n get().addLayer(layer, beforeLayerId);\n return id;\n },\n\n addImageOverlayLayer: (name, source, options, beforeLayerId = null) => {\n const id = uuidv4();\n const layer: GeoLibreLayer = {\n id,\n name,\n type: \"image\",\n source: {\n type: \"image\",\n url: source.url,\n coordinates: source.coordinates,\n },\n visible: options?.visible ?? true,\n opacity: options?.opacity ?? 1,\n style: { ...DEFAULT_LAYER_STYLE },\n metadata: {\n sourceKind: options?.sourceKind ?? \"kml-ground-overlay\",\n ...(options?.bounds ? { bounds: options.bounds } : {}),\n ...(options?.timeSpan ? { timeSpan: options.timeSpan } : {}),\n ...(options?.metadata ?? {}),\n },\n ...(options?.sourcePath ? { sourcePath: options.sourcePath } : {}),\n };\n get().addLayer(layer, beforeLayerId);\n return id;\n },\n\n addTileLayer: (name, options, beforeLayerId = null) => {\n // Every layer this builds carries a raster source, so a non-raster\n // `type` from an untyped JS caller (e.g. \"vector-tiles\") would persist\n // a layer whose `type` and `source.type` disagree. Reject it instead of\n // silently mislabelling the source; vector tiles need their own style\n // layers and go through the vector-tile path, not this one.\n const type = options.type ?? \"xyz\";\n if (!RASTER_TILE_LAYER_TYPES.has(type)) {\n throw new Error(\n `addTileLayer: unsupported type \"${String(type)}\"; expected one of ` +\n `${[...RASTER_TILE_LAYER_TYPES].join(\", \")}. Only raster tile layers are supported.`,\n );\n }\n const id = uuidv4();\n // Trim each template and drop blanks, then reject a registration that\n // sanitizes down to nothing: syncRasterTileLayer returns early on an\n // empty tile list, so without this the store would persist and select a\n // layer that can never render.\n const tiles = options.tiles\n .filter((tile): tile is string => typeof tile === \"string\")\n .map((tile) => tile.trim())\n .filter((tile) => tile !== \"\");\n if (tiles.length === 0) {\n throw new Error(\"addTileLayer requires at least one non-empty tile URL template.\");\n }\n // {-y} is accepted on XYZ templates. Reject a template that names both\n // row tokens before the layer is stored.\n resolveLayerTileAddress({ type, source: { scheme: options.scheme } }, tiles);\n // MapLibre's addSource throws synchronously when maxzoom < minzoom, and\n // syncRasterTileLayer does not catch it, which would strand a layer in\n // the store with no rendered source. Reject the bad range up front.\n if (\n options.minzoom !== undefined &&\n options.maxzoom !== undefined &&\n options.minzoom > options.maxzoom\n ) {\n throw new Error(\n `addTileLayer: minzoom (${options.minzoom}) must be <= maxzoom (${options.maxzoom}).`,\n );\n }\n const layer: GeoLibreLayer = {\n id,\n name,\n type,\n source: {\n // Extra source fields (e.g. WMS layers/styles) merge first so the\n // required raster descriptor below always wins. `type` above is\n // validated as a raster kind, so \"raster\" here always agrees with it.\n ...(options.source ?? {}),\n type: \"raster\",\n tiles,\n tileSize: options.tileSize ?? 256,\n ...(options.url !== undefined ? { url: options.url } : {}),\n ...(options.attribution !== undefined ? { attribution: options.attribution } : {}),\n ...(options.bounds !== undefined ? { bounds: options.bounds } : {}),\n ...(options.minzoom !== undefined ? { minzoom: options.minzoom } : {}),\n ...(options.maxzoom !== undefined ? { maxzoom: options.maxzoom } : {}),\n ...(options.scheme !== undefined ? { scheme: options.scheme } : {}),\n },\n visible: options.visible ?? true,\n opacity: options.opacity ?? 1,\n style: { ...DEFAULT_LAYER_STYLE },\n metadata: { ...(options.metadata ?? {}) },\n };\n get().addLayer(layer, beforeLayerId);\n return id;\n },\n});\n","// The Layer Library's data layer (issue #1520): capturing a configured layer as\n// a reusable entry, planning how to re-add one to a project, the shareable JSON\n// bundle format, and untrusted-input normalization. UI-free so both the desktop\n// app and tests consume it directly. Mirrors the shape of `style-library.ts`,\n// which does the same job for the Style Manager.\n//\n// The guiding rule is that an entry stores the layer's **source specification,\n// not its data**: the library stays small and re-adding an entry always shows\n// the current contents of its source. Layers whose features exist only in\n// memory (drawn features, processing output) or only in a local file have no\n// re-fetchable source, so those embed their features behind a size cap.\n\nimport { cesiumIonAssetId } from \"./cesium-ion\";\nimport type { FeatureCollection } from \"geojson\";\nimport {\n DEFAULT_LAYER_STYLE,\n LAYER_TYPES,\n type AttributeFormConfig,\n type GeoLibreLayer,\n type LayerDescriptiveMetadata,\n type LayerJoin,\n type LayerLibraryEntry,\n type LayerPopupConfig,\n type LayerStyle,\n type LayerType,\n type LayerVirtualField,\n} from \"./types\";\nimport { sanitizeLayerStylePatch } from \"./style-library\";\nimport { normalizeLayerDescriptiveMetadata } from \"./layer-descriptive-metadata\";\nimport { hasPathTraversal, isAbsoluteFilesystemPath } from \"./paths\";\nimport { isCredentialFieldName, redactUrlCredentials } from \"./credentials\";\n\n/** `type` discriminator of an exported Layer Library bundle file. */\nexport const LAYER_LIBRARY_BUNDLE_TYPE = \"geolibre-layer-library\";\n\n/** Current bundle format version, for forward-compatible readers. */\nexport const LAYER_LIBRARY_BUNDLE_VERSION = 1;\n\n/**\n * Size ceiling for one library entry's serialized JSON, in bytes. The library\n * lives in IndexedDB and is meant to be a small, shareable index of *sources*,\n * so an entry that can only be stored by embedding a large feature set is\n * refused rather than silently turning the library into a data store.\n *\n * Applied to the whole entry (not just `geojson`), because an Add Vector Layer\n * entry can also carry a `metadata.embeddedGeoJSON` copy of its features.\n */\nexport const MAX_LAYER_LIBRARY_ENTRY_BYTES = 5 * 1024 * 1024;\n\n/**\n * Cap on how many entries a single normalization pass keeps. Enforced on the way\n * *in* — an imported bundle or a hand-edited database is untrusted, and without\n * a bound one file could push an unlimited number of records through this\n * synchronous pass and into IndexedDB. Mirrors `MAX_FAVORITES`, which caps the\n * Browser panel's other user-grown list on read as well as write.\n */\nexport const MAX_LAYER_LIBRARY_ENTRIES = 500;\n\n/**\n * Metadata keys dropped when capturing an entry. `resolvedUrl` is the dev-server\n * proxy rewrite of an XYZ template — a per-session artifact that must not be\n * baked into a saved source (the same reason `prepareLayerForSave` strips it).\n * `geometryEdited` tracks changes to the live layer, not a library source.\n */\nconst TRANSIENT_METADATA_KEYS = [\"resolvedUrl\", \"geometryEdited\"] as const;\n\nfunction nonEmptyString(value: unknown): value is string {\n return typeof value === \"string\" && value.trim() !== \"\";\n}\n\n/**\n * Whether a layer's features can be re-fetched from its source specification\n * alone, so an entry need not embed them: a tile/service template, a remote\n * file URL, or a GeoJSON source pointing at a URL. A `geojson` source whose\n * `data` is an inline FeatureCollection (not a URL string) deliberately does\n * not count.\n *\n * @param layer - The layer to inspect (source + metadata only).\n * @returns True when the source alone is enough to recreate the layer's data.\n */\nexport function hasRestorableLayerSource(\n layer: Pick<GeoLibreLayer, \"source\" | \"metadata\">,\n): boolean {\n const source = layer.source ?? {};\n if (nonEmptyString(source.url)) return true;\n if (nonEmptyString(source.data)) return true;\n if (Array.isArray(source.tiles) && source.tiles.some(nonEmptyString)) return true;\n // A Cesium Ion asset is re-fetched from its id alone (under the cesium-ion\n // source kind, the same contract the globe loads it by).\n if (cesiumIonAssetId(layer) !== null) return true;\n return nonEmptyString((layer.metadata ?? {}).originalUrl);\n}\n\n/**\n * The absolute local path a layer's data can be **re-read** from, or undefined.\n *\n * A non-empty `sourcePath` is not enough on its own: a file picked in the\n * browser has no path, and both the vector and raster controls still record the\n * bare file *name* there for display. Each control marks a genuinely re-readable\n * absolute path differently, so both conventions are honored:\n *\n * - Vector / plain local GeoJSON: `metadata.localFileReloadable` alongside an\n * absolute `sourcePath` (`prepareLayerForSave`, `needsLocalFileReload`,\n * `restoreVectorLayers`).\n * - Raster / COG: `metadata.localFilePath` holds the absolute path outright,\n * and it is what `restoreRasterLayers` re-reads on restore. Without this\n * branch a locally-opened COG could not be saved at all, even though its\n * restore pass is fully able to replay it.\n *\n * Every candidate is re-validated, because a hand-edited project or an imported\n * bundle can set these fields to anything.\n */\nexport function layerLocalPath(\n layer: Pick<GeoLibreLayer, \"sourcePath\" | \"metadata\">,\n): string | undefined {\n const metadata = layer.metadata ?? {};\n if (isReReadablePath(metadata.localFilePath)) return metadata.localFilePath;\n if (metadata.localFileReloadable !== true) return undefined;\n return isReReadablePath(layer.sourcePath) ? layer.sourcePath : undefined;\n}\n\n/**\n * Whether a value is a path safe to hand to a host file read: a non-empty\n * string, absolute, with no `..` traversal. Takes `unknown` and does the\n * non-empty check itself so every path-like field — `sourcePath`,\n * `metadata.localFilePath`, and any added later — goes through one predicate;\n * splitting the two halves is what let those two fields drift apart in the\n * first place.\n *\n * Applied to captured *and* imported entries, because a shared bundle is\n * untrusted input: a crafted `needsLocalFile` entry could otherwise point at any\n * file on the importing user's disk, and clicking it in My Data would read that\n * file. Mirrors the client-side re-validation the project-restore paths already\n * do before calling into Tauri.\n */\nfunction isReReadablePath(value: unknown): value is string {\n return nonEmptyString(value) && isAbsoluteFilesystemPath(value) && !hasPathTraversal(value);\n}\n\nfunction featureCount(geojson: FeatureCollection | undefined): number {\n return Array.isArray(geojson?.features) ? geojson.features.length : 0;\n}\n\n/**\n * Whether \"Save to library\" applies to a layer at all — the cheap predicate the\n * layer actions menu gates on. Two things have to hold:\n *\n * 1. There is something to re-add *from*: a re-fetchable source, a local file\n * path, or features to embed. A control-painted layer's features live in its\n * control rather than the store record — a tiles-mode Add Vector Layer layer\n * has no `layer.geojson` at all — so the caller can answer for that case with\n * `hasMaterializableFeatures`, keeping this gate consistent with the capture\n * path, which reads those features through `options.features`.\n * 2. The host can actually render it again. For a control-painted layer\n * ({@link isExternalNativeLayerRecord}) that means either the map's own sync\n * rebuilds it from the record or the owning plugin's restore pass can be\n * re-run, which only the host knows — so the caller supplies\n * `canRestoreControlPainted`. Without it, control-painted layers are refused\n * rather than saved into an entry that would re-add unrendered.\n *\n * A layer that qualifies here can still fail to save because its features are\n * too large to embed ({@link MAX_LAYER_LIBRARY_ENTRY_BYTES}); that outcome needs\n * the full capture, so it is reported by {@link captureLayerLibraryEntry} rather\n * than hidden behind a disabled menu item.\n *\n * @param layer - The layer to test.\n * @param options - `canRestoreControlPainted` answers whether the host can\n * re-render a control-painted layer (the desktop app passes its\n * `canRestoreLibraryLayer`); `hasMaterializableFeatures` answers whether it can\n * read the layer's features out of its control (the app passes\n * `isEmbeddableLocalVectorLayer`, the same predicate its save handler uses).\n * @returns Whether the layer can be offered to the library.\n */\nexport function canSaveLayerToLibrary(\n layer: GeoLibreLayer,\n options: {\n canRestoreControlPainted?: (layer: GeoLibreLayer) => boolean;\n hasMaterializableFeatures?: (layer: GeoLibreLayer) => boolean;\n } = {},\n): boolean {\n const hasSomethingToReAddFrom =\n hasRestorableLayerSource(layer) ||\n layerLocalPath(layer) !== undefined ||\n featureCount(layer.geojson) > 0 ||\n options.hasMaterializableFeatures?.(layer) === true;\n if (!hasSomethingToReAddFrom) return false;\n if (!isExternalNativeLayerRecord(layer)) return true;\n return options.canRestoreControlPainted?.(layer) === true;\n}\n\n/**\n * The `source` to store for a layer, with a live XYZ layer's session-specific\n * endpoint rewound to the template it was resolved from.\n *\n * `prepareLayerForSave` does the same thing when writing a project file: an XYZ\n * layer's `source.tiles`/`source.url` can hold a *resolved* endpoint while\n * `metadata.originalUrl` keeps the template, and baking the resolved value into\n * a saved record makes it replay a stale host instead of re-resolving. A library\n * entry outlives a session by design, so it needs the same rewind — stripping\n * `metadata.resolvedUrl` alone would leave the resolved endpoint sitting in\n * `source`.\n */\nfunction sourceForCapture(layer: GeoLibreLayer): Record<string, unknown> {\n const source = layer.source ?? {};\n if (layer.type !== \"xyz\") return source;\n const originalUrl = nonEmptyString((layer.metadata ?? {}).originalUrl)\n ? layer.metadata.originalUrl\n : nonEmptyString(source.url)\n ? source.url\n : null;\n if (!originalUrl) return source;\n // A TileJSON layer keeps its own tile templates — see `prepareLayerForSave`.\n // The library re-adds a captured source verbatim, with no re-resolution step,\n // so collapsing `tiles` onto the document URL here would never load a tile.\n if (typeof (layer.metadata ?? {}).tilejsonUrl === \"string\") {\n return { ...source, url: originalUrl };\n }\n return { ...source, tiles: [originalUrl], url: originalUrl };\n}\n\n/** Why a layer could not be captured into the library. */\nexport type LayerLibraryCaptureFailure =\n /** Nothing to re-add from: no source URL, no local path, no features. */\n | \"no-source\"\n /** Its features are the only copy, and they exceed the per-entry size cap. */\n | \"features-too-large\"\n /**\n * The entry exceeds the cap without embedding any features — its style,\n * attribute form, joins, or virtual fields are simply that large. Reported\n * separately so the message does not blame the layer's data.\n */\n | \"config-too-large\";\n\n/** Outcome of {@link captureLayerLibraryEntry}. */\nexport type LayerLibraryCaptureResult =\n | { ok: true; entry: LayerLibraryEntry }\n | { ok: false; reason: LayerLibraryCaptureFailure };\n\n/**\n * Serialized size of a value in UTF-8 bytes — what IndexedDB and an exported\n * bundle actually cost. Measured with {@link TextEncoder} rather than\n * `JSON.stringify().length`, which counts UTF-16 code units: CJK, Arabic, and\n * Cyrillic text is 1 unit but 2-3 bytes per character, so a length-based check\n * would let an entry through at 2-3x the cap it is supposed to enforce.\n */\nfunction jsonByteLength(value: unknown): number {\n try {\n const json = JSON.stringify(value);\n return json === undefined ? 0 : new TextEncoder().encode(json).length;\n } catch {\n // A source or metadata value with a circular reference cannot be stored at\n // all, so treat it as over any cap.\n return Number.POSITIVE_INFINITY;\n }\n}\n\n/**\n * Whether a layer is painted by a plugin control rather than by GeoLibre's own\n * layer sync (Add Vector Layer, the deck.gl raster/COG control, 3D Tiles,\n * LiDAR, …). These recreate their map output from the layer record through\n * their plugin's restore pass, so an entry for one must carry the metadata that\n * pass reads — and re-adding it has to run that pass.\n *\n * @param layer - The layer to test (metadata only).\n * @returns True when a plugin control owns the layer's rendering.\n */\nexport function isExternalNativeLayerRecord(layer: Pick<GeoLibreLayer, \"metadata\">): boolean {\n return (layer.metadata ?? {}).externalNativeLayer === true;\n}\n\n/**\n * Capture a configured layer as a reusable Layer Library entry: its source\n * spec, layer type, full style (labels included), opacity, metadata, and the\n * per-layer configuration that makes it \"fully configured\" — joins, virtual\n * fields, and the attribute form. Project-specific placement (`id`,\n * `groupId`, `beforeId`) is deliberately not captured.\n *\n * Features are embedded only when the source cannot supply them (an in-memory\n * or local-file layer). Where they land depends on who paints the layer:\n * GeoLibre-rendered layers keep them in {@link LayerLibraryEntry.geojson},\n * while a control-painted layer gets them in `metadata.embeddedGeoJSON` — the\n * same field the project format's Embed/Share flow writes and the plugin's\n * restore pass reads.\n *\n * If the result exceeds {@link MAX_LAYER_LIBRARY_ENTRY_BYTES} the capture\n * degrades rather than failing outright: it falls back to a path-only entry\n * (`needsLocalFile`) when a local file can supply the features.\n *\n * @param layer - The live layer to save.\n * @param options - Fresh entry `id`, ISO `addedAt`, an optional `name` override\n * (defaults to the layer's name), and optional `features` to embed instead of\n * the layer's own `geojson` (the caller reads a control-painted layer's\n * current data from its control).\n * @returns The entry, or the reason it could not be captured.\n */\nexport function captureLayerLibraryEntry(\n layer: GeoLibreLayer,\n options: { id: string; addedAt: string; name?: string; features?: FeatureCollection },\n): LayerLibraryCaptureResult {\n const localPath = layerLocalPath(layer);\n const hasSource = hasRestorableLayerSource(layer);\n const supplied = featureCount(options.features) > 0 ? options.features : undefined;\n const features = supplied ?? (featureCount(layer.geojson) > 0 ? layer.geojson : undefined);\n if (!hasSource && !localPath && !features) {\n return { ok: false, reason: \"no-source\" };\n }\n\n const metadata: Record<string, unknown> = { ...(layer.metadata ?? {}) };\n for (const key of TRANSIENT_METADATA_KEYS) delete metadata[key];\n // A stale embedded copy from the project this layer was loaded from: the\n // capture below re-embeds current data (or drops the copy when the source can\n // re-fetch it), so never carry the old blob through.\n delete metadata.embeddedGeoJSON;\n\n const controlPainted = isExternalNativeLayerRecord(layer);\n // Embed features only when the source cannot re-fetch them; a layer with a\n // restorable source stays data-free so its entry keeps reflecting the source.\n const embed = !hasSource && features ? structuredClone(features) : undefined;\n if (embed && controlPainted) {\n metadata.embeddedGeoJSON = embed;\n // Mirrors the project Embed flow: with the data embedded, the restore pass\n // must replay it rather than a file path that may not exist on this machine.\n delete metadata.localFileReloadable;\n }\n\n const base: LayerLibraryEntry = {\n id: options.id,\n name: options.name?.trim() || layer.name,\n addedAt: options.addedAt,\n layerType: layer.type,\n source: structuredClone(sourceForCapture(layer)),\n style: structuredClone({ ...DEFAULT_LAYER_STYLE, ...layer.style }),\n opacity:\n typeof layer.opacity === \"number\" && Number.isFinite(layer.opacity) ? layer.opacity : 1,\n metadata: structuredClone(metadata),\n ...(localPath ? { sourcePath: localPath } : {}),\n ...(layer.joins?.length ? { joins: structuredClone(layer.joins) } : {}),\n ...(layer.virtualFields?.length ? { virtualFields: structuredClone(layer.virtualFields) } : {}),\n ...(layer.attributeForm ? { attributeForm: structuredClone(layer.attributeForm) } : {}),\n ...(layer.popup ? { popup: structuredClone(layer.popup) } : {}),\n ...(layer.descriptiveMetadata\n ? { descriptiveMetadata: structuredClone(layer.descriptiveMetadata) }\n : {}),\n };\n const withFeatures = embed && !controlPainted ? { ...base, geojson: embed } : base;\n\n if (jsonByteLength(withFeatures) <= MAX_LAYER_LIBRARY_ENTRY_BYTES) {\n return { ok: true, entry: withFeatures };\n }\n if (embed && localPath) {\n // Last resort for an oversized local file: keep the path and let the\n // desktop host re-read it at add time.\n const { embeddedGeoJSON: _dropped, ...leanMetadata } = withFeatures.metadata;\n const pathOnly: LayerLibraryEntry = {\n ...withFeatures,\n metadata: leanMetadata,\n needsLocalFile: true,\n };\n delete pathOnly.geojson;\n if (jsonByteLength(pathOnly) <= MAX_LAYER_LIBRARY_ENTRY_BYTES) {\n return { ok: true, entry: pathOnly };\n }\n }\n // Blame the right thing: an entry can also exceed the cap with no embedded\n // features at all (a rule-based style with hundreds of rules, a large\n // attribute form), and telling that user their \"features are the only copy\"\n // would send them looking in the wrong place.\n return { ok: false, reason: embed ? \"features-too-large\" : \"config-too-large\" };\n}\n\n/**\n * The presentation state a re-added layer takes from its entry. For the\n * `local-file` plan the host imports the file first (producing a default-styled\n * layer) and then applies this patch, so a degraded entry still comes back\n * \"fully configured\" rather than losing the styling the entry preserved.\n */\nexport interface LayerLibraryConfigPatch {\n name: string;\n style: LayerStyle;\n opacity: number;\n joins?: LayerJoin[];\n virtualFields?: LayerVirtualField[];\n attributeForm?: AttributeFormConfig;\n popup?: LayerPopupConfig;\n descriptiveMetadata?: LayerDescriptiveMetadata;\n}\n\n/** How an entry should be re-added to the current project. */\nexport type LayerLibraryAddPlan =\n /** Add this layer record to the store; the map sync renders it. */\n | { kind: \"layer\"; layer: GeoLibreLayer }\n /**\n * The entry's features live only in a local file the host must re-read (its\n * data was too large to embed), so the caller runs its local-file add path for\n * `path` and then applies `config` to the layer that import produced. Only\n * reachable on a host with filesystem access.\n */\n | { kind: \"local-file\"; path: string; config: LayerLibraryConfigPatch };\n\n/**\n * Plan how to re-add a library entry to the current project.\n *\n * @param entry - The library entry to add.\n * @param options - The fresh layer `id` to create it under.\n * @returns Either the layer record to add, or the local file to re-read.\n */\nexport function planLayerLibraryAdd(\n entry: LayerLibraryEntry,\n options: { id: string },\n): LayerLibraryAddPlan {\n if (layerLibraryEntryNeedsLocalFile(entry)) {\n return {\n kind: \"local-file\",\n // Non-null by the guard above, which also re-validates the path.\n path: entry.sourcePath as string,\n config: layerLibraryConfigPatch(entry),\n };\n }\n return {\n kind: \"layer\",\n layer: {\n id: options.id,\n name: entry.name,\n type: entry.layerType,\n source: structuredClone(entry.source),\n // Always added visible: the user just asked for this layer.\n visible: true,\n opacity: entry.opacity,\n style: structuredClone(entry.style),\n metadata: structuredClone(entry.metadata),\n ...(entry.sourcePath ? { sourcePath: entry.sourcePath } : {}),\n ...(entry.geojson ? { geojson: structuredClone(entry.geojson) } : {}),\n ...(entry.joins ? { joins: structuredClone(entry.joins) } : {}),\n ...(entry.virtualFields ? { virtualFields: structuredClone(entry.virtualFields) } : {}),\n ...(entry.attributeForm ? { attributeForm: structuredClone(entry.attributeForm) } : {}),\n ...(entry.popup ? { popup: structuredClone(entry.popup) } : {}),\n ...(entry.descriptiveMetadata\n ? { descriptiveMetadata: structuredClone(entry.descriptiveMetadata) }\n : {}),\n },\n };\n}\n\n/**\n * The presentation state to apply to a layer the host imported from an entry's\n * file, so the `local-file` plan preserves everything the entry captured.\n *\n * @param entry - The entry being re-added.\n * @returns The patch to merge onto the imported layer.\n */\nexport function layerLibraryConfigPatch(entry: LayerLibraryEntry): LayerLibraryConfigPatch {\n return {\n name: entry.name,\n style: structuredClone(entry.style),\n opacity: entry.opacity,\n ...(entry.joins ? { joins: structuredClone(entry.joins) } : {}),\n ...(entry.virtualFields ? { virtualFields: structuredClone(entry.virtualFields) } : {}),\n ...(entry.attributeForm ? { attributeForm: structuredClone(entry.attributeForm) } : {}),\n ...(entry.popup ? { popup: structuredClone(entry.popup) } : {}),\n ...(entry.descriptiveMetadata\n ? { descriptiveMetadata: structuredClone(entry.descriptiveMetadata) }\n : {}),\n };\n}\n\n/**\n * The saved joins whose source layer is not present in the target project.\n *\n * A {@link LayerJoin} points at another layer by its **project-local id**, so a\n * join travels only as far as the project it was captured in: re-adding an entry\n * elsewhere leaves those joins referring to layers that do not exist. The join\n * engine degrades quietly (`applyLayerJoins` just drops an unresolved join from\n * its active set), which is the right runtime behavior but a silent one — so the\n * caller uses this to tell the user which joins did not come back, instead of\n * letting them find a column missing later.\n *\n * The joins are still captured and reapplied rather than dropped: they resolve\n * whenever the source layer *is* there, which covers re-adding into the project\n * the entry came from, or into one restored from the same saved project.\n *\n * @param entry - The entry being re-added.\n * @param presentLayerIds - Ids of the layers in the target project.\n * @returns Ids of the join-target layers that cannot resolve ({@link LayerJoin}\n * has no display name).\n */\nexport function unresolvedLayerLibraryJoins(\n entry: Pick<LayerLibraryEntry, \"joins\">,\n presentLayerIds: Iterable<string>,\n): string[] {\n if (!entry.joins?.length) return [];\n const present = new Set(presentLayerIds);\n return entry.joins\n .filter((join) => join.enabled !== false && !present.has(join.joinLayerId))\n .map((join) => join.joinLayerId);\n}\n\n/**\n * Whether re-adding an entry needs a host that can read local files — used to\n * badge the entry in the Browser panel and to explain the failure in the\n * browser build, where there is no filesystem to re-read from.\n *\n * @param entry - The entry to test.\n * @returns True when only a filesystem-capable host can add it.\n */\nexport function layerLibraryEntryNeedsLocalFile(entry: LayerLibraryEntry): boolean {\n return entry.needsLocalFile === true && isReReadablePath(entry.sourcePath);\n}\n\n/** A plain JSON object (not an array, not null), or undefined. */\nfunction plainObject(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\n/** Whether every listed key on an object holds a non-empty string. */\nfunction hasStringKeys(object: Record<string, unknown>, keys: readonly string[]): boolean {\n return keys.every((key) => nonEmptyString(object[key]));\n}\n\n/**\n * Keep only the {@link LayerJoin} members a hand-edited or imported bundle got\n * right. A join missing any of its required fields resolves to nothing and would\n * sit in the Joins UI as a permanently-broken definition, so drop it rather than\n * the whole entry.\n */\nfunction validJoins(value: unknown): LayerJoin[] | undefined {\n const items = objectArray(value)?.filter((join) =>\n hasStringKeys(join, [\"id\", \"joinLayerId\", \"targetField\", \"joinField\"]),\n );\n return items && items.length > 0 ? (structuredClone(items) as unknown as LayerJoin[]) : undefined;\n}\n\n/** Keep only {@link LayerVirtualField} members that carry an id, name, and expression. */\nfunction validVirtualFields(value: unknown): LayerVirtualField[] | undefined {\n const items = objectArray(value)?.filter((field) =>\n hasStringKeys(field, [\"id\", \"name\", \"expression\"]),\n );\n return items && items.length > 0\n ? (structuredClone(items) as unknown as LayerVirtualField[])\n : undefined;\n}\n\n/**\n * Keep an {@link AttributeFormConfig} only when every field entry names the\n * feature property it configures; a field with no `field` key would apply to\n * nothing.\n */\nfunction validAttributeForm(value: unknown): AttributeFormConfig | undefined {\n const config = plainObject(value);\n if (!config || !Array.isArray(config.fields)) return undefined;\n const fields = config.fields.filter(\n (field) =>\n plainObject(field) !== undefined && nonEmptyString((field as { field?: unknown }).field),\n );\n if (fields.length === 0) return undefined;\n return structuredClone({ ...config, fields }) as unknown as AttributeFormConfig;\n}\n\n/**\n * A structurally plausible popup design, or undefined. `fields` is optional (a\n * config may carry only a title or the hover flag), so unlike the attribute\n * form an empty field list does not disqualify the block — but a config with\n * nothing set at all is dropped rather than stored as an empty object.\n */\nfunction validPopup(value: unknown): LayerPopupConfig | undefined {\n const config = plainObject(value);\n if (!config) return undefined;\n const fields = Array.isArray(config.fields)\n ? config.fields.filter(\n (field) =>\n plainObject(field) !== undefined && nonEmptyString((field as { field?: unknown }).field),\n )\n : undefined;\n const rest = { ...config };\n delete rest.fields;\n if (!fields?.length && Object.keys(rest).length === 0) return undefined;\n return structuredClone({\n ...rest,\n ...(fields?.length ? { fields } : {}),\n }) as unknown as LayerPopupConfig;\n}\n\n/** An array of plain JSON objects, or undefined when nothing usable is present. */\nfunction objectArray(value: unknown): Record<string, unknown>[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const items = value.filter((item): item is Record<string, unknown> => Boolean(plainObject(item)));\n return items.length > 0 ? items : undefined;\n}\n\n/** A GeoJSON FeatureCollection shape, or undefined. */\nfunction featureCollection(value: unknown): FeatureCollection | undefined {\n const object = plainObject(value);\n if (!object || object.type !== \"FeatureCollection\" || !Array.isArray(object.features)) {\n return undefined;\n }\n return object as unknown as FeatureCollection;\n}\n\n/**\n * Coerce an untrusted entries array (an imported bundle, or a record written by\n * an older version) into valid {@link LayerLibraryEntry} records. Entries\n * without a usable id, name, or layer type are dropped, ids are de-duplicated,\n * the style is sanitized through the Style Manager's\n * {@link sanitizeLayerStylePatch} and completed against the defaults, and each\n * optional block is kept only when structurally plausible.\n *\n * Entries that cannot be re-added at all (no source, no path, no features) are\n * dropped rather than shown as permanently-failing rows.\n *\n * @param value - The raw entries value.\n * @returns Normalized, de-duplicated entries (empty when none survive).\n */\nexport function normalizeLayerLibraryEntries(value: unknown): LayerLibraryEntry[] {\n if (!Array.isArray(value)) return [];\n const entries: LayerLibraryEntry[] = [];\n const seen = new Set<string>();\n for (const raw of value) {\n const candidate = plainObject(raw);\n if (!candidate) continue;\n const id = typeof candidate.id === \"string\" ? candidate.id.trim() : \"\";\n const name = typeof candidate.name === \"string\" ? candidate.name.trim() : \"\";\n // An unknown layer type is dropped rather than coerced: every downstream\n // renderer branches on it, so a made-up value would render nothing.\n const layerType = LAYER_TYPES.includes(candidate.layerType as LayerType)\n ? (candidate.layerType as LayerType)\n : undefined;\n if (!id || !name || !layerType || seen.has(id)) continue;\n const source = plainObject(candidate.source) ?? {};\n const metadata = { ...(plainObject(candidate.metadata) ?? {}) };\n // Strip the same per-session keys the capture path drops, so a stale one on\n // an older record or an imported bundle is cleaned up on load instead of\n // replaying a dev-server proxy rewrite indefinitely.\n for (const key of TRANSIENT_METADATA_KEYS) delete metadata[key];\n // `metadata.localFilePath` is the *second* path in an entry, and the one\n // `restoreRasterLayers` re-reads — so it needs the same validation as\n // `sourcePath` below, not a free pass because it happens to live in\n // metadata. Without this a shared bundle could keep a valid-looking\n // `sourcePath` while pointing the raster restore at any file on the\n // importing user's disk.\n if (\"localFilePath\" in metadata && !isReReadablePath(metadata.localFilePath)) {\n delete metadata.localFilePath;\n }\n // A bundle is shareable, so its `sourcePath` is untrusted: keep it only when\n // it is an absolute, traversal-free path a host could legitimately re-read.\n // Without this a crafted entry could point `onAddFilePath` at any file on the\n // importing user's disk.\n const sourcePath = isReReadablePath(candidate.sourcePath) ? candidate.sourcePath : undefined;\n const geojson = featureCollection(candidate.geojson);\n // Same \"is there anything to re-add from\" gate as the capture path, so a\n // hand-edited bundle cannot introduce an entry that always fails. Both path\n // conventions count (a local raster's only path is `metadata.localFilePath`,\n // already validated above), and so does a control-painted entry's\n // `metadata.embeddedGeoJSON`, which holds its features instead of `geojson`.\n if (\n !hasRestorableLayerSource({ source, metadata }) &&\n !sourcePath &&\n !nonEmptyString(metadata.localFilePath) &&\n featureCount(geojson) === 0 &&\n featureCount(featureCollection(metadata.embeddedGeoJSON)) === 0\n ) {\n continue;\n }\n const opacity =\n typeof candidate.opacity === \"number\" &&\n Number.isFinite(candidate.opacity) &&\n candidate.opacity >= 0 &&\n candidate.opacity <= 1\n ? candidate.opacity\n : 1;\n const normalized: LayerLibraryEntry = {\n id,\n name,\n addedAt: typeof candidate.addedAt === \"string\" ? candidate.addedAt : \"\",\n layerType,\n source: structuredClone(source),\n style: { ...DEFAULT_LAYER_STYLE, ...sanitizeLayerStylePatch(candidate.style) },\n opacity,\n metadata: structuredClone(metadata),\n ...(sourcePath ? { sourcePath } : {}),\n ...(() => {\n // Each block is kept only when its members carry the fields their engine\n // needs; a malformed member is dropped rather than the whole entry.\n const joins = validJoins(candidate.joins);\n const virtualFields = validVirtualFields(candidate.virtualFields);\n const attributeForm = validAttributeForm(candidate.attributeForm);\n const popup = validPopup(candidate.popup);\n const descriptiveMetadata = normalizeLayerDescriptiveMetadata(\n candidate.descriptiveMetadata,\n );\n return {\n ...(joins ? { joins } : {}),\n ...(virtualFields ? { virtualFields } : {}),\n ...(attributeForm ? { attributeForm } : {}),\n ...(popup ? { popup } : {}),\n ...(descriptiveMetadata ? { descriptiveMetadata } : {}),\n };\n })(),\n ...(geojson ? { geojson: structuredClone(geojson) } : {}),\n // `needsLocalFile` only means anything with a path to read.\n ...(candidate.needsLocalFile === true && sourcePath ? { needsLocalFile: true } : {}),\n };\n // The per-entry size ceiling is enforced here too, not only on capture: an\n // imported bundle is produced elsewhere, so without this a crafted (or just\n // hand-assembled) file could push tens of MB of embedded features per entry\n // into IndexedDB, exactly the \"library as a data store\" outcome the cap\n // exists to prevent.\n if (jsonByteLength(normalized) > MAX_LAYER_LIBRARY_ENTRY_BYTES) continue;\n seen.add(id);\n entries.push(normalized);\n if (entries.length >= MAX_LAYER_LIBRARY_ENTRIES) break;\n }\n return entries;\n}\n\n/**\n * Copy of an entry with per-user credentials removed from its source.\n *\n * `source` fields that can hold a per-user credential are removed on export.\n * `requestHeaders`, for example, carries the bearer token / API key an\n * authenticated 3D Tiles layer was added with (see\n * `persistedThreeDTilesRequestHeaders`, which keeps non-Google auth headers when\n * persisting into a project file).\n *\n * Kept in the local IndexedDB entry so re-adding on this machine still works,\n * and dropped only from the bundle: Export is pitched as a one-click \"share with\n * your team\" action, which makes it a far more direct exposure path than handing\n * someone a whole project file. A recipient re-enters their own credentials,\n * which is the correct outcome.\n *\n * The names come from `isCredentialFieldName`, so this path and project export\n * share one registry instead of drifting apart.\n */\nfunction redactEntryForExport(entry: LayerLibraryEntry): LayerLibraryEntry {\n const source = { ...entry.source };\n let changed = false;\n for (const key of Object.keys(source)) {\n if (isCredentialFieldName(key)) {\n delete source[key];\n changed = true;\n }\n }\n // Sweep every URL-shaped value in `source` rather than naming the fields that\n // hold one. Different layer types put their URL in different places — `url`,\n // `data` (a GeoJSON source pointing at a URL, which\n // `hasRestorableLayerSource` already treats as re-fetchable), `tiles[]` — and\n // a per-field list has to be extended for each new one, with a silent\n // credential leak as the cost of forgetting.\n for (const [key, value] of Object.entries(source)) {\n const redacted = redactSourceValue(value);\n if (redacted !== value) {\n source[key] = redacted;\n changed = true;\n }\n }\n // `metadata.originalUrl` is the template a tile layer restores from, so it\n // carries the same credential as `source.tiles`.\n let metadata = entry.metadata;\n if (typeof metadata.originalUrl === \"string\") {\n const redacted = redactUrlCredentials(metadata.originalUrl);\n if (redacted !== metadata.originalUrl) {\n metadata = { ...metadata, originalUrl: redacted };\n changed = true;\n }\n }\n return changed ? { ...entry, source, metadata } : entry;\n}\n\n/** Depth limit for the export sweep, so a pathological source cannot recurse far. */\nconst MAX_REDACT_DEPTH = 6;\n\n/**\n * Whether a value is GeoJSON data rather than source configuration. The sweep\n * stops at these: `source.data` can hold an inline FeatureCollection, and\n * descending into it would both walk every feature and *rewrite* any feature\n * property that happens to look like a URL with a `key=` parameter — corrupting\n * the user's data in the name of protecting it. Credentials live in\n * configuration, not in features.\n */\nfunction isGeoJsonPayload(value: Record<string, unknown>): boolean {\n return (\n value.type === \"FeatureCollection\" ||\n value.type === \"Feature\" ||\n value.type === \"GeometryCollection\"\n );\n}\n\n/**\n * Redact a `source` value of any shape: strings are treated as possible URLs,\n * arrays as lists of them (tile templates), and nested objects are swept\n * recursively — a layer type that groups its endpoint and credentials as\n * `{ store: { url, headers } }` must not slip a token past the export just\n * because it is one level deeper. Nested `requestHeaders`-style keys are dropped\n * the same way the top level drops them.\n *\n * GeoJSON payloads are left alone (see {@link isGeoJsonPayload}) and recursion is\n * depth-capped. Returns the original reference when nothing changed, so callers\n * can detect that by identity.\n */\nfunction redactSourceValue(value: unknown, depth = 0): unknown {\n if (typeof value === \"string\") return redactUrlCredentials(value);\n if (depth >= MAX_REDACT_DEPTH) {\n // Fail closed, matching `redactConfigurationValue` in credentials.ts: no\n // built-in layer needs configuration nested this deep to be re-addable, and\n // handing the value back unswept would let a credential ride out of the\n // bundle merely by sitting past the traversal cap. Only the exported bundle\n // loses it; the local IndexedDB entry is untouched.\n return undefined;\n }\n if (Array.isArray(value)) {\n const redacted = value.map((item) => redactSourceValue(item, depth + 1));\n return redacted.some((item, index) => item !== value[index]) ? redacted : value;\n }\n const object = plainObject(value);\n if (!object || isGeoJsonPayload(object)) return value;\n let changed = false;\n const out: Record<string, unknown> = {};\n for (const [key, nested] of Object.entries(object)) {\n if (isCredentialFieldName(key)) {\n changed = true;\n continue;\n }\n const redacted = redactSourceValue(nested, depth + 1);\n if (redacted !== nested) changed = true;\n out[key] = redacted;\n }\n return changed ? out : value;\n}\n\n/**\n * Serialize Layer Library entries into the shareable bundle JSON written by the\n * Export action and read back by {@link parseLayerLibrary}. Per-user credentials\n * are redacted first (see {@link EXPORT_REDACTED_SOURCE_KEYS}).\n *\n * @param entries - The entries to export.\n * @returns Pretty-printed bundle JSON.\n */\nexport function serializeLayerLibrary(entries: LayerLibraryEntry[]): string {\n return JSON.stringify(\n {\n type: LAYER_LIBRARY_BUNDLE_TYPE,\n version: LAYER_LIBRARY_BUNDLE_VERSION,\n entries: entries.map(redactEntryForExport),\n },\n null,\n 2,\n );\n}\n\n/**\n * Parse a Layer Library bundle produced by {@link serializeLayerLibrary} (a\n * bare entries array is also accepted, so hand-authored files work). Entries\n * are normalized through {@link normalizeLayerLibraryEntries}.\n *\n * @param json - The bundle file content.\n * @returns The normalized entries.\n * @throws Error when the JSON is not a layer-library bundle or holds no usable\n * entries.\n */\nexport function parseLayerLibrary(json: string): LayerLibraryEntry[] {\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n throw new Error(\"Not a valid layer library file (invalid JSON).\");\n }\n let rawEntries: unknown = null;\n if (Array.isArray(parsed)) {\n rawEntries = parsed;\n } else if (plainObject(parsed)?.type === LAYER_LIBRARY_BUNDLE_TYPE) {\n // Refuse a bundle from a newer format rather than misreading it with v1\n // semantics. Bare arrays stay accepted for hand-authored files.\n if (plainObject(parsed)?.version !== LAYER_LIBRARY_BUNDLE_VERSION) {\n throw new Error(\"Unsupported layer library version.\");\n }\n rawEntries = plainObject(parsed)?.entries;\n }\n if (rawEntries === null) {\n throw new Error(\"Not a valid layer library file.\");\n }\n const entries = normalizeLayerLibraryEntries(rawEntries);\n if (entries.length === 0) {\n throw new Error(\"The layer library file holds no usable entries.\");\n }\n return entries;\n}\n\n/**\n * Create a fresh unique id for a new library entry.\n *\n * @returns A random id (UUID when available).\n */\nexport function createLayerLibraryEntryId(): string {\n if (typeof crypto !== \"undefined\" && \"randomUUID\" in crypto) {\n return crypto.randomUUID();\n }\n return `layer-lib-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n","/**\n * The Style Manager, Layer Library and Template Library. The app-level lists\n * live outside the project lifecycle (persisted by the desktop app); only the\n * project-scoped style list is saved with the project.\n */\nimport { MAX_LAYER_LIBRARY_ENTRIES } from \"../layer-library\";\nimport type { LayerLibraryEntry, ProjectTemplateEntry, StyleLibraryEntry } from \"../types\";\nimport type { SliceCreator } from \"./types\";\n\nexport interface LibrariesSlice {\n /**\n * App-level Style Manager library (issue #1294). Lives outside the project\n * lifecycle: never serialized into the project file, untouched by\n * newProject/loadProject, and persisted by the desktop app (IndexedDB).\n */\n styleLibrary: StyleLibraryEntry[];\n /**\n * App-level Layer Library (issue #1520) — the Browser panel's My Data\n * section. Like {@link styleLibrary} it lives outside the project lifecycle:\n * never serialized into the project file, untouched by newProject/loadProject,\n * and persisted by the desktop app (IndexedDB). Most recently saved first.\n */\n layerLibrary: LayerLibraryEntry[];\n /**\n * App-level Template Library. Persisted by the desktop app (IndexedDB).\n */\n templateLibrary: ProjectTemplateEntry[];\n /**\n * Project-scoped Style Manager entries (issue #1294), serialized into the\n * `.geolibre.json` `styleLibrary` array and replaced on project load.\n */\n projectStyleLibrary: StyleLibraryEntry[];\n\n /**\n * Replace the app-level style library wholesale. Used by the persistence\n * layer on startup and by bundle imports.\n */\n setStyleLibrary: (entries: StyleLibraryEntry[]) => void;\n /**\n * Insert or replace (matching by `id`) a Style Manager entry in the given\n * scope. Scope-local, like {@link deleteStyleLibraryEntry}: the other\n * scope's list is never touched, since a same id there can belong to an\n * unrelated entry after loading a project authored elsewhere. Project-scope\n * saves mark the project dirty.\n */\n saveStyleLibraryEntry: (entry: StyleLibraryEntry, scope?: \"app\" | \"project\") => void;\n /**\n * Remove a Style Manager entry by id. When `scope` is given only that list\n * is touched — the two scopes can legitimately hold the same id after\n * loading a project authored elsewhere, and deleting a project entry must\n * not erase a local app-library style (or vice versa). Omitting `scope`\n * removes the id from both lists.\n */\n deleteStyleLibraryEntry: (id: string, scope?: \"app\" | \"project\") => void;\n\n /**\n * Replace the app-level Layer Library wholesale. Used by the persistence\n * layer on startup and by bundle imports.\n */\n setLayerLibrary: (entries: LayerLibraryEntry[]) => void;\n /**\n * Insert or replace (matching by `id`) a Layer Library entry. New entries go\n * to the front so the most recently saved layer leads the My Data section.\n */\n saveLayerLibraryEntry: (entry: LayerLibraryEntry) => void;\n /** Rename a Layer Library entry; a blank name is ignored. */\n renameLayerLibraryEntry: (id: string, name: string) => void;\n /** Remove a Layer Library entry by id. */\n deleteLayerLibraryEntry: (id: string) => void;\n\n /** Replace the app-level template library wholesale. */\n setTemplateLibrary: (templates: ProjectTemplateEntry[]) => void;\n /** Insert or replace a template in the Template Library. */\n saveTemplateEntry: (entry: ProjectTemplateEntry) => void;\n /** Remove a template entry by id from the Template Library. */\n deleteTemplateEntry: (id: string) => void;\n}\n\nexport const createLibrariesSlice: SliceCreator<LibrariesSlice> = (set) => ({\n styleLibrary: [],\n layerLibrary: [],\n templateLibrary: [],\n projectStyleLibrary: [],\n\n setStyleLibrary: (entries) => set({ styleLibrary: entries }),\n saveStyleLibraryEntry: (entry, scope = \"app\") =>\n set((s) => {\n const upsert = (list: StyleLibraryEntry[]) =>\n list.some((e) => e.id === entry.id)\n ? list.map((e) => (e.id === entry.id ? entry : e))\n : [...list, entry];\n if (scope === \"project\") {\n return {\n projectStyleLibrary: upsert(s.projectStyleLibrary),\n isDirty: true,\n };\n }\n // App-level saves don't touch the project file, so no dirty flag.\n return { styleLibrary: upsert(s.styleLibrary) };\n }),\n deleteStyleLibraryEntry: (id, scope) =>\n set((s) => {\n const inProject = scope !== \"app\" && s.projectStyleLibrary.some((e) => e.id === id);\n const inLibrary = scope !== \"project\" && s.styleLibrary.some((e) => e.id === id);\n // Keep untouched scopes reference-stable so the IndexedDB\n // persistence (which watches the styleLibrary reference) does not\n // rewrite an unchanged library.\n return {\n styleLibrary: inLibrary ? s.styleLibrary.filter((e) => e.id !== id) : s.styleLibrary,\n projectStyleLibrary: inProject\n ? s.projectStyleLibrary.filter((e) => e.id !== id)\n : s.projectStyleLibrary,\n isDirty: s.isDirty || inProject,\n };\n }),\n\n // The entry cap is applied on every write, not just when reading\n // untrusted input, so ordinary use (repeated saves, importing several\n // bundles over time) cannot grow the library past it. Mirrors\n // `writeBrowserFavorites`, which slices to MAX_FAVORITES on each write.\n setLayerLibrary: (entries) => set({ layerLibrary: entries.slice(0, MAX_LAYER_LIBRARY_ENTRIES) }),\n saveLayerLibraryEntry: (entry) =>\n set((s) => ({\n // App-level saves don't touch the project file, so no dirty flag\n // (mirrors saveStyleLibraryEntry's \"app\" scope). A new entry goes to\n // the front, so at the cap the oldest falls off the end.\n layerLibrary: s.layerLibrary.some((e) => e.id === entry.id)\n ? s.layerLibrary.map((e) => (e.id === entry.id ? entry : e))\n : [entry, ...s.layerLibrary].slice(0, MAX_LAYER_LIBRARY_ENTRIES),\n })),\n renameLayerLibraryEntry: (id, name) =>\n set((s) => {\n const trimmed = name.trim();\n // Keep the array reference stable for a no-op rename so the\n // IndexedDB persistence (which watches the reference) skips a write.\n if (!trimmed || !s.layerLibrary.some((e) => e.id === id && e.name !== trimmed)) {\n return {};\n }\n return {\n layerLibrary: s.layerLibrary.map((e) => (e.id === id ? { ...e, name: trimmed } : e)),\n };\n }),\n deleteLayerLibraryEntry: (id) =>\n set((s) =>\n s.layerLibrary.some((e) => e.id === id)\n ? { layerLibrary: s.layerLibrary.filter((e) => e.id !== id) }\n : {},\n ),\n\n setTemplateLibrary: (templates) => set({ templateLibrary: templates }),\n saveTemplateEntry: (entry) =>\n set((s) => ({\n templateLibrary: s.templateLibrary.some((t) => t.id === entry.id)\n ? s.templateLibrary.map((t) => (t.id === entry.id ? entry : t))\n : [...s.templateLibrary, entry],\n })),\n deleteTemplateEntry: (id) =>\n set((s) => ({\n templateLibrary: s.templateLibrary.filter((t) => t.id !== id),\n })),\n});\n","/**\n * Runtime-settable window (ms) used to coalesce rapid history-producing changes\n * (e.g. a continuous opacity/style slider drag) into a single undo entry. Tests\n * set this to 0 for deterministic one-entry-per-action behavior.\n */\nlet historyCoalesceMs = 400;\n\nexport function setHistoryCoalesceMs(ms: number): void {\n historyCoalesceMs = ms;\n}\n\nexport function getHistoryCoalesceMs(): number {\n return historyCoalesceMs;\n}\n\n/**\n * Soft budget (in total feature count) for the geometry/attribute payload\n * retained across undo snapshots. Each in-app edit of a vector layer pushes a\n * new snapshot that holds the layer's full `geojson`; without a bound, editing\n * a large layer many times pins several copies of its feature set in memory\n * (issue #341). When the distinct features across snapshots exceed this budget,\n * the oldest snapshots are dropped (see {@link trimHistoryBySize}).\n *\n * Feature count is a cheap proxy for payload size; it avoids serializing\n * geometry on every edit. The default is generous enough that ordinary\n * multi-step editing of small/medium layers keeps the full history depth, while\n * very large layers trade history depth for a bounded footprint.\n */\nlet maxHistoryFeatureCount = 500_000;\n\nexport function setMaxHistoryFeatureCount(count: number): void {\n if (!Number.isFinite(count) || count < 0) {\n throw new RangeError(\n `maxHistoryFeatureCount must be a non-negative finite number, got ${count}`,\n );\n }\n maxHistoryFeatureCount = count;\n}\n\nexport function getMaxHistoryFeatureCount(): number {\n return maxHistoryFeatureCount;\n}\n\n/** Structural shape of a partialized undo snapshot, for size accounting. */\ninterface HistorySnapshot {\n layers?: {\n geojson?: { features?: unknown[] } | null;\n source?: { url?: unknown } | null;\n }[];\n}\n\n/**\n * Bytes of inline payload charged as one feature-equivalent, so a `data:` URL\n * competes for the same budget as geometry. A rough vector feature costs a few\n * hundred bytes of live JS objects, which is the order this matches.\n */\nconst BYTES_PER_FEATURE_EQUIVALENT = 200;\n\n/**\n * The size a snapshot adds to the budget, skipping any payload already in\n * `seen` so payloads shared across snapshots (unchanged layers keep the same\n * reference) are counted once. Mutates `seen` with each newly counted payload.\n *\n * Two payload kinds count. Vector `geojson` contributes its feature count. A\n * layer whose `source.url` is an inline `data:` URL contributes its length as\n * feature-equivalents: a NetCDF grid baked to pixels holds a whole PNG there,\n * and re-symbolizing it writes a fresh multi-megabyte string on every change, so\n * without this the pruner sees a \"free\" snapshot and history grows unbounded.\n */\nfunction distinctFeatureCount(snapshot: HistorySnapshot, seen: Set<unknown>): number {\n let count = 0;\n for (const layer of snapshot.layers ?? []) {\n const geojson = layer?.geojson;\n // Dedup the reference first so a payload shared across snapshots is visited\n // once even when its `features` is missing/malformed (it then contributes 0).\n if (geojson && !seen.has(geojson)) {\n seen.add(geojson);\n if (Array.isArray(geojson.features)) count += geojson.features.length;\n }\n const url = layer?.source?.url;\n // Strings have no stable identity to dedup by reference, so `seen` holds the\n // value itself; an unchanged layer repeats the same URL across snapshots and\n // must be charged once, exactly as a shared feature payload is.\n if (typeof url === \"string\" && url.startsWith(\"data:\") && !seen.has(url)) {\n seen.add(url);\n count += Math.ceil(url.length / BYTES_PER_FEATURE_EQUIVALENT);\n }\n }\n return count;\n}\n\n/**\n * Bound the memory held by undo history by dropping the oldest snapshots whose\n * combined feature payload exceeds `maxFeatures`.\n *\n * Walks newest-to-oldest and keeps the most recent snapshots whose cumulative\n * distinct feature count fits the budget. The single newest snapshot is always\n * kept regardless of size, so one-step undo of even a huge edit still works.\n * Feature sets shared by reference across snapshots (unchanged layers) are\n * counted once, so retaining many snapshots of small layers stays cheap.\n *\n * Returns the original array when nothing needs trimming, or a trimmed slice\n * (oldest entries removed) otherwise. Pure: never mutates its input.\n */\nexport function trimHistoryBySize<T extends HistorySnapshot>(\n pastStates: T[],\n maxFeatures: number,\n): T[] {\n if (!Number.isFinite(maxFeatures) || maxFeatures < 0) {\n throw new RangeError(`maxFeatures must be a non-negative finite number, got ${maxFeatures}`);\n }\n if (pastStates.length <= 1) return pastStates;\n const seen = new Set<unknown>();\n const lastIndex = pastStates.length - 1;\n // The newest snapshot is always retained, even if it alone exceeds the budget.\n let total = distinctFeatureCount(pastStates[lastIndex], seen);\n let keepFrom = lastIndex;\n for (let i = lastIndex - 1; i >= 0; i--) {\n const added = distinctFeatureCount(pastStates[i], seen);\n if (total + added > maxFeatures) break;\n total += added;\n keepFrom = i;\n }\n return keepFrom === 0 ? pastStates : pastStates.slice(keepFrom);\n}\n\n/** A debounced function with a `cancel` to clear its in-flight burst window. */\nexport type DebouncedFn<A extends unknown[]> = ((...args: A) => void) & {\n /** Clear the active burst timer so the next call fires on its leading edge. */\n cancel: () => void;\n};\n\n/**\n * Leading-edge debounce. Fires `fn` immediately on the first call of a burst,\n * then suppresses further calls until `getWait()` ms of quiet have elapsed.\n * When the wait is <= 0, every call is passed straight through (used in tests).\n *\n * Used as zundo's `handleSet`: `fn` is zundo's \"save previous state to history\"\n * function, so firing only on the leading edge records the pre-burst state once.\n * `cancel()` resets the window so that clearing history mid-burst doesn't\n * suppress the next edit's save.\n */\nexport function leadingDebounce<A extends unknown[]>(\n fn: (...args: A) => void,\n getWait: () => number,\n): DebouncedFn<A> {\n let timer: ReturnType<typeof setTimeout> | null = null;\n const wrapped = (...args: A) => {\n const wait = getWait();\n if (wait <= 0) {\n fn(...args);\n return;\n }\n const atBurstStart = timer === null;\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n }, wait);\n if (atBurstStart) fn(...args);\n };\n wrapped.cancel = () => {\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n };\n return wrapped;\n}\n","/**\n * Undo/redo for the app store. zundo's `temporal` middleware tracks only the\n * project-data fields that {@link partializeHistory} lists (layers, groups,\n * basemap, story map, comments, dashboard widgets, legend); every other field,\n * including the whole `ui`\n * dialog sub-state, selection and the camera, is outside the history, so\n * changing it never records an undo step and undo/redo never reverts it.\n *\n * The functions here act on the store bound by {@link bindHistoryStore}\n * rather than importing it, so the slices (whose `newProject`/`loadProject`\n * call {@link clearHistory}) can depend on this module without a cycle.\n */\nimport { shallow } from \"zustand/shallow\";\nimport type { StoreApi } from \"zustand\";\nimport type { TemporalState, ZundoOptions } from \"zundo\";\nimport { DEFAULT_ELLIPSOID_ID, getPlanetaryBasemapByStyleUrl } from \"../ellipsoids\";\nimport {\n getHistoryCoalesceMs,\n getMaxHistoryFeatureCount,\n leadingDebounce,\n trimHistoryBySize,\n} from \"../history\";\nimport { scrubPrintLayoutForLayers } from \"../print-layout-config\";\nimport { getRegionalBasemapByStyleUrl } from \"../regional-basemaps\";\nimport { DEFAULT_BASEMAP, type GeoLibreProject, type LayerGroup } from \"../types\";\nimport { scrubSecondaryPaneLayerVisibility } from \"./layer-removal\";\nimport type { AppState } from \"./types\";\n\n/** The fields undo/redo snapshots, restores and compares. */\nexport type HistoryState = Pick<\n AppState,\n | \"layers\"\n | \"layerGroups\"\n | \"basemapStyleUrl\"\n | \"basemapVisible\"\n | \"basemapOpacity\"\n | \"blankBackgroundColor\"\n | \"storymap\"\n | \"comments\"\n | \"widgets\"\n | \"legend\"\n>;\n\n/** The parts of the bound store the history functions use. */\ninterface HistoryStore {\n getState: () => AppState;\n setState: (partial: Partial<AppState>) => void;\n temporal: StoreApi<TemporalState<HistoryState>>;\n}\n\nlet historyStore: HistoryStore | null = null;\n\n/** Point the history functions at the app store; called once by `store.ts`. */\nexport function bindHistoryStore(store: HistoryStore): void {\n historyStore = store;\n}\n\nfunction boundStore(): HistoryStore {\n if (!historyStore) throw new Error(\"The app store has not been created yet.\");\n return historyStore;\n}\n\n/**\n * Compare two `layerGroups` arrays for undo-history purposes, ignoring the\n * `collapsed` flag so expand/collapse (a UI-panel preference) never records a\n * history entry. Every other field — order, name, visibility, opacity — is\n * still compared, so real edits are tracked.\n */\nfunction layerGroupsEqualForHistory(a: LayerGroup[], b: LayerGroup[]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n const x = a[i];\n const y = b[i];\n if (x === y) continue;\n if (x.id !== y.id || x.name !== y.name || x.visible !== y.visible || x.opacity !== y.opacity) {\n return false;\n }\n }\n return true;\n}\n\n/** Cancels the active history coalesce window (assigned by zundo's handleSet). */\nlet cancelHistoryCoalesce: () => void = () => {};\n\ninterface ProjectRestoreHistoryEntry {\n before: GeoLibreProject;\n beforePath: string | null;\n after: GeoLibreProject;\n afterPath: string | null;\n}\n\nlet projectRestoreUndo: ProjectRestoreHistoryEntry | null = null;\nlet projectRestoreRedo: ProjectRestoreHistoryEntry | null = null;\nlet applyingProjectRestoreHistory = false;\nconst projectRestoreHistoryListeners = new Set<() => void>();\n\nfunction notifyProjectRestoreHistory(): void {\n projectRestoreHistoryListeners.forEach((listener) => listener());\n}\n\nexport function subscribeProjectRestoreHistory(listener: () => void): () => void {\n projectRestoreHistoryListeners.add(listener);\n return () => projectRestoreHistoryListeners.delete(listener);\n}\n\nexport function canUndoProjectRestore(): boolean {\n return projectRestoreUndo !== null;\n}\n\nexport function canRedoProjectRestore(): boolean {\n return projectRestoreRedo !== null;\n}\n\n/**\n * Register a whole-project restore as one undoable operation. The regular\n * temporal history intentionally tracks only editing fields, while restoring a\n * history snapshot also changes project metadata, camera, plugins, and other\n * serialized state, so it needs a full canonical project pair.\n */\nexport function registerProjectRestoreHistory(\n before: GeoLibreProject,\n beforePath: string | null,\n after: GeoLibreProject,\n afterPath: string | null = null,\n): void {\n projectRestoreUndo = { before, beforePath, after, afterPath };\n projectRestoreRedo = null;\n notifyProjectRestoreHistory();\n}\n\n/**\n * Drop the oldest undo snapshots once their combined feature payload exceeds the\n * configured budget, bounding the memory held by history when a large vector\n * layer is edited repeatedly (issue #341). Called after a snapshot is appended.\n * Operates on the live temporal store directly; this never touches the main\n * store, so it does not itself record a history entry.\n */\nfunction pruneHistoryBySize(): void {\n const temporalStore = boundStore().temporal;\n const { pastStates } = temporalStore.getState();\n const trimmed = trimHistoryBySize(pastStates, getMaxHistoryFeatureCount());\n if (trimmed.length !== pastStates.length) {\n temporalStore.setState({ pastStates: trimmed });\n }\n}\n\n/**\n * Only these fields participate in undo/redo; everything else (selection,\n * ui flags, mapView/camera, pointerCoords, project metadata, isDirty, ...)\n * is excluded, so changing them never creates a history entry.\n */\nexport function partializeHistory(s: AppState): HistoryState {\n return {\n layers: s.layers,\n layerGroups: s.layerGroups,\n basemapStyleUrl: s.basemapStyleUrl,\n basemapVisible: s.basemapVisible,\n basemapOpacity: s.basemapOpacity,\n blankBackgroundColor: s.blankBackgroundColor,\n storymap: s.storymap,\n comments: s.comments,\n // Widgets and legend entries reference layers, and deleting a layer\n // scrubs them; tracking them makes undoing a delete (or a snapshot layer\n // restore) bring them back with the layer instead of leaving them lost or\n // dangling. Secondary panes and the Print Layout are deliberately not\n // tracked (see finishHistoryStep).\n widgets: s.widgets,\n legend: s.legend,\n };\n}\n\n/** zundo options for the app store's undo history. */\nexport function createHistoryOptions(): ZundoOptions<AppState, HistoryState> {\n return {\n partialize: partializeHistory,\n // Records a history entry only when the tracked slice really changed.\n // Basemap fields compare with ===; `layers` is compared element-by-element\n // (Object.is per element) via shallow. Every mutating action creates new\n // layer/group objects, so real changes differ; two distinct empty arrays\n // compare equal, so resetting them (e.g. newProject) records nothing.\n // `storymap` is compared by reference: every authoring action creates a\n // new object, so real edits differ while an unchanged null stays equal.\n // `layerGroups` is compared ignoring `collapsed`, which is a UI preference\n // excluded from undo (see toggleLayerGroupCollapsed).\n // `comments` and `widgets` are compared shallowly by reference, and\n // `legend` by reference: setLegend always stores a new object.\n equality: (a, b) =>\n a.basemapStyleUrl === b.basemapStyleUrl &&\n a.basemapVisible === b.basemapVisible &&\n a.basemapOpacity === b.basemapOpacity &&\n a.blankBackgroundColor === b.blankBackgroundColor &&\n a.storymap === b.storymap &&\n shallow(a.layers, b.layers) &&\n shallow(a.comments, b.comments) &&\n shallow(a.widgets, b.widgets) &&\n a.legend === b.legend &&\n layerGroupsEqualForHistory(a.layerGroups, b.layerGroups),\n limit: 100,\n // Group rapid bursts (slider drags) into one entry; window is 0 in tests.\n // Keep the debounced wrapper so clearHistory can reset an in-flight burst.\n handleSet: (baseHandleSet) => {\n const debounced = leadingDebounce(baseHandleSet, getHistoryCoalesceMs);\n cancelHistoryCoalesce = debounced.cancel;\n // Trim history back under the feature-payload budget so editing large\n // layers can't pin unbounded copies of their feature sets in memory\n // (issue #341). Only the leading-edge call actually pushes a snapshot;\n // burst-suppressed calls leave `pastStates` untouched, so skip the scan\n // unless a new snapshot was appended.\n return (...args: Parameters<typeof debounced>) => {\n const before = boundStore().temporal.getState().pastStates;\n debounced(...args);\n if (boundStore().temporal.getState().pastStates !== before) {\n if (!applyingProjectRestoreHistory && projectRestoreRedo) {\n projectRestoreRedo = null;\n notifyProjectRestoreHistory();\n }\n pruneHistoryBySize();\n }\n };\n },\n };\n}\n\n/**\n * After an undo/redo restores the tracked slice, mark the project dirty and\n * drop references the step left pointing at a layer that no longer exists:\n * the selection, per-pane layer visibility, and Print Layout data/atlas\n * blocks. None of those is tracked in history (selection is UI state; panes\n * carry their own camera and must stay in step with the untracked grid\n * layout; the Print Layout composer keeps a local copy it writes back), so\n * they are scrubbed the way deleting the layer would have scrubbed them.\n */\nfunction finishHistoryStep(previousBasemapStyleUrl: string): void {\n const s = boundStore().getState();\n const layerIds = new Set(s.layers.map((layer) => layer.id));\n const danglingPaneIds = new Set<string>();\n for (const pane of s.secondaryMapViews) {\n for (const id of Object.keys(pane.layerVisibility)) {\n if (!layerIds.has(id)) danglingPaneIds.add(id);\n }\n }\n const secondaryMapViews = scrubSecondaryPaneLayerVisibility(s.secondaryMapViews, danglingPaneIds);\n const printLayout = scrubPrintLayoutForLayers(s.printLayout, layerIds);\n const referencePatch = {\n ...(secondaryMapViews !== s.secondaryMapViews ? { secondaryMapViews } : {}),\n ...(printLayout !== s.printLayout ? { printLayout } : {}),\n };\n const selectionDangling =\n s.selectedLayerId !== null && !s.layers.some((layer) => layer.id === s.selectedLayerId);\n // The basemap is in the undo history but the ellipsoid preference is not, so a\n // step that restores a *different* basemap can leave the two out of sync (e.g.\n // undoing a switch to Mars would keep the Mars radius under an Earth basemap).\n // Re-derive the ellipsoid from the restored basemap's body — Earth for a\n // non-planetary basemap — but only when this step actually changed the\n // basemap. Steps that leave the basemap untouched must not touch the ellipsoid,\n // which the user can set independently of the basemap in Settings.\n const restoredEllipsoidId =\n getPlanetaryBasemapByStyleUrl(s.basemapStyleUrl)?.ellipsoidId ?? DEFAULT_ELLIPSOID_ID;\n const ellipsoidPatch =\n s.basemapStyleUrl !== previousBasemapStyleUrl &&\n s.preferences.map.ellipsoidId !== restoredEllipsoidId\n ? {\n preferences: {\n ...s.preferences,\n map: { ...s.preferences.map, ellipsoidId: restoredEllipsoidId },\n },\n }\n : {};\n // A step recorded before the deployment disabled regional basemaps must not\n // bring one back.\n const basemapPatch: { basemapStyleUrl?: string } =\n s.regionalBasemapPolicy === \"block\" && getRegionalBasemapByStyleUrl(s.basemapStyleUrl)\n ? { basemapStyleUrl: DEFAULT_BASEMAP }\n : {};\n boundStore().setState(\n selectionDangling\n ? {\n isDirty: true,\n selectedLayerId: null,\n selectedFeatureId: null,\n selectedFeatureIds: [],\n ...ellipsoidPatch,\n ...referencePatch,\n }\n : { isDirty: true, ...ellipsoidPatch, ...referencePatch },\n );\n // The basemap is tracked, so recording this correction as an edit would\n // clear the redo stack and leave the regional basemap one undo step back.\n if (basemapPatch.basemapStyleUrl) withoutHistory(() => boundStore().setState(basemapPatch));\n // The setState above must not leave a coalesce window open for the next edit.\n cancelHistoryCoalesce();\n}\n\n/**\n * Step the layer/basemap history back one entry and mark the project dirty.\n * zundo restores the partialized slice via the store's set; the resulting new\n * `layers`/basemap refs drive MapCanvas's existing effects, so the map\n * reconciles through MapController.syncLayers (never mutated directly here).\n */\nexport function undo(): void {\n const temporal = boundStore().temporal.getState();\n if (temporal.pastStates.length === 0 && projectRestoreUndo) {\n const entry = projectRestoreUndo;\n applyingProjectRestoreHistory = true;\n try {\n boundStore().getState().loadProject(entry.before, entry.beforePath, {\n rememberRecent: false,\n presenting: false,\n // A snapshot of the store, whose basemap already passed the gate.\n trustBasemap: true,\n });\n projectRestoreUndo = null;\n boundStore().setState({ isDirty: true });\n projectRestoreRedo = entry;\n notifyProjectRestoreHistory();\n } catch (error) {\n console.error(\"Could not undo the project snapshot restore.\", error);\n } finally {\n applyingProjectRestoreHistory = false;\n }\n return;\n }\n if (temporal.pastStates.length === 0) return; // nothing to undo; stay clean\n cancelHistoryCoalesce(); // break any in-flight burst so the next edit records\n const previousBasemapStyleUrl = boundStore().getState().basemapStyleUrl;\n temporal.undo();\n finishHistoryStep(previousBasemapStyleUrl);\n}\n\n/** Step the history forward one entry and mark the project dirty. */\nexport function redo(): void {\n const temporal = boundStore().temporal.getState();\n if (temporal.futureStates.length === 0 && projectRestoreRedo) {\n const entry = projectRestoreRedo;\n applyingProjectRestoreHistory = true;\n try {\n boundStore().getState().loadProject(entry.after, entry.afterPath, {\n rememberRecent: false,\n presenting: false,\n // A snapshot of the store, whose basemap already passed the gate.\n trustBasemap: true,\n });\n projectRestoreRedo = null;\n boundStore().setState({ isDirty: true });\n projectRestoreUndo = entry;\n notifyProjectRestoreHistory();\n } catch (error) {\n console.error(\"Could not redo the project snapshot restore.\", error);\n } finally {\n applyingProjectRestoreHistory = false;\n }\n return;\n }\n if (temporal.futureStates.length === 0) return; // nothing to redo; stay clean\n cancelHistoryCoalesce(); // break any in-flight burst so the next edit records\n const previousBasemapStyleUrl = boundStore().getState().basemapStyleUrl;\n temporal.redo();\n finishHistoryStep(previousBasemapStyleUrl);\n}\n\n/**\n * Apply a store change that is not the user's (data loading in behind a\n * project) without recording an undo step for it. The history stays as it\n * was, so undo neither takes the change back out nor loses earlier steps.\n *\n * @param change - The store updates to apply.\n */\nexport function withoutHistory(change: () => void): void {\n cancelHistoryCoalesce(); // a burst left open would record this change\n const temporal = boundStore().temporal.getState();\n temporal.pause();\n try {\n change();\n } finally {\n temporal.resume();\n }\n}\n\n/**\n * Rewrite the basemap recorded in every undo/redo snapshot from `from` to `to`.\n *\n * Used when the user accepts a regional basemap a loaded project asked for:\n * snapshots taken while the confirmation was open recorded the fallback, which\n * is not a basemap anyone chose, so undo must not bring it back.\n *\n * @param from - The basemap style URL to replace (the fallback).\n * @param to - The basemap style URL to record instead.\n */\nexport function replaceHistoryBasemap(from: string, to: string): void {\n const temporal = boundStore().temporal;\n const swap = (states: Partial<HistoryState>[]) =>\n states.map((s) => (s.basemapStyleUrl === from ? { ...s, basemapStyleUrl: to } : s));\n const { pastStates, futureStates } = temporal.getState();\n if (pastStates.length === 0 && futureStates.length === 0) return;\n temporal.setState({ pastStates: swap(pastStates), futureStates: swap(futureStates) });\n}\n\n/** Empty both the undo and redo stacks (e.g. on new/loaded project). */\nexport function clearHistory(): void {\n cancelHistoryCoalesce(); // reset any in-flight burst so the next edit records\n boundStore().temporal.getState().clear();\n if (!applyingProjectRestoreHistory) {\n projectRestoreUndo = null;\n projectRestoreRedo = null;\n notifyProjectRestoreHistory();\n }\n}\n","/**\n * The map workspace: the primary camera, the basemap, project preferences, the\n * multi-map grid and its secondary panes, and which engine draws the primary\n * map. The basemap fields are tracked by undo history; the camera is not.\n */\nimport { v4 as uuidv4 } from \"uuid\";\nimport { createDefaultMapView, normalizeBlankBackgroundColor } from \"../project\";\nimport { DEFAULT_ELLIPSOID_ID } from \"../ellipsoids\";\nimport type { PlanetaryBasemap } from \"../ellipsoids\";\nimport {\n gateRegionalBasemap,\n getRegionalBasemapByStyleUrl,\n type RegionalBasemapHold,\n type RegionalBasemapPolicy,\n} from \"../regional-basemaps\";\nimport {\n DEFAULT_BASEMAP,\n DEFAULT_MAP_GRID_LAYOUT,\n DEFAULT_PRIMARY_RENDERER,\n DEFAULT_PROJECT_PREFERENCES,\n MAX_MAP_GRID_DIM,\n type MapGridLayout,\n type MapRendererKind,\n type MapViewState,\n type ProjectPreferences,\n type SecondaryMapView,\n} from \"../types\";\nimport type { AppState, SliceCreator } from \"./types\";\nimport { replaceHistoryBasemap, withoutHistory } from \"./undo-history\";\n\n/** An explicit background choice replaces the active renderer's override. */\nfunction preferencesForBasemap(state: AppState, ellipsoidId = state.preferences.map.ellipsoidId) {\n // Each engine keeps its own persisted basemap override, so this names them.\n /* eslint-disable local/no-renderer-kind-checks -- per-engine persisted basemap settings */\n const clearMapbox =\n state.primaryRenderer === \"mapbox\" && state.preferences.map.mapboxStyleUrl !== undefined;\n // Any Cesium or ArcGIS pane, not only a primary one: split panes pick the\n // renderer independently, and a pinned globe imagery or Esri style would\n // otherwise ignore the picker.\n const clearCesium =\n state.preferences.map.cesiumBasemap !== \"project\" &&\n (state.primaryRenderer === \"cesium\" ||\n state.secondaryMapViews.some((pane) => pane.viewKind === \"cesium\"));\n const clearArcgis =\n state.preferences.map.arcgisBasemap !== undefined &&\n (state.primaryRenderer === \"arcgis\" ||\n state.secondaryMapViews.some((pane) => pane.viewKind === \"arcgis\"));\n /* eslint-enable local/no-renderer-kind-checks */\n if (\n !clearMapbox &&\n !clearCesium &&\n !clearArcgis &&\n ellipsoidId === state.preferences.map.ellipsoidId\n )\n return state.preferences;\n return {\n ...state.preferences,\n map: {\n ...state.preferences.map,\n ...(clearMapbox ? { mapboxStyleUrl: undefined } : {}),\n ...(clearCesium ? { cesiumBasemap: \"project\" as const } : {}),\n ...(clearArcgis ? { arcgisBasemap: undefined } : {}),\n ellipsoidId,\n },\n };\n}\n\n/**\n * The basemap fields for a basemap the user (or code acting for them) picked.\n *\n * A deployment that disabled regional basemaps refuses them from every caller\n * (plugins, scripts, the assistant), not only the hidden picker. An explicit\n * pick is the user's own choice, so `\"prompt\"` never applies, and it supersedes\n * a pending prompt: answering Load afterwards must not replace it.\n *\n * Plugins, scripts and the assistant go through here too. They are code the\n * user installed or ran to act for them, like the picker, so they are trusted\n * the same way; only a project file (data someone else may have authored) is\n * gated by `\"prompt\"`.\n *\n * @param state - The current store state.\n * @param url - The requested basemap style URL.\n * @returns The `basemapStyleUrl` to apply, the updated hold and choice counter.\n */\nfunction explicitBasemapChoice(\n state: AppState,\n url: string,\n): Pick<\n AppState,\n \"basemapStyleUrl\" | \"regionalBasemapHold\" | \"basemapChoiceGeneration\" | \"regionalBasemapAnswers\"\n> {\n const gated = gateRegionalBasemap(url, state.regionalBasemapPolicy, DEFAULT_BASEMAP, true);\n if (gated.held) {\n console.warn(`Regional basemaps are disabled by this deployment; ignoring \"${url}\".`);\n }\n // Picking a regional basemap answers the prompt for it too: a project (or an\n // embed host re-sending one) that names it later loads it without asking,\n // even when the user turned it down earlier in the session.\n const pickedRegional = !gated.held && getRegionalBasemapByStyleUrl(url) !== undefined;\n return {\n regionalBasemapAnswers: pickedRegional\n ? { ...state.regionalBasemapAnswers, [url]: true }\n : state.regionalBasemapAnswers,\n basemapStyleUrl: gated.styleUrl,\n regionalBasemapHold:\n state.regionalBasemapHold?.reason === \"prompt\" ? null : state.regionalBasemapHold,\n basemapChoiceGeneration: state.basemapChoiceGeneration + 1,\n };\n}\n\n/** True when two camera states have identical center/zoom/bearing/pitch. */\nfunction sameCamera(a: MapViewState, b: MapViewState): boolean {\n return (\n a.center[0] === b.center[0] &&\n a.center[1] === b.center[1] &&\n a.zoom === b.zoom &&\n a.bearing === b.bearing &&\n a.pitch === b.pitch\n );\n}\n\n/** Clamp a requested grid row/column count into the supported [1, MAX] range. */\nfunction clampGridDim(value: number): number {\n if (!Number.isFinite(value)) return 1;\n return Math.max(1, Math.min(MAX_MAP_GRID_DIM, Math.floor(value)));\n}\n\n/**\n * Pick a grid that holds at least `total` panes within the supported\n * `MAX_MAP_GRID_DIM x MAX_MAP_GRID_DIM` bound, minimizing empty cells first and\n * then preferring a column count close to (and, on ties, no smaller than)\n * `preferredCols` so removing a pane keeps the layout's orientation. Used when\n * collapsing the grid after a pane is removed.\n *\n * Bounding both dimensions matters: a prime `total` (e.g. 5 panes left after\n * removing one from a 2x3 grid) has no gap-free factor pair inside the bound, so\n * the only gap-free options (1x5 / 5x1) would exceed `MAX_MAP_GRID_DIM`. In that\n * case we accept the smallest bounded grid with one empty trailing cell (2x3)\n * rather than returning an out-of-range dimension.\n */\nfunction fitGrid(total: number, preferredCols: number): { rows: number; cols: number } {\n if (total <= 1) return { rows: 1, cols: 1 };\n let best: {\n rows: number;\n cols: number;\n empty: number;\n score: number;\n } | null = null;\n for (let rows = 1; rows <= MAX_MAP_GRID_DIM; rows++) {\n for (let cols = 1; cols <= MAX_MAP_GRID_DIM; cols++) {\n const capacity = rows * cols;\n if (capacity < total) continue;\n const empty = capacity - total;\n const score = Math.abs(cols - preferredCols);\n // Fewest empty cells wins; then the column count closest to\n // preferredCols; then, on a tie, the larger column count (favoring wider,\n // side-by-side layouts over tall ones).\n const better =\n best === null ||\n empty < best.empty ||\n (empty === best.empty &&\n (score < best.score || (score === best.score && cols > best.cols)));\n if (better) best = { rows, cols, empty, score };\n }\n }\n return best ? { rows: best.rows, cols: best.cols } : { rows: 1, cols: 1 };\n}\n\nexport interface MapViewSlice {\n mapView: MapViewState;\n basemapStyleUrl: string;\n basemapVisible: boolean;\n basemapOpacity: number;\n blankBackgroundColor: string | null;\n preferences: ProjectPreferences;\n /**\n * Multi-map grid layout (issue: split/grid view). A 1x1 grid is the normal\n * single-map workspace. `rows * cols` panes are shown; pane 0 is the primary\n * map driven by `mapView` / `basemap*`, panes 1.. are `secondaryMapViews`.\n */\n mapLayout: MapGridLayout;\n /**\n * Secondary map panes (everything past the primary pane). The store keeps\n * exactly `rows * cols - 1` entries in sync with `mapLayout`.\n */\n secondaryMapViews: SecondaryMapView[];\n /** User-entered label for the primary pane (shown only in multi-map mode). */\n primaryMapLabel: string;\n /**\n * Which engine draws the primary map area (issue #2217): the 2D MapLibre map\n * or the 3D Cesium globe. Both render the same store state, so switching\n * keeps the camera, basemap, layers, groups, visibility, and opacity — it\n * only changes what draws them. Independent of `mapLayout`: switching never\n * adds or removes panes.\n */\n primaryRenderer: MapRendererKind;\n\n setMapView: (view: Partial<MapViewState>, markDirty?: boolean) => void;\n /**\n * Resize the map grid. Clamps `rows`/`cols` into range and grows/shrinks\n * `secondaryMapViews` so it always holds `rows * cols - 1` panes; new panes\n * clone the primary map's current camera and basemap.\n */\n setMapGrid: (rows: number, cols: number) => void;\n /** Toggle synchronized camera across all panes. */\n setSyncView: (syncView: boolean) => void;\n /** Patch one secondary pane's camera by id (no-op if the id is unknown). */\n setSecondaryMapView: (id: string, view: Partial<MapViewState>, markDirty?: boolean) => void;\n /**\n * Override a layer's visibility in one secondary pane (no-op if the pane id is\n * unknown). The override forces the layer visible/hidden in that pane only,\n * independent of the primary map's visibility.\n */\n setSecondaryLayerVisibility: (id: string, layerId: string, visible: boolean) => void;\n /** Set the primary pane's custom label. */\n setPrimaryMapLabel: (label: string) => void;\n /**\n * Switch the primary map area between the 2D map and the 3D globe (no-op if\n * unchanged). Touches nothing else in the store, so the shared camera, layer,\n * and basemap state carries straight across the swap.\n */\n setPrimaryRenderer: (renderer: MapRendererKind) => void;\n /** Set one secondary pane's custom label (no-op if the id is unknown). */\n setSecondaryMapLabel: (id: string, label: string) => void;\n /**\n * Switch one secondary pane between the 2D map and the 3D globe (no-op if the\n * id is unknown or the kind is unchanged).\n */\n setSecondaryViewKind: (id: string, viewKind: NonNullable<SecondaryMapView[\"viewKind\"]>) => void;\n /** Remove one secondary pane and collapse the grid back toward 1x1. */\n removeSecondaryMapView: (id: string) => void;\n setBasemapStyleUrl: (url: string) => void;\n /**\n * Apply a planetary basemap and sync the project's ellipsoid to the body it\n * depicts, so measurements and the globe control use that body's radius. Used\n * by both the basemap picker and the Layers-panel planet switcher.\n */\n applyPlanetaryBasemap: (basemap: PlanetaryBasemap) => void;\n /**\n * Return to Earth: apply `styleUrl` (typically the Earth basemap that was\n * active before a planet was selected, e.g. Liberty) and reset the ellipsoid\n * to Earth. Used when a planet is deselected in the switcher.\n */\n restoreEarthBasemap: (styleUrl: string) => void;\n setBasemapVisible: (visible: boolean) => void;\n setBasemapOpacity: (opacity: number) => void;\n setBlankBackgroundColor: (color: string | null) => void;\n setPreferences: (preferences: ProjectPreferences) => void;\n /**\n * How a regional basemap (served from mainland China) that a loaded project\n * names is treated; see {@link RegionalBasemapPolicy}. Not project data: the\n * app sets it once at startup from the deployment policy.\n */\n regionalBasemapPolicy: RegionalBasemapPolicy;\n /**\n * The regional basemap the last project load set aside in favour of the\n * default basemap, or null. The app shell renders a confirmation (or, when\n * blocked, a notice) from it. Not project data and not in undo history.\n */\n regionalBasemapHold: RegionalBasemapHold | null;\n /**\n * Bumped whenever the basemap changes because the user (or code acting for\n * them) chose it: a basemap setter, or Load on the regional prompt. Not\n * bumped by project loads, collaboration snapshots or undo. Lets observers\n * tell a chosen basemap from one that arrived with data, e.g. so a regional\n * basemap is only remembered for the next launch when it was chosen.\n */\n basemapChoiceGeneration: number;\n /**\n * The user's answer to the regional-basemap prompt this session, per\n * regional style URL (true for Load). A later load naming the same basemap\n * reuses it instead of asking again, so a host that re-sends its project\n * (the Jupyter widget on every Python-side change) does not re-prompt.\n * Session-only: never persisted or saved into a project.\n */\n regionalBasemapAnswers: Record<string, boolean>;\n /** Set the {@link regionalBasemapPolicy}. Switching to `\"block\"` drops a pending prompt. */\n setRegionalBasemapPolicy: (policy: RegionalBasemapPolicy) => void;\n /**\n * Answer the held regional basemap. With `load` true and the hold still\n * belonging to the open project, the regional basemap is applied without\n * marking the project dirty or recording an undo step (it is what the file\n * already says). Otherwise the default basemap stays. Either way the hold is\n * cleared. The answer is remembered for the session unless `remember` is\n * false (the dialog was dismissed rather than answered).\n */\n resolveRegionalBasemapHold: (load: boolean, options?: { remember?: boolean }) => void;\n}\n\nexport const createMapViewSlice: SliceCreator<MapViewSlice> = (set, get) => ({\n mapView: createDefaultMapView(),\n basemapStyleUrl: DEFAULT_BASEMAP,\n basemapVisible: true,\n basemapOpacity: 1,\n blankBackgroundColor: null,\n preferences: DEFAULT_PROJECT_PREFERENCES,\n mapLayout: { ...DEFAULT_MAP_GRID_LAYOUT },\n secondaryMapViews: [],\n primaryMapLabel: \"\",\n primaryRenderer: DEFAULT_PRIMARY_RENDERER,\n regionalBasemapPolicy: \"allow\",\n regionalBasemapHold: null,\n basemapChoiceGeneration: 0,\n regionalBasemapAnswers: {},\n\n setMapView: (view, markDirty = false) =>\n set((s) => ({\n mapView: { ...s.mapView, ...view },\n isDirty: markDirty || s.isDirty,\n })),\n setMapGrid: (rows, cols) =>\n set((s) => {\n const clampedRows = clampGridDim(rows);\n const clampedCols = clampGridDim(cols);\n const desiredSecondary = clampedRows * clampedCols - 1;\n let secondaryMapViews = s.secondaryMapViews;\n if (desiredSecondary < secondaryMapViews.length) {\n secondaryMapViews = secondaryMapViews.slice(0, desiredSecondary);\n } else if (desiredSecondary > secondaryMapViews.length) {\n const additions: SecondaryMapView[] = [];\n for (let i = secondaryMapViews.length; i < desiredSecondary; i++) {\n // New panes start as a clone of the primary map's camera and (by\n // having no overrides) inherit its layer visibility, so the\n // comparison begins from the same view the user is looking at.\n additions.push({\n id: uuidv4(),\n view: { ...s.mapView },\n layerVisibility: {},\n });\n }\n secondaryMapViews = [...secondaryMapViews, ...additions];\n }\n return {\n mapLayout: {\n ...s.mapLayout,\n rows: clampedRows,\n cols: clampedCols,\n },\n secondaryMapViews,\n isDirty: true,\n };\n }),\n setSyncView: (syncView) =>\n set((s) => ({\n mapLayout: { ...s.mapLayout, syncView },\n isDirty: true,\n })),\n setSecondaryMapView: (id, view, markDirty = false) =>\n set((s) => {\n let changed = false;\n const secondaryMapViews = s.secondaryMapViews.map((pane) => {\n if (pane.id !== id) return pane;\n const merged = { ...pane.view, ...view };\n // Skip value-identical writes: a programmatic `applyView` (camera\n // sync, initial load) fires \"moveend\" too, so without this guard\n // each pane re-stores the same camera it was just given, churning a\n // new `secondaryMapViews` array and re-rendering every subscriber.\n if (sameCamera(pane.view, merged)) return pane;\n changed = true;\n return { ...pane, view: merged };\n });\n if (!changed) return s;\n return {\n secondaryMapViews,\n isDirty: markDirty || s.isDirty,\n };\n }),\n setSecondaryLayerVisibility: (id, layerId, visible) =>\n set((s) => {\n let changed = false;\n const secondaryMapViews = s.secondaryMapViews.map((pane) => {\n if (pane.id !== id) return pane;\n changed = true;\n return {\n ...pane,\n layerVisibility: { ...pane.layerVisibility, [layerId]: visible },\n };\n });\n if (!changed) return s;\n return { secondaryMapViews, isDirty: true };\n }),\n setPrimaryMapLabel: (label) => set({ primaryMapLabel: label, isDirty: true }),\n setPrimaryRenderer: (renderer) =>\n set((s) => (s.primaryRenderer === renderer ? s : { primaryRenderer: renderer, isDirty: true })),\n setSecondaryMapLabel: (id, label) =>\n set((s) => {\n let changed = false;\n const secondaryMapViews = s.secondaryMapViews.map((pane) => {\n if (pane.id !== id) return pane;\n changed = true;\n return { ...pane, label };\n });\n if (!changed) return s;\n return { secondaryMapViews, isDirty: true };\n }),\n setSecondaryViewKind: (id, viewKind) =>\n set((s) => {\n let changed = false;\n const secondaryMapViews = s.secondaryMapViews.map((pane) => {\n if (pane.id !== id) return pane;\n // Treat an absent viewKind as \"maplibre\" so switching a legacy pane\n // to maplibre is a no-op rather than a churned array.\n if ((pane.viewKind ?? \"maplibre\") === viewKind) return pane;\n changed = true;\n return { ...pane, viewKind };\n });\n if (!changed) return s;\n return { secondaryMapViews, isDirty: true };\n }),\n removeSecondaryMapView: (id) =>\n set((s) => {\n const secondaryMapViews = s.secondaryMapViews.filter((pane) => pane.id !== id);\n if (secondaryMapViews.length === s.secondaryMapViews.length) {\n return s;\n }\n // Collapse to a gap-free grid that fits the remaining panes, keeping\n // the layout's orientation as close as possible to the current one.\n const total = secondaryMapViews.length + 1;\n const { rows, cols } = fitGrid(total, s.mapLayout.cols);\n return {\n secondaryMapViews,\n mapLayout: { ...s.mapLayout, rows, cols },\n isDirty: true,\n };\n }),\n setBasemapStyleUrl: (url) =>\n set((state) => ({\n ...explicitBasemapChoice(state, url),\n preferences: preferencesForBasemap(state),\n isDirty: true,\n })),\n applyPlanetaryBasemap: (basemap) =>\n set((state) => ({\n ...explicitBasemapChoice(state, basemap.styleUrl),\n preferences: preferencesForBasemap(state, basemap.ellipsoidId),\n isDirty: true,\n })),\n restoreEarthBasemap: (styleUrl) =>\n set((state) => ({\n ...explicitBasemapChoice(state, styleUrl),\n preferences: preferencesForBasemap(state, DEFAULT_ELLIPSOID_ID),\n isDirty: true,\n })),\n setBasemapVisible: (visible) => set({ basemapVisible: visible, isDirty: true }),\n setBasemapOpacity: (opacity) => set({ basemapOpacity: opacity, isDirty: true }),\n setBlankBackgroundColor: (color) =>\n set({ blankBackgroundColor: normalizeBlankBackgroundColor(color), isDirty: true }),\n setPreferences: (preferences) => set({ preferences, isDirty: true }),\n setRegionalBasemapPolicy: (policy) => {\n const state = get();\n const hold =\n policy === \"block\" && state.regionalBasemapHold?.reason === \"prompt\"\n ? null\n : state.regionalBasemapHold;\n // Blocking while a regional basemap is on screen takes it down too, so the\n // map stops requesting its tiles. Not the user's edit: no undo step.\n if (policy === \"block\" && getRegionalBasemapByStyleUrl(state.basemapStyleUrl)) {\n withoutHistory(() =>\n set({\n regionalBasemapPolicy: policy,\n regionalBasemapHold: hold,\n basemapStyleUrl: DEFAULT_BASEMAP,\n }),\n );\n return;\n }\n set({ regionalBasemapPolicy: policy, regionalBasemapHold: hold });\n },\n resolveRegionalBasemapHold: (load, options = {}) => {\n const state = get();\n const hold = state.regionalBasemapHold;\n if (!hold) return;\n // Every project load replaces or clears the hold, so the hold read here is\n // always the one the latest load set. Not comparing `projectGeneration`\n // here: a collaboration snapshot bumps it without a load, and that must not\n // drop a pending answer.\n const answered = hold.reason === \"prompt\" && state.regionalBasemapPolicy !== \"block\";\n // Remember a real answer for the session, so the next load of the same\n // basemap does not ask again.\n const regionalBasemapAnswers =\n answered && options.remember !== false\n ? { ...state.regionalBasemapAnswers, [hold.styleUrl]: load }\n : state.regionalBasemapAnswers;\n const applies =\n load &&\n answered &&\n // Only replace the fallback the load put in. An explicit pick already\n // clears the hold; this also covers a direct setState.\n state.basemapStyleUrl === DEFAULT_BASEMAP;\n if (!applies) {\n set({ regionalBasemapHold: null, regionalBasemapAnswers });\n return;\n }\n withoutHistory(() =>\n set({\n basemapStyleUrl: hold.styleUrl,\n regionalBasemapHold: null,\n regionalBasemapAnswers,\n basemapChoiceGeneration: state.basemapChoiceGeneration + 1,\n }),\n );\n // Edits made while the prompt was open recorded the fallback in their undo\n // snapshots. No explicit basemap pick happened since the load (that would\n // have cleared the hold), so every such snapshot gets the accepted basemap,\n // and undoing one of those edits does not take it away again.\n replaceHistoryBasemap(DEFAULT_BASEMAP, hold.styleUrl);\n },\n});\n","/**\n * Saved processing models (issue #344) and the Processing History log\n * (issue #1292). Both are saved with the project.\n */\nimport { MAX_PROCESSING_HISTORY, type ProcessingModel, type ProcessingRun } from \"../types\";\nimport type { SliceCreator } from \"./types\";\n\nexport interface ProcessingSlice {\n /** Saved processing pipelines (batch/model chaining; issue #344). */\n models: ProcessingModel[];\n /** Recorded processing tool runs, oldest first (Processing History; #1292). */\n processingHistory: ProcessingRun[];\n /**\n * Object-Based Analysis workbench state saved with the project (#3053), an\n * app-owned JSON object; null until the workbench is used.\n */\n obiaWorkbench: Record<string, unknown> | null;\n\n /** Insert a new model or replace an existing one matching by `id`. */\n saveModel: (model: ProcessingModel) => void;\n /** Remove a saved model by id. */\n deleteModel: (id: string) => void;\n\n /** Append a processing run to the history (bounded, de-duped by id; #1292). */\n addProcessingRun: (run: ProcessingRun) => void;\n /** Patch a recorded run by id (no-op if absent), e.g. to add output layers. */\n updateProcessingRun: (id: string, patch: Partial<Omit<ProcessingRun, \"id\">>) => void;\n /** Drop all recorded processing runs. */\n clearProcessingHistory: () => void;\n /**\n * Replace the saved workbench state. `markDirty` is false when the app is\n * only mirroring state it just restored, so opening a project stays clean.\n */\n setObiaWorkbench: (value: Record<string, unknown> | null, markDirty?: boolean) => void;\n}\n\nexport const createProcessingSlice: SliceCreator<ProcessingSlice> = (set) => ({\n models: [],\n processingHistory: [],\n obiaWorkbench: null,\n\n saveModel: (model) =>\n set((s) => {\n const exists = s.models.some((m) => m.id === model.id);\n const models = exists\n ? s.models.map((m) => (m.id === model.id ? model : m))\n : [...s.models, model];\n return { models, isDirty: true };\n }),\n deleteModel: (id) =>\n set((s) => ({\n models: s.models.filter((m) => m.id !== id),\n isDirty: true,\n })),\n\n addProcessingRun: (run) =>\n set((s) => {\n // Ignore a duplicate id so updateProcessingRun stays unambiguous.\n if (s.processingHistory.some((r) => r.id === run.id)) return s;\n const processingHistory = [...s.processingHistory, run].slice(-MAX_PROCESSING_HISTORY);\n return { processingHistory, isDirty: true };\n }),\n updateProcessingRun: (id, patch) =>\n set((s) => {\n if (!s.processingHistory.some((r) => r.id === id)) return s;\n return {\n processingHistory: s.processingHistory.map((r) =>\n r.id === id ? { ...r, ...patch, id: r.id } : r,\n ),\n isDirty: true,\n };\n }),\n clearProcessingHistory: () =>\n set((s) => (s.processingHistory.length === 0 ? s : { processingHistory: [], isDirty: true })),\n setObiaWorkbench: (obiaWorkbench, markDirty = true) =>\n set(markDirty ? { obiaWorkbench, isDirty: true } : { obiaWorkbench }),\n});\n","/**\n * Project content that sits beside the layers: review comments (issue #1518),\n * Dashboard widgets (issue #401) and the story map. All of it is saved with the\n * project; `comments` and `storymap` are also tracked by undo history.\n */\nimport {\n DEFAULT_DASHBOARD_COLUMNS,\n DEFAULT_STORY_MAP,\n MAX_DASHBOARD_COLUMNS,\n MIN_DASHBOARD_COLUMNS,\n type CommentReply,\n type DashboardWidget,\n type ProjectBookmark,\n type ProjectBookmarkGroup,\n type ProjectComment,\n type ProjectInteraction,\n type StoryChapter,\n type StoryMap,\n} from \"../types\";\nimport type { SliceCreator } from \"./types\";\n\nexport interface ProjectContentSlice {\n storymap: StoryMap | null;\n /** Saved Dashboard panel chart widgets (issue #401). */\n widgets: DashboardWidget[];\n /** Number of columns in the Dashboard widget grid. */\n dashboardColumns: number;\n /** Saved map views from the Bookmarks panel, in panel order (#2869). */\n bookmarks: ProjectBookmark[];\n /** Folders the bookmarks are organized into. */\n bookmarkGroups: ProjectBookmarkGroup[];\n /** Anchored review comments on map points or features (issue #1518). */\n comments: ProjectComment[];\n /**\n * The loaded project's startup `interaction` block (issue #2688). Applied\n * once on load and written back unchanged on save; live Identify and control\n * changes do not edit it.\n */\n projectInteraction: ProjectInteraction | null;\n\n /** Append a new dashboard widget. */\n addWidget: (widget: DashboardWidget) => void;\n /** Patch an existing dashboard widget by id (no-op if absent). Merges, so an\n * omitted key keeps its current value; use replaceWidget to clear one. */\n updateWidget: (id: string, patch: Partial<Omit<DashboardWidget, \"id\">>) => void;\n /** Swap an existing dashboard widget for a complete new record, keeping its\n * id and position (no-op if absent). Unlike updateWidget this does not merge,\n * so fields the caller omits are cleared — what the widget editor needs to\n * persist an emptied title, color, prefix, or suffix. */\n replaceWidget: (id: string, widget: Omit<DashboardWidget, \"id\">) => void;\n /** Remove a dashboard widget by id. */\n removeWidget: (id: string) => void;\n /** Move a widget to a new index, clamped into range, preserving the rest. */\n moveWidget: (id: string, toIndex: number) => void;\n /** Set the Dashboard widget-grid column count (clamped into range). */\n setDashboardColumns: (columns: number) => void;\n\n setStorymap: (storymap: StoryMap | null) => void;\n updateStorymapSettings: (patch: Partial<Omit<StoryMap, \"chapters\">>) => void;\n addStoryChapter: (chapter: StoryChapter, atIndex?: number) => void;\n updateStoryChapter: (id: string, patch: Partial<StoryChapter>) => void;\n removeStoryChapter: (id: string) => void;\n moveStoryChapter: (id: string, targetIndex: number) => void;\n addComment: (comment: ProjectComment) => void;\n replyToComment: (commentId: string, reply: CommentReply) => void;\n toggleResolveComment: (commentId: string, resolved?: boolean) => void;\n deleteComment: (commentId: string) => void;\n setComments: (comments: ProjectComment[]) => void;\n /**\n * Replace every bookmark and folder (the Bookmarks panel writes its whole\n * state back after each edit). Marks the project dirty.\n */\n setBookmarks: (bookmarks: ProjectBookmark[], bookmarkGroups: ProjectBookmarkGroup[]) => void;\n}\n\nexport const createProjectContentSlice: SliceCreator<ProjectContentSlice> = (set) => ({\n storymap: null,\n widgets: [],\n dashboardColumns: DEFAULT_DASHBOARD_COLUMNS,\n bookmarks: [],\n bookmarkGroups: [],\n comments: [],\n projectInteraction: null,\n\n setBookmarks: (bookmarks, bookmarkGroups) => set({ bookmarks, bookmarkGroups, isDirty: true }),\n\n addComment: (comment) =>\n set((s) => {\n // Ignore a duplicate id: the WebSocket relay echoes our own\n // comment-mutation back to the sender, and a reconnect can replay\n // recent history, so de-dupe defensively (mirrors addCollaborationChat).\n if (s.comments.some((c) => c.id === comment.id)) return s;\n return { comments: [...s.comments, comment], isDirty: true };\n }),\n replyToComment: (commentId, reply) =>\n set((s) => {\n let appended = false;\n const nextComments = s.comments.map((c) => {\n if (c.id !== commentId) return c;\n if (c.replies.some((r) => r.id === reply.id)) return c;\n appended = true;\n return { ...c, replies: [...c.replies, reply] };\n });\n if (!appended) return s;\n return { comments: nextComments, isDirty: true };\n }),\n toggleResolveComment: (commentId, resolved) =>\n set((s) => ({\n comments: s.comments.map((c) =>\n c.id === commentId\n ? { ...c, resolved: resolved !== undefined ? resolved : !c.resolved }\n : c,\n ),\n isDirty: true,\n })),\n deleteComment: (commentId) =>\n set((s) => ({\n comments: s.comments.filter((c) => c.id !== commentId),\n isDirty: true,\n })),\n setComments: (comments) => set({ comments, isDirty: true }),\n\n addWidget: (widget) =>\n set((s) => {\n // Ignore a duplicate id so updateWidget/removeWidget stay unambiguous\n // and a later entry isn't silently dropped when normalized on save.\n if (s.widgets.some((w) => w.id === widget.id)) return s;\n return { widgets: [...s.widgets, widget], isDirty: true };\n }),\n updateWidget: (id, patch) =>\n set((s) => {\n const exists = s.widgets.some((w) => w.id === id);\n if (!exists) return s;\n return {\n widgets: s.widgets.map((w) => (w.id === id ? { ...w, ...patch, id: w.id } : w)),\n isDirty: true,\n };\n }),\n replaceWidget: (id, widget) =>\n set((s) => {\n if (!s.widgets.some((w) => w.id === id)) return s;\n return {\n widgets: s.widgets.map((w) => (w.id === id ? { ...widget, id } : w)),\n isDirty: true,\n };\n }),\n removeWidget: (id) =>\n set((s) => ({\n widgets: s.widgets.filter((w) => w.id !== id),\n isDirty: true,\n })),\n moveWidget: (id, toIndex) =>\n set((s) => {\n const from = s.widgets.findIndex((w) => w.id === id);\n if (from < 0) return s;\n const target = Math.max(0, Math.min(s.widgets.length - 1, toIndex));\n if (target === from) return s;\n const widgets = [...s.widgets];\n const [moved] = widgets.splice(from, 1);\n widgets.splice(target, 0, moved);\n return { widgets, isDirty: true };\n }),\n setDashboardColumns: (columns) =>\n set((s) => {\n // Guard non-finite input so the clamp can't yield NaN columns.\n if (!Number.isFinite(columns)) return s;\n return {\n dashboardColumns: Math.max(\n MIN_DASHBOARD_COLUMNS,\n Math.min(MAX_DASHBOARD_COLUMNS, Math.trunc(columns)),\n ),\n isDirty: true,\n };\n }),\n\n setStorymap: (storymap) => set({ storymap, isDirty: true }),\n updateStorymapSettings: (patch) =>\n set((s) => ({\n storymap: { ...(s.storymap ?? DEFAULT_STORY_MAP), ...patch },\n isDirty: true,\n })),\n addStoryChapter: (chapter, atIndex) =>\n set((s) => {\n const base = s.storymap ?? DEFAULT_STORY_MAP;\n const chapters = [...base.chapters];\n const index =\n atIndex === undefined ? chapters.length : Math.min(Math.max(atIndex, 0), chapters.length);\n chapters.splice(index, 0, chapter);\n return { storymap: { ...base, chapters }, isDirty: true };\n }),\n updateStoryChapter: (id, patch) =>\n set((s) => {\n if (!s.storymap) return s;\n return {\n storymap: {\n ...s.storymap,\n chapters: s.storymap.chapters.map((chapter) =>\n chapter.id === id ? { ...chapter, ...patch } : chapter,\n ),\n },\n isDirty: true,\n };\n }),\n removeStoryChapter: (id) =>\n set((s) => {\n if (!s.storymap) return s;\n return {\n storymap: {\n ...s.storymap,\n chapters: s.storymap.chapters.filter((chapter) => chapter.id !== id),\n },\n isDirty: true,\n };\n }),\n moveStoryChapter: (id, targetIndex) =>\n set((s) => {\n if (!s.storymap) return s;\n const current = s.storymap.chapters.findIndex((chapter) => chapter.id === id);\n if (current < 0) return s;\n const chapters = [...s.storymap.chapters];\n const [chapter] = chapters.splice(current, 1);\n if (!chapter) return s;\n const next = Math.min(Math.max(targetIndex, 0), chapters.length);\n chapters.splice(next, 0, chapter);\n if (chapters.every((item, i) => item.id === s.storymap?.chapters[i]?.id)) {\n return s;\n }\n return { storymap: { ...s.storymap, chapters }, isDirty: true };\n }),\n});\n","/**\n * Project identity and lifecycle: name, path, dirty flag, recent projects, and\n * the project-level settings that have no richer home (plugins, legend, print\n * layout, metadata). `newProject` and `loadProject` replace the whole document\n * (every slice's project fields) and clear undo history.\n */\nimport {\n applyProjectToStore,\n type CreateProjectOptions,\n createEmptyProject,\n DEFAULT_PROJECT_NAME,\n} from \"../project\";\nimport {\n createDefaultPrintLayout,\n printLayoutConfigsEqual,\n type PrintLayoutConfig,\n} from \"../print-layout-config\";\nimport { reapplyLayerJoins } from \"../joins\";\nimport { gateRegionalBasemap } from \"../regional-basemaps\";\nimport {\n DEFAULT_BASEMAP,\n DEFAULT_LEGEND_CONFIG,\n type GeoLibreLayer,\n type GeoLibreProject,\n type LayerGroup,\n type LegendConfig,\n type ProjectPluginState,\n type RecentProjectEntry,\n} from \"../types\";\nimport { resolveIdentifyTarget } from \"./session-slice\";\nimport type { SliceCreator } from \"./types\";\nimport { clearHistory } from \"./undo-history\";\n\nconst MAX_RECENT_PROJECTS = 10;\n\n/** Derive a human-friendly display name from a file path or URL. */\nexport function projectPathLabel(path: string): string {\n return path.split(/[/\\\\]/).pop() || path;\n}\n\nfunction normalizeRecentProjects(projects: RecentProjectEntry[]): RecentProjectEntry[] {\n const seen = new Set<string>();\n const normalized: RecentProjectEntry[] = [];\n\n for (const project of projects) {\n const path = project.path.trim();\n if (!path || seen.has(path)) continue;\n\n const name = project.name.trim() || projectPathLabel(path);\n normalized.push({\n path,\n name,\n openedAt: project.openedAt || new Date().toISOString(),\n });\n seen.add(path);\n }\n\n return normalized.slice(0, MAX_RECENT_PROJECTS);\n}\n\nexport interface ProjectSlice {\n projectName: string;\n projectPath: string | null;\n projectGeneration: number;\n projectLoaded: boolean;\n isDirty: boolean;\n projectPlugins: ProjectPluginState | null;\n legend: LegendConfig;\n /** Print Layout composer settings for the open project (discussion #1992). */\n printLayout: PrintLayoutConfig;\n metadata: Record<string, unknown>;\n recentProjects: RecentProjectEntry[];\n\n setLegend: (legend: LegendConfig) => void;\n /**\n * Replace the Print Layout composer settings. A config equal to the current\n * one is ignored, so re-opening the composer (or a project load seeding the\n * dialog) never marks the project dirty.\n */\n setPrintLayout: (printLayout: PrintLayoutConfig) => void;\n setProjectPlugins: (projectPlugins: ProjectPluginState | null, shouldMarkDirty?: boolean) => void;\n /**\n * Start a new, untitled project. `layers` and `layerGroups` seed it with\n * layers (the Startup setting's default layers).\n */\n newProject: (\n options?: CreateProjectOptions & {\n name?: string;\n layers?: GeoLibreLayer[];\n layerGroups?: LayerGroup[];\n },\n ) => void;\n /**\n * Replace the open project with `project`.\n *\n * A regional basemap the project names is gated by `regionalBasemapPolicy`:\n * under `\"prompt\"` the default basemap is applied and the regional one is\n * held in `regionalBasemapHold` for the user to confirm, under `\"block\"` it is\n * always replaced. Pass `trustBasemap: true` for a load whose basemap the user\n * already chose in this session (an undo step, a duplicate, a template, a\n * host-driven embed load), which skips the prompt but not the block.\n */\n loadProject: (\n project: GeoLibreProject,\n path?: string | null,\n options?: { rememberRecent?: boolean; presenting?: boolean; trustBasemap?: boolean },\n ) => void;\n setProjectPath: (path: string | null) => void;\n setProjectName: (name: string) => void;\n setRecentProjects: (projects: RecentProjectEntry[]) => void;\n rememberRecentProject: (entry: RecentProjectEntry) => void;\n forgetRecentProject: (path: string) => void;\n clearRecentProjects: () => void;\n markSaved: () => void;\n}\n\nexport const createProjectSlice: SliceCreator<ProjectSlice> = (set, get) => ({\n projectName: DEFAULT_PROJECT_NAME,\n projectPath: null,\n projectGeneration: 0,\n projectLoaded: false,\n isDirty: false,\n projectPlugins: null,\n legend: { ...DEFAULT_LEGEND_CONFIG },\n printLayout: createDefaultPrintLayout(),\n metadata: {},\n recentProjects: [],\n\n setLegend: (legend) => set({ legend, isDirty: true }),\n\n setPrintLayout: (printLayout) =>\n set((s) =>\n printLayoutConfigsEqual(s.printLayout, printLayout) ? s : { printLayout, isDirty: true },\n ),\n // When shouldMarkDirty is false the existing dirty flag is preserved rather\n // than set; it cannot clear the flag (only markSaved() does that).\n setProjectPlugins: (projectPlugins, shouldMarkDirty = true) =>\n set((s) => ({\n projectPlugins,\n isDirty: shouldMarkDirty || s.isDirty,\n })),\n\n setProjectPath: (path) => set({ projectPath: path }),\n setProjectName: (name) => set({ projectName: name, isDirty: true }),\n setRecentProjects: (projects) => set({ recentProjects: normalizeRecentProjects(projects) }),\n rememberRecentProject: (entry) =>\n set((s) => ({\n recentProjects: normalizeRecentProjects([entry, ...s.recentProjects]),\n })),\n forgetRecentProject: (path) => {\n // Compare with separators normalized so a backslash/forward-slash mismatch\n // on Windows does not leave a stale entry behind.\n const normalized = path.replace(/\\\\/g, \"/\");\n set((s) => ({\n recentProjects: s.recentProjects.filter(\n (project) => project.path.replace(/\\\\/g, \"/\") !== normalized,\n ),\n }));\n },\n clearRecentProjects: () => set({ recentProjects: [] }),\n markSaved: () => set({ isDirty: false }),\n\n newProject: (options = {}) => {\n const project = {\n ...createEmptyProject(options.name, options),\n ...(options.layers ? { layers: options.layers } : {}),\n ...(options.layerGroups ? { layerGroups: options.layerGroups } : {}),\n };\n const applied = applyProjectToStore(project);\n // A new project's basemap is the user's own pick in the New Project dialog,\n // so only a deployment block replaces it.\n applied.basemapStyleUrl = gateRegionalBasemap(\n applied.basemapStyleUrl,\n get().regionalBasemapPolicy,\n DEFAULT_BASEMAP,\n true,\n ).styleUrl;\n set((s) => ({\n ...applied,\n regionalBasemapHold: null,\n // The New Project dialog's basemap is the user's pick.\n basemapChoiceGeneration: s.basemapChoiceGeneration + 1,\n hoverTooltipsEnabled: true,\n projectPath: null,\n projectGeneration: s.projectGeneration + 1,\n projectLoaded: false,\n isDirty: false,\n selectedLayerId: null,\n selectedFeatureId: null,\n selectedFeatureIds: [],\n identifyLayerId: null,\n identifyLayerIds: null,\n featureSelectionActive: false,\n // The copied style names a layer from the previous project, so a\n // paste in the new one would apply an orphaned entry.\n copiedLayerStyle: null,\n pointerCoords: null,\n pointerElevation: null,\n cameraAltitude: null,\n attributeFilter: \"\",\n // Don't carry an active story presentation into a different project.\n ui: {\n ...s.ui,\n storymapPresenting: false,\n storymapReturnToEditor: false,\n storymapLayerOpacity: {},\n storymapPanelOpen: false,\n storymapComposingId: null,\n // An open selection dialog (and its preselected layer id) belongs\n // to the previous project's layers.\n selectByExpressionOpen: false,\n selectByExpressionLayerId: null,\n selectByLocationOpen: false,\n selectByLocationLayerId: null,\n loadEditorFeaturesOpen: false,\n loadEditorFeaturesLayerId: null,\n // A pending assistant-requested Model Builder load names a model in\n // the previous project's `savedModels`.\n modelBuilderRequestedModelId: null,\n },\n }));\n clearHistory();\n },\n\n loadProject: (project, path = null, options = {}) => {\n const applied = applyProjectToStore(project);\n // Re-resolve persistent attribute joins against the loaded layer set,\n // so joined columns reflect the join tables as saved (and a stale\n // saved copy of the joined output self-heals).\n applied.layers = reapplyLayerJoins(applied.layers);\n // A project file that names a regional basemap would otherwise load tiles\n // from servers in mainland China without the user choosing that, so the\n // default basemap renders until the user confirms (or for good, when the\n // deployment disabled regional basemaps).\n const requestedBasemap = applied.basemapStyleUrl;\n // An answer the user already gave this session for the same basemap\n // stands: Load trusts it, Use default basemap keeps the fallback quietly.\n const priorAnswer = get().regionalBasemapAnswers[requestedBasemap];\n const gated = gateRegionalBasemap(\n requestedBasemap,\n get().regionalBasemapPolicy,\n DEFAULT_BASEMAP,\n options.trustBasemap === true || priorAnswer === true,\n );\n if (gated.held === \"prompt\" && priorAnswer === false) delete gated.held;\n applied.basemapStyleUrl = gated.styleUrl;\n // A project that ships a story map opens straight into the presentation\n // so the reader sees the story, not the editor. Projects without a story\n // (or with an empty one) open normally. Callers that open a project for\n // authoring rather than viewing can pass `presenting: false` to override.\n const presentStory = options.presenting ?? (applied.storymap?.chapters.length ?? 0) > 0;\n const selectedLayerId =\n project.selectedLayerId === null\n ? null\n : typeof project.selectedLayerId === \"string\" &&\n applied.layers.some((layer) => layer.id === project.selectedLayerId)\n ? project.selectedLayerId\n : (applied.layers[0]?.id ?? null);\n set((s) => ({\n ...applied,\n regionalBasemapHold: gated.held\n ? {\n styleUrl: requestedBasemap,\n reason: gated.held,\n }\n : null,\n hoverTooltipsEnabled: true,\n projectPath: path,\n projectGeneration: s.projectGeneration + 1,\n projectLoaded: true,\n isDirty: false,\n selectedLayerId,\n selectedFeatureId: null,\n selectedFeatureIds: [],\n featureSelectionActive: false,\n // A project that saved an Identify target (issue #2688) opens with it\n // armed; any other load disarms Identify, since the previous target\n // named a layer of the project being replaced.\n ...resolveIdentifyTarget(\n applied.projectInteraction?.identify,\n applied.layers.map((layer) => layer.id),\n ),\n // The copied style names a layer from the previous project, so a\n // paste in the loaded one would apply an orphaned entry.\n copiedLayerStyle: null,\n // Ephemeral readouts describe the previous project's map. The\n // elevation and altitude especially: a project that switches to a\n // planetary body would otherwise keep showing Earth-scaled values\n // until the next hover or camera move.\n pointerCoords: null,\n pointerElevation: null,\n cameraAltitude: null,\n // Present a bundled story on load; otherwise drop any presentation\n // carried over from the previous project.\n ui: {\n ...s.ui,\n storymapPresenting: presentStory,\n // A bundled story auto-presents for viewing, so exiting it should\n // not pop open the editor (#918).\n storymapReturnToEditor: false,\n storymapLayerOpacity: {},\n storymapPanelOpen: false,\n storymapComposingId: null,\n // An open selection dialog (and its preselected layer id) belongs\n // to the previous project's layers.\n selectByExpressionOpen: false,\n selectByExpressionLayerId: null,\n selectByLocationOpen: false,\n selectByLocationLayerId: null,\n loadEditorFeaturesOpen: false,\n loadEditorFeaturesLayerId: null,\n // A pending assistant-requested Model Builder load names a model in\n // the previous project's `savedModels`.\n modelBuilderRequestedModelId: null,\n },\n }));\n clearHistory();\n if (path && options.rememberRecent !== false) {\n get().rememberRecentProject({\n path,\n name: project.name,\n openedAt: new Date().toISOString(),\n });\n }\n },\n});\n","/**\n * UI-only state: which dialogs and panels are open, and the one-shot requests\n * they consume. Lives in the `ui` sub-state, which the undo history never\n * tracks (see `undo-history.ts`), so opening or closing a dialog never records\n * an undo step and undo/redo never toggles one.\n */\nimport type { ProcessingRerunRequest } from \"../types\";\nimport type { SliceCreator } from \"./types\";\n\nexport type ConversionToolKind =\n | \"vector-to-vector\"\n | \"vector-to-geoparquet\"\n | \"vector-to-flatgeobuf\"\n | \"vector-to-shapefile\"\n | \"vector-to-geopackage\"\n | \"csv-to-geoparquet\"\n | \"vector-to-pmtiles\"\n | \"raster-to-pmtiles\"\n | \"raster-to-cog\";\n\n/**\n * Identifiers of the vector processing tools. Kept in sync by hand with the\n * `id` fields of `VECTOR_TOOLS` in `@geolibre/processing` (`vector-tools.ts`);\n * deriving the type there would create a core -> processing circular import.\n */\nexport type VectorToolKind =\n | \"buffer\"\n | \"centroids\"\n | \"convex-hull\"\n | \"dissolve\"\n | \"bounding-box\"\n | \"simplify\"\n | \"clip\"\n | \"intersection\"\n | \"difference\"\n | \"union\"\n | \"spatial-join\"\n | \"attribute-join\"\n | \"select-by-value\"\n | \"select-by-location\"\n | \"random-extract\"\n | \"reproject\"\n | \"explode\"\n | \"aggregate\"\n | \"smooth\"\n | \"extract-vertices\"\n | \"points-along-geometry\"\n | \"grid\"\n | \"voronoi\"\n | \"cell-sectors\"\n | \"dggs-grid\"\n | \"dggs-bin\"\n | \"dggs-compact\"\n | \"trajectory-speed\"\n | \"detect-stops\"\n | \"space-time-proximity\"\n | \"decode-polyline\"\n | \"encode-polyline\"\n | \"merge-layers\"\n | \"check-validity\"\n | \"fix-geometries\"\n | \"check-topology-rules\"\n | \"fix-topology\";\n\n/** Identifiers of the network-analysis tools (`NETWORK_TOOLS` ids). */\nexport type NetworkToolKind = \"isochrone\" | \"od-matrix\" | \"sequential-route\";\n\n/** Identifiers of the spatial-statistics tools (`STATISTICS_TOOLS` ids). */\nexport type StatisticsToolKind =\n | \"global-morans-i\"\n | \"local-morans-i\"\n | \"getis-ord-gi\"\n | \"average-nearest-neighbor\"\n | \"kernel-density\"\n | \"emerging-hot-spot\"\n | \"composite-score\";\n\n/**\n * Identifiers of the raster processing tools. Kept in sync by hand with the\n * `id` fields of `RASTER_TOOLS` in `@geolibre/processing` (`raster-tools.ts`);\n * deriving the type there would create a core -> processing circular import.\n */\nexport type RasterToolKind =\n | \"hillshade\"\n | \"slope\"\n | \"aspect\"\n | \"reproject\"\n | \"resample\"\n | \"clip-extent\"\n | \"clip-mask\"\n | \"polygonize\"\n | \"contour\"\n | \"interpolate\"\n | \"zonal\"\n | \"raster-calc\"\n | \"spectral-index\"\n | \"reclassify\"\n | \"mosaic\"\n | \"focal\";\n\nexport interface UiSlice {\n ui: {\n processingOpen: boolean;\n /**\n * Tool id to preselect when the Whitebox toolbox dialog opens, set when the\n * user picks a specific tool from the Processing menu's category submenus.\n * Consumed and cleared by ProcessingDialog. Null means \"no preselection\".\n */\n processingInitialTool: string | null;\n conversionOpen: ConversionToolKind | null;\n vectorToolOpen: VectorToolKind | null;\n networkToolOpen: NetworkToolKind | null;\n statisticsToolOpen: StatisticsToolKind | null;\n rasterToolOpen: RasterToolKind | null;\n segmentationOpen: boolean;\n objectDetectionOpen: boolean;\n segmentEverythingOpen: boolean;\n geocodeOpen: boolean;\n sqlWorkspaceOpen: boolean;\n loadEditorFeaturesOpen: boolean;\n // Store layer preselected in the \"Load Features into Editor\" dialog when it\n // is opened from a layer's context menu, or null when opened without a target.\n loadEditorFeaturesLayerId: string | null;\n pythonConsoleOpen: boolean;\n notebookOpen: boolean;\n assistantOpen: boolean;\n attributeTableOpen: boolean;\n /**\n * Pending \"open the attribute table in edit mode\" request for this layer,\n * written by Identify's Edit attributes action (#2932). The table consumes\n * and clears it. Null when idle.\n */\n attributeTableEditLayerId: string | null;\n /** Whether the Raster Attribute Table bottom panel is open (issue #1307). */\n rasterAttributeTableOpen: boolean;\n dashboardOpen: boolean;\n storymapPanelOpen: boolean;\n storymapPresenting: boolean;\n // True when the active presentation was launched from the editor, so exiting\n // it reopens the Story Map editor instead of dropping to the bare map\n // (#918). Auto-presented projects (opened for viewing) leave this false.\n storymapReturnToEditor: boolean;\n /**\n * Layer opacities the active story presentation has applied so far, keyed\n * by store layer id. Playback fades layers by writing MapLibre paint\n * properties directly (never the persisted `layers[].opacity`), so this is\n * how renderers outside MapLibre's paint model (deck.gl diagrams, 3D\n * Z-value geometry) and the on-map Legend follow a chapter's fades. Empty\n * while not presenting; never saved with the project.\n */\n storymapLayerOpacity: Record<string, number>;\n // Id of the chapter currently being composed on the live map. When set, the\n // Story Map dialog is hidden so the user can pan/zoom/tilt the real map and\n // save the resulting camera back into this chapter (issue #775).\n storymapComposingId: string | null;\n /** The Batch tools dialog (run one tool across many layers). */\n batchToolsOpen: boolean;\n /** The Model Builder canvas panel (author a processing graph). */\n modelBuilderOpen: boolean;\n /** One-shot request for Model Builder to load a saved model. */\n modelBuilderRequestedModelId: string | null;\n /** Style Manager dialog visibility (issue #1294). */\n styleManagerOpen: boolean;\n /** Processing History panel visibility (#1292). */\n processingHistoryOpen: boolean;\n /** Select by Expression dialog visibility (#1314). */\n selectByExpressionOpen: boolean;\n // Layer preselected in the Select by Expression dialog when it is opened\n // from a layer's context menu, or null when opened without a target.\n // Deliberately not selectLayer(): that would clear the live selection the\n // dialog's add/remove/intersect modes need to combine with.\n selectByExpressionLayerId: string | null;\n /** Select by Location dialog visibility (#1314). */\n selectByLocationOpen: boolean;\n /** Same contract as `selectByExpressionLayerId`, for Select by Location. */\n selectByLocationLayerId: string | null;\n /**\n * Pending \"re-run from History\" request. Written by the History panel just\n * before it opens the target processing dialog; consumed and cleared by\n * that dialog once it has pre-filled its parameter form. Null when idle.\n */\n processingRerun: ProcessingRerunRequest | null;\n zoomToSelectedFeature: boolean;\n // Live-collaboration dialog visibility. Lifted into the store (rather than\n // local toolbar state) so the on-canvas session-status badge can reopen the\n // Collaborate dialog from outside the toolbar's component tree (#754).\n collaborateDialogOpen: boolean;\n };\n\n setProcessingOpen: (open: boolean) => void;\n setProcessingInitialTool: (toolId: string | null) => void;\n setConversionOpen: (kind: ConversionToolKind | null) => void;\n setVectorToolOpen: (kind: VectorToolKind | null) => void;\n setNetworkToolOpen: (kind: NetworkToolKind | null) => void;\n setStatisticsToolOpen: (kind: StatisticsToolKind | null) => void;\n setRasterToolOpen: (kind: RasterToolKind | null) => void;\n setSegmentationOpen: (open: boolean) => void;\n setObjectDetectionOpen: (open: boolean) => void;\n setSegmentEverythingOpen: (open: boolean) => void;\n setGeocodeOpen: (open: boolean) => void;\n setSqlWorkspaceOpen: (open: boolean) => void;\n setLoadEditorFeaturesOpen: (open: boolean, layerId?: string | null) => void;\n setPythonConsoleOpen: (open: boolean) => void;\n setNotebookOpen: (open: boolean) => void;\n setAssistantOpen: (open: boolean) => void;\n setAttributeTableOpen: (open: boolean) => void;\n /**\n * Ask the attribute table to open in edit mode on `layerId` (opening the\n * table too), or clear a pending request with `null`.\n */\n requestAttributeTableEdit: (layerId: string | null) => void;\n setRasterAttributeTableOpen: (open: boolean) => void;\n setDashboardOpen: (open: boolean) => void;\n setStorymapPanelOpen: (open: boolean) => void;\n setStorymapPresenting: (presenting: boolean, returnToEditor?: boolean) => void;\n /**\n * Record the layer opacities a story chapter applied, merging into the\n * presentation's running map (see `ui.storymapLayerOpacity`).\n */\n setStorymapLayerOpacity: (changes: Record<string, number>) => void;\n setStorymapComposing: (chapterId: string | null) => void;\n setBatchToolsOpen: (open: boolean) => void;\n setModelBuilderOpen: (open: boolean) => void;\n setModelBuilderRequestedModelId: (id: string | null) => void;\n setProcessingHistoryOpen: (open: boolean) => void;\n /** Open/close Select by Expression, optionally preselecting a target layer. */\n setSelectByExpressionOpen: (open: boolean, layerId?: string | null) => void;\n /** Open/close Select by Location, optionally preselecting a target layer. */\n setSelectByLocationOpen: (open: boolean, layerId?: string | null) => void;\n setProcessingRerun: (request: ProcessingRerunRequest | null) => void;\n setCollaborateDialogOpen: (open: boolean) => void;\n setZoomToSelectedFeature: (enabled: boolean) => void;\n setStyleManagerOpen: (open: boolean) => void;\n}\n\nexport const createUiSlice: SliceCreator<UiSlice> = (set) => ({\n ui: {\n processingOpen: false,\n processingInitialTool: null,\n conversionOpen: null,\n vectorToolOpen: null,\n networkToolOpen: null,\n statisticsToolOpen: null,\n rasterToolOpen: null,\n segmentationOpen: false,\n objectDetectionOpen: false,\n segmentEverythingOpen: false,\n geocodeOpen: false,\n sqlWorkspaceOpen: false,\n loadEditorFeaturesOpen: false,\n loadEditorFeaturesLayerId: null,\n pythonConsoleOpen: false,\n notebookOpen: false,\n assistantOpen: false,\n attributeTableOpen: false,\n attributeTableEditLayerId: null,\n rasterAttributeTableOpen: false,\n dashboardOpen: false,\n storymapPanelOpen: false,\n storymapPresenting: false,\n storymapReturnToEditor: false,\n storymapLayerOpacity: {},\n storymapComposingId: null,\n batchToolsOpen: false,\n modelBuilderOpen: false,\n modelBuilderRequestedModelId: null,\n styleManagerOpen: false,\n processingHistoryOpen: false,\n selectByExpressionOpen: false,\n selectByExpressionLayerId: null,\n selectByLocationOpen: false,\n selectByLocationLayerId: null,\n processingRerun: null,\n zoomToSelectedFeature: false,\n collaborateDialogOpen: false,\n },\n\n setProcessingOpen: (open) => set((s) => ({ ui: { ...s.ui, processingOpen: open } })),\n setProcessingInitialTool: (toolId) =>\n set((s) => ({ ui: { ...s.ui, processingInitialTool: toolId } })),\n setConversionOpen: (kind) => set((s) => ({ ui: { ...s.ui, conversionOpen: kind } })),\n setVectorToolOpen: (kind) =>\n set((s) => ({\n ui: {\n ...s.ui,\n // Pre-DGGS projects / callers may still pass h3-grid / h3-bin-points.\n vectorToolOpen:\n (kind as string | null) === \"h3-grid\"\n ? \"dggs-grid\"\n : (kind as string | null) === \"h3-bin-points\"\n ? \"dggs-bin\"\n : kind,\n },\n })),\n setNetworkToolOpen: (kind) => set((s) => ({ ui: { ...s.ui, networkToolOpen: kind } })),\n setStatisticsToolOpen: (kind) => set((s) => ({ ui: { ...s.ui, statisticsToolOpen: kind } })),\n setRasterToolOpen: (kind) => set((s) => ({ ui: { ...s.ui, rasterToolOpen: kind } })),\n setSegmentationOpen: (open) => set((s) => ({ ui: { ...s.ui, segmentationOpen: open } })),\n setObjectDetectionOpen: (open) => set((s) => ({ ui: { ...s.ui, objectDetectionOpen: open } })),\n setSegmentEverythingOpen: (open) =>\n set((s) => ({ ui: { ...s.ui, segmentEverythingOpen: open } })),\n setGeocodeOpen: (open) => set((s) => ({ ui: { ...s.ui, geocodeOpen: open } })),\n setSqlWorkspaceOpen: (open) => set((s) => ({ ui: { ...s.ui, sqlWorkspaceOpen: open } })),\n setLoadEditorFeaturesOpen: (open, layerId) =>\n set((s) => ({\n ui: {\n ...s.ui,\n loadEditorFeaturesOpen: open,\n loadEditorFeaturesLayerId: open ? (layerId ?? null) : null,\n },\n })),\n setPythonConsoleOpen: (open) => set((s) => ({ ui: { ...s.ui, pythonConsoleOpen: open } })),\n setNotebookOpen: (open) => set((s) => ({ ui: { ...s.ui, notebookOpen: open } })),\n setAssistantOpen: (open) => set((s) => ({ ui: { ...s.ui, assistantOpen: open } })),\n setAttributeTableOpen: (open) => set((s) => ({ ui: { ...s.ui, attributeTableOpen: open } })),\n requestAttributeTableEdit: (layerId) =>\n set((s) => ({\n ui: {\n ...s.ui,\n attributeTableEditLayerId: layerId,\n attributeTableOpen: layerId === null ? s.ui.attributeTableOpen : true,\n },\n })),\n setRasterAttributeTableOpen: (open) =>\n set((s) => ({ ui: { ...s.ui, rasterAttributeTableOpen: open } })),\n setDashboardOpen: (open) => set((s) => ({ ui: { ...s.ui, dashboardOpen: open } })),\n setStorymapPanelOpen: (open) =>\n set((s) => ({\n ui: {\n ...s.ui,\n storymapPanelOpen: open,\n // Opening the editor must leave compose mode, or the menu item could\n // re-open the dialog while the compose bar is still active over a\n // now-hidden map (#775). Closing (entering compose) leaves it as-is.\n ...(open ? { storymapComposingId: null } : {}),\n },\n })),\n setStorymapPresenting: (presenting, returnToEditor = false) =>\n set((s) => ({\n ui: {\n ...s.ui,\n storymapPresenting: presenting,\n // Track whether exiting should reopen the editor; only meaningful\n // while presenting, so it clears once the presentation ends (#918).\n storymapReturnToEditor: presenting ? returnToEditor : false,\n // A presentation starts from (and leaves behind) a clean slate; the\n // fades it applies are replayed from chapter 0 on the next run.\n storymapLayerOpacity: {},\n },\n })),\n setStorymapLayerOpacity: (changes) =>\n set((s) => {\n const next = { ...s.ui.storymapLayerOpacity };\n let changed = false;\n for (const [layerId, opacity] of Object.entries(changes)) {\n const clamped = Math.min(1, Math.max(0, opacity));\n if (next[layerId] === clamped) continue;\n next[layerId] = clamped;\n changed = true;\n }\n // Return the current state untouched when nothing moved: Zustand only\n // skips the listener broadcast for the same state reference, and a\n // chapter re-entering the same opacities would otherwise rebuild\n // every store subscriber's view.\n return changed ? { ui: { ...s.ui, storymapLayerOpacity: next } } : s;\n }),\n setStorymapComposing: (chapterId) =>\n set((s) => ({ ui: { ...s.ui, storymapComposingId: chapterId } })),\n setBatchToolsOpen: (open) => set((s) => ({ ui: { ...s.ui, batchToolsOpen: open } })),\n setModelBuilderOpen: (open) => set((s) => ({ ui: { ...s.ui, modelBuilderOpen: open } })),\n setModelBuilderRequestedModelId: (id) =>\n set((s) => ({ ui: { ...s.ui, modelBuilderRequestedModelId: id } })),\n setProcessingHistoryOpen: (open) =>\n set((s) => ({ ui: { ...s.ui, processingHistoryOpen: open } })),\n setProcessingRerun: (request) => set((s) => ({ ui: { ...s.ui, processingRerun: request } })),\n setSelectByExpressionOpen: (open, layerId) =>\n set((s) => ({\n ui: {\n ...s.ui,\n selectByExpressionOpen: open,\n selectByExpressionLayerId: open ? (layerId ?? null) : null,\n },\n })),\n setSelectByLocationOpen: (open, layerId) =>\n set((s) => ({\n ui: {\n ...s.ui,\n selectByLocationOpen: open,\n selectByLocationLayerId: open ? (layerId ?? null) : null,\n },\n })),\n setCollaborateDialogOpen: (open) =>\n set((s) => ({ ui: { ...s.ui, collaborateDialogOpen: open } })),\n setZoomToSelectedFeature: (enabled) =>\n set((s) => ({ ui: { ...s.ui, zoomToSelectedFeature: enabled } })),\n\n setStyleManagerOpen: (open) => set((s) => ({ ui: { ...s.ui, styleManagerOpen: open } })),\n});\n","/**\n * The app store: one Zustand store wrapped in zundo's undo history, composed\n * from the slices under `./store/` (zustand's slices pattern). The split is\n * internal — the hook, the state shape and every export are unchanged, and\n * consumers keep importing from here (or `@geolibre/core`).\n *\n * Slices: project (identity and lifecycle), map-view (camera, basemap, grid),\n * layers, layer-groups, project-content (comments, dashboard, story map),\n * processing (models, run history), libraries (style / layer / template),\n * session (selection and live readouts), collaboration, capabilities, and ui\n * (dialog and panel flags). Undo history lives in `./store/undo-history.ts`.\n */\nimport { create } from \"zustand\";\nimport { temporal } from \"zundo\";\nimport { appPrivilegeReason } from \"./capabilities\";\nimport { setActiveEllipsoidId } from \"./ellipsoids\";\nimport type { AppPrivilege } from \"./types\";\nimport { createCapabilitiesSlice } from \"./store/capabilities-slice\";\nimport { createCollaborationSlice } from \"./store/collaboration-slice\";\nimport { createLayerGroupsSlice } from \"./store/layer-groups-slice\";\nimport { createLayersSlice } from \"./store/layers-slice\";\nimport { createLibrariesSlice } from \"./store/libraries-slice\";\nimport { createMapViewSlice } from \"./store/map-view-slice\";\nimport { createProcessingSlice } from \"./store/processing-slice\";\nimport { createProjectContentSlice } from \"./store/project-content-slice\";\nimport { createProjectSlice } from \"./store/project-slice\";\nimport { createSessionSlice } from \"./store/session-slice\";\nimport { createUiSlice } from \"./store/ui-slice\";\nimport type { AppState } from \"./store/types\";\nimport { bindHistoryStore, createHistoryOptions } from \"./store/undo-history\";\n\nexport type { AppState } from \"./store/types\";\nexport type {\n ConversionToolKind,\n NetworkToolKind,\n RasterToolKind,\n StatisticsToolKind,\n VectorToolKind,\n} from \"./store/ui-slice\";\nexport {\n type GpsStatusFix,\n IDENTIFY_ALL_LAYERS_ID,\n type IdentifyState,\n identifyAllIncludes,\n identifyStateWithoutLayers,\n resolveIdentifyTarget,\n} from \"./store/session-slice\";\nexport { DEFAULT_COLLABORATION_STATE } from \"./store/collaboration-slice\";\nexport { projectPathLabel } from \"./store/project-slice\";\nexport {\n canRedoProjectRestore,\n canUndoProjectRestore,\n clearHistory,\n withoutHistory,\n redo,\n registerProjectRestoreHistory,\n subscribeProjectRestoreHistory,\n undo,\n} from \"./store/undo-history\";\n\nexport const useAppStore = create<AppState>()(\n temporal(\n (set, get) => ({\n ...createProjectSlice(set, get),\n ...createMapViewSlice(set, get),\n ...createLayersSlice(set, get),\n ...createLayerGroupsSlice(set, get),\n ...createProjectContentSlice(set, get),\n ...createProcessingSlice(set, get),\n ...createLibrariesSlice(set, get),\n ...createSessionSlice(set, get),\n ...createCollaborationSlice(set, get),\n ...createCapabilitiesSlice(set, get),\n ...createUiSlice(set, get),\n }),\n createHistoryOptions(),\n ),\n);\n\nbindHistoryStore(useAppStore);\n\n// Mirror the project's ellipsoid into the module-level singleton the\n// measurement helpers read. One subscription covers every path that changes\n// preferences (setPreferences, load/new project, undo/redo) without threading\n// the value through each call site. The subscription runs on every store\n// mutation (e.g. setPointerCoords on each mousemove), so guard on the id to skip\n// the redundant work for a value that changes at most once per session.\nlet lastEllipsoidId = useAppStore.getState().preferences.map.ellipsoidId;\nsetActiveEllipsoidId(lastEllipsoidId);\nuseAppStore.subscribe((state) => {\n const id = state.preferences.map.ellipsoidId;\n if (id === lastEllipsoidId) return;\n lastEllipsoidId = id;\n setActiveEllipsoidId(id);\n});\n\n/**\n * Whether the map should fit to data the user just added, per the project's\n * Map Preferences. Add-data paths consult this before an automatic fit; an\n * explicit \"Zoom to layer\" request ignores it.\n *\n * @returns `true` unless the user turned off zooming to new layers.\n */\nexport function shouldZoomToNewLayers(): boolean {\n return useAppStore.getState().preferences.map.zoomToNewLayers !== false;\n}\n\n/**\n * React hook for consuming application capability state for a specific privilege.\n *\n * @param privilege - The privilege to check.\n * @returns `{ granted: boolean, reason?: string }`\n */\nexport function useAppCapability(privilege: AppPrivilege): { granted: boolean; reason?: string } {\n const capabilities = useAppStore((state) => state.capabilities);\n return {\n granted: capabilities.privileges.includes(privilege),\n reason: appPrivilegeReason(capabilities, privilege),\n };\n}\n","import { useAppStore } from \"./store\";\nimport { applySelectionMode, type SelectionMode } from \"./selection\";\n\n/**\n * Store-touching selection helpers. Kept out of `selection.ts` so that module\n * stays a pure, directly testable set of functions over id arrays.\n */\n\n/**\n * Applies a matched id set to the live selection under the given mode and\n * returns the resulting selection size. Combines with the current selection\n * only when the target layer already holds it (ids are per-layer, so a\n * cross-layer combine would mix unrelated features). Otherwise `current` is\n * empty: \"new\"/\"add\" start a fresh selection on the target layer, while\n * \"remove\"/\"intersect\" necessarily yield an empty one — the dialogs guard\n * this by forcing mode \"new\" when the target layer does not hold the\n * selection (SelectionModeField's disableCombineModes). `selectLayer` runs\n * before `selectFeatures` because it clears the selection as a side effect.\n *\n * Lives in `@geolibre/core` rather than the app so the layer-panel dialogs and\n * the map's drawing gestures (`@geolibre/map`) share one copy of this rule\n * instead of two that can drift apart.\n */\nexport function applyMatchedSelection(\n targetLayerId: string,\n matchedIds: string[],\n mode: SelectionMode,\n): number {\n const store = useAppStore.getState();\n const current = store.selectedLayerId === targetLayerId ? store.selectedFeatureIds : [];\n const next = applySelectionMode(current, matchedIds, mode);\n if (store.selectedLayerId !== targetLayerId) store.selectLayer(targetLayerId);\n store.selectFeatures(next);\n return next.length;\n}\n","/**\n * Shared scale-bar unit maths.\n *\n * The on-screen scale bar (`PlanetaryScaleControl` in `@geolibre/map`) and the\n * Print Layout scale bar (`apps/geolibre-desktop/src/lib/print-layout.ts`) both\n * need to pick a \"nice\" round distance in the user's chosen unit system\n * (metric / imperial / nautical). Keeping that logic in one place here means the\n * two bars round a given ground span to the same value and share the same unit\n * constants, so a change to one can never silently diverge from the other.\n */\n\nimport type { MapScaleUnit } from \"./types\";\n\n/** Conversion constants shared by every scale-bar denomination. */\nexport const FEET_PER_METER = 3.2808398950131235; // 1 / 0.3048\nexport const METERS_PER_FOOT = 0.3048;\nexport const FEET_PER_MILE = 5280;\nexport const METERS_PER_MILE = 1609.344; // 5280 ft\nexport const METERS_PER_NAUTICAL_MILE = 1852;\n\n/**\n * The largest `1 / 2 / 3 / 5 / 10 × 10ⁿ` number that does not exceed `num`, so\n * the bar lands on a readable round value. Uses log10 for the magnitude (unlike\n * MapLibre's digit-count trick, which returns 1 for any `0 < num < 1` and so\n * rounds *up* — producing a bar wider than the max width for sub-unit spans).\n * Returns 0 for a non-positive input so callers can clamp or fall back.\n */\nexport function getRoundNum(num: number): number {\n if (!(num > 0)) return 0;\n const pow10 = Math.pow(10, Math.floor(Math.log10(num)));\n const d = num / pow10;\n const nice = d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : 1;\n return pow10 * nice;\n}\n\n/** Trim trailing-zero noise from a rounded value (e.g. 0.5, 2, 300). */\nexport function formatRoundNum(value: number): string {\n return Number.parseFloat(value.toPrecision(12)).toString();\n}\n\n/**\n * The denomination a scale bar rounds and labels distances with for a ground\n * span of `maxMeters` in the requested unit system: km/m for metric, mi/ft for\n * imperial, and nautical miles for nautical. The unit crosses to the larger\n * denomination once the span exceeds one of it. `metersPerUnit` is the size of\n * one unit of that denomination in metres, so a caller can round the span in\n * unit space and convert the result back to metres (and thus pixels).\n */\nexport function scaleDenomination(\n maxMeters: number,\n unit: MapScaleUnit,\n): { metersPerUnit: number; label: string } {\n if (unit === \"imperial\") {\n const feet = maxMeters * FEET_PER_METER;\n return feet >= FEET_PER_MILE\n ? { metersPerUnit: METERS_PER_MILE, label: \"mi\" }\n : { metersPerUnit: METERS_PER_FOOT, label: \"ft\" };\n }\n if (unit === \"nautical\") {\n return { metersPerUnit: METERS_PER_NAUTICAL_MILE, label: \"nmi\" };\n }\n return maxMeters >= 1000\n ? { metersPerUnit: 1000, label: \"km\" }\n : { metersPerUnit: 1, label: \"m\" };\n}\n\n/**\n * Convert a ground distance in metres into the span (already divided into the\n * bar's denomination) and its label — the shape the on-screen control consumes.\n * Derived from {@link scaleDenomination} so the two never diverge.\n */\nexport function scaleSpan(maxMeters: number, unit: MapScaleUnit): { span: number; label: string } {\n const { metersPerUnit, label } = scaleDenomination(maxMeters, unit);\n return { span: maxMeters / metersPerUnit, label };\n}\n","/**\n * Shared ground-elevation resolution.\n *\n * Several places in the app need \"how high is the ground at this coordinate\":\n * the Elevation Profile plugin (along a drawn line), the Measure tool's\n * terrain-aware readout (along a measured geometry), and the status bar (under\n * the cursor). They all resolve it the same way, from two sources tried in\n * order:\n *\n * 1. The map's own terrain (`map.queryTerrainElevation`) when 3D terrain is\n * enabled — instant and offline, but only where DEM tiles are loaded.\n * MapLibre returns elevations multiplied by the terrain exaggeration, so\n * values are divided back to true meters.\n * 2. The keyless Open-Meteo elevation API — works without terrain enabled and\n * anywhere on Earth, but costs a network round-trip.\n *\n * This module lives in `@geolibre/core` rather than beside the Elevation\n * Profile plugin because `@geolibre/map` (which owns the pointer readout)\n * cannot import from `@geolibre/plugins` without a dependency cycle — plugins\n * already depends on map. Core is the only package all three can share.\n *\n * Source 2 is Earth-only: Open-Meteo has no data for the planetary basemaps, so\n * callers must not reach for it when the active body is not Earth.\n */\n\n/** A coordinate as `[longitude, latitude]` in degrees. */\nexport type LngLat = [number, number];\n\n/** Open-Meteo accepts at most 100 coordinates per elevation request. */\nexport const MAX_POINTS_PER_REQUEST = 100;\n\n/** Abort an elevation request that has not responded within this window. */\nexport const ELEVATION_REQUEST_TIMEOUT_MS = 15000;\n\nconst ENDPOINT = \"https://api.open-meteo.com/v1/elevation\";\n\n/** Error thrown when an elevation request cannot be completed or parsed. */\nexport class ElevationFetchError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ElevationFetchError\";\n }\n}\n\n/** A `fetch`-compatible function, so tests can inject a stub. */\nexport type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;\n\ninterface ElevationResponse {\n elevation?: number[];\n}\n\n/**\n * Normalize longitude into [-180, 180] degrees (handles unwrapped coordinates\n * from antimeridian cross-overs and world-copy pannings).\n */\nexport function normalizeLongitude(lng: number): number {\n if (!Number.isFinite(lng)) return 0;\n return ((((lng + 180) % 360) + 360) % 360) - 180;\n}\n\n/**\n * Clamp latitude into [-90, 90] degrees.\n */\nexport function clampLatitude(lat: number): number {\n if (!Number.isFinite(lat)) return 0;\n return Math.min(90, Math.max(-90, lat));\n}\n\n/**\n * Fetch elevations (in meters) for an ordered list of coordinates.\n *\n * @param points - Coordinates as `[lng, lat]`, at most {@link MAX_POINTS_PER_REQUEST}\n * @param fetchImpl - Optional `fetch` implementation; defaults to the global `fetch`\n * @returns The elevation in meters for each input coordinate, in the same order\n * @throws {ElevationFetchError} On too many points, a non-finite coordinate, a\n * network error, a non-2xx response, a malformed body, or a length mismatch\n */\nexport async function fetchElevations(points: LngLat[], fetchImpl?: FetchLike): Promise<number[]> {\n if (points.length === 0) return [];\n if (points.length > MAX_POINTS_PER_REQUEST) {\n throw new ElevationFetchError(\n `Too many points: ${points.length} (max ${MAX_POINTS_PER_REQUEST}).`,\n );\n }\n // Normalizing would turn NaN/Infinity into 0 and query (0, 0) as if it were\n // the requested point, so reject them instead of returning a wrong elevation.\n if (points.some(([lng, lat]) => !Number.isFinite(lng) || !Number.isFinite(lat))) {\n throw new ElevationFetchError(\"Invalid coordinate: longitude and latitude must be finite.\");\n }\n\n const doFetch: FetchLike = fetchImpl ?? ((url, init) => fetch(url, init));\n const latitudes = points.map((p) => clampLatitude(p[1]).toFixed(6)).join(\",\");\n const longitudes = points.map((p) => normalizeLongitude(p[0]).toFixed(6)).join(\",\");\n const url = `${ENDPOINT}?latitude=${latitudes}&longitude=${longitudes}`;\n\n // A default fetch never times out, so a hung request would leave the control's\n // busy state stuck with no recovery. Abort after ELEVATION_REQUEST_TIMEOUT_MS\n // and surface it as a normal fetch error the caller already handles.\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), ELEVATION_REQUEST_TIMEOUT_MS);\n let response: Response;\n try {\n response = await doFetch(url, { signal: controller.signal });\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw new ElevationFetchError(\"Elevation request timed out.\");\n }\n const detail = error instanceof Error ? error.message : \"unknown error\";\n throw new ElevationFetchError(`Could not reach the elevation service: ${detail}`);\n } finally {\n clearTimeout(timeoutId);\n }\n\n if (!response.ok) {\n throw new ElevationFetchError(`Elevation request failed (HTTP ${response.status}).`);\n }\n\n let data: ElevationResponse;\n try {\n data = (await response.json()) as ElevationResponse;\n } catch {\n throw new ElevationFetchError(\"Could not parse the elevation response.\");\n }\n\n if (!data || !Array.isArray(data.elevation)) {\n throw new ElevationFetchError(\"Malformed elevation response.\");\n }\n if (data.elevation.length !== points.length) {\n throw new ElevationFetchError(\n `Expected ${points.length} elevations but received ${data.elevation.length}.`,\n );\n }\n\n return data.elevation;\n}\n\n/** The slice of the MapLibre map the terrain sampler needs (stubbed in tests). */\nexport interface TerrainMapLike {\n getTerrain?: () => { exaggeration?: number } | null | undefined;\n queryTerrainElevation?: (lngLat: LngLat) => number | null;\n}\n\n/**\n * Sample elevations from the map's enabled 3D terrain, in true meters\n * (MapLibre's `queryTerrainElevation` bakes the exaggeration in, so it is\n * divided back out). Returns null when terrain is not enabled.\n */\nexport function sampleMapTerrain(\n map: TerrainMapLike | null | undefined,\n points: LngLat[],\n): (number | null)[] | null {\n if (!map?.getTerrain || !map.queryTerrainElevation) return null;\n const terrain = map.getTerrain();\n if (!terrain) return null;\n const exaggeration =\n typeof terrain.exaggeration === \"number\" && terrain.exaggeration > 0 ? terrain.exaggeration : 1;\n return points.map((point) => {\n const elevation = map.queryTerrainElevation!(point);\n return typeof elevation === \"number\" && Number.isFinite(elevation)\n ? elevation / exaggeration\n : null;\n });\n}\n\n/**\n * Sample a single point's elevation from enabled 3D terrain, in true meters.\n * Returns null when terrain is off or the DEM tile under the point has not\n * loaded — the caller decides whether that is worth a network round-trip.\n */\nexport function sampleMapTerrainPoint(\n map: TerrainMapLike | null | undefined,\n point: LngLat,\n): number | null {\n return sampleMapTerrain(map, [point])?.[0] ?? null;\n}\n\n/**\n * Sample elevations from the Open-Meteo API, chunked to its 100-point request\n * limit. A failed chunk yields nulls for its points rather than throwing, so a\n * flaky network degrades the readout instead of breaking the caller.\n */\nexport async function sampleRemoteElevations(\n points: LngLat[],\n fetchImpl?: FetchLike,\n): Promise<(number | null)[]> {\n const chunks: LngLat[][] = [];\n for (let i = 0; i < points.length; i += MAX_POINTS_PER_REQUEST) {\n chunks.push(points.slice(i, i + MAX_POINTS_PER_REQUEST));\n }\n // The chunks are independent, so fire them concurrently; Promise.all\n // preserves their order for reassembly.\n const chunkResults = await Promise.all(\n chunks.map(async (chunk) => {\n try {\n const elevations = await fetchElevations(chunk, fetchImpl);\n return elevations.map((elevation) => (Number.isFinite(elevation) ? elevation : null));\n } catch {\n return chunk.map(() => null);\n }\n }),\n );\n return chunkResults.flat();\n}\n\n// --- Pointer elevation resolver --------------------------------------------\n\n/** Round a coordinate to ~11 m so nearby hovers share one cache entry. */\nfunction cacheKey(point: LngLat): string {\n return `${point[0].toFixed(4)},${point[1].toFixed(4)}`;\n}\n\n/** How long the pointer must sit still before a remote lookup is worth it. */\nexport const POINTER_ELEVATION_DEBOUNCE_MS = 500;\n\n/** Remote results kept per resolver; bounded so a long session cannot grow it without limit. */\nexport const POINTER_ELEVATION_CACHE_LIMIT = 500;\n\nexport interface PointerElevationResolverOptions {\n /** The live map, for the terrain sample. May return null before init. */\n getMap: () => TerrainMapLike | null | undefined;\n /**\n * Whether the readout is switched on (Controls -> Elevation). Read per call\n * rather than captured, and rechecked before publishing a remote result, so\n * turning it off mid-request neither shows a late value nor leaves the last\n * one on screen. Defaults to always-on for callers that do not gate it.\n */\n isEnabled?: () => boolean;\n /**\n * Whether the *remote* Open-Meteo fallback may run. The terrain path sends\n * nothing anywhere, so it is deliberately not gated by this — a user who has\n * declined the network lookup still gets a live readout wherever 3D terrain\n * is on. Defaults to allowed for callers that do not gate it.\n */\n canUseRemote?: () => boolean;\n /** Whether the active body is Earth — Open-Meteo has no data for anything else. */\n isEarth: () => boolean;\n /** Called with the resolved elevation in true metres, or null when unknown. */\n emit: (elevation: number | null) => void;\n fetchImpl?: FetchLike;\n debounceMs?: number;\n}\n\nexport interface PointerElevationResolver {\n /** Report a new pointer position, or null when the pointer leaves the map. */\n update: (point: LngLat | null) => void;\n /**\n * Drop any pending *and* in-flight lookup without tearing the resolver down.\n * Used when the map's context changes under it — loading another project\n * resets the readout, and a response for the previous project must not\n * repaint it.\n */\n invalidate: () => void;\n /** Cancel any pending lookup. */\n dispose: () => void;\n}\n\n/**\n * Resolves the ground elevation under the pointer for the status bar.\n *\n * Two paths, deliberately asymmetric:\n *\n * - **Terrain enabled** — `queryTerrainElevation` is synchronous and free, so\n * it runs on every move and the readout tracks the cursor live.\n * - **Terrain disabled** — falls back to Open-Meteo, but only after the\n * pointer has been still for {@link POINTER_ELEVATION_DEBOUNCE_MS}, and only\n * on Earth. Results are cached per ~11 m cell, so sweeping back over ground\n * already looked up costs nothing. Without both guards a hover readout would\n * fire a request per mousemove at a free, keyless public service.\n *\n * While a remote lookup is outstanding the readout reads null rather than the\n * previous point's value: showing a stale height next to fresh coordinates\n * would be worse than showing nothing.\n */\nexport function createPointerElevationResolver(\n options: PointerElevationResolverOptions,\n): PointerElevationResolver {\n const { getMap, isEarth, emit, fetchImpl } = options;\n const isEnabled = options.isEnabled ?? (() => true);\n const canUseRemote = options.canUseRemote ?? (() => true);\n const debounceMs = options.debounceMs ?? POINTER_ELEVATION_DEBOUNCE_MS;\n const cache = new Map<string, number | null>();\n let timer: ReturnType<typeof setTimeout> | null = null;\n // Bumped on every update so a slow response for an abandoned point is dropped\n // rather than overwriting the readout for wherever the pointer is now.\n let generation = 0;\n\n const cancelPending = () => {\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n };\n\n const update = (point: LngLat | null) => {\n generation += 1;\n cancelPending();\n\n if (!point || !isEnabled()) {\n emit(null);\n return;\n }\n\n const fromTerrain = sampleMapTerrainPoint(getMap(), point);\n if (fromTerrain !== null) {\n emit(fromTerrain);\n return;\n }\n\n // Open-Meteo has no data off Earth, and the remote path needs consent.\n if (!isEarth() || !canUseRemote()) {\n emit(null);\n return;\n }\n\n const key = cacheKey(point);\n if (cache.has(key)) {\n const cached = cache.get(key) ?? null;\n // Re-insert to move this entry to the end: Map iterates in insertion\n // order, so the first key is always the least recently used.\n cache.delete(key);\n cache.set(key, cached);\n emit(cached);\n return;\n }\n\n emit(null);\n const requested = generation;\n timer = setTimeout(() => {\n timer = null;\n void (async () => {\n const [elevation] = await sampleRemoteElevations([point], fetchImpl);\n const value = elevation ?? null;\n // `sampleRemoteElevations` turns a fetch failure into a null, which is\n // indistinguishable here from \"no data for this cell\". Caching that\n // would blackhole the readout for this ~11 m cell for the rest of the\n // session, so only successes are remembered and a transient network\n // blip gets another chance on the next hover.\n if (value !== null) {\n // Evict just the oldest entry. Clearing the whole cache at the cap\n // would make every 500th distinct cell trigger a burst of re-fetches\n // for cells that may still be under active hovering.\n if (cache.size >= POINTER_ELEVATION_CACHE_LIMIT) {\n const oldest = cache.keys().next();\n if (!oldest.done) cache.delete(oldest.value);\n }\n cache.set(key, value);\n }\n // The pointer moved, left, or the resolver was disposed while the\n // request was in flight. Also recheck the body: a switch to a planetary\n // basemap mid-request must not publish an Earth elevation over it.\n if (requested !== generation || !isEarth() || !isEnabled() || !canUseRemote()) return;\n emit(value);\n })();\n }, debounceMs);\n };\n\n // Bumping the generation is what actually invalidates an *in-flight*\n // request; cancelPending alone only clears a timer that has not fired yet, so\n // a fetch already in progress would still emit into a torn-down map.\n const invalidate = (): void => {\n generation += 1;\n cancelPending();\n };\n\n return { update, invalidate, dispose: invalidate };\n}\n","/**\n * Camera altitude (\"Eye alt\") for the status bar — issue #1816.\n *\n * MapLibre's `transform.getCameraAltitude()` returns the camera height above\n * sea level in metres, which is exactly what Google Earth Pro's `Eye alt`\n * reports. But it derives that height from the Web-Mercator scale, which is\n * built on Earth's radius — so on a Moon / Mars / Mercury basemap it is wrong by\n * the ratio of that body's circumference to Earth's, the same way MapLibre's\n * built-in ScaleControl is (see `PlanetaryScaleControl`, which fixes the\n * horizontal case).\n *\n * {@link scaleAltitudeToActiveBody} applies the matching correction so the\n * readout is right on every body, using the same active-ellipsoid singleton the\n * scale bar and the measurement tools read.\n */\n\nimport { getActiveBodyRadiusRatio } from \"./ellipsoids\";\nimport { scaleDenomination } from \"./scale-units\";\nimport type { MapScaleUnit } from \"./types\";\n\n/**\n * Convert a MapLibre camera altitude (computed against Earth's radius) into the\n * active body's true altitude. A no-op on Earth.\n *\n * @param earthAltitudeMeters Altitude in metres as MapLibre reports it\n * @returns Altitude in metres above the active body's datum, or null when the\n * input is not a usable number (no map, degenerate transform, globe edge case)\n */\nexport function scaleAltitudeToActiveBody(earthAltitudeMeters: number | null): number | null {\n if (earthAltitudeMeters === null || !Number.isFinite(earthAltitudeMeters)) return null;\n return earthAltitudeMeters * getActiveBodyRadiusRatio();\n}\n\n/**\n * Format a camera altitude for the status bar.\n *\n * Altitude spans metres (standing on a ridge) to tens of thousands of\n * kilometres (whole-globe view), so it borrows the scale bar's denomination\n * switching rather than printing \"12,742,000 m\". Metric crosses to km past\n * 1 km, imperial to miles past a mile, and nautical stays in nautical miles —\n * the same thresholds {@link scaleDenomination} gives the scale bar, so the two\n * always agree about which unit this view is being described in.\n */\nexport function formatCameraAltitude(meters: number, unit: MapScaleUnit): string {\n const { metersPerUnit, label } = scaleDenomination(Math.abs(meters), unit);\n const value = meters / metersPerUnit;\n // Sub-unit and single-digit values need a decimal to say anything; past that\n // the extra digit is noise on a number that moves with every zoom step.\n const digits = Math.abs(value) < 10 ? 1 : 0;\n return `${value.toLocaleString(undefined, {\n minimumFractionDigits: digits,\n maximumFractionDigits: digits,\n })} ${label}`;\n}\n","/**\n * The saved state of an ArcGIS I3S Building Scene Layer (issue #3341), shared\n * by the deck.gl overlay (`@geolibre/plugins`) and the globe\n * (`@geolibre/map`'s Cesium sync), so both draw the same sublayers.\n *\n * The plugins' `i3s-building.ts` parses the service's sublayer tree into this\n * shape and stores it as `metadata.i3sBuilding`.\n */\n\n/** One node of a Building Scene Layer's sublayer tree. */\nexport interface I3sBuildingNode {\n id: number;\n name: string;\n /** The I3S model name: `FullModel`, `Overview`, a discipline or category name. */\n modelName?: string;\n /** `group` for a discipline or folder; `3DObject` (or another type) for a drawable leaf. */\n layerType: string;\n visible: boolean;\n children?: I3sBuildingNode[];\n}\n\n/** What `metadata.i3sBuilding` holds for a Building Scene Layer. */\nexport interface I3sBuildingState {\n /** `overview` draws the Overview sublayer; `full` draws the visible Full Model leaves. */\n mode: \"overview\" | \"full\";\n /** The Overview sublayer's id, or `null` when the service has none. */\n overviewId: number | null;\n /** The Full Model's children (disciplines), or every top-level sublayer when there is no Full Model group. */\n disciplines: I3sBuildingNode[];\n}\n\n/** The drawable leaves under the given nodes whose whole branch is visible. */\nfunction visibleLeaves(nodes: readonly I3sBuildingNode[], out: number[]): number[] {\n for (const node of nodes) {\n if (!node.visible) continue;\n if (node.children?.length) visibleLeaves(node.children, out);\n else if (node.layerType === \"3DObject\") out.push(node.id);\n }\n return out;\n}\n\n/**\n * The sublayer ids to draw for a Building Scene Layer.\n *\n * @param state - The layer's `metadata.i3sBuilding`.\n * @returns The Overview id in Overview mode; otherwise every visible 3DObject leaf.\n */\nexport function visibleI3sBuildingSublayers(state: I3sBuildingState): number[] {\n if (state.mode === \"overview\" && state.overviewId !== null) return [state.overviewId];\n return visibleLeaves(state.disciplines, []);\n}\n\n/**\n * How far to lower an I3S scene layer onto a map or globe without terrain,\n * in metres (issue #3374). Shared by the 2D overlay and the globe so the rule\n * cannot drift between them.\n *\n * Scene layers store absolute elevations. Without terrain the ground is at\n * 0 m, so a layer whose reported lowest elevation (`fullExtent.zmin`) is above\n * 0 m is lowered by it; one that already reaches 0 m or below (a coastal city)\n * is left alone. With terrain the absolute heights are kept.\n *\n * @param baseElevation - The layer's `fullExtent.zmin`, if known.\n * @param terrain - Whether the map or globe draws 3D terrain.\n * @returns The offset (0 or negative).\n */\nexport function i3sGroundOffset(baseElevation: unknown, terrain: boolean): number {\n return !terrain &&\n typeof baseElevation === \"number\" &&\n Number.isFinite(baseElevation) &&\n baseElevation > 0\n ? -baseElevation\n : 0;\n}\n\n/**\n * Carry a saved Building selection onto a freshly parsed sublayer tree.\n *\n * Re-probing a layer refreshes its tree from the service. The saved mode and\n * each node's visibility are kept where the node still exists (matched by id);\n * a node the service added takes its parsed default, and one it removed is\n * dropped, so the renderer never asks for a sublayer that is gone.\n *\n * @param fresh - The tree parsed from the service now.\n * @param saved - The selection saved on the layer.\n * @returns The fresh tree with the saved choices applied.\n */\nexport function mergeI3sBuildingSelection(\n fresh: I3sBuildingState,\n saved: I3sBuildingState,\n): I3sBuildingState {\n const visibility = new Map<number, boolean>();\n const collect = (nodes: readonly I3sBuildingNode[]) => {\n for (const node of nodes) {\n visibility.set(node.id, node.visible);\n if (node.children) collect(node.children);\n }\n };\n collect(saved.disciplines);\n const apply = (nodes: readonly I3sBuildingNode[]): I3sBuildingNode[] =>\n nodes.map((node) => ({\n ...node,\n visible: visibility.get(node.id) ?? node.visible,\n ...(node.children ? { children: apply(node.children) } : {}),\n }));\n return {\n ...fresh,\n mode: saved.mode === \"overview\" && fresh.overviewId === null ? \"full\" : saved.mode,\n disciplines: apply(fresh.disciplines),\n };\n}\n","// A layers file holds a set of layer records that point at their data rather\n// than carry it: tile and service URLs, remote files, and absolute local paths\n// the desktop app can re-read. Project → Export → Export Layers writes one, and\n// the Startup setting adds its layers to every untitled workspace (at launch\n// and on File → New). Layers whose features exist only in memory are left out,\n// so the file stays small enough to keep a copy of in the desktop settings.\n\nimport { hasRestorableLayerSource, layerLocalPath } from \"./layer-library\";\nimport { createDefaultMapView, isSessionOnlyLayer, parseProject } from \"./project\";\nimport {\n LAYER_TYPES,\n PROJECT_VERSION,\n type GeoLibreLayer,\n type GeoLibreProject,\n type LayerGroup,\n} from \"./types\";\n\n/** `type` tag identifying a layers file. */\nexport const LAYERS_FILE_TYPE = \"geolibre-layers\";\n\n/** Format version written by {@link serializeLayersFile}. */\nexport const LAYERS_FILE_VERSION = 1;\n\n/**\n * Size ceiling for a layers file, in UTF-8 bytes. A file of references is a few\n * kilobytes per layer, and the Startup setting keeps a parsed copy in the\n * desktop settings (localStorage), so a file past this is refused rather than\n * crowding out the rest of the settings.\n */\nexport const MAX_LAYERS_FILE_BYTES = 1024 * 1024;\n\n/** The layers and the folders they sit in. */\nexport interface LayersFileContent {\n /** Layer records in store (bottom-to-top) order. */\n layers: GeoLibreLayer[];\n /** The folders the layers belong to, with their ancestors. */\n layerGroups: LayerGroup[];\n}\n\n/** The on-disk layers document. */\nexport interface LayersFile extends LayersFileContent {\n type: typeof LAYERS_FILE_TYPE;\n version: typeof LAYERS_FILE_VERSION;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isHttpUrl(value: unknown): value is string {\n if (typeof value !== \"string\") return false;\n try {\n const url = new URL(value.trim());\n return url.protocol === \"http:\" || url.protocol === \"https:\";\n } catch {\n return false;\n }\n}\n\n/** A `blob:` or `data:` URL is the data itself, not a reference to it. */\nfunction isInlineUrl(value: unknown): boolean {\n return typeof value === \"string\" && /^\\s*(blob|data):/i.test(value);\n}\n\nfunction hasInlineUrl(layer: GeoLibreLayer): boolean {\n const source = layer.source ?? {};\n const tiles = Array.isArray(source.tiles) ? source.tiles : [];\n return (\n isInlineUrl(source.url) ||\n isInlineUrl(source.data) ||\n tiles.some(isInlineUrl) ||\n isInlineUrl(layer.sourcePath) ||\n isInlineUrl((layer.metadata ?? {}).originalUrl)\n );\n}\n\n/**\n * Whether a layer (in its saved form) points at its data rather than carrying\n * it: it has a re-fetchable source, an http(s) source path, or an absolute\n * local path the desktop app re-reads, and holds no inline features.\n *\n * @param layer - A layer as `projectFromStore` writes it.\n * @returns True when the layer can go in a layers file.\n */\nexport function isReferencedLayer(layer: GeoLibreLayer): boolean {\n if (isSessionOnlyLayer(layer)) return false;\n if (layer.geojson) return false;\n const source = layer.source ?? {};\n const metadata = layer.metadata ?? {};\n if (source.data !== undefined && typeof source.data !== \"string\") return false;\n if (source.czmlData !== undefined) return false;\n if (metadata.embeddedGeoJSON !== undefined) return false;\n if (hasInlineUrl(layer)) return false;\n return (\n hasRestorableLayerSource(layer) ||\n layerLocalPath(layer) !== undefined ||\n isHttpUrl(layer.sourcePath)\n );\n}\n\n/** Folders the given layers sit in, plus every ancestor, in their original order. */\nfunction groupsForLayers(\n layers: readonly GeoLibreLayer[],\n groups: readonly LayerGroup[],\n): LayerGroup[] {\n const byId = new Map(groups.map((group) => [group.id, group]));\n const keep = new Set<string>();\n for (const layer of layers) {\n let id = layer.groupId;\n // The `keep` check also stops a hand-edited parent cycle.\n while (id && !keep.has(id) && byId.has(id)) {\n keep.add(id);\n id = byId.get(id)?.parentId;\n }\n }\n return groups.filter((group) => keep.has(group.id));\n}\n\n/**\n * Pick the layers of a saved project that can go in a layers file.\n *\n * @param project - A project as `projectFromStore` writes it. A host that can\n * re-fetch a layer's features (a GeoJSON layer loaded from a URL) strips its\n * `geojson` first, so the layer counts as a reference here.\n * @returns The file content, and the names of the layers left out because they\n * carry their data inline.\n */\nexport function extractLayersFileContent(\n project: Pick<GeoLibreProject, \"layers\" | \"layerGroups\">,\n): { content: LayersFileContent; skipped: string[] } {\n const layers: GeoLibreLayer[] = [];\n const skipped: string[] = [];\n for (const layer of project.layers) {\n if (isReferencedLayer(layer)) layers.push(layer);\n else if (!isSessionOnlyLayer(layer)) skipped.push(layer.name);\n }\n return {\n content: { layers, layerGroups: groupsForLayers(layers, project.layerGroups ?? []) },\n skipped,\n };\n}\n\n/**\n * Serialize layers into the JSON written by Export Layers.\n *\n * @param content - The layers and their folders.\n * @returns Pretty-printed file content.\n */\nexport function serializeLayersFile(content: LayersFileContent): string {\n const file: LayersFile = {\n type: LAYERS_FILE_TYPE,\n version: LAYERS_FILE_VERSION,\n layers: [...content.layers],\n layerGroups: [...content.layerGroups],\n };\n return JSON.stringify(file, null, 2);\n}\n\n/**\n * Coerce untrusted layers (a hand-edited file, or the copy kept in settings)\n * into clean ones. Each layer goes through the same normalization a project\n * file's layers do; one without an id, a name, a known type, or a reference to\n * its data is dropped, as is a repeated id.\n *\n * @param value - An object with `layers` and optional `layerGroups` arrays.\n * @returns The usable layers and the folders they sit in.\n */\nexport function normalizeLayersFileContent(value: unknown): LayersFileContent {\n const empty: LayersFileContent = { layers: [], layerGroups: [] };\n if (!isPlainObject(value) || !Array.isArray(value.layers)) return empty;\n try {\n return normalizeLayers(value.layers, value.layerGroups);\n } catch {\n // A corrupt copy in settings must not break loading the rest of them.\n return empty;\n }\n}\n\n/**\n * The body of {@link normalizeLayersFileContent}, which catches what it throws.\n *\n * @param rawLayers - The raw `layers` array.\n * @param rawGroups - The raw `layerGroups` value.\n * @returns The usable layers and the folders they sit in, or nothing when the\n * layers are past {@link MAX_LAYERS_FILE_BYTES}.\n */\nfunction normalizeLayers(rawLayers: unknown[], rawGroups: unknown): LayersFileContent {\n const seen = new Set<string>();\n const candidates = rawLayers.filter((layer): layer is Record<string, unknown> => {\n if (!isPlainObject(layer)) return false;\n if (typeof layer.id !== \"string\" || !layer.id.trim() || seen.has(layer.id)) return false;\n if (typeof layer.name !== \"string\") return false;\n if (!(LAYER_TYPES as readonly unknown[]).includes(layer.type)) return false;\n if (!isPlainObject(layer.source)) return false;\n seen.add(layer.id);\n return true;\n });\n // Borrow the project parser rather than duplicate its per-layer rules: the\n // layers below are exactly what a project file with only these layers holds.\n // The round trip through JSON also bounds the size of what is kept.\n const json = JSON.stringify({\n version: PROJECT_VERSION,\n name: \"layers\",\n mapView: createDefaultMapView(),\n layers: candidates.map((layer) => ({\n ...layer,\n metadata: isPlainObject(layer.metadata) ? layer.metadata : {},\n style: isPlainObject(layer.style) ? layer.style : {},\n })),\n layerGroups: Array.isArray(rawGroups) ? rawGroups : [],\n });\n if (new TextEncoder().encode(json).length > MAX_LAYERS_FILE_BYTES) {\n return { layers: [], layerGroups: [] };\n }\n const parsed = parseProject(json);\n const layers = parsed.layers.filter(isReferencedLayer);\n return { layers, layerGroups: groupsForLayers(layers, parsed.layerGroups ?? []) };\n}\n\n/**\n * Parse a file written by {@link serializeLayersFile}.\n *\n * @param json - The file content.\n * @returns The normalized layers and folders.\n * @throws Error when the content is too large, is not a layers file, comes\n * from a newer format version, or holds no usable layers.\n */\nexport function parseLayersFile(json: string): LayersFileContent {\n if (new TextEncoder().encode(json).length > MAX_LAYERS_FILE_BYTES) {\n throw new Error(\"The layers file is too large. Keep it under 1 MB.\");\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n throw new Error(\"Not a valid layers file (invalid JSON).\");\n }\n if (!isPlainObject(parsed) || parsed.type !== LAYERS_FILE_TYPE) {\n throw new Error(\"Not a valid layers file.\");\n }\n // Refuse a newer format rather than misread it with this version's rules.\n if (parsed.version !== LAYERS_FILE_VERSION) {\n throw new Error(\"Unsupported layers file version.\");\n }\n const content = normalizeLayersFileContent(parsed);\n if (content.layers.length === 0) {\n throw new Error(\"The layers file holds no usable layers.\");\n }\n return content;\n}\n\n/**\n * Add a layers file's layers on top of a project's own, keeping the project's\n * layers where an id repeats.\n *\n * @param project - The project to add to (usually a new, empty one).\n * @param content - The layers and folders to add.\n * @returns A new project with the layers added.\n */\nexport function addLayersToProject(\n project: GeoLibreProject,\n content: LayersFileContent,\n): GeoLibreProject {\n const layerIds = new Set(project.layers.map((layer) => layer.id));\n const groups = project.layerGroups ?? [];\n const groupIds = new Set(groups.map((group) => group.id));\n const layers = content.layers.filter((layer) => !layerIds.has(layer.id));\n const layerGroups = content.layerGroups.filter((group) => !groupIds.has(group.id));\n return {\n ...project,\n layers: [...project.layers, ...structuredClone(layers)],\n layerGroups: [...groups, ...structuredClone(layerGroups)],\n };\n}\n","/**\n * Trim a float sample to something readable. A 32-bit raster value decoded to a\n * JS double prints all 17 digits of its binary representation\n * (`48.11851119995117`), which is noise past the sensor's precision — six\n * significant digits is more than any COG carries. Integers are left alone so a\n * classification code is never shown in exponential form.\n *\n * @param value A decoded raster sample.\n * @returns The sample rendered for display.\n */\nexport function formatPixelValue(value: number): string {\n if (!Number.isFinite(value)) return String(value);\n if (Number.isInteger(value)) return String(value);\n return String(Number(value.toPrecision(6)));\n}\n","/**\n * Amazon S3 (and S3-compatible) object URLs: parsing, public HTTPS mapping,\n * SigV4 query-string presigning, and the registry the app uses to plug\n * credentialed signing into every reader.\n *\n * Readers in GeoLibre take a plain HTTPS URL (geotiff.js, cog-tiler-wasm,\n * pmtiles, DuckDB-WASM's HTTP reader, the vector control). A presigned URL is\n * the one form of authenticated S3 access all of them accept without a custom\n * fetch hook, so private objects are read by signing the URL first and handing\n * the reader the result. Layers keep the unsigned source URL (`s3://…` or the\n * plain object URL); the signed one is minted again on every load, so a saved\n * project never carries a signature or a session token.\n */\n\n/** A bucket and object key, with the endpoint the URL named, if any. */\nexport interface S3ObjectLocation {\n bucket: string;\n /** The raw (decoded) object key, without a leading slash. */\n key: string;\n /** The region an AWS HTTPS URL named in its host, when it named one. */\n region?: string;\n}\n\n/** Where and how a bucket is addressed. */\nexport interface S3EndpointConfig {\n /** Signing region, e.g. `us-east-1`. S3-compatible stores often accept `auto`. */\n region: string;\n /**\n * Custom endpoint origin for S3-compatible stores (MinIO, Cloudflare R2,\n * Wasabi, Ceph), e.g. `https://minio.example.com:9000`. Empty for AWS.\n */\n endpoint?: string;\n /** Address the bucket in the path instead of the host name. */\n pathStyle?: boolean;\n}\n\n/** AWS credentials used to sign a request. */\nexport interface S3Credentials {\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken?: string;\n /** Epoch milliseconds at which temporary credentials stop working. */\n expiresAt?: number;\n}\n\n/** A presigned URL and the moment it stops working. */\nexport interface S3SignedUrl {\n href: string;\n /** Epoch milliseconds. */\n expiresAt: number;\n}\n\n/** SigV4 allows a presigned URL to live at most seven days. */\nexport const S3_MAX_PRESIGN_SECONDS = 7 * 24 * 60 * 60;\n\nconst S3_SCHEME = /^s3a?:\\/\\//i;\n// bucket.s3.amazonaws.com, bucket.s3.us-west-2.amazonaws.com,\n// bucket.s3-us-west-2.amazonaws.com, bucket.s3.dualstack.us-west-2.amazonaws.com\nconst VIRTUAL_HOST = /^(.+)\\.s3(?:[.-](?:dualstack\\.)?([a-z0-9-]+))?\\.amazonaws\\.com(?:\\.cn)?$/i;\n// s3.amazonaws.com, s3.us-west-2.amazonaws.com, s3-us-west-2.amazonaws.com\nconst PATH_HOST = /^s3(?:[.-](?:dualstack\\.)?([a-z0-9-]+))?\\.amazonaws\\.com(?:\\.cn)?$/i;\n\nfunction regionFromHost(value: string | undefined): string | undefined {\n // `s3-external-1` and `s3.dualstack` carry no region of their own.\n if (!value || value === \"external-1\" || value === \"dualstack\") return undefined;\n return value.toLowerCase();\n}\n\n/** Static-website endpoints serve HTML, not the REST API a signature targets. */\nfunction isWebsiteRegion(value: string | undefined): boolean {\n return Boolean(value && /^website/i.test(value));\n}\n\nfunction decodeKey(path: string): string {\n try {\n return decodeURIComponent(path);\n } catch {\n return path;\n }\n}\n\n/**\n * Parses an `s3://bucket/key` URI or an AWS S3 HTTPS object URL\n * (virtual-hosted or path-style, any region) into its bucket and key.\n * Query strings and fragments are ignored, so a presigned URL parses to the\n * object it signs.\n *\n * @param url The URL to parse.\n * @returns The bucket and key, or null when the URL is not an S3 object URL.\n */\nexport function parseS3Url(url: string): S3ObjectLocation | null {\n const trimmed = url.trim();\n if (S3_SCHEME.test(trimmed)) {\n const rest = trimmed.replace(S3_SCHEME, \"\").split(/[?#]/)[0];\n const slash = rest.indexOf(\"/\");\n const bucket = slash === -1 ? rest : rest.slice(0, slash);\n const key = slash === -1 ? \"\" : rest.slice(slash + 1);\n return isSafeBucketName(bucket) ? { bucket, key } : null;\n }\n let parsed: URL;\n try {\n parsed = new URL(trimmed);\n } catch {\n return null;\n }\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") return null;\n const host = parsed.hostname.toLowerCase();\n const path = parsed.pathname.replace(/^\\//, \"\");\n const virtual = host.match(VIRTUAL_HOST);\n if (virtual && !PATH_HOST.test(host)) {\n if (isWebsiteRegion(virtual[2])) return null;\n const region = regionFromHost(virtual[2]);\n return { bucket: virtual[1], key: decodeKey(path), ...(region ? { region } : {}) };\n }\n const pathStyle = host.match(PATH_HOST);\n if (pathStyle) {\n if (isWebsiteRegion(pathStyle[1])) return null;\n const slash = path.indexOf(\"/\");\n const bucket = slash === -1 ? path : path.slice(0, slash);\n if (!bucket) return null;\n const region = regionFromHost(pathStyle[1]);\n return {\n bucket: decodeKey(bucket),\n key: slash === -1 ? \"\" : decodeKey(path.slice(slash + 1)),\n ...(region ? { region } : {}),\n };\n }\n return null;\n}\n\n/**\n * Bucket names are letters, digits, dots, dashes, and underscores (the last\n * two outside AWS's own rules, for S3-compatible stores). Anything else, a\n * backslash or `@` say, could move a virtual-hosted URL's authority to\n * another host before a signature is attached, so it is refused outright.\n */\nconst SAFE_BUCKET_NAME = /^[A-Za-z0-9._-]{1,255}$/;\n\nfunction isSafeBucketName(bucket: string): boolean {\n return SAFE_BUCKET_NAME.test(bucket);\n}\n\n/** Whether `url` is an `s3://` URI (as opposed to an HTTPS object URL). */\nexport function isS3Uri(url: string): boolean {\n return S3_SCHEME.test(url.trim());\n}\n\n/** Formats a location as an `s3://bucket/key` URI. */\nexport function formatS3Uri(location: S3ObjectLocation): string {\n return `s3://${location.bucket}/${location.key}`;\n}\n\n/**\n * URI-encodes a key the way SigV4 canonicalizes an S3 path: every byte except\n * the unreserved set and `/` is percent-encoded, once.\n */\nexport function encodeS3Key(key: string): string {\n return key\n .split(\"/\")\n .map((segment) =>\n encodeURIComponent(segment).replace(\n /[!'()*]/g,\n (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`,\n ),\n )\n .join(\"/\");\n}\n\n/** A custom endpoint's path, without a trailing slash (\"\" for none). */\nfunction endpointPathPrefix(base: URL): string {\n return base.pathname.replace(/\\/+$/, \"\");\n}\n\n/**\n * Builds the HTTPS URL of an object.\n *\n * AWS buckets whose name contains a dot are addressed path-style, since the\n * `*.s3.amazonaws.com` wildcard certificate covers one label only.\n *\n * @param location The bucket and key.\n * @param config The endpoint; without one the global AWS endpoint is used.\n * @returns The unsigned object URL.\n */\nexport function s3ObjectHttpsUrl(\n location: S3ObjectLocation,\n config: Partial<S3EndpointConfig> = {},\n): string {\n if (location.bucket && !isSafeBucketName(location.bucket)) {\n throw new Error(`Invalid S3 bucket name: ${location.bucket}`);\n }\n // Every URL parser drops \".\" and \"..\" path segments (encoded or not), so such\n // a key would address another object, or with path style another bucket,\n // and a signer would sign that read with the covered bucket's credentials.\n if (location.key.split(\"/\").some((segment) => segment === \".\" || segment === \"..\")) {\n throw new Error(\n `S3 object key has a \".\" or \"..\" segment a URL cannot address: ${location.key}`,\n );\n }\n const key = encodeS3Key(location.key);\n const endpoint = config.endpoint?.trim().replace(/\\/+$/, \"\");\n // No bucket addresses the service itself (ListBuckets).\n if (!location.bucket) {\n if (endpoint) {\n const base = new URL(endpoint.includes(\"://\") ? endpoint : `https://${endpoint}`);\n return `${base.protocol}//${base.host}${endpointPathPrefix(base)}/`;\n }\n const serviceRegion = config.region || location.region;\n return serviceRegion && serviceRegion !== \"us-east-1\"\n ? `https://s3.${serviceRegion}.amazonaws.com/`\n : \"https://s3.amazonaws.com/\";\n }\n if (endpoint) {\n const base = new URL(endpoint.includes(\"://\") ? endpoint : `https://${endpoint}`);\n // An S3-compatible gateway may sit under a path (`https://host/s3`).\n const prefix = endpointPathPrefix(base);\n return config.pathStyle\n ? `${base.protocol}//${base.host}${prefix}/${encodeS3Key(location.bucket)}/${key}`\n : `${base.protocol}//${location.bucket}.${base.host}${prefix}/${key}`;\n }\n const region = config.region || location.region;\n const host = region && region !== \"us-east-1\" ? `s3.${region}.amazonaws.com` : \"s3.amazonaws.com\";\n if (config.pathStyle || location.bucket.includes(\".\")) {\n return `https://${host}/${encodeS3Key(location.bucket)}/${key}`;\n }\n return `https://${location.bucket}.${host}/${key}`;\n}\n\n// ---------------------------------------------------------------------------\n// SigV4 query-string signing\n// ---------------------------------------------------------------------------\n\nconst encoder = new TextEncoder();\n\nfunction toHex(buffer: ArrayBuffer): string {\n return Array.from(new Uint8Array(buffer), (byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nasync function sha256Hex(value: string): Promise<string> {\n return toHex(await crypto.subtle.digest(\"SHA-256\", encoder.encode(value)));\n}\n\nasync function hmac(key: ArrayBuffer | Uint8Array, value: string): Promise<ArrayBuffer> {\n const cryptoKey = await crypto.subtle.importKey(\n \"raw\",\n key as BufferSource,\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n return crypto.subtle.sign(\"HMAC\", cryptoKey, encoder.encode(value));\n}\n\n/** RFC 3986 encoding, as SigV4 requires for query names and values. */\nfunction encodeRfc3986(value: string): string {\n return encodeURIComponent(value).replace(\n /[!'()*]/g,\n (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`,\n );\n}\n\nfunction amzDate(date: Date): { stamp: string; day: string } {\n const stamp = date\n .toISOString()\n .replace(/[-:]/g, \"\")\n .replace(/\\.\\d{3}/, \"\");\n return { stamp, day: stamp.slice(0, 8) };\n}\n\nexport interface PresignS3Options {\n /** The unsigned HTTPS object URL (see {@link s3ObjectHttpsUrl}). */\n url: string;\n region: string;\n credentials: S3Credentials;\n /** Lifetime in seconds, clamped to 1…{@link S3_MAX_PRESIGN_SECONDS}. */\n expiresIn: number;\n /** HTTP method the URL authorizes. Defaults to GET. */\n method?: string;\n /** Signing time; defaults to now. Exposed for tests. */\n now?: Date;\n}\n\n/**\n * Presigns an S3 request with AWS Signature Version 4 in the query string.\n * Runs on WebCrypto, so it works in the browser, the Tauri webview, and Node.\n *\n * The URL authorizes exactly one method: a GET URL cannot answer a HEAD, so\n * readers that probe with HEAD need a GET range request instead.\n *\n * @param options The object URL, region, credentials, and lifetime.\n * @returns The presigned URL.\n */\nexport async function presignS3Url(options: PresignS3Options): Promise<string> {\n const { credentials, region } = options;\n const method = (options.method ?? \"GET\").toUpperCase();\n const url = new URL(options.url);\n const { stamp, day } = amzDate(options.now ?? new Date());\n const scope = `${day}/${region}/s3/aws4_request`;\n const expiresIn = Math.max(1, Math.min(S3_MAX_PRESIGN_SECONDS, Math.floor(options.expiresIn)));\n\n const params: Array<[string, string]> = [];\n url.searchParams.forEach((value, name) => {\n if (!name.toLowerCase().startsWith(\"x-amz-\")) params.push([name, value]);\n });\n params.push(\n [\"X-Amz-Algorithm\", \"AWS4-HMAC-SHA256\"],\n [\"X-Amz-Credential\", `${credentials.accessKeyId}/${scope}`],\n [\"X-Amz-Date\", stamp],\n [\"X-Amz-Expires\", String(expiresIn)],\n [\"X-Amz-SignedHeaders\", \"host\"],\n );\n if (credentials.sessionToken) params.push([\"X-Amz-Security-Token\", credentials.sessionToken]);\n const canonicalQuery = params\n .map(([name, value]) => [encodeRfc3986(name), encodeRfc3986(value)] as const)\n .sort(([a, av], [b, bv]) => (a < b ? -1 : a > b ? 1 : av < bv ? -1 : av > bv ? 1 : 0))\n .map(([name, value]) => `${name}=${value}`)\n .join(\"&\");\n\n // The URL constructor already percent-encoded the path; S3 signs it as sent.\n const canonicalPath = url.pathname || \"/\";\n const canonicalRequest = [\n method,\n canonicalPath,\n canonicalQuery,\n `host:${url.host}\\n`,\n \"host\",\n \"UNSIGNED-PAYLOAD\",\n ].join(\"\\n\");\n const stringToSign = [\"AWS4-HMAC-SHA256\", stamp, scope, await sha256Hex(canonicalRequest)].join(\n \"\\n\",\n );\n\n const dateKey = await hmac(encoder.encode(`AWS4${credentials.secretAccessKey}`), day);\n const regionKey = await hmac(dateKey, region);\n const serviceKey = await hmac(regionKey, \"s3\");\n const signingKey = await hmac(serviceKey, \"aws4_request\");\n const signature = toHex(await hmac(signingKey, stringToSign));\n\n return `${url.origin}${canonicalPath}?${canonicalQuery}&X-Amz-Signature=${signature}`;\n}\n\n// ---------------------------------------------------------------------------\n// Signer registry\n// ---------------------------------------------------------------------------\n\n/** One signed S3 GET: an object read, or a bucket-level call such as ListObjectsV2. */\nexport interface S3PresignRequest {\n bucket: string;\n /** Object key; empty for a bucket-level request. */\n key: string;\n /** Extra query parameters to sign (e.g. `list-type=2`). */\n query?: Record<string, string>;\n /** Region an AWS URL named, used ahead of the connection's default. */\n region?: string;\n /**\n * Sign with this connection instead of the one matched by bucket name (the\n * S3 browser listing a chosen connection's buckets, where `bucket` is empty).\n */\n connectionId?: string;\n}\n\n/** A configured connection, as the S3 browser lists it. No secrets. */\nexport interface S3ConnectionSummary {\n id: string;\n name: string;\n /** Bucket patterns the connection covers; empty means every bucket. */\n buckets: string[];\n}\n\n/**\n * Presigns S3 requests with the app's configured credentials. `presign`\n * resolves to null when no configured connection covers the bucket, in which\n * case the object is read anonymously.\n */\nexport interface S3UrlSigner {\n /** Whether a configured connection covers `bucket`. Must be synchronous. */\n covers(bucket: string): boolean;\n /** Presigns a GET; rejects when the connection's credentials cannot be resolved. */\n presign(request: S3PresignRequest, signal?: AbortSignal): Promise<S3SignedUrl | null>;\n /** The configured connections, for pickers. */\n connections(): S3ConnectionSummary[];\n /**\n * Fetches a presigned bucket-level URL (listing). The desktop app routes it\n * through its native HTTP client, which needs no bucket CORS rule.\n */\n fetchText?(url: string, signal?: AbortSignal): Promise<{ status: number; body: string }>;\n /**\n * Drops the cached credentials and signed URLs of the connection that\n * serves `target`, so the next `presign` resolves them afresh. Called when S3\n * refuses a request as expired.\n */\n invalidateCredentials?(target: { bucket?: string; connectionId?: string }): void;\n /** The persisted location (`s3://bucket/prefix/`) the S3 browser opens at, or \"\". */\n defaultLocation?(): string;\n /** Persists the S3 browser's default location; \"\" clears it. */\n setDefaultLocation?(location: string): void;\n}\n\nlet activeSigner: S3UrlSigner | null = null;\n/** Presigned URL → the unsigned URL it was minted for. Bounded. */\nconst sourceBySignedHref = new Map<string, string>();\nconst SIGNED_SOURCE_LIMIT = 500;\n\n/** Installs (or with null, removes) the app's S3 signer. */\nexport function registerS3UrlSigner(signer: S3UrlSigner | null): void {\n activeSigner = signer;\n}\n\n/**\n * Whether reading `url` needs the S3 signer: it names an S3 object in a bucket\n * a configured connection covers.\n */\nexport function isCredentialedS3Url(url: string): boolean {\n if (!activeSigner) return false;\n const location = parseS3Url(url);\n return Boolean(location && activeSigner.covers(location.bucket));\n}\n\n/** Records that `signedHref` reads `sourceUrl`, so a layer can keep the latter. */\nfunction rememberSignedSource(signedHref: string, sourceUrl: string): void {\n // Least recently used goes first: deleting before setting moves a URL that\n // is signed (handed out) again to the back of the eviction order.\n sourceBySignedHref.delete(signedHref);\n if (sourceBySignedHref.size >= SIGNED_SOURCE_LIMIT) {\n const oldest = sourceBySignedHref.keys().next().value;\n if (oldest !== undefined) sourceBySignedHref.delete(oldest);\n }\n sourceBySignedHref.set(signedHref, sourceUrl);\n}\n\n/** Removes the SigV4 query parameters (credential, token, signature) from a URL. */\nfunction stripPresignParameters(url: string): string {\n try {\n const parsed = new URL(url);\n for (const name of [...parsed.searchParams.keys()]) {\n if (name.toLowerCase().startsWith(\"x-amz-\")) parsed.searchParams.delete(name);\n }\n return parsed.href;\n } catch {\n return url;\n }\n}\n\n/**\n * The unsigned URL a presigned one was minted for by\n * {@link resolveReadableUrl}, or `url` itself. Layer sync runs every URL a\n * control reports through this, so the store (and a saved project) keeps the\n * `s3://` source rather than an expiring signature.\n */\nexport function unsignedSourceUrl(url: string): string;\nexport function unsignedSourceUrl(url: string | undefined): string | undefined;\nexport function unsignedSourceUrl(url: string | undefined): string | undefined {\n if (url === undefined) return undefined;\n const known = sourceBySignedHref.get(url);\n if (known !== undefined) return known;\n // A presigned URL whose mapping was evicted must still never reach a saved\n // project: fall back to the object it signs, without the signature.\n if (!/[?&]x-amz-signature=/i.test(url)) return url;\n const location = parseS3Url(url);\n // An `s3://` URI cannot carry a key with `?` or `#` (they would be read as a\n // query or fragment), so such keys keep the object URL form.\n return location && !/[?#]/.test(location.key)\n ? formatS3Uri(location)\n : stripPresignParameters(url);\n}\n\n/**\n * Resolves a data URL into one a plain HTTP reader can fetch:\n *\n * - an S3 object in a bucket a configured connection covers → a presigned URL;\n * - any other `s3://` URI → the public HTTPS object URL (anonymous read);\n * - anything else → unchanged.\n *\n * @param url The URL a layer or query names.\n * @param signal Aborts a pending credential lookup.\n * @returns The URL to hand the reader.\n * @throws When the bucket is covered but its credentials cannot be resolved.\n */\nexport async function resolveReadableUrl(url: string, signal?: AbortSignal): Promise<string> {\n const location = parseS3Url(url);\n if (!location) return url;\n if (activeSigner?.covers(location.bucket)) {\n const signed = await activeSigner.presign(\n { bucket: location.bucket, key: location.key, region: location.region },\n signal,\n );\n if (signed) {\n rememberSignedSource(signed.href, url);\n return signed.href;\n }\n }\n return isS3Uri(url) ? s3ObjectHttpsUrl(location) : url;\n}\n\n/** The installed signer, for the S3 browser. */\nexport function getS3UrlSigner(): S3UrlSigner | null {\n return activeSigner;\n}\n\n// ---------------------------------------------------------------------------\n// ListObjectsV2\n// ---------------------------------------------------------------------------\n\n/** One page of a ListObjectsV2 response. */\nexport interface S3ListPage {\n /** Common prefixes (\"folders\") under the requested prefix. */\n prefixes: string[];\n objects: Array<{ key: string; size: number; lastModified?: string }>;\n /** Present when more results follow. */\n nextContinuationToken?: string;\n}\n\nfunction decodeXmlText(value: string): string {\n return value\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/&#(\\d+);/g, (_, code: string) => String.fromCodePoint(Number(code)))\n .replace(/&#x([0-9a-f]+);/gi, (_, code: string) => String.fromCodePoint(parseInt(code, 16)))\n .replace(/&/g, \"&\");\n}\n\nfunction xmlTag(block: string, tag: string): string | undefined {\n const match = block.match(new RegExp(`<${tag}>([\\\\s\\\\S]*?)</${tag}>`));\n return match ? decodeXmlText(match[1]) : undefined;\n}\n\nfunction xmlBlocks(xml: string, tag: string): string[] {\n return Array.from(xml.matchAll(new RegExp(`<${tag}>([\\\\s\\\\S]*?)</${tag}>`, \"g\")), (m) => m[1]);\n}\n\n/**\n * Parses a ListObjectsV2 XML body. S3's listing XML is flat and predictable,\n * so this reads it with patterns rather than an XML DOM, which keeps it usable\n * in workers and Node tests.\n */\nexport function parseS3ListObjects(xml: string): S3ListPage {\n const prefixes = xmlBlocks(xml, \"CommonPrefixes\")\n .map((block) => xmlTag(block, \"Prefix\"))\n .filter((value): value is string => Boolean(value));\n const objects = xmlBlocks(xml, \"Contents\").flatMap((block) => {\n const key = xmlTag(block, \"Key\");\n if (key === undefined) return [];\n const lastModified = xmlTag(block, \"LastModified\");\n return [\n { key, size: Number(xmlTag(block, \"Size\") ?? 0), ...(lastModified ? { lastModified } : {}) },\n ];\n });\n const truncated = xmlTag(xml, \"IsTruncated\") === \"true\";\n const token = xmlTag(xml, \"NextContinuationToken\");\n return { prefixes, objects, ...(truncated && token ? { nextContinuationToken: token } : {}) };\n}\n\n/** The error code and message of an S3 XML error body, when it is one. */\nexport function parseS3Error(\n xml: string,\n): { code: string; message: string; region?: string } | null {\n const block = xmlBlocks(xml, \"Error\")[0];\n if (block === undefined) return null;\n // A wrong-region signature names the region; an unsigned request to the\n // wrong endpoint (301 PermanentRedirect) names the right endpoint instead.\n const endpoint = xmlTag(block, \"Endpoint\");\n const region =\n xmlTag(block, \"Region\") ?? (endpoint ? parseS3Url(`https://${endpoint}/`)?.region : undefined);\n return {\n code: xmlTag(block, \"Code\") ?? \"Error\",\n message: xmlTag(block, \"Message\") ?? \"\",\n ...(region ? { region } : {}),\n };\n}\n\n/** Bucket names from a ListBuckets (`GET /` on the service) XML body. */\nexport function parseS3BucketList(xml: string): string[] {\n return xmlBlocks(xml, \"Bucket\")\n .map((block) => xmlTag(block, \"Name\"))\n .filter((name): name is string => Boolean(name));\n}\n\n// ---------------------------------------------------------------------------\n// CORS diagnosis\n// ---------------------------------------------------------------------------\n\n/**\n * How browsers word a request they blocked or could not complete, plus the\n * readers that already guess at CORS themselves (maplibre-gl-lidar's message\n * tells the user to use a file picker, which is wrong for a bucket).\n */\nconst NETWORK_FAILURE =\n /failed to fetch|networkerror|load failed|network request failed|err_failed|cross-origin|\\bcors\\b/i;\n\nfunction errorText(error: unknown): string {\n const parts: string[] = [];\n let current: unknown = error;\n for (let depth = 0; current && depth < 4; depth += 1) {\n parts.push(current instanceof Error ? `${current.name}: ${current.message}` : String(current));\n current = current instanceof Error ? (current as Error & { cause?: unknown }).cause : null;\n }\n return parts.join(\" | \");\n}\n\n/** A read the bucket's CORS configuration blocked. */\nexport class S3CorsError extends Error {\n readonly bucket: string;\n readonly origin: string;\n\n constructor(message: string, bucket: string, origin: string, cause: unknown) {\n super(message, { cause });\n this.name = \"S3CorsError\";\n this.bucket = bucket;\n this.origin = origin;\n }\n}\n\n/** Translates `key`, falling back to `defaultValue` (an app API's `translate`). */\nexport type S3ErrorTranslate = (\n key: string,\n defaultValue: string,\n params?: Record<string, string | number>,\n) => string;\n\n/**\n * Explains why reading an S3 object failed when the browser only said\n * \"Failed to fetch\". A browser reports a request the bucket's CORS rules\n * blocked exactly like a network outage, so this repeats the request twice:\n * normally, where success means the failure was transient; then in `no-cors`\n * mode, which CORS cannot block. Only when the first fails and the second gets\n * an answer is the bucket's CORS configuration the cause.\n *\n * @param url The `s3://` URI or object URL that was being read.\n * @param error What the reader threw.\n * @param translate Turns the message into the UI language.\n * @returns An {@link S3CorsError} naming the bucket and the origin to allow,\n * or `error` unchanged when it is not a CORS refusal of an S3 read.\n */\nexport async function explainS3ReadError(\n url: string,\n error: unknown,\n translate?: S3ErrorTranslate,\n): Promise<unknown> {\n const location = parseS3Url(url);\n if (!location || error instanceof S3CorsError) return error;\n if (!NETWORK_FAILURE.test(errorText(error))) return error;\n if (typeof fetch !== \"function\" || typeof window === \"undefined\") return error;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 10_000);\n try {\n const href = await resolveReadableUrl(url);\n const probe = { signal: controller.signal, cache: \"no-store\" as const };\n // A normal (CORS) read first: if that works now, the failure was transient\n // and CORS is not the cause. The body is never needed, so each request is\n // cut off once its headers arrive.\n const corsAllowed = await fetch(href, probe).then(\n () => true,\n () => false,\n );\n if (corsAllowed) return error;\n // Blocked under CORS, yet S3 answers a no-cors request, which CORS cannot\n // block: the bucket's CORS configuration is what refuses this origin.\n await fetch(href, { ...probe, mode: \"no-cors\" });\n } catch {\n // Unreachable under no-cors too: a real network failure, not CORS.\n return error;\n } finally {\n controller.abort();\n clearTimeout(timer);\n }\n const origin = window.location.origin;\n const fallback =\n `The bucket \"${location.bucket}\" does not allow reads from ${origin}: its CORS configuration ` +\n \"is missing or does not list this origin. Add a CORS rule that allows GET and HEAD from this \" +\n \"origin and exposes Content-Length, Content-Range, and ETag (see Settings > Cloud Storage).\";\n const message = translate\n ? translate(\"s3Browser.corsError\", fallback, { bucket: location.bucket, origin })\n : fallback;\n return new S3CorsError(message, location.bucket, origin, error);\n}\n","import type { StoryMap } from \"./types\";\n\nconst SAMPLE_ASSET_BASE = \"https://opengeos.org/maplibre-gl-storymaps/assets\";\n\n/**\n * Build a ready-to-play sample story map.\n *\n * Mirrors the chapters of the `opengeos/maplibre-gl-storymaps` demo (five world\n * cities) so users can explore the feature instantly without authoring content.\n * A fresh object is returned on each call so callers can mutate it freely.\n *\n * @returns A populated {@link StoryMap} with five sample chapters.\n */\nexport function createSampleStoryMap(): StoryMap {\n return {\n title: \"A Tour of Five Cities\",\n subtitle: \"A scrollytelling map experience built with GeoLibre\",\n byline: \"By GeoLibre\",\n footer:\n 'Source: Wikipedia. Built with <a href=\"https://github.com/opengeos/GeoLibre\" target=\"_blank\">GeoLibre</a>, inspired by <a href=\"https://github.com/opengeos/maplibre-gl-storymaps\" target=\"_blank\">MapLibre Storytelling</a>.',\n theme: \"dark\",\n showMarkers: true,\n markerColor: \"#3fb1ce\",\n inset: true,\n insetPosition: \"bottom-left\",\n hideChapterNav: false,\n startSlide: \"none\",\n endSlide: \"none\",\n chapters: [\n {\n id: \"sample-san-francisco\",\n title: \"San Francisco, California\",\n image: `${SAMPLE_ASSET_BASE}/san-francisco.jpg`,\n description:\n \"San Francisco, a hilly city on the tip of a peninsula surrounded by the Pacific Ocean and San Francisco Bay, is known for its year-round fog, iconic Golden Gate Bridge, cable cars and colorful Victorian houses. <br><br>The city is also known for its vibrant tech industry, diverse neighborhoods, and rich cultural scene.\",\n alignment: \"left\",\n hidden: false,\n location: {\n center: [-122.4194, 37.7749],\n zoom: 11,\n pitch: 45,\n bearing: 0,\n },\n mapAnimation: \"flyTo\",\n rotateAnimation: false,\n onChapterEnter: [],\n onChapterExit: [],\n },\n {\n id: \"sample-new-york\",\n title: \"New York City, New York\",\n image: `${SAMPLE_ASSET_BASE}/new-york.jpg`,\n description:\n \"New York City comprises 5 boroughs sitting where the Hudson River meets the Atlantic Ocean. At its core is Manhattan, a densely populated borough that's among the world's major commercial, financial and cultural centers. <br><br>Its iconic sites include skyscrapers such as the Empire State Building and sprawling Central Park.\",\n alignment: \"right\",\n hidden: false,\n location: {\n center: [-74.006, 40.7128],\n zoom: 11,\n pitch: 60,\n bearing: -43.2,\n },\n mapAnimation: \"flyTo\",\n rotateAnimation: true,\n onChapterEnter: [],\n onChapterExit: [],\n },\n {\n id: \"sample-tokyo\",\n title: \"Tokyo, Japan\",\n image: `${SAMPLE_ASSET_BASE}/tokyo.jpg`,\n description:\n \"Tokyo, Japan's busy capital, mixes the ultramodern and the traditional, from neon-lit skyscrapers to historic temples. The opulent Meiji Shinto Shrine is known for its towering gate and surrounding woods. <br><br>The Imperial Palace sits amid large public gardens. The city's many museums offer exhibits ranging from classical art to a reconstructed kabuki theater.\",\n alignment: \"left\",\n hidden: false,\n location: {\n center: [139.6917, 35.6895],\n zoom: 10,\n pitch: 30,\n bearing: 20,\n },\n mapAnimation: \"flyTo\",\n rotateAnimation: false,\n onChapterEnter: [],\n onChapterExit: [],\n },\n {\n id: \"sample-sydney\",\n title: \"Sydney, Australia\",\n image: `${SAMPLE_ASSET_BASE}/sydney.jpg`,\n description:\n \"Sydney, capital of New South Wales and one of Australia's largest cities, is best known for its harbourfront Sydney Opera House, with a distinctive sail-like design. <br><br>Massive Darling Harbour and the smaller Circular Quay port are hubs of waterside life, with the arched Harbour Bridge and esteemed Royal Botanic Garden nearby.\",\n alignment: \"right\",\n hidden: false,\n location: {\n center: [151.2093, -33.8688],\n zoom: 11,\n pitch: 45,\n bearing: 0,\n },\n mapAnimation: \"flyTo\",\n rotateAnimation: false,\n onChapterEnter: [],\n onChapterExit: [],\n },\n {\n id: \"sample-cape-town\",\n title: \"Cape Town, South Africa\",\n image: `${SAMPLE_ASSET_BASE}/cape-town.jpg`,\n description:\n \"Cape Town is a port city on South Africa's southwest coast, on a peninsula beneath the imposing Table Mountain. Slowly rotating cable cars climb to the mountain's flat top, from which there are sweeping views of the city, the busy harbor and boats headed for Robben Island, the infamous prison that once held Nelson Mandela. <br><br>You can add as many chapters as you need to tell your story.\",\n alignment: \"left\",\n hidden: false,\n location: {\n center: [18.4241, -33.9249],\n zoom: 10,\n pitch: 50,\n bearing: 30,\n },\n mapAnimation: \"flyTo\",\n rotateAnimation: false,\n onChapterEnter: [],\n onChapterExit: [],\n },\n ],\n };\n}\n","/**\n * Helpers for following a story-map presentation's layer fades outside the\n * MapLibre paint model.\n *\n * Story playback fades layers by writing MapLibre paint properties directly\n * (see `MapController.setStoryLayerOpacity`) and records the applied values in\n * `ui.storymapLayerOpacity`. Anything that renders a layer some other way, such\n * as deck.gl feature diagrams and 3D Z-value geometry, or that lists layers\n * for the reader, such as the on-map Legend, reads that record through these\n * helpers so a faded-out layer disappears everywhere at once.\n */\nimport type { GeoLibreLayer, MapViewState, StoryChapterLocation } from \"./types\";\n\n/** The clamped opacity recorded for a layer, or undefined when untouched. */\nfunction recordedStoryOpacity(\n opacities: Record<string, number> | undefined,\n layerId: string,\n): number | undefined {\n const value = opacities?.[layerId];\n if (typeof value !== \"number\" || !Number.isFinite(value)) return undefined;\n return Math.min(1, Math.max(0, value));\n}\n\n/**\n * The opacity a story presentation has applied to a layer.\n *\n * @param opacities The store's `ui.storymapLayerOpacity` record.\n * @param layerId Store layer id.\n * @returns The recorded opacity in `[0, 1]`, or `1` when the presentation has\n * not touched the layer (or nothing is presenting).\n */\nexport function storyLayerOpacityFactor(\n opacities: Record<string, number> | undefined,\n layerId: string,\n): number {\n return recordedStoryOpacity(opacities, layerId) ?? 1;\n}\n\n/**\n * A layer whose opacity reflects the story presentation's fade.\n *\n * A chapter's opacity *replaces* the layer's own opacity rather than scaling\n * it, matching `MapController.setStoryLayerOpacity`, which redraws the paint\n * with the chapter value in place of the layer opacity (the style's own fill\n * opacity still applies); otherwise a layer set to 0.7 in the Layers panel and\n * faded to 0.4 by a chapter would draw its diagrams at 0.28 while its MapLibre\n * layers draw at 0.4.\n *\n * Returns the same object when the presentation has not touched the layer (or\n * recorded the opacity it already has), so identity-keyed caches downstream\n * keep hitting.\n *\n * @param layer Store layer.\n * @param opacities The store's `ui.storymapLayerOpacity` record.\n */\nexport function applyStoryLayerOpacity(\n layer: GeoLibreLayer,\n opacities: Record<string, number> | undefined,\n): GeoLibreLayer {\n const opacity = recordedStoryOpacity(opacities, layer.id);\n if (opacity === undefined || opacity === layer.opacity) return layer;\n return { ...layer, opacity };\n}\n\n/**\n * Whether a story presentation has faded a layer fully out.\n *\n * @param opacities The store's `ui.storymapLayerOpacity` record.\n * @param layerId Store layer id.\n */\nexport function isStoryHiddenLayer(\n opacities: Record<string, number> | undefined,\n layerId: string,\n): boolean {\n return storyLayerOpacityFactor(opacities, layerId) === 0;\n}\n\n/**\n * The layers a reader can currently see during a story presentation: the\n * input minus those the active chapter has faded fully out. Outside a\n * presentation the input is returned as-is (same array identity).\n *\n * @param layers Store layers in panel order.\n * @param presenting The store's `ui.storymapPresenting` flag.\n * @param opacities The store's `ui.storymapLayerOpacity` record.\n */\nexport function storyVisibleLayers(\n layers: GeoLibreLayer[],\n presenting: boolean,\n opacities: Record<string, number> | undefined,\n): GeoLibreLayer[] {\n if (!presenting) return layers;\n if (!layers.some((layer) => isStoryHiddenLayer(opacities, layer.id))) return layers;\n return layers.filter((layer) => !isStoryHiddenLayer(opacities, layer.id));\n}\n\n/**\n * Resolve a story chapter location to a full camera view.\n *\n * A location's `pitch` and `bearing` are optional in memory (a project file\n * fills them with 0 on load). An absent value keeps the camera's current one,\n * the same as MapLibre's camera methods do for an omitted option, so engines\n * whose camera API needs every field (Cesium, `MapEngine.applyView`) behave\n * like the MapLibre map.\n *\n * @param location The chapter's camera target.\n * @param current The camera's current view, used for an absent pitch/bearing.\n * @returns The location with `pitch` and `bearing` filled in.\n */\nexport function storyLocationView(\n location: StoryChapterLocation,\n current: Pick<MapViewState, \"pitch\" | \"bearing\">,\n): MapViewState {\n return {\n center: location.center,\n zoom: location.zoom,\n pitch: location.pitch ?? current.pitch,\n bearing: location.bearing ?? current.bearing,\n };\n}\n","import { normalizeStoryMap } from \"./project\";\nimport { DEFAULT_STORY_MAP, type StoryMap } from \"./types\";\n\n/**\n * Serialize a story map to pretty-printed JSON for export.\n *\n * @param storymap The story map to serialize.\n * @returns A JSON string (settings and chapters).\n */\nexport function serializeStoryMapJson(storymap: StoryMap): string {\n return JSON.stringify(storymap, null, 2);\n}\n\n/**\n * Parse a story map from JSON authored outside GeoLibre.\n *\n * Accepts either a bare story map object or a full `.geolibre.json` project with\n * a `storymap` field, then validates it through the same normalizer used when\n * loading projects.\n *\n * @param text JSON text to import.\n * @returns A normalized story map.\n * @throws If the JSON is invalid or contains no usable chapters.\n */\nexport function parseStoryMapJson(text: string): StoryMap {\n const data = JSON.parse(text) as unknown;\n const candidate =\n data && typeof data === \"object\" && \"storymap\" in (data as object)\n ? (data as { storymap: unknown }).storymap\n : data;\n const normalized = normalizeStoryMap(candidate);\n if (!normalized) {\n throw new Error(\"The imported story map has no chapters.\");\n }\n return normalized;\n}\n\n// Flat, spreadsheet-friendly chapter columns. Layer opacity effects are stored\n// as JSON in their own columns so the CSV round-trips, but hand-authors can\n// simply leave them blank.\nconst CSV_HEADERS = [\n \"id\",\n \"title\",\n \"description\",\n \"image\",\n \"alignment\",\n \"hidden\",\n \"lng\",\n \"lat\",\n \"zoom\",\n \"pitch\",\n \"bearing\",\n \"mapAnimation\",\n \"rotateAnimation\",\n \"onChapterEnter\",\n \"onChapterExit\",\n \"durationSeconds\",\n] as const;\n\n/**\n * Serialize a story map's chapters to CSV (one row per chapter).\n *\n * Story-level settings are not represented in CSV; they are kept in the project\n * and preserved on CSV import.\n *\n * @param storymap The story map whose chapters to serialize.\n * @returns A CSV string with a header row.\n */\nexport function serializeStoryMapCsv(storymap: StoryMap): string {\n const rows = [CSV_HEADERS.join(\",\")];\n for (const c of storymap.chapters) {\n rows.push(\n [\n c.id,\n c.title,\n c.description,\n c.image ?? \"\",\n c.alignment,\n String(c.hidden),\n String(c.location.center[0]),\n String(c.location.center[1]),\n String(c.location.zoom),\n // An absent pitch/bearing is an empty cell, not the string \"undefined\".\n String(c.location.pitch ?? \"\"),\n String(c.location.bearing ?? \"\"),\n c.mapAnimation,\n String(c.rotateAnimation),\n JSON.stringify(c.onChapterEnter),\n JSON.stringify(c.onChapterExit),\n String(c.durationSeconds ?? \"\"),\n ]\n .map(csvEscape)\n .join(\",\"),\n );\n }\n return rows.join(\"\\n\") + \"\\n\";\n}\n\n/**\n * Parse chapters from a CSV authored outside GeoLibre.\n *\n * Columns are matched by header name (case-insensitive) so column order is\n * flexible; `lng`/`lat` set the chapter center. Story-level settings come from\n * `base` (the current story) since CSV only carries chapters; missing ids are\n * generated.\n *\n * @param text CSV text to import.\n * @param base Existing story map whose settings should be preserved.\n * @returns A normalized story map.\n * @throws If the CSV has no data rows or no valid chapters.\n */\nexport function parseStoryMapCsv(text: string, base?: StoryMap | null): StoryMap {\n const rows = parseCsvRows(text);\n if (rows.length < 2) {\n throw new Error(\"The CSV has no chapter rows.\");\n }\n const header = rows[0].map((h) => h.trim().toLowerCase());\n const col = (name: string) => header.indexOf(name.toLowerCase());\n\n const chapters = rows\n .slice(1)\n .filter((row) => row.some((cell) => cell.trim() !== \"\"))\n .map((row, index) => {\n const get = (name: string) => {\n const j = col(name);\n return j >= 0 ? (row[j] ?? \"\") : \"\";\n };\n const num = (name: string, fallback: number) => {\n const value = Number(get(name));\n return Number.isFinite(value) ? value : fallback;\n };\n const bool = (name: string) => /^(true|1|yes)$/i.test(get(name).trim());\n const jsonArray = (name: string) => {\n try {\n const parsed = JSON.parse(get(name) || \"[]\");\n return Array.isArray(parsed) ? parsed : [];\n } catch {\n return [];\n }\n };\n return {\n id: get(\"id\").trim() || `chapter-${index + 1}`,\n title: get(\"title\"),\n description: get(\"description\"),\n image: get(\"image\").trim() || undefined,\n alignment: get(\"alignment\").trim() || \"left\",\n hidden: bool(\"hidden\"),\n location: {\n center: [num(\"lng\", 0), num(\"lat\", 0)],\n zoom: num(\"zoom\", 2),\n pitch: num(\"pitch\", 0),\n bearing: num(\"bearing\", 0),\n },\n mapAnimation: get(\"mapanimation\").trim() || \"flyTo\",\n rotateAnimation: bool(\"rotateanimation\"),\n // An absent/blank cell means the default, not Number(\"\") === 0.\n durationSeconds: get(\"durationseconds\").trim() ? Number(get(\"durationseconds\")) : undefined,\n onChapterEnter: jsonArray(\"onchapterenter\"),\n onChapterExit: jsonArray(\"onchapterexit\"),\n };\n });\n\n const normalized = normalizeStoryMap({\n ...(base ?? DEFAULT_STORY_MAP),\n chapters,\n });\n if (!normalized) {\n throw new Error(\"The CSV contained no valid chapters.\");\n }\n return normalized;\n}\n\n/** Quote a CSV field when it contains a comma, quote, or newline. */\nfunction csvEscape(value: string): string {\n if (/[\",\\n\\r]/.test(value)) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n }\n return value;\n}\n\n/** Parse CSV text into rows of fields, honoring quotes and escaped quotes. */\nfunction parseCsvRows(text: string): string[][] {\n const rows: string[][] = [];\n let row: string[] = [];\n let field = \"\";\n let inQuotes = false;\n // Normalize line endings so \\r\\n and \\r behave like \\n.\n const input = text.replace(/\\r\\n?/g, \"\\n\");\n\n for (let i = 0; i < input.length; i++) {\n const ch = input[i];\n if (inQuotes) {\n if (ch === '\"') {\n if (input[i + 1] === '\"') {\n field += '\"';\n i++;\n } else {\n inQuotes = false;\n }\n } else {\n field += ch;\n }\n } else if (ch === '\"') {\n inQuotes = true;\n } else if (ch === \",\") {\n row.push(field);\n field = \"\";\n } else if (ch === \"\\n\") {\n row.push(field);\n rows.push(row);\n row = [];\n field = \"\";\n } else {\n field += ch;\n }\n }\n // Flush the final field/row unless the input ended with a trailing newline.\n if (field !== \"\" || row.length > 0) {\n row.push(field);\n rows.push(row);\n }\n return rows;\n}\n","/**\n * Narrow subscriptions to the store's `layers` array.\n *\n * `updateLayer` (and every other layer action) replaces `layers` with a new\n * array on each change, so a component that subscribes with\n * `useAppStore((s) => s.layers)` re-renders on every opacity tweak, rename or\n * style edit of *any* layer. The hooks here select only what a component reads\n * and compare it so unrelated layer changes do not re-render it:\n *\n * - {@link useLayer} — one layer record, by id. Layer actions copy only the\n * layer they change and keep every other record's identity, so this is\n * stable while other layers change.\n * - {@link useLayerIds} — the ordered id list, shallow-compared.\n * - {@link useLayerSummaries} — `{ id, name, type, visible, groupId }` per\n * layer, for components that only list layers.\n * - {@link useLayersWhen} — the full array, but only while `active` (a dialog\n * is open, a search has a query); a constant empty array otherwise.\n *\n * The plain `select*` functions are exported so the selection semantics can be\n * tested without React.\n */\nimport { useShallow } from \"zustand/react/shallow\";\nimport { type AppState, useAppStore } from \"./store\";\nimport type { GeoLibreLayer, LayerType } from \"./types\";\n\n/**\n * The fields a layer list needs: enough to draw a row, a `<select>` option or\n * a visibility checkbox. It deliberately leaves out opacity, style, source,\n * metadata and feature data, which change far more often.\n */\nexport interface LayerSummary {\n /** The layer id. */\n readonly id: string;\n /** The display name. */\n readonly name: string;\n /** The layer kind. */\n readonly type: LayerType;\n /** Whether the layer is shown on the primary map. */\n readonly visible: boolean;\n /** The layer panel group the layer belongs to, if any. */\n readonly groupId: string | undefined;\n}\n\n/**\n * The array {@link useLayersWhen} returns while inactive. Frozen, and shared so\n * the inactive selection never changes identity.\n */\nexport const NO_LAYERS: GeoLibreLayer[] = Object.freeze([]) as unknown as GeoLibreLayer[];\n\n/**\n * Select one layer record by id.\n *\n * @param state - The app state.\n * @param id - The layer id; `null`/`undefined` selects nothing.\n * @returns The layer record, or `undefined` when there is no such layer.\n */\nexport function selectLayerById(\n state: Pick<AppState, \"layers\">,\n id: string | null | undefined,\n): GeoLibreLayer | undefined {\n if (!id) return undefined;\n return state.layers.find((layer) => layer.id === id);\n}\n\n/**\n * Select the layer ids in store order (bottom to top).\n *\n * Returns a new array on every call; pair it with a shallow comparison (as\n * {@link useLayerIds} does) so an unchanged id list keeps its identity.\n *\n * @param state - The app state.\n * @returns The layer ids.\n */\nexport function selectLayerIds(state: Pick<AppState, \"layers\">): string[] {\n return state.layers.map((layer) => layer.id);\n}\n\n// Summaries from the previous call, by layer id. Reusing an entry whose fields\n// are unchanged keeps each summary's identity across unrelated edits (an\n// opacity change replaces the layer object but not its summary), which is what\n// lets a shallow comparison of the summary array succeed. Rebuilt on every call\n// so removed layers drop out. Summaries are immutable values, so sharing them\n// between subscribers is safe.\n//\n// The cache is module-level on purpose: every caller reads the one app store,\n// and an entry is reused only after `sameSummary` re-checks it against the\n// current layer. Another subscriber, or a React render that is discarded,\n// can therefore only cause a cache miss (a new summary object), never a stale\n// summary. Tests that care about identity should not rely on a clean cache.\nlet summaryCache = new Map<string, LayerSummary>();\n\nfunction sameSummary(summary: LayerSummary, layer: GeoLibreLayer): boolean {\n return (\n summary.name === layer.name &&\n summary.type === layer.type &&\n summary.visible === layer.visible &&\n summary.groupId === layer.groupId\n );\n}\n\n/**\n * Select a {@link LayerSummary} per layer, in store order (bottom to top).\n *\n * Each summary keeps its identity while its own fields are unchanged, so the\n * returned array is shallow-equal to the previous one after an edit that\n * touches none of those fields (opacity, style, data, metadata).\n *\n * @param state - The app state.\n * @returns One summary per layer.\n */\nexport function selectLayerSummaries(state: Pick<AppState, \"layers\">): LayerSummary[] {\n const next = new Map<string, LayerSummary>();\n const summaries = state.layers.map((layer) => {\n const previous = summaryCache.get(layer.id);\n const summary =\n previous && sameSummary(previous, layer)\n ? previous\n : Object.freeze({\n id: layer.id,\n name: layer.name,\n type: layer.type,\n visible: layer.visible,\n groupId: layer.groupId,\n });\n next.set(layer.id, summary);\n return summary;\n });\n summaryCache = next;\n return summaries;\n}\n\n/**\n * Build a selector for the full `layers` array that is live only while\n * `active`.\n *\n * @param active - Whether the caller currently needs the layers.\n * @returns A selector returning `state.layers` while active, and the constant\n * {@link NO_LAYERS} otherwise.\n */\nexport function selectLayersWhen(\n active: boolean,\n): (state: Pick<AppState, \"layers\">) => GeoLibreLayer[] {\n return active ? (state) => state.layers : () => NO_LAYERS;\n}\n\n/**\n * Subscribe to one layer record.\n *\n * Re-renders only when that layer changes (or appears/disappears), not when\n * another layer is edited.\n *\n * @param id - The layer id; `null`/`undefined` subscribes to nothing.\n * @returns The layer record, or `undefined` when there is no such layer.\n */\nexport function useLayer(id: string | null | undefined): GeoLibreLayer | undefined {\n return useAppStore((state) => selectLayerById(state, id));\n}\n\n/**\n * Subscribe to the ordered list of layer ids.\n *\n * Re-renders only when a layer is added, removed or reordered.\n *\n * @returns The layer ids in store order (bottom to top).\n */\nexport function useLayerIds(): string[] {\n return useAppStore(useShallow(selectLayerIds));\n}\n\n/**\n * Subscribe to a {@link LayerSummary} per layer.\n *\n * Re-renders only when a layer is added, removed, reordered, renamed, shown or\n * hidden, retyped or regrouped; opacity, style, data and metadata edits do not\n * re-render the caller.\n *\n * @returns One summary per layer, in store order (bottom to top).\n */\nexport function useLayerSummaries(): LayerSummary[] {\n return useAppStore(useShallow(selectLayerSummaries));\n}\n\n/**\n * Subscribe to the full `layers` array only while `active`.\n *\n * For always-mounted surfaces (dialogs, floating panels) that need every layer\n * record while open but keep local state across closes and so cannot simply be\n * unmounted. While inactive the caller gets the constant {@link NO_LAYERS} and\n * layer edits do not re-render it.\n *\n * @param active - Whether the caller currently needs the layers (for example,\n * whether its dialog is open).\n * @returns `layers` while active, otherwise an empty, frozen array.\n */\nexport function useLayersWhen(active: boolean): GeoLibreLayer[] {\n return useAppStore(selectLayersWhen(active));\n}\n","/**\n * Platform detection shared across the workspace. Carries no React/MapLibre\n * dependency so callers stay unit-testable.\n */\n\n/**\n * Whether a \"Macintosh\" user agent is really an iPad.\n *\n * iPadOS 13+ requests desktop sites by default and reports a macOS user agent,\n * so the UA string alone cannot tell an iPad from a Mac. Multi-touch does: a\n * real Mac reports `maxTouchPoints` 0 or 1, an iPad reports more.\n *\n * This lives in `@geolibre/core` because two packages need it and the rule is\n * an Apple behaviour that could change: the desktop app's `isMobile` (which\n * hides sidecar-backed tools on mobile) and `@geolibre/plugins`'\n * `isEarthEngineAvailable` (which hides Earth Engine sign-in on the Apple App\n * Store builds). Keeping one copy means a future correction lands in both.\n *\n * @param userAgent - The user agent string to test.\n * @param maxTouchPoints - `navigator.maxTouchPoints`, or undefined where the\n * runtime does not provide it (Node exposes a `navigator` without it).\n * @returns True when the UA claims macOS but the device reports multi-touch.\n */\nexport function isIpadDesktopUserAgent(\n userAgent: string,\n maxTouchPoints: number | undefined,\n): boolean {\n return /Macintosh/.test(userAgent) && (maxTouchPoints ?? 0) > 1;\n}\n","import { getGoogleMapsApiKey } from \"./runtime-env\";\n\n// Shared request-header resolution for 3D Tiles layers. Google Photorealistic\n// 3D Tiles authenticate with a Maps API key sent in the `X-GOOG-API-KEY` header,\n// but the key is deliberately stripped from the store/project record (so shared\n// projects never carry it in plain text) and re-injected at render time from\n// runtime env. Both the MapLibre/deck.gl render path (packages/plugins) and the\n// Cesium globe path (packages/map) must resolve headers the same way, so the\n// logic lives here in core rather than being duplicated per renderer.\n\n/** Google Photorealistic 3D Tiles root host + path. */\nconst GOOGLE_PHOTOREALISTIC_TILES_HOST = \"tile.googleapis.com\";\nconst GOOGLE_PHOTOREALISTIC_TILES_PATH = \"/v1/3dtiles/root.json\";\n/** Header Google's tile server expects the Maps API key in. */\nexport const GOOGLE_MAPS_API_KEY_HEADER = \"X-GOOG-API-KEY\";\n\n/** Whether `url` points at Google's Photorealistic 3D Tiles root tileset. */\nexport function isGooglePhotorealisticTilesetUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n return (\n parsed.hostname === GOOGLE_PHOTOREALISTIC_TILES_HOST &&\n parsed.pathname === GOOGLE_PHOTOREALISTIC_TILES_PATH\n );\n } catch {\n return false;\n }\n}\n\n/** A masked placeholder (all asterisks) is a UI stand-in, not a real key. */\nfunction isMaskedKey(value: string): boolean {\n return /^\\*+$/.test(value.trim());\n}\n\n/** Drop the `X-GOOG-API-KEY` header (case-insensitive), keeping the rest. */\nexport function stripGoogleMapsApiKeyHeader(\n headers: Record<string, string> | undefined,\n): Record<string, string> | undefined {\n if (!headers) return undefined;\n const entries = Object.entries(headers).filter(\n ([name]) => name.toLowerCase() !== GOOGLE_MAPS_API_KEY_HEADER.toLowerCase(),\n );\n return entries.length > 0 ? Object.fromEntries(entries) : undefined;\n}\n\n/**\n * The real `X-GOOG-API-KEY` value from `headers`, or `undefined` when absent,\n * blank, or a masked placeholder.\n */\nexport function googleMapsApiKeyHeaderValue(\n headers: Record<string, string> | undefined,\n): string | undefined {\n if (!headers) return undefined;\n const entry = Object.entries(headers).find(\n ([name]) => name.toLowerCase() === GOOGLE_MAPS_API_KEY_HEADER.toLowerCase(),\n );\n const value = entry?.[1].trim();\n if (!value || isMaskedKey(value)) return undefined;\n return value;\n}\n\n/** Collapse an empty record to `undefined`, otherwise pass it through. */\nexport function nonEmptyRecord(\n value: Record<string, string> | undefined,\n): Record<string, string> | undefined {\n return value && Object.keys(value).length > 0 ? value : undefined;\n}\n\n/**\n * Resolve the request headers a 3D Tiles layer should load with. For non-Google\n * tilesets the headers pass through unchanged. For Google Photorealistic tiles\n * the `X-GOOG-API-KEY` header is (re)built from, in order: an explicit key in\n * the given headers, the `googleMapsApiKey` argument, then `getGoogleMapsApiKey()`\n * (runtime env). Returns `undefined` when there is nothing to send.\n */\nexport function resolveThreeDTilesRequestHeaders(\n url: string,\n headers: Record<string, string> | undefined,\n googleMapsApiKey?: string,\n): Record<string, string> | undefined {\n if (!isGooglePhotorealisticTilesetUrl(url)) return headers;\n const nonGoogleHeaders = stripGoogleMapsApiKeyHeader(headers);\n const apiKey = googleMapsApiKeyHeaderValue(headers) ?? googleMapsApiKey ?? getGoogleMapsApiKey();\n if (!apiKey) return nonEmptyRecord(nonGoogleHeaders);\n return {\n ...(nonGoogleHeaders ?? {}),\n [GOOGLE_MAPS_API_KEY_HEADER]: apiKey,\n };\n}\n\n/**\n * The request headers to persist on a 3D Tiles layer record. Non-Google\n * tilesets keep their headers; Google Photorealistic tiles keep any non-key\n * custom headers but never persist the `X-GOOG-API-KEY` value, so shared\n * projects do not carry the key in plain text (it is re-injected at render\n * time by `resolveThreeDTilesRequestHeaders`).\n */\nexport function persistedThreeDTilesRequestHeaders(\n url: string,\n headers: Record<string, string> | undefined,\n): Record<string, string> | undefined {\n if (!isGooglePhotorealisticTilesetUrl(url)) return headers;\n return nonEmptyRecord(stripGoogleMapsApiKeyHeader(headers));\n}\n","/**\n * Credentials a project refers to by name rather than carrying inline.\n *\n * On the desktop app the geocoding API keys and secret environment variables\n * of a project live in the OS keychain, keyed device-wide by provider id /\n * variable name. The project file keeps the names with empty values. Request\n * headers never go there: they reference variables as `${NAME}` instead (see\n * `resolveProjectHeaderReferences`). This module is the pure half of that\n * scheme: the account names, the overlay that fills empty values from the\n * stored ones, and the split that moves values out of a project before it is\n * written. The app installs the lookup; without one (web, Jupyter) every\n * function here is the identity, so values stay in the project file.\n *\n * A non-empty value in the project always wins over the stored one: a file\n * that still carries plaintext (older files, the web build) is used as-is for\n * the session.\n */\n\nimport { GEOCODING_PROVIDERS } from \"./geocoding\";\nimport { resolveHeaderReferences } from \"./header-references\";\nimport { useAppStore } from \"./store\";\nimport type {\n GeocodingPreferences,\n GeoLibreProject,\n ProjectPreferences,\n RuntimeEnvironmentVariable,\n} from \"./types\";\n\n// Account names are stable keychain data: renaming one orphans every value a\n// user already stored under it.\nexport function geocodingApiKeyAccount(providerId: string): string {\n return `project.geocoding.apiKey.${providerId}`;\n}\n\nexport function environmentVariableAccount(key: string): string {\n return `project.env.${key}`;\n}\n\n/** Rows are secret unless explicitly marked `secret: false`. */\nexport function isSecretEnvironmentVariable(variable: RuntimeEnvironmentVariable): boolean {\n return variable.secret !== false;\n}\n\nexport type ProjectCredentialLookup = (account: string) => string | undefined;\n\nlet projectCredentialLookup: ProjectCredentialLookup | null = null;\n\n/** Install (or clear, with `null`) the source of stored project credentials. */\nexport function setProjectCredentialLookup(lookup: ProjectCredentialLookup | null): void {\n projectCredentialLookup = lookup;\n}\n\n/** The stored value for `account`, or `undefined` when none (or no lookup). */\nexport function lookupProjectCredential(account: string): string | undefined {\n const value = projectCredentialLookup?.(account);\n return value ? value : undefined;\n}\n\n/** Fill empty geocoding API keys from the stored ones. */\nexport function overlayStoredGeocodingApiKeys(\n geocoding: GeocodingPreferences,\n): GeocodingPreferences {\n let apiKeys: Record<string, string> | null = null;\n for (const { id } of GEOCODING_PROVIDERS) {\n if (geocoding.apiKeys[id]?.trim()) continue;\n const stored = lookupProjectCredential(geocodingApiKeyAccount(id));\n if (!stored) continue;\n apiKeys ??= { ...geocoding.apiKeys };\n apiKeys[id] = stored;\n }\n return apiKeys ? { ...geocoding, apiKeys } : geocoding;\n}\n\n/** Fill empty secret environment variable values from the stored ones. */\nexport function overlayStoredEnvironmentVariables(\n variables: RuntimeEnvironmentVariable[],\n): RuntimeEnvironmentVariable[] {\n let changed = false;\n const overlaid = variables.map((variable) => {\n if (!isSecretEnvironmentVariable(variable) || variable.value !== \"\") return variable;\n const key = variable.key.trim();\n const stored = key ? lookupProjectCredential(environmentVariableAccount(key)) : undefined;\n if (!stored) return variable;\n changed = true;\n return { ...variable, value: stored };\n });\n return changed ? overlaid : variables;\n}\n\n/** Preferences with every empty stored credential filled in. */\nexport function overlayStoredPreferenceCredentials(\n preferences: ProjectPreferences,\n): ProjectPreferences {\n const geocoding = overlayStoredGeocodingApiKeys(preferences.geocoding);\n const environmentVariables = overlayStoredEnvironmentVariables(preferences.environmentVariables);\n if (\n geocoding === preferences.geocoding &&\n environmentVariables === preferences.environmentVariables\n ) {\n return preferences;\n }\n return { ...preferences, geocoding, environmentVariables };\n}\n\n/**\n * The credential changes an edited Settings draft makes, keyed by account\n * (`\"\"` deletes).\n *\n * - `seeded`: the overlaid preferences the dialog opened with.\n * - `next`: the normalized draft being saved.\n *\n * Only values the user changed are returned, so an untouched session override\n * (plaintext from the opened file) stays out of the keychain and opening a\n * file never writes it. Removing, renaming, or un-secreting a variable never\n * deletes its stored value: names are shared by every project on the device.\n * Clearing a field deletes the stored value only when the field showed that\n * stored value; clearing a file override that differed from it just drops the\n * override, so the shared value applies again and other projects keep it.\n */\nexport function changedPreferenceCredentials(\n seeded: ProjectPreferences,\n next: ProjectPreferences,\n): Record<string, string> {\n const changes: Record<string, string> = {};\n const record = (account: string, seededValue: string, nextValue: string) => {\n if (nextValue === seededValue) return;\n if (nextValue === \"\" && seededValue !== lookupProjectCredential(account)) return;\n changes[account] = nextValue;\n };\n\n const providerIds = new Set([\n ...Object.keys(seeded.geocoding.apiKeys),\n ...Object.keys(next.geocoding.apiKeys),\n ]);\n for (const providerId of providerIds) {\n record(\n geocodingApiKeyAccount(providerId),\n seeded.geocoding.apiKeys[providerId]?.trim() ?? \"\",\n next.geocoding.apiKeys[providerId]?.trim() ?? \"\",\n );\n }\n\n const seededSecrets = new Map<string, string>();\n for (const variable of seeded.environmentVariables) {\n if (!isSecretEnvironmentVariable(variable) || seededSecrets.has(variable.key)) continue;\n seededSecrets.set(variable.key, variable.value);\n }\n for (const variable of next.environmentVariables) {\n if (!isSecretEnvironmentVariable(variable)) continue;\n const seededValue = seededSecrets.get(variable.key);\n // A new blank row falls back to any value stored under the name; it\n // never deletes it.\n if (seededValue === undefined && variable.value === \"\") continue;\n record(environmentVariableAccount(variable.key), seededValue ?? \"\", variable.value);\n }\n\n return changes;\n}\n\n/**\n * Move unambiguous geocoding API keys and secret environment variable values\n * out of `project` (a copy; the input is untouched) into `secrets`, keyed by\n * account. Secret environment rows that have no unique name remain in the\n * project for an explicit keep/strip choice. Layers pass through: their\n * request headers reference variables as `${NAME}` rather than holding\n * secrets that need moving.\n */\nexport function splitProjectCredentials(project: GeoLibreProject): {\n project: GeoLibreProject;\n secrets: Record<string, string>;\n} {\n const secrets: Record<string, string> = {};\n\n for (const [providerId, apiKey] of Object.entries(project.preferences.geocoding.apiKeys)) {\n const trimmed = apiKey.trim();\n if (trimmed) secrets[geocodingApiKeyAccount(providerId)] = trimmed;\n }\n // A hand-edited file can contain duplicate names even though Settings\n // rejects them. One keychain account cannot preserve two different values.\n const nameCounts = new Map<string, number>();\n for (const variable of project.preferences.environmentVariables) {\n const key = variable.key.trim();\n if (key) nameCounts.set(key, (nameCounts.get(key) ?? 0) + 1);\n }\n const environmentVariables = project.preferences.environmentVariables.map((variable) => {\n if (!isSecretEnvironmentVariable(variable) || variable.value === \"\") return variable;\n const key = variable.key.trim();\n // Keep ambiguous or nameless rows intact for the local keep/strip prompt.\n if (!key || (nameCounts.get(key) ?? 0) > 1) return variable;\n secrets[environmentVariableAccount(key)] = variable.value;\n return { ...variable, value: \"\" };\n });\n\n return {\n project: {\n ...project,\n preferences: {\n ...project.preferences,\n geocoding: { ...project.preferences.geocoding, apiKeys: {} },\n environmentVariables,\n },\n },\n secrets,\n };\n}\n\n/**\n * `headers` with every `${NAME}` resolved from the project's enabled\n * Environment Variables (stored secret values filled in). Only those rows\n * count, not the OS environment or build-time values that share the runtime\n * env. A header referencing an unset variable is dropped.\n */\nexport function resolveProjectHeaderReferences(\n headers: Record<string, string> | undefined,\n): Record<string, string> | undefined {\n if (!headers) return headers;\n const values: Record<string, string> = {};\n const variables = overlayStoredEnvironmentVariables(\n useAppStore.getState().preferences.environmentVariables,\n );\n for (const variable of variables) {\n const key = variable.key.trim();\n if (variable.enabled && key) values[key] = variable.value;\n }\n return resolveHeaderReferences(headers, values);\n}\n","import type { FeatureCollection } from \"geojson\";\nimport type { GeoLibreProject, FieldVisibility } from \"./types\";\n\n/**\n * Returns a new FeatureCollection with properties marked as \"excluded\" removed.\n */\nexport function excludeHiddenFieldsFromGeojson(\n geojson: FeatureCollection,\n fieldVisibility?: Record<string, FieldVisibility>,\n): FeatureCollection {\n const excludedKeys = new Set(\n Object.entries(fieldVisibility || {})\n .filter(([_, visibility]) => visibility === \"excluded\")\n .map(([key]) => key),\n );\n\n if (excludedKeys.size === 0) {\n return geojson;\n }\n\n // Deep clone to avoid mutating the live store state\n const stripped: FeatureCollection = {\n ...geojson,\n features: geojson.features.map((feature) => {\n const properties = { ...feature.properties };\n for (const key of excludedKeys) {\n delete properties[key];\n }\n return { ...feature, properties };\n }),\n };\n\n return stripped;\n}\n\n/**\n * Returns a new GeoLibreProject where all layers have their excluded fields\n * physically removed from their inline GeoJSON.\n */\nexport function excludeHiddenFieldsFromProject(project: GeoLibreProject): GeoLibreProject {\n let changed = false;\n const layers = project.layers.map((layer) => {\n if (!layer.fieldVisibility) return layer;\n\n let updatedLayer = layer;\n\n if (layer.geojson) {\n const strippedGeojson = excludeHiddenFieldsFromGeojson(layer.geojson, layer.fieldVisibility);\n if (strippedGeojson !== layer.geojson) {\n changed = true;\n const { geoarrow: _staleGeoarrow, ...rest } = updatedLayer;\n updatedLayer = { ...rest, geojson: strippedGeojson };\n }\n }\n\n if (layer.metadata?.embeddedGeoJSON) {\n const strippedEmbedded = excludeHiddenFieldsFromGeojson(\n layer.metadata.embeddedGeoJSON as FeatureCollection,\n layer.fieldVisibility,\n );\n if (strippedEmbedded !== layer.metadata.embeddedGeoJSON) {\n changed = true;\n updatedLayer = {\n ...updatedLayer,\n metadata: {\n ...updatedLayer.metadata,\n embeddedGeoJSON: strippedEmbedded,\n },\n };\n }\n }\n\n return updatedLayer;\n });\n\n return changed ? { ...project, layers } : project;\n}\n","import type { Feature, FeatureCollection } from \"geojson\";\nimport type { EditorTrackingConfig } from \"./types\";\n\n/** Default field names for maintained editor tracking columns. */\nexport const DEFAULT_EDITOR_TRACKING_CONFIG: Required<EditorTrackingConfig> = {\n enabled: false,\n createdByField: \"created_by\",\n createdAtField: \"created_at\",\n editedByField: \"edited_by\",\n editedAtField: \"edited_at\",\n};\n\n/**\n * Identity written to the author fields when nothing better is known.\n *\n * Deliberately NOT translated: it is written into the user's data, where a\n * value that changes with the UI language would make the same editor appear as\n * several different people across a project's history.\n */\nexport const DEFAULT_EDITOR_IDENTITY = \"local-user\";\n\n/**\n * `localStorage` key holding the display name this browser edits under.\n *\n * Shared with the Comments panel, which asks for the same name and stores it\n * here, so a user who has introduced themselves once is recognized by both\n * features instead of being prompted twice.\n */\nexport const EDITOR_IDENTITY_STORAGE_KEY = \"geolibre_author_name\";\n\n/**\n * Choose the identity to stamp, most authoritative source first.\n *\n * A live collaboration session names its participants, so that name wins: it is\n * the one other people in the session see attached to the same edits. Otherwise\n * fall back to the locally configured display name, then to\n * {@link DEFAULT_EDITOR_IDENTITY}.\n *\n * @param collabName Display name from an active collaboration session.\n * @param storedName Locally configured display name.\n * @returns A non-empty identity string.\n */\nexport function pickEditorIdentity(collabName?: string | null, storedName?: string | null): string {\n return collabName?.trim() || storedName?.trim() || DEFAULT_EDITOR_IDENTITY;\n}\n\n/**\n * The four maintained column names, or `null` when tracking is off or the\n * configuration is unusable. Ordered created-then-edited so callers that render\n * the columns get a stable, meaningful order.\n */\nexport function editorTrackingFieldNames(config?: EditorTrackingConfig): string[] | null {\n const resolved = resolveEditorTrackingConfigForQuery(config);\n if (!resolved?.enabled) return null;\n return [\n resolved.createdByField,\n resolved.createdAtField,\n resolved.editedByField,\n resolved.editedAtField,\n ];\n}\n\n/**\n * Options for editor tracking stamping functions.\n */\nexport interface EditorTrackingStampOptions {\n /** Optional custom editor tracking field configuration. */\n config?: EditorTrackingConfig;\n /**\n * Identity string of the author/editor (e.g. username, email, or client ID).\n * Defaults to {@link DEFAULT_EDITOR_IDENTITY}.\n */\n userIdentity?: string;\n /** ISO timestamp override for deterministic testing or batch operations. Defaults to `new Date().toISOString()`. */\n timestamp?: string;\n}\n\n/**\n * Fully resolve an {@link EditorTrackingConfig} with fallback default field names.\n */\nexport function resolveEditorTrackingConfig(\n config?: EditorTrackingConfig,\n): Required<EditorTrackingConfig> {\n // Trimmed on the way in, because these become literal GeoJSON property keys:\n // a name typed as `\" created_by \"` in the settings form would otherwise pass\n // validation and then write a column whose name has invisible padding,\n // matching neither what the user typed nor the default it looks like.\n const resolved = {\n enabled: config?.enabled ?? DEFAULT_EDITOR_TRACKING_CONFIG.enabled,\n createdByField: (\n config?.createdByField ?? DEFAULT_EDITOR_TRACKING_CONFIG.createdByField\n ).trim(),\n createdAtField: (\n config?.createdAtField ?? DEFAULT_EDITOR_TRACKING_CONFIG.createdAtField\n ).trim(),\n editedByField: (config?.editedByField ?? DEFAULT_EDITOR_TRACKING_CONFIG.editedByField).trim(),\n editedAtField: (config?.editedAtField ?? DEFAULT_EDITOR_TRACKING_CONFIG.editedAtField).trim(),\n };\n\n // Tracking that is off writes nothing, so the names cannot matter — and a\n // half-filled or hand-edited config left behind on a disabled layer must not\n // make every save on that layer throw. Validate only what will be used.\n if (!resolved.enabled) return resolved;\n\n const fields = [\n resolved.createdByField,\n resolved.createdAtField,\n resolved.editedByField,\n resolved.editedAtField,\n ];\n if (fields.some((field) => field === \"\") || new Set(fields).size !== fields.length) {\n throw new Error(\"Editor tracking field names must be non-empty and unique\");\n }\n\n return resolved;\n}\n\n/**\n * Resolve a config for the query helpers, yielding `null` rather than throwing when\n * the configured field names are blank or collide.\n *\n * The query helpers run once per field on every Attribute Table / Field Calculator\n * render, and the field names are user-editable strings, so a half-filled settings\n * form or a hand-edited `.geolibre.json` must degrade to \"tracking off\" instead of\n * crashing the panel. The stamping helpers keep the throw: a write that silently\n * skipped tracking would leave a layer looking tracked while recording nothing.\n */\nfunction resolveEditorTrackingConfigForQuery(\n config?: EditorTrackingConfig,\n): Required<EditorTrackingConfig> | null {\n try {\n return resolveEditorTrackingConfig(config);\n } catch {\n return null;\n }\n}\n\n/**\n * Check whether a field name corresponds to one of the maintained editor tracking columns.\n *\n * Returns `false` for an invalid config, so callers can use this as a plain predicate.\n */\nexport function isMaintainedEditorTrackingField(\n fieldName: string,\n config?: EditorTrackingConfig,\n): boolean {\n const resolved = resolveEditorTrackingConfigForQuery(config);\n if (!resolved?.enabled) {\n return false;\n }\n return (\n fieldName === resolved.createdByField ||\n fieldName === resolved.createdAtField ||\n fieldName === resolved.editedByField ||\n fieldName === resolved.editedAtField\n );\n}\n\n/**\n * Ensure all configured editor tracking field names are included in a field list.\n *\n * Returns the list unchanged for an invalid config, matching\n * {@link isMaintainedEditorTrackingField}.\n */\nexport function ensureEditorTrackingFields(\n fields: string[],\n config?: EditorTrackingConfig,\n): string[] {\n const trackingFields = editorTrackingFieldNames(config);\n if (!trackingFields) {\n return fields;\n }\n const result = [...fields];\n for (const tf of trackingFields) {\n if (!result.includes(tf)) {\n result.push(tf);\n }\n }\n return result;\n}\n\n/**\n * Stamp editor tracking metadata onto a feature's properties object.\n *\n * On `\"create\"`: sets all four columns to this editor and this timestamp.\n * `\"create\"` states that the feature is being created here and now, so it is\n * authoritative — a value already sitting in one of these columns describes\n * some other feature, not this one. That case is reachable: the geometry\n * editor's copy action clones a tracked feature's properties, and a Field\n * Collection form can define a capture field whose key happens to be a tracking\n * column. Both would otherwise credit a brand-new feature to whoever created\n * the thing it was copied from.\n *\n * On `\"update\"`: sets `edited_by`/`edited_at` and preserves `created_by` /\n * `created_at`, which record something this edit did not change.\n */\nexport function stampFeaturePropertiesEditorTracking(\n properties: Record<string, unknown> | null | undefined,\n action: \"create\" | \"update\",\n options?: EditorTrackingStampOptions,\n): Record<string, unknown> {\n const resolved = resolveEditorTrackingConfig(options?.config);\n if (!resolved.enabled) {\n return properties ? { ...properties } : {};\n }\n\n const result: Record<string, unknown> = properties ? { ...properties } : {};\n const now = options?.timestamp ?? new Date().toISOString();\n const actor = options?.userIdentity || DEFAULT_EDITOR_IDENTITY;\n\n if (action === \"create\") {\n result[resolved.createdByField] = actor;\n result[resolved.createdAtField] = now;\n }\n\n result[resolved.editedByField] = actor;\n result[resolved.editedAtField] = now;\n\n return result;\n}\n\n/**\n * Stamp editor tracking metadata onto a GeoJSON {@link Feature}.\n */\nexport function stampFeatureEditorTracking<T extends Feature>(\n feature: T,\n action: \"create\" | \"update\",\n options?: EditorTrackingStampOptions,\n): T {\n const resolved = resolveEditorTrackingConfig(options?.config);\n if (!resolved.enabled) {\n return feature;\n }\n\n return {\n ...feature,\n properties: stampFeaturePropertiesEditorTracking(feature.properties, action, options),\n };\n}\n\n/**\n * Stamp editor tracking metadata onto all features in a {@link FeatureCollection}.\n */\nexport function stampFeatureCollectionEditorTracking(\n collection: FeatureCollection,\n action: \"create\" | \"update\",\n options?: EditorTrackingStampOptions,\n): FeatureCollection {\n const resolved = resolveEditorTrackingConfig(options?.config);\n if (!resolved.enabled) {\n return collection;\n }\n\n const timestamp = options?.timestamp ?? new Date().toISOString();\n const opts = { ...options, timestamp };\n\n return {\n ...collection,\n features: collection.features.map((feat) => stampFeatureEditorTracking(feat, action, opts)),\n };\n}\n","import { EDITOR_IDENTITY_STORAGE_KEY, pickEditorIdentity } from \"./editor-tracking\";\nimport { useAppStore } from \"./store\";\n\n/**\n * Runtime resolution of \"who is editing\", for the editor tracking stamps.\n *\n * Split from the pure `editor-tracking.ts` helpers because it reads two ambient\n * sources — the collaboration session in the store and `localStorage` — which\n * the stamping functions must stay free of so they remain unit-testable and\n * usable from Node (the MCP server authors projects without either).\n */\n\n/**\n * The display name this browser edits under, or `\"\"` when none is set.\n *\n * `localStorage` throws rather than returning null in a partitioned or\n * storage-blocked context (Safari private browsing, an embed in a third-party\n * iframe), so a failure here has to read as \"no name set\" — an editor whose\n * browser refuses storage must still be able to edit.\n */\nexport function readStoredAuthorName(): string {\n try {\n if (typeof localStorage === \"undefined\") return \"\";\n return localStorage.getItem(EDITOR_IDENTITY_STORAGE_KEY)?.trim() ?? \"\";\n } catch {\n return \"\";\n }\n}\n\n/**\n * Persist the display name to edit under. A blank name clears it, so the user\n * can go back to the anonymous default rather than being stuck with a typo.\n */\nexport function setStoredAuthorName(name: string): void {\n try {\n if (typeof localStorage === \"undefined\") return;\n const trimmed = name.trim();\n if (trimmed) {\n localStorage.setItem(EDITOR_IDENTITY_STORAGE_KEY, trimmed);\n } else {\n localStorage.removeItem(EDITOR_IDENTITY_STORAGE_KEY);\n }\n } catch {\n // Storage unavailable: the name applies to this session only.\n }\n}\n\n/**\n * The identity to stamp on edits made right now: the collaboration session's\n * name when one is active, else the locally configured name, else the anonymous\n * default.\n *\n * @returns A non-empty identity string.\n */\nexport function currentEditorIdentity(): string {\n const { isActive, selfName } = useAppStore.getState().collaboration;\n return pickEditorIdentity(isActive ? selfName : undefined, readStoredAuthorName());\n}\n","/**\n * Datum shifts (proj4 `+towgs84` Helmert parameters) for coordinate systems\n * whose datum is not WGS84-compatible, by EPSG code.\n *\n * The offline EPSG tables GeoLibre resolves codes with\n * (`geotiff-geokeys-to-proj4`) leave the shift out for many national grids, and\n * epsg.io's PROJJSON for a projected CRS carries no transformation either, so\n * proj4 would treat those datums as WGS84 and place data tens to hundreds of\n * metres off (about 100 m for the British National Grid). The parameters are\n * EPSG's (the transformation epsg.io's proj4 strings use), within a few\n * metres of PROJ's best transformation for each (15 m for the NAD27 mean);\n * grid-based transformations (OSTN15, NTv2, NADCON) are finer but need grid\n * files proj4 does not ship. Datums the tables already shift (ED50, AGD66,\n * AGD84, Tokyo, NZGD49, TM75 and others) are not listed.\n */\n\ninterface DatumShift {\n /** Datum name, for the record. */\n datum: string;\n /** EPSG datum codes (`GeogGeodeticDatumGeoKey`). */\n datums: number[];\n /**\n * The datum's names in a WKT `.prj`, without ESRI's `D_` prefix: the ESRI\n * name, and the OGC one where it differs. Matched ignoring case.\n */\n names: string[];\n /** EPSG geographic and projected CRS codes on the datum. */\n crs: number[];\n towgs84: string;\n}\n\n/** Inclusive ranges of codes, as a list. */\nconst range = (from: number, to: number) =>\n Array.from({ length: to - from + 1 }, (_, i) => from + i);\n\nconst SHIFTS: DatumShift[] = [\n {\n datum: \"OSGB 1936\",\n datums: [6277],\n names: [\"OSGB_1936\", \"Ordnance_Survey_of_Great_Britain_1936\"],\n crs: [4277, 27700],\n towgs84: \"446.448,-125.157,542.06,0.15,0.247,0.842,-20.489\",\n },\n {\n datum: \"TM65 (Ireland)\",\n datums: [6299],\n names: [\"TM65\"],\n crs: [4299, 29902],\n towgs84: \"482.5,-130.6,564.6,-1.042,-0.214,-0.631,8.15\",\n },\n {\n datum: \"DHDN\",\n datums: [6314],\n names: [\"Deutsches_Hauptdreiecksnetz\"],\n // 5676-5679: the Gauss-Kruger zones 2 to 5 with east-north axes.\n crs: [4314, 3068, ...range(31466, 31469), ...range(5676, 5679)],\n towgs84: \"598.1,73.7,418.2,0.202,0.045,-2.455,6.7\",\n },\n {\n datum: \"Amersfoort\",\n datums: [6289],\n names: [\"Amersfoort\"],\n crs: [4289, 28991, 28992],\n towgs84: \"565.417,50.3319,465.552,-0.398957,0.343988,-1.8774,4.0725\",\n },\n {\n datum: \"CH1903\",\n datums: [6149],\n names: [\"CH1903\"],\n crs: [4149, 21781, 21782],\n towgs84: \"674.4,15.1,405.3,0,0,0,0\",\n },\n {\n datum: \"CH1903+\",\n datums: [6150],\n names: [\"CH1903+\"],\n crs: [4150, 2056],\n towgs84: \"674.374,15.056,405.346,0,0,0,0\",\n },\n {\n datum: \"Belge 1972\",\n datums: [6313],\n names: [\"Belge_1972\", \"Reseau_National_Belge_1972\"],\n crs: [4313, 31300, 31370],\n towgs84: \"-106.8686,52.2978,-103.7239,0.3366,-0.457,1.8422,-1.2747\",\n },\n {\n datum: \"S-JTSK\",\n datums: [6156],\n names: [\"S_JTSK\", \"System_of_the_Unified_Trigonometrical_Cadastral_Network\"],\n crs: [4156, 5514],\n towgs84: \"589,76,480,0,0,0,0\",\n },\n {\n datum: \"Monte Mario\",\n datums: [6265],\n names: [\"Monte_Mario\"],\n // 5659: TM Emilia-Romagna, the region's own grid.\n crs: [4265, 3003, 3004, 5659],\n towgs84: \"-104.1,-49.1,-9.9,0.971,-2.917,0.714,-11.68\",\n },\n {\n datum: \"HD72\",\n datums: [6237],\n names: [\"Hungarian_1972\", \"Hungarian_Datum_1972\"],\n crs: [4237, 23700],\n towgs84: \"52.17,-71.82,-14.9,0,0,0,0\",\n },\n {\n datum: \"GGRS87\",\n datums: [6121],\n names: [\"GGRS_1987\", \"Greek_Geodetic_Reference_System_1987\"],\n crs: [4121, 2100],\n towgs84: \"-199.87,74.79,246.62,0,0,0,0\",\n },\n {\n datum: \"Hong Kong 1980\",\n datums: [6611],\n names: [\"Hong_Kong_1980\"],\n crs: [4611, 2326],\n towgs84: \"-162.619,-276.959,-161.764,0.067753,-2.24365,-1.15883,-1.09425\",\n },\n {\n // A conterminous-US mean, so only the UTM zones over it (10 N to 19 N),\n // not the geographic CRS or the datum, which span all of North America.\n datum: \"NAD27 (conterminous US mean)\",\n datums: [],\n names: [],\n crs: range(26710, 26719),\n towgs84: \"-8,160,176,0,0,0,0\",\n },\n];\n\nconst BY_CRS = new Map<number, string>();\nconst BY_DATUM = new Map<number, string>();\nconst BY_NAME = new Map<string, string>();\nfor (const shift of SHIFTS) {\n for (const code of shift.crs) BY_CRS.set(code, shift.towgs84);\n for (const code of shift.datums) BY_DATUM.set(code, shift.towgs84);\n for (const name of shift.names) BY_NAME.set(name.toLowerCase(), shift.towgs84);\n}\n\n/**\n * The `+towgs84` parameters for an EPSG CRS or datum code, or null when the\n * code is not one whose shift GeoLibre adds.\n *\n * @param crs An EPSG geographic or projected CRS code.\n * @param datum An EPSG datum code, used when the CRS code is unknown.\n */\nexport function datumShiftFor(\n crs: number | null | undefined,\n datum?: number | null,\n): string | null {\n return (\n (crs != null ? BY_CRS.get(crs) : undefined) ??\n (datum != null ? BY_DATUM.get(datum) : undefined) ??\n null\n );\n}\n\n/**\n * Definitions the EPSG tables get wrong, replaced whole, by CRS code. EPSG:31300\n * (Belge Lambert 72) uses Lambert Conic Conformal (2SP Belgium), which rotates the\n * grid by 29.2985\" (EPSG Guidance Note 7-2, §1.3.1.4). proj4 has no such method and\n * the tables give a plain LCC 2SP, about 745 m off. EPSG:31370 is the same grid\n * written as a plain LCC 2SP, so its parameters (from the same tables) stand in.\n */\nconst REPLACED = new Map<number, string>([\n [\n 31300,\n \"+proj=lcc +lat_0=90 +lon_0=4.367486666667 +lat_1=51.166667233333 +lat_2=49.8333339 +x_0=150000.013 +y_0=5400088.438 +ellps=intl +a=6378388 +b=6356911.9461279465 +pm=0 +no_defs\",\n ],\n]);\n\n/** GeoTIFF's \"user-defined\" geokey value. */\nconst USER_DEFINED = 32767;\n\n/** A geokey's numeric value, or null for a missing or user-defined one. */\nfunction keyCode(value: unknown): number | null {\n const code = Number(value);\n return Number.isInteger(code) && code > 0 && code !== USER_DEFINED ? code : null;\n}\n\n/**\n * Add the datum shift to a proj4 definition built for an EPSG CRS (or from a\n * GeoTIFF's geokeys), when the definition has none and the CRS is one whose\n * shift is known; a definition the tables get wrong (see `REPLACED`) is replaced\n * first. A definition that already names a datum transformation (`+towgs84`,\n * `+datum=`, `+nadgrids=`) is returned unchanged.\n *\n * @param definition A proj4 definition string.\n * @param crs The EPSG CRS code it was built for.\n * @param datum The EPSG datum code, for a user-defined CRS.\n */\nexport function withDatumShift(\n definition: string,\n crs: number | null | undefined,\n datum?: number | null,\n): string {\n if (/\\+(towgs84|datum|nadgrids)=/.test(definition)) return definition;\n const base = (crs != null ? REPLACED.get(crs) : undefined) ?? definition;\n const shift = datumShiftFor(crs, datum);\n return shift ? `${base.trim()} +towgs84=${shift}` : base;\n}\n\n/**\n * Add the datum shift to a proj4 definition built from GeoTIFF geokeys, from\n * the projected or geographic CRS code, else the datum code.\n *\n * @param definition The proj4 definition built from `geoKeys`.\n * @param geoKeys The GeoTIFF's geokeys (geotiff.js names).\n */\nexport function withGeoKeysDatumShift(\n definition: string,\n geoKeys: Record<string, unknown> | null | undefined,\n): string {\n if (!geoKeys) return definition;\n const crs =\n keyCode(geoKeys.ProjectedCSTypeGeoKey) ??\n keyCode(geoKeys.ProjectedCRSGeoKey) ??\n keyCode(geoKeys.GeographicTypeGeoKey) ??\n keyCode(geoKeys.GeodeticCRSGeoKey);\n const datum = keyCode(geoKeys.GeogGeodeticDatumGeoKey) ?? keyCode(geoKeys.GeodeticDatumGeoKey);\n return withDatumShift(definition, crs, datum);\n}\n\n/** The index just past the bracketed node that opens at `open` (a `[`), or -1. */\nfunction closingBracket(text: string, open: number): number {\n let depth = 0;\n for (let i = open; i < text.length; i += 1) {\n if (text[i] === \"[\") depth += 1;\n else if (text[i] === \"]\") {\n depth -= 1;\n if (depth === 0) return i + 1;\n }\n }\n return -1;\n}\n\n/**\n * Add the datum shift to a WKT 1 CRS (a shapefile's `.prj`) whose datum is one\n * whose shift is known, as a `TOWGS84` node after the datum's spheroid. An ESRI\n * `.prj` never carries one, and proj4 knows only a few datums by name, so\n * without it most national datums are read as WGS84 and the data lands 70 to\n * 325 m off. WKT that already has a `TOWGS84` is returned unchanged.\n *\n * @param wkt A WKT 1 CRS, as in a `.prj` file.\n */\nexport function withWktDatumShift(wkt: string): string {\n if (/\\bTOWGS84\\s*\\[/i.test(wkt)) return wkt;\n const datum = /\\bDATUM\\s*\\[\\s*\"([^\"]*)\"/i.exec(wkt);\n const shift = datum && BY_NAME.get(datum[1].replace(/^D_/i, \"\").toLowerCase());\n if (!datum || !shift) return wkt;\n const spheroid = /\\bSPHEROID\\s*\\[/i.exec(wkt.slice(datum.index));\n if (!spheroid) return wkt;\n const end = closingBracket(wkt, datum.index + spheroid.index + spheroid[0].length - 1);\n return end < 0 ? wkt : `${wkt.slice(0, end)},TOWGS84[${shift}]${wkt.slice(end)}`;\n}\n\n/** The part of `cog-tiler-wasm` the datum-shift hook needs (0.5 and later). */\ninterface CogTilerCrsHook {\n setSourceCrsResolver?: (\n fn: ((def: string, geoKeys: Record<string, unknown>) => string | null | undefined) | null,\n ) => void;\n}\n\n/**\n * Have `cog-tiler-wasm` add the datum shift to each COG's source CRS, so a\n * raster it tiles lands where the rest of GeoLibre puts it. Idempotent; a\n * tiler without the hook is left as it is. WKT definitions are left alone.\n *\n * @param module The imported `cog-tiler-wasm` module.\n */\nexport function installCogTilerDatumShift(module: CogTilerCrsHook): void {\n module.setSourceCrsResolver?.((def, geoKeys) =>\n def.trimStart().startsWith(\"+\") ? withGeoKeysDatumShift(def, geoKeys) : def,\n );\n}\n","// JPEG-in-TIFF tiles that omit their own tables (TIFF tag 347 holds them), which\n// cog-tiler-wasm's decoder rejects as \"use of unset quantization table\". Every\n// engine that opens a COG through the tiler (the MapLibre raster control, the\n// Cesium globe, the ArcGIS view) wraps its `openCog` with {@link\n// withJpegTablesPatch} so those tiles decode through geotiff.js instead.\n\nconst DEFAULT_COEFFICIENTS = [0.299, 0.587, 0.114] as const;\nconst DEFAULT_REFERENCE_BLACK_WHITE = [0, 255, 128, 255, 128, 255] as const;\n\ntype NumericArray = ArrayLike<number> | undefined;\n\nfunction finiteValues(values: NumericArray, length: number): number[] | null {\n if (!values || values.length < length) return null;\n const result = Array.from({ length }, (_, index) => Number(values[index]));\n return result.every(Number.isFinite) ? result : null;\n}\n\nfunction validCoefficients(values: NumericArray): number[] | null {\n const coefficients = finiteValues(values, 3);\n if (!coefficients) return null;\n const [kr, kg, kb] = coefficients;\n const sum = kr + kg + kb;\n return kr >= 0 && kg > 0 && kb >= 0 && kr <= 1 && kg <= 1 && kb <= 1 && Math.abs(sum - 1) <= 1e-6\n ? coefficients\n : null;\n}\n\nfunction validReference(values: NumericArray): number[] | null {\n const reference = finiteValues(values, 6);\n if (!reference) return null;\n return reference[1] > reference[0] && reference[3] > reference[2] && reference[5] > reference[4]\n ? reference\n : null;\n}\n\n/** Convert separate TIFF Y/Cb/Cr planes to RGB using tags 529 and 532. */\nexport function convertTiffYCbCrToRgb(\n yPlane: ArrayLike<number>,\n cbPlane: ArrayLike<number>,\n crPlane: ArrayLike<number>,\n coefficients?: NumericArray,\n referenceBlackWhite?: NumericArray,\n): [Float64Array, Float64Array, Float64Array] {\n const [kr, kg, kb] = validCoefficients(coefficients) ?? DEFAULT_COEFFICIENTS;\n const reference = validReference(referenceBlackWhite) ?? DEFAULT_REFERENCE_BLACK_WHITE;\n const [blackY, whiteY, blackCb, whiteCb, blackCr, whiteCr] = reference;\n const yRange = whiteY - blackY;\n const cbRange = whiteCb - blackCb;\n const crRange = whiteCr - blackCr;\n const size = Math.min(yPlane.length, cbPlane.length, crPlane.length);\n const red = new Float64Array(size);\n const green = new Float64Array(size);\n const blue = new Float64Array(size);\n\n for (let index = 0; index < size; index += 1) {\n const y = ((Number(yPlane[index]) - blackY) * 255) / yRange;\n const cb = ((Number(cbPlane[index]) - blackCb) * 127) / cbRange;\n const cr = ((Number(crPlane[index]) - blackCr) * 127) / crRange;\n const r = y + (2 - 2 * kr) * cr;\n const b = y + (2 - 2 * kb) * cb;\n const g = (y - kr * r - kb * b) / kg;\n red[index] = Math.max(0, Math.min(255, r));\n green[index] = Math.max(0, Math.min(255, g));\n blue[index] = Math.max(0, Math.min(255, b));\n }\n\n return [red, green, blue];\n}\n\n/**\n * Whether a cog-tiler-wasm level's `compression` is baseline JPEG, the only\n * codec whose tiles may omit their tables (TIFF tag 347) and so need the\n * geotiff.js window read in {@link patchJpegCogSource}. The string is whitebox-wasm's\n * enum name, so match `Jpeg`/`OldJpeg` exactly: a looser `/jpeg/i` would also\n * catch `JpegXl`, which the wasm decoder handles and geotiff.js cannot decode\n * at all (#2339).\n */\nexport function isAbbreviatedJpegCompression(compression: string | undefined): boolean {\n return /^(old)?jpeg$/i.test(compression ?? \"\");\n}\n\ntype GeoTiffImage = {\n fileDirectory?: {\n PhotometricInterpretation?: number;\n getValue?: (tag: number) => unknown;\n hasTag?: (tag: number) => boolean;\n loadValue?: (tag: number) => Promise<unknown>;\n };\n readRasters: (options: {\n window: [number, number, number, number];\n }) => Promise<ArrayLike<ArrayLike<number>>>;\n};\ntype CogSourceInternals = {\n levels?: Array<{ compression?: string }>;\n tiff?: unknown;\n _tiffImage?: (level: number) => Promise<GeoTiffImage>;\n _assembleWindow?: (\n level: number,\n x: number,\n y: number,\n width: number,\n height: number,\n band?: number,\n ) => Promise<ArrayLike<number>>;\n geolibreJpegTablesPatched?: boolean;\n};\n\n/**\n * Route an abbreviated-JPEG COG's tile windows through geotiff.js, which reads\n * the shared tables, converting Y/Cb/Cr to the RGB bands the renderers expect.\n * Any other source is returned unchanged.\n *\n * @param source - A cog-tiler-wasm source.\n * @returns The same source, patched in place when it needs it.\n */\nexport function patchJpegCogSource(source: unknown): unknown {\n const cog = source as CogSourceInternals;\n if (\n cog.geolibreJpegTablesPatched ||\n !isAbbreviatedJpegCompression(cog.levels?.[0]?.compression) ||\n !cog.tiff ||\n !cog._tiffImage ||\n !cog._assembleWindow\n ) {\n return source;\n }\n\n const windowCache = new Map<string, Promise<ArrayLike<ArrayLike<number>>>>();\n cog._assembleWindow = async (level, x, y, width, height, band = 0) => {\n const key = `${level}/${x}/${y}/${width}/${height}`;\n let decoded = windowCache.get(key);\n if (decoded) {\n // Re-insert on a hit so the size cap below evicts the least recently used.\n windowCache.delete(key);\n windowCache.set(key, decoded);\n } else {\n decoded = cog._tiffImage!(level).then(async (image) => {\n const rasters = await image.readRasters({\n window: [x, y, x + width, y + height],\n });\n // geotiff.js expands the chroma subsampling but deliberately returns\n // the TIFF's native Y/Cb/Cr samples. The renderer expects RGB bands,\n // like the GPU engine, so perform the TIFF/JPEG color transform once\n // for the shared three-band window.\n const photometric =\n image.fileDirectory?.PhotometricInterpretation ?? image.fileDirectory?.getValue?.(262);\n if (photometric !== 6 || rasters.length < 3) {\n return rasters;\n }\n const directory = image.fileDirectory;\n const readTag = async (tag: number): Promise<ArrayLike<number> | undefined> => {\n if (directory?.hasTag?.(tag) === false) return undefined;\n const value = directory?.loadValue\n ? await directory.loadValue(tag)\n : directory?.getValue?.(tag);\n return value as ArrayLike<number> | undefined;\n };\n const [coefficients, referenceBlackWhite] = await Promise.all([readTag(529), readTag(532)]);\n // Baseline JPEG is 8-bit, so narrow the clamped float planes to one\n // byte per sample before caching: a window held as Float64Array costs\n // 8x the memory across the whole cache.\n return convertTiffYCbCrToRgb(\n rasters[0],\n rasters[1],\n rasters[2],\n coefficients,\n referenceBlackWhite,\n ).map((plane) => Uint8ClampedArray.from(plane));\n });\n windowCache.set(key, decoded);\n void decoded.catch(() => {\n if (windowCache.get(key) === decoded) windowCache.delete(key);\n });\n if (windowCache.size > 32) windowCache.delete(windowCache.keys().next().value!);\n }\n const rasters = await decoded;\n return rasters[band] ?? rasters[0];\n };\n cog.geolibreJpegTablesPatched = true;\n return source;\n}\n\n/**\n * A cog-tiler module whose `openCog` returns {@link patchJpegCogSource}-patched\n * sources; the rest of the module is passed through.\n *\n * @param module - The cog-tiler-wasm module (or a test double).\n * @returns The wrapped module.\n */\nexport function withJpegTablesPatch<M extends { openCog: (input: never) => Promise<unknown> }>(\n module: M,\n): M {\n return {\n ...module,\n openCog: (async (input: never) =>\n patchJpegCogSource(await module.openCog(input))) as M[\"openCog\"],\n };\n}\n"],"mappings":"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"}
|