@expofp/wayfinding 3.11.11
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 +84 -0
- package/dist/core/createWayfindingEngine.d.ts +16 -0
- package/dist/core/createWayfindingEngine.js +87 -0
- package/dist/core/geometry/pointInPolygon.d.ts +17 -0
- package/dist/core/geometry/pointInPolygon.js +50 -0
- package/dist/core/geometry/projectPointOnSegment.d.ts +27 -0
- package/dist/core/geometry/projectPointOnSegment.js +21 -0
- package/dist/core/graph/buildGraph.d.ts +3 -0
- package/dist/core/graph/buildGraph.js +7 -0
- package/dist/core/graph/buildNGraph.d.ts +13 -0
- package/dist/core/graph/buildNGraph.js +24 -0
- package/dist/core/graph/constants.d.ts +20 -0
- package/dist/core/graph/constants.js +19 -0
- package/dist/core/graph/findShortestPath.d.ts +23 -0
- package/dist/core/graph/findShortestPath.js +65 -0
- package/dist/core/graph/graphCache.d.ts +7 -0
- package/dist/core/graph/graphCache.js +20 -0
- package/dist/core/graph/graphHelpers.d.ts +8 -0
- package/dist/core/graph/graphHelpers.js +52 -0
- package/dist/core/graph/linkCost.d.ts +13 -0
- package/dist/core/graph/linkCost.js +24 -0
- package/dist/core/graph/pathfinder/aStarPathFinder.d.ts +3 -0
- package/dist/core/graph/pathfinder/aStarPathFinder.js +64 -0
- package/dist/core/graph/pathfinder/parseNodeId.d.ts +10 -0
- package/dist/core/graph/pathfinder/parseNodeId.js +20 -0
- package/dist/core/index.d.ts +17 -0
- package/dist/core/index.js +12 -0
- package/dist/core/position/distanceToRoute.d.ts +3 -0
- package/dist/core/position/distanceToRoute.js +29 -0
- package/dist/core/position/gpsThreshold.d.ts +32 -0
- package/dist/core/position/gpsThreshold.js +48 -0
- package/dist/core/position/rerouteController.d.ts +13 -0
- package/dist/core/position/rerouteController.js +22 -0
- package/dist/core/position/snapToRoute.d.ts +9 -0
- package/dist/core/position/snapToRoute.js +50 -0
- package/dist/core/position/splitRouteByPoint.d.ts +6 -0
- package/dist/core/position/splitRouteByPoint.js +56 -0
- package/dist/core/rendering/computeTrailPoints.d.ts +12 -0
- package/dist/core/rendering/computeTrailPoints.js +35 -0
- package/dist/core/rendering/computeTransitionPoints.d.ts +32 -0
- package/dist/core/rendering/computeTransitionPoints.js +114 -0
- package/dist/core/rendering/getVisibleRouteLines.d.ts +10 -0
- package/dist/core/rendering/getVisibleRouteLines.js +18 -0
- package/dist/core/rendering/normalizeRouteDirection.d.ts +19 -0
- package/dist/core/rendering/normalizeRouteDirection.js +82 -0
- package/dist/core/rendering/routeGeometry.d.ts +9 -0
- package/dist/core/rendering/routeGeometry.js +10 -0
- package/dist/core/routing/buildMultiPointRoute.d.ts +13 -0
- package/dist/core/routing/buildMultiPointRoute.js +36 -0
- package/dist/core/routing/buildRoute.d.ts +12 -0
- package/dist/core/routing/buildRoute.js +19 -0
- package/dist/core/routing/getRouteLength.d.ts +3 -0
- package/dist/core/routing/getRouteLength.js +4 -0
- package/dist/core/routing/graphPointResolvers.d.ts +9 -0
- package/dist/core/routing/graphPointResolvers.js +13 -0
- package/dist/core/routing/optimizeWaypointOrder.d.ts +14 -0
- package/dist/core/routing/optimizeWaypointOrder.js +88 -0
- package/dist/core/routing/resolveWaypointCandidates.d.ts +23 -0
- package/dist/core/routing/resolveWaypointCandidates.js +49 -0
- package/dist/core/routing/routeResult.d.ts +4 -0
- package/dist/core/routing/routeResult.js +2 -0
- package/dist/core/types.d.ts +102 -0
- package/dist/core/types.js +1 -0
- package/dist/createWayfinding.d.ts +68 -0
- package/dist/createWayfinding.js +44 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +13 -0
- package/dist/renderer/createWayfindingRenderer.d.ts +14 -0
- package/dist/renderer/createWayfindingRenderer.js +51 -0
- package/dist/renderer/iconManager.d.ts +39 -0
- package/dist/renderer/iconManager.js +166 -0
- package/dist/renderer/index.d.ts +3 -0
- package/dist/renderer/index.js +1 -0
- package/dist/renderer/layerManager.d.ts +27 -0
- package/dist/renderer/layerManager.js +40 -0
- package/dist/renderer/lineAnimation.d.ts +11 -0
- package/dist/renderer/lineAnimation.js +127 -0
- package/dist/renderer/routeLineManager.d.ts +44 -0
- package/dist/renderer/routeLineManager.js +62 -0
- package/dist/renderer/trailManager.d.ts +32 -0
- package/dist/renderer/trailManager.js +79 -0
- package/dist/renderer/types.d.ts +129 -0
- package/dist/renderer/types.js +1 -0
- package/dist/runtime/createWayfindingRuntime.d.ts +3 -0
- package/dist/runtime/createWayfindingRuntime.js +248 -0
- package/dist/runtime/endpointView.d.ts +20 -0
- package/dist/runtime/endpointView.js +40 -0
- package/dist/runtime/getRouteLines.d.ts +19 -0
- package/dist/runtime/getRouteLines.js +16 -0
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/positionTrailView.d.ts +52 -0
- package/dist/runtime/positionTrailView.js +35 -0
- package/dist/runtime/positionView.d.ts +23 -0
- package/dist/runtime/positionView.js +25 -0
- package/dist/runtime/routeLinesView.d.ts +18 -0
- package/dist/runtime/routeLinesView.js +18 -0
- package/dist/runtime/routeRenderData.d.ts +31 -0
- package/dist/runtime/routeRenderData.js +46 -0
- package/dist/runtime/routeUpdate.d.ts +17 -0
- package/dist/runtime/routeUpdate.js +17 -0
- package/dist/runtime/snapPositionToRoute.d.ts +10 -0
- package/dist/runtime/snapPositionToRoute.js +36 -0
- package/dist/runtime/trailView.d.ts +20 -0
- package/dist/runtime/trailView.js +43 -0
- package/dist/runtime/transitionView.d.ts +17 -0
- package/dist/runtime/transitionView.js +50 -0
- package/dist/runtime/types.d.ts +86 -0
- package/dist/runtime/types.js +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type WayfindingRenderer } from '../renderer/index.js';
|
|
2
|
+
import { type IconProvider, type PositionInput } from './types.js';
|
|
3
|
+
export declare const CURRENT_POSITION_POINT_ID = "current-position";
|
|
4
|
+
interface PositionView {
|
|
5
|
+
/**
|
|
6
|
+
* Place or update the current-position icon. The icon variant (dot vs
|
|
7
|
+
* arrow) is derived from `input.heading`: finite heading → arrow, otherwise
|
|
8
|
+
* → dot.
|
|
9
|
+
*
|
|
10
|
+
* `hidden` collapses the monolith's dual (hidden, dimmed) fields into a
|
|
11
|
+
* single boolean — they were always driven by the same
|
|
12
|
+
* `!isLayerVisible(position.layer)` source.
|
|
13
|
+
*/
|
|
14
|
+
place(input: PositionInput, hidden: boolean): void;
|
|
15
|
+
hide(): void;
|
|
16
|
+
}
|
|
17
|
+
export declare function createPositionView({ renderer, iconProvider, layer, }: {
|
|
18
|
+
renderer: Pick<WayfindingRenderer, 'setIcon'>;
|
|
19
|
+
iconProvider: Pick<IconProvider, 'getIcon'>;
|
|
20
|
+
layer: string;
|
|
21
|
+
}): PositionView;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=positionView.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { degToRad } from '@expofp/geometry';
|
|
2
|
+
export const CURRENT_POSITION_POINT_ID = 'current-position';
|
|
3
|
+
const POSITION_ICON_KEY = 'position';
|
|
4
|
+
export function createPositionView({ renderer, iconProvider, layer, }) {
|
|
5
|
+
return {
|
|
6
|
+
place(input, hidden) {
|
|
7
|
+
const hasHeading = Number.isFinite(input.heading);
|
|
8
|
+
const iconName = hasHeading ? 'current_arrow' : 'current';
|
|
9
|
+
const rotation = hasHeading ? degToRad(input.heading) : undefined;
|
|
10
|
+
renderer.setIcon(POSITION_ICON_KEY, {
|
|
11
|
+
canvas: iconProvider.getIcon(iconName).canvas,
|
|
12
|
+
layer,
|
|
13
|
+
x: input.x,
|
|
14
|
+
y: input.y,
|
|
15
|
+
rotation,
|
|
16
|
+
hidden,
|
|
17
|
+
dimmed: hidden,
|
|
18
|
+
origin: [0.5, 0.5],
|
|
19
|
+
});
|
|
20
|
+
},
|
|
21
|
+
hide() {
|
|
22
|
+
renderer.setIcon(POSITION_ICON_KEY, null);
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type RouteLine } from '../core/index.js';
|
|
2
|
+
import { type WayfindingRenderer } from '../renderer/index.js';
|
|
3
|
+
interface RouteLinesView {
|
|
4
|
+
/** Push pre-split passed/remaining route lines to the renderer. */
|
|
5
|
+
draw(data: {
|
|
6
|
+
readonly passed: RouteLine[];
|
|
7
|
+
readonly remaining: RouteLine[];
|
|
8
|
+
}, preserveProgress?: boolean): void;
|
|
9
|
+
/** Clear all rendered route lines + animated segments. */
|
|
10
|
+
clear(): void;
|
|
11
|
+
}
|
|
12
|
+
export declare function createRouteLinesView({ renderer, linesLayer, animatedLinesLayer, }: {
|
|
13
|
+
renderer: Pick<WayfindingRenderer, 'setRouteLines'>;
|
|
14
|
+
linesLayer: string;
|
|
15
|
+
animatedLinesLayer: string;
|
|
16
|
+
}): RouteLinesView;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=routeLinesView.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function createRouteLinesView({ renderer, linesLayer, animatedLinesLayer, }) {
|
|
2
|
+
return {
|
|
3
|
+
draw({ passed, remaining }, preserveProgress = false) {
|
|
4
|
+
renderer.setRouteLines(passed, remaining, {
|
|
5
|
+
linesLayer,
|
|
6
|
+
animatedLinesLayer,
|
|
7
|
+
resetAnimation: !preserveProgress,
|
|
8
|
+
});
|
|
9
|
+
},
|
|
10
|
+
clear() {
|
|
11
|
+
renderer.setRouteLines([], [], {
|
|
12
|
+
linesLayer,
|
|
13
|
+
animatedLinesLayer,
|
|
14
|
+
resetAnimation: true,
|
|
15
|
+
});
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type RouteLine, type RouteSnapResult } from '../core/index.js';
|
|
2
|
+
interface RouteRenderData {
|
|
3
|
+
/** Route lines behind the current user position (empty when no snap). */
|
|
4
|
+
readonly passed: RouteLine[];
|
|
5
|
+
/** Route lines ahead of the current user position (the full normalized route when no snap). */
|
|
6
|
+
readonly remaining: RouteLine[];
|
|
7
|
+
/** Remaining route distance along the polyline. */
|
|
8
|
+
readonly distance: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Pure pipeline that produces the data needed to render the route: the
|
|
12
|
+
* passed/remaining split for the visible floor plus the remaining
|
|
13
|
+
* distance to the destination.
|
|
14
|
+
*
|
|
15
|
+
* The full non-virtual route is sliced down to the visible floor for rendering.
|
|
16
|
+
* The remaining distance is read from `snap.distance` (computed once by the
|
|
17
|
+
* runtime snap, `snapPositionToRoute`); with no snap it falls back to the full
|
|
18
|
+
* route length.
|
|
19
|
+
* @param root0
|
|
20
|
+
* @param root0.routeLines
|
|
21
|
+
* @param root0.currentRouteLayer
|
|
22
|
+
* @param root0.snap
|
|
23
|
+
*/
|
|
24
|
+
export declare function computeRouteRenderData({ routeLines, currentRouteLayer, snap, }: {
|
|
25
|
+
readonly routeLines: RouteLine[];
|
|
26
|
+
/** null = show-all mode; otherwise the active route floor. */
|
|
27
|
+
readonly currentRouteLayer: string | null;
|
|
28
|
+
readonly snap: RouteSnapResult | null;
|
|
29
|
+
}): RouteRenderData;
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=routeRenderData.d.ts.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { getRouteLength, splitRouteByPoint, } from '../core/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Pure pipeline that produces the data needed to render the route: the
|
|
4
|
+
* passed/remaining split for the visible floor plus the remaining
|
|
5
|
+
* distance to the destination.
|
|
6
|
+
*
|
|
7
|
+
* The full non-virtual route is sliced down to the visible floor for rendering.
|
|
8
|
+
* The remaining distance is read from `snap.distance` (computed once by the
|
|
9
|
+
* runtime snap, `snapPositionToRoute`); with no snap it falls back to the full
|
|
10
|
+
* route length.
|
|
11
|
+
* @param root0
|
|
12
|
+
* @param root0.routeLines
|
|
13
|
+
* @param root0.currentRouteLayer
|
|
14
|
+
* @param root0.snap
|
|
15
|
+
*/
|
|
16
|
+
export function computeRouteRenderData({ routeLines, currentRouteLayer, snap, }) {
|
|
17
|
+
const nonVirtualOrdered = routeLines.filter((line) => !line.virtual);
|
|
18
|
+
const clipToVisibleFloor = (lines) => currentRouteLayer === null
|
|
19
|
+
? lines
|
|
20
|
+
: lines.filter((line) => line.p0.layer === currentRouteLayer);
|
|
21
|
+
if (!snap?.snapped) {
|
|
22
|
+
return {
|
|
23
|
+
passed: [],
|
|
24
|
+
remaining: clipToVisibleFloor(nonVirtualOrdered),
|
|
25
|
+
distance: getRouteLength(nonVirtualOrdered),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
// Split the full route at the snap point first, then filter each side to the visible
|
|
29
|
+
// floor, so an already-passed floor renders fully as passed even when the snap is on a
|
|
30
|
+
// later floor.
|
|
31
|
+
const { passed, remaining } = splitRouteByPoint(nonVirtualOrdered, snap.snappedPoint);
|
|
32
|
+
// Ref-equal result means no segment matched the snap layer (bestIndex === -1); only on a
|
|
33
|
+
// real split do we anchor the first remaining segment at the snap point.
|
|
34
|
+
const splitOccurred = remaining !== nonVirtualOrdered;
|
|
35
|
+
// Anchor only when `remaining[0]` is on the snap's floor — at a transition it
|
|
36
|
+
// already belongs to the next floor, and keeping its own `p0` survives the clip.
|
|
37
|
+
const anchorAtSnap = splitOccurred && remaining.length > 0 && remaining[0].p0.layer === snap.snappedPoint.layer;
|
|
38
|
+
const adjustedRemaining = anchorAtSnap
|
|
39
|
+
? [{ ...remaining[0], p0: snap.snappedPoint }, ...remaining.slice(1)]
|
|
40
|
+
: remaining;
|
|
41
|
+
return {
|
|
42
|
+
passed: clipToVisibleFloor(passed),
|
|
43
|
+
remaining: clipToVisibleFloor(adjustedRemaining),
|
|
44
|
+
distance: snap.distance,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Box } from '@expofp/geometry';
|
|
2
|
+
import { type RouteLine } from '../core/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Compute the payload for the runtime's `onRouteUpdate` callback:
|
|
5
|
+
* - `lines`: route lines as the consumer sees them — virtual graph-internal
|
|
6
|
+
* segments are filtered out before publication.
|
|
7
|
+
* - `bounds`: bounding box over the visible route points. It is an EMPTY box
|
|
8
|
+
* (`bounds.isEmpty()`) when the route is empty or collapses to a single point
|
|
9
|
+
* (no useful viewport to fit); consumers check `isEmpty()` rather than null.
|
|
10
|
+
* @param routeLines
|
|
11
|
+
* @param visibleLines
|
|
12
|
+
*/
|
|
13
|
+
export declare function computeRouteUpdate(routeLines: readonly RouteLine[], visibleLines: readonly RouteLine[]): {
|
|
14
|
+
lines: RouteLine[];
|
|
15
|
+
bounds: Box;
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=routeUpdate.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { computeBoundingBox } from '../core/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Compute the payload for the runtime's `onRouteUpdate` callback:
|
|
4
|
+
* - `lines`: route lines as the consumer sees them — virtual graph-internal
|
|
5
|
+
* segments are filtered out before publication.
|
|
6
|
+
* - `bounds`: bounding box over the visible route points. It is an EMPTY box
|
|
7
|
+
* (`bounds.isEmpty()`) when the route is empty or collapses to a single point
|
|
8
|
+
* (no useful viewport to fit); consumers check `isEmpty()` rather than null.
|
|
9
|
+
* @param routeLines
|
|
10
|
+
* @param visibleLines
|
|
11
|
+
*/
|
|
12
|
+
export function computeRouteUpdate(routeLines, visibleLines) {
|
|
13
|
+
const lines = routeLines.filter((line) => !line.virtual);
|
|
14
|
+
const points = visibleLines.flatMap((line) => [line.p0, line.p1]);
|
|
15
|
+
const bbox = computeBoundingBox(points);
|
|
16
|
+
return { lines, bounds: bbox };
|
|
17
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type RouteEndpoint, type RouteLine, type RoutePoint, type RouteSnapResult } from '../core/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Snap a position to the from→to route, resolving from/to booth containment
|
|
4
|
+
* first (domain logic kept out of the geometric `snapToRoute`):
|
|
5
|
+
* - inside the origin booth → route start, full distance remaining;
|
|
6
|
+
* - inside the destination booth → route end, zero remaining;
|
|
7
|
+
* - otherwise → geometric snap within `snapThreshold`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function snapPositionToRoute(position: RoutePoint, routeLines: readonly RouteLine[], from: RouteEndpoint, to: RouteEndpoint, snapThreshold: number): RouteSnapResult;
|
|
10
|
+
//# sourceMappingURL=snapPositionToRoute.d.ts.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { pointInPolygon } from '../core/geometry/pointInPolygon.js';
|
|
2
|
+
import { getRouteLength, snapToRoute, } from '../core/index.js';
|
|
3
|
+
/** Whether `position` lies inside `endpoint`'s booth bounds on a matching layer. */
|
|
4
|
+
function insideBounds(position, endpoint, posLayer) {
|
|
5
|
+
if (!endpoint?.bounds)
|
|
6
|
+
return false;
|
|
7
|
+
const layerMatches = posLayer == null || !endpoint.layer || endpoint.layer === posLayer;
|
|
8
|
+
const containsPosition = pointInPolygon({ x: position.x, y: position.y }, endpoint.bounds);
|
|
9
|
+
return layerMatches && containsPosition;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Snap a position to the from→to route, resolving from/to booth containment
|
|
13
|
+
* first (domain logic kept out of the geometric `snapToRoute`):
|
|
14
|
+
* - inside the origin booth → route start, full distance remaining;
|
|
15
|
+
* - inside the destination booth → route end, zero remaining;
|
|
16
|
+
* - otherwise → geometric snap within `snapThreshold`.
|
|
17
|
+
*/
|
|
18
|
+
export function snapPositionToRoute(position, routeLines, from, to, snapThreshold) {
|
|
19
|
+
if (!routeLines.length)
|
|
20
|
+
return { snapped: false };
|
|
21
|
+
const posLayer = position.layer || null;
|
|
22
|
+
if (insideBounds(position, from, posLayer)) {
|
|
23
|
+
const first = routeLines[0];
|
|
24
|
+
return {
|
|
25
|
+
snapped: true,
|
|
26
|
+
snappedPoint: first.p0,
|
|
27
|
+
snappedLine: first,
|
|
28
|
+
distance: getRouteLength(routeLines),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (insideBounds(position, to, posLayer)) {
|
|
32
|
+
const last = routeLines[routeLines.length - 1];
|
|
33
|
+
return { snapped: true, snappedPoint: last.p1, snappedLine: last, distance: 0 };
|
|
34
|
+
}
|
|
35
|
+
return snapToRoute(position, routeLines, { snapThreshold });
|
|
36
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type RoutePoint } from '../core/index.js';
|
|
2
|
+
import { type WayfindingRenderer } from '../renderer/index.js';
|
|
3
|
+
import { type FloorContext, type IconProvider } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Callers choose the slot name and provide two route-points; the view draws a dotted trail between them (or hides
|
|
6
|
+
* them if the layers are not on the active floor)
|
|
7
|
+
*/
|
|
8
|
+
export interface TrailView {
|
|
9
|
+
/** Draws dotted points between two route-points in the named slot. */
|
|
10
|
+
place(slot: string, from: RoutePoint, to: RoutePoint): void;
|
|
11
|
+
clear(slot: string): void;
|
|
12
|
+
clearAll(): void;
|
|
13
|
+
}
|
|
14
|
+
export declare function createTrailView({ renderer, iconProvider, floorContext, layer, }: {
|
|
15
|
+
renderer: Pick<WayfindingRenderer, 'setTrail'>;
|
|
16
|
+
iconProvider: Pick<IconProvider, 'getIcon'>;
|
|
17
|
+
floorContext: Pick<FloorContext, 'showAllFloors' | 'getActiveFloor'>;
|
|
18
|
+
layer: string;
|
|
19
|
+
}): TrailView;
|
|
20
|
+
//# sourceMappingURL=trailView.d.ts.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export function createTrailView({ renderer, iconProvider, floorContext, layer, }) {
|
|
2
|
+
const activeSlots = new Set();
|
|
3
|
+
/**
|
|
4
|
+
* Trails share one global layer (not per-floor). In scoped-floor mode the
|
|
5
|
+
* trail renders only when both endpoints lie on the active floor —
|
|
6
|
+
* otherwise the line would bleed through on unrelated floors.
|
|
7
|
+
* @param fromLayer
|
|
8
|
+
* @param toLayer
|
|
9
|
+
*/
|
|
10
|
+
function isTrailVisible(fromLayer, toLayer) {
|
|
11
|
+
if (floorContext.showAllFloors())
|
|
12
|
+
return true;
|
|
13
|
+
const activeFloor = floorContext.getActiveFloor();
|
|
14
|
+
if (!activeFloor)
|
|
15
|
+
return false;
|
|
16
|
+
return fromLayer === activeFloor && toLayer === activeFloor;
|
|
17
|
+
}
|
|
18
|
+
function clearSlot(slot) {
|
|
19
|
+
renderer.setTrail(slot, null, null);
|
|
20
|
+
activeSlots.delete(slot);
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
place(slot, from, to) {
|
|
24
|
+
if (!isTrailVisible(from.layer, to.layer)) {
|
|
25
|
+
clearSlot(slot);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
renderer.setTrail(slot, from, to, {
|
|
29
|
+
canvas: iconProvider.getIcon('trail').canvas,
|
|
30
|
+
layer,
|
|
31
|
+
});
|
|
32
|
+
activeSlots.add(slot);
|
|
33
|
+
},
|
|
34
|
+
clear(slot) {
|
|
35
|
+
clearSlot(slot);
|
|
36
|
+
},
|
|
37
|
+
clearAll() {
|
|
38
|
+
for (const slot of activeSlots)
|
|
39
|
+
renderer.setTrail(slot, null, null);
|
|
40
|
+
activeSlots.clear();
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type RouteLine, type TransitionPointDef } from '../core/index.js';
|
|
2
|
+
import type { WayfindingRenderer } from '../renderer/index.js';
|
|
3
|
+
import type { FloorContext, IconProvider } from './types.js';
|
|
4
|
+
interface TransitionView {
|
|
5
|
+
/** Recomputes transition points from route lines + floor context and places their icons. */
|
|
6
|
+
place(routeLines: RouteLine[]): void;
|
|
7
|
+
clear(): void;
|
|
8
|
+
}
|
|
9
|
+
export declare function createTransitionView({ renderer, iconProvider, floorContext, onTransitionClick, layer, }: {
|
|
10
|
+
renderer: Pick<WayfindingRenderer, 'setIcon' | 'clearIcons'>;
|
|
11
|
+
iconProvider: Pick<IconProvider, 'getIcon'>;
|
|
12
|
+
floorContext: Pick<FloorContext, 'showAllFloors' | 'getActiveFloor' | 'getFloorOrder'>;
|
|
13
|
+
onTransitionClick: (point: TransitionPointDef) => void;
|
|
14
|
+
layer: string;
|
|
15
|
+
}): TransitionView;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=transitionView.d.ts.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { computeTransitionPoints } from '../core/index.js';
|
|
2
|
+
const TRANSITION_ICON_NAME = 'transition';
|
|
3
|
+
export function createTransitionView({ renderer, iconProvider, floorContext, onTransitionClick, layer, }) {
|
|
4
|
+
return {
|
|
5
|
+
place(routeLines) {
|
|
6
|
+
renderer.clearIcons(TRANSITION_ICON_NAME);
|
|
7
|
+
// `null` here means "don't scope to a floor" (computeTransitionPoints
|
|
8
|
+
// returns transitions for every floor). Only emit `null` in show-all
|
|
9
|
+
// mode — in scoped mode with an unresolvable active floor we must
|
|
10
|
+
// hide transitions instead of accidentally placing them on all floors.
|
|
11
|
+
let currentLayerName;
|
|
12
|
+
if (floorContext.showAllFloors()) {
|
|
13
|
+
currentLayerName = null;
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
const activeFloor = floorContext.getActiveFloor();
|
|
17
|
+
if (!activeFloor)
|
|
18
|
+
return;
|
|
19
|
+
currentLayerName = activeFloor;
|
|
20
|
+
}
|
|
21
|
+
const transitionPoints = computeTransitionPoints({
|
|
22
|
+
routeLines,
|
|
23
|
+
currentLayerName,
|
|
24
|
+
floorOrder: floorContext.getFloorOrder(),
|
|
25
|
+
});
|
|
26
|
+
for (const [index, point] of transitionPoints.entries()) {
|
|
27
|
+
renderer.setIcon(TRANSITION_ICON_NAME, {
|
|
28
|
+
canvas: iconProvider.getIcon(iconNameFor(point.role, point.direction)).canvas,
|
|
29
|
+
layer,
|
|
30
|
+
x: point.x,
|
|
31
|
+
y: point.y,
|
|
32
|
+
cardinalSnap: true,
|
|
33
|
+
onClick: point.role === 'exit' ? () => onTransitionClick(point) : undefined,
|
|
34
|
+
}, index);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
clear() {
|
|
38
|
+
renderer.clearIcons(TRANSITION_ICON_NAME);
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function iconNameFor(role, direction) {
|
|
43
|
+
if (role === 'entry')
|
|
44
|
+
return 'transition_entry';
|
|
45
|
+
if (direction === 'up')
|
|
46
|
+
return 'transition_up';
|
|
47
|
+
if (direction === 'down')
|
|
48
|
+
return 'transition_down';
|
|
49
|
+
return 'transition';
|
|
50
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Box } from '@expofp/geometry';
|
|
2
|
+
import type { RouteEndpoint, RouteLine, RoutePoint, TransitionPointDef, WayfindingEngine } from '../core/index.js';
|
|
3
|
+
import type { WayfindingRenderer } from '../renderer/index.js';
|
|
4
|
+
export type IconName = 'source' | 'source_inactive' | 'destination' | 'current' | 'current_arrow' | 'transition' | 'transition_up' | 'transition_down' | 'transition_entry' | 'trail';
|
|
5
|
+
export interface IconAsset {
|
|
6
|
+
readonly canvas: HTMLCanvasElement;
|
|
7
|
+
readonly width: number;
|
|
8
|
+
readonly height: number;
|
|
9
|
+
}
|
|
10
|
+
export interface IconProvider {
|
|
11
|
+
getIcon(name: IconName): IconAsset;
|
|
12
|
+
}
|
|
13
|
+
/** User position + optional heading, without efp store coupling. */
|
|
14
|
+
export interface PositionInput {
|
|
15
|
+
readonly x: number;
|
|
16
|
+
readonly y: number;
|
|
17
|
+
readonly layer: string;
|
|
18
|
+
/** Heading in degrees. Absent → no rotation (dot variant). */
|
|
19
|
+
readonly heading?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface LayerNames {
|
|
22
|
+
readonly points: string;
|
|
23
|
+
readonly trail: string;
|
|
24
|
+
readonly lines: string;
|
|
25
|
+
readonly linesAnimated: string;
|
|
26
|
+
readonly currentPosition: string;
|
|
27
|
+
}
|
|
28
|
+
export interface SetRouteInput {
|
|
29
|
+
readonly from: RouteEndpoint;
|
|
30
|
+
readonly to: RouteEndpoint;
|
|
31
|
+
readonly waypoints?: readonly RouteEndpoint[];
|
|
32
|
+
readonly accessible: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface WayfindingRuntime {
|
|
35
|
+
/**
|
|
36
|
+
* Build (or update) the active route. Places endpoint/transition/trail icons
|
|
37
|
+
* and fires `onRouteUpdate` + `onRouteDistance` callbacks.
|
|
38
|
+
*/
|
|
39
|
+
setRoute(input: SetRouteInput): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Notify the runtime that the user position changed. `null` hides the position icon and clears
|
|
42
|
+
* the off-route trail. Dot vs arrow variant is derived from `position.heading` internally.
|
|
43
|
+
*/
|
|
44
|
+
onPositionChanged(position: PositionInput | null): void;
|
|
45
|
+
/** Notify the runtime that the active floor changed; re-renders visible route content. */
|
|
46
|
+
onFloorChanged(): void;
|
|
47
|
+
/** Whether the position drifted off the active (non-virtual) route far enough to reroute. */
|
|
48
|
+
shouldReroute(position: RoutePoint): boolean;
|
|
49
|
+
clearRoute(): void;
|
|
50
|
+
destroy(): void;
|
|
51
|
+
}
|
|
52
|
+
export interface WayfindingRuntimeConfig {
|
|
53
|
+
readonly engine: WayfindingEngine;
|
|
54
|
+
readonly renderer: WayfindingRenderer;
|
|
55
|
+
readonly iconProvider: IconProvider;
|
|
56
|
+
readonly floorContext: FloorContext;
|
|
57
|
+
readonly layers: LayerNames;
|
|
58
|
+
readonly snapThreshold: number;
|
|
59
|
+
/** Plan-scale-aware off-route reroute distance (SVG units). */
|
|
60
|
+
readonly rerouteThreshold: number;
|
|
61
|
+
readonly onTransitionClick: (point: TransitionPointDef) => void;
|
|
62
|
+
/** Fired when the route itself changes. `bounds` is the bbox of visible route points; call `bounds.isEmpty()` to detect an empty/cleared route. */
|
|
63
|
+
readonly onRouteUpdate: (lines: readonly RouteLine[], bounds: Box) => void;
|
|
64
|
+
/** Fired when distance changes — on route change and on position snap updates. */
|
|
65
|
+
readonly onRouteDistance: (distance: number) => void;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Abstracts floor-state queries for the runtime.
|
|
69
|
+
*
|
|
70
|
+
* Views ask these predicates instead of reading any store directly;
|
|
71
|
+
* an adapter (see `adapters/floorContext.ts`) supplies the implementation
|
|
72
|
+
* that bridges to a concrete store (e.g., efp `LayerStore` + `RouteStore`).
|
|
73
|
+
*
|
|
74
|
+
* - {@link showAllFloors} — true when the map is rendering all floors simultaneously (no floor scoping/single-floor).
|
|
75
|
+
* - {@link getActiveFloor} — name of the active route floor, or `""` when unresolvable;
|
|
76
|
+
* callers must check `!showAllFloors()` first.
|
|
77
|
+
* - {@link isLayerVisible} — per-layer visibility for endpoint dim/hide and cross-floor trail gating.
|
|
78
|
+
* - {@link getFloorOrder} — stable floor-name order used by transition placement.
|
|
79
|
+
*/
|
|
80
|
+
export interface FloorContext {
|
|
81
|
+
showAllFloors(): boolean;
|
|
82
|
+
getActiveFloor(): string;
|
|
83
|
+
isLayerVisible(layer: string): boolean;
|
|
84
|
+
getFloorOrder(): string[];
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@expofp/wayfinding",
|
|
3
|
+
"version": "3.11.11",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "ExpoFP SDK internal: framework-neutral wayfinding (routing, snapping, scene rendering)",
|
|
6
|
+
"homepage": "https://developer.expofp.com/",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/expofp/efp-app.git",
|
|
11
|
+
"directory": "packages/wayfinding"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"module": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
"./package.json": "./package.json",
|
|
22
|
+
".": {
|
|
23
|
+
"@expofp/source": "./src/index.ts",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"import": "./dist/index.js",
|
|
26
|
+
"default": "./dist/index.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"!**/*.tsbuildinfo",
|
|
32
|
+
"!**/*.map"
|
|
33
|
+
],
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"ngraph.graph": "^19.1.0",
|
|
36
|
+
"tslib": "^2.3.0",
|
|
37
|
+
"@expofp/geometry": "3.11.11"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"@expofp/renderer": "^3.2.4"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@expofp/renderer": "^3.2.4"
|
|
44
|
+
}
|
|
45
|
+
}
|