networkforge 1.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. networkforge-1.4.1/.gitignore +14 -0
  2. networkforge-1.4.1/CHANGELOG.md +666 -0
  3. networkforge-1.4.1/LICENSE +674 -0
  4. networkforge-1.4.1/PKG-INFO +336 -0
  5. networkforge-1.4.1/README.md +306 -0
  6. networkforge-1.4.1/docs/network-analyst.md +273 -0
  7. networkforge-1.4.1/docs/network-rules.md +610 -0
  8. networkforge-1.4.1/docs/osm-data.md +89 -0
  9. networkforge-1.4.1/docs/tagging-guide.md +11 -0
  10. networkforge-1.4.1/docs/traffic.md +240 -0
  11. networkforge-1.4.1/pyproject.toml +116 -0
  12. networkforge-1.4.1/src/networkforge/__init__.py +48 -0
  13. networkforge-1.4.1/src/networkforge/__main__.py +7 -0
  14. networkforge-1.4.1/src/networkforge/cli.py +681 -0
  15. networkforge-1.4.1/src/networkforge/coverage.py +176 -0
  16. networkforge-1.4.1/src/networkforge/edits.py +460 -0
  17. networkforge-1.4.1/src/networkforge/errors.py +87 -0
  18. networkforge-1.4.1/src/networkforge/export.py +1056 -0
  19. networkforge-1.4.1/src/networkforge/inputs.py +256 -0
  20. networkforge-1.4.1/src/networkforge/modes.py +336 -0
  21. networkforge-1.4.1/src/networkforge/network.py +703 -0
  22. networkforge-1.4.1/src/networkforge/osm.py +450 -0
  23. networkforge-1.4.1/src/networkforge/paths.py +113 -0
  24. networkforge-1.4.1/src/networkforge/points.py +290 -0
  25. networkforge-1.4.1/src/networkforge/presets.py +50 -0
  26. networkforge-1.4.1/src/networkforge/projection.py +81 -0
  27. networkforge-1.4.1/src/networkforge/tags.py +132 -0
  28. networkforge-1.4.1/src/networkforge/topology.py +1086 -0
  29. networkforge-1.4.1/src/networkforge/traffic.py +905 -0
  30. networkforge-1.4.1/src/networkforge/turns.py +308 -0
  31. networkforge-1.4.1/src/networkforge/validation.py +642 -0
  32. networkforge-1.4.1/tests/__init__.py +0 -0
  33. networkforge-1.4.1/tests/conftest.py +12 -0
  34. networkforge-1.4.1/tests/data/custom_road_test.geojson +11 -0
  35. networkforge-1.4.1/tests/data/custom_road_test.qmd +27 -0
  36. networkforge-1.4.1/tests/data/extent.geojson +8 -0
  37. networkforge-1.4.1/tests/data/extent.qmd +27 -0
  38. networkforge-1.4.1/tests/helpers.py +132 -0
  39. networkforge-1.4.1/tests/integration/__init__.py +0 -0
  40. networkforge-1.4.1/tests/integration/conftest.py +43 -0
  41. networkforge-1.4.1/tests/integration/grid.py +314 -0
  42. networkforge-1.4.1/tests/integration/test_build_options.py +148 -0
  43. networkforge-1.4.1/tests/integration/test_cli_build.py +167 -0
  44. networkforge-1.4.1/tests/integration/test_coverage.py +148 -0
  45. networkforge-1.4.1/tests/integration/test_custom_ferries.py +74 -0
  46. networkforge-1.4.1/tests/integration/test_edge_cases.py +249 -0
  47. networkforge-1.4.1/tests/integration/test_edits.py +679 -0
  48. networkforge-1.4.1/tests/integration/test_export.py +422 -0
  49. networkforge-1.4.1/tests/integration/test_file_names.py +172 -0
  50. networkforge-1.4.1/tests/integration/test_gpkg.py +190 -0
  51. networkforge-1.4.1/tests/integration/test_gpkg_layers.py +187 -0
  52. networkforge-1.4.1/tests/integration/test_osm_source.py +138 -0
  53. networkforge-1.4.1/tests/integration/test_points.py +151 -0
  54. networkforge-1.4.1/tests/integration/test_properties.py +158 -0
  55. networkforge-1.4.1/tests/integration/test_standalone.py +374 -0
  56. networkforge-1.4.1/tests/integration/test_synthetic_grid.py +470 -0
  57. networkforge-1.4.1/tests/integration/test_traffic_build.py +157 -0
  58. networkforge-1.4.1/tests/integration/test_turns.py +284 -0
  59. networkforge-1.4.1/tests/live/__init__.py +0 -0
  60. networkforge-1.4.1/tests/live/conftest.py +30 -0
  61. networkforge-1.4.1/tests/live/test_cities.py +230 -0
  62. networkforge-1.4.1/tests/live/test_local_extract.py +96 -0
  63. networkforge-1.4.1/tests/live/test_structural_invariants.py +70 -0
  64. networkforge-1.4.1/tests/live/test_valhalla_islands.py +107 -0
  65. networkforge-1.4.1/tests/live/test_valhalla_real_data.py +341 -0
  66. networkforge-1.4.1/tests/qgis/__init__.py +0 -0
  67. networkforge-1.4.1/tests/qgis/check_with_qgis.py +108 -0
  68. networkforge-1.4.1/tests/qgis/make_fixture.py +105 -0
  69. networkforge-1.4.1/tests/r5/__init__.py +5 -0
  70. networkforge-1.4.1/tests/r5/conftest.py +221 -0
  71. networkforge-1.4.1/tests/r5/test_before_and_after.py +36 -0
  72. networkforge-1.4.1/tests/r5/test_cycling.py +43 -0
  73. networkforge-1.4.1/tests/r5/test_edits.py +60 -0
  74. networkforge-1.4.1/tests/r5/test_transit.py +28 -0
  75. networkforge-1.4.1/tests/r5/worker.py +49 -0
  76. networkforge-1.4.1/tests/unit/__init__.py +0 -0
  77. networkforge-1.4.1/tests/unit/test_access_rules.py +79 -0
  78. networkforge-1.4.1/tests/unit/test_cli.py +284 -0
  79. networkforge-1.4.1/tests/unit/test_custom_tags.py +296 -0
  80. networkforge-1.4.1/tests/unit/test_grade_separation.py +38 -0
  81. networkforge-1.4.1/tests/unit/test_inputs.py +225 -0
  82. networkforge-1.4.1/tests/unit/test_presets.py +57 -0
  83. networkforge-1.4.1/tests/unit/test_projection.py +69 -0
  84. networkforge-1.4.1/tests/unit/test_speeds.py +39 -0
  85. networkforge-1.4.1/tests/unit/test_traffic.py +296 -0
  86. networkforge-1.4.1/tests/valhalla/__init__.py +0 -0
  87. networkforge-1.4.1/tests/valhalla/conftest.py +132 -0
  88. networkforge-1.4.1/tests/valhalla/harness.py +252 -0
  89. networkforge-1.4.1/tests/valhalla/test_before_and_after.py +219 -0
  90. networkforge-1.4.1/tests/valhalla/test_cli_outputs.py +91 -0
  91. networkforge-1.4.1/tests/valhalla/test_cycling.py +157 -0
  92. networkforge-1.4.1/tests/valhalla/test_edits.py +254 -0
  93. networkforge-1.4.1/tests/valhalla/test_esri_network_dataset.py +319 -0
  94. networkforge-1.4.1/tests/valhalla/test_ferries.py +88 -0
  95. networkforge-1.4.1/tests/valhalla/test_joins.py +369 -0
  96. networkforge-1.4.1/tests/valhalla/test_less_common_tags.py +139 -0
  97. networkforge-1.4.1/tests/valhalla/test_multipart.py +69 -0
  98. networkforge-1.4.1/tests/valhalla/test_points.py +96 -0
  99. networkforge-1.4.1/tests/valhalla/test_random_lines.py +129 -0
  100. networkforge-1.4.1/tests/valhalla/test_standalone.py +135 -0
  101. networkforge-1.4.1/tests/valhalla/test_tags_as_valhalla_reads_them.py +173 -0
  102. networkforge-1.4.1/tests/valhalla/test_traffic.py +315 -0
  103. networkforge-1.4.1/tests/valhalla/test_turns.py +126 -0
  104. networkforge-1.4.1/tests/valhalla/test_wheelchair.py +110 -0
  105. networkforge-1.4.1/uv.lock +1314 -0
@@ -0,0 +1,14 @@
1
+ .venv/
2
+ __pycache__/
3
+ .pytest_cache/
4
+ .env
5
+ .hypothesis/
6
+ .coverage
7
+ coverage.xml
8
+ htmlcov/
9
+ cache/
10
+ .ruff_cache/
11
+ .cache/
12
+
13
+ # Java crash logs (R5, tests/r5)
14
+ hs_err_pid*.log
@@ -0,0 +1,666 @@
1
+ # Changelog
2
+
3
+ All notable changes to NetworkForge. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions
5
+ follow [Semantic Versioning](https://semver.org/). From 1.0 the command
6
+ line (flags, exit codes, `--json` events) and the Python API are stable:
7
+ a breaking change means a new major version.
8
+
9
+ ## [Unreleased]
10
+
11
+ ## [1.4.1] - 2026-11-13
12
+
13
+ ### Added
14
+ - Published on PyPI: `pip install networkforge`. Each release tag now
15
+ uploads the built package to PyPI as well as to GitHub Releases.
16
+
17
+ ### Changed
18
+ - README links point to GitHub, so they work on the PyPI page too.
19
+
20
+ ## [1.4.0] - 2026-10-28
21
+
22
+ ### Added
23
+ - A `restrictions` layer in every GeoPackage (`write_gpkg`): one line
24
+ per turn restriction in the OSM file written from the same network,
25
+ OpenStreetMap's and the user's, drawn 20 m along the from street,
26
+ through the junction or along the via way(s), and 20 m along the to
27
+ street. Columns `relation_id`, `from_way`, `via_node`, `via_ways`,
28
+ `to_way`, `custom` (`yes` on the user's) and the relation's tags. Always
29
+ written, empty when there are none. `info` has `gpkg_layers` and
30
+ `gpkg_restriction_columns`.
31
+ - The nodes a custom layer's points tag are marked in the GeoPackage's
32
+ `nodes` layer: `modified` = `yes` on an OpenStreetMap node, `custom` =
33
+ `yes` on a new node or one on a custom line.
34
+ - More node columns: `bus`, `psv`, `hgv`, `wheelchair`, `toll`,
35
+ `maxheight`, `maxwidth`, `maxweight`, `entrance` (in the OSM file
36
+ already; now in the GeoPackage too).
37
+ - Custom features and single streets (`traffic --streets`) whose
38
+ coordinates can't be longitude / latitude in their CRS (metres in a
39
+ layer that says degrees) are refused, naming the features. Single
40
+ streets in a wrong CRS used to match no street, with only a warning.
41
+
42
+ ### Changed
43
+ - `write_osm` and `write_gpkg` take the result of `prepare_osm_data`
44
+ (`data=` / `osm_data=`); the CLI makes it once for each network.
45
+
46
+ ## [1.3.0] - 2026-10-21
47
+
48
+ ### Added
49
+ - Measured speeds for traffic: `speed_kph` (km/h) wherever a `factor`
50
+ could be given, in road-class bands and on single streets; where a
51
+ factor and a speed overlap, the slower wins.
52
+ - A speed table by OpenStreetMap way id (`--speed-table FILE`, a CSV or
53
+ a GeoPackage table; Python: `speed_table`): rows of `osm_id` / `osmid`,
54
+ `days`, `time`, `speed_kph` or `factor`, `direction`. The form measured
55
+ speeds usually come in. Rows that match nothing are named by row
56
+ (`--speed-table-id-field`, or the row number) in a warning (`rows`)
57
+ and in the done event's `unmatched_rows`; `table_rows` counts the rows
58
+ used.
59
+ - `direction = backward` for single streets and the speed table: only
60
+ the direction opposite the drawn line, or the way's own direction.
61
+ - A warning when Valhalla can only approximate a band (short and much
62
+ slower than normal: its compact week of speeds can't hold the dip).
63
+ - docs/traffic.md: what uses traffic in the Network Analyst plugin
64
+ (routes and isochrones in its panel do; matrices and its Processing
65
+ tools don't).
66
+
67
+ ### Fixed
68
+ - A short, deep band (e.g. 64 down to 12 km/h for 90 minutes) unpacked in
69
+ Valhalla with speeds near or below zero in its middle, so a trip took
70
+ many times longer than given (a 238 m slip road took 14 minutes). Speeds
71
+ that would unpack below half of those asked for are now raised before
72
+ packing.
73
+
74
+ ## [1.2.1] - 2026-10-20
75
+
76
+ ### Fixed
77
+ - `networkforge traffic` failed on Windows ("Valhalla could not build a
78
+ graph from the OSM file", with no detail): pyvalhalla's programs only
79
+ find their libraries there when `pyvalhalla.libs` is on PATH, which
80
+ the engine now sets for them. Its error messages now give Valhalla's
81
+ exit code. The Windows CI job runs the traffic tests in Valhalla.
82
+
83
+ ## [1.2.0] - 2026-10-20
84
+
85
+ ### Added
86
+ - `networkforge traffic` (Python: `add_traffic`, `read_traffic_settings`):
87
+ time-of-day speeds for Valhalla. A settings file of time bands by road
88
+ class ("primary roads at 60% on weekdays from 07:00 to 09:30") is put on
89
+ a network as Valhalla's predicted traffic, and Valhalla's graph is
90
+ written as a `.tar` file (the Network Analyst plugin's "From Tar").
91
+ Only routes with a departure or arrival time use it; without a time
92
+ every street keeps its normal speed. Needs the new `traffic` extra
93
+ (`pip install "networkforge[traffic]"`, pyvalhalla 3.9.0). See
94
+ docs/traffic.md. `networkforge info` lists the settings' days, road
95
+ classes and factor range under `traffic`.
96
+ - Single streets for traffic (`--streets FILE`, Python: `street_bands`):
97
+ a line layer, one row per street and time band (`days`, `time`,
98
+ `factor`, optional `direction` both/forward and `osm_id` / `osmid`).
99
+ A line without a way id is matched onto the stretches it runs along,
100
+ so it can be part of a long street or a new one. A street with rows of
101
+ its own ignores the road-class bands. Lines that match nothing are
102
+ named in a warning (`--id-field`) and in the done event's `unmatched`.
103
+
104
+ ## [1.1.0] - 2026-10-16
105
+
106
+ ### Added
107
+ - `ramp`, `ramp:wheelchair`, `ramp:stroller` and `ramp:bicycle` are
108
+ written for your own lines and for changes to existing streets (they
109
+ were dropped), and their values are checked. Valhalla tests record
110
+ what 3.9 does with them and with kerbs: nothing yet. Steps with a
111
+ ramp still stop a wheelchair, and no kerb does.
112
+ - A warning names fields that look like OpenStreetMap tags
113
+ (`tactile_paving`, `cycleway:surface` ...) but that NetworkForge
114
+ doesn't write, so routers won't see them. Names, sources, notes and
115
+ addresses (`name:en`, `source:maxspeed` ...) aren't warned about.
116
+ - A warning when a local OSM file (`osm_source=` / `--osm-source`)
117
+ doesn't cover the whole area. A region's extract stops at its
118
+ boundary, so a box reaching across it quietly lost streets: built from
119
+ Geofabrik's Berkshire file, a box around Reading lost about 1,800
120
+ streets north of the Thames, with no warning. The build now finds the
121
+ part of the area where the file has no streets at all, reaching the
122
+ area's edge and covering at least 5% of it, and says where it is.
123
+ The sea side of a coastline doesn't count. With `--json` the warning
124
+ event carries `gaps`, a `[west, south, east, north]` box for each part.
125
+
126
+ ### Fixed
127
+ Found by probing the build with shapes the tests didn't draw
128
+ (`tests/integration/test_edge_cases.py`; the first four changed routes
129
+ in Valhalla).
130
+
131
+ - A custom bridge, tunnel, ferry or line on another `layer` was joined
132
+ to a custom road it crosses wherever either had a vertex within
133
+ `snap_tolerance` of the crossing. A vertex of a bridge drawn exactly
134
+ on a junction (or of a road on a motorway's node) joined it too. Only
135
+ ends join now, as documented.
136
+ - A line crossing or ending on a street right beside the dead end of
137
+ another way (within `snap_tolerance`) was pulled onto the dead end and
138
+ never joined the street.
139
+ - Making a street one-way when its OSM way closes on itself (a loop, a
140
+ crescent): drawn against the way's direction it was written the wrong
141
+ way round, and the closing stretch was reversed in the GeoPackage
142
+ either way.
143
+ - An existing turn restriction over a via way was dropped, silently,
144
+ when any part of one of its ways was changed. It now follows the
145
+ pieces, as restrictions with a via node did. (It still goes, rightly,
146
+ if part of the via way is removed.)
147
+ - About one line end in ten moved onto a street mid-block became two
148
+ nodes in one place joined by an edge of no length; so did three lines
149
+ crossing at one point.
150
+ - `--gpkg` (or another output) naming the `--custom` file overwrote the
151
+ custom data and reported success. Outputs that would replace an input
152
+ or each other are refused, and a missing output folder is reported
153
+ before the build rather than after it.
154
+ - An extent or custom layer in a projection measured in feet (US State
155
+ Plane) was built in feet: `length_m` and travel times 3.28 times too
156
+ large. Such projections are replaced by the UTM zone.
157
+ - Attributes named `index_right` / `index_left` crashed the build; `fid`,
158
+ a name differing from another only in capitals, or a GeoPackage
159
+ analysis column name (`car`, `length_m` ...) stopped the GeoPackage
160
+ being written. The first and last are set aside with the other
161
+ internal names; the others get a trailing underscore in the
162
+ GeoPackage.
163
+ - `--json`: a feature whose `--id-field` is empty was written as `NaN`,
164
+ which is not JSON; it is `null`.
165
+ - Control characters in a tag value made OSM XML that could not be read;
166
+ an extent file without features and an infinite `--snap-tolerance` are
167
+ input errors (exit 3), not crashes.
168
+ - A new line drawn to the pier of an existing ferry that no street
169
+ reaches got a node of its own beside the ferry's, so the ferry stayed
170
+ out of reach in Valhalla. Its end now joins the ferry.
171
+ - `maxspeed=0` gave endless travel times (GeoPackage minutes, OSMnx
172
+ routing): a speed of 0 is ignored, as routers do, and on a custom
173
+ feature it is an error.
174
+ - `bridge=0` / `bridge=false` (and the same for `tunnel`) counted as a
175
+ bridge, so the line didn't join the streets it crossed. They mean no.
176
+ - A change to an existing street drawn ACROSS a block (through the
177
+ middle of it) was applied to that block. A feature must now run along
178
+ the stretch, within 45 degrees.
179
+
180
+ ### Tests
181
+ - Cycling infrastructure is now tested in Valhalla
182
+ (`tests/valhalla/test_cycling.py`): a cycle lane, a track or a
183
+ cycleway draws cyclists off a shorter street and nobody else; a lane
184
+ added to an existing street makes it cheaper to cycle; unpaved
185
+ surfaces close a street to the bicycles that can't take them.
186
+
187
+ - Wheelchair routing (`tests/valhalla/test_wheelchair.py`): steps,
188
+ `wheelchair=no` and gravel turn a wheelchair back and nobody else.
189
+ - Less common tags (`tests/valhalla/test_less_common_tags.py`):
190
+ motorcycles, mopeds and taxis, buses against a one-way street,
191
+ roundabouts, `maxspeed:hgv`, rough surfaces and mountain paths.
192
+
193
+ ### Documentation
194
+ - [network-analyst.md](docs/network-analyst.md#cycling-infrastructure)
195
+ says which cycling tags Valhalla routes by and which it ignores
196
+ (`cycleway:both`, `bicycle_road`, `cyclestreet`).
197
+ - The tagging guide is now [network-rules.md](docs/network-rules.md),
198
+ "Network rules: how your lines become a network", written for people
199
+ using the QGIS plugin. It opens with a short summary of the rules
200
+ instead of code, names each setting as the QGIS form does, and ends
201
+ with a table of each setting in QGIS, on the command line and in
202
+ Python. The code examples are in the README. `docs/tagging-guide.md`
203
+ is now a short page pointing to the new one. Errors' `guide` (in
204
+ `--json` error events too) now names the file:
205
+ `network-rules.md#presets`, not `presets`.
206
+ - The guide brought up to date. The
207
+ quick start shows the command line, the PBF and the GeoPackage, plus
208
+ a table of Python options with their CLI flags. Points are allowed in
209
+ the layer, and lines that join nothing are kept with a warning (the
210
+ guide said both were refused). Attributes that aren't tags are kept in
211
+ the GeoPackage. The reserved names list is complete. New: `wheelchair`,
212
+ `maxspeed:hgv`, time-of-day access, line ends that nearly meet, loops,
213
+ an error table for points, and a list of the warnings and what to do
214
+ about each.
215
+
216
+ ## [1.0.1] - 2026-10-11
217
+
218
+ ### Fixed
219
+ - On Windows, an OSM file in a folder with accented or other non-English
220
+ letters in its name (`C:\Users\Zoë\Documents\...`) could not be read
221
+ ("Open failed ... The system cannot find the file specified"), and
222
+ `.osm.pbf`, `.osm.gz` and `.osm.bz2` output could not be written to
223
+ one. The same went for such letters in the file's own name.
224
+
225
+ ### Tests
226
+ - The offline tests now also run on Windows on every push.
227
+
228
+ ## [1.0.0] - 2026-10-06
229
+
230
+ The first stable release: every OSM feature a network build needs -
231
+ new lines, changed and removed streets, turn restrictions, points on
232
+ the network, ferries - tested against Valhalla.
233
+
234
+ ### Stability
235
+ - The command line (`build`, `check`, `presets`, `info`), its exit codes
236
+ and its `--json` events, and the Python API (`build_network`,
237
+ `write_osm`, `write_gpkg`, the errors) are now stable. New fields may
238
+ be added to the JSON events; none is removed or changes meaning
239
+ without a new major version.
240
+
241
+ ### Added
242
+ - **New ferries.** A custom line with `route=ferry` (or
243
+ `route=shuttle_train`) and no `highway` tag is a ferry, as in
244
+ OpenStreetMap. It joins the streets only at its ends, passing over
245
+ streets it crosses. Everyone may use it unless an access tag closes
246
+ it (`motor_vehicle=no` for a foot ferry); `duration=hh:mm` sets the
247
+ crossing time, else 10 km/h - both as Valhalla reads them. In the
248
+ GeoPackage every mode takes the ferry's time. `info --json` lists
249
+ `tag_values.route` and `tag_patterns.duration`.
250
+ - **Warnings for values routers don't know** on `surface`, `smoothness`,
251
+ `tracktype`, `sac_scale`, `sidewalk`, `cycleway*`, `segregated`, `lit`,
252
+ `toll`, `junction`, `bridge`, `tunnel` and `incline`, for new lines and
253
+ edits alike, with the likely spelling (`surface='asphault' ... did you
254
+ mean 'asphalt'?`). OSM allows any value, so the build carries on.
255
+ `info --json` lists the known values in `tag_values` (and
256
+ `tag_patterns.incline`).
257
+ - **Deleting tags from an existing street.** On a feature with an OSM
258
+ id, `remove_tags=maxspeed;motor_vehicle` deletes those tags from the
259
+ stretch (an empty attribute still means "no change"). Deleting
260
+ `oneway` makes the street two-way. `info --json` has
261
+ `remove_tags_field`.
262
+ - **Points: barriers, signals, crossings.** A point in the custom layer
263
+ with node tags (`barrier=bollard`, `highway=traffic_signals`,
264
+ `crossing=zebra`, ...) tags the node it is on (within snap_tolerance)
265
+ or cuts the nearest street there with a new node - on new lines,
266
+ existing and changed streets alike. `remove_tags` on a point deletes
267
+ an existing node's tags (take out a bollard). `layer` picks the street
268
+ where a bridge crosses one. `check` / `build --json` report `points` /
269
+ `tagged_nodes`.
270
+ - **Via-way turn restrictions.** A turn line through two junctions
271
+ joined by a street becomes a restriction over that stretch (from way,
272
+ via way, to way), e.g. no U-turn across a dual carriageway. Valhalla
273
+ obeys it.
274
+ - Tests for multi-part custom lines: offline (parts that share an end,
275
+ cross, nearly meet, stray from the network or collapse; random
276
+ multi-part lines in the property tests) and in Valhalla (each part
277
+ routable, crossing parts joined, per-part edits and removals,
278
+ standalone networks).
279
+
280
+ ### Changed
281
+ - **Point features are accepted** in the custom layer (they tag nodes,
282
+ see above); only other geometry types are refused, now as "not a line
283
+ or point".
284
+
285
+ ### Fixed
286
+ - **A multi-part feature that changes an existing street** (`osm_id`)
287
+ now applies to each part on its own: `oneway` follows the direction
288
+ each part is drawn in (all parts took the first part's), and a problem
289
+ or an unchanged feature is reported once, not once per part.
290
+ - **A new roundabout without `oneway`** is one-way in the GeoPackage's
291
+ `car_direction` / `bike_direction`, as OSM and Valhalla treat it. It
292
+ was marked two-way there (the PBF was already right).
293
+ - **A line that crosses itself within 1 m of a motorway** (or another
294
+ grade-separated way) no longer joins it there. The line was cut at the
295
+ crossing before snapping, and the cut ends counted as line ends, which
296
+ may join a motorway (as slip roads do). Now only the ends you drew do.
297
+ Found by the new multi-part property tests; single lines were affected
298
+ too.
299
+
300
+ ## [0.12.0] - 2026-10-06
301
+
302
+ ### Added
303
+ - **Turn restrictions.** A line in the custom layer drawn from one street,
304
+ through a junction, onto another, with OSM's `restriction` tag
305
+ (`no_left_turn`, `only_straight_on`, ...), becomes an OSM turn
306
+ restriction relation. `restriction:hgv` / `:bus` / `:motorcar` /
307
+ `:bicycle` limit it to one kind of vehicle, and `except` exempts some.
308
+ It works between existing streets, new lines and changed streets, and
309
+ in standalone networks. Streets that run through the junction are cut
310
+ there, as OSM requires. Lines that pass through no junction or more
311
+ than one are refused with the feature named; a value that disagrees
312
+ with the drawn turn (no_right_turn drawn as a left turn) is warned
313
+ about. See "Turn restrictions" in the tagging guide.
314
+ - `--json`: the `done` event of `build` and `check` has
315
+ `turn_restrictions`; `info` has `turn_restriction_fields` and
316
+ `tag_values.restriction`.
317
+
318
+ ## [0.11.1] - 2026-10-06
319
+
320
+ ### Fixed
321
+ Found by further testing (odd inputs, larger areas):
322
+
323
+ - **Attributes that aren't tags now keep their type.** All attributes were
324
+ turned into text, so a number such as a surveyed travel time reached
325
+ the GeoPackage as `'1.5'` and couldn't be used as a cost in QGIS.
326
+ - **A coordinate that isn't a number** (NaN, infinite) is reported for
327
+ its feature instead of crashing the build with a GEOS error.
328
+ - When every feature is outside the box, the message suggests checking
329
+ the layer's CRS and the order of longitude and latitude.
330
+
331
+ ### Changed
332
+ - Tag names and values are taken exactly as OSM writes them: text `True`
333
+ / `False` is no longer turned into `yes` / `no` (a boolean field still
334
+ is, and empty text still counts as no value).
335
+ - Dev container: the environment is `/opt/venv` instead of the shared
336
+ `/workspace/.venv`, which the host's and the container's uv kept
337
+ rebuilding for their own Python. Rebuild the container to use it.
338
+ - Building a network of your own lines is about four times faster: a
339
+ 39,000-line network (Malta) takes 25 s instead of 100 s. The steps that
340
+ went through the custom lines one at a time in Python now work on all
341
+ of them at once.
342
+
343
+ ## [0.11.0] - 2026-10-05
344
+
345
+ ### Changed
346
+ - **`--json` edge counts are streets, not edges.** The `done` event's
347
+ `edges`, `custom_edges`, `modified_edges` and `removed_edges` count one
348
+ per street, as the GeoPackage has rows. They used to count OSMnx's
349
+ edges, which hold a two-way street as two (45 "edges" for a layer of
350
+ 24 rows).
351
+ - **GeoPackage `oneway` is always OSM text** (`yes`, `no`, `-1`). Existing
352
+ streets came out as booleans and new or changed lines as text, so a
353
+ mixed build wrote `True`, `False` and `yes` in one column.
354
+ - Standalone builds no longer mention OpenStreetMap in their progress
355
+ messages and log ("Checking custom lines reach the OSM network").
356
+
357
+ ### Fixed
358
+ - A line drawn along an existing street joins it at every node it lies
359
+ on; it used to pass over the street's junctions without joining them.
360
+ - When the Overpass API can't be reached (it refuses connections when
361
+ busy), the download is tried again after 10 s and 30 s before failing.
362
+
363
+ ### Tests
364
+ - The nightly property test compared Valhalla's distance matrix with
365
+ NetworkForge's own routing. Valhalla 3.9.0's matrix can disagree with
366
+ its own route search on the same graph (229 m against 100 m), and its
367
+ default 5 m node snapping started trips on a nearby dead end; cells
368
+ that disagree are now measured with route(), locations aren't snapped,
369
+ and junctions another line passes within 0.5 m of are left out. The
370
+ cases found are kept as fixed examples.
371
+ - The live city tests checked that each OSM tag was on as many exported
372
+ ways as downloaded edges, which no longer holds since the export writes
373
+ OSM ways as they are (v0.6.0); they now check the file holds the
374
+ downloaded ways and tags exactly.
375
+ - The Overpass-versus-extract test allows up to 1% of rows to differ (a
376
+ day of OpenStreetMap edits between the two sources).
377
+ - A live test whose Overpass download still fails after the retries is
378
+ skipped with the reason, not reported as an error.
379
+
380
+ ## [0.10.0] - 2026-10-02
381
+
382
+ ### Added
383
+ - **Standalone networks**: `networkforge build --no-osm` (Python:
384
+ `build_network(None, lines, standalone=True)`) builds a network from the
385
+ custom lines alone, with no OpenStreetMap, no area and no download.
386
+ Tags, presets and checks work as usual. `--join-at crossings` (default)
387
+ joins lines wherever they cross; `--join-at vertices` only where they
388
+ share a vertex, for data that already has a vertex at every junction.
389
+ A warning names the features outside the largest connected piece.
390
+ `networkforge info` reports `standalone` and `join_at`.
391
+ - Line ends that stop within the snap distance of each other without
392
+ touching now share one node (also when adding lines to OpenStreetMap).
393
+
394
+ ### Changed
395
+ - The package description and README now say what the tool has become:
396
+ building and editing street networks, not only adding proposed lines.
397
+ - `build` no longer requires `--extent` / `--bbox` on the command line
398
+ when `--no-osm` is given (it still does otherwise; exit code 2).
399
+
400
+ ### Fixed
401
+ - An area or layer in a world-wide projection such as Web Mercator
402
+ (EPSG:3857) was measured in that projection, where a metre on the map
403
+ is not a metre on the ground (79% too long in Scotland): lengths in the
404
+ GeoPackage and the snap distance were wrong. A local UTM zone is now
405
+ used unless the CRS is a regional one.
406
+
407
+ ## [0.9.0] - 2026-10-02
408
+
409
+ ### Fixed
410
+ Found by building on whole-country extracts (Liechtenstein, Andorra,
411
+ Isle of Man, Malta, Faroe Islands, Seychelles, Maldives) and comparing
412
+ with Valhalla on the raw data:
413
+
414
+ - **Only the largest connected piece of the network was kept.** Other
415
+ islands, and streets whose link to the rest lies outside the area, were
416
+ dropped: all of Gozo in Malta, 89% of the Maldives. Every piece is now
417
+ kept. Leaving pieces out also changed Valhalla's speeds on the streets
418
+ that remained (it slows traffic where streets are dense).
419
+ - **A new line could weld a tunnel or bridge to the street next to it.**
420
+ A junction point within the snap distance of an existing node was
421
+ merged into that node even when the node belonged to a tunnel or bridge
422
+ the point must not join; and a crossing that lay exactly on a tunnel or
423
+ bridge line joined it. Points now merge only with the node they are on,
424
+ and an existing way is only joined where it may be: at a custom line's
425
+ end, or at grade on the same layer.
426
+ - **Boolean and empty attributes.** A boolean field (`oneway` = true) was
427
+ rejected as `oneway='True'`, and empty text (`maxspeed` = "") as an
428
+ invalid value. They are now read as yes / no and as "no value".
429
+ - A roundabout counts as one-way when comparing a feature's `oneway`
430
+ with the street's.
431
+ - A further piece of a cropped way gets the same new id in the "before"
432
+ and "after" file.
433
+
434
+ ### Changed
435
+ - A custom line is reported as not connected when its piece of network
436
+ contains no existing street, rather than when it is outside the largest
437
+ piece (the existing network can itself be in several pieces).
438
+
439
+ ## [0.8.0] - 2026-10-02
440
+
441
+ ### Added
442
+ - **Removing existing streets.** A feature with an OSM way id and
443
+ `remove=yes` takes the stretch it lies along out of the "after" network:
444
+ it is in neither the GeoPackage nor the OSM file, new lines don't join
445
+ it, and a turn restriction that needs it goes with it. `--json`: the
446
+ `done` event has `removed_edges`; `info` has `remove_field`.
447
+ - **Ferries.** Ferry and shuttle-train routes (`route=ferry`,
448
+ `route=shuttle_train`, ways with no `highway` tag) are written to the OSM
449
+ file as they are in OpenStreetMap, so routers can use them. They are not
450
+ part of the edge table or the GeoPackage. On the Monaco extract this was
451
+ the whole difference between walking routes on the "before" file and on
452
+ raw OpenStreetMap; they are now identical.
453
+
454
+ ### Fixed
455
+ - New junction nodes get ids above every node id in the area's OSM data,
456
+ including nodes that are only on ferries or on removed streets.
457
+
458
+ ## [0.7.0] - 2026-10-02
459
+
460
+ ### Added
461
+ - **Changing existing streets.** A custom feature with an OSM way id
462
+ (an `osm_id` attribute, as on QuickOSM layers, or `osmid`, as in
463
+ NetworkForge's GeoPackage) changes that street instead of adding a
464
+ line: make it one-way, close it, change its speed limit or type. Only
465
+ attributes that differ from OpenStreetMap are applied, and only on the
466
+ stretches the feature lies along. See "Changing existing streets" in
467
+ the tagging guide.
468
+ - Changed stretches are marked `modified` = `yes` in the GeoPackage and
469
+ `nf:modified=yes` in the OSM file; the "before" outputs are untouched.
470
+ - `--json`: the build's `done` event has `modified_edges`; `check`'s has
471
+ `edits`; `info` has `edit_id_fields`. Errors about such features use
472
+ the guide anchor `changing-existing-streets`.
473
+ - A build may consist of changes only, with no new lines.
474
+
475
+ ### Changed
476
+ - A turn restriction whose way is written in several pieces (cropped at
477
+ the edge of the area, or partly changed) now follows the piece its via
478
+ node is on, instead of being dropped.
479
+ - `nf_edit` and `modified` join the attribute names set aside as
480
+ internal. Such names are now only noted, not warned about, when the
481
+ layer has OSM ids (rows copied from a NetworkForge layer always have
482
+ them).
483
+
484
+ ## [0.6.0] - 2026-10-02
485
+
486
+ ### Added
487
+ - Valhalla tests (`tests/valhalla`, `uv run pytest -m valhalla`, CI job
488
+ "PBF in Valhalla"): the exported PBF is built into a Valhalla routing
489
+ graph with a pinned `pyvalhalla` and routed on. They cover every
490
+ feature of Esri's "Create a network dataset" tutorial, who Valhalla
491
+ lets onto each kind of custom line, every way a line can join the
492
+ network, and that the "before" file routes exactly like the OSM data.
493
+ - `docs/network-analyst.md`: each ArcGIS Network Analyst feature, the
494
+ tag that replaces it, the test that proves it, and what isn't covered.
495
+ - A custom line that crosses itself gets a junction at the crossing
496
+ (unless it is a bridge or tunnel).
497
+
498
+ ### Changed
499
+ - OSM / PBF export writes the existing network as OpenStreetMap has it,
500
+ instead of one new 2-node way per edge. Every way keeps its id, its
501
+ nodes in order and all its tags (not only the tags NetworkForge lists),
502
+ tagged nodes keep all theirs, and turn restrictions and route relations
503
+ are kept. Where a custom line joins a street mid-way, the new junction
504
+ node is inserted into that way. Each custom line is one way (id above
505
+ every OSM way id). Measured with Valhalla on a Monaco extract: routes on
506
+ the "before" file used to differ from routes on the OSM data itself for
507
+ about a third of trips; car, bicycle, bus and truck routes are now
508
+ identical.
509
+ - The `osmid` edge column (and GeoPackage field) is the OSM way id, empty
510
+ for custom edges. It used to be a row number.
511
+ - The Overpass download and a local extract now share one code path, and
512
+ one further Overpass request fetches the area's turn restrictions.
513
+ - More attributes of custom features are written as tags: time-dependent
514
+ access (`access:conditional`, ...), `taxi`, `hov`, `moped`, `emergency`,
515
+ `motorroad`, `maxaxles`, `hazmat`, `turn:lanes`, `destination`,
516
+ `alt_name`, `sidewalk` and others (see `networkforge info`).
517
+
518
+ - Access rules now follow OSM's defaults where Valhalla showed they
519
+ didn't: cycling on `highway=pedestrian` needs a tag that allows it
520
+ (the `pedestrian_street` preset now sets `bicycle=yes`, so it still
521
+ allows cycling), and `motorroad=yes` closes a road to walking and
522
+ cycling.
523
+ - A custom line only buses or other special vehicles may use (a busway,
524
+ `access=no` + `bus=yes`) is accepted with a note instead of rejected as
525
+ "unusable by every mode".
526
+ - `bus`, `psv`, `hgv`, `goods`, `taxi`, `motorcycle`, `moped`,
527
+ `emergency`, `horse`, `hov` are checked like the other access tags, and
528
+ `maxheight`, `maxwidth`, `maxlength`, `maxweight`, `maxaxleload` must be
529
+ valid OSM limits (`networkforge info`: `tag_values`, `tag_patterns`).
530
+ - GeoPackage `bike_direction` treats `cycleway=opposite*` as contraflow
531
+ cycling, like `oneway:bicycle=no`.
532
+
533
+ ### Fixed
534
+ - A two-way street was written to the OSM / PBF file twice, once per
535
+ direction, which routers read as two parallel streets.
536
+ - When a custom line ended on an existing junction, every street at that
537
+ junction gained a second, uncut copy of itself, which bypassed any new
538
+ junction on that street.
539
+
540
+ ## [0.5.0] - 2026-10-01
541
+
542
+ ### Changed
543
+ - Custom lines that don't connect to the rest of the network no longer
544
+ fail the build (`NetworkIntegrityError`, exit code 5). The build
545
+ finishes with a warning - a `--json` `warning` event - naming the
546
+ `features`; they are kept in the output. A build where no line
547
+ reaches the network still fails (`NoIntersectionError`).
548
+
549
+ ### Added
550
+ - `validation.disconnected_custom_edges(edges)`: which custom edges a
551
+ router can't reach.
552
+
553
+ ## [0.4.0] - 2026-09-30
554
+
555
+ ### Added
556
+ - `networkforge build --baseline-gpkg`: the untouched OSM network as a
557
+ GeoPackage with the same analysis columns, for before/after in QGIS.
558
+ - `--id-field NAME` (build, check): name features in warnings and errors
559
+ by an attribute, e.g. a GeoPackage's `fid`, instead of row number.
560
+ - Errors carry `issues` - `[{"feature": id, "message": ...}]` - on the
561
+ exception (`NetworkForgeError.issues`) and in `--json` error events.
562
+ - `--json` emits `warning` events, with the `features` / `fields`
563
+ they're about.
564
+ - `networkforge info [--json]`: version, presets, network types, valid tag
565
+ values and limits, for front ends building their UI from the engine.
566
+
567
+ ### Changed
568
+ - Custom attributes with internal names (`length`, `key`, `u`, `v`, ...)
569
+ are set aside with a warning instead of failing the build.
570
+ - Custom feature ids (the data's index) must be unique.
571
+
572
+ ## [0.3.0] - 2026-09-29
573
+
574
+ ### Added
575
+ - GeoPackage output for QGIS (`write_gpkg`, `networkforge build --gpkg`):
576
+ edges carry network-analysis columns (`car`/`bike`/`walk`,
577
+ `speed_kph`, `length_m`, travel minutes per mode, `car_direction` /
578
+ `bike_direction` for QGIS's direction field, incl. contraflow cycling).
579
+ One row per street (OSMnx's reverse copies of two-way streets are
580
+ dropped) and every edge ends exactly on its nodes.
581
+ - CI job routing on the GeoPackage with QGIS's own network analysis
582
+ (`qgis_process`, QGIS LTR and latest) and checking the routes.
583
+
584
+ ### Removed
585
+ - `scripts/` (`get_test_data.py`, `route_test.py`,
586
+ `diagnose_custom_network.py`): replaced by the CLI (`networkforge build`,
587
+ `networkforge check`) and the test suite.
588
+
589
+ ### Fixed
590
+ - The dev container's Dockerfile installs dependencies only
591
+ (`uv sync --no-install-project`); installing the package itself failed
592
+ at image build time because the source isn't in the image.
593
+
594
+ ## [0.2.0] - 2026-09-28
595
+
596
+ ### Added
597
+ - **Command-line interface**: `networkforge build`, `check` and `presets`
598
+ (also `python -m networkforge`). `--json` writes progress, results and
599
+ errors as JSON lines for programs; documented exit codes.
600
+ - **Local OSM extracts**: `build_network(..., osm_source="area.osm.pbf")`
601
+ reads the existing network from a file (e.g. Geofabrik) instead of
602
+ Overpass, cropped and filtered to match the Overpass result exactly.
603
+ - **PBF export**: `write_osm()` picks the format from the file name:
604
+ `.osm`, `.osm.pbf`, `.pbf`, `.osm.gz`, `.osm.bz2`.
605
+ - **Presets** (`preset="primary_road"`, ...) and per-feature tags:
606
+ each feature's own attributes, with blanket tags filling gaps, or
607
+ replacing values with `overwrite_tags=True`.
608
+ - **Tag validation** before any download: OSM value checks, which modes
609
+ can use each feature, and warnings for misspelt or Shapefile-truncated
610
+ attribute names.
611
+ - **Grade separation**: custom lines don't join motorways, bridges,
612
+ tunnels or other layers they cross, but may end on them (slip roads).
613
+ - **Transport-mode access rules** following the OSM access hierarchy
614
+ (`motorcar` > `motor_vehicle` > `vehicle` > `access`; `bicycle`,
615
+ `foot`), busways, and shared-use paths.
616
+ - **Exception classes** (`NetworkForgeError`, `InputError`,
617
+ `InvalidTagsError`, `NoIntersectionError`, `OSMDownloadError`,
618
+ `NetworkIntegrityError`) with plain-language messages linking to the docs.
619
+ - **Input checks**: CRS, geometry types, bounding box coverage, Z values.
620
+ - Progress reporting via `logging` and a `progress` callback.
621
+ - Docs: `docs/tagging-guide.md`, `docs/osm-data.md`.
622
+ - Tests: unit, synthetic-grid integration, Hypothesis property tests and
623
+ live city tests; CI with coverage.
624
+
625
+ ### Changed
626
+ - `network_tags` is optional; `build_network` has 13 steps (the old
627
+ final "apply tags" step is folded into the first).
628
+ - Boxes over 1,000 km² are refused for Overpass; use `osm_source`.
629
+ - Exports keep routing-relevant way tags (one-way exceptions, cycle
630
+ infrastructure, vehicle limits, surface) and node tags (barriers,
631
+ signals, crossings).
632
+ - Much faster: splitting, node assignment and export are vectorised
633
+ (demo area: build excluding download 23 s -> 5 s; 800 custom lines
634
+ 191 s -> 52 s).
635
+
636
+ ### Fixed
637
+ - Custom node ids could collide with real OSM node ids, moving streets.
638
+ - Snapping could bend or rewire existing streets near custom lines
639
+ (found by property tests).
640
+ - Custom lines could get gaps where segments ended near a node.
641
+ - `oneway` was exported as `True`/`False`, which made one-way streets
642
+ two-way when reloaded; now `yes`/`no`.
643
+ - Access restrictions (`motor_vehicle`, `foot`, `bicycle`, ...) were
644
+ dropped on download and export.
645
+ - Baseline and custom travel times weren't comparable: speeds for
646
+ untagged roads now come from a fixed table.
647
+
648
+ ## [0.1.0]
649
+
650
+ - Initial pipeline: merge custom lines into an OSMnx network and export
651
+ OSM XML.
652
+
653
+ [Unreleased]: https://github.com/Yibbzz/networkforge/compare/v1.0.0...HEAD
654
+ [1.0.0]: https://github.com/Yibbzz/networkforge/compare/v0.12.0...v1.0.0
655
+ [0.12.0]: https://github.com/Yibbzz/networkforge/compare/v0.11.1...v0.12.0
656
+ [0.11.1]: https://github.com/Yibbzz/networkforge/compare/v0.11.0...v0.11.1
657
+ [0.11.0]: https://github.com/Yibbzz/networkforge/compare/v0.10.0...v0.11.0
658
+ [0.10.0]: https://github.com/Yibbzz/networkforge/compare/v0.9.0...v0.10.0
659
+ [0.9.0]: https://github.com/Yibbzz/networkforge/compare/v0.8.0...v0.9.0
660
+ [0.8.0]: https://github.com/Yibbzz/networkforge/compare/v0.7.0...v0.8.0
661
+ [0.7.0]: https://github.com/Yibbzz/networkforge/compare/v0.6.0...v0.7.0
662
+ [0.6.0]: https://github.com/Yibbzz/networkforge/compare/v0.5.0...v0.6.0
663
+ [0.5.0]: https://github.com/Yibbzz/networkforge/compare/v0.4.0...v0.5.0
664
+ [0.4.0]: https://github.com/Yibbzz/networkforge/compare/v0.3.0...v0.4.0
665
+ [0.3.0]: https://github.com/Yibbzz/networkforge/compare/v0.2.0...v0.3.0
666
+ [0.2.0]: https://github.com/Yibbzz/networkforge/releases/tag/v0.2.0