@osmix/router 0.0.11 → 0.1.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/CHANGELOG.md +78 -0
- package/LICENSE +21 -0
- package/README.md +138 -53
- package/dist/algorithms/bidirectional.d.ts +6 -9
- package/dist/algorithms/bidirectional.d.ts.map +1 -1
- package/dist/algorithms/bidirectional.js +84 -104
- package/dist/algorithms/bidirectional.js.map +1 -1
- package/dist/algorithms/index.d.ts +2 -2
- package/dist/algorithms/index.d.ts.map +1 -1
- package/dist/algorithms/index.js +1 -1
- package/dist/algorithms/index.js.map +1 -1
- package/dist/algorithms/shortest-path.d.ts +2 -1
- package/dist/algorithms/shortest-path.d.ts.map +1 -1
- package/dist/algorithms/shortest-path.js +9 -7
- package/dist/algorithms/shortest-path.js.map +1 -1
- package/dist/binary-heap.d.ts +5 -0
- package/dist/binary-heap.d.ts.map +1 -1
- package/dist/binary-heap.js +11 -4
- package/dist/binary-heap.js.map +1 -1
- package/dist/graph.d.ts +35 -7
- package/dist/graph.d.ts.map +1 -1
- package/dist/graph.js +161 -119
- package/dist/graph.js.map +1 -1
- package/dist/index.d.ts +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -4
- package/dist/index.js.map +1 -1
- package/dist/router.d.ts +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +6 -3
- package/dist/router.js.map +1 -1
- package/dist/topology-stats.d.ts +37 -0
- package/dist/topology-stats.d.ts.map +1 -0
- package/dist/topology-stats.js +62 -0
- package/dist/topology-stats.js.map +1 -0
- package/dist/types.d.ts +15 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +1 -7
- package/dist/utils.js.map +1 -1
- package/package.json +32 -29
- package/src/algorithms/bidirectional.ts +124 -136
- package/src/algorithms/index.ts +10 -10
- package/src/algorithms/shortest-path.ts +96 -92
- package/src/binary-heap.ts +133 -129
- package/src/graph.ts +389 -355
- package/src/index.ts +14 -9
- package/src/router.ts +221 -233
- package/src/topology-stats.ts +84 -0
- package/src/types.ts +102 -87
- package/src/utils.ts +67 -74
- package/test/algorithms.test.ts +396 -353
- package/test/binary-heap.test.ts +27 -0
- package/test/limits.test.ts +82 -0
- package/test/no-shared-array-buffer.test.ts +37 -0
- package/test/router.bench.ts +171 -188
- package/test/router.test.ts +685 -726
- package/test/spatial-capability.test.ts +23 -0
- package/test/topology-stats.test.ts +82 -0
- package/tsconfig.build.json +6 -3
- package/tsconfig.json +7 -7
package/src/types.ts
CHANGED
|
@@ -3,91 +3,91 @@
|
|
|
3
3
|
* @module
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { BufferType } from "@osmix/core"
|
|
7
|
-
import type { LonLat, OsmTags } from "@osmix/
|
|
6
|
+
import type { BufferType } from "@osmix/core";
|
|
7
|
+
import type { LonLat, OsmTags } from "@osmix/types";
|
|
8
8
|
|
|
9
9
|
/** Per-way segment with distance and time (consecutive same-name ways merged). */
|
|
10
10
|
export interface WaySegment {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
/** OSM way IDs included in this segment (multiple if merged). */
|
|
12
|
+
wayIds: number[];
|
|
13
|
+
/** Way name from tags (may be empty). */
|
|
14
|
+
name: string;
|
|
15
|
+
/** Highway type from tags. */
|
|
16
|
+
highway: string;
|
|
17
|
+
/** Distance travelled on this segment in meters. */
|
|
18
|
+
distance: number;
|
|
19
|
+
/** Time travelled on this segment in seconds. */
|
|
20
|
+
time: number;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/** Route statistics (distance and time). */
|
|
24
24
|
export interface RouteStatistics {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
/** Total route distance in meters. */
|
|
26
|
+
distance: number;
|
|
27
|
+
/** Total route time in seconds. */
|
|
28
|
+
time: number;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/** Route path info (segments and turn points). */
|
|
32
32
|
export interface RoutePathInfo {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
/** Per-way breakdown (consecutive same-name ways merged). */
|
|
34
|
+
segments: WaySegment[];
|
|
35
|
+
/** Coordinates where way name changes (turn points). */
|
|
36
|
+
turnPoints: LonLat[];
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/** Route result containing the path geometry and metadata. */
|
|
40
40
|
export interface RouteResult {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
41
|
+
/** Coordinates along the route. */
|
|
42
|
+
coordinates: LonLat[];
|
|
43
|
+
/** Way indexes traversed, in order. */
|
|
44
|
+
wayIndexes: number[];
|
|
45
|
+
/** Node indexes at turns and intersections. */
|
|
46
|
+
nodeIndexes: number[];
|
|
47
|
+
/** Total route distance in meters (when includeStats is true). */
|
|
48
|
+
distance?: number;
|
|
49
|
+
/** Total route time in seconds (when includeStats is true). */
|
|
50
|
+
time?: number;
|
|
51
|
+
/** Per-way breakdown (when includePathInfo is true). */
|
|
52
|
+
segments?: WaySegment[];
|
|
53
|
+
/** Coordinates where way name changes (when includePathInfo is true). */
|
|
54
|
+
turnPoints?: LonLat[];
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
/** Filter function to determine which highways are routable. Default: common vehicle highways. */
|
|
58
|
-
export type HighwayFilter = (tags?: OsmTags) => boolean
|
|
58
|
+
export type HighwayFilter = (tags?: OsmTags) => boolean;
|
|
59
59
|
|
|
60
60
|
/** Available routing algorithms. */
|
|
61
|
-
export type RoutingAlgorithm = "dijkstra" | "astar" | "bidirectional"
|
|
61
|
+
export type RoutingAlgorithm = "dijkstra" | "astar" | "bidirectional";
|
|
62
62
|
|
|
63
63
|
/** Routing metric to optimize. */
|
|
64
|
-
export type RoutingMetric = "distance" | "time"
|
|
64
|
+
export type RoutingMetric = "distance" | "time";
|
|
65
65
|
|
|
66
66
|
/** Speed limits by highway type (km/h). */
|
|
67
|
-
export type DefaultSpeeds = Record<string, number
|
|
67
|
+
export type DefaultSpeeds = Record<string, number>;
|
|
68
68
|
|
|
69
69
|
/** Router configuration options. */
|
|
70
70
|
export interface RouteOptions {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
71
|
+
/** Routing algorithm. Default: "astar". */
|
|
72
|
+
algorithm: RoutingAlgorithm;
|
|
73
|
+
/** Optimization metric. Default: "distance". */
|
|
74
|
+
metric: RoutingMetric;
|
|
75
|
+
/** Include distance and time in result. Default: false. */
|
|
76
|
+
includeStats?: boolean;
|
|
77
|
+
/** Include segments and turnPoints in result. Default: false. */
|
|
78
|
+
includePathInfo?: boolean;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
/** Edge in the routing graph. */
|
|
82
82
|
export interface GraphEdge {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
83
|
+
/** Target node index. */
|
|
84
|
+
targetNodeIndex: number;
|
|
85
|
+
/** Way index this edge belongs to. */
|
|
86
|
+
wayIndex: number;
|
|
87
|
+
/** Distance in meters. */
|
|
88
|
+
distance: number;
|
|
89
|
+
/** Travel time in seconds. */
|
|
90
|
+
time: number;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
/**
|
|
@@ -95,44 +95,59 @@ export interface GraphEdge {
|
|
|
95
95
|
* Uses CSR (Compressed Sparse Row) format for efficient storage and zero-copy transfer.
|
|
96
96
|
*/
|
|
97
97
|
export interface RoutingGraphTransferables {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
98
|
+
/** Total number of nodes in the graph. */
|
|
99
|
+
nodeCount: number;
|
|
100
|
+
/** Total number of edges in the graph. */
|
|
101
|
+
edgeCount: number;
|
|
102
|
+
/** CSR offsets: edges for node i are at indices [edgeOffsets[i], edgeOffsets[i+1]) */
|
|
103
|
+
edgeOffsets: BufferType;
|
|
104
|
+
/** Target node index for each edge. */
|
|
105
|
+
edgeTargets: BufferType;
|
|
106
|
+
/** Way index for each edge. */
|
|
107
|
+
edgeWayIndexes: BufferType;
|
|
108
|
+
/** Distance in meters for each edge. */
|
|
109
|
+
edgeDistances: BufferType;
|
|
110
|
+
/** Travel time in seconds for each edge. */
|
|
111
|
+
edgeTimes: BufferType;
|
|
112
|
+
/** Bitset: 1 bit per node indicating if routable. */
|
|
113
|
+
routableBits: BufferType;
|
|
114
|
+
/** Bitset: 1 bit per node indicating if intersection. */
|
|
115
|
+
intersectionBits: BufferType;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
/** Path segment returned by routing algorithms. */
|
|
119
119
|
export interface PathSegment {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
120
|
+
/** Node index. */
|
|
121
|
+
nodeIndex: number;
|
|
122
|
+
/** Way index used to reach this node. */
|
|
123
|
+
wayIndex?: number;
|
|
124
|
+
/** Previous node in path. */
|
|
125
|
+
previousNodeIndex?: number;
|
|
126
|
+
/** Cost to reach this node. */
|
|
127
|
+
cost: number;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Extra graph data that some routing algorithms need. */
|
|
131
|
+
export interface RoutingAlgorithmContext {
|
|
132
|
+
/**
|
|
133
|
+
* Incoming edges of a node. In each returned edge, `targetNodeIndex` is the
|
|
134
|
+
* edge's source node. Required by `bidirectional`.
|
|
135
|
+
*/
|
|
136
|
+
reverseGraph?: (nodeIndex: number) => GraphEdge[];
|
|
137
|
+
/**
|
|
138
|
+
* Fastest edge speed in the graph, in meters per second. Bounds the A* time
|
|
139
|
+
* heuristic so it never overestimates. Default: 130 km/h.
|
|
140
|
+
*/
|
|
141
|
+
maxSpeedMps?: number;
|
|
128
142
|
}
|
|
129
143
|
|
|
130
144
|
/** Function signature for routing algorithms. */
|
|
131
145
|
export type RoutingAlgorithmFn = (
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
146
|
+
graph: (nodeIndex: number) => GraphEdge[],
|
|
147
|
+
startNodeIndex: number,
|
|
148
|
+
endNodeIndex: number,
|
|
149
|
+
getEdgeWeight: (edge: GraphEdge) => number,
|
|
150
|
+
getNodeCoord?: (nodeIndex: number) => LonLat | undefined,
|
|
151
|
+
metric?: RoutingMetric,
|
|
152
|
+
context?: RoutingAlgorithmContext,
|
|
153
|
+
) => PathSegment[] | null;
|
package/src/utils.ts
CHANGED
|
@@ -6,109 +6,102 @@
|
|
|
6
6
|
* @module
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type { OsmTags } from "@osmix/
|
|
10
|
-
|
|
9
|
+
import type { OsmTags } from "@osmix/types";
|
|
10
|
+
|
|
11
|
+
import type { DefaultSpeeds } from "./types.ts";
|
|
11
12
|
|
|
12
13
|
/** Default speeds (km/h) by highway type. */
|
|
13
14
|
export const DEFAULT_SPEEDS: DefaultSpeeds = {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
15
|
+
motorway: 120,
|
|
16
|
+
motorway_link: 60,
|
|
17
|
+
trunk: 100,
|
|
18
|
+
trunk_link: 50,
|
|
19
|
+
primary: 80,
|
|
20
|
+
primary_link: 50,
|
|
21
|
+
secondary: 70,
|
|
22
|
+
secondary_link: 50,
|
|
23
|
+
tertiary: 60,
|
|
24
|
+
tertiary_link: 50,
|
|
25
|
+
unclassified: 50,
|
|
26
|
+
residential: 30,
|
|
27
|
+
living_street: 20,
|
|
28
|
+
service: 20,
|
|
29
|
+
footway: 5,
|
|
30
|
+
path: 5,
|
|
31
|
+
cycleway: 20,
|
|
32
|
+
bridleway: 10,
|
|
33
|
+
steps: 2,
|
|
34
|
+
};
|
|
34
35
|
|
|
35
36
|
/** Highways included in default vehicle routing. */
|
|
36
37
|
const VEHICLE_HIGHWAYS = new Set([
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
])
|
|
38
|
+
"motorway",
|
|
39
|
+
"motorway_link",
|
|
40
|
+
"trunk",
|
|
41
|
+
"trunk_link",
|
|
42
|
+
"primary",
|
|
43
|
+
"primary_link",
|
|
44
|
+
"secondary",
|
|
45
|
+
"secondary_link",
|
|
46
|
+
"tertiary",
|
|
47
|
+
"tertiary_link",
|
|
48
|
+
"unclassified",
|
|
49
|
+
"residential",
|
|
50
|
+
"living_street",
|
|
51
|
+
"service",
|
|
52
|
+
]);
|
|
52
53
|
|
|
53
54
|
/** Footways included in default pedestrian routing. */
|
|
54
|
-
const FOOT_HIGHWAYS = new Set([
|
|
55
|
-
"footway",
|
|
56
|
-
"path",
|
|
57
|
-
"cycleway",
|
|
58
|
-
"bridleway",
|
|
59
|
-
"steps",
|
|
60
|
-
])
|
|
55
|
+
const FOOT_HIGHWAYS = new Set(["footway", "path", "cycleway", "bridleway", "steps"]);
|
|
61
56
|
|
|
62
57
|
/** Parse maxspeed tag to km/h. Handles "50", "50 km/h", "30 mph". */
|
|
63
|
-
export function parseMaxSpeed(
|
|
64
|
-
|
|
65
|
-
): number | null {
|
|
66
|
-
if (!value) return null
|
|
58
|
+
export function parseMaxSpeed(value: string | number | undefined): number | null {
|
|
59
|
+
if (!value) return null;
|
|
67
60
|
|
|
68
|
-
|
|
61
|
+
const str = String(value).trim().toLowerCase();
|
|
69
62
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
63
|
+
// Numeric: assume km/h
|
|
64
|
+
const numeric = str.match(/^(\d+)$/);
|
|
65
|
+
if (numeric) return Number.parseInt(numeric[1]!, 10);
|
|
73
66
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
67
|
+
// Explicit km/h
|
|
68
|
+
const kmh = str.match(/^(\d+)\s*(km\/h|kmh|kph)$/);
|
|
69
|
+
if (kmh) return Number.parseInt(kmh[1]!, 10);
|
|
77
70
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
71
|
+
// MPH -> km/h
|
|
72
|
+
const mph = str.match(/^(\d+)\s*mph$/);
|
|
73
|
+
if (mph) return Math.round(Number.parseInt(mph[1]!, 10) * 1.60934);
|
|
81
74
|
|
|
82
|
-
|
|
83
|
-
|
|
75
|
+
// Special values
|
|
76
|
+
if (str === "walk" || str === "none") return 5;
|
|
84
77
|
|
|
85
|
-
|
|
78
|
+
return null;
|
|
86
79
|
}
|
|
87
80
|
|
|
88
81
|
/** Get speed limit (km/h) for a way. */
|
|
89
82
|
export function getSpeedLimit(
|
|
90
|
-
|
|
91
|
-
|
|
83
|
+
tags: OsmTags | undefined,
|
|
84
|
+
defaultSpeeds: DefaultSpeeds = DEFAULT_SPEEDS,
|
|
92
85
|
): number {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
86
|
+
if (tags?.["maxspeed"]) {
|
|
87
|
+
const parsed = parseMaxSpeed(tags["maxspeed"]);
|
|
88
|
+
if (parsed !== null) return parsed;
|
|
89
|
+
}
|
|
97
90
|
|
|
98
|
-
|
|
99
|
-
|
|
91
|
+
const highway = tags?.["highway"] ? String(tags["highway"]) : undefined;
|
|
92
|
+
if (highway && defaultSpeeds[highway]) return defaultSpeeds[highway]!;
|
|
100
93
|
|
|
101
|
-
|
|
94
|
+
return 50;
|
|
102
95
|
}
|
|
103
96
|
|
|
104
97
|
/** Default filter: common vehicle highways. */
|
|
105
98
|
export function defaultHighwayFilter(tags?: OsmTags): boolean {
|
|
106
|
-
|
|
107
|
-
|
|
99
|
+
if (!tags?.["highway"]) return false;
|
|
100
|
+
return VEHICLE_HIGHWAYS.has(String(tags["highway"]));
|
|
108
101
|
}
|
|
109
102
|
|
|
110
103
|
/** Default pedestrian filter */
|
|
111
104
|
export function defaultPedestrianFilter(tags?: OsmTags): boolean {
|
|
112
|
-
|
|
113
|
-
|
|
105
|
+
if (!tags?.["highway"]) return false;
|
|
106
|
+
return FOOT_HIGHWAYS.has(String(tags["highway"]));
|
|
114
107
|
}
|