@ttoss/geovis
Advanced tools
| /** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */ | ||
| import { __name, applyMapDataPatchToSpec, buildFillColorExpression, coerceGeometryId } from "./chunk-QMEVH6NT.js"; | ||
| // src/adapters/maplibre/MapLibreAdapter.ts | ||
| import "maplibre-gl/dist/maplibre-gl.css"; | ||
| import { log } from "@ttoss/logger"; | ||
| import maplibregl from "maplibre-gl"; | ||
| // src/adapters/maplibre/layerTranslation.ts | ||
| var buildBase = /* @__PURE__ */__name((layer, sourceLayer) => { | ||
| const base = { | ||
| id: layer.id, | ||
| source: layer.sourceId, | ||
| minzoom: layer.minzoom, | ||
| maxzoom: layer.maxzoom, | ||
| layout: { | ||
| visibility: layer.visible === false ? "none" : "visible" | ||
| } | ||
| }; | ||
| const effective = layer.sourceLayer ?? sourceLayer; | ||
| if (effective) base["source-layer"] = effective; | ||
| return base; | ||
| }, "buildBase"); | ||
| var resolveThresholdBreaks = /* @__PURE__ */__name((layer, specLegends) => { | ||
| const legend = layer.legends?.find(item => { | ||
| return item.id === layer.activeLegendId; | ||
| }) ?? specLegends?.find(item => { | ||
| return item.id === layer.activeLegendId; | ||
| }); | ||
| if (!legend || legend.colorBy.type !== "quantitative") return []; | ||
| if (legend.colorBy.scale !== "threshold") return []; | ||
| return Array.from(new Set((legend.colorBy.thresholds ?? []).filter(value => { | ||
| return Number.isFinite(value); | ||
| }))).sort((a, b) => { | ||
| return a - b; | ||
| }); | ||
| }, "resolveThresholdBreaks"); | ||
| var resolveLegendFillColorExpression = /* @__PURE__ */__name((layer, specLegends) => { | ||
| if (layer.geometry !== "polygon") return void 0; | ||
| if (!layer.activeLegendId) return void 0; | ||
| const activeLegend = layer.legends?.find(legend => { | ||
| return legend.id === layer.activeLegendId; | ||
| }) ?? specLegends?.find(legend => { | ||
| return legend.id === layer.activeLegendId; | ||
| }); | ||
| if (!activeLegend) return void 0; | ||
| return buildFillColorExpression({ | ||
| legend: activeLegend, | ||
| breaks: resolveThresholdBreaks(layer, specLegends) | ||
| }); | ||
| }, "resolveLegendFillColorExpression"); | ||
| var buildPolygon = /* @__PURE__ */__name((base, layer, paint, specLegends) => { | ||
| const fp = paint ?? {}; | ||
| const legendFillColor = resolveLegendFillColorExpression(layer, specLegends); | ||
| return { | ||
| ...base, | ||
| type: "fill", | ||
| paint: { | ||
| "fill-color": legendFillColor ?? fp.fillColor ?? "#3b82f6", | ||
| "fill-opacity": fp.fillOpacity ?? 0.6, | ||
| "fill-outline-color": fp.lineColor ?? "#1d4ed8" | ||
| } | ||
| }; | ||
| }, "buildPolygon"); | ||
| var buildLine = /* @__PURE__ */__name((base, _layer, paint) => { | ||
| const lp = paint ?? {}; | ||
| return { | ||
| ...base, | ||
| type: "line", | ||
| paint: { | ||
| "line-color": lp.lineColor ?? "#3b82f6", | ||
| "line-width": lp.lineWidth ?? 2, | ||
| "line-opacity": lp.lineOpacity ?? 1, | ||
| "line-dasharray": lp.lineDasharray | ||
| } | ||
| }; | ||
| }, "buildLine"); | ||
| var buildPoint = /* @__PURE__ */__name((base, _layer, paint) => { | ||
| const cp = paint ?? {}; | ||
| return { | ||
| ...base, | ||
| type: "circle", | ||
| paint: { | ||
| "circle-color": cp.circleColor ?? "#3b82f6", | ||
| "circle-radius": cp.circleRadius ?? 6, | ||
| "circle-opacity": cp.circleOpacity ?? 1, | ||
| "circle-stroke-color": cp.circleStrokeColor ?? "#ffffff", | ||
| "circle-stroke-width": cp.circleStrokeWidth ?? 1 | ||
| } | ||
| }; | ||
| }, "buildPoint"); | ||
| var buildHeatmap = /* @__PURE__ */__name((base, _layer, paint) => { | ||
| const hp = paint ?? {}; | ||
| return { | ||
| ...base, | ||
| type: "heatmap", | ||
| paint: { | ||
| "heatmap-radius": hp.heatmapRadius ?? 15, | ||
| "heatmap-opacity": hp.heatmapOpacity ?? 1, | ||
| "heatmap-intensity": hp.heatmapIntensity ?? 1, | ||
| "heatmap-weight": hp.heatmapWeight ?? 1 | ||
| } | ||
| }; | ||
| }, "buildHeatmap"); | ||
| var buildSymbol = /* @__PURE__ */__name((base, _layer, paint) => { | ||
| const sp = paint ?? {}; | ||
| return { | ||
| ...base, | ||
| type: "symbol", | ||
| layout: { | ||
| ...base.layout, | ||
| "text-field": sp.textField ?? "", | ||
| "text-size": sp.textSize ?? 12, | ||
| "icon-image": sp.iconImage | ||
| }, | ||
| paint: { | ||
| "text-color": sp.textColor ?? "#000000", | ||
| "text-opacity": sp.textOpacity ?? 1, | ||
| "text-halo-color": sp.textHaloColor ?? "#ffffff", | ||
| "text-halo-width": sp.textHaloWidth ?? 0, | ||
| "icon-color": sp.iconColor ?? "#000000", | ||
| "icon-opacity": sp.iconOpacity ?? 1 | ||
| } | ||
| }; | ||
| }, "buildSymbol"); | ||
| var buildRaster = /* @__PURE__ */__name((base, _layer, paint) => { | ||
| const rp = paint ?? {}; | ||
| return { | ||
| ...base, | ||
| type: "raster", | ||
| paint: { | ||
| "raster-opacity": rp.rasterOpacity ?? 1 | ||
| } | ||
| }; | ||
| }, "buildRaster"); | ||
| var builders = { | ||
| polygon: buildPolygon, | ||
| line: buildLine, | ||
| point: buildPoint, | ||
| heatmap: buildHeatmap, | ||
| symbol: buildSymbol, | ||
| raster: buildRaster | ||
| }; | ||
| var stripUndefinedPaint = /* @__PURE__ */__name(layer => { | ||
| const paint = layer.paint; | ||
| if (paint) { | ||
| layer.paint = Object.fromEntries(Object.entries(paint).filter(([, v]) => { | ||
| return v !== void 0; | ||
| })); | ||
| } | ||
| return layer; | ||
| }, "stripUndefinedPaint"); | ||
| var toMaplibreLayer = /* @__PURE__ */__name((layer, sourceLayer, specLegends) => { | ||
| const base = buildBase(layer, sourceLayer); | ||
| return builders[layer.geometry](base, layer, layer.paint, specLegends); | ||
| }, "toMaplibreLayer"); | ||
| // src/adapters/maplibre/legendFillPaint.ts | ||
| var pendingStyleListeners = /* @__PURE__ */new WeakMap(); | ||
| var cancelPendingStyleListenersForLayer = /* @__PURE__ */__name((map, layerId) => { | ||
| const byKey = pendingStyleListeners.get(map); | ||
| if (!byKey) return; | ||
| for (const [key, listener] of byKey) { | ||
| if (key.startsWith(`${layerId}:`)) { | ||
| map.off("styledata", listener); | ||
| byKey.delete(key); | ||
| } | ||
| } | ||
| }, "cancelPendingStyleListenersForLayer"); | ||
| var setPaintWhenReady = /* @__PURE__ */__name((map, layerId, property, value) => { | ||
| const apply = /* @__PURE__ */__name(() => { | ||
| if (!map.getLayer(layerId)) return false; | ||
| map.setPaintProperty(layerId, property, value); | ||
| return true; | ||
| }, "apply"); | ||
| const applyWhenLayerAppears = /* @__PURE__ */__name(() => { | ||
| const listenerKey = `${layerId}:${property}`; | ||
| let byKey = pendingStyleListeners.get(map); | ||
| if (!byKey) { | ||
| byKey = /* @__PURE__ */new Map(); | ||
| pendingStyleListeners.set(map, byKey); | ||
| } | ||
| const existing = byKey.get(listenerKey); | ||
| if (existing) { | ||
| map.off("styledata", existing); | ||
| } | ||
| const onStyleData = /* @__PURE__ */__name(() => { | ||
| const applied = apply(); | ||
| if (!applied) return; | ||
| map.off("styledata", onStyleData); | ||
| pendingStyleListeners.get(map)?.delete(listenerKey); | ||
| }, "onStyleData"); | ||
| byKey.set(listenerKey, onStyleData); | ||
| map.on("styledata", onStyleData); | ||
| }, "applyWhenLayerAppears"); | ||
| if (map.isStyleLoaded()) { | ||
| const applied = apply(); | ||
| if (!applied) applyWhenLayerAppears(); | ||
| return; | ||
| } | ||
| map.once("style.load", () => { | ||
| const applied = apply(); | ||
| if (!applied) applyWhenLayerAppears(); | ||
| }); | ||
| }, "setPaintWhenReady"); | ||
| var reapplyLegendDrivenFillPaint = /* @__PURE__ */__name((map, spec) => { | ||
| for (const layer of spec.layers) { | ||
| if (layer.geometry !== "polygon") continue; | ||
| const expression = resolveLegendFillColorExpression(layer, spec.legends); | ||
| if (!expression) continue; | ||
| setPaintWhenReady(map, layer.id, "fill-color", expression); | ||
| } | ||
| }, "reapplyLegendDrivenFillPaint"); | ||
| // src/adapters/maplibre/mapDataFeatureState.ts | ||
| var pendingListeners = /* @__PURE__ */new WeakMap(); | ||
| var cancelPendingListener = /* @__PURE__ */__name((map, mapDataId) => { | ||
| const byId = pendingListeners.get(map); | ||
| const listener = byId?.get(mapDataId); | ||
| if (!byId || !listener) return; | ||
| map.off("sourcedata", listener); | ||
| byId.delete(mapDataId); | ||
| }, "cancelPendingListener"); | ||
| var buildJoinKeyIndex = /* @__PURE__ */__name((features, joinKey) => { | ||
| const index = /* @__PURE__ */new Map(); | ||
| for (const f of features) { | ||
| const k = f.properties?.[joinKey]; | ||
| if (k == null || f.id == null) continue; | ||
| index.set(String(k), f.id); | ||
| } | ||
| return index; | ||
| }, "buildJoinKeyIndex"); | ||
| var applyMapDataToSource = /* @__PURE__ */__name((map, mapData) => { | ||
| if (!map.getSource(mapData.mapId)) return; | ||
| if (!mapData.joinKey) { | ||
| for (const row of mapData.data) { | ||
| const safeValue = typeof row.value === "number" && !Number.isFinite(row.value) ? 0 : row.value; | ||
| map.setFeatureState({ | ||
| source: mapData.mapId, | ||
| id: coerceGeometryId(row.geometryId) | ||
| }, { | ||
| value: safeValue | ||
| }); | ||
| } | ||
| return; | ||
| } | ||
| const features = map.querySourceFeatures(mapData.mapId); | ||
| const idByJoinKey = buildJoinKeyIndex(features, mapData.joinKey); | ||
| for (const row of mapData.data) { | ||
| const fid = idByJoinKey.get(String(row.geometryId)); | ||
| if (fid == null) continue; | ||
| const safeValue = typeof row.value === "number" && !Number.isFinite(row.value) ? 0 : row.value; | ||
| map.setFeatureState({ | ||
| source: mapData.mapId, | ||
| id: fid | ||
| }, { | ||
| value: safeValue | ||
| }); | ||
| } | ||
| }, "applyMapDataToSource"); | ||
| var scheduleMapDataApply = /* @__PURE__ */__name((map, mapData) => { | ||
| if (map.isSourceLoaded(mapData.mapId)) { | ||
| applyMapDataToSource(map, mapData); | ||
| return; | ||
| } | ||
| cancelPendingListener(map, mapData.mapDataId); | ||
| const listener = /* @__PURE__ */__name(e => { | ||
| if (e.sourceId !== mapData.mapId || !e.isSourceLoaded) return; | ||
| applyMapDataToSource(map, mapData); | ||
| map.off("sourcedata", listener); | ||
| pendingListeners.get(map)?.delete(mapData.mapDataId); | ||
| }, "listener"); | ||
| if (!pendingListeners.has(map)) pendingListeners.set(map, /* @__PURE__ */new Map()); | ||
| pendingListeners.get(map).set(mapData.mapDataId, listener); | ||
| map.on("sourcedata", listener); | ||
| }, "scheduleMapDataApply"); | ||
| var removeMapDataFromSource = /* @__PURE__ */__name((map, mapData) => { | ||
| cancelPendingListener(map, mapData.mapDataId); | ||
| if (!map.getSource(mapData.mapId)) return; | ||
| if (!mapData.joinKey) { | ||
| for (const row of mapData.data) { | ||
| map.removeFeatureState({ | ||
| source: mapData.mapId, | ||
| id: coerceGeometryId(row.geometryId) | ||
| }); | ||
| } | ||
| return; | ||
| } | ||
| if (!map.isSourceLoaded(mapData.mapId)) return; | ||
| const features = map.querySourceFeatures(mapData.mapId); | ||
| const idByJoinKey = buildJoinKeyIndex(features, mapData.joinKey); | ||
| for (const row of mapData.data) { | ||
| const fid = idByJoinKey.get(String(row.geometryId)); | ||
| if (fid == null) continue; | ||
| map.removeFeatureState({ | ||
| source: mapData.mapId, | ||
| id: fid | ||
| }); | ||
| } | ||
| }, "removeMapDataFromSource"); | ||
| var reapplyAllMapData = /* @__PURE__ */__name((map, spec) => { | ||
| for (const md of spec.mapData ?? []) scheduleMapDataApply(map, md); | ||
| }, "reapplyAllMapData"); | ||
| var applyRowReplacement = /* @__PURE__ */__name((map, mapData, geometryId, value) => { | ||
| const safeValue = typeof value === "number" && !Number.isFinite(value) ? 0 : value; | ||
| if (!mapData.joinKey) { | ||
| const row = mapData.data.find(r => { | ||
| return String(r.geometryId) === geometryId; | ||
| }); | ||
| const featureId = row?.geometryId ?? coerceGeometryId(geometryId); | ||
| map.setFeatureState({ | ||
| source: mapData.mapId, | ||
| id: featureId | ||
| }, { | ||
| value: safeValue | ||
| }); | ||
| return; | ||
| } | ||
| if (!map.isSourceLoaded(mapData.mapId)) return; | ||
| const f = map.querySourceFeatures(mapData.mapId).find(feat => { | ||
| const k = feat.properties?.[mapData.joinKey]; | ||
| return k != null && String(k) === geometryId; | ||
| }); | ||
| if (f?.id != null) { | ||
| map.setFeatureState({ | ||
| source: mapData.mapId, | ||
| id: f.id | ||
| }, { | ||
| value: safeValue | ||
| }); | ||
| } | ||
| }, "applyRowReplacement"); | ||
| var handleMapDataReplace = /* @__PURE__ */__name((map, currentMapData, patch) => { | ||
| if (!patch.path) return; | ||
| const parts = patch.path.split("."); | ||
| if (parts.length < 2 || parts[0] !== "mapData") return; | ||
| const target = currentMapData.find(md => { | ||
| return md.mapDataId === parts[1]; | ||
| }); | ||
| if (!target) return; | ||
| if (parts.length === 2 && patch.value != null) { | ||
| removeMapDataFromSource(map, target); | ||
| scheduleMapDataApply(map, patch.value); | ||
| return; | ||
| } | ||
| if (parts.length === 4 && parts[2] === "data") { | ||
| applyRowReplacement(map, target, parts[3], patch.value); | ||
| } | ||
| }, "handleMapDataReplace"); | ||
| var applyMapDataPatchToMap = /* @__PURE__ */__name((map, currentMapData, patch) => { | ||
| if (patch.op === "add" && patch.value != null) { | ||
| scheduleMapDataApply(map, patch.value); | ||
| return; | ||
| } | ||
| if (patch.op === "remove") { | ||
| const mapDataId = patch.value; | ||
| cancelPendingListener(map, mapDataId); | ||
| const target = currentMapData.find(md => { | ||
| return md.mapDataId === mapDataId; | ||
| }); | ||
| if (target) removeMapDataFromSource(map, target); | ||
| return; | ||
| } | ||
| if (patch.op === "replace") { | ||
| handleMapDataReplace(map, currentMapData, patch); | ||
| } | ||
| }, "applyMapDataPatchToMap"); | ||
| // src/adapters/maplibre/paintKeyMap.ts | ||
| var SPEC_PAINT_KEY_MAP = { | ||
| fillColor: "fill-color", | ||
| fillOpacity: "fill-opacity", | ||
| lineColor: /* @__PURE__ */__name(g => { | ||
| return g === "polygon" ? "fill-outline-color" : "line-color"; | ||
| }, "lineColor"), | ||
| lineWidth: /* @__PURE__ */__name(g => { | ||
| return g === "polygon" ? void 0 : "line-width"; | ||
| }, "lineWidth"), | ||
| lineOpacity: "line-opacity", | ||
| lineDasharray: "line-dasharray", | ||
| circleColor: "circle-color", | ||
| circleRadius: "circle-radius", | ||
| circleOpacity: "circle-opacity", | ||
| circleStrokeColor: "circle-stroke-color", | ||
| circleStrokeWidth: "circle-stroke-width", | ||
| rasterOpacity: "raster-opacity", | ||
| heatmapRadius: "heatmap-radius", | ||
| heatmapOpacity: "heatmap-opacity", | ||
| heatmapIntensity: "heatmap-intensity", | ||
| heatmapWeight: "heatmap-weight", | ||
| textColor: "text-color", | ||
| textOpacity: "text-opacity", | ||
| textHaloColor: "text-halo-color", | ||
| textHaloWidth: "text-halo-width", | ||
| iconColor: "icon-color", | ||
| iconOpacity: "icon-opacity" | ||
| }; | ||
| var specPaintKeyToMaplibre = /* @__PURE__ */__name((key, geometry) => { | ||
| const entry = SPEC_PAINT_KEY_MAP[key]; | ||
| if (!entry) return void 0; | ||
| return typeof entry === "function" ? entry(geometry) : entry; | ||
| }, "specPaintKeyToMaplibre"); | ||
| // src/adapters/maplibre/sourceTranslation.ts | ||
| var sourceConverters = { | ||
| geojson: /* @__PURE__ */__name((source, opts) => { | ||
| return { | ||
| type: "geojson", | ||
| data: source.data, | ||
| attribution: source.attribution, | ||
| // `promoteId` lifts a feature property to the synthetic `feature.id` | ||
| // used by `setFeatureState`. Required when the underlying GeoJSON has | ||
| // duplicate or missing ids and a `mapData.joinKey` identifies the row. | ||
| // Without it, all features collapse to `id: 0` and `setFeatureState` | ||
| // overwrites itself, producing a single colour for the whole layer. | ||
| ...(opts.promoteId ? { | ||
| promoteId: opts.promoteId | ||
| } : {}) | ||
| }; | ||
| }, "geojson"), | ||
| "vector-tiles": /* @__PURE__ */__name((source, opts) => { | ||
| return { | ||
| type: "vector", | ||
| tiles: source.tiles, | ||
| minzoom: source.minzoom, | ||
| maxzoom: source.maxzoom, | ||
| attribution: source.attribution, | ||
| ...(opts.promoteId ? { | ||
| promoteId: opts.promoteId | ||
| } : {}) | ||
| }; | ||
| }, "vector-tiles"), | ||
| "raster-tiles": /* @__PURE__ */__name((source, _opts) => { | ||
| return { | ||
| type: "raster", | ||
| tiles: source.tiles, | ||
| tileSize: source.tileSize ?? 256, | ||
| minzoom: source.minzoom, | ||
| maxzoom: source.maxzoom, | ||
| attribution: source.attribution | ||
| }; | ||
| }, "raster-tiles"), | ||
| image: /* @__PURE__ */__name((source, _opts) => { | ||
| return { | ||
| type: "image", | ||
| url: source.url, | ||
| coordinates: source.coordinates | ||
| }; | ||
| }, "image"), | ||
| "raster-dem": /* @__PURE__ */__name((source, _opts) => { | ||
| return { | ||
| type: "raster-dem", | ||
| tiles: source.tiles, | ||
| url: source.url, | ||
| tileSize: source.tileSize ?? 256, | ||
| encoding: source.encoding, | ||
| minzoom: source.minzoom, | ||
| maxzoom: source.maxzoom, | ||
| attribution: source.attribution | ||
| }; | ||
| }, "raster-dem"), | ||
| video: /* @__PURE__ */__name((source, _opts) => { | ||
| return { | ||
| type: "video", | ||
| urls: source.urls, | ||
| coordinates: source.coordinates | ||
| }; | ||
| }, "video") | ||
| }; | ||
| var toMaplibreSource = /* @__PURE__ */__name((source, opts = {}) => { | ||
| const fn = sourceConverters[source.type]; | ||
| return fn(source, opts); | ||
| }, "toMaplibreSource"); | ||
| var resolvePromoteIdForSource = /* @__PURE__ */__name((spec, sourceId) => { | ||
| for (const entry of spec.mapData ?? []) { | ||
| if (entry.mapId === sourceId && entry.joinKey) return entry.joinKey; | ||
| } | ||
| return void 0; | ||
| }, "resolvePromoteIdForSource"); | ||
| // src/adapters/maplibre/patchDispatch.ts | ||
| var applyLayerAdd = /* @__PURE__ */__name((map, viewState, newLayer) => { | ||
| if (map.getLayer(newLayer.id)) return; | ||
| const source = viewState.spec.sources.find(s => { | ||
| return s.id === newLayer.sourceId; | ||
| }); | ||
| const effectiveSourceLayer = newLayer.sourceLayer ?? (source && "sourceLayer" in source ? source.sourceLayer : void 0); | ||
| map.addLayer(stripUndefinedPaint(toMaplibreLayer(newLayer, effectiveSourceLayer, viewState.spec.legends))); | ||
| viewState.spec = { | ||
| ...viewState.spec, | ||
| layers: [...viewState.spec.layers, newLayer] | ||
| }; | ||
| }, "applyLayerAdd"); | ||
| var applyLayerRemove = /* @__PURE__ */__name((map, viewState, layerId) => { | ||
| cancelPendingStyleListenersForLayer(map, layerId); | ||
| if (map.getLayer(layerId)) map.removeLayer(layerId); | ||
| viewState.spec = { | ||
| ...viewState.spec, | ||
| layers: viewState.spec.layers.filter(l => { | ||
| return l.id !== layerId; | ||
| }) | ||
| }; | ||
| }, "applyLayerRemove"); | ||
| var applyLayerPaintReplace = /* @__PURE__ */__name((map, viewState, path, value) => { | ||
| const parts = path.split("."); | ||
| if (parts.length < 4 || parts[2] !== "paint") return; | ||
| const layerId = parts[1]; | ||
| const specKey = parts[3]; | ||
| const layer = viewState.spec.layers.find(l => { | ||
| return l.id === layerId; | ||
| }); | ||
| if (!layer) return; | ||
| const maplibreKey = specPaintKeyToMaplibre(specKey, layer.geometry); | ||
| if (!maplibreKey) return; | ||
| setPaintWhenReady(map, layerId, maplibreKey, value); | ||
| }, "applyLayerPaintReplace"); | ||
| var applyLayerPatch = /* @__PURE__ */__name((map, viewState, patch) => { | ||
| if (patch.op === "add" && patch.value != null) { | ||
| applyLayerAdd(map, viewState, patch.value); | ||
| return; | ||
| } | ||
| if (patch.op === "remove") { | ||
| applyLayerRemove(map, viewState, patch.value); | ||
| return; | ||
| } | ||
| if (patch.op === "replace" && patch.value !== void 0) { | ||
| applyLayerPaintReplace(map, viewState, patch.path, patch.value); | ||
| } | ||
| }, "applyLayerPatch"); | ||
| var applySourcePatch = /* @__PURE__ */__name((map, viewState, patch) => { | ||
| if (patch.op === "add" && patch.value != null) { | ||
| const newSource = patch.value; | ||
| if (map.getSource(newSource.id)) return; | ||
| map.addSource(newSource.id, toMaplibreSource(newSource, { | ||
| promoteId: resolvePromoteIdForSource(viewState.spec, newSource.id) | ||
| })); | ||
| viewState.spec = { | ||
| ...viewState.spec, | ||
| sources: [...viewState.spec.sources, newSource] | ||
| }; | ||
| return; | ||
| } | ||
| if (patch.op !== "remove") return; | ||
| const sourceId = patch.value; | ||
| if (map.getSource(sourceId)) { | ||
| for (const layer of viewState.spec.layers) { | ||
| if (layer.sourceId === sourceId && map.getLayer(layer.id)) { | ||
| cancelPendingStyleListenersForLayer(map, layer.id); | ||
| map.removeLayer(layer.id); | ||
| } | ||
| } | ||
| map.removeSource(sourceId); | ||
| } | ||
| viewState.spec = { | ||
| ...viewState.spec, | ||
| layers: viewState.spec.layers.filter(l => { | ||
| return l.sourceId !== sourceId; | ||
| }), | ||
| sources: viewState.spec.sources.filter(s => { | ||
| return s.id !== sourceId; | ||
| }) | ||
| }; | ||
| }, "applySourcePatch"); | ||
| // src/adapters/maplibre/syncSourcesAndLayers.ts | ||
| var removeStaleLayers = /* @__PURE__ */__name((map, spec, previousSpec) => { | ||
| for (const layer of previousSpec.layers) { | ||
| const stillExists = spec.layers.some(nextLayer => { | ||
| return nextLayer.id === layer.id; | ||
| }); | ||
| if (!stillExists && map.getLayer(layer.id)) { | ||
| map.removeLayer(layer.id); | ||
| } | ||
| } | ||
| }, "removeStaleLayers"); | ||
| var removeStaleSources = /* @__PURE__ */__name((map, spec, previousSpec) => { | ||
| for (const source of previousSpec.sources) { | ||
| const stillExists = spec.sources.some(nextSource => { | ||
| return nextSource.id === source.id; | ||
| }); | ||
| if (!stillExists && map.getSource(source.id)) { | ||
| map.removeSource(source.id); | ||
| } | ||
| } | ||
| }, "removeStaleSources"); | ||
| var upsertSources = /* @__PURE__ */__name((map, spec, previousSpec) => { | ||
| for (const source of spec.sources) { | ||
| if (!map.getSource(source.id)) { | ||
| map.addSource(source.id, toMaplibreSource(source, { | ||
| promoteId: resolvePromoteIdForSource(spec, source.id) | ||
| })); | ||
| continue; | ||
| } | ||
| if (source.type !== "geojson") continue; | ||
| const prevSource = previousSpec?.sources.find(s => { | ||
| return s.id === source.id; | ||
| }); | ||
| const prevData = prevSource?.type === "geojson" ? prevSource.data : void 0; | ||
| if (prevData !== source.data) { | ||
| map.getSource(source.id).setData(source.data); | ||
| } | ||
| } | ||
| }, "upsertSources"); | ||
| var writePaintProperty = /* @__PURE__ */__name((map, spec, layer, property, value) => { | ||
| if (property === "fill-color" && layer.mapDataId) { | ||
| const hasExplicitFillColor = !!layer.paint?.fillColor; | ||
| const hasLegendFillColor = resolveLegendFillColorExpression(layer, spec.legends) !== void 0; | ||
| if (!hasExplicitFillColor && !hasLegendFillColor) return; | ||
| } | ||
| map.setPaintProperty(layer.id, property, value); | ||
| }, "writePaintProperty"); | ||
| var upsertLayers = /* @__PURE__ */__name((map, spec) => { | ||
| for (const layer of spec.layers) { | ||
| const source = spec.sources.find(s => { | ||
| return s.id === layer.sourceId; | ||
| }); | ||
| const sourceLayer = source && "sourceLayer" in source ? source.sourceLayer : void 0; | ||
| const desiredLayer = toMaplibreLayer(layer, sourceLayer, spec.legends); | ||
| stripUndefinedPaint(desiredLayer); | ||
| if (!map.getLayer(layer.id)) { | ||
| map.addLayer(desiredLayer); | ||
| continue; | ||
| } | ||
| map.setLayoutProperty(layer.id, "visibility", layer.visible === false ? "none" : "visible"); | ||
| const paint = desiredLayer.paint; | ||
| if (!paint) continue; | ||
| for (const [property, value] of Object.entries(paint)) { | ||
| writePaintProperty(map, spec, layer, property, value); | ||
| } | ||
| } | ||
| }, "upsertLayers"); | ||
| var syncSourcesAndLayers = /* @__PURE__ */__name((map, spec, previousSpec) => { | ||
| if (previousSpec) { | ||
| removeStaleLayers(map, spec, previousSpec); | ||
| removeStaleSources(map, spec, previousSpec); | ||
| } | ||
| upsertSources(map, spec, previousSpec); | ||
| upsertLayers(map, spec); | ||
| }, "syncSourcesAndLayers"); | ||
| // src/adapters/maplibre/MapLibreAdapter.ts | ||
| var DEFAULT_STYLE = "https://demotiles.maplibre.org/style.json"; | ||
| var BLANK_STYLE = { | ||
| version: 8, | ||
| sources: {}, | ||
| layers: [] | ||
| }; | ||
| var resolveStyle = /* @__PURE__ */__name(spec => { | ||
| if (spec.basemap?.visible === false) return BLANK_STYLE; | ||
| return spec.basemap?.styleUrl ?? DEFAULT_STYLE; | ||
| }, "resolveStyle"); | ||
| var syncCenter = /* @__PURE__ */__name((map, prev, next) => { | ||
| if (!next?.center || next.center.length !== 2) return; | ||
| const [lng, lat] = next.center; | ||
| if (prev?.center?.[0] === lng && prev?.center?.[1] === lat) return; | ||
| map.setCenter(next.center); | ||
| }, "syncCenter"); | ||
| var syncZoom = /* @__PURE__ */__name((map, prev, next) => { | ||
| if (next?.zoom === void 0 || next.zoom === prev?.zoom) return; | ||
| map.setZoom(next.zoom); | ||
| }, "syncZoom"); | ||
| var syncMapView = /* @__PURE__ */__name((map, prev, next) => { | ||
| if (!next) return; | ||
| const p = prev ?? {}; | ||
| syncCenter(map, prev, next); | ||
| syncZoom(map, prev, next); | ||
| const pp = p.pitch ?? 0; | ||
| const np = next.pitch ?? 0; | ||
| if (pp !== np) map.setPitch(np); | ||
| const pb = p.bearing ?? 0; | ||
| const nb = next.bearing ?? 0; | ||
| if (pb !== nb) map.setBearing(nb); | ||
| }, "syncMapView"); | ||
| var createMap = /* @__PURE__ */__name((spec, container) => { | ||
| const { | ||
| view | ||
| } = spec; | ||
| const style = resolveStyle(spec); | ||
| const map = new maplibregl.Map({ | ||
| container, | ||
| style, | ||
| center: view?.center ?? [0, 0], | ||
| zoom: view?.zoom ?? 1, | ||
| pitch: view?.pitch ?? 0, | ||
| bearing: view?.bearing ?? 0 | ||
| }); | ||
| map.addControl(new maplibregl.NavigationControl({ | ||
| visualizePitch: true, | ||
| visualizeRoll: true, | ||
| showZoom: true, | ||
| showCompass: true | ||
| })); | ||
| return { | ||
| map, | ||
| style | ||
| }; | ||
| }, "createMap"); | ||
| var mountView = /* @__PURE__ */__name((views, container, spec, viewId) => { | ||
| const { | ||
| map, | ||
| style | ||
| } = createMap(spec, container); | ||
| views.set(viewId, { | ||
| map, | ||
| spec, | ||
| style | ||
| }); | ||
| map.on("load", () => { | ||
| const viewState = views.get(viewId); | ||
| if (!viewState) return; | ||
| syncSourcesAndLayers(map, viewState.spec, null); | ||
| reapplyAllMapData(map, viewState.spec); | ||
| }); | ||
| let _removed = false; | ||
| return { | ||
| viewId, | ||
| container, | ||
| destroy: /* @__PURE__ */__name(() => { | ||
| if (_removed) return; | ||
| _removed = true; | ||
| try { | ||
| map.remove(); | ||
| } catch {} | ||
| views.delete(viewId); | ||
| }, "destroy") | ||
| }; | ||
| }, "mountView"); | ||
| var updateView = /* @__PURE__ */__name((views, viewId, viewState, spec) => { | ||
| const { | ||
| map | ||
| } = viewState; | ||
| const nextStyle = resolveStyle(spec); | ||
| const previousSpec = viewState.spec; | ||
| viewState.spec = spec; | ||
| syncMapView(map, previousSpec.view, spec.view); | ||
| const onStyleReady = /* @__PURE__ */__name(() => { | ||
| const updated = views.get(viewId); | ||
| if (!updated) return; | ||
| syncSourcesAndLayers(map, updated.spec, null); | ||
| reapplyAllMapData(map, updated.spec); | ||
| reapplyLegendDrivenFillPaint(map, updated.spec); | ||
| }, "onStyleReady"); | ||
| if (nextStyle !== viewState.style) { | ||
| viewState.style = nextStyle; | ||
| map.once("style.load", onStyleReady); | ||
| map.setStyle(nextStyle); | ||
| return; | ||
| } | ||
| if (map.isStyleLoaded()) { | ||
| syncSourcesAndLayers(map, spec, previousSpec); | ||
| if (previousSpec.mapData !== spec.mapData) { | ||
| for (const prevMd of previousSpec.mapData ?? []) { | ||
| const nextMd = (spec.mapData ?? []).find(md => { | ||
| return md.mapDataId === prevMd.mapDataId; | ||
| }); | ||
| if (!nextMd || nextMd !== prevMd) removeMapDataFromSource(map, prevMd); | ||
| } | ||
| reapplyAllMapData(map, spec); | ||
| reapplyLegendDrivenFillPaint(map, spec); | ||
| } | ||
| } else { | ||
| map.once("style.load", onStyleReady); | ||
| } | ||
| }, "updateView"); | ||
| var dispatchPatch = /* @__PURE__ */__name((viewState, patch) => { | ||
| const { | ||
| map | ||
| } = viewState; | ||
| if (patch.target === "layer") { | ||
| applyLayerPatch(map, viewState, patch); | ||
| } else if (patch.target === "source") { | ||
| applySourcePatch(map, viewState, patch); | ||
| } else if (patch.target === "mapData") { | ||
| applyMapDataPatchToMap(map, viewState.spec.mapData ?? [], patch); | ||
| viewState.spec = applyMapDataPatchToSpec(viewState.spec, patch); | ||
| reapplyLegendDrivenFillPaint(map, viewState.spec); | ||
| } else { | ||
| log.warn(`[geovis] MapLibreAdapter: unknown patch target "${patch.target}" \u2014 patch was ignored.`); | ||
| } | ||
| }, "dispatchPatch"); | ||
| var applySetView = /* @__PURE__ */__name((map, options) => { | ||
| const { | ||
| center, | ||
| zoom, | ||
| pitch, | ||
| bearing, | ||
| animate = true | ||
| } = options; | ||
| const camera = {}; | ||
| if (center !== void 0) camera.center = center; | ||
| if (zoom !== void 0) camera.zoom = zoom; | ||
| if (pitch !== void 0) camera.pitch = pitch; | ||
| if (bearing !== void 0) camera.bearing = bearing; | ||
| if (Object.keys(camera).length === 0) return; | ||
| if (animate) { | ||
| map.flyTo(camera); | ||
| } else { | ||
| map.jumpTo(camera); | ||
| } | ||
| }, "applySetView"); | ||
| var destroyAll = /* @__PURE__ */__name(views => { | ||
| for (const viewState of views.values()) { | ||
| try { | ||
| viewState.map.remove(); | ||
| } catch {} | ||
| } | ||
| views.clear(); | ||
| }, "destroyAll"); | ||
| var CAPABILITIES = { | ||
| supports3D: false, | ||
| supportsRaster: true, | ||
| supportsVectorTiles: true, | ||
| supportsCustomLayers: true | ||
| }; | ||
| var createMapLibreAdapter = /* @__PURE__ */__name(() => { | ||
| const _views = /* @__PURE__ */new Map(); | ||
| return { | ||
| id: "maplibre", | ||
| getCapabilities: /* @__PURE__ */__name(() => { | ||
| return CAPABILITIES; | ||
| }, "getCapabilities"), | ||
| mount: /* @__PURE__ */__name((container, spec, viewId) => { | ||
| return mountView(_views, container, spec, viewId); | ||
| }, "mount"), | ||
| update: /* @__PURE__ */__name(spec => { | ||
| for (const [viewId, viewState] of _views) updateView(_views, viewId, viewState, spec); | ||
| }, "update"), | ||
| applyPatch: /* @__PURE__ */__name(patch => { | ||
| for (const viewState of _views.values()) dispatchPatch(viewState, patch); | ||
| }, "applyPatch"), | ||
| setView: /* @__PURE__ */__name(options => { | ||
| for (const viewState of _views.values()) { | ||
| applySetView(viewState.map, options); | ||
| } | ||
| }, "setView"), | ||
| destroy: /* @__PURE__ */__name(() => { | ||
| destroyAll(_views); | ||
| }, "destroy"), | ||
| getNativeInstance: /* @__PURE__ */__name(() => { | ||
| return _views.size > 0 ? _views.values().next().value?.map ?? null : null; | ||
| }, "getNativeInstance") | ||
| }; | ||
| }, "createMapLibreAdapter"); | ||
| var MapLibreAdapter_default = createMapLibreAdapter; | ||
| export { MapLibreAdapter_default as default, toMaplibreLayer, toMaplibreSource }; |
+74
-4
@@ -256,2 +256,3 @@ /** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */ | ||
| // src/runtime/createRuntime.ts | ||
| import { log } from "@ttoss/logger"; | ||
| var applyLayerPatchToSpec = /* @__PURE__ */__name((spec, patch) => { | ||
@@ -335,3 +336,3 @@ if (patch.op === "add" && patch.value != null) { | ||
| } else { | ||
| console.warn(`[GeoVis] applyPatch: unknown target "${patch.target}" - patch ignored.`); | ||
| log.warn(`[geovis] Runtime: unknown patch target "${patch.target}" \u2014 patch was ignored.`); | ||
| } | ||
@@ -422,3 +423,4 @@ }, "applyPatch"), | ||
| sourceByLayerId, | ||
| setHover | ||
| setHover, | ||
| lastPointRef | ||
| }) => { | ||
@@ -444,2 +446,6 @@ return event => { | ||
| }); | ||
| lastPointRef.current = { | ||
| x: event.point.x, | ||
| y: event.point.y | ||
| }; | ||
| map.getCanvas().style.cursor = "pointer"; | ||
@@ -459,2 +465,44 @@ const rect = map.getCanvas().getBoundingClientRect(); | ||
| }, "buildHandleMove"); | ||
| var buildHandleWindowFocus = /* @__PURE__ */__name(({ | ||
| map, | ||
| trackedLayerIds, | ||
| sourceByLayerId, | ||
| lastPointRef, | ||
| setHover | ||
| }) => { | ||
| return () => { | ||
| const canvasPoint = lastPointRef.current; | ||
| if (!canvasPoint) return; | ||
| const hits = map.queryRenderedFeatures([canvasPoint.x, canvasPoint.y], { | ||
| layers: trackedLayerIds | ||
| }); | ||
| const feature = hits[0]; | ||
| if (!feature || feature.id == null) { | ||
| clearHover(map, setHover); | ||
| return; | ||
| } | ||
| const resolvedLayerId = feature.layer?.id ?? trackedLayerIds[0]; | ||
| const sourceId = sourceByLayerId.get(resolvedLayerId); | ||
| if (!sourceId) { | ||
| clearHover(map, setHover); | ||
| return; | ||
| } | ||
| const state = map.getFeatureState({ | ||
| source: sourceId, | ||
| id: feature.id | ||
| }); | ||
| const rect = map.getCanvas().getBoundingClientRect(); | ||
| map.getCanvas().style.cursor = "pointer"; | ||
| setHover({ | ||
| layerId: resolvedLayerId, | ||
| sourceId, | ||
| featureId: feature.id, | ||
| value: coerceFeatureStateValue(state.value), | ||
| point: { | ||
| x: rect.left + canvasPoint.x, | ||
| y: rect.top + canvasPoint.y | ||
| } | ||
| }); | ||
| }; | ||
| }, "buildHandleWindowFocus"); | ||
| var useMapHover = /* @__PURE__ */__name(({ | ||
@@ -465,2 +513,3 @@ runtime, | ||
| const [hover, setHover] = React5.useState(null); | ||
| const lastPointRef = React5.useRef(null); | ||
| const trackedKey = React5.useMemo(() => { | ||
@@ -488,2 +537,5 @@ return spec.layers.filter(layer => { | ||
| })); | ||
| const trackedLayerIds = tracked.map(t => { | ||
| return t.layerId; | ||
| }); | ||
| const handlers = tracked.map(({ | ||
@@ -498,3 +550,4 @@ layerId | ||
| sourceByLayerId, | ||
| setHover | ||
| setHover, | ||
| lastPointRef | ||
| }) | ||
@@ -506,2 +559,9 @@ }; | ||
| }, "handleLeave"); | ||
| const handleWindowFocus = buildHandleWindowFocus({ | ||
| map, | ||
| trackedLayerIds, | ||
| sourceByLayerId, | ||
| lastPointRef, | ||
| setHover | ||
| }); | ||
| for (const { | ||
@@ -514,2 +574,9 @@ layerId, | ||
| } | ||
| const handleVisibilityChange = /* @__PURE__ */__name(() => { | ||
| if (document.visibilityState === "visible") { | ||
| handleWindowFocus(); | ||
| } | ||
| }, "handleVisibilityChange"); | ||
| window.addEventListener("focus", handleWindowFocus); | ||
| document.addEventListener("visibilitychange", handleVisibilityChange); | ||
| return () => { | ||
@@ -523,2 +590,5 @@ for (const { | ||
| } | ||
| window.removeEventListener("focus", handleWindowFocus); | ||
| document.removeEventListener("visibilitychange", handleVisibilityChange); | ||
| lastPointRef.current = null; | ||
| clearHover(map, setHover); | ||
@@ -652,3 +722,3 @@ }; | ||
| { | ||
| const mod = await import("./MapLibreAdapter-45ROTL3W.js"); | ||
| const mod = await import("./MapLibreAdapter-VL4HZEFT.js"); | ||
| return mod.default(); | ||
@@ -655,0 +725,0 @@ } |
+37
-5
@@ -310,2 +310,24 @@ import * as react_jsx_runtime from 'react/jsx-runtime'; | ||
| } | ||
| /** | ||
| * Declares one visual perspective of a spec in a multi-view layout. | ||
| * Each view references a subset of `spec.layers` by id and is intended to be | ||
| * consumed by layout components that derive per-panel specs and manage view | ||
| * synchronisation automatically. | ||
| */ | ||
| interface VisualizationView { | ||
| /** Unique view identifier. Must match the `viewId` prop of `GeoVisCanvas`. */ | ||
| id: string; | ||
| /** Human-readable label rendered above the canvas by layout components. */ | ||
| label?: string; | ||
| /** | ||
| * Layer ids from `spec.layers` that this view displays. | ||
| * | ||
| * @remarks | ||
| * No runtime validation is performed — IDs that do not match any entry in | ||
| * `spec.layers` are silently ignored, resulting in an empty render for that | ||
| * view. Ensure every id listed here corresponds to a layer defined in the | ||
| * top-level `spec.layers` array. | ||
| */ | ||
| layers: string[]; | ||
| } | ||
| interface VisualizationSpec { | ||
@@ -421,5 +443,3 @@ id: string; | ||
| /** | ||
| * Snapshot of a feature currently hovered on the map. Coordinates are pixel | ||
| * offsets relative to the map canvas, so consumers can render overlays inside | ||
| * the same `position: relative` container that hosts `<GeoVisCanvas>`. | ||
| * Snapshot of a feature currently hovered on the map. | ||
| */ | ||
@@ -439,3 +459,7 @@ interface MapHoverInfo { | ||
| value: number | string | null; | ||
| /** Pixel coordinates relative to the map canvas. */ | ||
| /** | ||
| * Viewport-absolute pixel coordinates of the cursor (`clientX / clientY` | ||
| * equivalent). Use these directly with `position: fixed` elements such as | ||
| * `<GeoVisHoverTooltip>`, which renders via a portal into `document.body`. | ||
| */ | ||
| point: { | ||
@@ -567,2 +591,10 @@ x: number; | ||
| * hover updates do not re-render `useGeoVis()` consumers. | ||
| * | ||
| * | ||
| * @remarks | ||
| * The tooltip renders via `ReactDOM.createPortal` into `document.body` and | ||
| * uses `position: fixed`, so it is not clipped by `overflow: hidden` ancestors | ||
| * and does not require any special wrapper around `<GeoVisCanvas>`. | ||
| * `MapHoverInfo.point` already carries viewport-absolute coordinates, so the | ||
| * tooltip is positioned correctly without any additional offset math. | ||
| */ | ||
@@ -647,2 +679,2 @@ declare const GeoVisHoverTooltip: ({ render, formatValue, className, style, offset, emptyValueLabel, }: GeoVisHoverTooltipProps) => React.ReactPortal | null; | ||
| export { type BaseMapSpec, type CapabilitySet, type CategoricalColorBy, type CategoricalColorByTemplate, type CirclePaint, type ColorBy, type ColorByTemplate, type DataSource, type EngineAdapter, type FillPaint, type GeoJSONBoundingBox, type GeoJSONFeature, type GeoJSONFeatureCollection, type GeoJSONGeometry, type GeoJSONGeometryCollection, type GeoJSONLineString, type GeoJSONMultiLineString, type GeoJSONMultiPoint, type GeoJSONMultiPolygon, type GeoJSONObject, type GeoJSONPoint, type GeoJSONPolygon, type GeoJSONPosition, type GeoJSONSource, GeoVisCanvas, type GeoVisCanvasProps, GeoVisClickContext, GeoVisContext, type GeoVisContextValue, type GeoVisGeometryType, GeoVisHoverContext, GeoVisHoverTooltip, type GeoVisHoverTooltipProps, GeoVisLegend, type GeoVisLegendProps, GeoVisProvider, type GeoVisRuntime, type GeovisSpec, type HeatmapPaint, type ImageSource, type LayerPaint, type LegendSpec, type LinePaint, type LngLat, type MapClickInfo, type MapData, type MapDataRow, type MapHoverInfo, type MountedView, type PolicyViolation, type QuantitativeColorBy, type QuantitativeColorByTemplate, type RasterDemSource, type RasterPaint, type RasterTileSource, type SetViewOptions, type SpecPatch, type SpecPatchTarget, type SymbolPaint, type UseMapDataResult, type ValidationResult, type VectorTileSource, type VideoSource, type ViewState, type VisualizationLayer, type VisualizationSpec, createRuntime, useGeoVis, useGeoVisClick, useGeoVisHover, useMapData, validateSpec }; | ||
| export { type BaseMapSpec, type CapabilitySet, type CategoricalColorBy, type CategoricalColorByTemplate, type CirclePaint, type ColorBy, type ColorByTemplate, type DataSource, type EngineAdapter, type FillPaint, type GeoJSONBoundingBox, type GeoJSONFeature, type GeoJSONFeatureCollection, type GeoJSONGeometry, type GeoJSONGeometryCollection, type GeoJSONLineString, type GeoJSONMultiLineString, type GeoJSONMultiPoint, type GeoJSONMultiPolygon, type GeoJSONObject, type GeoJSONPoint, type GeoJSONPolygon, type GeoJSONPosition, type GeoJSONSource, GeoVisCanvas, type GeoVisCanvasProps, GeoVisClickContext, GeoVisContext, type GeoVisContextValue, type GeoVisGeometryType, GeoVisHoverContext, GeoVisHoverTooltip, type GeoVisHoverTooltipProps, GeoVisLegend, type GeoVisLegendProps, GeoVisProvider, type GeoVisRuntime, type GeovisSpec, type HeatmapPaint, type ImageSource, type LayerPaint, type LegendSpec, type LinePaint, type LngLat, type MapClickInfo, type MapData, type MapDataRow, type MapHoverInfo, type MountedView, type PolicyViolation, type QuantitativeColorBy, type QuantitativeColorByTemplate, type RasterDemSource, type RasterPaint, type RasterTileSource, type SetViewOptions, type SpecPatch, type SpecPatchTarget, type SymbolPaint, type UseMapDataResult, type ValidationResult, type VectorTileSource, type VideoSource, type ViewState, type VisualizationLayer, type VisualizationSpec, type VisualizationView, createRuntime, useGeoVis, useGeoVisClick, useGeoVisHover, useMapData, validateSpec }; |
+4
-2
| { | ||
| "name": "@ttoss/geovis", | ||
| "version": "0.1.10", | ||
| "version": "0.1.11", | ||
| "description": "Geovisualization components for React applications.", | ||
@@ -28,2 +28,3 @@ "keywords": [ | ||
| "types": "./dist/index.d.ts", | ||
| "import": "./dist/esm/index.js", | ||
| "default": "./dist/esm/index.js" | ||
@@ -36,3 +37,4 @@ } | ||
| "dependencies": { | ||
| "ajv": "^8.18.0" | ||
| "ajv": "^8.18.0", | ||
| "@ttoss/logger": "^0.8.11" | ||
| }, | ||
@@ -39,0 +41,0 @@ "devDependencies": { |
| /** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */ | ||
| import { __name, applyMapDataPatchToSpec, buildFillColorExpression, coerceGeometryId } from "./chunk-QMEVH6NT.js"; | ||
| // src/adapters/maplibre/MapLibreAdapter.ts | ||
| import "maplibre-gl/dist/maplibre-gl.css"; | ||
| import maplibregl from "maplibre-gl"; | ||
| // src/adapters/maplibre/layerTranslation.ts | ||
| var buildBase = /* @__PURE__ */__name((layer, sourceLayer) => { | ||
| const base = { | ||
| id: layer.id, | ||
| source: layer.sourceId, | ||
| minzoom: layer.minzoom, | ||
| maxzoom: layer.maxzoom, | ||
| layout: { | ||
| visibility: layer.visible === false ? "none" : "visible" | ||
| } | ||
| }; | ||
| const effective = layer.sourceLayer ?? sourceLayer; | ||
| if (effective) base["source-layer"] = effective; | ||
| return base; | ||
| }, "buildBase"); | ||
| var resolveThresholdBreaks = /* @__PURE__ */__name((layer, specLegends) => { | ||
| const legend = layer.legends?.find(item => { | ||
| return item.id === layer.activeLegendId; | ||
| }) ?? specLegends?.find(item => { | ||
| return item.id === layer.activeLegendId; | ||
| }); | ||
| if (!legend || legend.colorBy.type !== "quantitative") return []; | ||
| if (legend.colorBy.scale !== "threshold") return []; | ||
| return Array.from(new Set((legend.colorBy.thresholds ?? []).filter(value => { | ||
| return Number.isFinite(value); | ||
| }))).sort((a, b) => { | ||
| return a - b; | ||
| }); | ||
| }, "resolveThresholdBreaks"); | ||
| var resolveLegendFillColorExpression = /* @__PURE__ */__name((layer, specLegends) => { | ||
| if (layer.geometry !== "polygon") return void 0; | ||
| if (!layer.activeLegendId) return void 0; | ||
| const activeLegend = layer.legends?.find(legend => { | ||
| return legend.id === layer.activeLegendId; | ||
| }) ?? specLegends?.find(legend => { | ||
| return legend.id === layer.activeLegendId; | ||
| }); | ||
| if (!activeLegend) return void 0; | ||
| return buildFillColorExpression({ | ||
| legend: activeLegend, | ||
| breaks: resolveThresholdBreaks(layer, specLegends) | ||
| }); | ||
| }, "resolveLegendFillColorExpression"); | ||
| var buildPolygon = /* @__PURE__ */__name((base, layer, paint, specLegends) => { | ||
| const fp = paint ?? {}; | ||
| const legendFillColor = resolveLegendFillColorExpression(layer, specLegends); | ||
| return { | ||
| ...base, | ||
| type: "fill", | ||
| paint: { | ||
| "fill-color": legendFillColor ?? fp.fillColor ?? "#3b82f6", | ||
| "fill-opacity": fp.fillOpacity ?? 0.6, | ||
| "fill-outline-color": fp.lineColor ?? "#1d4ed8" | ||
| } | ||
| }; | ||
| }, "buildPolygon"); | ||
| var buildLine = /* @__PURE__ */__name((base, _layer, paint) => { | ||
| const lp = paint ?? {}; | ||
| return { | ||
| ...base, | ||
| type: "line", | ||
| paint: { | ||
| "line-color": lp.lineColor ?? "#3b82f6", | ||
| "line-width": lp.lineWidth ?? 2, | ||
| "line-opacity": lp.lineOpacity ?? 1, | ||
| "line-dasharray": lp.lineDasharray | ||
| } | ||
| }; | ||
| }, "buildLine"); | ||
| var buildPoint = /* @__PURE__ */__name((base, _layer, paint) => { | ||
| const cp = paint ?? {}; | ||
| return { | ||
| ...base, | ||
| type: "circle", | ||
| paint: { | ||
| "circle-color": cp.circleColor ?? "#3b82f6", | ||
| "circle-radius": cp.circleRadius ?? 6, | ||
| "circle-opacity": cp.circleOpacity ?? 1, | ||
| "circle-stroke-color": cp.circleStrokeColor ?? "#ffffff", | ||
| "circle-stroke-width": cp.circleStrokeWidth ?? 1 | ||
| } | ||
| }; | ||
| }, "buildPoint"); | ||
| var buildHeatmap = /* @__PURE__ */__name((base, _layer, paint) => { | ||
| const hp = paint ?? {}; | ||
| return { | ||
| ...base, | ||
| type: "heatmap", | ||
| paint: { | ||
| "heatmap-radius": hp.heatmapRadius ?? 15, | ||
| "heatmap-opacity": hp.heatmapOpacity ?? 1, | ||
| "heatmap-intensity": hp.heatmapIntensity ?? 1, | ||
| "heatmap-weight": hp.heatmapWeight ?? 1 | ||
| } | ||
| }; | ||
| }, "buildHeatmap"); | ||
| var buildSymbol = /* @__PURE__ */__name((base, _layer, paint) => { | ||
| const sp = paint ?? {}; | ||
| return { | ||
| ...base, | ||
| type: "symbol", | ||
| layout: { | ||
| ...base.layout, | ||
| "text-field": sp.textField ?? "", | ||
| "text-size": sp.textSize ?? 12, | ||
| "icon-image": sp.iconImage | ||
| }, | ||
| paint: { | ||
| "text-color": sp.textColor ?? "#000000", | ||
| "text-opacity": sp.textOpacity ?? 1, | ||
| "text-halo-color": sp.textHaloColor ?? "#ffffff", | ||
| "text-halo-width": sp.textHaloWidth ?? 0, | ||
| "icon-color": sp.iconColor ?? "#000000", | ||
| "icon-opacity": sp.iconOpacity ?? 1 | ||
| } | ||
| }; | ||
| }, "buildSymbol"); | ||
| var buildRaster = /* @__PURE__ */__name((base, _layer, paint) => { | ||
| const rp = paint ?? {}; | ||
| return { | ||
| ...base, | ||
| type: "raster", | ||
| paint: { | ||
| "raster-opacity": rp.rasterOpacity ?? 1 | ||
| } | ||
| }; | ||
| }, "buildRaster"); | ||
| var builders = { | ||
| polygon: buildPolygon, | ||
| line: buildLine, | ||
| point: buildPoint, | ||
| heatmap: buildHeatmap, | ||
| symbol: buildSymbol, | ||
| raster: buildRaster | ||
| }; | ||
| var stripUndefinedPaint = /* @__PURE__ */__name(layer => { | ||
| const paint = layer.paint; | ||
| if (paint) { | ||
| layer.paint = Object.fromEntries(Object.entries(paint).filter(([, v]) => { | ||
| return v !== void 0; | ||
| })); | ||
| } | ||
| return layer; | ||
| }, "stripUndefinedPaint"); | ||
| var toMaplibreLayer = /* @__PURE__ */__name((layer, sourceLayer, specLegends) => { | ||
| const base = buildBase(layer, sourceLayer); | ||
| return builders[layer.geometry](base, layer, layer.paint, specLegends); | ||
| }, "toMaplibreLayer"); | ||
| // src/adapters/maplibre/legendFillPaint.ts | ||
| var pendingStyleListeners = /* @__PURE__ */new WeakMap(); | ||
| var cancelPendingStyleListenersForLayer = /* @__PURE__ */__name((map, layerId) => { | ||
| const byKey = pendingStyleListeners.get(map); | ||
| if (!byKey) return; | ||
| for (const [key, listener] of byKey) { | ||
| if (key.startsWith(`${layerId}:`)) { | ||
| map.off("styledata", listener); | ||
| byKey.delete(key); | ||
| } | ||
| } | ||
| }, "cancelPendingStyleListenersForLayer"); | ||
| var setPaintWhenReady = /* @__PURE__ */__name((map, layerId, property, value) => { | ||
| const apply = /* @__PURE__ */__name(() => { | ||
| if (!map.getLayer(layerId)) return false; | ||
| map.setPaintProperty(layerId, property, value); | ||
| return true; | ||
| }, "apply"); | ||
| const applyWhenLayerAppears = /* @__PURE__ */__name(() => { | ||
| const listenerKey = `${layerId}:${property}`; | ||
| let byKey = pendingStyleListeners.get(map); | ||
| if (!byKey) { | ||
| byKey = /* @__PURE__ */new Map(); | ||
| pendingStyleListeners.set(map, byKey); | ||
| } | ||
| const existing = byKey.get(listenerKey); | ||
| if (existing) { | ||
| map.off("styledata", existing); | ||
| } | ||
| const onStyleData = /* @__PURE__ */__name(() => { | ||
| const applied = apply(); | ||
| if (!applied) return; | ||
| map.off("styledata", onStyleData); | ||
| pendingStyleListeners.get(map)?.delete(listenerKey); | ||
| }, "onStyleData"); | ||
| byKey.set(listenerKey, onStyleData); | ||
| map.on("styledata", onStyleData); | ||
| }, "applyWhenLayerAppears"); | ||
| if (map.isStyleLoaded()) { | ||
| const applied = apply(); | ||
| if (!applied) applyWhenLayerAppears(); | ||
| return; | ||
| } | ||
| map.once("style.load", () => { | ||
| const applied = apply(); | ||
| if (!applied) applyWhenLayerAppears(); | ||
| }); | ||
| }, "setPaintWhenReady"); | ||
| var reapplyLegendDrivenFillPaint = /* @__PURE__ */__name((map, spec) => { | ||
| for (const layer of spec.layers) { | ||
| if (layer.geometry !== "polygon") continue; | ||
| const expression = resolveLegendFillColorExpression(layer, spec.legends); | ||
| if (!expression) continue; | ||
| setPaintWhenReady(map, layer.id, "fill-color", expression); | ||
| } | ||
| }, "reapplyLegendDrivenFillPaint"); | ||
| // src/adapters/maplibre/mapDataFeatureState.ts | ||
| var pendingListeners = /* @__PURE__ */new WeakMap(); | ||
| var cancelPendingListener = /* @__PURE__ */__name((map, mapDataId) => { | ||
| const byId = pendingListeners.get(map); | ||
| const listener = byId?.get(mapDataId); | ||
| if (!byId || !listener) return; | ||
| map.off("sourcedata", listener); | ||
| byId.delete(mapDataId); | ||
| }, "cancelPendingListener"); | ||
| var buildJoinKeyIndex = /* @__PURE__ */__name((features, joinKey) => { | ||
| const index = /* @__PURE__ */new Map(); | ||
| for (const f of features) { | ||
| const k = f.properties?.[joinKey]; | ||
| if (k == null || f.id == null) continue; | ||
| index.set(String(k), f.id); | ||
| } | ||
| return index; | ||
| }, "buildJoinKeyIndex"); | ||
| var applyMapDataToSource = /* @__PURE__ */__name((map, mapData) => { | ||
| if (!map.getSource(mapData.mapId)) return; | ||
| if (!mapData.joinKey) { | ||
| for (const row of mapData.data) { | ||
| const safeValue = typeof row.value === "number" && !Number.isFinite(row.value) ? 0 : row.value; | ||
| map.setFeatureState({ | ||
| source: mapData.mapId, | ||
| id: coerceGeometryId(row.geometryId) | ||
| }, { | ||
| value: safeValue | ||
| }); | ||
| } | ||
| return; | ||
| } | ||
| const features = map.querySourceFeatures(mapData.mapId); | ||
| const idByJoinKey = buildJoinKeyIndex(features, mapData.joinKey); | ||
| for (const row of mapData.data) { | ||
| const fid = idByJoinKey.get(String(row.geometryId)); | ||
| if (fid == null) continue; | ||
| const safeValue = typeof row.value === "number" && !Number.isFinite(row.value) ? 0 : row.value; | ||
| map.setFeatureState({ | ||
| source: mapData.mapId, | ||
| id: fid | ||
| }, { | ||
| value: safeValue | ||
| }); | ||
| } | ||
| }, "applyMapDataToSource"); | ||
| var scheduleMapDataApply = /* @__PURE__ */__name((map, mapData) => { | ||
| if (map.isSourceLoaded(mapData.mapId)) { | ||
| applyMapDataToSource(map, mapData); | ||
| return; | ||
| } | ||
| cancelPendingListener(map, mapData.mapDataId); | ||
| const listener = /* @__PURE__ */__name(e => { | ||
| if (e.sourceId !== mapData.mapId || !e.isSourceLoaded) return; | ||
| applyMapDataToSource(map, mapData); | ||
| map.off("sourcedata", listener); | ||
| pendingListeners.get(map)?.delete(mapData.mapDataId); | ||
| }, "listener"); | ||
| if (!pendingListeners.has(map)) pendingListeners.set(map, /* @__PURE__ */new Map()); | ||
| pendingListeners.get(map).set(mapData.mapDataId, listener); | ||
| map.on("sourcedata", listener); | ||
| }, "scheduleMapDataApply"); | ||
| var removeMapDataFromSource = /* @__PURE__ */__name((map, mapData) => { | ||
| cancelPendingListener(map, mapData.mapDataId); | ||
| if (!map.getSource(mapData.mapId)) return; | ||
| if (!mapData.joinKey) { | ||
| for (const row of mapData.data) { | ||
| map.removeFeatureState({ | ||
| source: mapData.mapId, | ||
| id: coerceGeometryId(row.geometryId) | ||
| }); | ||
| } | ||
| return; | ||
| } | ||
| if (!map.isSourceLoaded(mapData.mapId)) return; | ||
| const features = map.querySourceFeatures(mapData.mapId); | ||
| const idByJoinKey = buildJoinKeyIndex(features, mapData.joinKey); | ||
| for (const row of mapData.data) { | ||
| const fid = idByJoinKey.get(String(row.geometryId)); | ||
| if (fid == null) continue; | ||
| map.removeFeatureState({ | ||
| source: mapData.mapId, | ||
| id: fid | ||
| }); | ||
| } | ||
| }, "removeMapDataFromSource"); | ||
| var reapplyAllMapData = /* @__PURE__ */__name((map, spec) => { | ||
| for (const md of spec.mapData ?? []) scheduleMapDataApply(map, md); | ||
| }, "reapplyAllMapData"); | ||
| var applyRowReplacement = /* @__PURE__ */__name((map, mapData, geometryId, value) => { | ||
| const safeValue = typeof value === "number" && !Number.isFinite(value) ? 0 : value; | ||
| if (!mapData.joinKey) { | ||
| const row = mapData.data.find(r => { | ||
| return String(r.geometryId) === geometryId; | ||
| }); | ||
| const featureId = row?.geometryId ?? coerceGeometryId(geometryId); | ||
| map.setFeatureState({ | ||
| source: mapData.mapId, | ||
| id: featureId | ||
| }, { | ||
| value: safeValue | ||
| }); | ||
| return; | ||
| } | ||
| if (!map.isSourceLoaded(mapData.mapId)) return; | ||
| const f = map.querySourceFeatures(mapData.mapId).find(feat => { | ||
| const k = feat.properties?.[mapData.joinKey]; | ||
| return k != null && String(k) === geometryId; | ||
| }); | ||
| if (f?.id != null) { | ||
| map.setFeatureState({ | ||
| source: mapData.mapId, | ||
| id: f.id | ||
| }, { | ||
| value: safeValue | ||
| }); | ||
| } | ||
| }, "applyRowReplacement"); | ||
| var handleMapDataReplace = /* @__PURE__ */__name((map, currentMapData, patch) => { | ||
| if (!patch.path) return; | ||
| const parts = patch.path.split("."); | ||
| if (parts.length < 2 || parts[0] !== "mapData") return; | ||
| const target = currentMapData.find(md => { | ||
| return md.mapDataId === parts[1]; | ||
| }); | ||
| if (!target) return; | ||
| if (parts.length === 2 && patch.value != null) { | ||
| removeMapDataFromSource(map, target); | ||
| scheduleMapDataApply(map, patch.value); | ||
| return; | ||
| } | ||
| if (parts.length === 4 && parts[2] === "data") { | ||
| applyRowReplacement(map, target, parts[3], patch.value); | ||
| } | ||
| }, "handleMapDataReplace"); | ||
| var applyMapDataPatchToMap = /* @__PURE__ */__name((map, currentMapData, patch) => { | ||
| if (patch.op === "add" && patch.value != null) { | ||
| scheduleMapDataApply(map, patch.value); | ||
| return; | ||
| } | ||
| if (patch.op === "remove") { | ||
| const mapDataId = patch.value; | ||
| cancelPendingListener(map, mapDataId); | ||
| const target = currentMapData.find(md => { | ||
| return md.mapDataId === mapDataId; | ||
| }); | ||
| if (target) removeMapDataFromSource(map, target); | ||
| return; | ||
| } | ||
| if (patch.op === "replace") { | ||
| handleMapDataReplace(map, currentMapData, patch); | ||
| } | ||
| }, "applyMapDataPatchToMap"); | ||
| // src/adapters/maplibre/paintKeyMap.ts | ||
| var SPEC_PAINT_KEY_MAP = { | ||
| fillColor: "fill-color", | ||
| fillOpacity: "fill-opacity", | ||
| lineColor: /* @__PURE__ */__name(g => { | ||
| return g === "polygon" ? "fill-outline-color" : "line-color"; | ||
| }, "lineColor"), | ||
| lineWidth: /* @__PURE__ */__name(g => { | ||
| return g === "polygon" ? void 0 : "line-width"; | ||
| }, "lineWidth"), | ||
| lineOpacity: "line-opacity", | ||
| lineDasharray: "line-dasharray", | ||
| circleColor: "circle-color", | ||
| circleRadius: "circle-radius", | ||
| circleOpacity: "circle-opacity", | ||
| circleStrokeColor: "circle-stroke-color", | ||
| circleStrokeWidth: "circle-stroke-width", | ||
| rasterOpacity: "raster-opacity", | ||
| heatmapRadius: "heatmap-radius", | ||
| heatmapOpacity: "heatmap-opacity", | ||
| heatmapIntensity: "heatmap-intensity", | ||
| heatmapWeight: "heatmap-weight", | ||
| textColor: "text-color", | ||
| textOpacity: "text-opacity", | ||
| textHaloColor: "text-halo-color", | ||
| textHaloWidth: "text-halo-width", | ||
| iconColor: "icon-color", | ||
| iconOpacity: "icon-opacity" | ||
| }; | ||
| var specPaintKeyToMaplibre = /* @__PURE__ */__name((key, geometry) => { | ||
| const entry = SPEC_PAINT_KEY_MAP[key]; | ||
| if (!entry) return void 0; | ||
| return typeof entry === "function" ? entry(geometry) : entry; | ||
| }, "specPaintKeyToMaplibre"); | ||
| // src/adapters/maplibre/sourceTranslation.ts | ||
| var sourceConverters = { | ||
| geojson: /* @__PURE__ */__name((source, opts) => { | ||
| return { | ||
| type: "geojson", | ||
| data: source.data, | ||
| attribution: source.attribution, | ||
| // `promoteId` lifts a feature property to the synthetic `feature.id` | ||
| // used by `setFeatureState`. Required when the underlying GeoJSON has | ||
| // duplicate or missing ids and a `mapData.joinKey` identifies the row. | ||
| // Without it, all features collapse to `id: 0` and `setFeatureState` | ||
| // overwrites itself, producing a single colour for the whole layer. | ||
| ...(opts.promoteId ? { | ||
| promoteId: opts.promoteId | ||
| } : {}) | ||
| }; | ||
| }, "geojson"), | ||
| "vector-tiles": /* @__PURE__ */__name((source, opts) => { | ||
| return { | ||
| type: "vector", | ||
| tiles: source.tiles, | ||
| minzoom: source.minzoom, | ||
| maxzoom: source.maxzoom, | ||
| attribution: source.attribution, | ||
| ...(opts.promoteId ? { | ||
| promoteId: opts.promoteId | ||
| } : {}) | ||
| }; | ||
| }, "vector-tiles"), | ||
| "raster-tiles": /* @__PURE__ */__name((source, _opts) => { | ||
| return { | ||
| type: "raster", | ||
| tiles: source.tiles, | ||
| tileSize: source.tileSize ?? 256, | ||
| minzoom: source.minzoom, | ||
| maxzoom: source.maxzoom, | ||
| attribution: source.attribution | ||
| }; | ||
| }, "raster-tiles"), | ||
| image: /* @__PURE__ */__name((source, _opts) => { | ||
| return { | ||
| type: "image", | ||
| url: source.url, | ||
| coordinates: source.coordinates | ||
| }; | ||
| }, "image"), | ||
| "raster-dem": /* @__PURE__ */__name((source, _opts) => { | ||
| return { | ||
| type: "raster-dem", | ||
| tiles: source.tiles, | ||
| url: source.url, | ||
| tileSize: source.tileSize ?? 256, | ||
| encoding: source.encoding, | ||
| minzoom: source.minzoom, | ||
| maxzoom: source.maxzoom, | ||
| attribution: source.attribution | ||
| }; | ||
| }, "raster-dem"), | ||
| video: /* @__PURE__ */__name((source, _opts) => { | ||
| return { | ||
| type: "video", | ||
| urls: source.urls, | ||
| coordinates: source.coordinates | ||
| }; | ||
| }, "video") | ||
| }; | ||
| var toMaplibreSource = /* @__PURE__ */__name((source, opts = {}) => { | ||
| const fn = sourceConverters[source.type]; | ||
| return fn(source, opts); | ||
| }, "toMaplibreSource"); | ||
| var resolvePromoteIdForSource = /* @__PURE__ */__name((spec, sourceId) => { | ||
| for (const entry of spec.mapData ?? []) { | ||
| if (entry.mapId === sourceId && entry.joinKey) return entry.joinKey; | ||
| } | ||
| return void 0; | ||
| }, "resolvePromoteIdForSource"); | ||
| // src/adapters/maplibre/patchDispatch.ts | ||
| var applyLayerAdd = /* @__PURE__ */__name((map, viewState, newLayer) => { | ||
| if (map.getLayer(newLayer.id)) return; | ||
| const source = viewState.spec.sources.find(s => { | ||
| return s.id === newLayer.sourceId; | ||
| }); | ||
| const effectiveSourceLayer = newLayer.sourceLayer ?? (source && "sourceLayer" in source ? source.sourceLayer : void 0); | ||
| map.addLayer(stripUndefinedPaint(toMaplibreLayer(newLayer, effectiveSourceLayer, viewState.spec.legends))); | ||
| viewState.spec = { | ||
| ...viewState.spec, | ||
| layers: [...viewState.spec.layers, newLayer] | ||
| }; | ||
| }, "applyLayerAdd"); | ||
| var applyLayerRemove = /* @__PURE__ */__name((map, viewState, layerId) => { | ||
| cancelPendingStyleListenersForLayer(map, layerId); | ||
| if (map.getLayer(layerId)) map.removeLayer(layerId); | ||
| viewState.spec = { | ||
| ...viewState.spec, | ||
| layers: viewState.spec.layers.filter(l => { | ||
| return l.id !== layerId; | ||
| }) | ||
| }; | ||
| }, "applyLayerRemove"); | ||
| var applyLayerPaintReplace = /* @__PURE__ */__name((map, viewState, path, value) => { | ||
| const parts = path.split("."); | ||
| if (parts.length < 4 || parts[2] !== "paint") return; | ||
| const layerId = parts[1]; | ||
| const specKey = parts[3]; | ||
| const layer = viewState.spec.layers.find(l => { | ||
| return l.id === layerId; | ||
| }); | ||
| if (!layer) return; | ||
| const maplibreKey = specPaintKeyToMaplibre(specKey, layer.geometry); | ||
| if (!maplibreKey) return; | ||
| setPaintWhenReady(map, layerId, maplibreKey, value); | ||
| }, "applyLayerPaintReplace"); | ||
| var applyLayerPatch = /* @__PURE__ */__name((map, viewState, patch) => { | ||
| if (patch.op === "add" && patch.value != null) { | ||
| applyLayerAdd(map, viewState, patch.value); | ||
| return; | ||
| } | ||
| if (patch.op === "remove") { | ||
| applyLayerRemove(map, viewState, patch.value); | ||
| return; | ||
| } | ||
| if (patch.op === "replace" && patch.value !== void 0) { | ||
| applyLayerPaintReplace(map, viewState, patch.path, patch.value); | ||
| } | ||
| }, "applyLayerPatch"); | ||
| var applySourcePatch = /* @__PURE__ */__name((map, viewState, patch) => { | ||
| if (patch.op === "add" && patch.value != null) { | ||
| const newSource = patch.value; | ||
| if (map.getSource(newSource.id)) return; | ||
| map.addSource(newSource.id, toMaplibreSource(newSource, { | ||
| promoteId: resolvePromoteIdForSource(viewState.spec, newSource.id) | ||
| })); | ||
| viewState.spec = { | ||
| ...viewState.spec, | ||
| sources: [...viewState.spec.sources, newSource] | ||
| }; | ||
| return; | ||
| } | ||
| if (patch.op !== "remove") return; | ||
| const sourceId = patch.value; | ||
| if (map.getSource(sourceId)) { | ||
| for (const layer of viewState.spec.layers) { | ||
| if (layer.sourceId === sourceId && map.getLayer(layer.id)) { | ||
| cancelPendingStyleListenersForLayer(map, layer.id); | ||
| map.removeLayer(layer.id); | ||
| } | ||
| } | ||
| map.removeSource(sourceId); | ||
| } | ||
| viewState.spec = { | ||
| ...viewState.spec, | ||
| layers: viewState.spec.layers.filter(l => { | ||
| return l.sourceId !== sourceId; | ||
| }), | ||
| sources: viewState.spec.sources.filter(s => { | ||
| return s.id !== sourceId; | ||
| }) | ||
| }; | ||
| }, "applySourcePatch"); | ||
| // src/adapters/maplibre/syncSourcesAndLayers.ts | ||
| var removeStaleLayers = /* @__PURE__ */__name((map, spec, previousSpec) => { | ||
| for (const layer of previousSpec.layers) { | ||
| const stillExists = spec.layers.some(nextLayer => { | ||
| return nextLayer.id === layer.id; | ||
| }); | ||
| if (!stillExists && map.getLayer(layer.id)) { | ||
| map.removeLayer(layer.id); | ||
| } | ||
| } | ||
| }, "removeStaleLayers"); | ||
| var removeStaleSources = /* @__PURE__ */__name((map, spec, previousSpec) => { | ||
| for (const source of previousSpec.sources) { | ||
| const stillExists = spec.sources.some(nextSource => { | ||
| return nextSource.id === source.id; | ||
| }); | ||
| if (!stillExists && map.getSource(source.id)) { | ||
| map.removeSource(source.id); | ||
| } | ||
| } | ||
| }, "removeStaleSources"); | ||
| var upsertSources = /* @__PURE__ */__name((map, spec, previousSpec) => { | ||
| for (const source of spec.sources) { | ||
| if (!map.getSource(source.id)) { | ||
| map.addSource(source.id, toMaplibreSource(source, { | ||
| promoteId: resolvePromoteIdForSource(spec, source.id) | ||
| })); | ||
| continue; | ||
| } | ||
| if (source.type !== "geojson") continue; | ||
| const prevSource = previousSpec?.sources.find(s => { | ||
| return s.id === source.id; | ||
| }); | ||
| const prevData = prevSource?.type === "geojson" ? prevSource.data : void 0; | ||
| if (prevData !== source.data) { | ||
| map.getSource(source.id).setData(source.data); | ||
| } | ||
| } | ||
| }, "upsertSources"); | ||
| var writePaintProperty = /* @__PURE__ */__name((map, spec, layer, property, value) => { | ||
| if (property === "fill-color" && layer.mapDataId) { | ||
| const hasExplicitFillColor = !!layer.paint?.fillColor; | ||
| const hasLegendFillColor = resolveLegendFillColorExpression(layer, spec.legends) !== void 0; | ||
| if (!hasExplicitFillColor && !hasLegendFillColor) return; | ||
| } | ||
| map.setPaintProperty(layer.id, property, value); | ||
| }, "writePaintProperty"); | ||
| var upsertLayers = /* @__PURE__ */__name((map, spec) => { | ||
| for (const layer of spec.layers) { | ||
| const source = spec.sources.find(s => { | ||
| return s.id === layer.sourceId; | ||
| }); | ||
| const sourceLayer = source && "sourceLayer" in source ? source.sourceLayer : void 0; | ||
| const desiredLayer = toMaplibreLayer(layer, sourceLayer, spec.legends); | ||
| stripUndefinedPaint(desiredLayer); | ||
| if (!map.getLayer(layer.id)) { | ||
| map.addLayer(desiredLayer); | ||
| continue; | ||
| } | ||
| map.setLayoutProperty(layer.id, "visibility", layer.visible === false ? "none" : "visible"); | ||
| const paint = desiredLayer.paint; | ||
| if (!paint) continue; | ||
| for (const [property, value] of Object.entries(paint)) { | ||
| writePaintProperty(map, spec, layer, property, value); | ||
| } | ||
| } | ||
| }, "upsertLayers"); | ||
| var syncSourcesAndLayers = /* @__PURE__ */__name((map, spec, previousSpec) => { | ||
| if (previousSpec) { | ||
| removeStaleLayers(map, spec, previousSpec); | ||
| removeStaleSources(map, spec, previousSpec); | ||
| } | ||
| upsertSources(map, spec, previousSpec); | ||
| upsertLayers(map, spec); | ||
| }, "syncSourcesAndLayers"); | ||
| // src/adapters/maplibre/MapLibreAdapter.ts | ||
| var DEFAULT_STYLE = "https://demotiles.maplibre.org/style.json"; | ||
| var BLANK_STYLE = { | ||
| version: 8, | ||
| sources: {}, | ||
| layers: [] | ||
| }; | ||
| var resolveStyle = /* @__PURE__ */__name(spec => { | ||
| if (spec.basemap?.visible === false) return BLANK_STYLE; | ||
| return spec.basemap?.styleUrl ?? DEFAULT_STYLE; | ||
| }, "resolveStyle"); | ||
| var syncCenter = /* @__PURE__ */__name((map, prev, next) => { | ||
| if (!next?.center || next.center.length !== 2) return; | ||
| const [lng, lat] = next.center; | ||
| if (prev?.center?.[0] === lng && prev?.center?.[1] === lat) return; | ||
| map.setCenter(next.center); | ||
| }, "syncCenter"); | ||
| var syncZoom = /* @__PURE__ */__name((map, prev, next) => { | ||
| if (next?.zoom === void 0 || next.zoom === prev?.zoom) return; | ||
| map.setZoom(next.zoom); | ||
| }, "syncZoom"); | ||
| var syncMapView = /* @__PURE__ */__name((map, prev, next) => { | ||
| if (!next) return; | ||
| const p = prev ?? {}; | ||
| syncCenter(map, prev, next); | ||
| syncZoom(map, prev, next); | ||
| const pp = p.pitch ?? 0; | ||
| const np = next.pitch ?? 0; | ||
| if (pp !== np) map.setPitch(np); | ||
| const pb = p.bearing ?? 0; | ||
| const nb = next.bearing ?? 0; | ||
| if (pb !== nb) map.setBearing(nb); | ||
| }, "syncMapView"); | ||
| var createMap = /* @__PURE__ */__name((spec, container) => { | ||
| const { | ||
| view | ||
| } = spec; | ||
| const style = resolveStyle(spec); | ||
| const map = new maplibregl.Map({ | ||
| container, | ||
| style, | ||
| center: view?.center ?? [0, 0], | ||
| zoom: view?.zoom ?? 1, | ||
| pitch: view?.pitch ?? 0, | ||
| bearing: view?.bearing ?? 0 | ||
| }); | ||
| map.addControl(new maplibregl.NavigationControl({ | ||
| visualizePitch: true, | ||
| visualizeRoll: true, | ||
| showZoom: true, | ||
| showCompass: true | ||
| })); | ||
| return { | ||
| map, | ||
| style | ||
| }; | ||
| }, "createMap"); | ||
| var mountView = /* @__PURE__ */__name((views, container, spec, viewId) => { | ||
| const { | ||
| map, | ||
| style | ||
| } = createMap(spec, container); | ||
| views.set(viewId, { | ||
| map, | ||
| spec, | ||
| style | ||
| }); | ||
| map.on("load", () => { | ||
| const viewState = views.get(viewId); | ||
| if (!viewState) return; | ||
| syncSourcesAndLayers(map, viewState.spec, null); | ||
| reapplyAllMapData(map, viewState.spec); | ||
| }); | ||
| let _removed = false; | ||
| return { | ||
| viewId, | ||
| container, | ||
| destroy: /* @__PURE__ */__name(() => { | ||
| if (_removed) return; | ||
| _removed = true; | ||
| try { | ||
| map.remove(); | ||
| } catch {} | ||
| views.delete(viewId); | ||
| }, "destroy") | ||
| }; | ||
| }, "mountView"); | ||
| var updateView = /* @__PURE__ */__name((views, viewId, viewState, spec) => { | ||
| const { | ||
| map | ||
| } = viewState; | ||
| const nextStyle = resolveStyle(spec); | ||
| const previousSpec = viewState.spec; | ||
| viewState.spec = spec; | ||
| syncMapView(map, previousSpec.view, spec.view); | ||
| const onStyleReady = /* @__PURE__ */__name(() => { | ||
| const updated = views.get(viewId); | ||
| if (!updated) return; | ||
| syncSourcesAndLayers(map, updated.spec, null); | ||
| reapplyAllMapData(map, updated.spec); | ||
| reapplyLegendDrivenFillPaint(map, updated.spec); | ||
| }, "onStyleReady"); | ||
| if (nextStyle !== viewState.style) { | ||
| viewState.style = nextStyle; | ||
| map.once("style.load", onStyleReady); | ||
| map.setStyle(nextStyle); | ||
| return; | ||
| } | ||
| if (map.isStyleLoaded()) { | ||
| syncSourcesAndLayers(map, spec, previousSpec); | ||
| if (previousSpec.mapData !== spec.mapData) { | ||
| for (const prevMd of previousSpec.mapData ?? []) { | ||
| const nextMd = (spec.mapData ?? []).find(md => { | ||
| return md.mapDataId === prevMd.mapDataId; | ||
| }); | ||
| if (!nextMd || nextMd !== prevMd) removeMapDataFromSource(map, prevMd); | ||
| } | ||
| reapplyAllMapData(map, spec); | ||
| reapplyLegendDrivenFillPaint(map, spec); | ||
| } | ||
| } else { | ||
| map.once("style.load", onStyleReady); | ||
| } | ||
| }, "updateView"); | ||
| var dispatchPatch = /* @__PURE__ */__name((viewState, patch) => { | ||
| const { | ||
| map | ||
| } = viewState; | ||
| if (patch.target === "layer") { | ||
| applyLayerPatch(map, viewState, patch); | ||
| } else if (patch.target === "source") { | ||
| applySourcePatch(map, viewState, patch); | ||
| } else if (patch.target === "mapData") { | ||
| applyMapDataPatchToMap(map, viewState.spec.mapData ?? [], patch); | ||
| viewState.spec = applyMapDataPatchToSpec(viewState.spec, patch); | ||
| reapplyLegendDrivenFillPaint(map, viewState.spec); | ||
| } else { | ||
| console.warn(`[GeoVis] dispatchPatch: unknown target "${patch.target}" \u2014 patch ignored.`); | ||
| } | ||
| }, "dispatchPatch"); | ||
| var applySetView = /* @__PURE__ */__name((map, options) => { | ||
| const { | ||
| center, | ||
| zoom, | ||
| pitch, | ||
| bearing, | ||
| animate = true | ||
| } = options; | ||
| const camera = {}; | ||
| if (center !== void 0) camera.center = center; | ||
| if (zoom !== void 0) camera.zoom = zoom; | ||
| if (pitch !== void 0) camera.pitch = pitch; | ||
| if (bearing !== void 0) camera.bearing = bearing; | ||
| if (Object.keys(camera).length === 0) return; | ||
| if (animate) { | ||
| map.flyTo(camera); | ||
| } else { | ||
| map.jumpTo(camera); | ||
| } | ||
| }, "applySetView"); | ||
| var destroyAll = /* @__PURE__ */__name(views => { | ||
| for (const viewState of views.values()) { | ||
| try { | ||
| viewState.map.remove(); | ||
| } catch {} | ||
| } | ||
| views.clear(); | ||
| }, "destroyAll"); | ||
| var CAPABILITIES = { | ||
| supports3D: false, | ||
| supportsRaster: true, | ||
| supportsVectorTiles: true, | ||
| supportsCustomLayers: true | ||
| }; | ||
| var createMapLibreAdapter = /* @__PURE__ */__name(() => { | ||
| const _views = /* @__PURE__ */new Map(); | ||
| return { | ||
| id: "maplibre", | ||
| getCapabilities: /* @__PURE__ */__name(() => { | ||
| return CAPABILITIES; | ||
| }, "getCapabilities"), | ||
| mount: /* @__PURE__ */__name((container, spec, viewId) => { | ||
| return mountView(_views, container, spec, viewId); | ||
| }, "mount"), | ||
| update: /* @__PURE__ */__name(spec => { | ||
| for (const [viewId, viewState] of _views) updateView(_views, viewId, viewState, spec); | ||
| }, "update"), | ||
| applyPatch: /* @__PURE__ */__name(patch => { | ||
| for (const viewState of _views.values()) dispatchPatch(viewState, patch); | ||
| }, "applyPatch"), | ||
| setView: /* @__PURE__ */__name(options => { | ||
| for (const viewState of _views.values()) { | ||
| applySetView(viewState.map, options); | ||
| } | ||
| }, "setView"), | ||
| destroy: /* @__PURE__ */__name(() => { | ||
| destroyAll(_views); | ||
| }, "destroy"), | ||
| getNativeInstance: /* @__PURE__ */__name(() => { | ||
| return _views.size > 0 ? _views.values().next().value?.map ?? null : null; | ||
| }, "getNativeInstance") | ||
| }; | ||
| }, "createMapLibreAdapter"); | ||
| var MapLibreAdapter_default = createMapLibreAdapter; | ||
| export { MapLibreAdapter_default as default, toMaplibreLayer, toMaplibreSource }; |
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
129925
2.8%3230
3.29%5
25%+ Added
+ Added