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 CHANGED
@@ -4,7 +4,214 @@ All notable changes to this project are documented here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project aims
5
5
  to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
- ## [Unreleased]
7
+ ## [0.3.0] - 2026-10-15
8
+
9
+ ### Added
10
+
11
+ - **Conditional restrictions, when a route says when it starts.**
12
+ `router.route(from, to, "fastest", { departAt })` closes the roads shut at
13
+ that moment to both the search and the snapping, and slows the ones a
14
+ conditional speed limit slows. The reported duration is the slowed one, not
15
+ the one the route was planned against.
16
+
17
+ `*:conditional` is read from the access ladder — `access`, `vehicle`,
18
+ `motor_vehicle` and the profile's own key — and from `maxspeed`. Conditions
19
+ resting on time of day, weekday, month, public holiday and the vehicle's own
20
+ dimensions are answered, including windows that run past midnight, seasons
21
+ that span the new year and several windows in one rule. Where more than one
22
+ rule matches, the last wins, which is what OpenStreetMap specifies.
23
+
24
+ Measured on Portugal, where the Ponte 25 de Abril carries
25
+ `hgv:conditional = no @ (05:00-02:00)`: routing Almada to Lisbon, a lorry
26
+ with no departure time takes the bridge in 13.0 km, at 03:00 takes it in
27
+ 13.0 km, and at 10:00 detours 63.4 km over the Ponte Vasco da Gama. A car
28
+ crosses in 12.2 km at every hour. 3,328 of Portugal's 2,760,171 edges carry
29
+ a conditional restriction, costing 14.8 kB — the bundle grew from 40.17 MB
30
+ to 40.18 MB — and about 10 ms on a repeat long-distance route.
31
+
32
+ - **Alternative routes.** `routeAlternatives(from, to, preference, { count })`
33
+ returns several routes, best first. It finds the best one, makes the roads
34
+ it used dearer to the search than they really are, and searches again; the
35
+ penalty steers the search and never reaches the answer, so every route is
36
+ reported at the graph's own cost. Candidates far worse than the best, or
37
+ mostly along the same road, are rejected rather than offered, so fewer
38
+ routes than asked for is the normal answer on a network with one obvious
39
+ way through.
40
+
41
+ Porto to Lisbon returned three routes — 312.0 km, 319.0 km and 350.5 km —
42
+ in 3.4 s against 2.5 s for the single route, downloading 3.0 MB beyond the
43
+ 29.2 MB the first route needed. A short urban route returned two in 79 ms
44
+ with nothing further to download.
45
+
46
+ - **A departure may name the time zone the roads are in.** `timeZone:
47
+ "Europe/Lisbon"` reads the date there rather than wherever the code runs,
48
+ which is what a restriction written in local time means. Built on `Intl`;
49
+ nothing is bundled for it.
50
+
51
+ - **Weather, who is driving and what for can be answered.**
52
+ `circumstances: { conditions: ["wet"], userGroups: ["resident"],
53
+ purposes: ["delivery"] }` makes the rules resting on them apply.
54
+ Unanswered they never hold, so the behaviour of anyone not passing them is
55
+ unchanged. `unreadableConditions` now counts only what the parser genuinely
56
+ cannot read, which is what its name always claimed.
57
+
58
+ - **Roads the caller knows are shut can be avoided.** `avoid: [{ lat, lng,
59
+ radiusMeters }]` closes everything a circle touches, to the search and to
60
+ snapping alike, for that call only. Roadworks and incidents happen after a
61
+ bundle is compiled and cannot be in it; this is how a caller reading a feed
62
+ of them says so.
63
+
64
+ - **`searchWiderCorridor: false`** keeps an alternatives search to the tiles
65
+ one route needed, trading reach for about three megabytes on a long
66
+ journey.
67
+
68
+ - **The compile report counts `conditionalTurnRestrictionsIgnored`.** A turn
69
+ banned only at certain hours is still banned at every hour; the number now
70
+ says how much of a region that costs. Across Portugal it is 1 relation in
71
+ 26,599.
72
+
73
+ ### Fixed
74
+
75
+ - **A road is identified by its shape in order, not by a sum of its points.**
76
+ Adding coordinates up does not care which point came first, so a road
77
+ bending north then east and one bending east then north read as the same
78
+ road and one of them would be dropped as a duplicate alternative.
79
+
80
+ - **A closure standing on the date line shuts the roads under it.** The
81
+ projection it was tested against subtracted longitudes at face value, so a
82
+ road a few metres away measured half a world off.
83
+
84
+ - **A rule table too wide for its index stops the compile.** It used to drop
85
+ the surplus rows, which turned a restricted road into an unrestricted one
86
+ with nothing said. A tile holds up to 65,536 distinct conditional values;
87
+ past that the compile fails and names `--tile-size-km` as the way through.
88
+
89
+ - **Two roads joining the same pair of junctions are two roads.** Telling
90
+ alternatives apart by their endpoints alone treated a slip road and the road
91
+ beside it as one, hiding a genuine second way there. The Porto to Lisbon
92
+ corridor holds 12,015 such pairs, none the same length as its twin, so the
93
+ length now separates them while staying equal for the two directions of one
94
+ road.
95
+
96
+ - **Each end of a route counts what it actually drives.** The distance driven
97
+ on the part-roads a route begins and ends on was split evenly between the
98
+ two, which misreports overlap whenever one end joins its road near the far
99
+ junction. `RouteSnap.travelledMeters` now carries the real figure.
100
+
101
+ - **A month selector is read with the window it governs.**
102
+ `no @ (Oct 22:00-06:00)` stopped at midnight on the thirty-first instead of
103
+ running to six on the first of November. `TravelTime` may now carry
104
+ `dayOfMonth`; without one the answer is unchanged, which is right on thirty
105
+ days in thirty-one.
106
+
107
+ - **Alternative options are refused before anything is downloaded**, rather
108
+ than after a remote bundle has been asked for tiles.
109
+
110
+ - **A window named with a day is read as one window.** `no @ (Fr 22:00-06:00)`
111
+ was matched by asking "is it Friday?" and "is it between 22:00 and 06:00?"
112
+ separately, which answered both ends of it backwards: it applied at two on
113
+ Friday morning, before it began, and not at two on Saturday morning, while
114
+ it was still in force. A span running past midnight now belongs to the day
115
+ it starts on, which also fixes Sunday into Monday.
116
+
117
+ - **`24:30` is no longer a time.** It parsed as half past midnight, quietly
118
+ moving a window onto the following day. Only `24:00` is the end of a day.
119
+
120
+ - **A conditional speed limit keeps its units.** `30 mph @ (…)` was read with
121
+ `parseFloat` and became 30 km/h — two-thirds of the real limit, applied to
122
+ the road as fact. It now uses the same parser as a plain `maxspeed`.
123
+
124
+ - **A rule table too wide for its index drops its surplus.** Past 65,535
125
+ distinct values the extra rows were kept and left pointing at rule zero,
126
+ applying one road's restriction to another.
127
+
128
+ - **Alternatives search the corridor that was downloaded for them.** The
129
+ widening that fetches the tiles an alternative could lie in replaced the
130
+ stitched graph, but the search was handed the graph from before it, so the
131
+ tiles were downloaded and discarded. Asking for a single route no longer
132
+ widens the corridor at all.
133
+
134
+ - **Alternatives come back in the order they would be driven**, by their real
135
+ cost, rather than in the order the penalties happened to find them; and two
136
+ routes are told apart by the road they travel rather than by which direction
137
+ of it, so coming back along a road already offered is no longer a different
138
+ way there.
139
+
140
+ - **A bundle compiled for one vehicle now carries its conditional
141
+ restrictions.** They were stored in the per-vehicle chunk, which only exists
142
+ when a bundle holds several vehicles or one whose routes depend on its size.
143
+ A `--profile car` compile wrote the single-chunk layout instead, where
144
+ nothing held them, so every road read as permanently open however carefully
145
+ a route named its departure time. The restrictions now ride in that layout
146
+ too, in a header byte no chunk written before them ever used.
147
+
148
+ Found while comparing bundle sizes between releases: a car-only Portugal
149
+ compiled byte for byte the same under 0.2.0 and 0.3.0, which it should not
150
+ have, since 129 of its ways carry `access:conditional` and 85
151
+ `motor_vehicle:conditional`. Compiled for motorcycle alone, Monaco now
152
+ stores the Avenue Pasteur ban it used to drop, and a route across it travels
153
+ 183 m at midday and 3,081 m at 23:00 — the same answers the multi-vehicle
154
+ bundle gives.
155
+
156
+ - **Points on a road the network cannot reach no longer report no route.** The
157
+ road nearest a point is sometimes one nothing may legally enter: a pocket of
158
+ one-way streets behind banned turns, or an island of service roads. A route
159
+ to such a point failed outright, however much of the network lay between.
160
+
161
+ It now finishes on a road a little further off, trying the nearest few in
162
+ turn and taking the first that works, and `destinationSnap.distanceMeters`
163
+ reports how far short it stopped. `finishOnANearbyRoad: false` restores the
164
+ old behaviour of being told plainly that the point cannot be reached.
165
+
166
+ Found on Évora, where `no_left_turn` and `no_right_turn` relations on Rua de
167
+ Serpa Pinto seal the only entry to a street: Lisbon to a point there now
168
+ routes 133.2 km, finishing 23 m away, and Braga to Évora routes 460.2 km
169
+ where both previously failed. Lisbon airport, whose terminal roads are an
170
+ island 527 m from the nearest road a car can reach, routes too.
171
+
172
+ ### Added
173
+
174
+ - `edgesWithConditionalRestrictions` in the compile report, beside
175
+ `edgesWithPhysicalLimits`, so a region's conditional data can be counted
176
+ rather than assumed.
177
+
178
+ ### Known limitations
179
+
180
+ - **Nothing changes without `departAt`.** Omitting it ignores conditional
181
+ restrictions entirely, which is how the library behaved before.
182
+ - **Only closures are applied.** A road the plain tags already forbid stays
183
+ forbidden where a conditional value would open it, which errs towards
184
+ refusing a usable road rather than permitting a forbidden one.
185
+ - **One moment for the whole route**, rather than an arrival time per edge. A
186
+ long route is judged by when it set out.
187
+ - **`restriction:conditional` on a turn restriction relation is still not
188
+ read.** A morning-peak no-left-turn is enforced at every hour.
189
+ - **Conditions this library cannot answer are counted, not guessed.** Weather,
190
+ user groups, the purpose of access and a stay duration are parsed, reported
191
+ by `unreadableConditions`, and then left to one side with the road open.
192
+ - **`departAt` is the local time of the roads travelled.** No time-zone
193
+ conversion is done and no clock is consulted.
194
+
195
+ Measured against the earlier releases on one machine: routing is as fast as
196
+ 0.2.0 — Porto to Lisbon 2,252 ms cold and 206 ms repeated, against 2,252 and
197
+ 208 — and the compiler is 1% slower back to back, 8 min 05 s against
198
+ 8 min 01 s, for 0.17 GB more memory. The browser build grew from 21.1 kB
199
+ gzipped to 24.8 kB, and the published tarball from 75.9 kB to 91.0 kB.
200
+
201
+ Braga to Évora is the one journey that changed materially, and it changed
202
+ from failing to working: 0.2.0 spent 7.9 s widening its corridor before
203
+ reporting no route, where this release finishes in 2.3 s.
204
+
205
+ ### Compatibility
206
+
207
+ A bundle whose roads carry no conditional restriction is byte for byte the
208
+ file 0.2.0 wrote, in both layouts: the new sections live in header bytes that
209
+ every version so far left as zero. Monaco for a car is 26,177 bytes from
210
+ either compiler. Every bundle published so far still opens and routes
211
+ identically.
212
+
213
+ A bundle that does carry conditional restrictions cannot be opened by 0.2.0
214
+ or earlier, which report a truncated chunk.
8
215
 
9
216
  ## [0.2.0] - 2026-10-15
10
217
 
@@ -114,11 +321,19 @@ ship too, in a bundle of their own. Alternative routes moved to 0.3.0.
114
321
 
115
322
  ### Known limitations
116
323
 
324
+ - **Conditional restrictions are not read in this release.**
325
+ `access:conditional`, `restriction:conditional` and `hgv:conditional` are
326
+ ignored, so a street closed to lorries overnight or a square pedestrianised
327
+ on Sundays appears permanently open. It is the limitation most likely to
328
+ make a route wrong rather than merely longer, particularly for a lorry, so
329
+ it was scheduled ahead of alternative routes and is addressed under
330
+ Unreleased above.
117
331
  - An active-travel bundle at country scale cannot be compiled with small
118
332
  tiles: Portugal for walking and cycling at 40 km tiles exceeds the
119
333
  compiler's 3 GB ceiling and is refused. The default 120 km works, at the
120
334
  cost of a larger first download for a short urban route.
121
- - Alternative routes are not in this release; they are planned for 0.3.0.
335
+ - Alternative routes are not in this release; they are planned for 0.3.0,
336
+ after conditional restrictions.
122
337
 
123
338
  ### Compatibility
124
339