@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,82 @@
|
|
|
1
|
+
import { pointDistance } from '@expofp/geometry';
|
|
2
|
+
/**
|
|
3
|
+
* Projects `point` onto the closest segment of `segments` that shares the same layer,
|
|
4
|
+
* returning the arc-length distance from the polyline start to the projection.
|
|
5
|
+
* @param point
|
|
6
|
+
* @param segments
|
|
7
|
+
* @param segmentLengths
|
|
8
|
+
*/
|
|
9
|
+
function projectPointOnRoute(point, segments, segmentLengths) {
|
|
10
|
+
let accumulatedLength = 0;
|
|
11
|
+
let bestDistance = Infinity;
|
|
12
|
+
let bestFromStart = Infinity;
|
|
13
|
+
const total = segmentLengths.reduce((sum, len) => sum + len, 0);
|
|
14
|
+
for (let i = 0; i < segments.length; i++) {
|
|
15
|
+
const segment = segments[i];
|
|
16
|
+
const segmentLength = segmentLengths[i];
|
|
17
|
+
// Empty `point.layer` = wildcard (single-floor plans without layers).
|
|
18
|
+
if (point.layer && segment.p0.layer !== point.layer) {
|
|
19
|
+
accumulatedLength += segmentLength;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const dx = segment.p1.x - segment.p0.x;
|
|
23
|
+
const dy = segment.p1.y - segment.p0.y;
|
|
24
|
+
const lengthSquared = dx * dx + dy * dy;
|
|
25
|
+
// Near-zero-length segment — nothing meaningful to project onto.
|
|
26
|
+
if (lengthSquared < 1e-10) {
|
|
27
|
+
accumulatedLength += segmentLength;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
// Project point onto segment, clamped to [0, 1].
|
|
31
|
+
const t = ((point.x - segment.p0.x) * dx + (point.y - segment.p0.y) * dy) / lengthSquared;
|
|
32
|
+
const clamped = Math.max(0, Math.min(1, t));
|
|
33
|
+
const projectedX = segment.p0.x + dx * clamped;
|
|
34
|
+
const projectedY = segment.p0.y + dy * clamped;
|
|
35
|
+
const distance = Math.hypot(point.x - projectedX, point.y - projectedY);
|
|
36
|
+
if (distance < bestDistance) {
|
|
37
|
+
bestDistance = distance;
|
|
38
|
+
bestFromStart = accumulatedLength + segmentLength * clamped;
|
|
39
|
+
}
|
|
40
|
+
accumulatedLength += segmentLength;
|
|
41
|
+
}
|
|
42
|
+
return { found: bestFromStart !== Infinity, fromStart: bestFromStart, total };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Ensures that `routeLines` run in the from→to direction.
|
|
46
|
+
*
|
|
47
|
+
* A* returns paths in target→source order. For rendering (arrow animation,
|
|
48
|
+
* route progress split) and remaining-distance computation the lines must
|
|
49
|
+
* go from the origin to the destination.
|
|
50
|
+
*
|
|
51
|
+
* Direction is decided by projecting `from` (or `to` as a fallback) onto
|
|
52
|
+
* `routeLines`: if the anchor lands closer to the polyline end than to
|
|
53
|
+
* its start, the array is reversed and each segment's endpoints are
|
|
54
|
+
* flipped to keep continuity.
|
|
55
|
+
* @param routeLines Lines to normalize.
|
|
56
|
+
* @param from Route origin (user-selected "from" point).
|
|
57
|
+
* @param to Route destination (user-selected "to" point).
|
|
58
|
+
* @returns Lines guaranteed to run from→to, or the original array unchanged.
|
|
59
|
+
*/
|
|
60
|
+
export function normalizeRouteDirection(routeLines, from, to) {
|
|
61
|
+
// Precompute segment lengths once (used for both projections).
|
|
62
|
+
const segmentLengths = routeLines.map((line) => pointDistance(line.p0, line.p1));
|
|
63
|
+
// Try to anchor using `from`. Fall back to `to` if `from` doesn't land on any same-layer segment.
|
|
64
|
+
const fromProjection = projectPointOnRoute(from, routeLines, segmentLengths);
|
|
65
|
+
const anchor = fromProjection.found
|
|
66
|
+
? fromProjection
|
|
67
|
+
: projectPointOnRoute(to, routeLines, segmentLengths);
|
|
68
|
+
if (!anchor.found)
|
|
69
|
+
return routeLines;
|
|
70
|
+
const distToStart = anchor.fromStart;
|
|
71
|
+
const distToEnd = anchor.total - anchor.fromStart;
|
|
72
|
+
// If `from` was used as anchor: `from` should be closer to the start.
|
|
73
|
+
// If `to` was used as fallback: `to` should be closer to the end.
|
|
74
|
+
const shouldReverse = fromProjection.found ? distToEnd < distToStart : distToStart < distToEnd;
|
|
75
|
+
if (!shouldReverse)
|
|
76
|
+
return routeLines;
|
|
77
|
+
// Reverse segment order AND flip each segment's endpoints to maintain continuity.
|
|
78
|
+
return routeLines
|
|
79
|
+
.slice()
|
|
80
|
+
.reverse()
|
|
81
|
+
.map((line) => ({ ...line, p0: line.p1, p1: line.p0 }));
|
|
82
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Box, type Point2Like } from '@expofp/geometry';
|
|
2
|
+
/**
|
|
3
|
+
* Computes the axis-aligned bounding box of a set of points.
|
|
4
|
+
* Always returns a {@link Box}. An empty array returns `Box.fromPoints([])` — a zero-size
|
|
5
|
+
* box at the origin — which is distinguished from a real route bbox via `.isEmpty()`.
|
|
6
|
+
* @param points
|
|
7
|
+
*/
|
|
8
|
+
export declare function computeBoundingBox(points: readonly Point2Like[]): Box;
|
|
9
|
+
//# sourceMappingURL=routeGeometry.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Box } from '@expofp/geometry';
|
|
2
|
+
/**
|
|
3
|
+
* Computes the axis-aligned bounding box of a set of points.
|
|
4
|
+
* Always returns a {@link Box}. An empty array returns `Box.fromPoints([])` — a zero-size
|
|
5
|
+
* box at the origin — which is distinguished from a real route bbox via `.isEmpty()`.
|
|
6
|
+
* @param points
|
|
7
|
+
*/
|
|
8
|
+
export function computeBoundingBox(points) {
|
|
9
|
+
return Box.fromPoints(points);
|
|
10
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type GraphInstance, type RoutePoint, type RouteResult } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Builds a route through an ordered sequence of pre-resolved candidate groups.
|
|
4
|
+
* Each group is a `RoutePoint[]` — the candidate graph points for one waypoint.
|
|
5
|
+
*
|
|
6
|
+
* Intermediate groups with no candidates (empty arrays) are silently skipped —
|
|
7
|
+
* adjacent groups are connected directly. This matches the legacy behavior
|
|
8
|
+
* where unreachable waypoints were omitted.
|
|
9
|
+
* @param graph
|
|
10
|
+
* @param candidateGroups
|
|
11
|
+
*/
|
|
12
|
+
export declare function buildMultiPointRoute(graph: GraphInstance, candidateGroups: RoutePoint[][]): RouteResult;
|
|
13
|
+
//# sourceMappingURL=buildMultiPointRoute.d.ts.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { buildRoute } from './buildRoute.js';
|
|
2
|
+
import { emptyResult } from './routeResult.js';
|
|
3
|
+
/**
|
|
4
|
+
* Builds a route through an ordered sequence of pre-resolved candidate groups.
|
|
5
|
+
* Each group is a `RoutePoint[]` — the candidate graph points for one waypoint.
|
|
6
|
+
*
|
|
7
|
+
* Intermediate groups with no candidates (empty arrays) are silently skipped —
|
|
8
|
+
* adjacent groups are connected directly. This matches the legacy behavior
|
|
9
|
+
* where unreachable waypoints were omitted.
|
|
10
|
+
* @param graph
|
|
11
|
+
* @param candidateGroups
|
|
12
|
+
*/
|
|
13
|
+
export function buildMultiPointRoute(graph, candidateGroups) {
|
|
14
|
+
if (candidateGroups.length < 2)
|
|
15
|
+
return emptyResult();
|
|
16
|
+
// Skip intermediate waypoints with no graph candidates (legacy behavior:
|
|
17
|
+
// the old code omitted such waypoints from pointsArr, connecting adjacent ones)
|
|
18
|
+
const reachable = candidateGroups.filter((candidates, index) => {
|
|
19
|
+
if (index === 0 || index === candidateGroups.length - 1)
|
|
20
|
+
return true;
|
|
21
|
+
return candidates.length > 0;
|
|
22
|
+
});
|
|
23
|
+
if (reachable.length < 2)
|
|
24
|
+
return emptyResult();
|
|
25
|
+
if (reachable.length === 2)
|
|
26
|
+
return buildRoute(graph, reachable[0], reachable[1]);
|
|
27
|
+
const segmentResults = [];
|
|
28
|
+
for (let i = 0; i < reachable.length - 1; i++) {
|
|
29
|
+
segmentResults.push(buildRoute(graph, reachable[i], reachable[i + 1]));
|
|
30
|
+
}
|
|
31
|
+
// Legs concatenate directly: for [A, B, C], buildRoute(A, B) → [A→…→B] and
|
|
32
|
+
// buildRoute(B, C) → [B→…→C] share endpoint B, forming one continuous polyline.
|
|
33
|
+
const allLines = segmentResults.flatMap((result) => result.lines);
|
|
34
|
+
const totalDistance = segmentResults.reduce((sum, result) => sum + result.totalDistance, 0);
|
|
35
|
+
return { lines: allLines, totalDistance };
|
|
36
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type GraphInstance, type RoutePoint, type RouteResult } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Builds a route between two sets of candidate graph points.
|
|
4
|
+
*
|
|
5
|
+
* This is pure pathfinding — it does not resolve waypoints to graph points.
|
|
6
|
+
* Resolution is the caller's responsibility (see `getGraphLines`).
|
|
7
|
+
* @param graph
|
|
8
|
+
* @param fromCandidates
|
|
9
|
+
* @param toCandidates
|
|
10
|
+
*/
|
|
11
|
+
export declare function buildRoute(graph: GraphInstance, fromCandidates: RoutePoint[], toCandidates: RoutePoint[]): RouteResult;
|
|
12
|
+
//# sourceMappingURL=buildRoute.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { findShortestPath } from '../graph/findShortestPath.js';
|
|
2
|
+
import { emptyResult } from './routeResult.js';
|
|
3
|
+
/**
|
|
4
|
+
* Builds a route between two sets of candidate graph points.
|
|
5
|
+
*
|
|
6
|
+
* This is pure pathfinding — it does not resolve waypoints to graph points.
|
|
7
|
+
* Resolution is the caller's responsibility (see `getGraphLines`).
|
|
8
|
+
* @param graph
|
|
9
|
+
* @param fromCandidates
|
|
10
|
+
* @param toCandidates
|
|
11
|
+
*/
|
|
12
|
+
export function buildRoute(graph, fromCandidates, toCandidates) {
|
|
13
|
+
if (!fromCandidates.length || !toCandidates.length)
|
|
14
|
+
return emptyResult();
|
|
15
|
+
const result = findShortestPath(graph, fromCandidates, toCandidates);
|
|
16
|
+
if (!result)
|
|
17
|
+
return emptyResult();
|
|
18
|
+
return { lines: result.lines, totalDistance: result.distance };
|
|
19
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type GraphDataSource, type PolygonVertex, type RoutePoint } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Graph endpoints inside a convex polygon (e.g. booth rect), filtered by layer.
|
|
4
|
+
* @param dataSource
|
|
5
|
+
* @param polygon
|
|
6
|
+
* @param layer
|
|
7
|
+
*/
|
|
8
|
+
export declare function findGraphPointsInPolygon(dataSource: GraphDataSource, polygon: readonly PolygonVertex[], layer?: string): RoutePoint[];
|
|
9
|
+
//# sourceMappingURL=graphPointResolvers.d.ts.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { computePolygonArea, pointInPolygon } from '../geometry/pointInPolygon.js';
|
|
2
|
+
/**
|
|
3
|
+
* Graph endpoints inside a convex polygon (e.g. booth rect), filtered by layer.
|
|
4
|
+
* @param dataSource
|
|
5
|
+
* @param polygon
|
|
6
|
+
* @param layer
|
|
7
|
+
*/
|
|
8
|
+
export function findGraphPointsInPolygon(dataSource, polygon, layer) {
|
|
9
|
+
const polygonArea = computePolygonArea(polygon);
|
|
10
|
+
return dataSource
|
|
11
|
+
.getLineEnds()
|
|
12
|
+
.filter((lineEnd) => (!layer || lineEnd.layer === layer) && pointInPolygon(lineEnd, polygon, polygonArea));
|
|
13
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Point2Like } from '@expofp/geometry';
|
|
2
|
+
/**
|
|
3
|
+
* Optimizes waypoint ordering to minimize total travel distance.
|
|
4
|
+
* Nearest neighbor O(n²) + 2-opt refinement O(k·n²), k ≤ 100.
|
|
5
|
+
* @param data
|
|
6
|
+
*/
|
|
7
|
+
export declare function optimizeWaypointOrder(data: readonly [string, Point2Like][]): string[];
|
|
8
|
+
/**
|
|
9
|
+
* Reorders waypoints for fastest traversal.
|
|
10
|
+
* First and last stay fixed (from/to), only middle waypoints are reordered.
|
|
11
|
+
* @param waypoints
|
|
12
|
+
*/
|
|
13
|
+
export declare function reorderWaypoints<T extends Point2Like>(waypoints: T[]): T[];
|
|
14
|
+
//# sourceMappingURL=optimizeWaypointOrder.d.ts.map
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const MAX_ITERATIONS = 100;
|
|
2
|
+
const MIN_IMPROVEMENT = -0.01;
|
|
3
|
+
/**
|
|
4
|
+
* Optimizes waypoint ordering to minimize total travel distance.
|
|
5
|
+
* Nearest neighbor O(n²) + 2-opt refinement O(k·n²), k ≤ 100.
|
|
6
|
+
* @param data
|
|
7
|
+
*/
|
|
8
|
+
export function optimizeWaypointOrder(data) {
|
|
9
|
+
const points = new Map(data);
|
|
10
|
+
const waypoints = Array.from(points.keys());
|
|
11
|
+
if (waypoints.length <= 2)
|
|
12
|
+
return waypoints;
|
|
13
|
+
const route = nearestNeighbor(waypoints, points);
|
|
14
|
+
return route.length < 4 ? route : twoOpt(route, points);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Reorders waypoints for fastest traversal.
|
|
18
|
+
* First and last stay fixed (from/to), only middle waypoints are reordered.
|
|
19
|
+
* @param waypoints
|
|
20
|
+
*/
|
|
21
|
+
export function reorderWaypoints(waypoints) {
|
|
22
|
+
if (waypoints.length <= 3)
|
|
23
|
+
return waypoints;
|
|
24
|
+
const first = waypoints[0];
|
|
25
|
+
const last = waypoints[waypoints.length - 1];
|
|
26
|
+
const middle = waypoints.slice(1, -1);
|
|
27
|
+
const indexed = middle.map((wp, index) => [
|
|
28
|
+
String(index),
|
|
29
|
+
{ x: wp.x, y: wp.y },
|
|
30
|
+
]);
|
|
31
|
+
const optimizedOrder = optimizeWaypointOrder(indexed);
|
|
32
|
+
const reordered = optimizedOrder.map((index) => middle[Number(index)]);
|
|
33
|
+
return [first, ...reordered, last];
|
|
34
|
+
}
|
|
35
|
+
function nearestNeighbor(waypoints, points) {
|
|
36
|
+
const sorted = [waypoints[0]];
|
|
37
|
+
const remaining = new Set(waypoints);
|
|
38
|
+
let current = waypoints[0];
|
|
39
|
+
remaining.delete(waypoints[0]);
|
|
40
|
+
while (remaining.size > 0) {
|
|
41
|
+
const cur = points.get(current);
|
|
42
|
+
let nearest = '';
|
|
43
|
+
let minDist = Infinity;
|
|
44
|
+
for (const id of remaining) {
|
|
45
|
+
const pt = points.get(id);
|
|
46
|
+
const distance = Math.abs(cur.x - pt.x) + Math.abs(cur.y - pt.y);
|
|
47
|
+
if (distance < minDist) {
|
|
48
|
+
minDist = distance;
|
|
49
|
+
nearest = id;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (!nearest)
|
|
53
|
+
break;
|
|
54
|
+
sorted.push(nearest);
|
|
55
|
+
remaining.delete(nearest);
|
|
56
|
+
current = nearest;
|
|
57
|
+
}
|
|
58
|
+
return sorted;
|
|
59
|
+
}
|
|
60
|
+
function twoOpt(route, points) {
|
|
61
|
+
const result = [...route];
|
|
62
|
+
let improved = true;
|
|
63
|
+
for (let iter = 0; improved && iter < MAX_ITERATIONS; iter++) {
|
|
64
|
+
improved = false;
|
|
65
|
+
scan: for (let index = 1; index < result.length - 2; index++) {
|
|
66
|
+
for (let swapEnd = index + 1; swapEnd < result.length - 1; swapEnd++) {
|
|
67
|
+
const before = points.get(result[index - 1]);
|
|
68
|
+
const start = points.get(result[index]);
|
|
69
|
+
const end = points.get(result[swapEnd]);
|
|
70
|
+
const after = points.get(result[swapEnd + 1]);
|
|
71
|
+
const delta = Math.abs(before.x - end.x) +
|
|
72
|
+
Math.abs(before.y - end.y) +
|
|
73
|
+
Math.abs(start.x - after.x) +
|
|
74
|
+
Math.abs(start.y - after.y) -
|
|
75
|
+
(Math.abs(before.x - start.x) + Math.abs(before.y - start.y)) -
|
|
76
|
+
(Math.abs(end.x - after.x) + Math.abs(end.y - after.y));
|
|
77
|
+
if (delta < MIN_IMPROVEMENT) {
|
|
78
|
+
for (let left = index, right = swapEnd; left < right; left++, right--) {
|
|
79
|
+
[result[left], result[right]] = [result[right], result[left]];
|
|
80
|
+
}
|
|
81
|
+
improved = true;
|
|
82
|
+
break scan;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { GraphInstance, RouteEndpoint, RouteLine, RoutePoint } from '../types.js';
|
|
2
|
+
export interface OffGraphEntry {
|
|
3
|
+
readonly segment: RouteLine;
|
|
4
|
+
readonly projection: RoutePoint;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* A* candidates plus an optional `offGraphEntry` — set when the route's entry needs perpendicular
|
|
8
|
+
* trim to land on a point inside a segment instead of at a graph node.
|
|
9
|
+
*/
|
|
10
|
+
export interface ResolvedCandidates {
|
|
11
|
+
readonly candidates: RoutePoint[];
|
|
12
|
+
readonly offGraphEntry: OffGraphEntry | null;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Turns a route endpoint into A* candidates.
|
|
16
|
+
* - Booth (has `bounds`) → graph nodes inside the polygon.
|
|
17
|
+
* - Off-graph point → nearest segment on its layer; both ends are candidates and `offGraphEntry`
|
|
18
|
+
* holds the perpendicular foot to trim to. If the foot falls off the segment, attach at its node.
|
|
19
|
+
* @param graph
|
|
20
|
+
* @param endpoint
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveWaypointCandidates(graph: GraphInstance, endpoint: RouteEndpoint): ResolvedCandidates;
|
|
23
|
+
//# sourceMappingURL=resolveWaypointCandidates.d.ts.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { projectPointOnSegment, } from '../geometry/projectPointOnSegment.js';
|
|
2
|
+
import { findGraphPointsInPolygon } from './graphPointResolvers.js';
|
|
3
|
+
/**
|
|
4
|
+
* Turns a route endpoint into A* candidates.
|
|
5
|
+
* - Booth (has `bounds`) → graph nodes inside the polygon.
|
|
6
|
+
* - Off-graph point → nearest segment on its layer; both ends are candidates and `offGraphEntry`
|
|
7
|
+
* holds the perpendicular foot to trim to. If the foot falls off the segment, attach at its node.
|
|
8
|
+
* @param graph
|
|
9
|
+
* @param endpoint
|
|
10
|
+
*/
|
|
11
|
+
export function resolveWaypointCandidates(graph, endpoint) {
|
|
12
|
+
if (endpoint.bounds?.length) {
|
|
13
|
+
return {
|
|
14
|
+
candidates: findGraphPointsInPolygon(graph.dataSource, endpoint.bounds, endpoint.layer),
|
|
15
|
+
offGraphEntry: null,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
let nearest = null;
|
|
19
|
+
let nearestDistance = Infinity;
|
|
20
|
+
for (const line of graph.dataSource.getLines()) {
|
|
21
|
+
if (line.virtual)
|
|
22
|
+
continue;
|
|
23
|
+
if (endpoint.layer && line.p0.layer !== endpoint.layer)
|
|
24
|
+
continue;
|
|
25
|
+
if (line.p0.x === line.p1.x && line.p0.y === line.p1.y)
|
|
26
|
+
continue;
|
|
27
|
+
const proj = projectPointOnSegment(endpoint, line.p0, line.p1);
|
|
28
|
+
if (proj.distance < nearestDistance) {
|
|
29
|
+
nearestDistance = proj.distance;
|
|
30
|
+
nearest = { segment: line, proj };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (!nearest)
|
|
34
|
+
return { candidates: [], offGraphEntry: null };
|
|
35
|
+
const { segment, proj } = nearest;
|
|
36
|
+
// Foot outside the segment → closest point is a graph node; attach there directly, no trim.
|
|
37
|
+
if (proj.t === 0)
|
|
38
|
+
return { candidates: [segment.p0], offGraphEntry: null };
|
|
39
|
+
if (proj.t === 1)
|
|
40
|
+
return { candidates: [segment.p1], offGraphEntry: null };
|
|
41
|
+
// Foot inside the segment → both ends are A* candidates (shorter path wins); trim to the foot.
|
|
42
|
+
return {
|
|
43
|
+
candidates: [segment.p0, segment.p1],
|
|
44
|
+
offGraphEntry: {
|
|
45
|
+
segment,
|
|
46
|
+
projection: { layer: segment.p0.layer, x: proj.projected.x, y: proj.projected.y },
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export interface PolygonVertex {
|
|
2
|
+
readonly x: number;
|
|
3
|
+
readonly y: number;
|
|
4
|
+
}
|
|
5
|
+
export interface RoutePoint {
|
|
6
|
+
readonly x: number;
|
|
7
|
+
readonly y: number;
|
|
8
|
+
readonly layer: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A computed route segment — the public output of routing, consumed by the
|
|
12
|
+
* runtime and rendering. Carries only what consumers read; graph-build
|
|
13
|
+
* properties (direction, cost) live on {@link GraphLine}, not here.
|
|
14
|
+
*/
|
|
15
|
+
export interface RouteLine {
|
|
16
|
+
readonly p0: RoutePoint;
|
|
17
|
+
readonly p1: RoutePoint;
|
|
18
|
+
/** Virtual link rather than a physical path segment. May or may not change layers. */
|
|
19
|
+
readonly virtual: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A source graph line from external wayfinding data (wf.data.js) — the input to
|
|
23
|
+
* graph building. Extends {@link RouteLine} with build-only properties
|
|
24
|
+
* (accessibility, direction, cost) that never reach a computed route.
|
|
25
|
+
*/
|
|
26
|
+
export interface GraphLine extends RouteLine {
|
|
27
|
+
/** Flagged inaccessible in source data: graph-build filter + the venue accessible toggle. */
|
|
28
|
+
readonly unaccessible: boolean;
|
|
29
|
+
/** One-way link: in oriented mode no reverse edge is added. */
|
|
30
|
+
readonly unidirection: boolean;
|
|
31
|
+
/** Link weight for cost (`0` = unset → `DEFAULT_LINE_WEIGHT`). */
|
|
32
|
+
readonly weight: number;
|
|
33
|
+
/**
|
|
34
|
+
* Per-line virtual cost from the designer ("Virtual length"). When set it
|
|
35
|
+
* replaces the default policy (`0` = free/preferred, large = avoided);
|
|
36
|
+
* otherwise a layer-changing link costs `FLOOR_TRANSITION_PENALTY`, a
|
|
37
|
+
* same-layer one is free.
|
|
38
|
+
*/
|
|
39
|
+
readonly virtualLength?: number;
|
|
40
|
+
}
|
|
41
|
+
export interface PathNode {
|
|
42
|
+
readonly id: string;
|
|
43
|
+
readonly layer: string;
|
|
44
|
+
readonly x: number;
|
|
45
|
+
readonly y: number;
|
|
46
|
+
}
|
|
47
|
+
export interface PathFinderOptions {
|
|
48
|
+
readonly oriented: boolean;
|
|
49
|
+
readonly onlyAccessible: boolean;
|
|
50
|
+
}
|
|
51
|
+
export interface PathFinder {
|
|
52
|
+
build(lines: GraphLine[], options: PathFinderOptions): void;
|
|
53
|
+
find(fromId: string, toId: string): PathNode[];
|
|
54
|
+
hasNode(id: string): boolean;
|
|
55
|
+
}
|
|
56
|
+
export interface GraphDataSource {
|
|
57
|
+
getLines(): GraphLine[];
|
|
58
|
+
getLineEnds(): RoutePoint[];
|
|
59
|
+
}
|
|
60
|
+
export interface GraphInstance {
|
|
61
|
+
readonly finder: PathFinder;
|
|
62
|
+
readonly dataSource: GraphDataSource;
|
|
63
|
+
readonly options: GraphBuildOptions;
|
|
64
|
+
}
|
|
65
|
+
export interface GraphBuildOptions {
|
|
66
|
+
readonly oriented?: boolean;
|
|
67
|
+
readonly onlyAccessible?: boolean;
|
|
68
|
+
}
|
|
69
|
+
export interface ShortestPathResult {
|
|
70
|
+
readonly lines: RouteLine[];
|
|
71
|
+
readonly distance: number;
|
|
72
|
+
}
|
|
73
|
+
export interface RouteResult {
|
|
74
|
+
readonly lines: RouteLine[];
|
|
75
|
+
readonly totalDistance: number;
|
|
76
|
+
}
|
|
77
|
+
export type RouteSnapResult = {
|
|
78
|
+
snapped: false;
|
|
79
|
+
} | {
|
|
80
|
+
readonly snapped: true;
|
|
81
|
+
readonly snappedPoint: RoutePoint;
|
|
82
|
+
readonly snappedLine: RouteLine;
|
|
83
|
+
/** Arc-length from `snappedPoint` to the destination, along the route. */
|
|
84
|
+
readonly distance: number;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Route endpoint: anchor on a layer, with optional perimeter polygon (booth bounds).
|
|
88
|
+
* With `bounds` → engine picks the optimal graph node inside the polygon.
|
|
89
|
+
* Without `bounds` → off-graph point (e.g. kiosk); engine snaps to nearest graph anchor.
|
|
90
|
+
*
|
|
91
|
+
* `id` is an opaque runtime discriminator the engine ignores — used by the
|
|
92
|
+
* adapter layer to mark off-graph endpoints (e.g. CP-derived vs kiosk anchor)
|
|
93
|
+
* so the renderer can branch on endpoint origin without re-checking UI types.
|
|
94
|
+
*/
|
|
95
|
+
export interface RouteEndpoint extends RoutePoint {
|
|
96
|
+
readonly bounds?: readonly PolygonVertex[];
|
|
97
|
+
readonly id?: string;
|
|
98
|
+
}
|
|
99
|
+
export interface SnapToRouteConfig {
|
|
100
|
+
readonly snapThreshold: number;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Box } from '@expofp/geometry';
|
|
2
|
+
import type { RenderableDef } from '@expofp/renderer';
|
|
3
|
+
import { type GpsProjectionConfig, type GraphDataSource, type Route, type RouteEndpoint, type RouteLine, type RoutePoint, type TransitionPointDef } from './core/index.js';
|
|
4
|
+
import { type IconConfig, type RendererPort } from './renderer/index.js';
|
|
5
|
+
import { type FloorContext, type IconProvider, type LayerNames, type PositionInput, type SetRouteInput } from './runtime/index.js';
|
|
6
|
+
/** Plan GPS calibration plus optional snap / reroute distance overrides (meters). */
|
|
7
|
+
export type GpsConfig = GpsProjectionConfig & {
|
|
8
|
+
snapThreshold?: number;
|
|
9
|
+
rerouteThreshold?: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Everything `createWayfinding` needs. The host implements the four ports
|
|
13
|
+
* (`dataSource`/`renderer`/`iconProvider`/`floorContext`) so the package stays
|
|
14
|
+
* free of any concrete renderer, DOM, or app state.
|
|
15
|
+
*/
|
|
16
|
+
export interface WayfindingConfig {
|
|
17
|
+
readonly dataSource: GraphDataSource;
|
|
18
|
+
readonly renderer: RendererPort;
|
|
19
|
+
readonly iconProvider: IconProvider;
|
|
20
|
+
readonly floorContext: FloorContext;
|
|
21
|
+
readonly layers: LayerNames;
|
|
22
|
+
/** Plan GPS calibration; the snap and reroute thresholds are derived from it. */
|
|
23
|
+
readonly gpsConfig?: GpsConfig;
|
|
24
|
+
readonly onTransitionClick?: (point: TransitionPointDef) => void;
|
|
25
|
+
/** Fired when the route changes. `bounds.isEmpty()` detects an empty/cleared route. */
|
|
26
|
+
readonly onRouteUpdate?: (lines: readonly RouteLine[], bounds: Box) => void;
|
|
27
|
+
/** Fired when the remaining distance changes (route change + position snaps). */
|
|
28
|
+
readonly onRouteDistance?: (distance: number) => void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The single wayfinding instance. Owns the routing engine, the scene renderer,
|
|
32
|
+
* and the runtime; the host drives it with route/position/floor updates and
|
|
33
|
+
* forwards its camera/pointer events to the `apply*`/`handle*` methods.
|
|
34
|
+
*/
|
|
35
|
+
export interface Wayfinding {
|
|
36
|
+
/** Build (or update) the active route; `false` when no path exists. */
|
|
37
|
+
setRoute(input: SetRouteInput): boolean;
|
|
38
|
+
/** Build a one-off route without touching the active scene (diagnostics). */
|
|
39
|
+
buildRoute(from: RouteEndpoint, to: RouteEndpoint, options?: {
|
|
40
|
+
accessible?: boolean;
|
|
41
|
+
}): Route;
|
|
42
|
+
clearRoute(): void;
|
|
43
|
+
/** Update the user position; `null` hides the position icon and off-route trail. */
|
|
44
|
+
setPosition(position: PositionInput | null): void;
|
|
45
|
+
/** Re-render route content after the active floor changed. */
|
|
46
|
+
notifyFloorChanged(): void;
|
|
47
|
+
/** Whether the position has drifted off the active route far enough to reroute. */
|
|
48
|
+
shouldReroute(position: RoutePoint): boolean;
|
|
49
|
+
/** Forward a camera-zoom change; rescales icons/trails and commits. */
|
|
50
|
+
applyScale(scale: number): void;
|
|
51
|
+
/** Forward a camera-roll change; re-orients cardinal-snap icons and commits. */
|
|
52
|
+
applyRoll(angle: number): void;
|
|
53
|
+
/** Forward a pointer click (already picked scene defs); fires icon `onClick`. */
|
|
54
|
+
handleClick(defs: readonly RenderableDef[]): void;
|
|
55
|
+
/** Forward a pointer move (already picked scene defs); `true` over a clickable icon. */
|
|
56
|
+
handleHover(defs: readonly RenderableDef[]): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Place, update, or hide (`config: null`) a host annotation icon — e.g. kiosk
|
|
59
|
+
* or you-are-here — on the wayfinding icon layer. These icons share the route
|
|
60
|
+
* icons' scale/roll handling. Buffered — call `flush()` to commit.
|
|
61
|
+
*/
|
|
62
|
+
setIcon(name: string, config: IconConfig | null, key?: string | number): void;
|
|
63
|
+
/** Commit buffered scene mutations. */
|
|
64
|
+
flush(): void;
|
|
65
|
+
destroy(): void;
|
|
66
|
+
}
|
|
67
|
+
export declare function createWayfinding(config: WayfindingConfig): Wayfinding;
|
|
68
|
+
//# sourceMappingURL=createWayfinding.d.ts.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { createWayfindingEngine, getThresholdFromMetersToSvg, getThresholdOrDefault, resolveRerouteThresholdSvg, SNAP_THRESHOLD_METERS, } from './core/index.js';
|
|
2
|
+
import { createWayfindingRenderer } from './renderer/index.js';
|
|
3
|
+
import { createWayfindingRuntime, } from './runtime/index.js';
|
|
4
|
+
export function createWayfinding(config) {
|
|
5
|
+
const { dataSource, renderer: rendererPort, iconProvider, floorContext, layers, gpsConfig, } = config;
|
|
6
|
+
const engine = createWayfindingEngine(dataSource);
|
|
7
|
+
const renderer = createWayfindingRenderer(rendererPort);
|
|
8
|
+
const snapThresholdMeters = getThresholdOrDefault(gpsConfig?.snapThreshold, SNAP_THRESHOLD_METERS);
|
|
9
|
+
const snapThreshold = getThresholdFromMetersToSvg({
|
|
10
|
+
thresholdInMeters: snapThresholdMeters,
|
|
11
|
+
gpsConfig,
|
|
12
|
+
});
|
|
13
|
+
const runtime = createWayfindingRuntime({
|
|
14
|
+
engine,
|
|
15
|
+
renderer,
|
|
16
|
+
iconProvider,
|
|
17
|
+
floorContext,
|
|
18
|
+
layers,
|
|
19
|
+
snapThreshold,
|
|
20
|
+
rerouteThreshold: resolveRerouteThresholdSvg(gpsConfig),
|
|
21
|
+
onTransitionClick: (point) => config.onTransitionClick?.(point),
|
|
22
|
+
onRouteUpdate: (lines, bounds) => config.onRouteUpdate?.(lines, bounds),
|
|
23
|
+
onRouteDistance: (distance) => config.onRouteDistance?.(distance),
|
|
24
|
+
});
|
|
25
|
+
const renderDefs = (defs) => {
|
|
26
|
+
if (defs.length)
|
|
27
|
+
rendererPort.update(...defs);
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
setRoute: (input) => runtime.setRoute(input),
|
|
31
|
+
buildRoute: (from, to, options) => engine.buildRoute(from, to, options),
|
|
32
|
+
clearRoute: () => runtime.clearRoute(),
|
|
33
|
+
setPosition: (position) => runtime.onPositionChanged(position),
|
|
34
|
+
notifyFloorChanged: () => runtime.onFloorChanged(),
|
|
35
|
+
shouldReroute: (position) => runtime.shouldReroute(position),
|
|
36
|
+
applyScale: (scale) => renderDefs(renderer.applyScale(scale)),
|
|
37
|
+
applyRoll: (angle) => renderDefs(renderer.applyRoll(angle)),
|
|
38
|
+
handleClick: (defs) => renderer.handleClick(defs),
|
|
39
|
+
handleHover: (defs) => renderer.handleHover(defs),
|
|
40
|
+
setIcon: (name, config, key) => renderer.setIcon(name, config, key),
|
|
41
|
+
flush: () => renderer.flush(),
|
|
42
|
+
destroy: () => runtime.destroy(),
|
|
43
|
+
};
|
|
44
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@expofp/wayfinding` — framework-neutral wayfinding: routing/snapping engine,
|
|
3
|
+
* an imperative runtime, and a scene renderer behind a single entry point.
|
|
4
|
+
*/
|
|
5
|
+
export type { GpsConfig, Wayfinding, WayfindingConfig } from './createWayfinding.js';
|
|
6
|
+
export { createWayfinding } from './createWayfinding.js';
|
|
7
|
+
export type { GraphDataSource } from './core/index.js';
|
|
8
|
+
export type { IconConfig, RendererPort } from './renderer/index.js';
|
|
9
|
+
export type { FloorContext, IconAsset, IconName, IconProvider, LayerNames, } from './runtime/index.js';
|
|
10
|
+
export type { GraphLine, Route, RouteEndpoint, RouteLine, RoutePoint, TransitionPointDef, } from './core/index.js';
|
|
11
|
+
export type { PositionInput, SetRouteInput } from './runtime/index.js';
|
|
12
|
+
export { CURRENT_POSITION_POINT_ID } from './runtime/index.js';
|
|
13
|
+
export { optimizeWaypointOrder } from './core/routing/optimizeWaypointOrder.js';
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|