roadgraph-router-v2 0.2.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 +217 -2
- package/README.md +408 -24
- package/dist/{chunk-PXL65P3L.js → chunk-QSIKDM4A.js} +942 -316
- package/dist/index.d.ts +480 -66
- package/dist/index.js +201 -14
- package/dist/worker.js +2 -2
- package/package.json +1 -1
- package/scripts/roadgraph-compile.mjs +30 -7
|
@@ -80,6 +80,365 @@ var VarintReader = class {
|
|
|
80
80
|
}
|
|
81
81
|
};
|
|
82
82
|
|
|
83
|
+
// src/speed.ts
|
|
84
|
+
function parseMaxSpeedKph(value) {
|
|
85
|
+
if (!value) return null;
|
|
86
|
+
const text = value.trim().toLowerCase();
|
|
87
|
+
if (text === "walk") return 7;
|
|
88
|
+
const match = /^(\d+(?:\.\d+)?)\s*(mph|km\/h|kmh|kph|knots)?$/.exec(text);
|
|
89
|
+
if (!match) return null;
|
|
90
|
+
const amount = Number(match[1]);
|
|
91
|
+
if (!Number.isFinite(amount) || amount <= 0) return null;
|
|
92
|
+
if (match[2] === "mph") return amount * 1.609344;
|
|
93
|
+
if (match[2] === "knots") return amount * 1.852;
|
|
94
|
+
return amount;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/conditional.ts
|
|
98
|
+
var MAX_STORED_VALUES = 65536;
|
|
99
|
+
function answered(circumstances) {
|
|
100
|
+
const yes = /* @__PURE__ */ new Set();
|
|
101
|
+
for (const list of [circumstances?.conditions, circumstances?.userGroups, circumstances?.purposes]) {
|
|
102
|
+
for (const name of list ?? []) yes.add(name.trim().toLowerCase());
|
|
103
|
+
}
|
|
104
|
+
return yes;
|
|
105
|
+
}
|
|
106
|
+
var KNOWN_CIRCUMSTANCES = /* @__PURE__ */ new Set([
|
|
107
|
+
"wet",
|
|
108
|
+
"dry",
|
|
109
|
+
"snow",
|
|
110
|
+
"ice",
|
|
111
|
+
"fog",
|
|
112
|
+
"rain",
|
|
113
|
+
"disabled",
|
|
114
|
+
"resident",
|
|
115
|
+
"residents",
|
|
116
|
+
"customers",
|
|
117
|
+
"employees",
|
|
118
|
+
"emergency",
|
|
119
|
+
"permit",
|
|
120
|
+
"school",
|
|
121
|
+
"delivery",
|
|
122
|
+
"destination",
|
|
123
|
+
"agricultural",
|
|
124
|
+
"forestry",
|
|
125
|
+
"loading",
|
|
126
|
+
"unloading"
|
|
127
|
+
]);
|
|
128
|
+
var WEEKDAYS = ["mo", "tu", "we", "th", "fr", "sa", "su"];
|
|
129
|
+
var MONTHS = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"];
|
|
130
|
+
function splitOutsideBrackets(text, separator) {
|
|
131
|
+
const parts = [];
|
|
132
|
+
let depth = 0;
|
|
133
|
+
let start = 0;
|
|
134
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
135
|
+
const character = text[index];
|
|
136
|
+
if (character === "(") depth += 1;
|
|
137
|
+
else if (character === ")") depth = Math.max(0, depth - 1);
|
|
138
|
+
else if (character === separator && depth === 0) {
|
|
139
|
+
parts.push(text.slice(start, index));
|
|
140
|
+
start = index + 1;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
parts.push(text.slice(start));
|
|
144
|
+
return parts.map((part) => part.trim()).filter(Boolean);
|
|
145
|
+
}
|
|
146
|
+
function parseClock(text) {
|
|
147
|
+
const match = /^(\d{1,2}):(\d{2})$/.exec(text.trim());
|
|
148
|
+
if (!match) return null;
|
|
149
|
+
const hours = Number(match[1]);
|
|
150
|
+
const minutes = Number(match[2]);
|
|
151
|
+
if (hours > 24 || minutes > 59) return null;
|
|
152
|
+
if (hours === 24 && minutes !== 0) return null;
|
|
153
|
+
return hours * 60 + minutes;
|
|
154
|
+
}
|
|
155
|
+
function parseIndexedRange(text, table) {
|
|
156
|
+
const [from, to] = text.split("-").map((part) => part.trim().toLowerCase().slice(0, 3));
|
|
157
|
+
const start = table.indexOf(from);
|
|
158
|
+
if (start < 0) return null;
|
|
159
|
+
if (to === void 0) return { from: start, to: start };
|
|
160
|
+
const end = table.indexOf(to);
|
|
161
|
+
return end < 0 ? null : { from: start, to: end };
|
|
162
|
+
}
|
|
163
|
+
function parseVehicleComparison(text) {
|
|
164
|
+
const match = /^(weight|height|width|length)\s*(<=|>=|<|>|=)\s*(\d+(?:\.\d+)?)$/.exec(text.trim().toLowerCase());
|
|
165
|
+
if (!match) return null;
|
|
166
|
+
return { property: match[1], operator: match[2], amount: Number(match[3]) };
|
|
167
|
+
}
|
|
168
|
+
function parseCondition(text) {
|
|
169
|
+
const condition = {};
|
|
170
|
+
const cleaned = text.trim().replace(/^\(|\)$/g, "").trim();
|
|
171
|
+
if (!cleaned) return { unreadable: text };
|
|
172
|
+
if (cleaned.toLowerCase() === "24/7") return { times: [{ from: 0, to: 1440 }] };
|
|
173
|
+
for (const token of cleaned.split(/\s+(?:and|AND)\s+|\s+/).filter(Boolean)) {
|
|
174
|
+
const lower = token.toLowerCase().replace(/,$/, "");
|
|
175
|
+
if (lower === "ph") {
|
|
176
|
+
condition.publicHoliday = true;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const comparison = parseVehicleComparison(lower);
|
|
180
|
+
if (comparison) {
|
|
181
|
+
(condition.vehicle ??= []).push(comparison);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const pieces = lower.split(",").filter(Boolean);
|
|
185
|
+
const spans = [];
|
|
186
|
+
const days = [];
|
|
187
|
+
const months = [];
|
|
188
|
+
let understood = true;
|
|
189
|
+
for (const piece of pieces) {
|
|
190
|
+
const [left, right] = piece.split("-");
|
|
191
|
+
const from = parseClock(left ?? "");
|
|
192
|
+
if (from !== null && right !== void 0) {
|
|
193
|
+
const to = parseClock(right);
|
|
194
|
+
if (to === null) {
|
|
195
|
+
understood = false;
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
spans.push({ from, to });
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const day = parseIndexedRange(piece, WEEKDAYS);
|
|
202
|
+
if (day) {
|
|
203
|
+
days.push(day);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
const month = parseIndexedRange(piece, MONTHS);
|
|
207
|
+
if (month) {
|
|
208
|
+
months.push(month);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (KNOWN_CIRCUMSTANCES.has(piece)) {
|
|
212
|
+
(condition.circumstances ??= []).push(piece);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
understood = false;
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
if (!understood) {
|
|
219
|
+
condition.unreadable = token;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (spans.length) (condition.times ??= []).push(...spans);
|
|
223
|
+
if (days.length) (condition.days ??= []).push(...days);
|
|
224
|
+
if (months.length) (condition.months ??= []).push(...months);
|
|
225
|
+
}
|
|
226
|
+
return condition;
|
|
227
|
+
}
|
|
228
|
+
function parseConditional(value) {
|
|
229
|
+
if (!value || !value.includes("@")) return [];
|
|
230
|
+
const rules = [];
|
|
231
|
+
for (const part of splitOutsideBrackets(value, ";")) {
|
|
232
|
+
const at = part.indexOf("@");
|
|
233
|
+
if (at < 0) continue;
|
|
234
|
+
const left = part.slice(0, at).trim();
|
|
235
|
+
const right = part.slice(at + 1).trim();
|
|
236
|
+
if (!left || !right) continue;
|
|
237
|
+
const grouped = right.startsWith("(") && right.endsWith(")") ? right.slice(1, -1) : right;
|
|
238
|
+
const parts = splitOutsideBrackets(grouped, ";");
|
|
239
|
+
rules.push({ value: left, conditions: (parts.length ? parts : [grouped]).map(parseCondition) });
|
|
240
|
+
}
|
|
241
|
+
return rules;
|
|
242
|
+
}
|
|
243
|
+
var withinRange = (value, range) => range.from <= range.to ? value >= range.from && value <= range.to : value >= range.from || value <= range.to;
|
|
244
|
+
var withinSpan = (minutes, span) => span.from <= span.to ? minutes >= span.from && minutes < span.to : minutes >= span.from || minutes < span.to;
|
|
245
|
+
var YESTERDAY = (weekday) => (weekday + 6) % 7;
|
|
246
|
+
function dayBefore(when) {
|
|
247
|
+
const rolledOver = when.dayOfMonth === 1;
|
|
248
|
+
return {
|
|
249
|
+
weekday: YESTERDAY(when.weekday),
|
|
250
|
+
month: rolledOver ? when.month === 1 ? 12 : when.month - 1 : when.month
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function selects(condition, day) {
|
|
254
|
+
if (condition.days?.length && !condition.days.some((range) => withinRange(day.weekday, range))) return false;
|
|
255
|
+
if (condition.months?.length && !condition.months.some((range) => withinRange(day.month - 1, range))) return false;
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
function dayAndTimeHold(condition, when) {
|
|
259
|
+
if (!condition.times?.length) return selects(condition, when);
|
|
260
|
+
return condition.times.some((span) => {
|
|
261
|
+
if (span.from <= span.to) return selects(condition, when) && withinSpan(when.minutes, span);
|
|
262
|
+
if (when.minutes >= span.from) return selects(condition, when);
|
|
263
|
+
if (when.minutes < span.to) return selects(condition, dayBefore(when));
|
|
264
|
+
return false;
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
function conditionHolds(condition, when, vehicle, circumstances) {
|
|
268
|
+
if (condition.unreadable !== void 0) return false;
|
|
269
|
+
if (condition.publicHoliday && !when.publicHoliday) return false;
|
|
270
|
+
if (condition.circumstances?.length) {
|
|
271
|
+
const yes = answered(circumstances);
|
|
272
|
+
if (!condition.circumstances.every((name) => yes.has(name))) return false;
|
|
273
|
+
}
|
|
274
|
+
if (!dayAndTimeHold(condition, when)) return false;
|
|
275
|
+
if (condition.vehicle?.length) {
|
|
276
|
+
if (!vehicle) return false;
|
|
277
|
+
for (const test of condition.vehicle) {
|
|
278
|
+
const measured = test.property === "weight" ? vehicle.weightTonnes : test.property === "height" ? vehicle.heightMeters : test.property === "width" ? vehicle.widthMeters : vehicle.lengthMeters;
|
|
279
|
+
const holds = test.operator === "<" ? measured < test.amount : test.operator === "<=" ? measured <= test.amount : test.operator === ">" ? measured > test.amount : test.operator === ">=" ? measured >= test.amount : measured === test.amount;
|
|
280
|
+
if (!holds) return false;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
285
|
+
function conditionalValue(rules, when, vehicle, circumstances) {
|
|
286
|
+
let effective = null;
|
|
287
|
+
for (const rule of rules) {
|
|
288
|
+
if (rule.conditions.some((condition) => conditionHolds(condition, when, vehicle, circumstances))) effective = rule.value;
|
|
289
|
+
}
|
|
290
|
+
return effective;
|
|
291
|
+
}
|
|
292
|
+
function unreadableConditions(rules) {
|
|
293
|
+
return rules.reduce((count, rule) => count + rule.conditions.filter((condition) => condition.unreadable !== void 0).length, 0);
|
|
294
|
+
}
|
|
295
|
+
var WEEKDAY_NUMBER = { Mon: 0, Tue: 1, Wed: 2, Thu: 3, Fri: 4, Sat: 5, Sun: 6 };
|
|
296
|
+
function travelTimeFrom(date, publicHoliday = false, timeZone) {
|
|
297
|
+
if (!timeZone) {
|
|
298
|
+
return {
|
|
299
|
+
weekday: (date.getDay() + 6) % 7,
|
|
300
|
+
minutes: date.getHours() * 60 + date.getMinutes(),
|
|
301
|
+
month: date.getMonth() + 1,
|
|
302
|
+
dayOfMonth: date.getDate(),
|
|
303
|
+
publicHoliday
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
const parts = new Intl.DateTimeFormat("en-GB", {
|
|
307
|
+
timeZone,
|
|
308
|
+
weekday: "short",
|
|
309
|
+
year: "numeric",
|
|
310
|
+
month: "numeric",
|
|
311
|
+
day: "numeric",
|
|
312
|
+
hour: "numeric",
|
|
313
|
+
minute: "numeric",
|
|
314
|
+
hour12: false
|
|
315
|
+
}).formatToParts(date);
|
|
316
|
+
const field = (type) => parts.find((part) => part.type === type)?.value ?? "";
|
|
317
|
+
const hour = Number(field("hour")) % 24;
|
|
318
|
+
return {
|
|
319
|
+
weekday: WEEKDAY_NUMBER[field("weekday")] ?? 0,
|
|
320
|
+
minutes: hour * 60 + Number(field("minute")),
|
|
321
|
+
month: Number(field("month")),
|
|
322
|
+
dayOfMonth: Number(field("day")),
|
|
323
|
+
publicHoliday
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
var ConditionKind = { access: 0, maxspeed: 1 };
|
|
327
|
+
function packEdgeConditions(entries) {
|
|
328
|
+
const rows = entries.filter((entry) => parseConditional(entry.value).length > 0).sort((a, b) => a.edge - b.edge || a.kind - b.kind);
|
|
329
|
+
if (!rows.length) return void 0;
|
|
330
|
+
const index = /* @__PURE__ */ new Map();
|
|
331
|
+
const values = [];
|
|
332
|
+
const kept = [];
|
|
333
|
+
for (const row of rows) {
|
|
334
|
+
let at = index.get(row.value);
|
|
335
|
+
if (at === void 0) {
|
|
336
|
+
if (values.length >= MAX_STORED_VALUES) {
|
|
337
|
+
throw new RangeError(
|
|
338
|
+
`A tile holds more than ${MAX_STORED_VALUES} distinct conditional restrictions, which is more than the stored index can name. Compile the region in smaller tiles with --tile-size-km.`
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
at = values.push(row.value) - 1;
|
|
342
|
+
index.set(row.value, at);
|
|
343
|
+
}
|
|
344
|
+
kept.push({ edge: row.edge, kind: row.kind, rule: at });
|
|
345
|
+
}
|
|
346
|
+
if (!kept.length) return void 0;
|
|
347
|
+
return {
|
|
348
|
+
edges: Uint32Array.from(kept, (row) => row.edge),
|
|
349
|
+
kinds: Uint8Array.from(kept, (row) => row.kind),
|
|
350
|
+
rules: Uint16Array.from(kept, (row) => row.rule),
|
|
351
|
+
values
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
function parseEdgeConditions(table) {
|
|
355
|
+
if (!table?.edges.length) return void 0;
|
|
356
|
+
return { table, parsed: table.values.map(parseConditional) };
|
|
357
|
+
}
|
|
358
|
+
function firstRowFor(edges, edge) {
|
|
359
|
+
let low = 0;
|
|
360
|
+
let high = edges.length - 1;
|
|
361
|
+
let found = -1;
|
|
362
|
+
while (low <= high) {
|
|
363
|
+
const middle = low + high >> 1;
|
|
364
|
+
if (edges[middle] >= edge) {
|
|
365
|
+
if (edges[middle] === edge) found = middle;
|
|
366
|
+
high = middle - 1;
|
|
367
|
+
} else low = middle + 1;
|
|
368
|
+
}
|
|
369
|
+
return found;
|
|
370
|
+
}
|
|
371
|
+
var CLOSED = /* @__PURE__ */ new Set(["no", "private", "destination", "customers", "delivery", "permit", "agricultural", "forestry"]);
|
|
372
|
+
function conditionsOnEdge(conditions, edge, when, vehicle, circumstances) {
|
|
373
|
+
const { table, parsed } = conditions;
|
|
374
|
+
let closed = false;
|
|
375
|
+
let speedKph;
|
|
376
|
+
for (let row = firstRowFor(table.edges, edge); row >= 0 && row < table.edges.length && table.edges[row] === edge; row += 1) {
|
|
377
|
+
const value = conditionalValue(parsed[table.rules[row]], when, vehicle, circumstances);
|
|
378
|
+
if (value === null) continue;
|
|
379
|
+
if (table.kinds[row] === ConditionKind.access) {
|
|
380
|
+
if (CLOSED.has(value.trim().toLowerCase())) closed = true;
|
|
381
|
+
} else {
|
|
382
|
+
const parsedSpeed = parseMaxSpeedKph(value);
|
|
383
|
+
if (parsedSpeed !== null && parsedSpeed > 0) speedKph = speedKph === void 0 ? parsedSpeed : Math.min(speedKph, parsedSpeed);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return { closed, speedKph };
|
|
387
|
+
}
|
|
388
|
+
function accessAtTime(graph, conditions, when, vehicle, base, circumstances) {
|
|
389
|
+
const start = base ?? graph.edgeAccess;
|
|
390
|
+
if (!when || !conditions) return start;
|
|
391
|
+
const edges = graph.edgeTargets.length;
|
|
392
|
+
let mask;
|
|
393
|
+
const { table } = conditions;
|
|
394
|
+
for (let row = 0; row < table.edges.length; row += 1) {
|
|
395
|
+
const edge = table.edges[row];
|
|
396
|
+
if (edge >= edges || table.kinds[row] !== ConditionKind.access) continue;
|
|
397
|
+
if (mask && !edgeAllowed(mask, edge)) continue;
|
|
398
|
+
if (!conditionsOnEdge(conditions, edge, when, vehicle, circumstances).closed) continue;
|
|
399
|
+
mask ??= start ? Uint8Array.from(start) : accessMaskFor(edges, () => true);
|
|
400
|
+
mask[edge >> 3] &= ~(1 << (edge & 7));
|
|
401
|
+
}
|
|
402
|
+
return mask ?? start;
|
|
403
|
+
}
|
|
404
|
+
function countClosedAt(graph, conditions, when, vehicle) {
|
|
405
|
+
const before = graph.edgeAccess;
|
|
406
|
+
const after = accessAtTime(graph, conditions, when, vehicle);
|
|
407
|
+
if (after === before) return 0;
|
|
408
|
+
let closed = 0;
|
|
409
|
+
for (let edge = 0; edge < graph.edgeTargets.length; edge += 1) {
|
|
410
|
+
if (edgeAllowed(before, edge) && !edgeAllowed(after, edge)) closed += 1;
|
|
411
|
+
}
|
|
412
|
+
return closed;
|
|
413
|
+
}
|
|
414
|
+
var parsedByTable = /* @__PURE__ */ new WeakMap();
|
|
415
|
+
function conditionsOf(graph) {
|
|
416
|
+
const table = graph.edgeConditions;
|
|
417
|
+
if (!table?.edges.length) return void 0;
|
|
418
|
+
const existing = parsedByTable.get(table);
|
|
419
|
+
if (existing) return existing;
|
|
420
|
+
const parsed = parseEdgeConditions(table);
|
|
421
|
+
parsedByTable.set(table, parsed);
|
|
422
|
+
return parsed;
|
|
423
|
+
}
|
|
424
|
+
function conditionalDurations(graph, conditions, when, vehicle, circumstances) {
|
|
425
|
+
if (!conditions) return void 0;
|
|
426
|
+
const distanceScale = graph.edgeDistanceScale ?? 1;
|
|
427
|
+
const durationScale = graph.edgeDurationScale ?? 1;
|
|
428
|
+
const slowed = /* @__PURE__ */ new Map();
|
|
429
|
+
const { table } = conditions;
|
|
430
|
+
for (let row = 0; row < table.edges.length; row += 1) {
|
|
431
|
+
const edge = table.edges[row];
|
|
432
|
+
if (table.kinds[row] !== ConditionKind.maxspeed || edge >= graph.edgeTargets.length || slowed.has(edge)) continue;
|
|
433
|
+
const { speedKph } = conditionsOnEdge(conditions, edge, when, vehicle, circumstances);
|
|
434
|
+
if (speedKph === void 0) continue;
|
|
435
|
+
const stored = graph.edgeDurationsSeconds[edge] * durationScale;
|
|
436
|
+
const limited = graph.edgeDistancesMeters[edge] * distanceScale / (speedKph / 3.6);
|
|
437
|
+
if (limited > stored) slowed.set(edge, limited);
|
|
438
|
+
}
|
|
439
|
+
return slowed.size ? slowed : void 0;
|
|
440
|
+
}
|
|
441
|
+
|
|
83
442
|
// src/routing-profile.ts
|
|
84
443
|
var ProfileId = {
|
|
85
444
|
unspecified: 0,
|
|
@@ -164,18 +523,6 @@ function clampSpeed(value) {
|
|
|
164
523
|
if (!Number.isFinite(value)) return MINIMUM_SPEED_KPH;
|
|
165
524
|
return Math.min(MAXIMUM_SPEED_KPH, Math.max(MINIMUM_SPEED_KPH, value));
|
|
166
525
|
}
|
|
167
|
-
function parseMaxSpeedKph(value) {
|
|
168
|
-
if (!value) return null;
|
|
169
|
-
const text = value.trim().toLowerCase();
|
|
170
|
-
if (text === "walk") return 7;
|
|
171
|
-
const match = /^(\d+(?:\.\d+)?)\s*(mph|km\/h|kmh|kph|knots)?$/.exec(text);
|
|
172
|
-
if (!match) return null;
|
|
173
|
-
const amount = Number(match[1]);
|
|
174
|
-
if (!Number.isFinite(amount) || amount <= 0) return null;
|
|
175
|
-
if (match[2] === "mph") return amount * 1.609344;
|
|
176
|
-
if (match[2] === "knots") return amount * 1.852;
|
|
177
|
-
return amount;
|
|
178
|
-
}
|
|
179
526
|
var UNMEASURED = /* @__PURE__ */ new Set(["default", "below_default", "none", "unsigned", "no_sign", "no", "yes", "unknown"]);
|
|
180
527
|
var FEET_METERS = 0.3048;
|
|
181
528
|
var INCH_METERS = 0.0254;
|
|
@@ -226,6 +573,22 @@ function limitsFor(tags, mode, direction) {
|
|
|
226
573
|
}
|
|
227
574
|
return found ? limits : void 0;
|
|
228
575
|
}
|
|
576
|
+
function conditionsFor(tags, accessKeys, speedKeys) {
|
|
577
|
+
const found = [];
|
|
578
|
+
let access;
|
|
579
|
+
for (const key of accessKeys) {
|
|
580
|
+
const value = tags[`${key}:conditional`];
|
|
581
|
+
if (value !== void 0) access = value;
|
|
582
|
+
}
|
|
583
|
+
if (access) found.push({ kind: ConditionKind.access, value: access });
|
|
584
|
+
let speed;
|
|
585
|
+
for (const key of speedKeys) {
|
|
586
|
+
const value = tags[`${key}:conditional`];
|
|
587
|
+
if (value !== void 0) speed = value;
|
|
588
|
+
}
|
|
589
|
+
if (speed) found.push({ kind: ConditionKind.maxspeed, value: speed });
|
|
590
|
+
return found.length ? found : void 0;
|
|
591
|
+
}
|
|
229
592
|
function accessAllowed(tags, keys) {
|
|
230
593
|
let allowed = true;
|
|
231
594
|
for (const key of keys) {
|
|
@@ -268,9 +631,14 @@ function motorVehicleProfile(options) {
|
|
|
268
631
|
`maxspeed:${mode}`,
|
|
269
632
|
...accessKeys,
|
|
270
633
|
...onewayKeys,
|
|
634
|
+
// The conditional forms of the same keys, which the router reads against
|
|
635
|
+
// a departure time rather than the compiler against nothing.
|
|
636
|
+
...accessKeys.map((key) => `${key}:conditional`),
|
|
637
|
+
"maxspeed:conditional",
|
|
638
|
+
`maxspeed:${mode}:conditional`,
|
|
271
639
|
...options.physicalLimits ? LIMIT_KEYS.flatMap(([, key]) => [key, `${key}:${mode}`, `${key}:forward`, `${key}:backward`, ...key === "maxheight" || key === "maxwidth" ? [`${key}:physical`] : []]) : []
|
|
272
640
|
],
|
|
273
|
-
relationTagKeys: ["type", "except", ...restrictionKeys],
|
|
641
|
+
relationTagKeys: ["type", "except", ...restrictionKeys, ...restrictionKeys.map((key) => `${key}:conditional`)],
|
|
274
642
|
maxSpeedKph: ceiling,
|
|
275
643
|
nodeTagKeys: BARRIER_TAG_KEYS,
|
|
276
644
|
evaluateBarrier: (tags) => barrierAllows(tags, accessKeys, "motor"),
|
|
@@ -293,7 +661,8 @@ function motorVehicleProfile(options) {
|
|
|
293
661
|
...options.physicalLimits ? {
|
|
294
662
|
forwardLimits: limitsFor(tags, mode, "forward"),
|
|
295
663
|
backwardLimits: limitsFor(tags, mode, "backward")
|
|
296
|
-
} : {}
|
|
664
|
+
} : {},
|
|
665
|
+
.../* @__PURE__ */ ((conditions) => conditions ? { conditions } : {})(conditionsFor(tags, accessKeys, ["maxspeed", `maxspeed:${mode}`]))
|
|
297
666
|
};
|
|
298
667
|
},
|
|
299
668
|
evaluateTurnRestriction(tags) {
|
|
@@ -369,8 +738,16 @@ function activeTravelProfile(options) {
|
|
|
369
738
|
return {
|
|
370
739
|
name,
|
|
371
740
|
id,
|
|
372
|
-
wayTagKeys: [
|
|
373
|
-
|
|
741
|
+
wayTagKeys: [
|
|
742
|
+
"highway",
|
|
743
|
+
"junction",
|
|
744
|
+
"maxspeed",
|
|
745
|
+
...accessKeys,
|
|
746
|
+
"oneway",
|
|
747
|
+
...onewayKeys,
|
|
748
|
+
...accessKeys.map((key) => `${key}:conditional`)
|
|
749
|
+
],
|
|
750
|
+
relationTagKeys: ["type", "except", ...restrictionKeys, ...restrictionKeys.map((key) => `${key}:conditional`)],
|
|
374
751
|
maxSpeedKph: ceilingKph,
|
|
375
752
|
nodeTagKeys: BARRIER_TAG_KEYS,
|
|
376
753
|
evaluateBarrier: (tags) => barrierAllows(tags, accessKeys, mode === "foot" ? "foot" : "bicycle"),
|
|
@@ -391,7 +768,8 @@ function activeTravelProfile(options) {
|
|
|
391
768
|
const posted = parseMaxSpeedKph(tags.maxspeed);
|
|
392
769
|
const wanted = Math.min(speeds[highway], posted !== null && posted > 0 ? posted : Infinity);
|
|
393
770
|
const speed = Math.min(ceilingKph, Math.max(floorKph, wanted));
|
|
394
|
-
|
|
771
|
+
const conditions = conditionsFor(tags, accessKeys, []);
|
|
772
|
+
return { forward, backward, forwardSpeedKph: speed, backwardSpeedKph: speed, ...conditions ? { conditions } : {} };
|
|
395
773
|
},
|
|
396
774
|
evaluateTurnRestriction(tags) {
|
|
397
775
|
if (tags.type !== "restriction") return null;
|
|
@@ -685,6 +1063,32 @@ function buildShapeTable(graph) {
|
|
|
685
1063
|
function encodeSharedGraph(graph) {
|
|
686
1064
|
return encodePackedGraph(graph, SPLIT_COMPACT_VERSION);
|
|
687
1065
|
}
|
|
1066
|
+
function assertEdgeConditions(conditions, edges) {
|
|
1067
|
+
const rows = conditions.edges.length;
|
|
1068
|
+
if (conditions.kinds.length !== rows || conditions.rules.length !== rows) throw new Error("Edge condition columns must all be the same length.");
|
|
1069
|
+
for (let index = 0; index < rows; index += 1) {
|
|
1070
|
+
if (conditions.edges[index] >= edges) throw new Error("Edge condition references an edge outside the graph.");
|
|
1071
|
+
if (index && conditions.edges[index] < conditions.edges[index - 1]) throw new Error("Edge conditions must be sorted by edge.");
|
|
1072
|
+
if (conditions.rules[index] >= conditions.values.length) throw new Error("Edge condition names a value that is not in the table.");
|
|
1073
|
+
}
|
|
1074
|
+
if (conditions.values.some((value) => value.includes("\0"))) throw new Error("A conditional value cannot contain a null byte.");
|
|
1075
|
+
}
|
|
1076
|
+
function readEdgeConditions(buffer, at, edges) {
|
|
1077
|
+
const view = new DataView(buffer);
|
|
1078
|
+
const rows = view.getUint32(at, true);
|
|
1079
|
+
const textBytes = view.getUint32(at + 4, true);
|
|
1080
|
+
const conditionEdges = new Uint32Array(buffer.slice(at + 8, at + 8 + rows * 4));
|
|
1081
|
+
for (let index = 0; index < rows; index += 1) {
|
|
1082
|
+
if (conditionEdges[index] >= edges) throw new Error("Invalid routing graph: condition references an edge outside the graph.");
|
|
1083
|
+
if (index && conditionEdges[index] < conditionEdges[index - 1]) throw new Error("Invalid routing graph: conditions are not sorted.");
|
|
1084
|
+
}
|
|
1085
|
+
const values = new TextDecoder().decode(new Uint8Array(buffer, alignUp(at + 8 + rows * 7), textBytes)).split("\0");
|
|
1086
|
+
const rules = new Uint16Array(buffer.slice(at + 8 + rows * 4, at + 8 + rows * 6));
|
|
1087
|
+
for (let index = 0; index < rows; index += 1) {
|
|
1088
|
+
if (rules[index] >= values.length) throw new Error("Invalid routing graph: condition names a value that is not in the table.");
|
|
1089
|
+
}
|
|
1090
|
+
return { edges: conditionEdges, rules, kinds: new Uint8Array(buffer.slice(at + 8 + rows * 6, at + 8 + rows * 7)), values };
|
|
1091
|
+
}
|
|
688
1092
|
function encodePackedGraph(graph, version = PACKED_COMPACT_VERSION) {
|
|
689
1093
|
const nodes = graph.nodeLatitudes.length;
|
|
690
1094
|
const edges = graph.edgeTargets.length;
|
|
@@ -741,7 +1145,14 @@ function encodePackedGraph(graph, version = PACKED_COMPACT_VERSION) {
|
|
|
741
1145
|
const variableBytes = sections.reduce((sum, section) => sum + section.byteLength, 0);
|
|
742
1146
|
const weightBytes = edges * (shared ? 2 : 4);
|
|
743
1147
|
const restrictionsAt = alignUp(PACKED_HEADER_BYTES + (shared ? 0 : nodes * 8) + weightBytes + variableBytes);
|
|
744
|
-
const
|
|
1148
|
+
const restrictionsEnd = restrictionsAt + pairs * 8 + (runs ? (runs + 1) * 4 + runEdges * 4 : 0);
|
|
1149
|
+
const conditions = !shared && graph.edgeConditions?.edges.length ? graph.edgeConditions : void 0;
|
|
1150
|
+
if (conditions) assertEdgeConditions(conditions, edges);
|
|
1151
|
+
const conditionRows = conditions?.edges.length ?? 0;
|
|
1152
|
+
const conditionText = conditions ? new TextEncoder().encode(conditions.values.join("\0")) : void 0;
|
|
1153
|
+
const conditionsAt = alignUp(restrictionsEnd);
|
|
1154
|
+
const conditionTextAt = alignUp(conditionsAt + 8 + conditionRows * 7);
|
|
1155
|
+
const buffer = new ArrayBuffer(conditionRows ? conditionTextAt + (conditionText?.byteLength ?? 0) : restrictionsEnd);
|
|
745
1156
|
const view = new DataView(buffer);
|
|
746
1157
|
view.setUint32(0, COMPACT_MAGIC, true);
|
|
747
1158
|
view.setUint16(4, version, true);
|
|
@@ -758,6 +1169,7 @@ function encodePackedGraph(graph, version = PACKED_COMPACT_VERSION) {
|
|
|
758
1169
|
for (let index = 0; index < sections.length; index += 1) view.setUint32(40 + index * 4, sections[index].byteLength, true);
|
|
759
1170
|
if (!shared) {
|
|
760
1171
|
view.setUint8(60, graph.profileId ?? 0);
|
|
1172
|
+
view.setUint8(61, conditionRows ? 1 : 0);
|
|
761
1173
|
view.setUint16(62, Math.round(graph.maxSpeedKph ?? 0), true);
|
|
762
1174
|
}
|
|
763
1175
|
let offset = PACKED_HEADER_BYTES;
|
|
@@ -790,6 +1202,14 @@ function encodePackedGraph(graph, version = PACKED_COMPACT_VERSION) {
|
|
|
790
1202
|
offset += (runs + 1) * 4;
|
|
791
1203
|
new Uint32Array(buffer, offset, runEdges).set(graph.turnRestrictionSequenceEdges);
|
|
792
1204
|
}
|
|
1205
|
+
if (conditions && conditionRows && conditionText) {
|
|
1206
|
+
view.setUint32(conditionsAt, conditionRows, true);
|
|
1207
|
+
view.setUint32(conditionsAt + 4, conditionText.byteLength, true);
|
|
1208
|
+
new Uint32Array(buffer, conditionsAt + 8, conditionRows).set(conditions.edges);
|
|
1209
|
+
new Uint16Array(buffer, conditionsAt + 8 + conditionRows * 4, conditionRows).set(conditions.rules);
|
|
1210
|
+
new Uint8Array(buffer, conditionsAt + 8 + conditionRows * 6, conditionRows).set(conditions.kinds);
|
|
1211
|
+
new Uint8Array(buffer, conditionTextAt, conditionText.byteLength).set(conditionText);
|
|
1212
|
+
}
|
|
793
1213
|
return buffer;
|
|
794
1214
|
}
|
|
795
1215
|
function decodePackedGraph(buffer, shared = false) {
|
|
@@ -806,7 +1226,17 @@ function decodePackedGraph(buffer, shared = false) {
|
|
|
806
1226
|
const lengths = Array.from({ length: shared ? 6 : 5 }, (_, index) => view.getUint32(40 + index * 4, true));
|
|
807
1227
|
const variableBytes = lengths.reduce((sum, length) => sum + length, 0);
|
|
808
1228
|
const restrictionsAt = alignUp(PACKED_HEADER_BYTES + (shared ? 0 : nodes * 8) + edges * (shared ? 2 : 4) + variableBytes);
|
|
809
|
-
const
|
|
1229
|
+
const restrictionsEnd = restrictionsAt + pairs * 8 + (runs ? (runs + 1) * 4 + runEdges * 4 : 0);
|
|
1230
|
+
const hasConditions = !shared && view.getUint8(61) === 1;
|
|
1231
|
+
const conditionsAt = alignUp(restrictionsEnd);
|
|
1232
|
+
let expected = restrictionsEnd;
|
|
1233
|
+
if (hasConditions) {
|
|
1234
|
+
if (buffer.byteLength < conditionsAt + 8) throw new Error("Truncated compact routing graph");
|
|
1235
|
+
const rows = view.getUint32(conditionsAt, true);
|
|
1236
|
+
const textBytes = view.getUint32(conditionsAt + 4, true);
|
|
1237
|
+
if (rows > edges || !textBytes) throw new Error("Invalid compact routing graph: conditions do not fit the graph.");
|
|
1238
|
+
expected = alignUp(conditionsAt + 8 + rows * 7) + textBytes;
|
|
1239
|
+
}
|
|
810
1240
|
if (!coordinateScale || !edgeDistanceScale || !edgeDurationScale || buffer.byteLength !== expected) throw new Error("Truncated compact routing graph");
|
|
811
1241
|
if (shapeCount > edges) throw new Error("Invalid compact routing graph: more shapes than edges.");
|
|
812
1242
|
const profileId = shared ? 0 : view.getUint8(60);
|
|
@@ -935,6 +1365,7 @@ function decodePackedGraph(buffer, shared = false) {
|
|
|
935
1365
|
}
|
|
936
1366
|
for (const edge of graph.turnRestrictionFromEdges ?? []) if (edge >= edges) throw new Error("Invalid routing graph: turn restriction references an edge outside the graph.");
|
|
937
1367
|
for (const edge of graph.turnRestrictionToEdges ?? []) if (edge >= edges) throw new Error("Invalid routing graph: turn restriction references an edge outside the graph.");
|
|
1368
|
+
if (hasConditions) graph.edgeConditions = readEdgeConditions(buffer, conditionsAt, edges);
|
|
938
1369
|
return graph;
|
|
939
1370
|
}
|
|
940
1371
|
var PROFILE_CHUNK_HEADER_BYTES = 64;
|
|
@@ -963,7 +1394,14 @@ function encodeProfileChunk(chunk) {
|
|
|
963
1394
|
const variableAt = PROFILE_CHUNK_HEADER_BYTES + edges * 2 + (mask?.byteLength ?? 0) + (turns?.byteLength ?? 0);
|
|
964
1395
|
const limitsAt = alignUp(variableAt);
|
|
965
1396
|
const limitRows = limits?.edges.length ?? 0;
|
|
966
|
-
const
|
|
1397
|
+
const limitsEnd = limitRows ? limitsAt + limitRows * 8 : variableAt;
|
|
1398
|
+
const conditions = chunk.edgeConditions?.edges.length ? chunk.edgeConditions : void 0;
|
|
1399
|
+
if (conditions) assertEdgeConditions(conditions, edges);
|
|
1400
|
+
const conditionRows = conditions?.edges.length ?? 0;
|
|
1401
|
+
const conditionText = conditions ? new TextEncoder().encode(conditions.values.join("\0")) : void 0;
|
|
1402
|
+
const conditionsAt = alignUp(limitsEnd);
|
|
1403
|
+
const conditionTextAt = alignUp(conditionsAt + conditionRows * 7);
|
|
1404
|
+
const buffer = new ArrayBuffer(conditionRows ? conditionTextAt + (conditionText?.byteLength ?? 0) : limitsEnd);
|
|
967
1405
|
const view = new DataView(buffer);
|
|
968
1406
|
view.setUint32(0, COMPACT_MAGIC, true);
|
|
969
1407
|
view.setUint16(4, SPLIT_COMPACT_VERSION, true);
|
|
@@ -976,6 +1414,8 @@ function encodeProfileChunk(chunk) {
|
|
|
976
1414
|
view.setUint32(20, limitRows, true);
|
|
977
1415
|
view.setUint32(24, restrictions, true);
|
|
978
1416
|
view.setUint32(28, turns?.byteLength ?? 0, true);
|
|
1417
|
+
view.setUint32(32, conditionRows, true);
|
|
1418
|
+
view.setUint32(36, conditionText?.byteLength ?? 0, true);
|
|
979
1419
|
new Uint16Array(buffer, PROFILE_CHUNK_HEADER_BYTES, edges).set(chunk.edgeDurationsSeconds);
|
|
980
1420
|
if (mask) new Uint8Array(buffer, PROFILE_CHUNK_HEADER_BYTES + edges * 2, mask.byteLength).set(mask);
|
|
981
1421
|
if (turns) new Uint8Array(buffer, PROFILE_CHUNK_HEADER_BYTES + edges * 2 + (mask?.byteLength ?? 0), turns.byteLength).set(turns);
|
|
@@ -987,6 +1427,12 @@ function encodeProfileChunk(chunk) {
|
|
|
987
1427
|
columns.set(limits.lengthDecimetres, limitRows * 2);
|
|
988
1428
|
columns.set(limits.weightHalfTonnes, limitRows * 3);
|
|
989
1429
|
}
|
|
1430
|
+
if (conditions && conditionRows && conditionText) {
|
|
1431
|
+
new Uint32Array(buffer, conditionsAt, conditionRows).set(conditions.edges);
|
|
1432
|
+
new Uint16Array(buffer, conditionsAt + conditionRows * 4, conditionRows).set(conditions.rules);
|
|
1433
|
+
new Uint8Array(buffer, conditionsAt + conditionRows * 6, conditionRows).set(conditions.kinds);
|
|
1434
|
+
new Uint8Array(buffer, conditionTextAt, conditionText.byteLength).set(conditionText);
|
|
1435
|
+
}
|
|
990
1436
|
return buffer;
|
|
991
1437
|
}
|
|
992
1438
|
function decodeProfileChunk(buffer) {
|
|
@@ -1003,6 +1449,8 @@ function decodeProfileChunk(buffer) {
|
|
|
1003
1449
|
const limitRows = view.getUint32(20, true);
|
|
1004
1450
|
const restrictionCount = view.getUint32(24, true);
|
|
1005
1451
|
const turnMaskBytes = view.getUint32(28, true);
|
|
1452
|
+
const conditionRows = view.getUint32(32, true);
|
|
1453
|
+
const conditionTextBytes = view.getUint32(36, true);
|
|
1006
1454
|
if (!profileId || !getRoutingProfileById(profileId)) throw new Error(`Routing graph was compiled for an unsupported profile (id ${profileId}).`);
|
|
1007
1455
|
if (!edgeDurationScale) throw new Error("Invalid routing profile chunk: zero duration scale.");
|
|
1008
1456
|
if (maxSpeedKph > MAX_PLAUSIBLE_SPEED_KPH) throw new Error("Invalid routing profile chunk: implausible profile speed ceiling.");
|
|
@@ -1010,8 +1458,12 @@ function decodeProfileChunk(buffer) {
|
|
|
1010
1458
|
if (turnMaskBytes && turnMaskBytes !== restrictionCount + 7 >> 3) throw new Error("Invalid routing profile chunk: restriction mask does not cover every restriction.");
|
|
1011
1459
|
const variableAt = PROFILE_CHUNK_HEADER_BYTES + edges * 2 + maskBytes + turnMaskBytes;
|
|
1012
1460
|
const limitsAt = alignUp(variableAt);
|
|
1013
|
-
const
|
|
1461
|
+
const limitsEnd = limitRows ? limitsAt + limitRows * 8 : variableAt;
|
|
1462
|
+
const conditionsAt = alignUp(limitsEnd);
|
|
1463
|
+
const conditionTextAt = alignUp(conditionsAt + conditionRows * 7);
|
|
1464
|
+
const expected = conditionRows ? conditionTextAt + conditionTextBytes : limitsEnd;
|
|
1014
1465
|
if (limitRows > edges) throw new Error("Invalid routing profile chunk: more limits than edges.");
|
|
1466
|
+
if (conditionRows && !conditionTextBytes) throw new Error("Invalid routing profile chunk: conditions with no values.");
|
|
1015
1467
|
if (buffer.byteLength !== expected) throw new Error("Truncated routing profile chunk");
|
|
1016
1468
|
let edgeLimits;
|
|
1017
1469
|
if (limitRows) {
|
|
@@ -1029,6 +1481,25 @@ function decodeProfileChunk(buffer) {
|
|
|
1029
1481
|
weightHalfTonnes: columns.slice(limitRows * 3)
|
|
1030
1482
|
};
|
|
1031
1483
|
}
|
|
1484
|
+
let edgeConditions;
|
|
1485
|
+
if (conditionRows) {
|
|
1486
|
+
const conditionEdges = new Uint32Array(buffer.slice(conditionsAt, conditionsAt + conditionRows * 4));
|
|
1487
|
+
for (let index = 0; index < conditionRows; index += 1) {
|
|
1488
|
+
if (conditionEdges[index] >= edges) throw new Error("Invalid routing profile chunk: condition references an edge outside the graph.");
|
|
1489
|
+
if (index && conditionEdges[index] < conditionEdges[index - 1]) throw new Error("Invalid routing profile chunk: conditions are not sorted.");
|
|
1490
|
+
}
|
|
1491
|
+
const values = new TextDecoder().decode(new Uint8Array(buffer, conditionTextAt, conditionTextBytes)).split("\0");
|
|
1492
|
+
const rules = new Uint16Array(buffer.slice(conditionsAt + conditionRows * 4, conditionsAt + conditionRows * 6));
|
|
1493
|
+
for (let index = 0; index < conditionRows; index += 1) {
|
|
1494
|
+
if (rules[index] >= values.length) throw new Error("Invalid routing profile chunk: condition names a value that is not in the table.");
|
|
1495
|
+
}
|
|
1496
|
+
edgeConditions = {
|
|
1497
|
+
edges: conditionEdges,
|
|
1498
|
+
rules,
|
|
1499
|
+
kinds: new Uint8Array(buffer.slice(conditionsAt + conditionRows * 6, conditionsAt + conditionRows * 7)),
|
|
1500
|
+
values
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1032
1503
|
return {
|
|
1033
1504
|
profileId,
|
|
1034
1505
|
maxSpeedKph,
|
|
@@ -1037,7 +1508,8 @@ function decodeProfileChunk(buffer) {
|
|
|
1037
1508
|
...maskBytes ? { edgeAccess: new Uint8Array(buffer.slice(PROFILE_CHUNK_HEADER_BYTES + edges * 2, PROFILE_CHUNK_HEADER_BYTES + edges * 2 + maskBytes)) } : {},
|
|
1038
1509
|
...restrictionCount ? { restrictionCount } : {},
|
|
1039
1510
|
...turnMaskBytes ? { restrictionMask: new Uint8Array(buffer.slice(PROFILE_CHUNK_HEADER_BYTES + edges * 2 + maskBytes, variableAt)) } : {},
|
|
1040
|
-
...edgeLimits ? { edgeLimits } : {}
|
|
1511
|
+
...edgeLimits ? { edgeLimits } : {},
|
|
1512
|
+
...edgeConditions ? { edgeConditions } : {}
|
|
1041
1513
|
};
|
|
1042
1514
|
}
|
|
1043
1515
|
function decodeSharedGraph(buffer) {
|
|
@@ -1061,6 +1533,7 @@ function combineProfiledGraph(shared, profile) {
|
|
|
1061
1533
|
maxSpeedKph: profile.maxSpeedKph,
|
|
1062
1534
|
...profile.edgeAccess ? { edgeAccess: profile.edgeAccess } : {},
|
|
1063
1535
|
...profile.edgeLimits ? { edgeLimits: profile.edgeLimits } : {},
|
|
1536
|
+
...profile.edgeConditions ? { edgeConditions: profile.edgeConditions } : {},
|
|
1064
1537
|
...profile.restrictionMask ? { restrictionMask: profile.restrictionMask } : {}
|
|
1065
1538
|
};
|
|
1066
1539
|
}
|
|
@@ -1153,303 +1626,144 @@ function componentSizes(labels) {
|
|
|
1153
1626
|
return sizes;
|
|
1154
1627
|
}
|
|
1155
1628
|
|
|
1156
|
-
// src/
|
|
1157
|
-
var
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
if (metres === void 0 || !(metres > 0)) return 0;
|
|
1161
|
-
const value = Math.floor(metres * 10);
|
|
1162
|
-
return value > DECIMETRE_CEILING ? 0 : value;
|
|
1163
|
-
}
|
|
1164
|
-
function toHalfTonnes(tonnes) {
|
|
1165
|
-
if (tonnes === void 0 || !(tonnes > 0)) return 0;
|
|
1166
|
-
const value = Math.floor(tonnes * 2);
|
|
1167
|
-
return value > HALF_TONNE_CEILING ? 0 : value;
|
|
1168
|
-
}
|
|
1169
|
-
var fromDecimetres = (value) => value / 10;
|
|
1170
|
-
var fromHalfTonnes = (value) => value / 2;
|
|
1171
|
-
function packEdgeLimits(entries) {
|
|
1172
|
-
const rows = entries.map(({ edge, limits }) => ({
|
|
1173
|
-
edge,
|
|
1174
|
-
height: toDecimetres(limits.heightMeters),
|
|
1175
|
-
width: toDecimetres(limits.widthMeters),
|
|
1176
|
-
length: toDecimetres(limits.lengthMeters),
|
|
1177
|
-
weight: toHalfTonnes(limits.weightTonnes)
|
|
1178
|
-
})).filter((row) => row.height || row.width || row.length || row.weight).sort((a, b) => a.edge - b.edge);
|
|
1179
|
-
if (!rows.length) return void 0;
|
|
1180
|
-
return {
|
|
1181
|
-
edges: Uint32Array.from(rows, (row) => row.edge),
|
|
1182
|
-
heightDecimetres: Uint8Array.from(rows, (row) => row.height),
|
|
1183
|
-
widthDecimetres: Uint8Array.from(rows, (row) => row.width),
|
|
1184
|
-
lengthDecimetres: Uint8Array.from(rows, (row) => row.length),
|
|
1185
|
-
weightHalfTonnes: Uint8Array.from(rows, (row) => row.weight)
|
|
1186
|
-
};
|
|
1187
|
-
}
|
|
1188
|
-
function blocks(limits, index, vehicle) {
|
|
1189
|
-
const height = limits.heightDecimetres[index];
|
|
1190
|
-
if (height && vehicle.heightMeters > fromDecimetres(height)) return true;
|
|
1191
|
-
const width = limits.widthDecimetres[index];
|
|
1192
|
-
if (width && vehicle.widthMeters > fromDecimetres(width)) return true;
|
|
1193
|
-
const length = limits.lengthDecimetres[index];
|
|
1194
|
-
if (length && vehicle.lengthMeters > fromDecimetres(length)) return true;
|
|
1195
|
-
const weight = limits.weightHalfTonnes[index];
|
|
1196
|
-
return Boolean(weight) && vehicle.weightTonnes > fromHalfTonnes(weight);
|
|
1629
|
+
// src/edge-index.ts
|
|
1630
|
+
var MAX_CELLS_PER_EDGE = 4096;
|
|
1631
|
+
function cellKey(latitudeCell, longitudeCell) {
|
|
1632
|
+
return `${latitudeCell}:${longitudeCell}`;
|
|
1197
1633
|
}
|
|
1198
|
-
function
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
if (edge < edges && blocks(limits, index, vehicle)) mask[edge >> 3] &= ~(1 << (edge & 7));
|
|
1634
|
+
function edgeSource(graph, edge) {
|
|
1635
|
+
let low = 0;
|
|
1636
|
+
let high = graph.nodeLatitudes.length - 1;
|
|
1637
|
+
while (low < high) {
|
|
1638
|
+
const middle = low + high + 1 >> 1;
|
|
1639
|
+
if (graph.edgeOffsets[middle] <= edge) low = middle;
|
|
1640
|
+
else high = middle - 1;
|
|
1206
1641
|
}
|
|
1207
|
-
return
|
|
1208
|
-
}
|
|
1209
|
-
function countBlocked(graph, vehicle) {
|
|
1210
|
-
const mask = accessForVehicle(graph, vehicle);
|
|
1211
|
-
if (!mask) return 0;
|
|
1212
|
-
let blocked = 0;
|
|
1213
|
-
for (let edge = 0; edge < graph.edgeTargets.length; edge += 1) if (!edgeAllowed(mask, edge)) blocked += 1;
|
|
1214
|
-
return blocked;
|
|
1642
|
+
return low;
|
|
1215
1643
|
}
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1644
|
+
function edgePolyline(graph, edge, source = edgeSource(graph, edge)) {
|
|
1645
|
+
const scale = graph.coordinateScale ?? 1;
|
|
1646
|
+
const target = graph.edgeTargets[edge];
|
|
1647
|
+
const points = [{ lat: graph.nodeLatitudes[source] / scale, lng: graph.nodeLongitudes[source] / scale }];
|
|
1648
|
+
if (graph.edgeShapeOffsets && graph.edgeShapeLatitudes && graph.edgeShapeLongitudes) {
|
|
1649
|
+
for (let point = graph.edgeShapeOffsets[edge]; point < graph.edgeShapeOffsets[edge + 1]; point += 1) {
|
|
1650
|
+
points.push({ lat: graph.edgeShapeLatitudes[point] / scale, lng: graph.edgeShapeLongitudes[point] / scale });
|
|
1651
|
+
}
|
|
1223
1652
|
}
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
// src/spatial-index.ts
|
|
1228
|
-
function cellKey(latitude, longitude, cellSizeDegrees) {
|
|
1229
|
-
return `${Math.floor(latitude / cellSizeDegrees)}:${Math.floor(longitude / cellSizeDegrees)}`;
|
|
1653
|
+
points.push({ lat: graph.nodeLatitudes[target] / scale, lng: graph.nodeLongitudes[target] / scale });
|
|
1654
|
+
return points;
|
|
1230
1655
|
}
|
|
1231
|
-
function
|
|
1656
|
+
function createEdgeSpatialIndex(graph, cellSizeDegrees = 0.02) {
|
|
1657
|
+
if (!Number.isFinite(cellSizeDegrees) || cellSizeDegrees <= 0) throw new Error("Edge index cell size must be positive.");
|
|
1232
1658
|
const scale = graph.coordinateScale ?? 1;
|
|
1233
|
-
return { lat: graph.nodeLatitudes[nodeId] / scale, lng: graph.nodeLongitudes[nodeId] / scale };
|
|
1234
|
-
}
|
|
1235
|
-
function createSpatialIndex(graph, cellSizeDegrees = 0.02) {
|
|
1236
|
-
if (!Number.isFinite(cellSizeDegrees) || cellSizeDegrees <= 0) throw new Error("Spatial index cell size must be positive.");
|
|
1237
1659
|
const buckets = /* @__PURE__ */ new Map();
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1660
|
+
const oversized = [];
|
|
1661
|
+
const shapeOffsets = graph.edgeShapeOffsets;
|
|
1662
|
+
const shapeLatitudes = graph.edgeShapeLatitudes;
|
|
1663
|
+
const shapeLongitudes = graph.edgeShapeLongitudes;
|
|
1664
|
+
for (let source = 0; source < graph.nodeLatitudes.length; source += 1) {
|
|
1665
|
+
const sourceLat = graph.nodeLatitudes[source] / scale;
|
|
1666
|
+
const sourceLng = graph.nodeLongitudes[source] / scale;
|
|
1667
|
+
for (let edge = graph.edgeOffsets[source]; edge < graph.edgeOffsets[source + 1]; edge += 1) {
|
|
1668
|
+
const target = graph.edgeTargets[edge];
|
|
1669
|
+
let minLat = Math.min(sourceLat, graph.nodeLatitudes[target] / scale);
|
|
1670
|
+
let maxLat = Math.max(sourceLat, graph.nodeLatitudes[target] / scale);
|
|
1671
|
+
let minLng = Math.min(sourceLng, graph.nodeLongitudes[target] / scale);
|
|
1672
|
+
let maxLng = Math.max(sourceLng, graph.nodeLongitudes[target] / scale);
|
|
1673
|
+
if (shapeOffsets && shapeLatitudes && shapeLongitudes) {
|
|
1674
|
+
for (let point = shapeOffsets[edge]; point < shapeOffsets[edge + 1]; point += 1) {
|
|
1675
|
+
const lat = shapeLatitudes[point] / scale;
|
|
1676
|
+
const lng = shapeLongitudes[point] / scale;
|
|
1677
|
+
if (lat < minLat) minLat = lat;
|
|
1678
|
+
else if (lat > maxLat) maxLat = lat;
|
|
1679
|
+
if (lng < minLng) minLng = lng;
|
|
1680
|
+
else if (lng > maxLng) maxLng = lng;
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
const minLatCell = Math.floor(minLat / cellSizeDegrees);
|
|
1684
|
+
const maxLatCell = Math.floor(maxLat / cellSizeDegrees);
|
|
1685
|
+
const minLngCell = Math.floor(minLng / cellSizeDegrees);
|
|
1686
|
+
const maxLngCell = Math.floor(maxLng / cellSizeDegrees);
|
|
1687
|
+
if (!Number.isFinite(minLatCell) || !Number.isFinite(minLngCell) || (maxLatCell - minLatCell + 1) * (maxLngCell - minLngCell + 1) > MAX_CELLS_PER_EDGE) {
|
|
1688
|
+
oversized.push(edge);
|
|
1689
|
+
continue;
|
|
1690
|
+
}
|
|
1691
|
+
for (let latitudeCell = minLatCell; latitudeCell <= maxLatCell; latitudeCell += 1) {
|
|
1692
|
+
for (let longitudeCell = minLngCell; longitudeCell <= maxLngCell; longitudeCell += 1) {
|
|
1693
|
+
const key = cellKey(latitudeCell, longitudeCell);
|
|
1694
|
+
const bucket = buckets.get(key);
|
|
1695
|
+
if (bucket) bucket.push(edge);
|
|
1696
|
+
else buckets.set(key, [edge]);
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1244
1700
|
}
|
|
1245
|
-
return {
|
|
1246
|
-
cellSizeDegrees,
|
|
1247
|
-
cells: new Map([...buckets].map(([key, ids]) => [key, Uint32Array.from(ids)]))
|
|
1248
|
-
};
|
|
1249
|
-
}
|
|
1250
|
-
function withSpatialIndex(graph, cellSizeDegrees) {
|
|
1251
|
-
return { ...graph, spatialIndex: createSpatialIndex(graph, cellSizeDegrees) };
|
|
1701
|
+
return { cellSizeDegrees, cells: new Map([...buckets].map(([key, ids]) => [key, Uint32Array.from(ids)])), oversized: Uint32Array.from(oversized) };
|
|
1252
1702
|
}
|
|
1253
|
-
|
|
1703
|
+
var indexes = /* @__PURE__ */ new WeakMap();
|
|
1704
|
+
function edgeSpatialIndexFor(graph, cellSizeDegrees) {
|
|
1705
|
+
let index = indexes.get(graph);
|
|
1706
|
+
if (!index) {
|
|
1707
|
+
index = createEdgeSpatialIndex(graph, cellSizeDegrees);
|
|
1708
|
+
indexes.set(graph, index);
|
|
1709
|
+
}
|
|
1710
|
+
return index;
|
|
1711
|
+
}
|
|
1712
|
+
function nearbyEdgeIds(index, coordinate, maxDistanceMeters) {
|
|
1254
1713
|
const latitudeDelta = maxDistanceMeters / 110574;
|
|
1255
1714
|
const longitudeDelta = maxDistanceMeters / Math.max(1, 111320 * Math.cos(coordinate.lat * Math.PI / 180));
|
|
1256
1715
|
const minLatitudeCell = Math.floor((coordinate.lat - latitudeDelta) / index.cellSizeDegrees);
|
|
1257
1716
|
const maxLatitudeCell = Math.floor((coordinate.lat + latitudeDelta) / index.cellSizeDegrees);
|
|
1258
1717
|
const minLongitudeCell = Math.floor((coordinate.lng - longitudeDelta) / index.cellSizeDegrees);
|
|
1259
1718
|
const maxLongitudeCell = Math.floor((coordinate.lng + longitudeDelta) / index.cellSizeDegrees);
|
|
1260
|
-
const ids =
|
|
1719
|
+
const ids = new Set(index.oversized);
|
|
1261
1720
|
for (let latitudeCell = minLatitudeCell; latitudeCell <= maxLatitudeCell; latitudeCell += 1) {
|
|
1262
1721
|
for (let longitudeCell = minLongitudeCell; longitudeCell <= maxLongitudeCell; longitudeCell += 1) {
|
|
1263
|
-
const bucket = index.cells.get(
|
|
1264
|
-
if (bucket) ids.
|
|
1722
|
+
const bucket = index.cells.get(cellKey(latitudeCell, longitudeCell));
|
|
1723
|
+
if (bucket) for (const id of bucket) ids.add(id);
|
|
1265
1724
|
}
|
|
1266
1725
|
}
|
|
1267
1726
|
return ids;
|
|
1268
1727
|
}
|
|
1269
1728
|
|
|
1270
|
-
// src/
|
|
1271
|
-
function
|
|
1272
|
-
|
|
1273
|
-
const toEdges = graph.turnRestrictionToEdges;
|
|
1274
|
-
if (!fromEdges || !toEdges || !fromEdges.length) return null;
|
|
1275
|
-
if (fromEdges.length !== toEdges.length) throw new Error("Turn restriction arrays must have the same length.");
|
|
1276
|
-
const index = /* @__PURE__ */ new Map();
|
|
1277
|
-
for (let pair = 0; pair < fromEdges.length; pair += 1) {
|
|
1278
|
-
if (!edgeAllowed(graph.restrictionMask, pair)) continue;
|
|
1279
|
-
const banned = index.get(fromEdges[pair]);
|
|
1280
|
-
if (banned) banned.add(toEdges[pair]);
|
|
1281
|
-
else index.set(fromEdges[pair], /* @__PURE__ */ new Set([toEdges[pair]]));
|
|
1282
|
-
}
|
|
1283
|
-
return index.size ? index : null;
|
|
1284
|
-
}
|
|
1285
|
-
function isTurnBanned(index, fromEdge, toEdge) {
|
|
1286
|
-
return index.get(fromEdge)?.has(toEdge) ?? false;
|
|
1287
|
-
}
|
|
1288
|
-
function packTurnRestrictions(pairs) {
|
|
1289
|
-
const unique = /* @__PURE__ */ new Set();
|
|
1290
|
-
const sorted = [];
|
|
1291
|
-
for (const [fromEdge, toEdge] of pairs) {
|
|
1292
|
-
const key = `${fromEdge}:${toEdge}`;
|
|
1293
|
-
if (unique.has(key)) continue;
|
|
1294
|
-
unique.add(key);
|
|
1295
|
-
sorted.push([fromEdge, toEdge]);
|
|
1296
|
-
}
|
|
1297
|
-
sorted.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
|
|
1298
|
-
return {
|
|
1299
|
-
turnRestrictionFromEdges: Uint32Array.from(sorted, (pair) => pair[0]),
|
|
1300
|
-
turnRestrictionToEdges: Uint32Array.from(sorted, (pair) => pair[1])
|
|
1301
|
-
};
|
|
1302
|
-
}
|
|
1303
|
-
function buildTurnSequenceIndex(graph) {
|
|
1304
|
-
const offsets = graph.turnRestrictionSequenceOffsets;
|
|
1305
|
-
const edges = graph.turnRestrictionSequenceEdges;
|
|
1306
|
-
if (!offsets || !edges || offsets.length < 2) return null;
|
|
1307
|
-
const sequences = [];
|
|
1308
|
-
const startingAt = /* @__PURE__ */ new Map();
|
|
1309
|
-
const pairs = graph.turnRestrictionFromEdges?.length ?? 0;
|
|
1310
|
-
for (let index = 0; index + 1 < offsets.length; index += 1) {
|
|
1311
|
-
if (!edgeAllowed(graph.restrictionMask, pairs + index)) continue;
|
|
1312
|
-
const sequence = Array.from(edges.subarray(offsets[index], offsets[index + 1]));
|
|
1313
|
-
if (sequence.length < 3) continue;
|
|
1314
|
-
const id = sequences.length;
|
|
1315
|
-
sequences.push(sequence);
|
|
1316
|
-
const bucket = startingAt.get(sequence[0]);
|
|
1317
|
-
if (bucket) bucket.push(id);
|
|
1318
|
-
else startingAt.set(sequence[0], [id]);
|
|
1319
|
-
}
|
|
1320
|
-
if (!sequences.length) return null;
|
|
1321
|
-
const stride = sequences.reduce((longest, sequence) => Math.max(longest, sequence.length), 0) + 1;
|
|
1322
|
-
return { sequences, startingAt, stride };
|
|
1323
|
-
}
|
|
1324
|
-
function packTurnSequences(sequences) {
|
|
1325
|
-
const kept = sequences.filter((sequence) => sequence.length >= 3);
|
|
1326
|
-
const unique = new Map(kept.map((sequence) => [sequence.join(","), sequence]));
|
|
1327
|
-
const ordered = [...unique.values()].sort((a, b) => a[0] - b[0] || a.length - b.length || a[1] - b[1]);
|
|
1328
|
-
const turnRestrictionSequenceOffsets = new Uint32Array(ordered.length + 1);
|
|
1329
|
-
for (let index = 0; index < ordered.length; index += 1) turnRestrictionSequenceOffsets[index + 1] = turnRestrictionSequenceOffsets[index] + ordered[index].length;
|
|
1330
|
-
const turnRestrictionSequenceEdges = new Uint32Array(turnRestrictionSequenceOffsets[ordered.length]);
|
|
1331
|
-
let at = 0;
|
|
1332
|
-
for (const sequence of ordered) for (const edge of sequence) turnRestrictionSequenceEdges[at++] = edge;
|
|
1333
|
-
return { turnRestrictionSequenceOffsets, turnRestrictionSequenceEdges };
|
|
1334
|
-
}
|
|
1335
|
-
var guardedCache = /* @__PURE__ */ new WeakMap();
|
|
1336
|
-
function guardedJunctionsFor(graph, restrictions, sequences) {
|
|
1337
|
-
const held = guardedCache.get(graph);
|
|
1338
|
-
if (held) return held;
|
|
1339
|
-
const nodes = graph.nodeLatitudes.length;
|
|
1340
|
-
const edges = graph.edgeTargets.length;
|
|
1341
|
-
const guarded = new Uint8Array(nodes);
|
|
1342
|
-
if (restrictions) for (const edge of restrictions.keys()) guarded[graph.edgeTargets[edge]] = 1;
|
|
1343
|
-
if (sequences) {
|
|
1344
|
-
for (const sequence of sequences.sequences) {
|
|
1345
|
-
for (const edge of sequence) guarded[graph.edgeTargets[edge]] = 1;
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1348
|
-
const stateOfEdge = new Int32Array(edges).fill(-1);
|
|
1349
|
-
let count = 0;
|
|
1350
|
-
for (let edge = 0; edge < edges; edge += 1) if (guarded[graph.edgeTargets[edge]]) stateOfEdge[edge] = count++;
|
|
1351
|
-
const edgeOfState = new Int32Array(count);
|
|
1352
|
-
for (let edge = 0; edge < edges; edge += 1) if (stateOfEdge[edge] >= 0) edgeOfState[stateOfEdge[edge]] = edge;
|
|
1353
|
-
const index = { guarded, stateOfEdge, edgeOfState, count };
|
|
1354
|
-
guardedCache.set(graph, index);
|
|
1355
|
-
return index;
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
|
-
// src/edge-index.ts
|
|
1359
|
-
var MAX_CELLS_PER_EDGE = 4096;
|
|
1360
|
-
function cellKey2(latitudeCell, longitudeCell) {
|
|
1361
|
-
return `${latitudeCell}:${longitudeCell}`;
|
|
1362
|
-
}
|
|
1363
|
-
function edgeSource(graph, edge) {
|
|
1364
|
-
let low = 0;
|
|
1365
|
-
let high = graph.nodeLatitudes.length - 1;
|
|
1366
|
-
while (low < high) {
|
|
1367
|
-
const middle = low + high + 1 >> 1;
|
|
1368
|
-
if (graph.edgeOffsets[middle] <= edge) low = middle;
|
|
1369
|
-
else high = middle - 1;
|
|
1370
|
-
}
|
|
1371
|
-
return low;
|
|
1729
|
+
// src/spatial-index.ts
|
|
1730
|
+
function cellKey2(latitude, longitude, cellSizeDegrees) {
|
|
1731
|
+
return `${Math.floor(latitude / cellSizeDegrees)}:${Math.floor(longitude / cellSizeDegrees)}`;
|
|
1372
1732
|
}
|
|
1373
|
-
function
|
|
1733
|
+
function coordinateFor(graph, nodeId) {
|
|
1374
1734
|
const scale = graph.coordinateScale ?? 1;
|
|
1375
|
-
|
|
1376
|
-
const points = [{ lat: graph.nodeLatitudes[source] / scale, lng: graph.nodeLongitudes[source] / scale }];
|
|
1377
|
-
if (graph.edgeShapeOffsets && graph.edgeShapeLatitudes && graph.edgeShapeLongitudes) {
|
|
1378
|
-
for (let point = graph.edgeShapeOffsets[edge]; point < graph.edgeShapeOffsets[edge + 1]; point += 1) {
|
|
1379
|
-
points.push({ lat: graph.edgeShapeLatitudes[point] / scale, lng: graph.edgeShapeLongitudes[point] / scale });
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
points.push({ lat: graph.nodeLatitudes[target] / scale, lng: graph.nodeLongitudes[target] / scale });
|
|
1383
|
-
return points;
|
|
1735
|
+
return { lat: graph.nodeLatitudes[nodeId] / scale, lng: graph.nodeLongitudes[nodeId] / scale };
|
|
1384
1736
|
}
|
|
1385
|
-
function
|
|
1386
|
-
if (!Number.isFinite(cellSizeDegrees) || cellSizeDegrees <= 0) throw new Error("
|
|
1387
|
-
const scale = graph.coordinateScale ?? 1;
|
|
1737
|
+
function createSpatialIndex(graph, cellSizeDegrees = 0.02) {
|
|
1738
|
+
if (!Number.isFinite(cellSizeDegrees) || cellSizeDegrees <= 0) throw new Error("Spatial index cell size must be positive.");
|
|
1388
1739
|
const buckets = /* @__PURE__ */ new Map();
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
const sourceLng = graph.nodeLongitudes[source] / scale;
|
|
1396
|
-
for (let edge = graph.edgeOffsets[source]; edge < graph.edgeOffsets[source + 1]; edge += 1) {
|
|
1397
|
-
const target = graph.edgeTargets[edge];
|
|
1398
|
-
let minLat = Math.min(sourceLat, graph.nodeLatitudes[target] / scale);
|
|
1399
|
-
let maxLat = Math.max(sourceLat, graph.nodeLatitudes[target] / scale);
|
|
1400
|
-
let minLng = Math.min(sourceLng, graph.nodeLongitudes[target] / scale);
|
|
1401
|
-
let maxLng = Math.max(sourceLng, graph.nodeLongitudes[target] / scale);
|
|
1402
|
-
if (shapeOffsets && shapeLatitudes && shapeLongitudes) {
|
|
1403
|
-
for (let point = shapeOffsets[edge]; point < shapeOffsets[edge + 1]; point += 1) {
|
|
1404
|
-
const lat = shapeLatitudes[point] / scale;
|
|
1405
|
-
const lng = shapeLongitudes[point] / scale;
|
|
1406
|
-
if (lat < minLat) minLat = lat;
|
|
1407
|
-
else if (lat > maxLat) maxLat = lat;
|
|
1408
|
-
if (lng < minLng) minLng = lng;
|
|
1409
|
-
else if (lng > maxLng) maxLng = lng;
|
|
1410
|
-
}
|
|
1411
|
-
}
|
|
1412
|
-
const minLatCell = Math.floor(minLat / cellSizeDegrees);
|
|
1413
|
-
const maxLatCell = Math.floor(maxLat / cellSizeDegrees);
|
|
1414
|
-
const minLngCell = Math.floor(minLng / cellSizeDegrees);
|
|
1415
|
-
const maxLngCell = Math.floor(maxLng / cellSizeDegrees);
|
|
1416
|
-
if (!Number.isFinite(minLatCell) || !Number.isFinite(minLngCell) || (maxLatCell - minLatCell + 1) * (maxLngCell - minLngCell + 1) > MAX_CELLS_PER_EDGE) {
|
|
1417
|
-
oversized.push(edge);
|
|
1418
|
-
continue;
|
|
1419
|
-
}
|
|
1420
|
-
for (let latitudeCell = minLatCell; latitudeCell <= maxLatCell; latitudeCell += 1) {
|
|
1421
|
-
for (let longitudeCell = minLngCell; longitudeCell <= maxLngCell; longitudeCell += 1) {
|
|
1422
|
-
const key = cellKey2(latitudeCell, longitudeCell);
|
|
1423
|
-
const bucket = buckets.get(key);
|
|
1424
|
-
if (bucket) bucket.push(edge);
|
|
1425
|
-
else buckets.set(key, [edge]);
|
|
1426
|
-
}
|
|
1427
|
-
}
|
|
1428
|
-
}
|
|
1740
|
+
for (let nodeId = 0; nodeId < graph.nodeLatitudes.length; nodeId += 1) {
|
|
1741
|
+
const coordinate = coordinateFor(graph, nodeId);
|
|
1742
|
+
const key = cellKey2(coordinate.lat, coordinate.lng, cellSizeDegrees);
|
|
1743
|
+
const bucket = buckets.get(key);
|
|
1744
|
+
if (bucket) bucket.push(nodeId);
|
|
1745
|
+
else buckets.set(key, [nodeId]);
|
|
1429
1746
|
}
|
|
1430
|
-
return {
|
|
1747
|
+
return {
|
|
1748
|
+
cellSizeDegrees,
|
|
1749
|
+
cells: new Map([...buckets].map(([key, ids]) => [key, Uint32Array.from(ids)]))
|
|
1750
|
+
};
|
|
1431
1751
|
}
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
let index = indexes.get(graph);
|
|
1435
|
-
if (!index) {
|
|
1436
|
-
index = createEdgeSpatialIndex(graph, cellSizeDegrees);
|
|
1437
|
-
indexes.set(graph, index);
|
|
1438
|
-
}
|
|
1439
|
-
return index;
|
|
1752
|
+
function withSpatialIndex(graph, cellSizeDegrees) {
|
|
1753
|
+
return { ...graph, spatialIndex: createSpatialIndex(graph, cellSizeDegrees) };
|
|
1440
1754
|
}
|
|
1441
|
-
function
|
|
1755
|
+
function nearbyNodeIds(index, coordinate, maxDistanceMeters) {
|
|
1442
1756
|
const latitudeDelta = maxDistanceMeters / 110574;
|
|
1443
1757
|
const longitudeDelta = maxDistanceMeters / Math.max(1, 111320 * Math.cos(coordinate.lat * Math.PI / 180));
|
|
1444
1758
|
const minLatitudeCell = Math.floor((coordinate.lat - latitudeDelta) / index.cellSizeDegrees);
|
|
1445
1759
|
const maxLatitudeCell = Math.floor((coordinate.lat + latitudeDelta) / index.cellSizeDegrees);
|
|
1446
1760
|
const minLongitudeCell = Math.floor((coordinate.lng - longitudeDelta) / index.cellSizeDegrees);
|
|
1447
1761
|
const maxLongitudeCell = Math.floor((coordinate.lng + longitudeDelta) / index.cellSizeDegrees);
|
|
1448
|
-
const ids =
|
|
1762
|
+
const ids = [];
|
|
1449
1763
|
for (let latitudeCell = minLatitudeCell; latitudeCell <= maxLatitudeCell; latitudeCell += 1) {
|
|
1450
1764
|
for (let longitudeCell = minLongitudeCell; longitudeCell <= maxLongitudeCell; longitudeCell += 1) {
|
|
1451
|
-
const bucket = index.cells.get(
|
|
1452
|
-
if (bucket)
|
|
1765
|
+
const bucket = index.cells.get(`${latitudeCell}:${longitudeCell}`);
|
|
1766
|
+
if (bucket) ids.push(...bucket);
|
|
1453
1767
|
}
|
|
1454
1768
|
}
|
|
1455
1769
|
return ids;
|
|
@@ -1461,6 +1775,8 @@ var METERS_PER_DEGREE_LATITUDE = 110574;
|
|
|
1461
1775
|
var METERS_PER_DEGREE_LONGITUDE = 111320;
|
|
1462
1776
|
var NODE_SNAP_TOLERANCE_METERS = 1;
|
|
1463
1777
|
var TIE_TOLERANCE_METERS = 0.5;
|
|
1778
|
+
var DEFAULT_SNAP_RADIUS_METERS = 500;
|
|
1779
|
+
var FALLBACK_SNAP_RADIUS_METERS = 1e3;
|
|
1464
1780
|
function haversineMeters(a, b) {
|
|
1465
1781
|
const radians = Math.PI / 180;
|
|
1466
1782
|
const dLat = (b.lat - a.lat) * radians;
|
|
@@ -1499,7 +1815,7 @@ function projectOntoPolyline(points, query) {
|
|
|
1499
1815
|
}
|
|
1500
1816
|
return { ...best, totalMeters: total };
|
|
1501
1817
|
}
|
|
1502
|
-
function snapToGraph(graph, coordinate, maxDistanceMeters =
|
|
1818
|
+
function snapToGraph(graph, coordinate, maxDistanceMeters = DEFAULT_SNAP_RADIUS_METERS, access = graph.edgeAccess, accept, everyNearbyRoad = false) {
|
|
1503
1819
|
const candidates = graph.edgeTargets.length ? nearbyEdgeIds(edgeSpatialIndexFor(graph), coordinate, maxDistanceMeters) : /* @__PURE__ */ new Set();
|
|
1504
1820
|
let best = null;
|
|
1505
1821
|
const projections = [];
|
|
@@ -1529,9 +1845,9 @@ function snapToGraph(graph, coordinate, maxDistanceMeters = 500, access = graph.
|
|
|
1529
1845
|
}
|
|
1530
1846
|
const startMeters = best.fraction * totalLengthOf(best);
|
|
1531
1847
|
const endMeters = (1 - best.fraction) * totalLengthOf(best);
|
|
1532
|
-
if (startMeters <= NODE_SNAP_TOLERANCE_METERS) return { kind: "node", node: best.source, point: best.polyline[0], distanceMeters: best.distanceMeters };
|
|
1533
|
-
if (endMeters <= NODE_SNAP_TOLERANCE_METERS) return { kind: "node", node: best.target, point: best.polyline[best.polyline.length - 1], distanceMeters: best.distanceMeters };
|
|
1534
|
-
const sameRoad = projections.filter((projection) => projection.distanceMeters <= best.distanceMeters + TIE_TOLERANCE_METERS);
|
|
1848
|
+
if (!everyNearbyRoad && startMeters <= NODE_SNAP_TOLERANCE_METERS) return { kind: "node", node: best.source, point: best.polyline[0], distanceMeters: best.distanceMeters };
|
|
1849
|
+
if (!everyNearbyRoad && endMeters <= NODE_SNAP_TOLERANCE_METERS) return { kind: "node", node: best.target, point: best.polyline[best.polyline.length - 1], distanceMeters: best.distanceMeters };
|
|
1850
|
+
const sameRoad = everyNearbyRoad ? projections.sort((left, right) => left.distanceMeters - right.distanceMeters) : projections.filter((projection) => projection.distanceMeters <= best.distanceMeters + TIE_TOLERANCE_METERS);
|
|
1535
1851
|
return { kind: "edge", projections: sameRoad, point: best.point, distanceMeters: best.distanceMeters };
|
|
1536
1852
|
}
|
|
1537
1853
|
function totalLengthOf(projection) {
|
|
@@ -1542,7 +1858,7 @@ function totalLengthOf(projection) {
|
|
|
1542
1858
|
function nearestNodeWithin(graph, coordinate, maxDistanceMeters, accept) {
|
|
1543
1859
|
let closest = null;
|
|
1544
1860
|
let closestDistance = maxDistanceMeters;
|
|
1545
|
-
const
|
|
1861
|
+
const check2 = (nodeId) => {
|
|
1546
1862
|
if (accept && !accept(nodeId)) return;
|
|
1547
1863
|
const distance = haversineMeters(coordinate, coordinateFor2(graph, nodeId));
|
|
1548
1864
|
if (distance <= closestDistance) {
|
|
@@ -1550,8 +1866,8 @@ function nearestNodeWithin(graph, coordinate, maxDistanceMeters, accept) {
|
|
|
1550
1866
|
closestDistance = distance;
|
|
1551
1867
|
}
|
|
1552
1868
|
};
|
|
1553
|
-
if (graph.spatialIndex) for (const nodeId of nearbyNodeIds(graph.spatialIndex, coordinate, maxDistanceMeters))
|
|
1554
|
-
else for (let nodeId = 0; nodeId < graph.nodeLatitudes.length; nodeId += 1)
|
|
1869
|
+
if (graph.spatialIndex) for (const nodeId of nearbyNodeIds(graph.spatialIndex, coordinate, maxDistanceMeters)) check2(nodeId);
|
|
1870
|
+
else for (let nodeId = 0; nodeId < graph.nodeLatitudes.length; nodeId += 1) check2(nodeId);
|
|
1555
1871
|
return closest;
|
|
1556
1872
|
}
|
|
1557
1873
|
function geometryAfter(projection) {
|
|
@@ -1564,6 +1880,230 @@ function geometryBetween(from, to) {
|
|
|
1564
1880
|
return [from.point, ...from.polyline.slice(from.vertexIndex + 1, to.vertexIndex + 1), to.point];
|
|
1565
1881
|
}
|
|
1566
1882
|
|
|
1883
|
+
// src/avoid.ts
|
|
1884
|
+
function check(area, index) {
|
|
1885
|
+
const say = (what) => {
|
|
1886
|
+
throw new RangeError(`avoid[${index}]: ${what}`);
|
|
1887
|
+
};
|
|
1888
|
+
if (!Number.isFinite(area.lat) || area.lat < -90 || area.lat > 90) say(`lat must be a latitude, not ${area.lat}.`);
|
|
1889
|
+
if (!Number.isFinite(area.lng) || area.lng < -180 || area.lng > 180) say(`lng must be a longitude, not ${area.lng}.`);
|
|
1890
|
+
if (!Number.isFinite(area.radiusMeters) || area.radiusMeters <= 0) say(`radiusMeters must be a positive distance, not ${area.radiusMeters}.`);
|
|
1891
|
+
}
|
|
1892
|
+
var METRES_PER_DEGREE_LATITUDE2 = 111320;
|
|
1893
|
+
function touchesArea(graph, edge, area, centre = { lat: area.lat, lng: area.lng }) {
|
|
1894
|
+
const polyline = edgePolyline(graph, edge, edgeSource(graph, edge));
|
|
1895
|
+
const longitudeScale = METRES_PER_DEGREE_LATITUDE2 * Math.cos(centre.lat * Math.PI / 180);
|
|
1896
|
+
const apart = (from, to) => {
|
|
1897
|
+
const delta = from - to;
|
|
1898
|
+
return delta > 180 ? delta - 360 : delta < -180 ? delta + 360 : delta;
|
|
1899
|
+
};
|
|
1900
|
+
const x = (point) => apart(point.lng, centre.lng) * longitudeScale;
|
|
1901
|
+
const y = (point) => (point.lat - centre.lat) * METRES_PER_DEGREE_LATITUDE2;
|
|
1902
|
+
for (let index = 0; index < polyline.length; index += 1) {
|
|
1903
|
+
if (haversineMeters(centre, polyline[index]) <= area.radiusMeters) return true;
|
|
1904
|
+
if (index === 0) continue;
|
|
1905
|
+
const ax = x(polyline[index - 1]);
|
|
1906
|
+
const ay = y(polyline[index - 1]);
|
|
1907
|
+
const bx = x(polyline[index]);
|
|
1908
|
+
const by = y(polyline[index]);
|
|
1909
|
+
const dx = bx - ax;
|
|
1910
|
+
const dy = by - ay;
|
|
1911
|
+
const lengthSquared = dx * dx + dy * dy;
|
|
1912
|
+
if (lengthSquared === 0) continue;
|
|
1913
|
+
const along = Math.max(0, Math.min(1, -(ax * dx + ay * dy) / lengthSquared));
|
|
1914
|
+
if (Math.hypot(ax + along * dx, ay + along * dy) <= area.radiusMeters) return true;
|
|
1915
|
+
}
|
|
1916
|
+
return false;
|
|
1917
|
+
}
|
|
1918
|
+
function accessAvoiding(graph, areas, base) {
|
|
1919
|
+
const start = base ?? graph.edgeAccess;
|
|
1920
|
+
if (!areas?.length) return start;
|
|
1921
|
+
areas.forEach(check);
|
|
1922
|
+
const edges = graph.edgeTargets.length;
|
|
1923
|
+
let mask;
|
|
1924
|
+
const index = edgeSpatialIndexFor(graph);
|
|
1925
|
+
for (const area of areas) {
|
|
1926
|
+
const centre = { lat: area.lat, lng: area.lng };
|
|
1927
|
+
for (const edge of nearbyEdgeIds(index, centre, area.radiusMeters)) {
|
|
1928
|
+
if (edge >= edges) continue;
|
|
1929
|
+
if (mask && !edgeAllowed(mask, edge)) continue;
|
|
1930
|
+
if (!touchesArea(graph, edge, area, centre)) continue;
|
|
1931
|
+
mask ??= start ? Uint8Array.from(start) : accessMaskFor(edges, () => true);
|
|
1932
|
+
mask[edge >> 3] &= ~(1 << (edge & 7));
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
return mask ?? start;
|
|
1936
|
+
}
|
|
1937
|
+
function countAvoided(graph, areas) {
|
|
1938
|
+
const before = graph.edgeAccess;
|
|
1939
|
+
const after = accessAvoiding(graph, areas);
|
|
1940
|
+
if (after === before) return 0;
|
|
1941
|
+
let closed = 0;
|
|
1942
|
+
for (let edge = 0; edge < graph.edgeTargets.length; edge += 1) {
|
|
1943
|
+
if (edgeAllowed(before, edge) && !edgeAllowed(after, edge)) closed += 1;
|
|
1944
|
+
}
|
|
1945
|
+
return closed;
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
// src/limits.ts
|
|
1949
|
+
var DECIMETRE_CEILING = 255;
|
|
1950
|
+
var HALF_TONNE_CEILING = 255;
|
|
1951
|
+
function toDecimetres(metres) {
|
|
1952
|
+
if (metres === void 0 || !(metres > 0)) return 0;
|
|
1953
|
+
const value = Math.floor(metres * 10);
|
|
1954
|
+
return value > DECIMETRE_CEILING ? 0 : value;
|
|
1955
|
+
}
|
|
1956
|
+
function toHalfTonnes(tonnes) {
|
|
1957
|
+
if (tonnes === void 0 || !(tonnes > 0)) return 0;
|
|
1958
|
+
const value = Math.floor(tonnes * 2);
|
|
1959
|
+
return value > HALF_TONNE_CEILING ? 0 : value;
|
|
1960
|
+
}
|
|
1961
|
+
var fromDecimetres = (value) => value / 10;
|
|
1962
|
+
var fromHalfTonnes = (value) => value / 2;
|
|
1963
|
+
function packEdgeLimits(entries) {
|
|
1964
|
+
const rows = entries.map(({ edge, limits }) => ({
|
|
1965
|
+
edge,
|
|
1966
|
+
height: toDecimetres(limits.heightMeters),
|
|
1967
|
+
width: toDecimetres(limits.widthMeters),
|
|
1968
|
+
length: toDecimetres(limits.lengthMeters),
|
|
1969
|
+
weight: toHalfTonnes(limits.weightTonnes)
|
|
1970
|
+
})).filter((row) => row.height || row.width || row.length || row.weight).sort((a, b) => a.edge - b.edge);
|
|
1971
|
+
if (!rows.length) return void 0;
|
|
1972
|
+
return {
|
|
1973
|
+
edges: Uint32Array.from(rows, (row) => row.edge),
|
|
1974
|
+
heightDecimetres: Uint8Array.from(rows, (row) => row.height),
|
|
1975
|
+
widthDecimetres: Uint8Array.from(rows, (row) => row.width),
|
|
1976
|
+
lengthDecimetres: Uint8Array.from(rows, (row) => row.length),
|
|
1977
|
+
weightHalfTonnes: Uint8Array.from(rows, (row) => row.weight)
|
|
1978
|
+
};
|
|
1979
|
+
}
|
|
1980
|
+
function blocks(limits, index, vehicle) {
|
|
1981
|
+
const height = limits.heightDecimetres[index];
|
|
1982
|
+
if (height && vehicle.heightMeters > fromDecimetres(height)) return true;
|
|
1983
|
+
const width = limits.widthDecimetres[index];
|
|
1984
|
+
if (width && vehicle.widthMeters > fromDecimetres(width)) return true;
|
|
1985
|
+
const length = limits.lengthDecimetres[index];
|
|
1986
|
+
if (length && vehicle.lengthMeters > fromDecimetres(length)) return true;
|
|
1987
|
+
const weight = limits.weightHalfTonnes[index];
|
|
1988
|
+
return Boolean(weight) && vehicle.weightTonnes > fromHalfTonnes(weight);
|
|
1989
|
+
}
|
|
1990
|
+
function accessForVehicle(graph, vehicle) {
|
|
1991
|
+
const limits = graph.edgeLimits;
|
|
1992
|
+
if (!vehicle || !limits?.edges.length) return graph.edgeAccess;
|
|
1993
|
+
const edges = graph.edgeTargets.length;
|
|
1994
|
+
const mask = graph.edgeAccess ? Uint8Array.from(graph.edgeAccess) : accessMaskFor(edges, () => true);
|
|
1995
|
+
for (let index = 0; index < limits.edges.length; index += 1) {
|
|
1996
|
+
const edge = limits.edges[index];
|
|
1997
|
+
if (edge < edges && blocks(limits, index, vehicle)) mask[edge >> 3] &= ~(1 << (edge & 7));
|
|
1998
|
+
}
|
|
1999
|
+
return mask;
|
|
2000
|
+
}
|
|
2001
|
+
function countBlocked(graph, vehicle) {
|
|
2002
|
+
const mask = accessForVehicle(graph, vehicle);
|
|
2003
|
+
if (!mask) return 0;
|
|
2004
|
+
let blocked = 0;
|
|
2005
|
+
for (let edge = 0; edge < graph.edgeTargets.length; edge += 1) if (!edgeAllowed(mask, edge)) blocked += 1;
|
|
2006
|
+
return blocked;
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
// src/types.ts
|
|
2010
|
+
var RouterError = class extends Error {
|
|
2011
|
+
constructor(code, message) {
|
|
2012
|
+
super(message);
|
|
2013
|
+
this.code = code;
|
|
2014
|
+
this.name = "RouterError";
|
|
2015
|
+
}
|
|
2016
|
+
code;
|
|
2017
|
+
};
|
|
2018
|
+
|
|
2019
|
+
// src/turn-restrictions.ts
|
|
2020
|
+
function buildTurnRestrictionIndex(graph) {
|
|
2021
|
+
const fromEdges = graph.turnRestrictionFromEdges;
|
|
2022
|
+
const toEdges = graph.turnRestrictionToEdges;
|
|
2023
|
+
if (!fromEdges || !toEdges || !fromEdges.length) return null;
|
|
2024
|
+
if (fromEdges.length !== toEdges.length) throw new Error("Turn restriction arrays must have the same length.");
|
|
2025
|
+
const index = /* @__PURE__ */ new Map();
|
|
2026
|
+
for (let pair = 0; pair < fromEdges.length; pair += 1) {
|
|
2027
|
+
if (!edgeAllowed(graph.restrictionMask, pair)) continue;
|
|
2028
|
+
const banned = index.get(fromEdges[pair]);
|
|
2029
|
+
if (banned) banned.add(toEdges[pair]);
|
|
2030
|
+
else index.set(fromEdges[pair], /* @__PURE__ */ new Set([toEdges[pair]]));
|
|
2031
|
+
}
|
|
2032
|
+
return index.size ? index : null;
|
|
2033
|
+
}
|
|
2034
|
+
function isTurnBanned(index, fromEdge, toEdge) {
|
|
2035
|
+
return index.get(fromEdge)?.has(toEdge) ?? false;
|
|
2036
|
+
}
|
|
2037
|
+
function packTurnRestrictions(pairs) {
|
|
2038
|
+
const unique = /* @__PURE__ */ new Set();
|
|
2039
|
+
const sorted = [];
|
|
2040
|
+
for (const [fromEdge, toEdge] of pairs) {
|
|
2041
|
+
const key = `${fromEdge}:${toEdge}`;
|
|
2042
|
+
if (unique.has(key)) continue;
|
|
2043
|
+
unique.add(key);
|
|
2044
|
+
sorted.push([fromEdge, toEdge]);
|
|
2045
|
+
}
|
|
2046
|
+
sorted.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
|
|
2047
|
+
return {
|
|
2048
|
+
turnRestrictionFromEdges: Uint32Array.from(sorted, (pair) => pair[0]),
|
|
2049
|
+
turnRestrictionToEdges: Uint32Array.from(sorted, (pair) => pair[1])
|
|
2050
|
+
};
|
|
2051
|
+
}
|
|
2052
|
+
function buildTurnSequenceIndex(graph) {
|
|
2053
|
+
const offsets = graph.turnRestrictionSequenceOffsets;
|
|
2054
|
+
const edges = graph.turnRestrictionSequenceEdges;
|
|
2055
|
+
if (!offsets || !edges || offsets.length < 2) return null;
|
|
2056
|
+
const sequences = [];
|
|
2057
|
+
const startingAt = /* @__PURE__ */ new Map();
|
|
2058
|
+
const pairs = graph.turnRestrictionFromEdges?.length ?? 0;
|
|
2059
|
+
for (let index = 0; index + 1 < offsets.length; index += 1) {
|
|
2060
|
+
if (!edgeAllowed(graph.restrictionMask, pairs + index)) continue;
|
|
2061
|
+
const sequence = Array.from(edges.subarray(offsets[index], offsets[index + 1]));
|
|
2062
|
+
if (sequence.length < 3) continue;
|
|
2063
|
+
const id = sequences.length;
|
|
2064
|
+
sequences.push(sequence);
|
|
2065
|
+
const bucket = startingAt.get(sequence[0]);
|
|
2066
|
+
if (bucket) bucket.push(id);
|
|
2067
|
+
else startingAt.set(sequence[0], [id]);
|
|
2068
|
+
}
|
|
2069
|
+
if (!sequences.length) return null;
|
|
2070
|
+
const stride = sequences.reduce((longest, sequence) => Math.max(longest, sequence.length), 0) + 1;
|
|
2071
|
+
return { sequences, startingAt, stride };
|
|
2072
|
+
}
|
|
2073
|
+
function packTurnSequences(sequences) {
|
|
2074
|
+
const kept = sequences.filter((sequence) => sequence.length >= 3);
|
|
2075
|
+
const unique = new Map(kept.map((sequence) => [sequence.join(","), sequence]));
|
|
2076
|
+
const ordered = [...unique.values()].sort((a, b) => a[0] - b[0] || a.length - b.length || a[1] - b[1]);
|
|
2077
|
+
const turnRestrictionSequenceOffsets = new Uint32Array(ordered.length + 1);
|
|
2078
|
+
for (let index = 0; index < ordered.length; index += 1) turnRestrictionSequenceOffsets[index + 1] = turnRestrictionSequenceOffsets[index] + ordered[index].length;
|
|
2079
|
+
const turnRestrictionSequenceEdges = new Uint32Array(turnRestrictionSequenceOffsets[ordered.length]);
|
|
2080
|
+
let at = 0;
|
|
2081
|
+
for (const sequence of ordered) for (const edge of sequence) turnRestrictionSequenceEdges[at++] = edge;
|
|
2082
|
+
return { turnRestrictionSequenceOffsets, turnRestrictionSequenceEdges };
|
|
2083
|
+
}
|
|
2084
|
+
var guardedCache = /* @__PURE__ */ new WeakMap();
|
|
2085
|
+
function guardedJunctionsFor(graph, restrictions, sequences) {
|
|
2086
|
+
const held = guardedCache.get(graph);
|
|
2087
|
+
if (held) return held;
|
|
2088
|
+
const nodes = graph.nodeLatitudes.length;
|
|
2089
|
+
const edges = graph.edgeTargets.length;
|
|
2090
|
+
const guarded = new Uint8Array(nodes);
|
|
2091
|
+
if (restrictions) for (const edge of restrictions.keys()) guarded[graph.edgeTargets[edge]] = 1;
|
|
2092
|
+
if (sequences) {
|
|
2093
|
+
for (const sequence of sequences.sequences) {
|
|
2094
|
+
for (const edge of sequence) guarded[graph.edgeTargets[edge]] = 1;
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
const stateOfEdge = new Int32Array(edges).fill(-1);
|
|
2098
|
+
let count = 0;
|
|
2099
|
+
for (let edge = 0; edge < edges; edge += 1) if (guarded[graph.edgeTargets[edge]]) stateOfEdge[edge] = count++;
|
|
2100
|
+
const edgeOfState = new Int32Array(count);
|
|
2101
|
+
for (let edge = 0; edge < edges; edge += 1) if (stateOfEdge[edge] >= 0) edgeOfState[stateOfEdge[edge]] = edge;
|
|
2102
|
+
const index = { guarded, stateOfEdge, edgeOfState, count };
|
|
2103
|
+
guardedCache.set(graph, index);
|
|
2104
|
+
return index;
|
|
2105
|
+
}
|
|
2106
|
+
|
|
1567
2107
|
// src/priority-queue.ts
|
|
1568
2108
|
var MinPriorityQueue = class {
|
|
1569
2109
|
values = [];
|
|
@@ -1608,6 +2148,7 @@ var MinPriorityQueue = class {
|
|
|
1608
2148
|
};
|
|
1609
2149
|
|
|
1610
2150
|
// src/router.ts
|
|
2151
|
+
var ROADS_TO_TRY = 6;
|
|
1611
2152
|
function assertGraph(graph) {
|
|
1612
2153
|
const nodes = graph.nodeLatitudes.length;
|
|
1613
2154
|
const edges = graph.edgeTargets.length;
|
|
@@ -1632,7 +2173,7 @@ function findNearestNode(graph, coordinate, maxDistanceMeters = 500) {
|
|
|
1632
2173
|
}
|
|
1633
2174
|
return closest;
|
|
1634
2175
|
}
|
|
1635
|
-
function searchContext(graph, destinationPoint, preference) {
|
|
2176
|
+
function searchContext(graph, destinationPoint, preference, slowed) {
|
|
1636
2177
|
const distanceScale = graph.edgeDistanceScale ?? 1;
|
|
1637
2178
|
const durationScale = graph.edgeDurationScale ?? 1;
|
|
1638
2179
|
const { maxSpeedMps, longitudeMetresPerDegree } = searchBoundsFor(graph);
|
|
@@ -1643,7 +2184,11 @@ function searchContext(graph, destinationPoint, preference) {
|
|
|
1643
2184
|
const destinationLongitude = destinationPoint.lng;
|
|
1644
2185
|
const toCost = preference === "fastest" ? 1 / maxSpeedMps : 1;
|
|
1645
2186
|
return {
|
|
1646
|
-
|
|
2187
|
+
// An overridden cost only ever exceeds the graph's own — a conditional
|
|
2188
|
+
// limit slows an edge and a penalty makes it dearer — so the heuristic
|
|
2189
|
+
// built from the graph's fastest edge stays a lower bound and the search
|
|
2190
|
+
// still returns the best route under the costs it was given.
|
|
2191
|
+
costOf: (edge) => slowed?.get(edge) ?? (preference === "fastest" ? graph.edgeDurationsSeconds[edge] * durationScale : graph.edgeDistancesMeters[edge] * distanceScale),
|
|
1647
2192
|
// Called once per relaxation, millions of times on a long route, so it
|
|
1648
2193
|
// reads the coordinate arrays directly and avoids a haversine's four
|
|
1649
2194
|
// trigonometric calls for a number that only has to be a lower bound.
|
|
@@ -1656,8 +2201,8 @@ function searchContext(graph, destinationPoint, preference) {
|
|
|
1656
2201
|
) * toCost
|
|
1657
2202
|
};
|
|
1658
2203
|
}
|
|
1659
|
-
function searchByNode(graph, seeds, finishers, destinationPoint, preference, costBound, access) {
|
|
1660
|
-
const { costOf, heuristicFor } = searchContext(graph, destinationPoint, preference);
|
|
2204
|
+
function searchByNode(graph, seeds, finishers, destinationPoint, preference, costBound, access, slowed) {
|
|
2205
|
+
const { costOf, heuristicFor } = searchContext(graph, destinationPoint, preference, slowed);
|
|
1661
2206
|
const nodeCount = graph.nodeLatitudes.length;
|
|
1662
2207
|
const costs = new Float64Array(nodeCount).fill(Infinity);
|
|
1663
2208
|
const previousNode = new Int32Array(nodeCount).fill(-1);
|
|
@@ -1705,8 +2250,8 @@ function searchByNode(graph, seeds, finishers, destinationPoint, preference, cos
|
|
|
1705
2250
|
edges.reverse();
|
|
1706
2251
|
return { edges, finisher: bestFinisher, seed: seedAt.get(node) };
|
|
1707
2252
|
}
|
|
1708
|
-
function searchWithRestrictions(graph, seeds, finishers, destinationPoint, preference, restrictions, sequenceIndex, costBound, access) {
|
|
1709
|
-
const { costOf, heuristicFor } = searchContext(graph, destinationPoint, preference);
|
|
2253
|
+
function searchWithRestrictions(graph, seeds, finishers, destinationPoint, preference, restrictions, sequenceIndex, costBound, access, slowed) {
|
|
2254
|
+
const { costOf, heuristicFor } = searchContext(graph, destinationPoint, preference, slowed);
|
|
1710
2255
|
const targets = graph.edgeTargets;
|
|
1711
2256
|
const { guarded, stateOfEdge, edgeOfState, count: guardedStates } = guardedJunctionsFor(graph, restrictions, sequenceIndex);
|
|
1712
2257
|
const nodes = graph.nodeLatitudes.length;
|
|
@@ -1875,8 +2420,24 @@ function finishersFor(snap, costOf) {
|
|
|
1875
2420
|
function projectionOf(snap, edge) {
|
|
1876
2421
|
return snap.kind === "edge" ? snap.projections.find((projection) => projection.edge === edge) : void 0;
|
|
1877
2422
|
}
|
|
1878
|
-
function snapSummary(snap) {
|
|
1879
|
-
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 };
|
|
2423
|
+
function snapSummary(snap, travelledMeters = 0) {
|
|
2424
|
+
return snap.kind === "node" ? { node: snap.node, edge: -1, point: snap.point, distanceMeters: snap.distanceMeters, travelledMeters: 0 } : { node: -1, edge: snap.projections[0].edge, point: snap.point, distanceMeters: snap.distanceMeters, travelledMeters };
|
|
2425
|
+
}
|
|
2426
|
+
function withPenalties(graph, preference, slowed, penalties) {
|
|
2427
|
+
if (!penalties?.size) return slowed;
|
|
2428
|
+
const distanceScale = graph.edgeDistanceScale ?? 1;
|
|
2429
|
+
const durationScale = graph.edgeDurationScale ?? 1;
|
|
2430
|
+
const costs = new Map(slowed);
|
|
2431
|
+
for (const [edge, multiplier] of penalties) {
|
|
2432
|
+
const base = costs.get(edge) ?? (preference === "fastest" ? graph.edgeDurationsSeconds[edge] * durationScale : graph.edgeDistancesMeters[edge] * distanceScale);
|
|
2433
|
+
costs.set(edge, base * multiplier);
|
|
2434
|
+
}
|
|
2435
|
+
return costs;
|
|
2436
|
+
}
|
|
2437
|
+
function departureTime(options) {
|
|
2438
|
+
const departAt = options.departAt;
|
|
2439
|
+
if (!departAt) return void 0;
|
|
2440
|
+
return departAt instanceof Date ? travelTimeFrom(departAt, options.publicHoliday, options.timeZone) : { ...departAt, publicHoliday: departAt.publicHoliday ?? options.publicHoliday };
|
|
1880
2441
|
}
|
|
1881
2442
|
function nodeOfSnap(snap) {
|
|
1882
2443
|
return snap.kind === "node" ? snap.node : snap.projections[0].source;
|
|
@@ -1901,24 +2462,67 @@ function reconnect(graph, from, to, preference, options, access) {
|
|
|
1901
2462
|
}
|
|
1902
2463
|
function route(graph, from, to, preference = "fastest", options = {}) {
|
|
1903
2464
|
assertGraph(graph);
|
|
1904
|
-
const
|
|
2465
|
+
const vehicleAccess = accessForVehicle(graph, options.vehicle);
|
|
2466
|
+
const timed = accessAtTime(graph, conditionsOf(graph), departureTime(options), options.vehicle, vehicleAccess, options.circumstances);
|
|
2467
|
+
const access = accessAvoiding(graph, options.avoid, timed);
|
|
1905
2468
|
try {
|
|
1906
2469
|
return routeBetween(graph, from, to, preference, options, access);
|
|
1907
2470
|
} catch (error) {
|
|
1908
2471
|
if (!(error instanceof RouterError) || error.code !== "NO_ROUTE") throw error;
|
|
1909
|
-
|
|
2472
|
+
const mended = reconnect(graph, from, to, preference, options, access);
|
|
2473
|
+
if (mended) return mended;
|
|
2474
|
+
if (options.finishOnANearbyRoad === false) throw error;
|
|
2475
|
+
return onAnotherRoad(graph, from, to, preference, options, access) ?? (() => {
|
|
1910
2476
|
throw error;
|
|
1911
2477
|
})();
|
|
1912
2478
|
}
|
|
1913
2479
|
}
|
|
1914
|
-
function
|
|
1915
|
-
const
|
|
2480
|
+
function roadsNear(graph, point, access) {
|
|
2481
|
+
const nearby = snapToGraph(graph, point, FALLBACK_SNAP_RADIUS_METERS, access, void 0, true);
|
|
2482
|
+
if (!nearby || nearby.kind === "node") return nearby ? [nearby] : [];
|
|
2483
|
+
const byRoad = /* @__PURE__ */ new Map();
|
|
2484
|
+
for (const projection of nearby.projections) {
|
|
2485
|
+
const key = `${Math.min(projection.source, projection.target)}:${Math.max(projection.source, projection.target)}`;
|
|
2486
|
+
const held = byRoad.get(key);
|
|
2487
|
+
if (held) held.push(projection);
|
|
2488
|
+
else byRoad.set(key, [projection]);
|
|
2489
|
+
}
|
|
2490
|
+
return [...byRoad.values()].map((projections) => ({
|
|
2491
|
+
kind: "edge",
|
|
2492
|
+
projections,
|
|
2493
|
+
point: projections[0].point,
|
|
2494
|
+
distanceMeters: projections[0].distanceMeters
|
|
2495
|
+
}));
|
|
2496
|
+
}
|
|
2497
|
+
function onAnotherRoad(graph, from, to, preference, options, access) {
|
|
2498
|
+
const attempt = (chosen) => {
|
|
2499
|
+
try {
|
|
2500
|
+
return routeBetween(graph, from, to, preference, options, access, void 0, void 0, chosen);
|
|
2501
|
+
} catch {
|
|
2502
|
+
return null;
|
|
2503
|
+
}
|
|
2504
|
+
};
|
|
2505
|
+
for (const destination of roadsNear(graph, to, access).slice(1, ROADS_TO_TRY + 1)) {
|
|
2506
|
+
const found = attempt({ destination });
|
|
2507
|
+
if (found) return found;
|
|
2508
|
+
}
|
|
2509
|
+
for (const origin of roadsNear(graph, from, access).slice(1, ROADS_TO_TRY + 1)) {
|
|
2510
|
+
const found = attempt({ origin });
|
|
2511
|
+
if (found) return found;
|
|
2512
|
+
}
|
|
2513
|
+
return null;
|
|
2514
|
+
}
|
|
2515
|
+
function routeBetween(graph, from, to, preference, options, access, acceptOrigin, acceptDestination, chosen) {
|
|
2516
|
+
const originSnap = chosen?.origin ?? snapToGraph(graph, from, void 0, access, acceptOrigin);
|
|
1916
2517
|
if (!originSnap) throw new RouterError("ORIGIN_NOT_ON_GRAPH", "No routable road is close to the origin.");
|
|
1917
|
-
const destinationSnap = snapToGraph(graph, to, void 0, access, acceptDestination);
|
|
2518
|
+
const destinationSnap = chosen?.destination ?? snapToGraph(graph, to, void 0, access, acceptDestination);
|
|
1918
2519
|
if (!destinationSnap) throw new RouterError("DESTINATION_NOT_ON_GRAPH", "No routable road is close to the destination.");
|
|
1919
2520
|
const distanceScale = graph.edgeDistanceScale ?? 1;
|
|
1920
2521
|
const durationScale = graph.edgeDurationScale ?? 1;
|
|
1921
|
-
const
|
|
2522
|
+
const when = departureTime(options);
|
|
2523
|
+
const conditionallySlowed = preference === "fastest" && when ? conditionalDurations(graph, conditionsOf(graph), when, options.vehicle, options.circumstances) : void 0;
|
|
2524
|
+
const slowed = withPenalties(graph, preference, conditionallySlowed, options.costPenalties);
|
|
2525
|
+
const costOf = (edge) => preference === "fastest" ? conditionallySlowed?.get(edge) ?? graph.edgeDurationsSeconds[edge] * durationScale : graph.edgeDistancesMeters[edge] * distanceScale;
|
|
1922
2526
|
const seeds = seedsFor(originSnap, costOf);
|
|
1923
2527
|
const finisherList = finishersFor(destinationSnap, costOf);
|
|
1924
2528
|
const finishers = /* @__PURE__ */ new Map();
|
|
@@ -1940,7 +2544,7 @@ function routeBetween(graph, from, to, preference, options, access, acceptOrigin
|
|
|
1940
2544
|
const costBound = options.costBound ?? Infinity;
|
|
1941
2545
|
const restrictions = buildTurnRestrictionIndex(graph);
|
|
1942
2546
|
const sequenceIndex = buildTurnSequenceIndex(graph);
|
|
1943
|
-
const outcome = sameNode ? { edges: [], finisher: finisherList[0], seed: seeds[0] } : restrictions || sequenceIndex ? searchWithRestrictions(graph, seeds, finishers, destinationSnap.point, preference, restrictions, sequenceIndex, costBound, access) : searchByNode(graph, seeds, finishers, destinationSnap.point, preference, costBound, access);
|
|
2547
|
+
const outcome = sameNode ? { edges: [], finisher: finisherList[0], seed: seeds[0] } : restrictions || sequenceIndex ? searchWithRestrictions(graph, seeds, finishers, destinationSnap.point, preference, restrictions, sequenceIndex, costBound, access, slowed) : searchByNode(graph, seeds, finishers, destinationSnap.point, preference, costBound, access, slowed);
|
|
1944
2548
|
if (direct && direct.cost >= costBound) direct = null;
|
|
1945
2549
|
if (!outcome && !direct) throw new RouterError("NO_ROUTE", `No ${profileOf(graph).name} route connects the selected points.`);
|
|
1946
2550
|
const searchCost = outcome ? outcome.seed.cost + outcome.edges.reduce((sum, edge) => sum + costOf(edge), 0) + outcome.finisher.cost : Infinity;
|
|
@@ -1954,11 +2558,14 @@ function routeBetween(graph, from, to, preference, options, access, acceptOrigin
|
|
|
1954
2558
|
let edgeIds = [];
|
|
1955
2559
|
let distanceMeters = 0;
|
|
1956
2560
|
let durationSeconds = 0;
|
|
2561
|
+
let travelledAtOrigin = 0;
|
|
2562
|
+
let travelledAtDestination = 0;
|
|
1957
2563
|
if (direct && direct.cost <= searchCost) {
|
|
1958
2564
|
for (const point of geometryBetween(direct.origin, direct.destination)) push(point);
|
|
1959
2565
|
const span = partial(direct.origin, direct.destination.fraction - direct.origin.fraction);
|
|
1960
2566
|
distanceMeters = span.distance;
|
|
1961
2567
|
durationSeconds = span.duration;
|
|
2568
|
+
travelledAtOrigin = span.distance;
|
|
1962
2569
|
} else {
|
|
1963
2570
|
const { edges, finisher, seed } = outcome;
|
|
1964
2571
|
edgeIds = edges;
|
|
@@ -1969,6 +2576,7 @@ function routeBetween(graph, from, to, preference, options, access, acceptOrigin
|
|
|
1969
2576
|
const span = partial(entry, 1 - entry.fraction);
|
|
1970
2577
|
distanceMeters += span.distance;
|
|
1971
2578
|
durationSeconds += span.duration;
|
|
2579
|
+
travelledAtOrigin = span.distance;
|
|
1972
2580
|
nodeIds.push(entry.target);
|
|
1973
2581
|
push(coordinateFor2(graph, entry.target));
|
|
1974
2582
|
} else {
|
|
@@ -1977,7 +2585,7 @@ function routeBetween(graph, from, to, preference, options, access, acceptOrigin
|
|
|
1977
2585
|
const scale = graph.coordinateScale ?? 1;
|
|
1978
2586
|
for (const edge of edges) {
|
|
1979
2587
|
distanceMeters += graph.edgeDistancesMeters[edge] * distanceScale;
|
|
1980
|
-
durationSeconds += graph.edgeDurationsSeconds[edge] * durationScale;
|
|
2588
|
+
durationSeconds += conditionallySlowed?.get(edge) ?? graph.edgeDurationsSeconds[edge] * durationScale;
|
|
1981
2589
|
if (graph.edgeShapeOffsets && graph.edgeShapeLatitudes && graph.edgeShapeLongitudes) {
|
|
1982
2590
|
for (let point = graph.edgeShapeOffsets[edge]; point < graph.edgeShapeOffsets[edge + 1]; point += 1) {
|
|
1983
2591
|
coordinates.push([graph.edgeShapeLongitudes[point] / scale, graph.edgeShapeLatitudes[point] / scale]);
|
|
@@ -1992,6 +2600,7 @@ function routeBetween(graph, from, to, preference, options, access, acceptOrigin
|
|
|
1992
2600
|
const span = partial(exit, exit.fraction);
|
|
1993
2601
|
distanceMeters += span.distance;
|
|
1994
2602
|
durationSeconds += span.duration;
|
|
2603
|
+
travelledAtDestination = span.distance;
|
|
1995
2604
|
}
|
|
1996
2605
|
}
|
|
1997
2606
|
return {
|
|
@@ -1999,8 +2608,8 @@ function routeBetween(graph, from, to, preference, options, access, acceptOrigin
|
|
|
1999
2608
|
edgeIds,
|
|
2000
2609
|
distanceMeters,
|
|
2001
2610
|
durationSeconds,
|
|
2002
|
-
originSnap: snapSummary(originSnap),
|
|
2003
|
-
destinationSnap: snapSummary(destinationSnap),
|
|
2611
|
+
originSnap: snapSummary(originSnap, travelledAtOrigin),
|
|
2612
|
+
destinationSnap: snapSummary(destinationSnap, travelledAtDestination),
|
|
2004
2613
|
geometry: { type: "LineString", coordinates }
|
|
2005
2614
|
};
|
|
2006
2615
|
}
|
|
@@ -2013,8 +2622,22 @@ export {
|
|
|
2013
2622
|
unzigzag,
|
|
2014
2623
|
VarintWriter,
|
|
2015
2624
|
VarintReader,
|
|
2016
|
-
ProfileId,
|
|
2017
2625
|
parseMaxSpeedKph,
|
|
2626
|
+
MAX_STORED_VALUES,
|
|
2627
|
+
parseConditional,
|
|
2628
|
+
conditionHolds,
|
|
2629
|
+
conditionalValue,
|
|
2630
|
+
unreadableConditions,
|
|
2631
|
+
travelTimeFrom,
|
|
2632
|
+
ConditionKind,
|
|
2633
|
+
packEdgeConditions,
|
|
2634
|
+
parseEdgeConditions,
|
|
2635
|
+
conditionsOnEdge,
|
|
2636
|
+
accessAtTime,
|
|
2637
|
+
countClosedAt,
|
|
2638
|
+
conditionsOf,
|
|
2639
|
+
conditionalDurations,
|
|
2640
|
+
ProfileId,
|
|
2018
2641
|
parseLengthMeters,
|
|
2019
2642
|
parseWeightTonnes,
|
|
2020
2643
|
carRoutingProfile,
|
|
@@ -2037,6 +2660,17 @@ export {
|
|
|
2037
2660
|
straightLineAtMost,
|
|
2038
2661
|
componentsFor,
|
|
2039
2662
|
componentSizes,
|
|
2663
|
+
edgeSource,
|
|
2664
|
+
edgePolyline,
|
|
2665
|
+
createEdgeSpatialIndex,
|
|
2666
|
+
edgeSpatialIndexFor,
|
|
2667
|
+
nearbyEdgeIds,
|
|
2668
|
+
createSpatialIndex,
|
|
2669
|
+
withSpatialIndex,
|
|
2670
|
+
snapToGraph,
|
|
2671
|
+
touchesArea,
|
|
2672
|
+
accessAvoiding,
|
|
2673
|
+
countAvoided,
|
|
2040
2674
|
toDecimetres,
|
|
2041
2675
|
toHalfTonnes,
|
|
2042
2676
|
fromDecimetres,
|
|
@@ -2045,20 +2679,12 @@ export {
|
|
|
2045
2679
|
accessForVehicle,
|
|
2046
2680
|
countBlocked,
|
|
2047
2681
|
RouterError,
|
|
2048
|
-
createSpatialIndex,
|
|
2049
|
-
withSpatialIndex,
|
|
2050
2682
|
buildTurnRestrictionIndex,
|
|
2051
2683
|
isTurnBanned,
|
|
2052
2684
|
packTurnRestrictions,
|
|
2053
2685
|
buildTurnSequenceIndex,
|
|
2054
2686
|
packTurnSequences,
|
|
2055
2687
|
guardedJunctionsFor,
|
|
2056
|
-
edgeSource,
|
|
2057
|
-
edgePolyline,
|
|
2058
|
-
createEdgeSpatialIndex,
|
|
2059
|
-
edgeSpatialIndexFor,
|
|
2060
|
-
nearbyEdgeIds,
|
|
2061
|
-
snapToGraph,
|
|
2062
2688
|
findNearestNode,
|
|
2063
2689
|
route
|
|
2064
2690
|
};
|