roadgraph-router-v2 0.1.0 → 0.3.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 +348 -1
- package/README.md +729 -18
- package/dist/chunk-QSIKDM4A.js +2690 -0
- package/dist/index.d.ts +966 -89
- package/dist/index.js +624 -82
- package/dist/worker.js +2 -2
- package/package.json +2 -2
- package/scripts/roadgraph-compile.mjs +307 -76
- package/dist/chunk-4FIOQAFG.js +0 -1333
package/dist/chunk-4FIOQAFG.js
DELETED
|
@@ -1,1333 +0,0 @@
|
|
|
1
|
-
// src/varint.ts
|
|
2
|
-
function zigzag(value) {
|
|
3
|
-
return (value << 1 ^ value >> 31) >>> 0;
|
|
4
|
-
}
|
|
5
|
-
function unzigzag(value) {
|
|
6
|
-
return value >>> 1 ^ -(value & 1);
|
|
7
|
-
}
|
|
8
|
-
var VarintWriter = class {
|
|
9
|
-
bytes;
|
|
10
|
-
length = 0;
|
|
11
|
-
constructor(capacity = 1024) {
|
|
12
|
-
this.bytes = new Uint8Array(capacity);
|
|
13
|
-
}
|
|
14
|
-
reserve(extra) {
|
|
15
|
-
if (this.length + extra <= this.bytes.length) return;
|
|
16
|
-
let capacity = this.bytes.length * 2;
|
|
17
|
-
while (capacity < this.length + extra) capacity *= 2;
|
|
18
|
-
const grown = new Uint8Array(capacity);
|
|
19
|
-
grown.set(this.bytes.subarray(0, this.length));
|
|
20
|
-
this.bytes = grown;
|
|
21
|
-
}
|
|
22
|
-
/** Appends an unsigned integer. Values must be below 2^32. */
|
|
23
|
-
push(value) {
|
|
24
|
-
this.reserve(5);
|
|
25
|
-
let remaining = value >>> 0;
|
|
26
|
-
while (remaining > 127) {
|
|
27
|
-
this.bytes[this.length++] = remaining & 127 | 128;
|
|
28
|
-
remaining >>>= 7;
|
|
29
|
-
}
|
|
30
|
-
this.bytes[this.length++] = remaining;
|
|
31
|
-
}
|
|
32
|
-
/** Appends a signed integer through zigzag, so small magnitudes stay short. */
|
|
33
|
-
pushSigned(value) {
|
|
34
|
-
this.push(zigzag(value));
|
|
35
|
-
}
|
|
36
|
-
get byteLength() {
|
|
37
|
-
return this.length;
|
|
38
|
-
}
|
|
39
|
-
toUint8Array() {
|
|
40
|
-
return this.bytes.subarray(0, this.length);
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
var VarintReader = class {
|
|
44
|
-
constructor(bytes, start = 0) {
|
|
45
|
-
this.bytes = bytes;
|
|
46
|
-
this.at = start;
|
|
47
|
-
}
|
|
48
|
-
bytes;
|
|
49
|
-
at;
|
|
50
|
-
read() {
|
|
51
|
-
let value = 0;
|
|
52
|
-
let shift = 0;
|
|
53
|
-
for (; ; ) {
|
|
54
|
-
const byte = this.bytes[this.at++];
|
|
55
|
-
value += shift < 28 ? (byte & 127) << shift : (byte & 127) * 2 ** shift;
|
|
56
|
-
if ((byte & 128) === 0) return value >>> 0;
|
|
57
|
-
shift += 7;
|
|
58
|
-
if (shift > 35) throw new Error("Malformed varint in routing graph.");
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
readSigned() {
|
|
62
|
-
return unzigzag(this.read());
|
|
63
|
-
}
|
|
64
|
-
get offset() {
|
|
65
|
-
return this.at;
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
// src/codec.ts
|
|
70
|
-
var MAGIC = 1162236500;
|
|
71
|
-
var VERSION = 1;
|
|
72
|
-
var HEADER_BYTES = 16;
|
|
73
|
-
var COMPACT_MAGIC = 844252741;
|
|
74
|
-
var COMPACT_VERSION = 2;
|
|
75
|
-
var SHAPED_COMPACT_VERSION = 3;
|
|
76
|
-
var RESTRICTED_COMPACT_VERSION = 4;
|
|
77
|
-
var SEQUENCED_COMPACT_VERSION = 5;
|
|
78
|
-
var COMPACT_HEADER_BYTES = 32;
|
|
79
|
-
var SHAPED_COMPACT_HEADER_BYTES = 40;
|
|
80
|
-
var SUPPORTED_COMPACT_VERSIONS = /* @__PURE__ */ new Set([COMPACT_VERSION, SHAPED_COMPACT_VERSION, RESTRICTED_COMPACT_VERSION, SEQUENCED_COMPACT_VERSION, 6]);
|
|
81
|
-
function encodeGraph(graph) {
|
|
82
|
-
const nodes = graph.nodeLatitudes.length, edges = graph.edgeTargets.length;
|
|
83
|
-
if (graph.nodeLongitudes.length !== nodes || graph.edgeOffsets.length !== nodes + 1 || graph.edgeDistancesMeters.length !== edges || graph.edgeDurationsSeconds.length !== edges || graph.edgeOffsets[nodes] !== edges) throw new Error("Invalid road graph");
|
|
84
|
-
const buffer = new ArrayBuffer(HEADER_BYTES + nodes * 16 + (nodes + 1) * 4 + edges * 12);
|
|
85
|
-
const view = new DataView(buffer);
|
|
86
|
-
view.setUint32(0, MAGIC, true);
|
|
87
|
-
view.setUint16(4, VERSION, true);
|
|
88
|
-
view.setUint32(8, nodes, true);
|
|
89
|
-
view.setUint32(12, edges, true);
|
|
90
|
-
const coordinateScale = graph.coordinateScale ?? 1;
|
|
91
|
-
const distanceScale = graph.edgeDistanceScale ?? 1;
|
|
92
|
-
const durationScale = graph.edgeDurationScale ?? 1;
|
|
93
|
-
let offset = HEADER_BYTES;
|
|
94
|
-
new Float64Array(buffer, offset, nodes).set(Float64Array.from(graph.nodeLatitudes, (value) => value / coordinateScale));
|
|
95
|
-
offset += nodes * 8;
|
|
96
|
-
new Float64Array(buffer, offset, nodes).set(Float64Array.from(graph.nodeLongitudes, (value) => value / coordinateScale));
|
|
97
|
-
offset += nodes * 8;
|
|
98
|
-
new Uint32Array(buffer, offset, nodes + 1).set(graph.edgeOffsets);
|
|
99
|
-
offset += (nodes + 1) * 4;
|
|
100
|
-
new Uint32Array(buffer, offset, edges).set(graph.edgeTargets);
|
|
101
|
-
offset += edges * 4;
|
|
102
|
-
new Float32Array(buffer, offset, edges).set(Float32Array.from(graph.edgeDistancesMeters, (value) => value * distanceScale));
|
|
103
|
-
offset += edges * 4;
|
|
104
|
-
new Float32Array(buffer, offset, edges).set(Float32Array.from(graph.edgeDurationsSeconds, (value) => value * durationScale));
|
|
105
|
-
return buffer;
|
|
106
|
-
}
|
|
107
|
-
function decodeGraph(buffer) {
|
|
108
|
-
if (buffer.byteLength < HEADER_BYTES) throw new Error("Invalid routing graph header");
|
|
109
|
-
const view = new DataView(buffer), magic = view.getUint32(0, true), version = view.getUint16(4, true);
|
|
110
|
-
if (magic === COMPACT_MAGIC && SUPPORTED_COMPACT_VERSIONS.has(version)) return decodeCompactGraph(buffer);
|
|
111
|
-
if (magic !== MAGIC || version !== VERSION) throw new Error("Unsupported routing graph format");
|
|
112
|
-
const nodes = view.getUint32(8, true), edges = view.getUint32(12, true), expected = HEADER_BYTES + nodes * 16 + (nodes + 1) * 4 + edges * 12;
|
|
113
|
-
if (buffer.byteLength !== expected) throw new Error("Truncated routing graph");
|
|
114
|
-
let offset = HEADER_BYTES;
|
|
115
|
-
const nodeLatitudes = new Float64Array(buffer.slice(offset, offset += nodes * 8));
|
|
116
|
-
const nodeLongitudes = new Float64Array(buffer.slice(offset, offset += nodes * 8));
|
|
117
|
-
const edgeOffsets = new Uint32Array(buffer.slice(offset, offset += (nodes + 1) * 4));
|
|
118
|
-
const edgeTargets = new Uint32Array(buffer.slice(offset, offset += edges * 4));
|
|
119
|
-
const edgeDistancesMeters = new Float32Array(buffer.slice(offset, offset += edges * 4));
|
|
120
|
-
const edgeDurationsSeconds = new Float32Array(buffer.slice(offset, offset += edges * 4));
|
|
121
|
-
assertGeographic(nodeLatitudes, nodeLongitudes, 1);
|
|
122
|
-
for (const target of edgeTargets) if (target >= nodes) throw new Error("Invalid routing graph: edge target outside the graph.");
|
|
123
|
-
assertOffsets(edgeOffsets, nodes, edges, "edge offsets");
|
|
124
|
-
return { nodeLatitudes, nodeLongitudes, edgeOffsets, edgeTargets, edgeDistancesMeters, edgeDurationsSeconds };
|
|
125
|
-
}
|
|
126
|
-
function encodeCompactGraph(graph, options = {}) {
|
|
127
|
-
return (options.layout ?? "packed") === "packed" ? encodePackedGraph(graph) : encodeLegacyCompactGraph(graph);
|
|
128
|
-
}
|
|
129
|
-
function encodeLegacyCompactGraph(graph) {
|
|
130
|
-
if (!(graph.nodeLatitudes instanceof Int32Array) || !(graph.nodeLongitudes instanceof Int32Array) || !(graph.edgeDistancesMeters instanceof Uint16Array) || !(graph.edgeDurationsSeconds instanceof Uint16Array)) throw new Error("Compact graphs require Int32 coordinates and Uint16 edge weights.");
|
|
131
|
-
const coordinateScale = graph.coordinateScale ?? 0, edgeDistanceScale = graph.edgeDistanceScale ?? 1, edgeDurationScale = graph.edgeDurationScale ?? 1;
|
|
132
|
-
if (!Number.isInteger(coordinateScale) || !Number.isInteger(edgeDistanceScale) || !Number.isInteger(edgeDurationScale) || coordinateScale <= 0 || edgeDistanceScale <= 0 || edgeDurationScale <= 0 || edgeDistanceScale > 65535 || edgeDurationScale > 65535) throw new Error("Invalid compact graph scales.");
|
|
133
|
-
const nodes = graph.nodeLatitudes.length, edges = graph.edgeTargets.length;
|
|
134
|
-
if (graph.nodeLongitudes.length !== nodes || graph.edgeOffsets.length !== nodes + 1 || graph.edgeDistancesMeters.length !== edges || graph.edgeDurationsSeconds.length !== edges || graph.edgeOffsets[nodes] !== edges) throw new Error("Invalid road graph");
|
|
135
|
-
const sequenceEdges = graph.turnRestrictionSequenceEdges?.length ?? 0;
|
|
136
|
-
const sequences = sequenceEdges ? (graph.turnRestrictionSequenceOffsets?.length ?? 1) - 1 : 0;
|
|
137
|
-
if (sequences && (!graph.turnRestrictionSequenceOffsets || graph.turnRestrictionSequenceOffsets[sequences] !== sequenceEdges)) throw new Error("Invalid compact turn restriction sequences.");
|
|
138
|
-
for (let index = 0; index < sequenceEdges; index += 1) if (graph.turnRestrictionSequenceEdges[index] >= edges) throw new Error("Turn restriction references an edge outside the graph.");
|
|
139
|
-
const restricted = Boolean(graph.turnRestrictionFromEdges?.length || graph.turnRestrictionToEdges?.length) || sequences > 0;
|
|
140
|
-
if ((graph.turnRestrictionFromEdges?.length ?? 0) !== (graph.turnRestrictionToEdges?.length ?? 0)) throw new Error("Invalid compact turn restrictions.");
|
|
141
|
-
const restrictions = graph.turnRestrictionFromEdges?.length ?? 0;
|
|
142
|
-
for (let pair = 0; pair < restrictions; pair += 1) if (graph.turnRestrictionFromEdges[pair] >= edges || graph.turnRestrictionToEdges[pair] >= edges) throw new Error("Turn restriction references an edge outside the graph.");
|
|
143
|
-
const hasShapes = Boolean(graph.edgeShapeOffsets || graph.edgeShapeLatitudes || graph.edgeShapeLongitudes);
|
|
144
|
-
if (hasShapes && (!graph.edgeShapeOffsets || !graph.edgeShapeLatitudes || !graph.edgeShapeLongitudes || graph.edgeShapeOffsets.length !== edges + 1 || graph.edgeShapeLatitudes.length !== graph.edgeShapeLongitudes.length || graph.edgeShapeOffsets[edges] !== graph.edgeShapeLatitudes.length)) throw new Error("Invalid compact edge geometry.");
|
|
145
|
-
const shaped = hasShapes || restricted;
|
|
146
|
-
const version = sequences ? SEQUENCED_COMPACT_VERSION : restricted ? RESTRICTED_COMPACT_VERSION : hasShapes ? SHAPED_COMPACT_VERSION : COMPACT_VERSION;
|
|
147
|
-
const points = graph.edgeShapeLatitudes?.length ?? 0, header = shaped ? SHAPED_COMPACT_HEADER_BYTES : COMPACT_HEADER_BYTES;
|
|
148
|
-
const buffer = new ArrayBuffer(header + nodes * 8 + (nodes + 1) * 4 + edges * 8 + (shaped ? (edges + 1) * 4 + points * 8 : 0) + restrictions * 8 + (sequences ? (sequences + 1) * 4 + sequenceEdges * 4 : 0));
|
|
149
|
-
const view = new DataView(buffer);
|
|
150
|
-
view.setUint32(0, COMPACT_MAGIC, true);
|
|
151
|
-
view.setUint16(4, version, true);
|
|
152
|
-
view.setUint32(8, nodes, true);
|
|
153
|
-
view.setUint32(12, edges, true);
|
|
154
|
-
view.setUint32(16, coordinateScale, true);
|
|
155
|
-
view.setUint16(20, edgeDistanceScale, true);
|
|
156
|
-
view.setUint16(22, edgeDurationScale, true);
|
|
157
|
-
if (shaped) {
|
|
158
|
-
view.setUint32(24, points, true);
|
|
159
|
-
view.setUint32(28, restrictions, true);
|
|
160
|
-
view.setUint32(32, sequences, true);
|
|
161
|
-
view.setUint32(36, sequenceEdges, true);
|
|
162
|
-
}
|
|
163
|
-
let offset = header;
|
|
164
|
-
new Int32Array(buffer, offset, nodes).set(graph.nodeLatitudes);
|
|
165
|
-
offset += nodes * 4;
|
|
166
|
-
new Int32Array(buffer, offset, nodes).set(graph.nodeLongitudes);
|
|
167
|
-
offset += nodes * 4;
|
|
168
|
-
new Uint32Array(buffer, offset, nodes + 1).set(graph.edgeOffsets);
|
|
169
|
-
offset += (nodes + 1) * 4;
|
|
170
|
-
new Uint32Array(buffer, offset, edges).set(graph.edgeTargets);
|
|
171
|
-
offset += edges * 4;
|
|
172
|
-
new Uint16Array(buffer, offset, edges).set(graph.edgeDistancesMeters);
|
|
173
|
-
offset += edges * 2;
|
|
174
|
-
new Uint16Array(buffer, offset, edges).set(graph.edgeDurationsSeconds);
|
|
175
|
-
offset += edges * 2;
|
|
176
|
-
if (shaped) {
|
|
177
|
-
if (hasShapes) new Uint32Array(buffer, offset, edges + 1).set(graph.edgeShapeOffsets);
|
|
178
|
-
offset += (edges + 1) * 4;
|
|
179
|
-
new Int32Array(buffer, offset, points).set(graph.edgeShapeLatitudes ?? []);
|
|
180
|
-
offset += points * 4;
|
|
181
|
-
new Int32Array(buffer, offset, points).set(graph.edgeShapeLongitudes ?? []);
|
|
182
|
-
offset += points * 4;
|
|
183
|
-
}
|
|
184
|
-
if (restrictions) {
|
|
185
|
-
new Uint32Array(buffer, offset, restrictions).set(graph.turnRestrictionFromEdges);
|
|
186
|
-
offset += restrictions * 4;
|
|
187
|
-
new Uint32Array(buffer, offset, restrictions).set(graph.turnRestrictionToEdges);
|
|
188
|
-
offset += restrictions * 4;
|
|
189
|
-
}
|
|
190
|
-
if (sequences) {
|
|
191
|
-
new Uint32Array(buffer, offset, sequences + 1).set(graph.turnRestrictionSequenceOffsets);
|
|
192
|
-
offset += (sequences + 1) * 4;
|
|
193
|
-
new Uint32Array(buffer, offset, sequenceEdges).set(graph.turnRestrictionSequenceEdges);
|
|
194
|
-
}
|
|
195
|
-
return buffer;
|
|
196
|
-
}
|
|
197
|
-
function decodeCompactGraph(buffer) {
|
|
198
|
-
if (buffer.byteLength < COMPACT_HEADER_BYTES) throw new Error("Invalid compact routing graph header");
|
|
199
|
-
const view = new DataView(buffer), version = view.getUint16(4, true), nodes = view.getUint32(8, true), edges = view.getUint32(12, true), coordinateScale = view.getUint32(16, true), edgeDistanceScale = view.getUint16(20, true), edgeDurationScale = view.getUint16(22, true);
|
|
200
|
-
if (version === 6) return decodePackedGraph(buffer);
|
|
201
|
-
const shaped = version >= SHAPED_COMPACT_VERSION, points = shaped ? view.getUint32(24, true) : 0, header = shaped ? SHAPED_COMPACT_HEADER_BYTES : COMPACT_HEADER_BYTES;
|
|
202
|
-
const restrictions = version >= RESTRICTED_COMPACT_VERSION ? view.getUint32(28, true) : 0;
|
|
203
|
-
const sequences = version >= SEQUENCED_COMPACT_VERSION ? view.getUint32(32, true) : 0;
|
|
204
|
-
const sequenceEdges = version >= SEQUENCED_COMPACT_VERSION ? view.getUint32(36, true) : 0;
|
|
205
|
-
const expected = header + nodes * 8 + (nodes + 1) * 4 + edges * 8 + (shaped ? (edges + 1) * 4 + points * 8 : 0) + restrictions * 8 + (sequences ? (sequences + 1) * 4 + sequenceEdges * 4 : 0);
|
|
206
|
-
if (!coordinateScale || !edgeDistanceScale || !edgeDurationScale || buffer.byteLength !== expected) throw new Error("Truncated compact routing graph");
|
|
207
|
-
let offset = header;
|
|
208
|
-
const nodeLatitudes = new Int32Array(buffer.slice(offset, offset += nodes * 4));
|
|
209
|
-
const nodeLongitudes = new Int32Array(buffer.slice(offset, offset += nodes * 4));
|
|
210
|
-
const edgeOffsets = new Uint32Array(buffer.slice(offset, offset += (nodes + 1) * 4));
|
|
211
|
-
const edgeTargets = new Uint32Array(buffer.slice(offset, offset += edges * 4));
|
|
212
|
-
const edgeDistancesMeters = new Uint16Array(buffer.slice(offset, offset += edges * 2));
|
|
213
|
-
const edgeDurationsSeconds = new Uint16Array(buffer.slice(offset, offset += edges * 2));
|
|
214
|
-
assertGeographic(nodeLatitudes, nodeLongitudes, coordinateScale);
|
|
215
|
-
for (const target of edgeTargets) if (target >= nodes) throw new Error("Invalid routing graph: edge target outside the graph.");
|
|
216
|
-
assertOffsets(edgeOffsets, nodes, edges, "edge offsets");
|
|
217
|
-
const graph = { nodeLatitudes, nodeLongitudes, edgeOffsets, edgeTargets, edgeDistancesMeters, edgeDurationsSeconds, coordinateScale, edgeDistanceScale, edgeDurationScale };
|
|
218
|
-
if (!shaped) return graph;
|
|
219
|
-
const edgeShapeOffsets = new Uint32Array(buffer.slice(offset, offset += (edges + 1) * 4));
|
|
220
|
-
const edgeShapeLatitudes = new Int32Array(buffer.slice(offset, offset += points * 4));
|
|
221
|
-
const edgeShapeLongitudes = new Int32Array(buffer.slice(offset, offset += points * 4));
|
|
222
|
-
if (points) {
|
|
223
|
-
assertOffsets(edgeShapeOffsets, edges, points, "shape offsets");
|
|
224
|
-
assertGeographic(edgeShapeLatitudes, edgeShapeLongitudes, coordinateScale);
|
|
225
|
-
Object.assign(graph, { edgeShapeOffsets, edgeShapeLatitudes, edgeShapeLongitudes });
|
|
226
|
-
}
|
|
227
|
-
if (restrictions) {
|
|
228
|
-
graph.turnRestrictionFromEdges = new Uint32Array(buffer.slice(offset, offset += restrictions * 4));
|
|
229
|
-
graph.turnRestrictionToEdges = new Uint32Array(buffer.slice(offset, offset += restrictions * 4));
|
|
230
|
-
}
|
|
231
|
-
if (sequences) {
|
|
232
|
-
graph.turnRestrictionSequenceOffsets = new Uint32Array(buffer.slice(offset, offset += (sequences + 1) * 4));
|
|
233
|
-
graph.turnRestrictionSequenceEdges = new Uint32Array(buffer.slice(offset, offset += sequenceEdges * 4));
|
|
234
|
-
assertOffsets(graph.turnRestrictionSequenceOffsets, sequences, sequenceEdges, "turn restriction offsets");
|
|
235
|
-
}
|
|
236
|
-
for (const edge of [...graph.turnRestrictionFromEdges ?? [], ...graph.turnRestrictionToEdges ?? [], ...graph.turnRestrictionSequenceEdges ?? []]) {
|
|
237
|
-
if (edge >= edges) throw new Error("Invalid routing graph: turn restriction references an edge outside the graph.");
|
|
238
|
-
}
|
|
239
|
-
return graph;
|
|
240
|
-
}
|
|
241
|
-
var PACKED_COMPACT_VERSION = 6;
|
|
242
|
-
var PACKED_HEADER_BYTES = 64;
|
|
243
|
-
function alignUp(offset) {
|
|
244
|
-
return offset + 3 & ~3;
|
|
245
|
-
}
|
|
246
|
-
function assertOffsets(offsets, entries, total, what) {
|
|
247
|
-
if (offsets.length !== entries + 1 || offsets[0] !== 0 || offsets[entries] !== total) throw new Error(`Invalid routing graph: ${what} do not span the data.`);
|
|
248
|
-
for (let index = 1; index <= entries; index += 1) {
|
|
249
|
-
if (offsets[index] < offsets[index - 1] || offsets[index] > total) throw new Error(`Invalid routing graph: ${what} are not ordered.`);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
function assertGeographic(latitudes, longitudes, scale) {
|
|
253
|
-
for (let index = 0; index < latitudes.length; index += 1) {
|
|
254
|
-
if (!(Math.abs(latitudes[index] / scale) <= 90) || !(Math.abs(longitudes[index] / scale) <= 180)) {
|
|
255
|
-
throw new Error("Invalid routing graph: coordinate outside the world.");
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
function buildShapeTable(graph) {
|
|
260
|
-
const edges = graph.edgeTargets.length;
|
|
261
|
-
const offsets = graph.edgeShapeOffsets;
|
|
262
|
-
const latitudes = graph.edgeShapeLatitudes;
|
|
263
|
-
const longitudes = graph.edgeShapeLongitudes;
|
|
264
|
-
const references = new Uint32Array(edges);
|
|
265
|
-
const shapes = [];
|
|
266
|
-
if (!offsets || !latitudes || !longitudes) return { references, shapes, latitudes, longitudes };
|
|
267
|
-
const seen = /* @__PURE__ */ new Map();
|
|
268
|
-
for (let edge = 0; edge < edges; edge += 1) {
|
|
269
|
-
const start = offsets[edge];
|
|
270
|
-
const count = offsets[edge + 1] - start;
|
|
271
|
-
if (!count) continue;
|
|
272
|
-
const forward = [];
|
|
273
|
-
const backward = [];
|
|
274
|
-
for (let point = 0; point < count; point += 1) {
|
|
275
|
-
forward.push(latitudes[start + point], longitudes[start + point]);
|
|
276
|
-
backward.push(latitudes[start + count - 1 - point], longitudes[start + count - 1 - point]);
|
|
277
|
-
}
|
|
278
|
-
const forwardKey = forward.join(",");
|
|
279
|
-
const existing = seen.get(forwardKey);
|
|
280
|
-
if (existing !== void 0) {
|
|
281
|
-
references[edge] = (existing + 1) * 2;
|
|
282
|
-
continue;
|
|
283
|
-
}
|
|
284
|
-
const mirrored = seen.get(backward.join(","));
|
|
285
|
-
if (mirrored !== void 0) {
|
|
286
|
-
references[edge] = (mirrored + 1) * 2 + 1;
|
|
287
|
-
continue;
|
|
288
|
-
}
|
|
289
|
-
const id = shapes.length;
|
|
290
|
-
shapes.push({ start, count });
|
|
291
|
-
seen.set(forwardKey, id);
|
|
292
|
-
references[edge] = (id + 1) * 2;
|
|
293
|
-
}
|
|
294
|
-
return { references, shapes, latitudes, longitudes };
|
|
295
|
-
}
|
|
296
|
-
function encodePackedGraph(graph) {
|
|
297
|
-
const nodes = graph.nodeLatitudes.length;
|
|
298
|
-
const edges = graph.edgeTargets.length;
|
|
299
|
-
if (!(graph.nodeLatitudes instanceof Int32Array) || !(graph.nodeLongitudes instanceof Int32Array) || !(graph.edgeDistancesMeters instanceof Uint16Array) || !(graph.edgeDurationsSeconds instanceof Uint16Array)) throw new Error("Compact graphs require Int32 coordinates and Uint16 edge weights.");
|
|
300
|
-
if (graph.nodeLongitudes.length !== nodes || graph.edgeOffsets.length !== nodes + 1 || graph.edgeDistancesMeters.length !== edges || graph.edgeDurationsSeconds.length !== edges || graph.edgeOffsets[nodes] !== edges) throw new Error("Invalid road graph");
|
|
301
|
-
const coordinateScale = graph.coordinateScale ?? 0;
|
|
302
|
-
if (!Number.isInteger(coordinateScale) || coordinateScale <= 0) throw new Error("Invalid compact graph scales.");
|
|
303
|
-
if ((graph.turnRestrictionFromEdges?.length ?? 0) !== (graph.turnRestrictionToEdges?.length ?? 0)) throw new Error("Invalid compact turn restrictions.");
|
|
304
|
-
for (const edge of graph.turnRestrictionFromEdges ?? []) if (edge >= edges) throw new Error("Turn restriction references an edge outside the graph.");
|
|
305
|
-
for (const edge of graph.turnRestrictionToEdges ?? []) if (edge >= edges) throw new Error("Turn restriction references an edge outside the graph.");
|
|
306
|
-
for (const edge of graph.turnRestrictionSequenceEdges ?? []) if (edge >= edges) throw new Error("Turn restriction references an edge outside the graph.");
|
|
307
|
-
const { references, shapes, latitudes, longitudes } = buildShapeTable(graph);
|
|
308
|
-
const degrees = new VarintWriter(nodes + 16);
|
|
309
|
-
for (let node = 0; node < nodes; node += 1) degrees.push(graph.edgeOffsets[node + 1] - graph.edgeOffsets[node]);
|
|
310
|
-
const targets = new VarintWriter(edges + 16);
|
|
311
|
-
for (let node = 0; node < nodes; node += 1) {
|
|
312
|
-
for (let edge = graph.edgeOffsets[node]; edge < graph.edgeOffsets[node + 1]; edge += 1) targets.pushSigned(graph.edgeTargets[edge] - node);
|
|
313
|
-
}
|
|
314
|
-
const shapeRefs = new VarintWriter(edges + 16);
|
|
315
|
-
for (let edge = 0; edge < edges; edge += 1) shapeRefs.push(references[edge]);
|
|
316
|
-
const shapeSizes = new VarintWriter(shapes.length + 16);
|
|
317
|
-
for (const shape of shapes) shapeSizes.push(shape.count);
|
|
318
|
-
const shapePoints = new VarintWriter(shapes.length * 8 + 16);
|
|
319
|
-
for (const shape of shapes) {
|
|
320
|
-
let previousLat = 0;
|
|
321
|
-
let previousLng = 0;
|
|
322
|
-
for (let point = 0; point < shape.count; point += 1) {
|
|
323
|
-
const lat = latitudes[shape.start + point];
|
|
324
|
-
const lng = longitudes[shape.start + point];
|
|
325
|
-
shapePoints.pushSigned(point ? lat - previousLat : lat);
|
|
326
|
-
shapePoints.pushSigned(point ? lng - previousLng : lng);
|
|
327
|
-
previousLat = lat;
|
|
328
|
-
previousLng = lng;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
const pairs = graph.turnRestrictionFromEdges?.length ?? 0;
|
|
332
|
-
const runs = graph.turnRestrictionSequenceEdges?.length ? (graph.turnRestrictionSequenceOffsets?.length ?? 1) - 1 : 0;
|
|
333
|
-
const runEdges = graph.turnRestrictionSequenceEdges?.length ?? 0;
|
|
334
|
-
const sections = [degrees, targets, shapeRefs, shapeSizes, shapePoints];
|
|
335
|
-
const variableBytes = sections.reduce((sum, section) => sum + section.byteLength, 0);
|
|
336
|
-
const restrictionsAt = alignUp(PACKED_HEADER_BYTES + nodes * 8 + edges * 4 + variableBytes);
|
|
337
|
-
const buffer = new ArrayBuffer(restrictionsAt + pairs * 8 + (runs ? (runs + 1) * 4 + runEdges * 4 : 0));
|
|
338
|
-
const view = new DataView(buffer);
|
|
339
|
-
view.setUint32(0, COMPACT_MAGIC, true);
|
|
340
|
-
view.setUint16(4, PACKED_COMPACT_VERSION, true);
|
|
341
|
-
view.setUint32(8, nodes, true);
|
|
342
|
-
view.setUint32(12, edges, true);
|
|
343
|
-
view.setUint32(16, graph.coordinateScale ?? 0, true);
|
|
344
|
-
view.setUint16(20, graph.edgeDistanceScale ?? 1, true);
|
|
345
|
-
view.setUint16(22, graph.edgeDurationScale ?? 1, true);
|
|
346
|
-
view.setUint32(24, shapes.length, true);
|
|
347
|
-
view.setUint32(28, pairs, true);
|
|
348
|
-
view.setUint32(32, runs, true);
|
|
349
|
-
view.setUint32(36, runEdges, true);
|
|
350
|
-
for (let index = 0; index < sections.length; index += 1) view.setUint32(40 + index * 4, sections[index].byteLength, true);
|
|
351
|
-
let offset = PACKED_HEADER_BYTES;
|
|
352
|
-
new Int32Array(buffer, offset, nodes).set(graph.nodeLatitudes);
|
|
353
|
-
offset += nodes * 4;
|
|
354
|
-
new Int32Array(buffer, offset, nodes).set(graph.nodeLongitudes);
|
|
355
|
-
offset += nodes * 4;
|
|
356
|
-
new Uint16Array(buffer, offset, edges).set(graph.edgeDistancesMeters);
|
|
357
|
-
offset += edges * 2;
|
|
358
|
-
new Uint16Array(buffer, offset, edges).set(graph.edgeDurationsSeconds);
|
|
359
|
-
offset += edges * 2;
|
|
360
|
-
const bytes = new Uint8Array(buffer);
|
|
361
|
-
for (const section of sections) {
|
|
362
|
-
bytes.set(section.toUint8Array(), offset);
|
|
363
|
-
offset += section.byteLength;
|
|
364
|
-
}
|
|
365
|
-
offset = restrictionsAt;
|
|
366
|
-
if (pairs) {
|
|
367
|
-
new Uint32Array(buffer, offset, pairs).set(graph.turnRestrictionFromEdges);
|
|
368
|
-
offset += pairs * 4;
|
|
369
|
-
new Uint32Array(buffer, offset, pairs).set(graph.turnRestrictionToEdges);
|
|
370
|
-
offset += pairs * 4;
|
|
371
|
-
}
|
|
372
|
-
if (runs) {
|
|
373
|
-
new Uint32Array(buffer, offset, runs + 1).set(graph.turnRestrictionSequenceOffsets);
|
|
374
|
-
offset += (runs + 1) * 4;
|
|
375
|
-
new Uint32Array(buffer, offset, runEdges).set(graph.turnRestrictionSequenceEdges);
|
|
376
|
-
}
|
|
377
|
-
return buffer;
|
|
378
|
-
}
|
|
379
|
-
function decodePackedGraph(buffer) {
|
|
380
|
-
const view = new DataView(buffer);
|
|
381
|
-
const nodes = view.getUint32(8, true);
|
|
382
|
-
const edges = view.getUint32(12, true);
|
|
383
|
-
const coordinateScale = view.getUint32(16, true);
|
|
384
|
-
const edgeDistanceScale = view.getUint16(20, true);
|
|
385
|
-
const edgeDurationScale = view.getUint16(22, true);
|
|
386
|
-
const shapeCount = view.getUint32(24, true);
|
|
387
|
-
const pairs = view.getUint32(28, true);
|
|
388
|
-
const runs = view.getUint32(32, true);
|
|
389
|
-
const runEdges = view.getUint32(36, true);
|
|
390
|
-
const lengths = Array.from({ length: 5 }, (_, index) => view.getUint32(40 + index * 4, true));
|
|
391
|
-
const variableBytes = lengths.reduce((sum, length) => sum + length, 0);
|
|
392
|
-
const restrictionsAt = alignUp(PACKED_HEADER_BYTES + nodes * 8 + edges * 4 + variableBytes);
|
|
393
|
-
const expected = restrictionsAt + pairs * 8 + (runs ? (runs + 1) * 4 + runEdges * 4 : 0);
|
|
394
|
-
if (!coordinateScale || !edgeDistanceScale || !edgeDurationScale || buffer.byteLength !== expected) throw new Error("Truncated compact routing graph");
|
|
395
|
-
if (shapeCount > edges) throw new Error("Invalid compact routing graph: more shapes than edges.");
|
|
396
|
-
let offset = PACKED_HEADER_BYTES;
|
|
397
|
-
const nodeLatitudes = new Int32Array(buffer.slice(offset, offset += nodes * 4));
|
|
398
|
-
const nodeLongitudes = new Int32Array(buffer.slice(offset, offset += nodes * 4));
|
|
399
|
-
const edgeDistancesMeters = new Uint16Array(buffer.slice(offset, offset += edges * 2));
|
|
400
|
-
const edgeDurationsSeconds = new Uint16Array(buffer.slice(offset, offset += edges * 2));
|
|
401
|
-
const bytes = new Uint8Array(buffer);
|
|
402
|
-
const starts = [];
|
|
403
|
-
for (const length of lengths) {
|
|
404
|
-
starts.push(offset);
|
|
405
|
-
offset += length;
|
|
406
|
-
}
|
|
407
|
-
offset = restrictionsAt;
|
|
408
|
-
const edgeOffsets = new Uint32Array(nodes + 1);
|
|
409
|
-
const degrees = new VarintReader(bytes, starts[0]);
|
|
410
|
-
for (let node = 0; node < nodes; node += 1) {
|
|
411
|
-
const degree = degrees.read();
|
|
412
|
-
if (degree > edges - edgeOffsets[node]) throw new Error("Invalid compact routing graph: node degrees exceed the edge count.");
|
|
413
|
-
edgeOffsets[node + 1] = edgeOffsets[node] + degree;
|
|
414
|
-
}
|
|
415
|
-
if (edgeOffsets[nodes] !== edges) throw new Error("Invalid compact routing graph: node degrees do not sum to the edge count.");
|
|
416
|
-
const edgeTargets = new Uint32Array(edges);
|
|
417
|
-
const targets = new VarintReader(bytes, starts[1]);
|
|
418
|
-
for (let node = 0; node < nodes; node += 1) {
|
|
419
|
-
for (let edge = edgeOffsets[node]; edge < edgeOffsets[node + 1]; edge += 1) {
|
|
420
|
-
const target = node + targets.readSigned();
|
|
421
|
-
if (target < 0 || target >= nodes) throw new Error("Invalid compact routing graph: edge target outside the graph.");
|
|
422
|
-
edgeTargets[edge] = target;
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
assertGeographic(nodeLatitudes, nodeLongitudes, coordinateScale);
|
|
426
|
-
const graph = { nodeLatitudes, nodeLongitudes, edgeOffsets, edgeTargets, edgeDistancesMeters, edgeDurationsSeconds, coordinateScale, edgeDistanceScale, edgeDurationScale };
|
|
427
|
-
const references = new Uint32Array(edges);
|
|
428
|
-
const shapeRefs = new VarintReader(bytes, starts[2]);
|
|
429
|
-
for (let edge = 0; edge < edges; edge += 1) {
|
|
430
|
-
references[edge] = shapeRefs.read();
|
|
431
|
-
if (references[edge] >>> 1 > shapeCount) throw new Error("Invalid compact routing graph: edge references a missing shape.");
|
|
432
|
-
}
|
|
433
|
-
const shapeSizes = new VarintReader(bytes, starts[3]);
|
|
434
|
-
const sizes = new Uint32Array(shapeCount);
|
|
435
|
-
const pointCeiling = Math.floor(lengths[4] / 2);
|
|
436
|
-
let totalPoints = 0;
|
|
437
|
-
for (let shape = 0; shape < shapeCount; shape += 1) {
|
|
438
|
-
sizes[shape] = shapeSizes.read();
|
|
439
|
-
totalPoints += sizes[shape];
|
|
440
|
-
if (totalPoints > pointCeiling) throw new Error("Invalid compact routing graph: shape sizes exceed the stored geometry.");
|
|
441
|
-
}
|
|
442
|
-
const tableLatitudes = new Int32Array(totalPoints);
|
|
443
|
-
const tableLongitudes = new Int32Array(totalPoints);
|
|
444
|
-
const tableStarts = new Uint32Array(shapeCount + 1);
|
|
445
|
-
const points = new VarintReader(bytes, starts[4]);
|
|
446
|
-
let at = 0;
|
|
447
|
-
for (let shape = 0; shape < shapeCount; shape += 1) {
|
|
448
|
-
tableStarts[shape] = at;
|
|
449
|
-
let lat = 0;
|
|
450
|
-
let lng = 0;
|
|
451
|
-
for (let point = 0; point < sizes[shape]; point += 1) {
|
|
452
|
-
lat = point ? lat + points.readSigned() : points.readSigned();
|
|
453
|
-
lng = point ? lng + points.readSigned() : points.readSigned();
|
|
454
|
-
tableLatitudes[at] = lat;
|
|
455
|
-
tableLongitudes[at] = lng;
|
|
456
|
-
at += 1;
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
tableStarts[shapeCount] = at;
|
|
460
|
-
const edgeShapeOffsets = new Uint32Array(edges + 1);
|
|
461
|
-
for (let edge = 0; edge < edges; edge += 1) {
|
|
462
|
-
const reference = references[edge];
|
|
463
|
-
edgeShapeOffsets[edge + 1] = edgeShapeOffsets[edge] + (reference ? sizes[(reference >>> 1) - 1] : 0);
|
|
464
|
-
}
|
|
465
|
-
const expandedPoints = edgeShapeOffsets[edges];
|
|
466
|
-
if (expandedPoints) {
|
|
467
|
-
const edgeShapeLatitudes = new Int32Array(expandedPoints);
|
|
468
|
-
const edgeShapeLongitudes = new Int32Array(expandedPoints);
|
|
469
|
-
for (let edge = 0; edge < edges; edge += 1) {
|
|
470
|
-
const reference = references[edge];
|
|
471
|
-
if (!reference) continue;
|
|
472
|
-
const shape = (reference >>> 1) - 1;
|
|
473
|
-
const reversed = (reference & 1) === 1;
|
|
474
|
-
const count = sizes[shape];
|
|
475
|
-
const from = tableStarts[shape];
|
|
476
|
-
const to = edgeShapeOffsets[edge];
|
|
477
|
-
for (let point = 0; point < count; point += 1) {
|
|
478
|
-
const source = from + (reversed ? count - 1 - point : point);
|
|
479
|
-
edgeShapeLatitudes[to + point] = tableLatitudes[source];
|
|
480
|
-
edgeShapeLongitudes[to + point] = tableLongitudes[source];
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
assertGeographic(edgeShapeLatitudes, edgeShapeLongitudes, coordinateScale);
|
|
484
|
-
Object.assign(graph, { edgeShapeOffsets, edgeShapeLatitudes, edgeShapeLongitudes });
|
|
485
|
-
}
|
|
486
|
-
if (pairs) {
|
|
487
|
-
graph.turnRestrictionFromEdges = new Uint32Array(buffer.slice(offset, offset += pairs * 4));
|
|
488
|
-
graph.turnRestrictionToEdges = new Uint32Array(buffer.slice(offset, offset += pairs * 4));
|
|
489
|
-
}
|
|
490
|
-
if (runs) {
|
|
491
|
-
graph.turnRestrictionSequenceOffsets = new Uint32Array(buffer.slice(offset, offset += (runs + 1) * 4));
|
|
492
|
-
graph.turnRestrictionSequenceEdges = new Uint32Array(buffer.slice(offset, offset += runEdges * 4));
|
|
493
|
-
assertOffsets(graph.turnRestrictionSequenceOffsets, runs, runEdges, "turn restriction offsets");
|
|
494
|
-
for (const edge of graph.turnRestrictionSequenceEdges) if (edge >= edges) throw new Error("Invalid routing graph: turn restriction references an edge outside the graph.");
|
|
495
|
-
}
|
|
496
|
-
for (const edge of graph.turnRestrictionFromEdges ?? []) if (edge >= edges) throw new Error("Invalid routing graph: turn restriction references an edge outside the graph.");
|
|
497
|
-
for (const edge of graph.turnRestrictionToEdges ?? []) if (edge >= edges) throw new Error("Invalid routing graph: turn restriction references an edge outside the graph.");
|
|
498
|
-
return graph;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
// src/types.ts
|
|
502
|
-
var RouterError = class extends Error {
|
|
503
|
-
constructor(code, message) {
|
|
504
|
-
super(message);
|
|
505
|
-
this.code = code;
|
|
506
|
-
this.name = "RouterError";
|
|
507
|
-
}
|
|
508
|
-
code;
|
|
509
|
-
};
|
|
510
|
-
|
|
511
|
-
// src/spatial-index.ts
|
|
512
|
-
function cellKey(latitude, longitude, cellSizeDegrees) {
|
|
513
|
-
return `${Math.floor(latitude / cellSizeDegrees)}:${Math.floor(longitude / cellSizeDegrees)}`;
|
|
514
|
-
}
|
|
515
|
-
function coordinateFor(graph, nodeId) {
|
|
516
|
-
const scale = graph.coordinateScale ?? 1;
|
|
517
|
-
return { lat: graph.nodeLatitudes[nodeId] / scale, lng: graph.nodeLongitudes[nodeId] / scale };
|
|
518
|
-
}
|
|
519
|
-
function createSpatialIndex(graph, cellSizeDegrees = 0.02) {
|
|
520
|
-
if (!Number.isFinite(cellSizeDegrees) || cellSizeDegrees <= 0) throw new Error("Spatial index cell size must be positive.");
|
|
521
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
522
|
-
for (let nodeId = 0; nodeId < graph.nodeLatitudes.length; nodeId += 1) {
|
|
523
|
-
const coordinate = coordinateFor(graph, nodeId);
|
|
524
|
-
const key = cellKey(coordinate.lat, coordinate.lng, cellSizeDegrees);
|
|
525
|
-
const bucket = buckets.get(key);
|
|
526
|
-
if (bucket) bucket.push(nodeId);
|
|
527
|
-
else buckets.set(key, [nodeId]);
|
|
528
|
-
}
|
|
529
|
-
return {
|
|
530
|
-
cellSizeDegrees,
|
|
531
|
-
cells: new Map([...buckets].map(([key, ids]) => [key, Uint32Array.from(ids)]))
|
|
532
|
-
};
|
|
533
|
-
}
|
|
534
|
-
function withSpatialIndex(graph, cellSizeDegrees) {
|
|
535
|
-
return { ...graph, spatialIndex: createSpatialIndex(graph, cellSizeDegrees) };
|
|
536
|
-
}
|
|
537
|
-
function nearbyNodeIds(index, coordinate, maxDistanceMeters) {
|
|
538
|
-
const latitudeDelta = maxDistanceMeters / 110574;
|
|
539
|
-
const longitudeDelta = maxDistanceMeters / Math.max(1, 111320 * Math.cos(coordinate.lat * Math.PI / 180));
|
|
540
|
-
const minLatitudeCell = Math.floor((coordinate.lat - latitudeDelta) / index.cellSizeDegrees);
|
|
541
|
-
const maxLatitudeCell = Math.floor((coordinate.lat + latitudeDelta) / index.cellSizeDegrees);
|
|
542
|
-
const minLongitudeCell = Math.floor((coordinate.lng - longitudeDelta) / index.cellSizeDegrees);
|
|
543
|
-
const maxLongitudeCell = Math.floor((coordinate.lng + longitudeDelta) / index.cellSizeDegrees);
|
|
544
|
-
const ids = [];
|
|
545
|
-
for (let latitudeCell = minLatitudeCell; latitudeCell <= maxLatitudeCell; latitudeCell += 1) {
|
|
546
|
-
for (let longitudeCell = minLongitudeCell; longitudeCell <= maxLongitudeCell; longitudeCell += 1) {
|
|
547
|
-
const bucket = index.cells.get(`${latitudeCell}:${longitudeCell}`);
|
|
548
|
-
if (bucket) ids.push(...bucket);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
return ids;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
// src/turn-restrictions.ts
|
|
555
|
-
function buildTurnRestrictionIndex(graph) {
|
|
556
|
-
const fromEdges = graph.turnRestrictionFromEdges;
|
|
557
|
-
const toEdges = graph.turnRestrictionToEdges;
|
|
558
|
-
if (!fromEdges || !toEdges || !fromEdges.length) return null;
|
|
559
|
-
if (fromEdges.length !== toEdges.length) throw new Error("Turn restriction arrays must have the same length.");
|
|
560
|
-
const index = /* @__PURE__ */ new Map();
|
|
561
|
-
for (let pair = 0; pair < fromEdges.length; pair += 1) {
|
|
562
|
-
const banned = index.get(fromEdges[pair]);
|
|
563
|
-
if (banned) banned.add(toEdges[pair]);
|
|
564
|
-
else index.set(fromEdges[pair], /* @__PURE__ */ new Set([toEdges[pair]]));
|
|
565
|
-
}
|
|
566
|
-
return index;
|
|
567
|
-
}
|
|
568
|
-
function isTurnBanned(index, fromEdge, toEdge) {
|
|
569
|
-
return index.get(fromEdge)?.has(toEdge) ?? false;
|
|
570
|
-
}
|
|
571
|
-
function packTurnRestrictions(pairs) {
|
|
572
|
-
const unique = /* @__PURE__ */ new Set();
|
|
573
|
-
const sorted = [];
|
|
574
|
-
for (const [fromEdge, toEdge] of pairs) {
|
|
575
|
-
const key = `${fromEdge}:${toEdge}`;
|
|
576
|
-
if (unique.has(key)) continue;
|
|
577
|
-
unique.add(key);
|
|
578
|
-
sorted.push([fromEdge, toEdge]);
|
|
579
|
-
}
|
|
580
|
-
sorted.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
|
|
581
|
-
return {
|
|
582
|
-
turnRestrictionFromEdges: Uint32Array.from(sorted, (pair) => pair[0]),
|
|
583
|
-
turnRestrictionToEdges: Uint32Array.from(sorted, (pair) => pair[1])
|
|
584
|
-
};
|
|
585
|
-
}
|
|
586
|
-
function buildTurnSequenceIndex(graph) {
|
|
587
|
-
const offsets = graph.turnRestrictionSequenceOffsets;
|
|
588
|
-
const edges = graph.turnRestrictionSequenceEdges;
|
|
589
|
-
if (!offsets || !edges || offsets.length < 2) return null;
|
|
590
|
-
const sequences = [];
|
|
591
|
-
const startingAt = /* @__PURE__ */ new Map();
|
|
592
|
-
for (let index = 0; index + 1 < offsets.length; index += 1) {
|
|
593
|
-
const sequence = Array.from(edges.subarray(offsets[index], offsets[index + 1]));
|
|
594
|
-
if (sequence.length < 3) continue;
|
|
595
|
-
const id = sequences.length;
|
|
596
|
-
sequences.push(sequence);
|
|
597
|
-
const bucket = startingAt.get(sequence[0]);
|
|
598
|
-
if (bucket) bucket.push(id);
|
|
599
|
-
else startingAt.set(sequence[0], [id]);
|
|
600
|
-
}
|
|
601
|
-
if (!sequences.length) return null;
|
|
602
|
-
const stride = sequences.reduce((longest, sequence) => Math.max(longest, sequence.length), 0) + 1;
|
|
603
|
-
return { sequences, startingAt, stride };
|
|
604
|
-
}
|
|
605
|
-
function packTurnSequences(sequences) {
|
|
606
|
-
const kept = sequences.filter((sequence) => sequence.length >= 3);
|
|
607
|
-
const unique = new Map(kept.map((sequence) => [sequence.join(","), sequence]));
|
|
608
|
-
const ordered = [...unique.values()].sort((a, b) => a[0] - b[0] || a.length - b.length || a[1] - b[1]);
|
|
609
|
-
const turnRestrictionSequenceOffsets = new Uint32Array(ordered.length + 1);
|
|
610
|
-
for (let index = 0; index < ordered.length; index += 1) turnRestrictionSequenceOffsets[index + 1] = turnRestrictionSequenceOffsets[index] + ordered[index].length;
|
|
611
|
-
const turnRestrictionSequenceEdges = new Uint32Array(turnRestrictionSequenceOffsets[ordered.length]);
|
|
612
|
-
let at = 0;
|
|
613
|
-
for (const sequence of ordered) for (const edge of sequence) turnRestrictionSequenceEdges[at++] = edge;
|
|
614
|
-
return { turnRestrictionSequenceOffsets, turnRestrictionSequenceEdges };
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
// src/routing-profile.ts
|
|
618
|
-
var MINIMUM_SPEED_KPH = 5;
|
|
619
|
-
var MAXIMUM_SPEED_KPH = 150;
|
|
620
|
-
var BLOCKED_ACCESS = /* @__PURE__ */ new Set(["no", "private", "agricultural", "forestry", "military", "emergency", "use_sidepath"]);
|
|
621
|
-
var CAR_HIGHWAY_SPEEDS = {
|
|
622
|
-
motorway: 110,
|
|
623
|
-
motorway_link: 50,
|
|
624
|
-
trunk: 90,
|
|
625
|
-
trunk_link: 50,
|
|
626
|
-
primary: 70,
|
|
627
|
-
primary_link: 45,
|
|
628
|
-
secondary: 60,
|
|
629
|
-
secondary_link: 40,
|
|
630
|
-
tertiary: 50,
|
|
631
|
-
tertiary_link: 35,
|
|
632
|
-
unclassified: 35,
|
|
633
|
-
residential: 30,
|
|
634
|
-
living_street: 10,
|
|
635
|
-
service: 20,
|
|
636
|
-
road: 25
|
|
637
|
-
};
|
|
638
|
-
var CAR_ACCESS_KEYS = ["access", "vehicle", "motor_vehicle", "motorcar"];
|
|
639
|
-
var CAR_RESTRICTION_KEYS = ["restriction", "restriction:vehicle", "restriction:motor_vehicle", "restriction:motorcar"];
|
|
640
|
-
var CAR_EXCEPT_MODES = /* @__PURE__ */ new Set(["motorcar", "motor_vehicle", "vehicle"]);
|
|
641
|
-
var TURN_RESTRICTION_KINDS = {
|
|
642
|
-
no_left_turn: "no",
|
|
643
|
-
no_right_turn: "no",
|
|
644
|
-
no_straight_on: "no",
|
|
645
|
-
no_u_turn: "no",
|
|
646
|
-
no_entry: "no",
|
|
647
|
-
no_exit: "no",
|
|
648
|
-
only_left_turn: "only",
|
|
649
|
-
only_right_turn: "only",
|
|
650
|
-
only_straight_on: "only",
|
|
651
|
-
only_u_turn: "only"
|
|
652
|
-
};
|
|
653
|
-
var IMPLICITLY_ONEWAY = /* @__PURE__ */ new Set(["motorway", "motorway_link"]);
|
|
654
|
-
function clampSpeed(value) {
|
|
655
|
-
if (!Number.isFinite(value)) return MINIMUM_SPEED_KPH;
|
|
656
|
-
return Math.min(MAXIMUM_SPEED_KPH, Math.max(MINIMUM_SPEED_KPH, value));
|
|
657
|
-
}
|
|
658
|
-
function parseMaxSpeedKph(value) {
|
|
659
|
-
if (!value) return null;
|
|
660
|
-
const text = value.trim().toLowerCase();
|
|
661
|
-
if (text === "walk") return 7;
|
|
662
|
-
const match = /^(\d+(?:\.\d+)?)\s*(mph|km\/h|kmh|kph|knots)?$/.exec(text);
|
|
663
|
-
if (!match) return null;
|
|
664
|
-
const amount = Number(match[1]);
|
|
665
|
-
if (!Number.isFinite(amount) || amount <= 0) return null;
|
|
666
|
-
if (match[2] === "mph") return amount * 1.609344;
|
|
667
|
-
if (match[2] === "knots") return amount * 1.852;
|
|
668
|
-
return amount;
|
|
669
|
-
}
|
|
670
|
-
function accessAllowed(tags, keys) {
|
|
671
|
-
let allowed = true;
|
|
672
|
-
for (const key of keys) {
|
|
673
|
-
const value = tags[key];
|
|
674
|
-
if (value === void 0) continue;
|
|
675
|
-
allowed = !BLOCKED_ACCESS.has(value);
|
|
676
|
-
}
|
|
677
|
-
return allowed;
|
|
678
|
-
}
|
|
679
|
-
function onewayDirections(tags, highway) {
|
|
680
|
-
const oneway = tags.oneway?.trim().toLowerCase();
|
|
681
|
-
if (oneway === "yes" || oneway === "true" || oneway === "1") return { forward: true, backward: false };
|
|
682
|
-
if (oneway === "-1" || oneway === "reverse") return { forward: false, backward: true };
|
|
683
|
-
if (oneway !== void 0 && oneway !== "") return { forward: true, backward: true };
|
|
684
|
-
const junction = tags.junction?.trim().toLowerCase();
|
|
685
|
-
if (junction === "roundabout" || junction === "circular") return { forward: true, backward: false };
|
|
686
|
-
if (highway && IMPLICITLY_ONEWAY.has(highway)) return { forward: true, backward: false };
|
|
687
|
-
return { forward: true, backward: true };
|
|
688
|
-
}
|
|
689
|
-
var carRoutingProfile = {
|
|
690
|
-
name: "car",
|
|
691
|
-
wayTagKeys: ["highway", "oneway", "junction", "access", "vehicle", "motor_vehicle", "motorcar", "maxspeed", "maxspeed:forward", "maxspeed:backward"],
|
|
692
|
-
relationTagKeys: ["type", "except", ...CAR_RESTRICTION_KEYS],
|
|
693
|
-
maxSpeedKph: MAXIMUM_SPEED_KPH,
|
|
694
|
-
evaluateWay(tags) {
|
|
695
|
-
const highway = tags.highway;
|
|
696
|
-
if (!highway || !(highway in CAR_HIGHWAY_SPEEDS)) return null;
|
|
697
|
-
if (!accessAllowed(tags, CAR_ACCESS_KEYS)) return null;
|
|
698
|
-
const { forward, backward } = onewayDirections(tags, highway);
|
|
699
|
-
if (!forward && !backward) return null;
|
|
700
|
-
const base = parseMaxSpeedKph(tags.maxspeed) ?? CAR_HIGHWAY_SPEEDS[highway];
|
|
701
|
-
return {
|
|
702
|
-
forward,
|
|
703
|
-
backward,
|
|
704
|
-
forwardSpeedKph: clampSpeed(parseMaxSpeedKph(tags["maxspeed:forward"]) ?? base),
|
|
705
|
-
backwardSpeedKph: clampSpeed(parseMaxSpeedKph(tags["maxspeed:backward"]) ?? base)
|
|
706
|
-
};
|
|
707
|
-
},
|
|
708
|
-
evaluateTurnRestriction(tags) {
|
|
709
|
-
if (tags.type !== "restriction") return null;
|
|
710
|
-
const except = tags.except?.split(";").map((mode) => mode.trim().toLowerCase()) ?? [];
|
|
711
|
-
if (except.some((mode) => CAR_EXCEPT_MODES.has(mode))) return null;
|
|
712
|
-
let value;
|
|
713
|
-
for (const key of CAR_RESTRICTION_KEYS) if (tags[key] !== void 0) value = tags[key];
|
|
714
|
-
if (!value) return null;
|
|
715
|
-
const kind = TURN_RESTRICTION_KINDS[value.trim().toLowerCase()];
|
|
716
|
-
return kind ? { kind, value } : null;
|
|
717
|
-
}
|
|
718
|
-
};
|
|
719
|
-
var routingProfiles = { car: carRoutingProfile };
|
|
720
|
-
function getRoutingProfile(name) {
|
|
721
|
-
const profile = routingProfiles[name];
|
|
722
|
-
if (!profile) throw new Error(`Unknown routing profile "${name}". Available: ${Object.keys(routingProfiles).join(", ")}.`);
|
|
723
|
-
return profile;
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
// src/edge-index.ts
|
|
727
|
-
var MAX_CELLS_PER_EDGE = 4096;
|
|
728
|
-
function cellKey2(latitudeCell, longitudeCell) {
|
|
729
|
-
return `${latitudeCell}:${longitudeCell}`;
|
|
730
|
-
}
|
|
731
|
-
function edgeSource(graph, edge) {
|
|
732
|
-
let low = 0;
|
|
733
|
-
let high = graph.nodeLatitudes.length - 1;
|
|
734
|
-
while (low < high) {
|
|
735
|
-
const middle = low + high + 1 >> 1;
|
|
736
|
-
if (graph.edgeOffsets[middle] <= edge) low = middle;
|
|
737
|
-
else high = middle - 1;
|
|
738
|
-
}
|
|
739
|
-
return low;
|
|
740
|
-
}
|
|
741
|
-
function edgePolyline(graph, edge, source = edgeSource(graph, edge)) {
|
|
742
|
-
const scale = graph.coordinateScale ?? 1;
|
|
743
|
-
const target = graph.edgeTargets[edge];
|
|
744
|
-
const points = [{ lat: graph.nodeLatitudes[source] / scale, lng: graph.nodeLongitudes[source] / scale }];
|
|
745
|
-
if (graph.edgeShapeOffsets && graph.edgeShapeLatitudes && graph.edgeShapeLongitudes) {
|
|
746
|
-
for (let point = graph.edgeShapeOffsets[edge]; point < graph.edgeShapeOffsets[edge + 1]; point += 1) {
|
|
747
|
-
points.push({ lat: graph.edgeShapeLatitudes[point] / scale, lng: graph.edgeShapeLongitudes[point] / scale });
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
points.push({ lat: graph.nodeLatitudes[target] / scale, lng: graph.nodeLongitudes[target] / scale });
|
|
751
|
-
return points;
|
|
752
|
-
}
|
|
753
|
-
function createEdgeSpatialIndex(graph, cellSizeDegrees = 0.02) {
|
|
754
|
-
if (!Number.isFinite(cellSizeDegrees) || cellSizeDegrees <= 0) throw new Error("Edge index cell size must be positive.");
|
|
755
|
-
const scale = graph.coordinateScale ?? 1;
|
|
756
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
757
|
-
const oversized = [];
|
|
758
|
-
const shapeOffsets = graph.edgeShapeOffsets;
|
|
759
|
-
const shapeLatitudes = graph.edgeShapeLatitudes;
|
|
760
|
-
const shapeLongitudes = graph.edgeShapeLongitudes;
|
|
761
|
-
for (let source = 0; source < graph.nodeLatitudes.length; source += 1) {
|
|
762
|
-
const sourceLat = graph.nodeLatitudes[source] / scale;
|
|
763
|
-
const sourceLng = graph.nodeLongitudes[source] / scale;
|
|
764
|
-
for (let edge = graph.edgeOffsets[source]; edge < graph.edgeOffsets[source + 1]; edge += 1) {
|
|
765
|
-
const target = graph.edgeTargets[edge];
|
|
766
|
-
let minLat = Math.min(sourceLat, graph.nodeLatitudes[target] / scale);
|
|
767
|
-
let maxLat = Math.max(sourceLat, graph.nodeLatitudes[target] / scale);
|
|
768
|
-
let minLng = Math.min(sourceLng, graph.nodeLongitudes[target] / scale);
|
|
769
|
-
let maxLng = Math.max(sourceLng, graph.nodeLongitudes[target] / scale);
|
|
770
|
-
if (shapeOffsets && shapeLatitudes && shapeLongitudes) {
|
|
771
|
-
for (let point = shapeOffsets[edge]; point < shapeOffsets[edge + 1]; point += 1) {
|
|
772
|
-
const lat = shapeLatitudes[point] / scale;
|
|
773
|
-
const lng = shapeLongitudes[point] / scale;
|
|
774
|
-
if (lat < minLat) minLat = lat;
|
|
775
|
-
else if (lat > maxLat) maxLat = lat;
|
|
776
|
-
if (lng < minLng) minLng = lng;
|
|
777
|
-
else if (lng > maxLng) maxLng = lng;
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
const minLatCell = Math.floor(minLat / cellSizeDegrees);
|
|
781
|
-
const maxLatCell = Math.floor(maxLat / cellSizeDegrees);
|
|
782
|
-
const minLngCell = Math.floor(minLng / cellSizeDegrees);
|
|
783
|
-
const maxLngCell = Math.floor(maxLng / cellSizeDegrees);
|
|
784
|
-
if (!Number.isFinite(minLatCell) || !Number.isFinite(minLngCell) || (maxLatCell - minLatCell + 1) * (maxLngCell - minLngCell + 1) > MAX_CELLS_PER_EDGE) {
|
|
785
|
-
oversized.push(edge);
|
|
786
|
-
continue;
|
|
787
|
-
}
|
|
788
|
-
for (let latitudeCell = minLatCell; latitudeCell <= maxLatCell; latitudeCell += 1) {
|
|
789
|
-
for (let longitudeCell = minLngCell; longitudeCell <= maxLngCell; longitudeCell += 1) {
|
|
790
|
-
const key = cellKey2(latitudeCell, longitudeCell);
|
|
791
|
-
const bucket = buckets.get(key);
|
|
792
|
-
if (bucket) bucket.push(edge);
|
|
793
|
-
else buckets.set(key, [edge]);
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
return { cellSizeDegrees, cells: new Map([...buckets].map(([key, ids]) => [key, Uint32Array.from(ids)])), oversized: Uint32Array.from(oversized) };
|
|
799
|
-
}
|
|
800
|
-
var indexes = /* @__PURE__ */ new WeakMap();
|
|
801
|
-
function edgeSpatialIndexFor(graph, cellSizeDegrees) {
|
|
802
|
-
let index = indexes.get(graph);
|
|
803
|
-
if (!index) {
|
|
804
|
-
index = createEdgeSpatialIndex(graph, cellSizeDegrees);
|
|
805
|
-
indexes.set(graph, index);
|
|
806
|
-
}
|
|
807
|
-
return index;
|
|
808
|
-
}
|
|
809
|
-
function nearbyEdgeIds(index, coordinate, maxDistanceMeters) {
|
|
810
|
-
const latitudeDelta = maxDistanceMeters / 110574;
|
|
811
|
-
const longitudeDelta = maxDistanceMeters / Math.max(1, 111320 * Math.cos(coordinate.lat * Math.PI / 180));
|
|
812
|
-
const minLatitudeCell = Math.floor((coordinate.lat - latitudeDelta) / index.cellSizeDegrees);
|
|
813
|
-
const maxLatitudeCell = Math.floor((coordinate.lat + latitudeDelta) / index.cellSizeDegrees);
|
|
814
|
-
const minLongitudeCell = Math.floor((coordinate.lng - longitudeDelta) / index.cellSizeDegrees);
|
|
815
|
-
const maxLongitudeCell = Math.floor((coordinate.lng + longitudeDelta) / index.cellSizeDegrees);
|
|
816
|
-
const ids = new Set(index.oversized);
|
|
817
|
-
for (let latitudeCell = minLatitudeCell; latitudeCell <= maxLatitudeCell; latitudeCell += 1) {
|
|
818
|
-
for (let longitudeCell = minLongitudeCell; longitudeCell <= maxLongitudeCell; longitudeCell += 1) {
|
|
819
|
-
const bucket = index.cells.get(cellKey2(latitudeCell, longitudeCell));
|
|
820
|
-
if (bucket) for (const id of bucket) ids.add(id);
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
return ids;
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
// src/snap.ts
|
|
827
|
-
var EARTH_RADIUS_METERS = 6371e3;
|
|
828
|
-
var METERS_PER_DEGREE_LATITUDE = 110574;
|
|
829
|
-
var METERS_PER_DEGREE_LONGITUDE = 111320;
|
|
830
|
-
var NODE_SNAP_TOLERANCE_METERS = 1;
|
|
831
|
-
var TIE_TOLERANCE_METERS = 0.5;
|
|
832
|
-
function haversineMeters(a, b) {
|
|
833
|
-
const radians = Math.PI / 180;
|
|
834
|
-
const dLat = (b.lat - a.lat) * radians;
|
|
835
|
-
const dLng = (b.lng - a.lng) * radians;
|
|
836
|
-
const latA = a.lat * radians;
|
|
837
|
-
const latB = b.lat * radians;
|
|
838
|
-
const value = Math.sin(dLat / 2) ** 2 + Math.cos(latA) * Math.cos(latB) * Math.sin(dLng / 2) ** 2;
|
|
839
|
-
return EARTH_RADIUS_METERS * 2 * Math.atan2(Math.sqrt(value), Math.sqrt(1 - value));
|
|
840
|
-
}
|
|
841
|
-
function coordinateFor2(graph, nodeId) {
|
|
842
|
-
const scale = graph.coordinateScale ?? 1;
|
|
843
|
-
return { lat: graph.nodeLatitudes[nodeId] / scale, lng: graph.nodeLongitudes[nodeId] / scale };
|
|
844
|
-
}
|
|
845
|
-
function projectOntoPolyline(points, query) {
|
|
846
|
-
const longitudeScale = METERS_PER_DEGREE_LONGITUDE * Math.cos(query.lat * Math.PI / 180);
|
|
847
|
-
const x = (point) => (point.lng - query.lng) * longitudeScale;
|
|
848
|
-
const y = (point) => (point.lat - query.lat) * METERS_PER_DEGREE_LATITUDE;
|
|
849
|
-
let best = { distanceMeters: Infinity, vertexIndex: 0, along: 0, point: points[0] };
|
|
850
|
-
let travelled = 0;
|
|
851
|
-
let total = 0;
|
|
852
|
-
for (let index = 1; index < points.length; index += 1) {
|
|
853
|
-
const start = points[index - 1];
|
|
854
|
-
const end = points[index];
|
|
855
|
-
const segmentMeters = haversineMeters(start, end);
|
|
856
|
-
const dx = x(end) - x(start);
|
|
857
|
-
const dy = y(end) - y(start);
|
|
858
|
-
const lengthSquared = dx * dx + dy * dy;
|
|
859
|
-
const t = lengthSquared ? Math.max(0, Math.min(1, (-x(start) * dx + -y(start) * dy) / lengthSquared)) : 0;
|
|
860
|
-
const closest = { lat: start.lat + (end.lat - start.lat) * t, lng: start.lng + (end.lng - start.lng) * t };
|
|
861
|
-
const distanceMeters = haversineMeters(query, closest);
|
|
862
|
-
if (distanceMeters < best.distanceMeters) {
|
|
863
|
-
best = { distanceMeters, vertexIndex: index - 1, along: travelled + segmentMeters * t, point: closest };
|
|
864
|
-
}
|
|
865
|
-
travelled += segmentMeters;
|
|
866
|
-
total += segmentMeters;
|
|
867
|
-
}
|
|
868
|
-
return { ...best, totalMeters: total };
|
|
869
|
-
}
|
|
870
|
-
function snapToGraph(graph, coordinate, maxDistanceMeters = 500) {
|
|
871
|
-
const candidates = graph.edgeTargets.length ? nearbyEdgeIds(edgeSpatialIndexFor(graph), coordinate, maxDistanceMeters) : /* @__PURE__ */ new Set();
|
|
872
|
-
let best = null;
|
|
873
|
-
const projections = [];
|
|
874
|
-
for (const edge of candidates) {
|
|
875
|
-
const source = edgeSource(graph, edge);
|
|
876
|
-
const polyline = edgePolyline(graph, edge, source);
|
|
877
|
-
const projected = projectOntoPolyline(polyline, coordinate);
|
|
878
|
-
if (projected.distanceMeters > maxDistanceMeters) continue;
|
|
879
|
-
const projection = {
|
|
880
|
-
edge,
|
|
881
|
-
source,
|
|
882
|
-
target: graph.edgeTargets[edge],
|
|
883
|
-
fraction: projected.totalMeters ? projected.along / projected.totalMeters : 0,
|
|
884
|
-
point: projected.point,
|
|
885
|
-
distanceMeters: projected.distanceMeters,
|
|
886
|
-
vertexIndex: projected.vertexIndex,
|
|
887
|
-
polyline
|
|
888
|
-
};
|
|
889
|
-
projections.push(projection);
|
|
890
|
-
if (!best || projection.distanceMeters < best.distanceMeters) best = projection;
|
|
891
|
-
}
|
|
892
|
-
if (!best) {
|
|
893
|
-
const node = nearestNodeWithin(graph, coordinate, maxDistanceMeters);
|
|
894
|
-
return node === null ? null : { kind: "node", node, point: coordinateFor2(graph, node), distanceMeters: haversineMeters(coordinate, coordinateFor2(graph, node)) };
|
|
895
|
-
}
|
|
896
|
-
const startMeters = best.fraction * totalLengthOf(best);
|
|
897
|
-
const endMeters = (1 - best.fraction) * totalLengthOf(best);
|
|
898
|
-
if (startMeters <= NODE_SNAP_TOLERANCE_METERS) return { kind: "node", node: best.source, point: best.polyline[0], distanceMeters: best.distanceMeters };
|
|
899
|
-
if (endMeters <= NODE_SNAP_TOLERANCE_METERS) return { kind: "node", node: best.target, point: best.polyline[best.polyline.length - 1], distanceMeters: best.distanceMeters };
|
|
900
|
-
const sameRoad = projections.filter((projection) => projection.distanceMeters <= best.distanceMeters + TIE_TOLERANCE_METERS);
|
|
901
|
-
return { kind: "edge", projections: sameRoad, point: best.point, distanceMeters: best.distanceMeters };
|
|
902
|
-
}
|
|
903
|
-
function totalLengthOf(projection) {
|
|
904
|
-
let total = 0;
|
|
905
|
-
for (let index = 1; index < projection.polyline.length; index += 1) total += haversineMeters(projection.polyline[index - 1], projection.polyline[index]);
|
|
906
|
-
return total;
|
|
907
|
-
}
|
|
908
|
-
function nearestNodeWithin(graph, coordinate, maxDistanceMeters) {
|
|
909
|
-
let closest = null;
|
|
910
|
-
let closestDistance = maxDistanceMeters;
|
|
911
|
-
const check = (nodeId) => {
|
|
912
|
-
const distance = haversineMeters(coordinate, coordinateFor2(graph, nodeId));
|
|
913
|
-
if (distance <= closestDistance) {
|
|
914
|
-
closest = nodeId;
|
|
915
|
-
closestDistance = distance;
|
|
916
|
-
}
|
|
917
|
-
};
|
|
918
|
-
if (graph.spatialIndex) for (const nodeId of nearbyNodeIds(graph.spatialIndex, coordinate, maxDistanceMeters)) check(nodeId);
|
|
919
|
-
else for (let nodeId = 0; nodeId < graph.nodeLatitudes.length; nodeId += 1) check(nodeId);
|
|
920
|
-
return closest;
|
|
921
|
-
}
|
|
922
|
-
function geometryAfter(projection) {
|
|
923
|
-
return [projection.point, ...projection.polyline.slice(projection.vertexIndex + 1, projection.polyline.length - 1)];
|
|
924
|
-
}
|
|
925
|
-
function geometryBefore(projection) {
|
|
926
|
-
return [...projection.polyline.slice(1, projection.vertexIndex + 1), projection.point];
|
|
927
|
-
}
|
|
928
|
-
function geometryBetween(from, to) {
|
|
929
|
-
return [from.point, ...from.polyline.slice(from.vertexIndex + 1, to.vertexIndex + 1), to.point];
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
// src/priority-queue.ts
|
|
933
|
-
var MinPriorityQueue = class {
|
|
934
|
-
values = [];
|
|
935
|
-
get size() {
|
|
936
|
-
return this.values.length;
|
|
937
|
-
}
|
|
938
|
-
/** The smallest priority still queued, or Infinity when empty. A* uses it to
|
|
939
|
-
* stop once no queued state can beat the best finish found so far. */
|
|
940
|
-
get minimumPriority() {
|
|
941
|
-
return this.values.length ? this.values[0].priority : Infinity;
|
|
942
|
-
}
|
|
943
|
-
push(value, priority) {
|
|
944
|
-
const entry = { value, priority };
|
|
945
|
-
this.values.push(entry);
|
|
946
|
-
let index = this.values.length - 1;
|
|
947
|
-
while (index > 0) {
|
|
948
|
-
const parent = Math.floor((index - 1) / 2);
|
|
949
|
-
if (this.values[parent].priority <= entry.priority) break;
|
|
950
|
-
this.values[index] = this.values[parent];
|
|
951
|
-
index = parent;
|
|
952
|
-
}
|
|
953
|
-
this.values[index] = entry;
|
|
954
|
-
}
|
|
955
|
-
pop() {
|
|
956
|
-
if (!this.values.length) return void 0;
|
|
957
|
-
const first = this.values[0];
|
|
958
|
-
const last = this.values.pop();
|
|
959
|
-
if (this.values.length) {
|
|
960
|
-
let index = 0;
|
|
961
|
-
while (true) {
|
|
962
|
-
let child = index * 2 + 1;
|
|
963
|
-
if (child >= this.values.length) break;
|
|
964
|
-
if (child + 1 < this.values.length && this.values[child + 1].priority < this.values[child].priority) child++;
|
|
965
|
-
if (this.values[child].priority >= last.priority) break;
|
|
966
|
-
this.values[index] = this.values[child];
|
|
967
|
-
index = child;
|
|
968
|
-
}
|
|
969
|
-
this.values[index] = last;
|
|
970
|
-
}
|
|
971
|
-
return first.value;
|
|
972
|
-
}
|
|
973
|
-
};
|
|
974
|
-
|
|
975
|
-
// src/router.ts
|
|
976
|
-
var MAX_CAR_SPEED_MPS = carRoutingProfile.maxSpeedKph * 1e3 / 3600;
|
|
977
|
-
function assertGraph(graph) {
|
|
978
|
-
const nodes = graph.nodeLatitudes.length;
|
|
979
|
-
const edges = graph.edgeTargets.length;
|
|
980
|
-
if (!nodes || graph.nodeLongitudes.length !== nodes || graph.edgeOffsets.length !== nodes + 1 || graph.edgeDistancesMeters.length !== edges || graph.edgeDurationsSeconds.length !== edges || graph.edgeOffsets[nodes] !== edges) {
|
|
981
|
-
throw new Error("Invalid road graph");
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
function findNearestNode(graph, coordinate, maxDistanceMeters = 500) {
|
|
985
|
-
let closest = null;
|
|
986
|
-
let closestDistance = maxDistanceMeters;
|
|
987
|
-
const checkNode = (nodeId) => {
|
|
988
|
-
const distance = haversineMeters(coordinate, coordinateFor2(graph, nodeId));
|
|
989
|
-
if (distance <= closestDistance) {
|
|
990
|
-
closest = nodeId;
|
|
991
|
-
closestDistance = distance;
|
|
992
|
-
}
|
|
993
|
-
};
|
|
994
|
-
if (graph.spatialIndex) {
|
|
995
|
-
for (const nodeId of nearbyNodeIds(graph.spatialIndex, coordinate, maxDistanceMeters)) checkNode(nodeId);
|
|
996
|
-
} else {
|
|
997
|
-
for (let nodeId = 0; nodeId < graph.nodeLatitudes.length; nodeId += 1) checkNode(nodeId);
|
|
998
|
-
}
|
|
999
|
-
return closest;
|
|
1000
|
-
}
|
|
1001
|
-
function searchContext(graph, destinationPoint, preference) {
|
|
1002
|
-
const distanceScale = graph.edgeDistanceScale ?? 1;
|
|
1003
|
-
const durationScale = graph.edgeDurationScale ?? 1;
|
|
1004
|
-
return {
|
|
1005
|
-
costOf: (edge) => preference === "fastest" ? graph.edgeDurationsSeconds[edge] * durationScale : graph.edgeDistancesMeters[edge] * distanceScale,
|
|
1006
|
-
heuristicFor: (node) => {
|
|
1007
|
-
const remaining = haversineMeters(coordinateFor2(graph, node), destinationPoint);
|
|
1008
|
-
return preference === "fastest" ? remaining / MAX_CAR_SPEED_MPS : remaining;
|
|
1009
|
-
}
|
|
1010
|
-
};
|
|
1011
|
-
}
|
|
1012
|
-
function searchByNode(graph, seeds, finishers, destinationPoint, preference, costBound = Infinity) {
|
|
1013
|
-
const { costOf, heuristicFor } = searchContext(graph, destinationPoint, preference);
|
|
1014
|
-
const nodeCount = graph.nodeLatitudes.length;
|
|
1015
|
-
const costs = new Float64Array(nodeCount).fill(Infinity);
|
|
1016
|
-
const previousNode = new Int32Array(nodeCount).fill(-1);
|
|
1017
|
-
const previousEdge = new Int32Array(nodeCount).fill(-1);
|
|
1018
|
-
const seedAt = /* @__PURE__ */ new Map();
|
|
1019
|
-
const queue = new MinPriorityQueue();
|
|
1020
|
-
for (const seed of seeds) {
|
|
1021
|
-
if (seed.cost >= costs[seed.node]) continue;
|
|
1022
|
-
costs[seed.node] = seed.cost;
|
|
1023
|
-
seedAt.set(seed.node, seed);
|
|
1024
|
-
queue.push(seed.node, seed.cost + heuristicFor(seed.node));
|
|
1025
|
-
}
|
|
1026
|
-
let best = costBound;
|
|
1027
|
-
let bestNode = -1;
|
|
1028
|
-
let bestFinisher = null;
|
|
1029
|
-
while (queue.size) {
|
|
1030
|
-
if (queue.minimumPriority >= best) break;
|
|
1031
|
-
const node2 = queue.pop();
|
|
1032
|
-
for (const finisher of finishers.get(node2) ?? []) {
|
|
1033
|
-
const total = costs[node2] + finisher.cost;
|
|
1034
|
-
if (total < best) {
|
|
1035
|
-
best = total;
|
|
1036
|
-
bestNode = node2;
|
|
1037
|
-
bestFinisher = finisher;
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
for (let edge = graph.edgeOffsets[node2]; edge < graph.edgeOffsets[node2 + 1]; edge += 1) {
|
|
1041
|
-
const target = graph.edgeTargets[edge];
|
|
1042
|
-
const cost = costs[node2] + costOf(edge);
|
|
1043
|
-
if (cost >= costs[target]) continue;
|
|
1044
|
-
costs[target] = cost;
|
|
1045
|
-
previousNode[target] = node2;
|
|
1046
|
-
previousEdge[target] = edge;
|
|
1047
|
-
queue.push(target, cost + heuristicFor(target));
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
if (!bestFinisher) return null;
|
|
1051
|
-
const edges = [];
|
|
1052
|
-
let node = bestNode;
|
|
1053
|
-
while (previousNode[node] !== -1) {
|
|
1054
|
-
edges.push(previousEdge[node]);
|
|
1055
|
-
node = previousNode[node];
|
|
1056
|
-
}
|
|
1057
|
-
edges.reverse();
|
|
1058
|
-
return { edges, finisher: bestFinisher, seed: seedAt.get(node) };
|
|
1059
|
-
}
|
|
1060
|
-
function searchByEdge(graph, seeds, finishers, destinationPoint, preference, restrictions, sequenceIndex, costBound = Infinity) {
|
|
1061
|
-
const { costOf, heuristicFor } = searchContext(graph, destinationPoint, preference);
|
|
1062
|
-
const edgeCount = graph.edgeTargets.length;
|
|
1063
|
-
const costs = new Float64Array(edgeCount).fill(Infinity);
|
|
1064
|
-
const previousState = new Int32Array(edgeCount).fill(-1);
|
|
1065
|
-
const seedOf = /* @__PURE__ */ new Map();
|
|
1066
|
-
const queue = new MinPriorityQueue();
|
|
1067
|
-
const sequences = sequenceIndex?.sequences ?? [];
|
|
1068
|
-
const startingAt = sequenceIndex?.startingAt;
|
|
1069
|
-
const partialEdge = [];
|
|
1070
|
-
const partialMatches = [];
|
|
1071
|
-
const partialCost = [];
|
|
1072
|
-
const partialPrevious = [];
|
|
1073
|
-
const partialIds = /* @__PURE__ */ new Map();
|
|
1074
|
-
const stride = sequenceIndex?.stride ?? 1;
|
|
1075
|
-
const encode = (sequence, position) => sequence * stride + position;
|
|
1076
|
-
const edgeOf = (state) => state < edgeCount ? state : partialEdge[state - edgeCount];
|
|
1077
|
-
const matchesOf = (state) => state < edgeCount ? void 0 : partialMatches[state - edgeCount];
|
|
1078
|
-
const costOfState = (state) => state < edgeCount ? costs[state] : partialCost[state - edgeCount];
|
|
1079
|
-
const relax = (state, cost, from, seed2) => {
|
|
1080
|
-
if (state < edgeCount) {
|
|
1081
|
-
if (cost >= costs[state]) return;
|
|
1082
|
-
costs[state] = cost;
|
|
1083
|
-
previousState[state] = from;
|
|
1084
|
-
if (from === -1 && seed2) seedOf.set(state, seed2);
|
|
1085
|
-
} else {
|
|
1086
|
-
const slot = state - edgeCount;
|
|
1087
|
-
if (cost >= partialCost[slot]) return;
|
|
1088
|
-
partialCost[slot] = cost;
|
|
1089
|
-
partialPrevious[slot] = from;
|
|
1090
|
-
}
|
|
1091
|
-
queue.push(state, cost + heuristicFor(graph.edgeTargets[edgeOf(state)]));
|
|
1092
|
-
};
|
|
1093
|
-
const stateFor = (edge, matches) => {
|
|
1094
|
-
if (!matches.length) return edge;
|
|
1095
|
-
const key = `${edge}:${matches.sort((a, b) => a - b).join(",")}`;
|
|
1096
|
-
let id = partialIds.get(key);
|
|
1097
|
-
if (id === void 0) {
|
|
1098
|
-
id = edgeCount + partialEdge.length;
|
|
1099
|
-
partialIds.set(key, id);
|
|
1100
|
-
partialEdge.push(edge);
|
|
1101
|
-
partialMatches.push(matches);
|
|
1102
|
-
partialCost.push(Infinity);
|
|
1103
|
-
partialPrevious.push(-1);
|
|
1104
|
-
}
|
|
1105
|
-
return id;
|
|
1106
|
-
};
|
|
1107
|
-
const advance = (edge, matches, next) => {
|
|
1108
|
-
const carried = [];
|
|
1109
|
-
if (matches) {
|
|
1110
|
-
for (const match of matches) {
|
|
1111
|
-
const sequence = sequences[Math.floor(match / stride)];
|
|
1112
|
-
const position = match % stride;
|
|
1113
|
-
if (sequence[position + 1] !== next) continue;
|
|
1114
|
-
if (position + 2 === sequence.length) return null;
|
|
1115
|
-
carried.push(encode(Math.floor(match / stride), position + 1));
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
for (const id of startingAt?.get(edge) ?? []) {
|
|
1119
|
-
if (sequences[id][1] !== next) continue;
|
|
1120
|
-
if (sequences[id].length === 2) return null;
|
|
1121
|
-
carried.push(encode(id, 1));
|
|
1122
|
-
}
|
|
1123
|
-
return carried;
|
|
1124
|
-
};
|
|
1125
|
-
let best = costBound;
|
|
1126
|
-
let bestArrival = -1;
|
|
1127
|
-
let bestFinisher = null;
|
|
1128
|
-
let bestSeed = null;
|
|
1129
|
-
const considerFinishers = (state, node, seed2) => {
|
|
1130
|
-
const edge = edgeOf(state);
|
|
1131
|
-
const banned = restrictions?.get(edge);
|
|
1132
|
-
for (const finisher of finishers.get(node) ?? []) {
|
|
1133
|
-
if (finisher.edge >= 0 && banned?.has(finisher.edge)) continue;
|
|
1134
|
-
if (finisher.edge >= 0 && advance(edge, matchesOf(state), finisher.edge) === null) continue;
|
|
1135
|
-
const total = costOfState(state) + finisher.cost;
|
|
1136
|
-
if (total < best) {
|
|
1137
|
-
best = total;
|
|
1138
|
-
bestArrival = state;
|
|
1139
|
-
bestFinisher = finisher;
|
|
1140
|
-
bestSeed = seed2;
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
};
|
|
1144
|
-
for (const seed2 of seeds) {
|
|
1145
|
-
if (seed2.edge >= 0) {
|
|
1146
|
-
relax(seed2.edge, seed2.cost, -1, seed2);
|
|
1147
|
-
considerFinishers(seed2.edge, seed2.node, seed2);
|
|
1148
|
-
continue;
|
|
1149
|
-
}
|
|
1150
|
-
for (const finisher of finishers.get(seed2.node) ?? []) {
|
|
1151
|
-
const total = seed2.cost + finisher.cost;
|
|
1152
|
-
if (total < best) {
|
|
1153
|
-
best = total;
|
|
1154
|
-
bestArrival = -1;
|
|
1155
|
-
bestFinisher = finisher;
|
|
1156
|
-
bestSeed = seed2;
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
for (let edge = graph.edgeOffsets[seed2.node]; edge < graph.edgeOffsets[seed2.node + 1]; edge += 1) {
|
|
1160
|
-
relax(edge, seed2.cost + costOf(edge), -1, seed2);
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
while (queue.size) {
|
|
1164
|
-
if (queue.minimumPriority >= best) break;
|
|
1165
|
-
const state = queue.pop();
|
|
1166
|
-
const edge = edgeOf(state);
|
|
1167
|
-
const matches = matchesOf(state);
|
|
1168
|
-
const node = graph.edgeTargets[edge];
|
|
1169
|
-
const banned = restrictions?.get(edge);
|
|
1170
|
-
considerFinishers(state, node, null);
|
|
1171
|
-
for (let next = graph.edgeOffsets[node]; next < graph.edgeOffsets[node + 1]; next += 1) {
|
|
1172
|
-
if (banned?.has(next)) continue;
|
|
1173
|
-
const carried = advance(edge, matches, next);
|
|
1174
|
-
if (carried === null) continue;
|
|
1175
|
-
relax(stateFor(next, carried), costOfState(state) + costOf(next), state, null);
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
if (!bestFinisher) return null;
|
|
1179
|
-
const chain = [];
|
|
1180
|
-
for (let state = bestArrival; state !== -1; state = state < edgeCount ? previousState[state] : partialPrevious[state - edgeCount]) {
|
|
1181
|
-
chain.push(edgeOf(state));
|
|
1182
|
-
}
|
|
1183
|
-
chain.reverse();
|
|
1184
|
-
const seed = bestSeed ?? (chain.length ? seedOf.get(chain[0]) : seeds[0]);
|
|
1185
|
-
if (seed.edge >= 0 && chain[0] === seed.edge) chain.shift();
|
|
1186
|
-
return { edges: chain, finisher: bestFinisher, seed };
|
|
1187
|
-
}
|
|
1188
|
-
function seedsFor(snap, costOf) {
|
|
1189
|
-
if (snap.kind === "node") return [{ node: snap.node, edge: -1, cost: 0 }];
|
|
1190
|
-
return snap.projections.map((projection) => ({
|
|
1191
|
-
node: projection.target,
|
|
1192
|
-
edge: projection.edge,
|
|
1193
|
-
cost: (1 - projection.fraction) * costOf(projection.edge)
|
|
1194
|
-
}));
|
|
1195
|
-
}
|
|
1196
|
-
function finishersFor(snap, costOf) {
|
|
1197
|
-
if (snap.kind === "node") return [{ node: snap.node, edge: -1, cost: 0 }];
|
|
1198
|
-
return snap.projections.map((projection) => ({
|
|
1199
|
-
node: projection.source,
|
|
1200
|
-
edge: projection.edge,
|
|
1201
|
-
cost: projection.fraction * costOf(projection.edge)
|
|
1202
|
-
}));
|
|
1203
|
-
}
|
|
1204
|
-
function projectionOf(snap, edge) {
|
|
1205
|
-
return snap.kind === "edge" ? snap.projections.find((projection) => projection.edge === edge) : void 0;
|
|
1206
|
-
}
|
|
1207
|
-
function snapSummary(snap) {
|
|
1208
|
-
return snap.kind === "node" ? { node: snap.node, edge: -1, point: snap.point, distanceMeters: snap.distanceMeters } : { node: -1, edge: snap.projections[0].edge, point: snap.point, distanceMeters: snap.distanceMeters };
|
|
1209
|
-
}
|
|
1210
|
-
function route(graph, from, to, preference = "fastest", options = {}) {
|
|
1211
|
-
assertGraph(graph);
|
|
1212
|
-
const originSnap = snapToGraph(graph, from);
|
|
1213
|
-
if (!originSnap) throw new RouterError("ORIGIN_NOT_ON_GRAPH", "No routable road is close to the origin.");
|
|
1214
|
-
const destinationSnap = snapToGraph(graph, to);
|
|
1215
|
-
if (!destinationSnap) throw new RouterError("DESTINATION_NOT_ON_GRAPH", "No routable road is close to the destination.");
|
|
1216
|
-
const distanceScale = graph.edgeDistanceScale ?? 1;
|
|
1217
|
-
const durationScale = graph.edgeDurationScale ?? 1;
|
|
1218
|
-
const costOf = (edge) => preference === "fastest" ? graph.edgeDurationsSeconds[edge] * durationScale : graph.edgeDistancesMeters[edge] * distanceScale;
|
|
1219
|
-
const seeds = seedsFor(originSnap, costOf);
|
|
1220
|
-
const finisherList = finishersFor(destinationSnap, costOf);
|
|
1221
|
-
const finishers = /* @__PURE__ */ new Map();
|
|
1222
|
-
for (const finisher of finisherList) {
|
|
1223
|
-
const bucket = finishers.get(finisher.node);
|
|
1224
|
-
if (bucket) bucket.push(finisher);
|
|
1225
|
-
else finishers.set(finisher.node, [finisher]);
|
|
1226
|
-
}
|
|
1227
|
-
let direct = null;
|
|
1228
|
-
if (originSnap.kind === "edge" && destinationSnap.kind === "edge") {
|
|
1229
|
-
for (const origin of originSnap.projections) {
|
|
1230
|
-
const destination = projectionOf(destinationSnap, origin.edge);
|
|
1231
|
-
if (!destination || destination.fraction < origin.fraction) continue;
|
|
1232
|
-
const cost = (destination.fraction - origin.fraction) * costOf(origin.edge);
|
|
1233
|
-
if (!direct || cost < direct.cost) direct = { origin, destination, cost };
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
const sameNode = originSnap.kind === "node" && destinationSnap.kind === "node" && originSnap.node === destinationSnap.node;
|
|
1237
|
-
const costBound = options.costBound ?? Infinity;
|
|
1238
|
-
const restrictions = buildTurnRestrictionIndex(graph);
|
|
1239
|
-
const sequenceIndex = buildTurnSequenceIndex(graph);
|
|
1240
|
-
const outcome = sameNode ? { edges: [], finisher: finisherList[0], seed: seeds[0] } : restrictions || sequenceIndex ? searchByEdge(graph, seeds, finishers, destinationSnap.point, preference, restrictions, sequenceIndex, costBound) : searchByNode(graph, seeds, finishers, destinationSnap.point, preference, costBound);
|
|
1241
|
-
if (direct && direct.cost >= costBound) direct = null;
|
|
1242
|
-
if (!outcome && !direct) throw new RouterError("NO_ROUTE", "No car route connects the selected points.");
|
|
1243
|
-
const searchCost = outcome ? outcome.seed.cost + outcome.edges.reduce((sum, edge) => sum + costOf(edge), 0) + outcome.finisher.cost : Infinity;
|
|
1244
|
-
const partial = (projection, fraction) => ({
|
|
1245
|
-
distance: fraction * graph.edgeDistancesMeters[projection.edge] * distanceScale,
|
|
1246
|
-
duration: fraction * graph.edgeDurationsSeconds[projection.edge] * durationScale
|
|
1247
|
-
});
|
|
1248
|
-
const coordinates = [];
|
|
1249
|
-
const push = (point) => coordinates.push([point.lng, point.lat]);
|
|
1250
|
-
const nodeIds = [];
|
|
1251
|
-
let edgeIds = [];
|
|
1252
|
-
let distanceMeters = 0;
|
|
1253
|
-
let durationSeconds = 0;
|
|
1254
|
-
if (direct && direct.cost <= searchCost) {
|
|
1255
|
-
for (const point of geometryBetween(direct.origin, direct.destination)) push(point);
|
|
1256
|
-
const span = partial(direct.origin, direct.destination.fraction - direct.origin.fraction);
|
|
1257
|
-
distanceMeters = span.distance;
|
|
1258
|
-
durationSeconds = span.duration;
|
|
1259
|
-
} else {
|
|
1260
|
-
const { edges, finisher, seed } = outcome;
|
|
1261
|
-
edgeIds = edges;
|
|
1262
|
-
const entry = seed.edge >= 0 ? projectionOf(originSnap, seed.edge) : void 0;
|
|
1263
|
-
push(originSnap.point);
|
|
1264
|
-
if (entry) {
|
|
1265
|
-
for (const point of geometryAfter(entry).slice(1)) push(point);
|
|
1266
|
-
const span = partial(entry, 1 - entry.fraction);
|
|
1267
|
-
distanceMeters += span.distance;
|
|
1268
|
-
durationSeconds += span.duration;
|
|
1269
|
-
nodeIds.push(entry.target);
|
|
1270
|
-
push(coordinateFor2(graph, entry.target));
|
|
1271
|
-
} else {
|
|
1272
|
-
nodeIds.push(seed.node);
|
|
1273
|
-
}
|
|
1274
|
-
const scale = graph.coordinateScale ?? 1;
|
|
1275
|
-
for (const edge of edges) {
|
|
1276
|
-
distanceMeters += graph.edgeDistancesMeters[edge] * distanceScale;
|
|
1277
|
-
durationSeconds += graph.edgeDurationsSeconds[edge] * durationScale;
|
|
1278
|
-
if (graph.edgeShapeOffsets && graph.edgeShapeLatitudes && graph.edgeShapeLongitudes) {
|
|
1279
|
-
for (let point = graph.edgeShapeOffsets[edge]; point < graph.edgeShapeOffsets[edge + 1]; point += 1) {
|
|
1280
|
-
coordinates.push([graph.edgeShapeLongitudes[point] / scale, graph.edgeShapeLatitudes[point] / scale]);
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
nodeIds.push(graph.edgeTargets[edge]);
|
|
1284
|
-
push(coordinateFor2(graph, graph.edgeTargets[edge]));
|
|
1285
|
-
}
|
|
1286
|
-
const exit = finisher.edge >= 0 ? projectionOf(destinationSnap, finisher.edge) : void 0;
|
|
1287
|
-
if (exit) {
|
|
1288
|
-
for (const point of geometryBefore(exit)) push(point);
|
|
1289
|
-
const span = partial(exit, exit.fraction);
|
|
1290
|
-
distanceMeters += span.distance;
|
|
1291
|
-
durationSeconds += span.duration;
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
return {
|
|
1295
|
-
nodeIds,
|
|
1296
|
-
edgeIds,
|
|
1297
|
-
distanceMeters,
|
|
1298
|
-
durationSeconds,
|
|
1299
|
-
originSnap: snapSummary(originSnap),
|
|
1300
|
-
destinationSnap: snapSummary(destinationSnap),
|
|
1301
|
-
geometry: { type: "LineString", coordinates }
|
|
1302
|
-
};
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
export {
|
|
1306
|
-
zigzag,
|
|
1307
|
-
unzigzag,
|
|
1308
|
-
VarintWriter,
|
|
1309
|
-
VarintReader,
|
|
1310
|
-
encodeGraph,
|
|
1311
|
-
decodeGraph,
|
|
1312
|
-
encodeCompactGraph,
|
|
1313
|
-
RouterError,
|
|
1314
|
-
createSpatialIndex,
|
|
1315
|
-
withSpatialIndex,
|
|
1316
|
-
buildTurnRestrictionIndex,
|
|
1317
|
-
isTurnBanned,
|
|
1318
|
-
packTurnRestrictions,
|
|
1319
|
-
buildTurnSequenceIndex,
|
|
1320
|
-
packTurnSequences,
|
|
1321
|
-
parseMaxSpeedKph,
|
|
1322
|
-
carRoutingProfile,
|
|
1323
|
-
routingProfiles,
|
|
1324
|
-
getRoutingProfile,
|
|
1325
|
-
edgeSource,
|
|
1326
|
-
edgePolyline,
|
|
1327
|
-
createEdgeSpatialIndex,
|
|
1328
|
-
edgeSpatialIndexFor,
|
|
1329
|
-
nearbyEdgeIds,
|
|
1330
|
-
snapToGraph,
|
|
1331
|
-
findNearestNode,
|
|
1332
|
-
route
|
|
1333
|
-
};
|