@elabs-ai/components-maps 4.1.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/chunk-2FPB53NG.js +36 -0
- package/dist/chunk-2FPB53NG.js.map +1 -0
- package/dist/chunk-2VVJRT66.js +37 -0
- package/dist/chunk-2VVJRT66.js.map +1 -0
- package/dist/chunk-3HXFONRP.js +2 -0
- package/dist/chunk-3HXFONRP.js.map +1 -0
- package/dist/chunk-3SJN3IYT.js +2 -0
- package/dist/chunk-3SJN3IYT.js.map +1 -0
- package/dist/chunk-4DANBD5B.js +55 -0
- package/dist/chunk-4DANBD5B.js.map +1 -0
- package/dist/chunk-5PDVG53B.js +2 -0
- package/dist/chunk-5PDVG53B.js.map +1 -0
- package/dist/chunk-643R7IJC.js +2 -0
- package/dist/chunk-643R7IJC.js.map +1 -0
- package/dist/chunk-B3ZDBQ6I.js +164 -0
- package/dist/chunk-B3ZDBQ6I.js.map +1 -0
- package/dist/chunk-BGCE3RR5.js +35 -0
- package/dist/chunk-BGCE3RR5.js.map +1 -0
- package/dist/chunk-C4VI37TD.js +485 -0
- package/dist/chunk-C4VI37TD.js.map +1 -0
- package/dist/chunk-CJJT6AEI.js +179 -0
- package/dist/chunk-CJJT6AEI.js.map +1 -0
- package/dist/chunk-D2RCPTRO.js +2 -0
- package/dist/chunk-D2RCPTRO.js.map +1 -0
- package/dist/chunk-DTV7JZVN.js +2 -0
- package/dist/chunk-DTV7JZVN.js.map +1 -0
- package/dist/chunk-E65Z3MSV.js +80 -0
- package/dist/chunk-E65Z3MSV.js.map +1 -0
- package/dist/chunk-E7VPNINZ.js +70 -0
- package/dist/chunk-E7VPNINZ.js.map +1 -0
- package/dist/chunk-FHV5IA6N.js +100 -0
- package/dist/chunk-FHV5IA6N.js.map +1 -0
- package/dist/chunk-FQ6V7BFZ.js +347 -0
- package/dist/chunk-FQ6V7BFZ.js.map +1 -0
- package/dist/chunk-G7JDXDJN.js +19 -0
- package/dist/chunk-G7JDXDJN.js.map +1 -0
- package/dist/chunk-GWULZOMW.js +21 -0
- package/dist/chunk-GWULZOMW.js.map +1 -0
- package/dist/chunk-GYDTTAVD.js +120 -0
- package/dist/chunk-GYDTTAVD.js.map +1 -0
- package/dist/chunk-HCOBWIM4.js +48 -0
- package/dist/chunk-HCOBWIM4.js.map +1 -0
- package/dist/chunk-IAHK5MVT.js +2 -0
- package/dist/chunk-IAHK5MVT.js.map +1 -0
- package/dist/chunk-JGJ6442I.js +235 -0
- package/dist/chunk-JGJ6442I.js.map +1 -0
- package/dist/chunk-JYFERXZT.js +18 -0
- package/dist/chunk-JYFERXZT.js.map +1 -0
- package/dist/chunk-LU2UEKFM.js +53 -0
- package/dist/chunk-LU2UEKFM.js.map +1 -0
- package/dist/chunk-LVYM6C5C.js +2 -0
- package/dist/chunk-LVYM6C5C.js.map +1 -0
- package/dist/chunk-MIMDP3EU.js +104 -0
- package/dist/chunk-MIMDP3EU.js.map +1 -0
- package/dist/chunk-NNBURJHY.js +2 -0
- package/dist/chunk-NNBURJHY.js.map +1 -0
- package/dist/chunk-NPUC3V37.js +2 -0
- package/dist/chunk-NPUC3V37.js.map +1 -0
- package/dist/chunk-OEVVNPN3.js +201 -0
- package/dist/chunk-OEVVNPN3.js.map +1 -0
- package/dist/chunk-PF3R2BXM.js +200 -0
- package/dist/chunk-PF3R2BXM.js.map +1 -0
- package/dist/chunk-Q2I5YODW.js +2 -0
- package/dist/chunk-Q2I5YODW.js.map +1 -0
- package/dist/chunk-QDWWP3N7.js +247 -0
- package/dist/chunk-QDWWP3N7.js.map +1 -0
- package/dist/chunk-QU33KPMP.js +2 -0
- package/dist/chunk-QU33KPMP.js.map +1 -0
- package/dist/chunk-RK4BUMHS.js +2 -0
- package/dist/chunk-RK4BUMHS.js.map +1 -0
- package/dist/chunk-RNSLG64F.js +26 -0
- package/dist/chunk-RNSLG64F.js.map +1 -0
- package/dist/chunk-RV673TB4.js +68 -0
- package/dist/chunk-RV673TB4.js.map +1 -0
- package/dist/chunk-S7N2WMVT.js +180 -0
- package/dist/chunk-S7N2WMVT.js.map +1 -0
- package/dist/chunk-T3Z6TCDD.js +2 -0
- package/dist/chunk-T3Z6TCDD.js.map +1 -0
- package/dist/chunk-T4TJTVVA.js +105 -0
- package/dist/chunk-T4TJTVVA.js.map +1 -0
- package/dist/chunk-UMOYRCDX.js +103 -0
- package/dist/chunk-UMOYRCDX.js.map +1 -0
- package/dist/chunk-V2REM6UT.js +185 -0
- package/dist/chunk-V2REM6UT.js.map +1 -0
- package/dist/chunk-VFBRFGID.js +89 -0
- package/dist/chunk-VFBRFGID.js.map +1 -0
- package/dist/chunk-W76NLFER.js +103 -0
- package/dist/chunk-W76NLFER.js.map +1 -0
- package/dist/chunk-WNU4IOGL.js +116 -0
- package/dist/chunk-WNU4IOGL.js.map +1 -0
- package/dist/chunk-WS4Z7ENS.js +494 -0
- package/dist/chunk-WS4Z7ENS.js.map +1 -0
- package/dist/chunk-XQT7YLJY.js +497 -0
- package/dist/chunk-XQT7YLJY.js.map +1 -0
- package/dist/chunk-Y44MEKJV.js +21 -0
- package/dist/chunk-Y44MEKJV.js.map +1 -0
- package/dist/chunk-Z4EPSOB4.js +53 -0
- package/dist/chunk-Z4EPSOB4.js.map +1 -0
- package/dist/chunk-Z5OPDBDC.js +2 -0
- package/dist/chunk-Z5OPDBDC.js.map +1 -0
- package/dist/index.d.ts +1241 -46
- package/dist/index.js +189 -1456
- package/dist/index.js.map +1 -1
- package/dist/lib/arc-math.js +8 -0
- package/dist/lib/arc-math.js.map +1 -0
- package/dist/lib/map-position.js +11 -0
- package/dist/lib/map-position.js.map +1 -0
- package/dist/lib/merge-hover-paint.js +10 -0
- package/dist/lib/merge-hover-paint.js.map +1 -0
- package/dist/lib/plan-crs.js +14 -0
- package/dist/lib/plan-crs.js.map +1 -0
- package/dist/lib/plan-patterns.js +14 -0
- package/dist/lib/plan-patterns.js.map +1 -0
- package/dist/lib/plan-regions.js +18 -0
- package/dist/lib/plan-regions.js.map +1 -0
- package/dist/lib/plan-status.js +14 -0
- package/dist/lib/plan-status.js.map +1 -0
- package/dist/lib/route-arrow.js +12 -0
- package/dist/lib/route-arrow.js.map +1 -0
- package/dist/lib/use-map-breakpoint.js +33 -0
- package/dist/lib/use-map-breakpoint.js.map +1 -0
- package/dist/lib/use-plan-projection.js +9 -0
- package/dist/lib/use-plan-projection.js.map +1 -0
- package/dist/lib/use-token-color.js +10 -0
- package/dist/lib/use-token-color.js.map +1 -0
- package/dist/lib/warn-once.js +10 -0
- package/dist/lib/warn-once.js.map +1 -0
- package/dist/map-annotation/anchor.js +14 -0
- package/dist/map-annotation/anchor.js.map +1 -0
- package/dist/map-annotation/index.js +21 -0
- package/dist/map-annotation/index.js.map +1 -0
- package/dist/map-annotation/map-annotation.js +18 -0
- package/dist/map-annotation/map-annotation.js.map +1 -0
- package/dist/map-arc/index.js +16 -0
- package/dist/map-arc/index.js.map +1 -0
- package/dist/map-arc/map-arc.js +13 -0
- package/dist/map-arc/map-arc.js.map +1 -0
- package/dist/map-canvas/index.css +11 -0
- package/dist/map-canvas/index.css.map +1 -0
- package/dist/map-canvas/index.js +20 -0
- package/dist/map-canvas/index.js.map +1 -0
- package/dist/map-canvas/map-canvas.css +11 -0
- package/dist/map-canvas/map-canvas.css.map +1 -0
- package/dist/map-canvas/map-canvas.js +17 -0
- package/dist/map-canvas/map-canvas.js.map +1 -0
- package/dist/map-canvas/map-context.js +11 -0
- package/dist/map-canvas/map-context.js.map +1 -0
- package/dist/map-canvas/use-resolved-basemap-theme.js +9 -0
- package/dist/map-canvas/use-resolved-basemap-theme.js.map +1 -0
- package/dist/map-cluster-layer/index.js +12 -0
- package/dist/map-cluster-layer/index.js.map +1 -0
- package/dist/map-cluster-layer/map-cluster-layer.js +12 -0
- package/dist/map-cluster-layer/map-cluster-layer.js.map +1 -0
- package/dist/map-controls/index.js +11 -0
- package/dist/map-controls/index.js.map +1 -0
- package/dist/map-controls/map-controls.js +11 -0
- package/dist/map-controls/map-controls.js.map +1 -0
- package/dist/map-geojson/index.js +13 -0
- package/dist/map-geojson/index.js.map +1 -0
- package/dist/map-geojson/map-geojson.js +13 -0
- package/dist/map-geojson/map-geojson.js.map +1 -0
- package/dist/map-inset/index.css +11 -0
- package/dist/map-inset/index.css.map +1 -0
- package/dist/map-inset/index.js +20 -0
- package/dist/map-inset/index.js.map +1 -0
- package/dist/map-inset/map-inset.css +11 -0
- package/dist/map-inset/map-inset.css.map +1 -0
- package/dist/map-inset/map-inset.js +20 -0
- package/dist/map-inset/map-inset.js.map +1 -0
- package/dist/map-legend/index.js +14 -0
- package/dist/map-legend/index.js.map +1 -0
- package/dist/map-legend/map-corner.js +10 -0
- package/dist/map-legend/map-corner.js.map +1 -0
- package/dist/map-legend/map-legend.js +11 -0
- package/dist/map-legend/map-legend.js.map +1 -0
- package/dist/map-marker/index.js +22 -0
- package/dist/map-marker/index.js.map +1 -0
- package/dist/map-marker/map-marker.js +22 -0
- package/dist/map-marker/map-marker.js.map +1 -0
- package/dist/map-north-arrow/index.js +11 -0
- package/dist/map-north-arrow/index.js.map +1 -0
- package/dist/map-north-arrow/map-north-arrow.js +11 -0
- package/dist/map-north-arrow/map-north-arrow.js.map +1 -0
- package/dist/map-plan/index.js +30 -0
- package/dist/map-plan/index.js.map +1 -0
- package/dist/map-plan/map-plan-legend.js +10 -0
- package/dist/map-plan/map-plan-legend.js.map +1 -0
- package/dist/map-plan/map-plan-overlay.js +13 -0
- package/dist/map-plan/map-plan-overlay.js.map +1 -0
- package/dist/map-plan/map-plan-status.js +9 -0
- package/dist/map-plan/map-plan-status.js.map +1 -0
- package/dist/map-plan/map-plan-table.js +10 -0
- package/dist/map-plan/map-plan-table.js.map +1 -0
- package/dist/map-plan/use-plan-selection.js +9 -0
- package/dist/map-plan/use-plan-selection.js.map +1 -0
- package/dist/map-plan-image/index.js +11 -0
- package/dist/map-plan-image/index.js.map +1 -0
- package/dist/map-plan-image/map-plan-image.js +11 -0
- package/dist/map-plan-image/map-plan-image.js.map +1 -0
- package/dist/map-popup/index.js +12 -0
- package/dist/map-popup/index.js.map +1 -0
- package/dist/map-popup/map-popup.js +12 -0
- package/dist/map-popup/map-popup.js.map +1 -0
- package/dist/map-route/index.js +12 -0
- package/dist/map-route/index.js.map +1 -0
- package/dist/map-route/map-route.js +12 -0
- package/dist/map-route/map-route.js.map +1 -0
- package/dist/map-scale-bar/index.js +18 -0
- package/dist/map-scale-bar/index.js.map +1 -0
- package/dist/map-scale-bar/map-scale-bar.js +18 -0
- package/dist/map-scale-bar/map-scale-bar.js.map +1 -0
- package/package.json +5 -5
- package/src/index.ts +83 -0
- package/src/lib/arc-math.test.ts +19 -0
- package/src/lib/arc-math.ts +6 -1
- package/src/lib/map-position.ts +77 -0
- package/src/lib/merge-hover-paint.ts +44 -13
- package/src/lib/plan-crs.parity.test.ts +39 -0
- package/src/lib/plan-crs.test.ts +248 -0
- package/src/lib/plan-crs.ts +252 -0
- package/src/lib/plan-patterns.test.ts +255 -0
- package/src/lib/plan-patterns.ts +160 -0
- package/src/lib/plan-regions.test.ts +186 -0
- package/src/lib/plan-regions.ts +209 -0
- package/src/lib/plan-status.test.ts +87 -0
- package/src/lib/plan-status.ts +147 -0
- package/src/lib/route-arrow.test.ts +88 -0
- package/src/lib/route-arrow.ts +59 -0
- package/src/lib/use-map-breakpoint.test.ts +83 -0
- package/src/lib/use-map-breakpoint.ts +238 -0
- package/src/lib/use-plan-projection.ts +157 -0
- package/src/lib/warn-once.ts +21 -0
- package/src/map-annotation/anchor.ts +92 -0
- package/src/map-annotation/index.ts +15 -0
- package/src/map-annotation/map-annotation.stories.tsx +113 -0
- package/src/map-annotation/map-annotation.test.tsx +106 -0
- package/src/map-annotation/map-annotation.tsx +356 -0
- package/src/map-arc/map-arc.tsx +26 -5
- package/src/map-canvas/map-canvas.stories.tsx +211 -0
- package/src/map-canvas/map-canvas.test.tsx +421 -1
- package/src/map-canvas/map-canvas.tsx +466 -20
- package/src/map-canvas/map-context.ts +11 -0
- package/src/map-cluster-layer/map-cluster-layer.tsx +14 -1
- package/src/map-controls/map-controls.stories.tsx +16 -0
- package/src/map-controls/map-controls.test.tsx +145 -1
- package/src/map-controls/map-controls.tsx +57 -12
- package/src/map-geojson/map-geojson.stories.tsx +153 -3
- package/src/map-geojson/map-geojson.test.tsx +453 -0
- package/src/map-geojson/map-geojson.tsx +591 -51
- package/src/map-inset/index.ts +1 -0
- package/src/map-inset/map-inset.stories.tsx +61 -0
- package/src/map-inset/map-inset.test.tsx +117 -0
- package/src/map-inset/map-inset.tsx +264 -0
- package/src/map-legend/index.ts +10 -0
- package/src/map-legend/map-corner.ts +23 -0
- package/src/map-legend/map-legend.stories.tsx +171 -0
- package/src/map-legend/map-legend.test.tsx +136 -0
- package/src/map-legend/map-legend.tsx +264 -0
- package/src/map-marker/map-marker.stories.tsx +136 -4
- package/src/map-marker/map-marker.test.tsx +383 -3
- package/src/map-marker/map-marker.tsx +414 -44
- package/src/map-north-arrow/index.ts +1 -0
- package/src/map-north-arrow/map-north-arrow.stories.tsx +65 -0
- package/src/map-north-arrow/map-north-arrow.test.tsx +39 -0
- package/src/map-north-arrow/map-north-arrow.tsx +82 -0
- package/src/map-plan/index.ts +13 -0
- package/src/map-plan/map-plan-legend.test.tsx +61 -0
- package/src/map-plan/map-plan-legend.tsx +83 -0
- package/src/map-plan/map-plan-overlay.stories.tsx +260 -0
- package/src/map-plan/map-plan-overlay.test.tsx +302 -0
- package/src/map-plan/map-plan-overlay.tsx +531 -0
- package/src/map-plan/map-plan-status.test.tsx +71 -0
- package/src/map-plan/map-plan-status.tsx +77 -0
- package/src/map-plan/map-plan-table.test.tsx +86 -0
- package/src/map-plan/map-plan-table.tsx +153 -0
- package/src/map-plan/use-plan-selection.ts +78 -0
- package/src/map-plan-image/index.ts +1 -0
- package/src/map-plan-image/map-plan-image.stories.tsx +301 -0
- package/src/map-plan-image/map-plan-image.test.tsx +176 -0
- package/src/map-plan-image/map-plan-image.tsx +279 -0
- package/src/map-popup/map-popup.tsx +18 -11
- package/src/map-route/index.ts +1 -1
- package/src/map-route/map-route.test.tsx +216 -0
- package/src/map-route/map-route.tsx +105 -6
- package/src/map-scale-bar/index.ts +9 -0
- package/src/map-scale-bar/map-scale-bar.stories.tsx +63 -0
- package/src/map-scale-bar/map-scale-bar.test.tsx +65 -0
- package/src/map-scale-bar/map-scale-bar.tsx +145 -0
- package/src/test-utils/locator-fixture.ts +75 -0
- package/src/test-utils/maplibre-mock.ts +267 -20
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ import { ThemeProvider } from "@elabs-ai/components-tokens";
|
|
|
43
43
|
|
|
44
44
|
## What's in it
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
33 exported components — including `DEFAULT_MAP_HEIGHT`, `MAP_BREAKPOINT_THRESHOLDS`, `MAP_BREAKPOINTS`, `MAP_CORNERS`, `MAP_LABEL_ANCHORS`.
|
|
47
47
|
|
|
48
48
|
Don't guess the API — ask the CLI:
|
|
49
49
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
warnMapOnce
|
|
4
|
+
} from "./chunk-G7JDXDJN.js";
|
|
5
|
+
|
|
6
|
+
// src/lib/map-position.ts
|
|
7
|
+
function resolveMapPosition(position, plan, component) {
|
|
8
|
+
const { longitude, latitude, x, y } = position;
|
|
9
|
+
if (x !== void 0 && y !== void 0) {
|
|
10
|
+
if (!plan) {
|
|
11
|
+
warnMapOnce(
|
|
12
|
+
`${component}-plan-position`,
|
|
13
|
+
`<${component} x y> needs a plan coordinate system. Set \`plan\` on <MapCanvas>, or pass \`longitude\`/\`latitude\`.`
|
|
14
|
+
);
|
|
15
|
+
return [0, 0];
|
|
16
|
+
}
|
|
17
|
+
return plan.toLngLat({ x, y });
|
|
18
|
+
}
|
|
19
|
+
if (longitude === void 0 || latitude === void 0) {
|
|
20
|
+
warnMapOnce(
|
|
21
|
+
`${component}-missing-position`,
|
|
22
|
+
`<${component}> needs a position: \`longitude\` and \`latitude\`, or \`x\` and \`y\` on a plan.`
|
|
23
|
+
);
|
|
24
|
+
return [0, 0];
|
|
25
|
+
}
|
|
26
|
+
return [longitude, latitude];
|
|
27
|
+
}
|
|
28
|
+
function toPlanPoint(plan, lngLat) {
|
|
29
|
+
return plan ? plan.toPlan([lngLat.lng, lngLat.lat]) : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
resolveMapPosition,
|
|
34
|
+
toPlanPoint
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=chunk-2FPB53NG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/map-position.ts"],"sourcesContent":["import type { PlanCrs, PlanPoint } from \"./plan-crs\";\nimport { warnMapOnce } from \"./warn-once\";\n\n/** A geographic position — the only kind an ordinary basemap understands. */\nexport type MapGeoPosition = {\n /** Longitude coordinate. */\n longitude: number;\n /** Latitude coordinate. */\n latitude: number;\n x?: never;\n y?: never;\n};\n\n/**\n * A position in PLAN units, for a canvas that declares a `plan` extent: metres\n * along the hall, millimetres across the carriage, pixels into the drawing.\n */\nexport type MapPlanPosition = {\n /** Horizontal position in plan units. */\n x: number;\n /** Vertical position in plan units, from the plan's own origin. */\n y: number;\n longitude?: never;\n latitude?: never;\n};\n\n/**\n * Either a geographic position or a plan one — never a mix. The `never`-typed\n * twins make `{ longitude, x }` a type error rather than a silent winner.\n */\nexport type MapPosition = MapGeoPosition | MapPlanPosition;\n\n/**\n * Reduce a position prop to the two scalars MapLibre wants, so every downstream\n * effect keeps primitive dependencies.\n */\nexport function resolveMapPosition(\n position: {\n longitude?: number;\n latitude?: number;\n x?: number;\n y?: number;\n },\n plan: PlanCrs | null,\n component: string,\n): [number, number] {\n const { longitude, latitude, x, y } = position;\n\n if (x !== undefined && y !== undefined) {\n if (!plan) {\n warnMapOnce(\n `${component}-plan-position`,\n `<${component} x y> needs a plan coordinate system. Set \\`plan\\` on <MapCanvas>, or pass \\`longitude\\`/\\`latitude\\`.`,\n );\n return [0, 0];\n }\n return plan.toLngLat({ x, y });\n }\n\n if (longitude === undefined || latitude === undefined) {\n warnMapOnce(\n `${component}-missing-position`,\n `<${component}> needs a position: \\`longitude\\` and \\`latitude\\`, or \\`x\\` and \\`y\\` on a plan.`,\n );\n return [0, 0];\n }\n\n return [longitude, latitude];\n}\n\n/** The plan point for a lng/lat, or `null` on an ordinary geographic canvas. */\nexport function toPlanPoint(\n plan: PlanCrs | null,\n lngLat: { lng: number; lat: number },\n): PlanPoint | null {\n return plan ? plan.toPlan([lngLat.lng, lngLat.lat]) : null;\n}\n"],"mappings":";;;;;;AAoCO,SAAS,mBACd,UAMA,MACA,WACkB;AAClB,QAAM,EAAE,WAAW,UAAU,GAAG,EAAE,IAAI;AAEtC,MAAI,MAAM,UAAa,MAAM,QAAW;AACtC,QAAI,CAAC,MAAM;AACT;AAAA,QACE,GAAG,SAAS;AAAA,QACZ,IAAI,SAAS;AAAA,MACf;AACA,aAAO,CAAC,GAAG,CAAC;AAAA,IACd;AACA,WAAO,KAAK,SAAS,EAAE,GAAG,EAAE,CAAC;AAAA,EAC/B;AAEA,MAAI,cAAc,UAAa,aAAa,QAAW;AACrD;AAAA,MACE,GAAG,SAAS;AAAA,MACZ,IAAI,SAAS;AAAA,IACf;AACA,WAAO,CAAC,GAAG,CAAC;AAAA,EACd;AAEA,SAAO,CAAC,WAAW,QAAQ;AAC7B;AAGO,SAAS,YACd,MACA,QACkB;AAClB,SAAO,OAAO,KAAK,OAAO,CAAC,OAAO,KAAK,OAAO,GAAG,CAAC,IAAI;AACxD;","names":[]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/lib/route-arrow.ts
|
|
4
|
+
var ROUTE_ARROW_SIZE = 24;
|
|
5
|
+
function routeArrowImageId(routeId) {
|
|
6
|
+
return `route-arrow-${routeId}`;
|
|
7
|
+
}
|
|
8
|
+
function createRouteArrowImage(ink) {
|
|
9
|
+
if (typeof document === "undefined") return null;
|
|
10
|
+
const canvas = document.createElement("canvas");
|
|
11
|
+
canvas.width = ROUTE_ARROW_SIZE;
|
|
12
|
+
canvas.height = ROUTE_ARROW_SIZE;
|
|
13
|
+
const context = canvas.getContext("2d");
|
|
14
|
+
if (!context) return null;
|
|
15
|
+
const size = ROUTE_ARROW_SIZE;
|
|
16
|
+
const inset = size * 0.22;
|
|
17
|
+
const mid = size / 2;
|
|
18
|
+
context.clearRect(0, 0, size, size);
|
|
19
|
+
context.strokeStyle = ink;
|
|
20
|
+
context.lineWidth = size * 0.16;
|
|
21
|
+
context.lineCap = "round";
|
|
22
|
+
context.lineJoin = "round";
|
|
23
|
+
context.beginPath();
|
|
24
|
+
context.moveTo(inset, inset);
|
|
25
|
+
context.lineTo(size - inset, mid);
|
|
26
|
+
context.lineTo(inset, size - inset);
|
|
27
|
+
context.stroke();
|
|
28
|
+
const image = context.getImageData(0, 0, size, size);
|
|
29
|
+
return { width: size, height: size, data: image.data };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
ROUTE_ARROW_SIZE,
|
|
34
|
+
routeArrowImageId,
|
|
35
|
+
createRouteArrowImage
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=chunk-2VVJRT66.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/route-arrow.ts"],"sourcesContent":["/**\n * The direction chevron for `<MapRoute direction>`, drawn on a canvas and\n * registered with `map.addImage`.\n *\n * An icon rather than a text glyph on purpose: a blank MapLibre style ships no\n * glyph endpoint, so a `symbol` layer's `text-field` renders nothing at all —\n * while `icon-image` needs no network and no sprite.\n */\n\n/** Tile edge, in px. Drawn at `pixelRatio: 2`, so it reads ~12 px on screen. */\nexport const ROUTE_ARROW_SIZE = 24;\n\n/** Image id for one route's chevron. Per route, because the ink is per route. */\nexport function routeArrowImageId(routeId: string): string {\n return `route-arrow-${routeId}`;\n}\n\n/** A raw RGBA image MapLibre accepts, without depending on the DOM's `ImageData`. */\nexport interface RouteArrowImage {\n width: number;\n height: number;\n data: Uint8ClampedArray;\n}\n\n/**\n * A chevron pointing along +x. MapLibre aligns a line-placed symbol's x axis\n * with the line's direction, so +x is \"forward\"; `icon-rotate: 180` is backward.\n *\n * Returns `null` where there is no 2D context (jsdom) — the caller then leaves\n * the arrow layer out rather than pointing it at a missing image, which\n * MapLibre would draw as nothing while warning on every frame.\n */\nexport function createRouteArrowImage(ink: string): RouteArrowImage | null {\n if (typeof document === \"undefined\") return null;\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = ROUTE_ARROW_SIZE;\n canvas.height = ROUTE_ARROW_SIZE;\n const context = canvas.getContext(\"2d\");\n if (!context) return null;\n\n const size = ROUTE_ARROW_SIZE;\n const inset = size * 0.22;\n const mid = size / 2;\n\n context.clearRect(0, 0, size, size);\n context.strokeStyle = ink;\n context.lineWidth = size * 0.16;\n context.lineCap = \"round\";\n context.lineJoin = \"round\";\n context.beginPath();\n context.moveTo(inset, inset);\n context.lineTo(size - inset, mid);\n context.lineTo(inset, size - inset);\n context.stroke();\n\n const image = context.getImageData(0, 0, size, size);\n return { width: size, height: size, data: image.data };\n}\n"],"mappings":";;;AAUO,IAAM,mBAAmB;AAGzB,SAAS,kBAAkB,SAAyB;AACzD,SAAO,eAAe,OAAO;AAC/B;AAiBO,SAAS,sBAAsB,KAAqC;AACzE,MAAI,OAAO,aAAa,YAAa,QAAO;AAE5C,QAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,SAAO,QAAQ;AACf,SAAO,SAAS;AAChB,QAAM,UAAU,OAAO,WAAW,IAAI;AACtC,MAAI,CAAC,QAAS,QAAO;AAErB,QAAM,OAAO;AACb,QAAM,QAAQ,OAAO;AACrB,QAAM,MAAM,OAAO;AAEnB,UAAQ,UAAU,GAAG,GAAG,MAAM,IAAI;AAClC,UAAQ,cAAc;AACtB,UAAQ,YAAY,OAAO;AAC3B,UAAQ,UAAU;AAClB,UAAQ,WAAW;AACnB,UAAQ,UAAU;AAClB,UAAQ,OAAO,OAAO,KAAK;AAC3B,UAAQ,OAAO,OAAO,OAAO,GAAG;AAChC,UAAQ,OAAO,OAAO,OAAO,KAAK;AAClC,UAAQ,OAAO;AAEf,QAAM,QAAQ,QAAQ,aAAa,GAAG,GAAG,MAAM,IAAI;AACnD,SAAO,EAAE,OAAO,MAAM,QAAQ,MAAM,MAAM,MAAM,KAAK;AACvD;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/map-annotation/anchor.ts
|
|
4
|
+
var MAP_LABEL_ANCHORS = [
|
|
5
|
+
"top",
|
|
6
|
+
"top-right",
|
|
7
|
+
"right",
|
|
8
|
+
"bottom-right",
|
|
9
|
+
"bottom",
|
|
10
|
+
"bottom-left",
|
|
11
|
+
"left",
|
|
12
|
+
"top-left"
|
|
13
|
+
];
|
|
14
|
+
var DIAGONAL = Math.SQRT1_2;
|
|
15
|
+
function mapAnchorGeometry(anchor, distance) {
|
|
16
|
+
const d = Math.max(0, distance);
|
|
17
|
+
const k = d * DIAGONAL;
|
|
18
|
+
switch (anchor) {
|
|
19
|
+
case "top":
|
|
20
|
+
return { dx: 0, dy: -d, fx: -0.5, fy: -1 };
|
|
21
|
+
case "bottom":
|
|
22
|
+
return { dx: 0, dy: d, fx: -0.5, fy: 0 };
|
|
23
|
+
case "left":
|
|
24
|
+
return { dx: -d, dy: 0, fx: -1, fy: -0.5 };
|
|
25
|
+
case "right":
|
|
26
|
+
return { dx: d, dy: 0, fx: 0, fy: -0.5 };
|
|
27
|
+
case "top-left":
|
|
28
|
+
return { dx: -k, dy: -k, fx: -1, fy: -1 };
|
|
29
|
+
case "top-right":
|
|
30
|
+
return { dx: k, dy: -k, fx: 0, fy: -1 };
|
|
31
|
+
case "bottom-left":
|
|
32
|
+
return { dx: -k, dy: k, fx: -1, fy: 0 };
|
|
33
|
+
case "bottom-right":
|
|
34
|
+
return { dx: k, dy: k, fx: 0, fy: 0 };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function mapAnchorTransform(anchor, distance) {
|
|
38
|
+
const { dx, dy, fx, fy } = mapAnchorGeometry(anchor, distance);
|
|
39
|
+
return `translate(calc(${fx * 100}% + ${dx}px), calc(${fy * 100}% + ${dy}px))`;
|
|
40
|
+
}
|
|
41
|
+
function clampShift(left, top, width, height, boundsWidth, boundsHeight, pad = 4) {
|
|
42
|
+
const maxLeft = Math.max(pad, boundsWidth - width - pad);
|
|
43
|
+
const maxTop = Math.max(pad, boundsHeight - height - pad);
|
|
44
|
+
const clampedLeft = Math.min(maxLeft, Math.max(pad, left));
|
|
45
|
+
const clampedTop = Math.min(maxTop, Math.max(pad, top));
|
|
46
|
+
return { sx: clampedLeft - left, sy: clampedTop - top };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export {
|
|
50
|
+
MAP_LABEL_ANCHORS,
|
|
51
|
+
mapAnchorGeometry,
|
|
52
|
+
mapAnchorTransform,
|
|
53
|
+
clampShift
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=chunk-4DANBD5B.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/map-annotation/anchor.ts"],"sourcesContent":["/**\n * The eight label anchors a map label or annotation can sit at, relative to\n * its point. Compass-style and PHYSICAL on purpose: east of Toronto is east in\n * every writing direction, so a map label never mirrors in RTL.\n */\nexport type MapLabelAnchor =\n | \"top\"\n | \"bottom\"\n | \"left\"\n | \"right\"\n | \"top-left\"\n | \"top-right\"\n | \"bottom-left\"\n | \"bottom-right\";\n\n/** Every anchor, clockwise from `top` (for stories and validation). */\nexport const MAP_LABEL_ANCHORS: readonly MapLabelAnchor[] = [\n \"top\",\n \"top-right\",\n \"right\",\n \"bottom-right\",\n \"bottom\",\n \"bottom-left\",\n \"left\",\n \"top-left\",\n];\n\n/**\n * Where a label box sits for `anchor`, `distance` px away from its point:\n * `dx` / `dy` is the attach point (the box's nearest edge or corner) relative\n * to the point, and `fx` / `fy` the box's own offset as a fraction of its\n * width / height (`0`, `-0.5` or `-1`), so the box grows away from the point.\n */\nexport interface MapAnchorGeometry {\n dx: number;\n dy: number;\n fx: number;\n fy: number;\n}\n\nconst DIAGONAL = Math.SQRT1_2;\n\nexport function mapAnchorGeometry(anchor: MapLabelAnchor, distance: number): MapAnchorGeometry {\n const d = Math.max(0, distance);\n const k = d * DIAGONAL;\n switch (anchor) {\n case \"top\":\n return { dx: 0, dy: -d, fx: -0.5, fy: -1 };\n case \"bottom\":\n return { dx: 0, dy: d, fx: -0.5, fy: 0 };\n case \"left\":\n return { dx: -d, dy: 0, fx: -1, fy: -0.5 };\n case \"right\":\n return { dx: d, dy: 0, fx: 0, fy: -0.5 };\n case \"top-left\":\n return { dx: -k, dy: -k, fx: -1, fy: -1 };\n case \"top-right\":\n return { dx: k, dy: -k, fx: 0, fy: -1 };\n case \"bottom-left\":\n return { dx: -k, dy: k, fx: -1, fy: 0 };\n case \"bottom-right\":\n return { dx: k, dy: k, fx: 0, fy: 0 };\n }\n}\n\n/** The CSS transform that puts a label box at `anchor`, `distance` px from its point. */\nexport function mapAnchorTransform(anchor: MapLabelAnchor, distance: number): string {\n const { dx, dy, fx, fy } = mapAnchorGeometry(anchor, distance);\n return `translate(calc(${fx * 100}% + ${dx}px), calc(${fy * 100}% + ${dy}px))`;\n}\n\n/**\n * The shift that keeps a `width` × `height` box whose top-left corner would\n * land at (`left`, `top`) inside a `boundsWidth` × `boundsHeight` container,\n * `pad` px from every edge. A box larger than the container pins to the\n * start / top edge.\n */\nexport function clampShift(\n left: number,\n top: number,\n width: number,\n height: number,\n boundsWidth: number,\n boundsHeight: number,\n pad = 4,\n): { sx: number; sy: number } {\n const maxLeft = Math.max(pad, boundsWidth - width - pad);\n const maxTop = Math.max(pad, boundsHeight - height - pad);\n const clampedLeft = Math.min(maxLeft, Math.max(pad, left));\n const clampedTop = Math.min(maxTop, Math.max(pad, top));\n return { sx: clampedLeft - left, sy: clampedTop - top };\n}\n"],"mappings":";;;AAgBO,IAAM,oBAA+C;AAAA,EAC1D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAeA,IAAM,WAAW,KAAK;AAEf,SAAS,kBAAkB,QAAwB,UAAqC;AAC7F,QAAM,IAAI,KAAK,IAAI,GAAG,QAAQ;AAC9B,QAAM,IAAI,IAAI;AACd,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO,EAAE,IAAI,GAAG,IAAI,CAAC,GAAG,IAAI,MAAM,IAAI,GAAG;AAAA,IAC3C,KAAK;AACH,aAAO,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,MAAM,IAAI,EAAE;AAAA,IACzC,KAAK;AACH,aAAO,EAAE,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,IAAI,IAAI,KAAK;AAAA,IAC3C,KAAK;AACH,aAAO,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,KAAK;AAAA,IACzC,KAAK;AACH,aAAO,EAAE,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,IAAI,GAAG;AAAA,IAC1C,KAAK;AACH,aAAO,EAAE,IAAI,GAAG,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,GAAG;AAAA,IACxC,KAAK;AACH,aAAO,EAAE,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,IAAI,IAAI,EAAE;AAAA,IACxC,KAAK;AACH,aAAO,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,EAAE;AAAA,EACxC;AACF;AAGO,SAAS,mBAAmB,QAAwB,UAA0B;AACnF,QAAM,EAAE,IAAI,IAAI,IAAI,GAAG,IAAI,kBAAkB,QAAQ,QAAQ;AAC7D,SAAO,kBAAkB,KAAK,GAAG,OAAO,EAAE,aAAa,KAAK,GAAG,OAAO,EAAE;AAC1E;AAQO,SAAS,WACd,MACA,KACA,OACA,QACA,aACA,cACA,MAAM,GACsB;AAC5B,QAAM,UAAU,KAAK,IAAI,KAAK,cAAc,QAAQ,GAAG;AACvD,QAAM,SAAS,KAAK,IAAI,KAAK,eAAe,SAAS,GAAG;AACxD,QAAM,cAAc,KAAK,IAAI,SAAS,KAAK,IAAI,KAAK,IAAI,CAAC;AACzD,QAAM,aAAa,KAAK,IAAI,QAAQ,KAAK,IAAI,KAAK,GAAG,CAAC;AACtD,SAAO,EAAE,IAAI,cAAc,MAAM,IAAI,aAAa,IAAI;AACxD;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
useMap
|
|
4
|
+
} from "./chunk-JYFERXZT.js";
|
|
5
|
+
import {
|
|
6
|
+
warnMapOnce
|
|
7
|
+
} from "./chunk-G7JDXDJN.js";
|
|
8
|
+
|
|
9
|
+
// src/map-plan-image/map-plan-image.tsx
|
|
10
|
+
import { useEffect, useId, useMemo, useRef } from "react";
|
|
11
|
+
import { jsx } from "react/jsx-runtime";
|
|
12
|
+
function isThemedSource(src) {
|
|
13
|
+
return typeof src === "object" && src !== null && "light" in src && "dark" in src;
|
|
14
|
+
}
|
|
15
|
+
function toCanvas(source) {
|
|
16
|
+
if (typeof HTMLCanvasElement !== "undefined" && source instanceof HTMLCanvasElement) {
|
|
17
|
+
return source.width > 0 && source.height > 0 ? source : null;
|
|
18
|
+
}
|
|
19
|
+
const width = "naturalWidth" in source ? source.naturalWidth : source.width;
|
|
20
|
+
const height = "naturalHeight" in source ? source.naturalHeight : source.height;
|
|
21
|
+
if (!width || !height) return null;
|
|
22
|
+
const canvas = document.createElement("canvas");
|
|
23
|
+
canvas.width = width;
|
|
24
|
+
canvas.height = height;
|
|
25
|
+
const context = canvas.getContext("2d");
|
|
26
|
+
if (!context) return null;
|
|
27
|
+
context.drawImage(source, 0, 0);
|
|
28
|
+
return canvas;
|
|
29
|
+
}
|
|
30
|
+
function MapPlanImage({
|
|
31
|
+
src,
|
|
32
|
+
alt,
|
|
33
|
+
id: propId,
|
|
34
|
+
extent,
|
|
35
|
+
coordinates,
|
|
36
|
+
opacity = 1,
|
|
37
|
+
visible = true,
|
|
38
|
+
resampling = "linear",
|
|
39
|
+
beforeId,
|
|
40
|
+
onLoad,
|
|
41
|
+
onError
|
|
42
|
+
}) {
|
|
43
|
+
const { map, isLoaded, plan, resolvedTheme } = useMap();
|
|
44
|
+
const autoId = useId();
|
|
45
|
+
const id = propId ?? autoId;
|
|
46
|
+
const sourceId = `plan-image-source-${id}`;
|
|
47
|
+
const layerId = `plan-image-layer-${id}`;
|
|
48
|
+
const resolvedSrc = isThemedSource(src) ? resolvedTheme === "dark" ? src.dark : src.light : src;
|
|
49
|
+
const url = typeof resolvedSrc === "string" ? resolvedSrc : null;
|
|
50
|
+
const canvas = useMemo(
|
|
51
|
+
() => typeof resolvedSrc === "string" ? null : toCanvas(resolvedSrc),
|
|
52
|
+
[resolvedSrc]
|
|
53
|
+
);
|
|
54
|
+
const corners = useMemo(() => {
|
|
55
|
+
if (coordinates) return coordinates;
|
|
56
|
+
if (!plan) return null;
|
|
57
|
+
if (!extent) return plan.imageCoordinates;
|
|
58
|
+
const { x, y, width, height } = extent;
|
|
59
|
+
return [
|
|
60
|
+
plan.toLngLat({ x, y }),
|
|
61
|
+
plan.toLngLat({ x: x + width, y }),
|
|
62
|
+
plan.toLngLat({ x: x + width, y: y + height }),
|
|
63
|
+
plan.toLngLat({ x, y: y + height })
|
|
64
|
+
];
|
|
65
|
+
}, [coordinates, extent, plan]);
|
|
66
|
+
const cornersKey = corners ? JSON.stringify(corners) : null;
|
|
67
|
+
const latestRef = useRef({ corners, url, onLoad, onError });
|
|
68
|
+
latestRef.current = { corners, url, onLoad, onError };
|
|
69
|
+
if (!corners) {
|
|
70
|
+
warnMapOnce(
|
|
71
|
+
"plan-image-coordinates",
|
|
72
|
+
"<MapPlanImage> needs somewhere to sit: set `plan` on <MapCanvas>, or pass `coordinates`."
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
if (!url && !canvas) {
|
|
76
|
+
warnMapOnce(
|
|
77
|
+
"plan-image-source",
|
|
78
|
+
"<MapPlanImage> could not read its `src`. Pass a URL, or an image/bitmap/canvas that has already decoded."
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
const opacityRef = useRef(opacity);
|
|
82
|
+
opacityRef.current = opacity;
|
|
83
|
+
const resamplingRef = useRef(resampling);
|
|
84
|
+
resamplingRef.current = resampling;
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (!isLoaded || !map) return;
|
|
87
|
+
const { corners: initialCorners, url: initialUrl } = latestRef.current;
|
|
88
|
+
if (!initialCorners || !initialUrl && !canvas) return;
|
|
89
|
+
map.addSource(
|
|
90
|
+
sourceId,
|
|
91
|
+
canvas ? { type: "canvas", canvas, coordinates: initialCorners, animate: false } : { type: "image", url: initialUrl, coordinates: initialCorners }
|
|
92
|
+
);
|
|
93
|
+
map.addLayer(
|
|
94
|
+
{
|
|
95
|
+
id: layerId,
|
|
96
|
+
type: "raster",
|
|
97
|
+
source: sourceId,
|
|
98
|
+
paint: {
|
|
99
|
+
"raster-opacity": opacityRef.current,
|
|
100
|
+
// A plan swap should replace the picture, not cross-fade it.
|
|
101
|
+
"raster-fade-duration": 0,
|
|
102
|
+
"raster-resampling": resamplingRef.current
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
beforeId
|
|
106
|
+
);
|
|
107
|
+
return () => {
|
|
108
|
+
try {
|
|
109
|
+
if (map.getLayer(layerId)) map.removeLayer(layerId);
|
|
110
|
+
if (map.getSource(sourceId)) map.removeSource(sourceId);
|
|
111
|
+
} catch {
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}, [isLoaded, map, sourceId, layerId, canvas, beforeId]);
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if (!isLoaded || !map || !cornersKey) return;
|
|
117
|
+
const source = map.getSource(sourceId);
|
|
118
|
+
source?.setCoordinates?.(latestRef.current.corners);
|
|
119
|
+
}, [isLoaded, map, sourceId, cornersKey]);
|
|
120
|
+
const mountedUrlRef = useRef(url);
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
if (!isLoaded || !map || !url) return;
|
|
123
|
+
if (mountedUrlRef.current === url) return;
|
|
124
|
+
mountedUrlRef.current = url;
|
|
125
|
+
const source = map.getSource(sourceId);
|
|
126
|
+
source?.updateImage?.({
|
|
127
|
+
url,
|
|
128
|
+
...latestRef.current.corners ? { coordinates: latestRef.current.corners } : {}
|
|
129
|
+
});
|
|
130
|
+
}, [isLoaded, map, sourceId, url]);
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
if (!isLoaded || !map || !map.getLayer(layerId)) return;
|
|
133
|
+
map.setPaintProperty(layerId, "raster-opacity", opacity);
|
|
134
|
+
map.setPaintProperty(layerId, "raster-resampling", resampling);
|
|
135
|
+
map.setLayoutProperty(layerId, "visibility", visible ? "visible" : "none");
|
|
136
|
+
}, [isLoaded, map, layerId, opacity, resampling, visible]);
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
if (!isLoaded || !map) return;
|
|
139
|
+
const source = map.getSource(sourceId);
|
|
140
|
+
if (!source?.on) return;
|
|
141
|
+
const handleData = (payload) => {
|
|
142
|
+
if (payload?.sourceDataType !== "metadata") {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
latestRef.current.onLoad?.();
|
|
146
|
+
};
|
|
147
|
+
const handleError = (payload) => {
|
|
148
|
+
const error = payload?.error;
|
|
149
|
+
latestRef.current.onError?.(error instanceof Error ? error : new Error("Plan image failed"));
|
|
150
|
+
};
|
|
151
|
+
source.on("data", handleData);
|
|
152
|
+
source.on("error", handleError);
|
|
153
|
+
return () => {
|
|
154
|
+
source.off?.("data", handleData);
|
|
155
|
+
source.off?.("error", handleError);
|
|
156
|
+
};
|
|
157
|
+
}, [isLoaded, map, sourceId]);
|
|
158
|
+
return alt ? /* @__PURE__ */ jsx("span", { "data-slot": "map-plan-image", className: "sr-only", children: alt }) : /* @__PURE__ */ jsx("span", { "data-slot": "map-plan-image", hidden: true });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export {
|
|
162
|
+
MapPlanImage
|
|
163
|
+
};
|
|
164
|
+
//# sourceMappingURL=chunk-B3ZDBQ6I.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/map-plan-image/map-plan-image.tsx"],"sourcesContent":["\"use client\";\n\nimport type MapLibreGL from \"maplibre-gl\";\nimport { useEffect, useId, useMemo, useRef } from \"react\";\n\nimport { useMap } from \"../map-canvas/map-context\";\nimport { warnMapOnce } from \"../lib/warn-once\";\n\n/**\n * The picture under a plan: a URL, or an already-decoded image, bitmap or\n * canvas. A `{ light, dark }` pair swaps with the active theme — a plan drawing\n * has its own baked-in ink, and one asset rarely reads well on both surfaces.\n */\nexport type MapPlanImageSource = string | HTMLImageElement | ImageBitmap | HTMLCanvasElement;\n\nexport type MapPlanImageProps = {\n /** The plan picture. Pass `{ light, dark }` to swap it with the theme. */\n src: MapPlanImageSource | { light: MapPlanImageSource; dark: MapPlanImageSource };\n /**\n * What the picture shows, for anyone who cannot see it (\"Level 3 floor plan,\n * 14 rooms around a central core\"). A plan drawn into WebGL has no accessible\n * name of its own, so this is the only description of it.\n */\n alt?: string;\n /** Optional identifier for the source/layer. Auto-generated if not provided. */\n id?: string;\n /**\n * Where the picture sits, in PLAN units. Defaults to the whole plan extent, so\n * a single full-bleed plan needs nothing here. Use it to place one wing, one\n * machine cell or one carriage inside a larger plan.\n */\n extent?: { x: number; y: number; width: number; height: number };\n /**\n * The four corners in lng/lat (top-left, top-right, bottom-right, bottom-left)\n * — an escape hatch that also works on an ordinary geographic canvas, like\n * Leaflet's `imageOverlay`. Wins over `extent`.\n */\n coordinates?: MapLibreGL.Coordinates;\n /**\n * Opacity, 0–1 (default 1). A photographic or ink-heavy plan usually reads\n * best held back around 0.35 so the data drawn on top stays the focus.\n */\n opacity?: number;\n /** Hide the picture without tearing the source down — a multi-floor switch (default true). */\n visible?: boolean;\n /** `\"nearest\"` keeps CAD line art crisp when zoomed past its native resolution. */\n resampling?: \"linear\" | \"nearest\";\n /** MapLibre layer id to insert this layer before (z-order control). */\n beforeId?: string;\n /** Fired once the picture has been fetched and decoded. */\n onLoad?: () => void;\n /** Fired when the picture cannot be loaded. The plan itself keeps working without it. */\n onError?: (error: Error) => void;\n};\n\nfunction isThemedSource(\n src: MapPlanImageProps[\"src\"],\n): src is { light: MapPlanImageSource; dark: MapPlanImageSource } {\n return typeof src === \"object\" && src !== null && \"light\" in src && \"dark\" in src;\n}\n\n/**\n * Draw a decoded image or bitmap into a canvas MapLibre can read. Returns\n * `null` when there is no 2D context (jsdom, a zero-sized canvas) — the plan\n * then renders without its picture rather than throwing.\n */\nfunction toCanvas(source: Exclude<MapPlanImageSource, string>): HTMLCanvasElement | null {\n if (typeof HTMLCanvasElement !== \"undefined\" && source instanceof HTMLCanvasElement) {\n return source.width > 0 && source.height > 0 ? source : null;\n }\n\n const width = \"naturalWidth\" in source ? source.naturalWidth : source.width;\n const height = \"naturalHeight\" in source ? source.naturalHeight : source.height;\n if (!width || !height) return null;\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n const context = canvas.getContext(\"2d\");\n if (!context) return null;\n context.drawImage(source as CanvasImageSource, 0, 0);\n return canvas;\n}\n\n/**\n * The plan picture under the shapes — a floor plan, a factory layout, a deck\n * plan. The map equivalent of Qlik Sense's image background layer and Leaflet's\n * `imageOverlay`: by default it covers the whole plan extent declared on\n * `<MapCanvas plan>`, so its corners need no coordinates at all.\n *\n * Render it FIRST among the canvas children so the picture sits under the data,\n * or pin the order with `beforeId`.\n *\n * ```tsx\n * <MapCanvas blank plan={{ width: 2400, height: 1600, unit: \"cm\" }}>\n * <MapPlanImage src={floorPlanUrl} alt=\"Level 3 floor plan\" opacity={0.35} />\n * <MapGeoJSON data={roomsInPlanUnits} promoteId=\"id\" />\n * </MapCanvas>\n * ```\n *\n * A picture is optional and often worth skipping: a plan drawn as GeoJSON in\n * plan units paints from tokens, themes correctly, and never blurs.\n */\nexport function MapPlanImage({\n src,\n alt,\n id: propId,\n extent,\n coordinates,\n opacity = 1,\n visible = true,\n resampling = \"linear\",\n beforeId,\n onLoad,\n onError,\n}: MapPlanImageProps) {\n const { map, isLoaded, plan, resolvedTheme } = useMap();\n const autoId = useId();\n const id = propId ?? autoId;\n const sourceId = `plan-image-source-${id}`;\n const layerId = `plan-image-layer-${id}`;\n\n const resolvedSrc = isThemedSource(src) ? (resolvedTheme === \"dark\" ? src.dark : src.light) : src;\n const url = typeof resolvedSrc === \"string\" ? resolvedSrc : null;\n const canvas = useMemo(\n () => (typeof resolvedSrc === \"string\" ? null : toCanvas(resolvedSrc)),\n [resolvedSrc],\n );\n\n const corners = useMemo<MapLibreGL.Coordinates | null>(() => {\n if (coordinates) return coordinates;\n if (!plan) return null;\n if (!extent) return plan.imageCoordinates;\n const { x, y, width, height } = extent;\n return [\n plan.toLngLat({ x, y }),\n plan.toLngLat({ x: x + width, y }),\n plan.toLngLat({ x: x + width, y: y + height }),\n plan.toLngLat({ x, y: y + height }),\n ];\n }, [coordinates, extent, plan]);\n\n // Keyed on the values, so an inline `extent` literal doesn't re-place the\n // picture on every render.\n const cornersKey = corners ? JSON.stringify(corners) : null;\n\n const latestRef = useRef({ corners, url, onLoad, onError });\n latestRef.current = { corners, url, onLoad, onError };\n\n if (!corners) {\n warnMapOnce(\n \"plan-image-coordinates\",\n \"<MapPlanImage> needs somewhere to sit: set `plan` on <MapCanvas>, or pass `coordinates`.\",\n );\n }\n if (!url && !canvas) {\n warnMapOnce(\n \"plan-image-source\",\n \"<MapPlanImage> could not read its `src`. Pass a URL, or an image/bitmap/canvas that has already decoded.\",\n );\n }\n\n // Read paint through refs so changing it never re-creates the source below.\n const opacityRef = useRef(opacity);\n opacityRef.current = opacity;\n const resamplingRef = useRef(resampling);\n resamplingRef.current = resampling;\n\n // Add the source + layer. Deliberately NOT keyed on the url or the corners:\n // those are updated in place below, so a floor swap never refetches.\n useEffect(() => {\n if (!isLoaded || !map) return;\n const { corners: initialCorners, url: initialUrl } = latestRef.current;\n if (!initialCorners || (!initialUrl && !canvas)) return;\n\n map.addSource(\n sourceId,\n canvas\n ? { type: \"canvas\", canvas, coordinates: initialCorners, animate: false }\n : { type: \"image\", url: initialUrl as string, coordinates: initialCorners },\n );\n map.addLayer(\n {\n id: layerId,\n type: \"raster\",\n source: sourceId,\n paint: {\n \"raster-opacity\": opacityRef.current,\n // A plan swap should replace the picture, not cross-fade it.\n \"raster-fade-duration\": 0,\n \"raster-resampling\": resamplingRef.current,\n },\n },\n beforeId,\n );\n\n return () => {\n try {\n if (map.getLayer(layerId)) map.removeLayer(layerId);\n if (map.getSource(sourceId)) map.removeSource(sourceId);\n } catch {\n // style may be mid-reload\n }\n };\n // Deps deliberately exclude url/corners/paint: all three are read from\n // `latestRef` or refs and synced in place by the effects below.\n }, [isLoaded, map, sourceId, layerId, canvas, beforeId]);\n\n // Move the picture when its placement changes.\n useEffect(() => {\n if (!isLoaded || !map || !cornersKey) return;\n const source = map.getSource(sourceId) as MapLibreGL.ImageSource | undefined;\n source?.setCoordinates?.(latestRef.current.corners as MapLibreGL.Coordinates);\n }, [isLoaded, map, sourceId, cornersKey]);\n\n // Swap the picture in place — no teardown, no flash, no refetch of the rest.\n const mountedUrlRef = useRef<string | null>(url);\n useEffect(() => {\n if (!isLoaded || !map || !url) return;\n if (mountedUrlRef.current === url) return;\n mountedUrlRef.current = url;\n const source = map.getSource(sourceId) as MapLibreGL.ImageSource | undefined;\n source?.updateImage?.({\n url,\n ...(latestRef.current.corners ? { coordinates: latestRef.current.corners } : {}),\n });\n }, [isLoaded, map, sourceId, url]);\n\n // Paint and visibility.\n useEffect(() => {\n if (!isLoaded || !map || !map.getLayer(layerId)) return;\n map.setPaintProperty(layerId, \"raster-opacity\", opacity);\n map.setPaintProperty(layerId, \"raster-resampling\", resampling);\n map.setLayoutProperty(layerId, \"visibility\", visible ? \"visible\" : \"none\");\n }, [isLoaded, map, layerId, opacity, resampling, visible]);\n\n // Load and failure are reported by the SOURCE, not the map: a map-level error\n // carries no source id. The plan keeps working without its picture, so the\n // consumer owns whatever the failure should look like.\n useEffect(() => {\n if (!isLoaded || !map) return;\n const source = map.getSource(sourceId) as\n | (MapLibreGL.ImageSource & {\n on?: (event: string, handler: (payload: unknown) => void) => void;\n off?: (event: string, handler: (payload: unknown) => void) => void;\n })\n | undefined;\n if (!source?.on) return;\n\n const handleData = (payload: unknown) => {\n if ((payload as { sourceDataType?: string } | undefined)?.sourceDataType !== \"metadata\") {\n return;\n }\n latestRef.current.onLoad?.();\n };\n const handleError = (payload: unknown) => {\n const error = (payload as { error?: Error } | undefined)?.error;\n latestRef.current.onError?.(error instanceof Error ? error : new Error(\"Plan image failed\"));\n };\n\n source.on(\"data\", handleData);\n source.on(\"error\", handleError);\n\n return () => {\n source.off?.(\"data\", handleData);\n source.off?.(\"error\", handleError);\n };\n }, [isLoaded, map, sourceId]);\n\n // The picture lives in WebGL, where assistive technology cannot reach it. This\n // span is its only description — and the module's stable selector seam.\n return alt ? (\n <span data-slot=\"map-plan-image\" className=\"sr-only\">\n {alt}\n </span>\n ) : (\n <span data-slot=\"map-plan-image\" hidden />\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/lib/arc-math.ts
|
|
4
|
+
function buildArcCoordinates(from, to, curvature, samples, wrapLongitude = true) {
|
|
5
|
+
const [x0, y0] = from;
|
|
6
|
+
const [xTo, y2] = to;
|
|
7
|
+
const rawDx = xTo - x0;
|
|
8
|
+
const x2 = !wrapLongitude ? xTo : rawDx > 180 ? xTo - 360 : rawDx < -180 ? xTo + 360 : xTo;
|
|
9
|
+
const dx = x2 - x0;
|
|
10
|
+
const dy = y2 - y0;
|
|
11
|
+
const distance = Math.hypot(dx, dy);
|
|
12
|
+
if (distance === 0 || curvature === 0) return [from, [x2, y2]];
|
|
13
|
+
const mx = (x0 + x2) / 2;
|
|
14
|
+
const my = (y0 + y2) / 2;
|
|
15
|
+
const nx = -dy / distance;
|
|
16
|
+
const ny = dx / distance;
|
|
17
|
+
const offset = distance * curvature;
|
|
18
|
+
const cx = mx + nx * offset;
|
|
19
|
+
const cy = my + ny * offset;
|
|
20
|
+
const points = [];
|
|
21
|
+
const segments = Math.max(2, Math.floor(samples));
|
|
22
|
+
for (let i = 0; i <= segments; i += 1) {
|
|
23
|
+
const t = i / segments;
|
|
24
|
+
const inv = 1 - t;
|
|
25
|
+
const x = inv * inv * x0 + 2 * inv * t * cx + t * t * x2;
|
|
26
|
+
const y = inv * inv * y0 + 2 * inv * t * cy + t * t * y2;
|
|
27
|
+
points.push([x, y]);
|
|
28
|
+
}
|
|
29
|
+
return points;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
buildArcCoordinates
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=chunk-BGCE3RR5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/arc-math.ts"],"sourcesContent":["/** Pure geometry for `<MapArc>` — kept engine-free so it can be unit-tested. */\n\n/**\n * Sample a quadratic Bézier between `from` and `to` in lng/lat space.\n * `curvature` is how far the arc bows away from a straight line (0 = straight;\n * negative bends to the opposite side). The destination longitude is unwrapped\n * relative to the origin so arcs cross the antimeridian via the shorter\n * great-circle direction — resulting longitudes may fall outside [-180, 180],\n * which MapLibre renders correctly on the globe projection.\n *\n * `wrapLongitude: false` turns that unwrap off, for coordinates that are not\n * longitudes at all: on a plan map, an x of 900 in a 1600-unit-wide hall is a\n * position, and shifting it by 360 would move the arc's end across the floor.\n */\nexport function buildArcCoordinates(\n from: [number, number],\n to: [number, number],\n curvature: number,\n samples: number,\n wrapLongitude = true,\n): [number, number][] {\n const [x0, y0] = from;\n const [xTo, y2] = to;\n const rawDx = xTo - x0;\n const x2 = !wrapLongitude ? xTo : rawDx > 180 ? xTo - 360 : rawDx < -180 ? xTo + 360 : xTo;\n const dx = x2 - x0;\n const dy = y2 - y0;\n const distance = Math.hypot(dx, dy);\n\n if (distance === 0 || curvature === 0) return [from, [x2, y2]];\n\n const mx = (x0 + x2) / 2;\n const my = (y0 + y2) / 2;\n const nx = -dy / distance;\n const ny = dx / distance;\n const offset = distance * curvature;\n const cx = mx + nx * offset;\n const cy = my + ny * offset;\n\n const points: [number, number][] = [];\n const segments = Math.max(2, Math.floor(samples));\n for (let i = 0; i <= segments; i += 1) {\n const t = i / segments;\n const inv = 1 - t;\n const x = inv * inv * x0 + 2 * inv * t * cx + t * t * x2;\n const y = inv * inv * y0 + 2 * inv * t * cy + t * t * y2;\n points.push([x, y]);\n }\n return points;\n}\n"],"mappings":";;;AAcO,SAAS,oBACd,MACA,IACA,WACA,SACA,gBAAgB,MACI;AACpB,QAAM,CAAC,IAAI,EAAE,IAAI;AACjB,QAAM,CAAC,KAAK,EAAE,IAAI;AAClB,QAAM,QAAQ,MAAM;AACpB,QAAM,KAAK,CAAC,gBAAgB,MAAM,QAAQ,MAAM,MAAM,MAAM,QAAQ,OAAO,MAAM,MAAM;AACvF,QAAM,KAAK,KAAK;AAChB,QAAM,KAAK,KAAK;AAChB,QAAM,WAAW,KAAK,MAAM,IAAI,EAAE;AAElC,MAAI,aAAa,KAAK,cAAc,EAAG,QAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;AAE7D,QAAM,MAAM,KAAK,MAAM;AACvB,QAAM,MAAM,KAAK,MAAM;AACvB,QAAM,KAAK,CAAC,KAAK;AACjB,QAAM,KAAK,KAAK;AAChB,QAAM,SAAS,WAAW;AAC1B,QAAM,KAAK,KAAK,KAAK;AACrB,QAAM,KAAK,KAAK,KAAK;AAErB,QAAM,SAA6B,CAAC;AACpC,QAAM,WAAW,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,CAAC;AAChD,WAAS,IAAI,GAAG,KAAK,UAAU,KAAK,GAAG;AACrC,UAAM,IAAI,IAAI;AACd,UAAM,MAAM,IAAI;AAChB,UAAM,IAAI,MAAM,MAAM,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,IAAI;AACtD,UAAM,IAAI,MAAM,MAAM,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,IAAI;AACtD,WAAO,KAAK,CAAC,GAAG,CAAC,CAAC;AAAA,EACpB;AACA,SAAO;AACT;","names":[]}
|