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/README.md CHANGED
@@ -5,7 +5,7 @@ routing bundle, then route over it with A* — in Node.js or in the browser, wit
5
5
  no routing service involved.
6
6
 
7
7
  A 425 MB Portugal extract compiles to a 38 MB bundle. A city route downloads
8
- 4.3 MB of it and takes about 270 ms end to end. The browser build is 28 kB
8
+ 4.3 MB of it and takes about 270 ms end to end. The browser build is 24.5 kB
9
9
  gzipped and pulls in no Node built-ins.
10
10
 
11
11
  ## What a route here does and does not mean
@@ -15,17 +15,23 @@ say are forbidden** to the chosen vehicle. That is weaker than a statement
15
15
  that the route is legal, or that the vehicle will physically fit, and the gap
16
16
  matters most for a lorry.
17
17
 
18
- Three kinds of rule are read and obeyed: tags on the way, such as access,
19
- direction and signed limits; turn restriction relations; and barriers on
20
- nodes. Everything below is **not read at all**, and a road governed only by
21
- one of them is treated as open:
22
-
23
- - **Time-dependent access.** `access:conditional`, `restriction:conditional`,
24
- `hgv:conditional` and every other time-of-day or day-of-week rule. A street
25
- closed to lorries overnight, a bus lane that is shared outside peak hours,
26
- a market square pedestrianised on Sundays — all appear permanently open.
18
+ Four kinds of rule are read and obeyed: tags on the way, such as access,
19
+ direction and signed limits; turn restriction relations; barriers on nodes;
20
+ and, when you supply a departure time, conditional restrictions on the way.
21
+ Everything below is **not read**, and a road governed only by one of them is
22
+ treated as open:
23
+
24
+ - **Conditional restrictions, unless you pass `departAt`.** Without a
25
+ departure time a street closed to lorries overnight or a square
26
+ pedestrianised on Sundays appears permanently open. See
27
+ [Routing at a time of day](#routing-at-a-time-of-day) for what is read when
28
+ you do pass one, and what is still not.
29
+ - **Conditional turn restrictions.** `restriction:conditional` on a turn
30
+ restriction relation is not read, so a no-left-turn that applies only in the
31
+ morning peak is enforced at every hour of the day.
27
32
  - **Seasonal and weather rules.** Winter closures, snow-chain requirements,
28
- flood gates.
33
+ flood gates. A conditional value resting on weather, such as
34
+ `80 @ (wet)`, is counted but never acted on.
29
35
  - **Temporary closures.** Roadworks, events, landslides. Nothing in a compiled
30
36
  bundle changes after the compile, so a bundle is as current as the extract
31
37
  it was built from and no more.
@@ -39,14 +45,14 @@ one of them is treated as open:
39
45
  limit is treated as passable by any vehicle.
40
46
 
41
47
  For a lorry this means a route may pass under a bridge too low for it, over a
42
- structure too weak for it, or along a street it is banned from at the hour you
43
- travel. **Do not use this engine as the sole basis for dispatching a heavy
44
- vehicle.** Anything safety-critical needs a surveyed freight dataset and a
45
- routing engine that reads conditional restrictions.
48
+ structure too weak for it, or along a street banned to it by a rule nobody
49
+ tagged on the way. **Do not use this engine as the sole basis for dispatching
50
+ a heavy vehicle.** Anything safety-critical needs a surveyed freight dataset.
46
51
 
47
52
  The compiler reports what it understood. `turnRestrictionsRead` against
48
- `turnRestrictionsSkipped`, and `barriersRead`, in `<output>.json` tell you how
49
- much of a region's restriction data was used.
53
+ `turnRestrictionsSkipped`, `barriersRead` and
54
+ `edgesWithConditionalRestrictions`, in `<output>.json`, tell you how much of a
55
+ region's restriction data was used.
50
56
 
51
57
  ## Install
52
58
 
@@ -230,6 +236,242 @@ from the bounding box. A truck does not change shape when it crosses a border.
230
236
  > [What a route here does and does not mean](#what-a-route-here-does-and-does-not-mean)
231
237
  > before relying on one of these routes for a heavy vehicle.
232
238
 
239
+ ## Routing at a time of day
240
+
241
+ Some roads are only closed at certain hours. A street may be shut to lorries
242
+ overnight, a square pedestrianised on Sundays, a bridge limited by weight in
243
+ one season. OpenStreetMap writes these as `*:conditional` tags, and this
244
+ library reads them when — and only when — you say when the journey starts.
245
+
246
+ ```js
247
+ const router = await RoadRouter.open("portugal.efrb");
248
+
249
+ const route = await router.route(origin, destination, "fastest", {
250
+ departAt: new Date("2026-10-15T08:00:00"),
251
+ });
252
+
253
+ const alternatives = await router.routeAlternatives(origin, destination, "fastest", {
254
+ count: 3,
255
+ maxStretch: 1.4,
256
+ maxSharing: 0.75,
257
+ });
258
+ ```
259
+
260
+ Both work the same way in Node and in a browser: the only difference is that
261
+ a browser needs `fetch` and `DecompressionStream`, which it has.
262
+
263
+ `departAt` is in the **local time of the roads being travelled**. This library
264
+ does not convert time zones and does not consult a clock: a `Date` is read for
265
+ its weekday, hour, minute and month as your runtime reports them, so a caller
266
+ routing in a region other than its own must do the conversion first. You can
267
+ pass the parts directly instead of a `Date`:
268
+
269
+ ```js
270
+ // Monday, 08:00, in March.
271
+ await router.route(from, to, "fastest", {
272
+ departAt: { weekday: 0, minutes: 8 * 60, month: 3 },
273
+ });
274
+ ```
275
+
276
+ A restriction is written in the local time of the road it is on. Where the
277
+ roads and the code are in different places, name the zone:
278
+
279
+ ```js
280
+ await router.route(from, to, "fastest", {
281
+ departAt: new Date("2026-10-15T08:00:00Z"),
282
+ timeZone: "Europe/Lisbon",
283
+ });
284
+ ```
285
+
286
+ Public holidays are a calendar this library does not carry, so a rule written
287
+ `no @ (PH)` applies only if you say the day is one:
288
+
289
+ ```js
290
+ await router.route(from, to, "fastest", { departAt, publicHoliday: true });
291
+ ```
292
+
293
+ ### What is read
294
+
295
+ Every bundle carries them, whether it holds one vehicle or several: a
296
+ one-vehicle bundle keeps them in its single chunk, and a multi-vehicle bundle
297
+ in the per-vehicle half of each tile, since the same road can be shut to a
298
+ lorry and open to a car.
299
+
300
+ A `value @ condition` list is read from the way's access tags — `access`,
301
+ `vehicle`, `motor_vehicle` and the profile's own key, such as `hgv` — and from
302
+ `maxspeed`. Where several rules match the same moment, the **last** one wins,
303
+ which is what OpenStreetMap specifies and why the tagging convention is
304
+ general first and specific last.
305
+
306
+ Conditions resting on time of day, weekday, month and the vehicle's own
307
+ dimensions are answered. `no @ (Mo-Fr 07:00-19:00)`,
308
+ `no @ (22:00-06:00)`, `no @ (Nov-Feb)` and `no @ (weight>7.5)` all work, as do
309
+ several windows in one rule and conditions combined with `AND`.
310
+
311
+ An access rule evaluating to `no`, `private`, `destination` or a permit-style
312
+ value closes the road. `destination` counts as closed because this library has
313
+ no notion of a journey's purpose, and treating "only if you live here" as open
314
+ would route strangers down a residents' street.
315
+
316
+ A conditional `maxspeed` slows the edges it applies to, and the route reports
317
+ the slower duration rather than the one it was planned against. A conditional
318
+ limit can only **slow** a road, never speed it up: the search's bound is built
319
+ from the profile's ceiling, and an edge quicker than the graph says would
320
+ break the guarantee that the route found is the best one.
321
+
322
+ ### What is not
323
+
324
+ - **Nothing happens without `departAt`.** Leaving it out ignores conditional
325
+ restrictions entirely, which is how this library behaved before it could
326
+ read them.
327
+ - **Only closures are applied.** A road the plain tags already forbid stays
328
+ forbidden even where a conditional value would open it, because the compiled
329
+ mask has already dropped it. This errs towards refusing a road that is in
330
+ fact usable, which is the direction this library errs in everywhere else.
331
+ - **One moment, not an arrival time per edge.** This is the limitation to
332
+ understand before relying on any of it. Every road on the route is judged by
333
+ the moment the journey *starts*, not by when it would be reached. Leave
334
+ Lisbon at 07:00 on a six-hour drive and the roads near Porto are judged at
335
+ 07:00, though you would arrive at one in the afternoon. A restriction that
336
+ lifts at 10:00 therefore still closes a road you would reach at noon, and
337
+ one that begins at 10:00 is missed entirely.
338
+
339
+ Doing better means a time-dependent shortest path, where each edge is
340
+ evaluated at the arrival time the search has accumulated. That changes the
341
+ cost function, the A* bound and the corridor logic together, and it is a
342
+ piece of work in its own right rather than something to bolt on. It is not
343
+ in this release, and **this engine should not be described as doing
344
+ time-dependent routing**: it applies a departure time, which is a weaker
345
+ thing. The gap matters most on long journeys and least on urban ones, where
346
+ the whole route is driven inside the hour it began.
347
+ - **Conditions only you can answer wait for you to answer them.** Whether it
348
+ is raining, who is driving and what the journey is for are not things a
349
+ compiled graph knows. Tell it, and the rules resting on them apply:
350
+
351
+ ```js
352
+ await router.route(from, to, "fastest", {
353
+ departAt,
354
+ circumstances: { conditions: ["wet"], purposes: ["delivery"] },
355
+ });
356
+ ```
357
+
358
+ Unanswered, such a rule never holds, so a road shut `@ (wet)` stays open
359
+ until someone says it is wet. A stay duration is still not read at all. A rule resting on one — `80 @ (wet)` — is parsed, counted and then
360
+ ignored, leaving the road open. `unreadableConditions` reports how many of a
361
+ tag's rules fell into this.
362
+ - **Turn restriction relations are not conditional.** See the list in
363
+ [What a route here does and does not mean](#what-a-route-here-does-and-does-not-mean).
364
+
365
+ ### Everything a route can be told
366
+
367
+ | Option | Meaning |
368
+ | --- | --- |
369
+ | `departAt` | When the journey starts, as a `Date` or as `{ weekday, minutes, month, dayOfMonth }`. Without it, conditional restrictions are ignored. |
370
+ | `timeZone` | The IANA zone the roads are in, such as `"Europe/Lisbon"`. Without it a `Date` is read as this machine reads it. |
371
+ | `publicHoliday` | Whether that day is one where the roads are. |
372
+ | `circumstances` | What the map cannot know: `{ conditions, userGroups, purposes }`. |
373
+ | `avoid` | Circles of road that are shut, as `{ lat, lng, radiusMeters }`. |
374
+ | `vehicle` | The lorry being routed, where the graph states physical limits. |
375
+ | `finishOnANearbyRoad` | `false` to be refused outright when the point cannot be reached. |
376
+
377
+ ```js
378
+ const route = await router.route(origin, destination, "fastest", {
379
+ departAt: new Date("2026-10-15T08:00:00Z"),
380
+ timeZone: "Europe/Lisbon",
381
+ circumstances: { conditions: ["wet"] },
382
+ avoid: [{ lat: 38.7223, lng: -9.1393, radiusMeters: 150 }],
383
+ });
384
+ ```
385
+
386
+ ## More than one way there
387
+
388
+ `routeAlternatives` returns several routes, best first, for offering a choice
389
+ rather than a single answer.
390
+
391
+ ```js
392
+ const routes = await router.routeAlternatives(from, to, "fastest", { count: 3 });
393
+ // routes[0] is exactly what route() would have returned.
394
+ ```
395
+
396
+ The method is the straightforward one: find the best route, make the roads it
397
+ used dearer to the search than they really are, and search again. A road that
398
+ is the only sensible way through survives the penalty and is used again; one
399
+ that was merely convenient gives way to its neighbour. **The penalty never
400
+ reaches the answer** — every route is reported at the graph's own cost, so an
401
+ alternative's distance and duration are what it would really take.
402
+
403
+ Candidates are filtered rather than trusted. One that is much worse than the
404
+ best, or that is mostly the same road, is not an alternative anybody wants:
405
+
406
+ Each is checked rather than trusted: a `count` that is not a whole number
407
+ between 1 and 10, a `maxStretch` below 1, a `maxSharing` outside nought to
408
+ one, or a `penaltyFactor` below 1 raises a `RangeError` rather than searching
409
+ forever or returning something arbitrary.
410
+
411
+ | Option | Default | Meaning |
412
+ | --- | --- | --- |
413
+ | `count` | 3 | How many routes to return at most, the best one included. |
414
+ | `maxStretch` | 1.4 | How much worse than the best an alternative may be. |
415
+ | `maxSharing` | 0.75 | How much of the routes already offered it may reuse, by distance. |
416
+ | `penaltyFactor` | 2 | How much dearer a road already used is made to look. |
417
+ | `searchWiderCorridor` | `true` | Whether to fetch the wider corridor an alternative might lie in. `false` keeps the download to what one route needed. |
418
+
419
+ Routes come back in the order they would be driven: the best first, then the
420
+ rest by what they really cost. The penalties steer the search and never reach
421
+ the figures, so an alternative's distance and duration are its own.
422
+
423
+ Two routes count as the same road where they travel the same tarmac, measured
424
+ by distance travelled rather than by edges in common. A road driven in the
425
+ opposite direction is the same road. Two roads that merely join the same pair
426
+ of junctions are not: a slip road beside the road it leaves, or a lane round
427
+ the far side of a square, is a genuine second way there, and the compiled
428
+ graph holds a great many of them — 12,015 such pairs in the Porto to Lisbon
429
+ corridor alone. A junction two routes pass through costs no distance and so
430
+ does not make them similar. The roads a route only part-drives at its two
431
+ ends count for exactly what it drives of them, which
432
+ `RouteSnap.travelledMeters` reports.
433
+
434
+ **Fewer routes than you asked for is the normal answer.** A road network
435
+ usually has one obvious way through, and returning a second route that is
436
+ three-quarters the same road, or an hour longer, would be worse than
437
+ returning one. `routeAlternatives` always returns at least the best route, and
438
+ raises whatever `route` would raise when there is none.
439
+
440
+ Alternatives cost roughly one search each. Measured on the Portugal bundle,
441
+ Porto to Lisbon returned three routes — 312.0 km, 319.0 km and 350.5 km — in
442
+ 3.4 s against 2.5 s for the single route, downloading 3.0 MB more than the
443
+ 29.2 MB the first route needed. A short urban route in Lisbon returned two in
444
+ 79 ms with nothing further to download.
445
+
446
+ Over a bundle this deliberately downloads a wider corridor than one route
447
+ needs: an alternative may be worse than the best by as much as `maxStretch`,
448
+ and the tiles holding it lie outside the corridor that proved the best route.
449
+
450
+ Everything a single route obeys, these obey: access, turn restrictions,
451
+ barriers, a lorry's dimensions, and conditional restrictions when you pass a
452
+ departure time.
453
+
454
+ ## Roads you know are shut
455
+
456
+ A bundle is as current as the extract it was compiled from. Roadworks, an
457
+ incident, a street closed for a market: none of them is in it, and none of
458
+ them can be. What a caller reading a feed of such things can do is say so:
459
+
460
+ ```js
461
+ const route = await router.route(from, to, "fastest", {
462
+ avoid: [{ lat: 38.7223, lng: -9.1393, radiusMeters: 150 }],
463
+ });
464
+ ```
465
+
466
+ Anything a circle touches is closed to the search and to snapping, so a route
467
+ neither drives through a closure nor starts in the middle of one. It outranks
468
+ whatever the map says about the road being open, it applies to every
469
+ alternative, and it lasts only for that call — the graph is left as it was.
470
+
471
+ An area rather than a road, because a feed gives a place and a radius, not an
472
+ edge number. Only the roads near each circle are looked at, so closing a
473
+ junction costs the same whether the graph holds a town or a country.
474
+
233
475
  ## Turn restrictions
234
476
 
235
477
  The compiler reads OSM `type=restriction` relations and the routing graph
@@ -279,6 +521,39 @@ It bites hardest on walking and cycling, where small fragments are common: in
279
521
  Monaco the pedestrian network is 97.6% one piece, and the remaining 2.4% is
280
522
  enough to lose a route between two points a kilometre apart.
281
523
 
524
+ ### When the nearest road cannot be reached
525
+
526
+ The road nearest a point is not always one the rest of the network can get to.
527
+ A pocket of one-way streets behind banned turns can be driven out of but never
528
+ into; an island of service roads around a terminal joins nothing at all. Asked
529
+ for a point on one of those, this library used to report no route.
530
+
531
+ It now finishes on a road a little further off, trying the nearest few in turn
532
+ and taking the first that works. `destinationSnap.distanceMeters` says how far
533
+ short of the point it finished, and `originSnap.distanceMeters` the same for
534
+ the start:
535
+
536
+ ```js
537
+ const route = await router.route(from, to);
538
+ if (route.destinationSnap.distanceMeters > 100) {
539
+ // Finished on the nearest road it could reach, not at the point asked for.
540
+ }
541
+ ```
542
+
543
+ This only happens after a route has already failed, so a reachable road
544
+ nearest the point is still always the one used. To be told plainly that a
545
+ point cannot be reached, turn it off:
546
+
547
+ ```js
548
+ await router.route(from, to, "fastest", { finishOnANearbyRoad: false });
549
+ ```
550
+
551
+ Evora's walled centre is the case this was found on: `no_left_turn` and
552
+ `no_right_turn` relations on Rua de Serpa Pinto seal the only entry to a
553
+ street, so OpenStreetMap says no car may legally enter it. Routing Lisbon to a
554
+ point in that street now finishes 23 m away on the nearest road a car can
555
+ reach, rather than reporting no route across 133 km of motorway.
556
+
282
557
  ## Snapping to the road, not to the junction
283
558
 
284
559
  Topology simplification keeps only junctions and way endpoints as routing
@@ -380,7 +655,8 @@ EFR2 chunks inside EFRB.
380
655
  ## Using it in a browser
381
656
 
382
657
  The routing engine has no Node built-ins in it. Bundled for the browser it is
383
- about 18 kB gzipped, and it needs only `fetch` and `DecompressionStream`:
658
+ 19.7 kB gzipped on its own, or 24.5 kB for everything this package exports,
659
+ and it needs only `fetch` and `DecompressionStream`:
384
660
 
385
661
  ```ts
386
662
  import { RoadRouter } from "roadgraph-router";
@@ -512,6 +788,105 @@ Downloads are roughly 35% smaller for short routes and 47% smaller for long
512
788
  ones, and graph load is *faster* than before despite the varint decoding,
513
789
  because there is less to decompress.
514
790
 
791
+ ### What conditional restrictions cost, and what they catch
792
+
793
+ Portugal compiled for car, truck and motorcycle, before and after the
794
+ restrictions were read:
795
+
796
+ Both compiled back to back on the same machine, so the two columns are
797
+ comparable:
798
+
799
+ | | 0.2.0 compiler | 0.3.0 compiler | |
800
+ | --- | --- | --- | --- |
801
+ | Final EFRB | 40.17 MB | **40.18 MB** | +14.8 kB, or +0.04% |
802
+ | Compile time | 8 min 01 s | 8 min 05 s | +1% |
803
+ | Peak RSS | 1.35 GB | 1.52 GB | +0.17 GB, holding the tag text |
804
+ | Routing edges | 2,760,171 | 2,760,171 | identical graph |
805
+ | Edges with a conditional restriction | 0 | **3,328** | |
806
+
807
+ Compiled for a car alone, where the whole graph is one chunk, the same extract
808
+ grows by 4,187 bytes — 38,037,856 to 38,042,043 — for 1,087 restrictions.
809
+
810
+ A region whose roads carry no conditional restriction compiles to the same
811
+ file as before, byte for byte, in either layout: Monaco for a car is 26,177
812
+ bytes from either compiler.
813
+
814
+ Asking for a departure time costs nothing measurable. Routing Porto to Lisbon
815
+ by car, three runs each: 2,294 ms cold and 208 ms repeated without one,
816
+ 2,252 ms and 206 ms with one — a difference smaller than the spread between
817
+ runs.
818
+
819
+ The **Ponte 25 de Abril**, the bridge carrying the A2 into Lisbon, is tagged
820
+ `hgv:conditional = no @ (05:00-02:00)`: lorries may use it for three hours a
821
+ night. Routing Almada to Lisbon across it:
822
+
823
+ | Vehicle | Departure | Distance | Duration | Crossing |
824
+ | --- | --- | --- | --- | --- |
825
+ | Car | any hour | 12.2 km | 12 min | Ponte 25 de Abril |
826
+ | Truck | none given | 13.0 km | 14 min | Ponte 25 de Abril |
827
+ | Truck | 10:00 | **63.4 km** | **51 min** | Ponte Vasco da Gama |
828
+ | Truck | 03:00 | 13.0 km | 14 min | Ponte 25 de Abril |
829
+
830
+ The lorry without a departure time takes a bridge it is banned from for
831
+ twenty-one hours of the day. That is the gap this reads, and the 51-minute
832
+ detour is what obeying the sign actually costs.
833
+
834
+ Of Portugal's conditional values, the ones this library cannot answer are
835
+ left alone rather than guessed at. 555 trunk and primary roads carry
836
+ `maxspeed:conditional` values such as `90 @ sun`, which is not a condition
837
+ OpenStreetMap defines; they keep their posted limit.
838
+
839
+ ### Against the earlier versions
840
+
841
+ 0.1.0, 0.2.0 and this release reading the same file — the car bundle 0.1.0
842
+ published, 38.03 MB — so the comparison is of the engines rather than of
843
+ formats that changed alongside them. Cold is a fresh reader; repeated is the
844
+ same route asked for twice.
845
+
846
+ | Journey | 0.1.0 | 0.2.0 | 0.3.0 |
847
+ | --- | --- | --- | --- |
848
+ | Opening the bundle | 1 ms | 1 ms | 1 ms |
849
+ | Lisbon, across town | 245 / 14 ms | 289 / 8 ms | 290 / 9 ms |
850
+ | Bragança, rural | 269 / 140 ms | 251 / 4 ms | 281 / 3 ms |
851
+ | Porto to Lisbon | 2,715 / 2,104 ms | 1,991 / 281 ms | 1,976 / 284 ms |
852
+ | Porto to Faro | 3,653 / 3,258 ms | 2,253 / 427 ms | 2,244 / 416 ms |
853
+ | Braga to Évora | no route | no route | 6,176 / 3,166 ms |
854
+
855
+ Every version that can read a bundle finds the same roads: 312.02 km from
856
+ Porto to Lisbon and 553.20 km from Porto to Faro, on all three.
857
+
858
+ 0.2.0 is where the long routes improved, and the gain is almost entirely in
859
+ asking twice: the corridor it has already stitched is kept, so a repeated
860
+ Porto–Faro route went from 3.3 s to 0.4 s. **0.3.0 routes at the same speed as
861
+ 0.2.0** — the differences above are smaller than the spread between runs of
862
+ the same binary.
863
+
864
+ Braga to Évora is the exception, and it is not a slowdown: both earlier
865
+ versions spent their time failing. 0.2.0 widened its corridor to the whole
866
+ country over 7.9 s before reporting no route; 0.3.0 finishes on the nearest
867
+ road it can reach in 2.3 s. A point genuinely off the network still fails
868
+ about as fast as it always did, 810 ms against 866 ms.
869
+
870
+ ### What each version can read
871
+
872
+ | | 0.1.0 bundle | 0.2.0 bundle | 0.3.0 bundle |
873
+ | --- | --- | --- | --- |
874
+ | 0.1.0 | yes | no | no |
875
+ | 0.2.0 | yes | yes | no |
876
+ | 0.3.0 | yes | yes | yes |
877
+
878
+ A 0.2.0 reader opens a 0.3.0 bundle and then fails on the first tile whose
879
+ roads carry a conditional restriction — the chunk is longer than it expects.
880
+ A bundle without any reads as before.
881
+
882
+ ### What the package costs
883
+
884
+ | | 0.1.0 | 0.2.0 | 0.3.0 |
885
+ | --- | --- | --- | --- |
886
+ | Tarball | 46.4 kB | 75.9 kB | 91.0 kB |
887
+ | Unpacked | 182.8 kB | 289.3 kB | 342.1 kB |
888
+ | Bundled for a browser, gzipped | 13.6 kB | 21.1 kB | 24.8 kB |
889
+
515
890
  ### Nothing routes differently
516
891
 
517
892
  `tools/compare-bundles.mjs` routes the same random pairs through two bundles
@@ -599,31 +974,40 @@ What the bundled profiles read, and what they deliberately do not.
599
974
  | Lorry limits | `maxheight`, `maxwidth`, `maxlength`, `maxweight`, each with `:forward`, `:backward` and `:hgv` forms, plus `maxheight:physical` and `maxwidth:physical`; metric and feet-and-inches |
600
975
  | Barriers | `barrier` on a node, with `locked` and the access ladder; a bollard stops a car, a stile stops everything but walking, a gate is open unless it says otherwise |
601
976
  | Turn restrictions | `no_left_turn`, `no_right_turn`, `no_straight_on`, `no_u_turn`, `no_entry`, `no_exit`, `only_left_turn`, `only_right_turn`, `only_straight_on`, `only_u_turn` |
977
+ | Alternative routes | `routeAlternatives` returns several routes, best first, filtered by how much worse and how much the same they are |
978
+ | Conditional restrictions | `*:conditional` on the access ladder and on `maxspeed`, applied when a route supplies `departAt`; time of day, weekday, month, public holiday and vehicle dimensions; last matching rule wins |
602
979
  | Restriction scope | `via` node and `via` way; `restriction:<mode>`, `restriction:motor_vehicle` and `restriction:vehicle` override the generic key; `except` listing one of this vehicle's modes drops the relation for that vehicle alone |
603
980
 
604
981
  **Not supported**
605
982
 
606
983
  | Area | Behaviour |
607
984
  | --- | --- |
608
- | A one-way trap | Two points joined on the map but not reachable in the direction asked for — a one-way loop with no way back — still report no route. Snapping again cannot mend that, because both ends are already in the same piece of the network. |
985
+ | A one-way trap | Two points joined on the map but not reachable in the direction asked for still cannot be joined. The route finishes on the nearest road it can reach instead, which `destinationSnap.distanceMeters` reports; `finishOnANearbyRoad: false` asks to be refused outright. |
609
986
  | Surface, gradient and steps beyond a flat penalty | A cycling speed comes from the way class alone. `surface`, `smoothness` and elevation are not read, so a cobbled climb costs the same as smooth level tarmac. |
610
- | Alternative routes | Not in 0.2.0. `routeAlternatives` is planned for 0.3.0. |
611
987
  | Untagged limits | A road stating no `maxheight` or `maxweight` is treated as open to any lorry. Absence of a tag is not a measurement. |
612
988
  | Axle load, trailer and hazmat rules | `maxaxleload`, `hazmat` and trailer-specific access are not read. |
613
- | Conditional restrictions | `restriction:conditional`, `access:conditional`, `hgv:conditional` and every time-of-day, day-of-week or seasonal rule are ignored, so a time-limited restriction is treated as absent. See [What a route here does and does not mean](#what-a-route-here-does-and-does-not-mean). |
989
+ | Conditional restrictions at the time a road is reached | Every road is judged by the moment the journey starts, not by when it would be reached. See [Routing at a time of day](#routing-at-a-time-of-day). This is not time-dependent routing. |
990
+ | Conditional restrictions without `departAt` | Read only when a route supplies a departure time; without one a time-limited restriction is treated as absent, as before. See [Routing at a time of day](#routing-at-a-time-of-day). |
991
+ | Conditional turn restrictions | `restriction:conditional` on a relation is not read, so a morning-peak no-left-turn is enforced at every hour. It is rare enough to be worth measuring rather than guessing at: 1 relation in 26,599 across Portugal. The compile report counts them as `conditionalTurnRestrictionsIgnored`. |
992
+ | Conditions resting on weather, user group or purpose | Read, but only a caller knows the answer. `circumstances: { conditions: ["wet"] }` makes `80 @ (wet)` apply; unanswered, the road stays open. A stay duration is still not read at all. |
993
+ | More than 65,536 distinct conditional values in one tile | The compile stops rather than dropping the surplus, which would turn a restricted road into an open one. Compile in smaller tiles with `--tile-size-km`. |
994
+ | Temporary closures in the bundle | Roadworks, events and incidents happen after a bundle is compiled, so none of them is in it. A caller reading a feed of them can pass `avoid` areas per route instead; see [Roads you know are shut](#roads-you-know-are-shut). |
995
+ | Reopening a road | Only closures are applied. `access=no` with `access:conditional=yes @ (...)` stays closed at every hour. |
996
+ | Time zones | `departAt` is taken as the local time of the roads travelled; no conversion is done and no clock is consulted. |
614
997
  | Temporary closures | Roadworks, events and incidents are not modelled. A bundle is as current as the extract it was compiled from. |
615
998
  | Zone-level rules | Lorry bans, low-emission zones, permit areas and tolls that apply to an area rather than to a way are not read. |
616
999
  | `via` ways longer than four edges | Skipped rather than approximated, and counted as `turnRestrictionsSkipped` in the compile report. |
617
1000
  | Restrictions spanning a tile boundary | Dropped for tiles that do not hold every edge of the run, rather than enforced in a shortened form. |
618
1001
  | Turn costs and traffic signals | Not modelled; a turn costs nothing beyond the roads it joins. |
619
1002
  | Live traffic, elevation, ferries as a distinct mode | Not modelled. Ferry ways are excluded with everything that is not a road. |
620
- | `motor_vehicle:conditional`, toll avoidance | Not read. |
1003
+ | Toll avoidance | Not read. |
621
1004
  | A barrier this table has not met | Treated as passable. Inventing a closure severs a road that is probably open, and the tag may simply be unfamiliar; an `access` tag on the node still decides. |
622
1005
  | EFRT v1 | Carries topology only: encoding to it drops geometry and turn restrictions. Use EFR2 for anything that needs them. |
623
1006
 
624
1007
  The compiler reports what it skipped. `turnRestrictionsRead` against
625
- `turnRestrictionsSkipped` in `<output>.json` tells you how much of a region's
626
- restriction data was understood.
1008
+ `turnRestrictionsSkipped`, and `edgesWithConditionalRestrictions`, in
1009
+ `<output>.json` tell you how much of a region's restriction data was
1010
+ understood.
627
1011
 
628
1012
  ## Upgrading from 0.1.0
629
1013