@osmix/router 0.0.13 → 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.
Files changed (62) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/LICENSE +21 -0
  3. package/README.md +138 -53
  4. package/dist/algorithms/bidirectional.d.ts +6 -9
  5. package/dist/algorithms/bidirectional.d.ts.map +1 -1
  6. package/dist/algorithms/bidirectional.js +84 -104
  7. package/dist/algorithms/bidirectional.js.map +1 -1
  8. package/dist/algorithms/index.d.ts +1 -1
  9. package/dist/algorithms/index.d.ts.map +1 -1
  10. package/dist/algorithms/index.js +1 -1
  11. package/dist/algorithms/index.js.map +1 -1
  12. package/dist/algorithms/shortest-path.d.ts +2 -1
  13. package/dist/algorithms/shortest-path.d.ts.map +1 -1
  14. package/dist/algorithms/shortest-path.js +9 -7
  15. package/dist/algorithms/shortest-path.js.map +1 -1
  16. package/dist/binary-heap.d.ts +5 -0
  17. package/dist/binary-heap.d.ts.map +1 -1
  18. package/dist/binary-heap.js +11 -4
  19. package/dist/binary-heap.js.map +1 -1
  20. package/dist/graph.d.ts +35 -7
  21. package/dist/graph.d.ts.map +1 -1
  22. package/dist/graph.js +161 -119
  23. package/dist/graph.js.map +1 -1
  24. package/dist/index.d.ts +9 -4
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +9 -4
  27. package/dist/index.js.map +1 -1
  28. package/dist/router.d.ts +1 -1
  29. package/dist/router.d.ts.map +1 -1
  30. package/dist/router.js +6 -3
  31. package/dist/router.js.map +1 -1
  32. package/dist/topology-stats.d.ts +37 -0
  33. package/dist/topology-stats.d.ts.map +1 -0
  34. package/dist/topology-stats.js +62 -0
  35. package/dist/topology-stats.js.map +1 -0
  36. package/dist/types.d.ts +15 -2
  37. package/dist/types.d.ts.map +1 -1
  38. package/dist/utils.d.ts +1 -1
  39. package/dist/utils.d.ts.map +1 -1
  40. package/dist/utils.js +1 -7
  41. package/dist/utils.js.map +1 -1
  42. package/package.json +32 -29
  43. package/src/algorithms/bidirectional.ts +124 -136
  44. package/src/algorithms/index.ts +10 -10
  45. package/src/algorithms/shortest-path.ts +96 -92
  46. package/src/binary-heap.ts +133 -129
  47. package/src/graph.ts +389 -355
  48. package/src/index.ts +14 -9
  49. package/src/router.ts +221 -233
  50. package/src/topology-stats.ts +84 -0
  51. package/src/types.ts +102 -87
  52. package/src/utils.ts +67 -74
  53. package/test/algorithms.test.ts +396 -353
  54. package/test/binary-heap.test.ts +27 -0
  55. package/test/limits.test.ts +82 -0
  56. package/test/no-shared-array-buffer.test.ts +37 -0
  57. package/test/router.bench.ts +171 -188
  58. package/test/router.test.ts +685 -726
  59. package/test/spatial-capability.test.ts +23 -0
  60. package/test/topology-stats.test.ts +82 -0
  61. package/tsconfig.build.json +6 -6
  62. 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/shared/types"
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
- /** 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
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
- /** Total route distance in meters. */
26
- distance: number
27
- /** Total route time in seconds. */
28
- time: number
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
- /** Per-way breakdown (consecutive same-name ways merged). */
34
- segments: WaySegment[]
35
- /** Coordinates where way name changes (turn points). */
36
- turnPoints: LonLat[]
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
- /** 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[]
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
- /** 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
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
- /** 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
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
- /** 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
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
- /** 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
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
- graph: (nodeIndex: number) => GraphEdge[],
133
- startNodeIndex: number,
134
- endNodeIndex: number,
135
- getEdgeWeight: (edge: GraphEdge) => number,
136
- getNodeCoord?: (nodeIndex: number) => LonLat | undefined,
137
- metric?: RoutingMetric,
138
- ) => PathSegment[] | null
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/shared/types"
10
- import type { DefaultSpeeds } from "./types.ts"
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
- motorway: 120,
15
- motorway_link: 60,
16
- trunk: 100,
17
- trunk_link: 50,
18
- primary: 80,
19
- primary_link: 50,
20
- secondary: 70,
21
- secondary_link: 50,
22
- tertiary: 60,
23
- tertiary_link: 50,
24
- unclassified: 50,
25
- residential: 30,
26
- living_street: 20,
27
- service: 20,
28
- footway: 5,
29
- path: 5,
30
- cycleway: 20,
31
- bridleway: 10,
32
- steps: 2,
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
- "motorway",
38
- "motorway_link",
39
- "trunk",
40
- "trunk_link",
41
- "primary",
42
- "primary_link",
43
- "secondary",
44
- "secondary_link",
45
- "tertiary",
46
- "tertiary_link",
47
- "unclassified",
48
- "residential",
49
- "living_street",
50
- "service",
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
- value: string | number | undefined,
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
- const str = String(value).trim().toLowerCase()
61
+ const str = String(value).trim().toLowerCase();
69
62
 
70
- // Numeric: assume km/h
71
- const numeric = str.match(/^(\d+)$/)
72
- if (numeric) return Number.parseInt(numeric[1]!, 10)
63
+ // Numeric: assume km/h
64
+ const numeric = str.match(/^(\d+)$/);
65
+ if (numeric) return Number.parseInt(numeric[1]!, 10);
73
66
 
74
- // Explicit km/h
75
- const kmh = str.match(/^(\d+)\s*(km\/h|kmh|kph)$/)
76
- if (kmh) return Number.parseInt(kmh[1]!, 10)
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
- // MPH -> km/h
79
- const mph = str.match(/^(\d+)\s*mph$/)
80
- if (mph) return Math.round(Number.parseInt(mph[1]!, 10) * 1.60934)
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
- // Special values
83
- if (str === "walk" || str === "none") return 5
75
+ // Special values
76
+ if (str === "walk" || str === "none") return 5;
84
77
 
85
- return null
78
+ return null;
86
79
  }
87
80
 
88
81
  /** Get speed limit (km/h) for a way. */
89
82
  export function getSpeedLimit(
90
- tags: OsmTags | undefined,
91
- defaultSpeeds: DefaultSpeeds = DEFAULT_SPEEDS,
83
+ tags: OsmTags | undefined,
84
+ defaultSpeeds: DefaultSpeeds = DEFAULT_SPEEDS,
92
85
  ): number {
93
- if (tags?.["maxspeed"]) {
94
- const parsed = parseMaxSpeed(tags["maxspeed"])
95
- if (parsed !== null) return parsed
96
- }
86
+ if (tags?.["maxspeed"]) {
87
+ const parsed = parseMaxSpeed(tags["maxspeed"]);
88
+ if (parsed !== null) return parsed;
89
+ }
97
90
 
98
- const highway = tags?.["highway"] ? String(tags["highway"]) : undefined
99
- if (highway && defaultSpeeds[highway]) return defaultSpeeds[highway]!
91
+ const highway = tags?.["highway"] ? String(tags["highway"]) : undefined;
92
+ if (highway && defaultSpeeds[highway]) return defaultSpeeds[highway]!;
100
93
 
101
- return 50
94
+ return 50;
102
95
  }
103
96
 
104
97
  /** Default filter: common vehicle highways. */
105
98
  export function defaultHighwayFilter(tags?: OsmTags): boolean {
106
- if (!tags?.["highway"]) return false
107
- return VEHICLE_HIGHWAYS.has(String(tags["highway"]))
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
- if (!tags?.["highway"]) return false
113
- return FOOT_HIGHWAYS.has(String(tags["highway"]))
105
+ if (!tags?.["highway"]) return false;
106
+ return FOOT_HIGHWAYS.has(String(tags["highway"]));
114
107
  }