networkforge 1.4.1__py3-none-any.whl

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.
@@ -0,0 +1,336 @@
1
+ Metadata-Version: 2.5
2
+ Name: networkforge
3
+ Version: 1.4.1
4
+ Summary: Build and edit routable street networks from GIS layers: add to, change or replace OpenStreetMap; output OSM PBF and GeoPackage
5
+ Project-URL: Homepage, https://github.com/Yibbzz/networkforge
6
+ Project-URL: Documentation, https://github.com/Yibbzz/networkforge/blob/main/docs/network-rules.md
7
+ Project-URL: Changelog, https://github.com/Yibbzz/networkforge/blob/main/CHANGELOG.md
8
+ Project-URL: Issues, https://github.com/Yibbzz/networkforge/issues
9
+ License-File: LICENSE
10
+ Keywords: gis,network,openstreetmap,osm,routing,transport
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Scientific/Engineering :: GIS
16
+ Requires-Python: >=3.12
17
+ Requires-Dist: geopandas>=1.0
18
+ Requires-Dist: networkx>=3.0
19
+ Requires-Dist: numpy>=1.26
20
+ Requires-Dist: osmium>=4.0
21
+ Requires-Dist: osmnx<3,>=2.0
22
+ Requires-Dist: pandas>=2.0
23
+ Requires-Dist: pyproj>=3.6
24
+ Requires-Dist: requests>=2.31
25
+ Requires-Dist: scikit-learn>=1.3
26
+ Requires-Dist: shapely>=2.0
27
+ Provides-Extra: traffic
28
+ Requires-Dist: pyvalhalla==3.9.0; extra == 'traffic'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # NetworkForge
32
+
33
+ [![tests](https://github.com/Yibbzz/networkforge/actions/workflows/tests.yml/badge.svg)](https://github.com/Yibbzz/networkforge/actions/workflows/tests.yml)
34
+ [![coverage](https://codecov.io/gh/Yibbzz/networkforge/graph/badge.svg)](https://codecov.io/gh/Yibbzz/networkforge)
35
+
36
+ Build and edit routable street networks from GIS layers. Add your own
37
+ roads, cycleways and paths to OpenStreetMap, change or remove existing
38
+ streets by editing attributes, or build a network from your own lines
39
+ alone - and get the result as OSM PBF for routers (tested in Valhalla
40
+ and R5) and as a GeoPackage for QGIS.
41
+
42
+ ## What it does
43
+
44
+ - **Add** proposed roads, cycleways and paths to the OpenStreetMap
45
+ network. They join the streets they cross, except where one is a
46
+ bridge, a tunnel or on another layer.
47
+ - **Change** existing streets: copy one into your layer and edit an
48
+ attribute to make it one-way, close it, pedestrianise it or change its
49
+ speed limit ([how](https://github.com/Yibbzz/networkforge/blob/main/docs/network-rules.md#changing-existing-streets)).
50
+ - **Remove** existing streets (`remove=yes`).
51
+ - **Ban or force turns** at junctions, drawn as short lines through the
52
+ junction ([how](https://github.com/Yibbzz/networkforge/blob/main/docs/network-rules.md#turn-restrictions)).
53
+ - **Build a network from your own lines alone**, with no OpenStreetMap
54
+ ([how](https://github.com/Yibbzz/networkforge/blob/main/docs/network-rules.md#a-network-of-your-own-lines)).
55
+ - **Before and after**: the untouched network and the changed one, to
56
+ compare routes, travel times and catchments.
57
+ - **Describe streets with OpenStreetMap tags** as plain attributes
58
+ (`highway`, `maxspeed`, `oneway`, `maxheight`, `surface`, ...), or pick
59
+ a preset for the whole layer. Values are checked before anything is
60
+ built.
61
+
62
+ The OSM PBF output is tested by routing on it with
63
+ [Valhalla](https://valhalla.github.io/valhalla/): the "before" file routes
64
+ like OpenStreetMap itself, and each rule above is checked for cars,
65
+ buses, lorries, bikes and walking. It is also tested in
66
+ [R5](https://github.com/conveyal/r5), the router behind transport
67
+ accessibility tools, for walking, cycling, driving and public transport.
68
+ [docs/network-analyst.md](https://github.com/Yibbzz/networkforge/blob/main/docs/network-analyst.md)
69
+ compares the result with ArcGIS Network Analyst's network dataset,
70
+ feature by feature.
71
+
72
+ ## What it doesn't do
73
+
74
+ NetworkForge builds **street networks** (driving, cycling, walking). It
75
+ is not a full transport model:
76
+
77
+ - **No public transport.** Trains, trams, metro and buses are not
78
+ modelled: their travel times depend on timetables, not just the
79
+ physical network. Railway and tram tracks are not included. Ferry
80
+ routes are written to the OSM file for routers, but are not in the
81
+ GeoPackage. The PBF can be combined with GTFS timetables in tools such
82
+ as [r5py](https://r5py.readthedocs.io) or
83
+ [OpenTripPlanner](https://www.opentripplanner.org).
84
+ - **Existing streets can be re-tagged or removed, not redrawn.** To move
85
+ one, remove it and draw the new line.
86
+ - **No traffic simulation.** No congestion, demand or signal timing:
87
+ travel times come from speed limits (or default speeds per road type).
88
+ You can add **time-of-day speeds** you assume ("main roads at 60% on
89
+ weekday mornings") for Valhalla with `networkforge traffic`
90
+ ([docs/traffic.md](https://github.com/Yibbzz/networkforge/blob/main/docs/traffic.md)), but nothing reacts to them.
91
+ - **Turn restrictions are for routers.** Existing ones and the ones you
92
+ draw are written to the OSM file and Valhalla obeys them. The
93
+ GeoPackage's `restrictions` layer shows them on the map, but QGIS's own
94
+ network tools don't obey them.
95
+ - **The GeoPackage is simpler than a router.** Its `car`, `bike` and
96
+ `walk` columns say who may use each street and in which direction,
97
+ which is what QGIS's network tools need. Its `nodes` and
98
+ `restrictions` layers show bollards, signals and banned turns, but the
99
+ edges' columns ignore them, and turn delays and ferries. For those,
100
+ route on the PBF with a router such as Valhalla.
101
+
102
+ ## Install
103
+
104
+ From [PyPI](https://pypi.org/project/networkforge/):
105
+
106
+ ```bash
107
+ pip install networkforge
108
+ ```
109
+
110
+ For traffic (`networkforge traffic`), install the `traffic` extra, which
111
+ adds Valhalla's Python package (pyvalhalla):
112
+
113
+ ```bash
114
+ pip install "networkforge[traffic]"
115
+ ```
116
+
117
+ To run the command line without installing it into a project, use
118
+ `uvx networkforge` or `pipx install networkforge`. Older versions are on
119
+ the [Releases](https://github.com/Yibbzz/networkforge/releases) page.
120
+
121
+ For development, clone the repo and run `uv sync`.
122
+
123
+ ## Command line
124
+
125
+ ```bash
126
+ # Check a custom layer's tags (seconds, no download)
127
+ networkforge check --custom proposed.gpkg --preset primary_road
128
+
129
+ # Build: every custom line a 40 mph primary road; routers get PBF,
130
+ # QGIS gets before/after GeoPackages
131
+ networkforge build --extent extent.gpkg --custom proposed.gpkg \
132
+ --preset primary_road --tag maxspeed="40 mph" \
133
+ --out after.osm.pbf --baseline-out before.osm.pbf \
134
+ --gpkg after.gpkg --baseline-gpkg before.gpkg
135
+
136
+ # A network from your own lines alone (no OpenStreetMap, no area needed)
137
+ networkforge build --no-osm --custom streets.gpkg --out network.osm.pbf \
138
+ --gpkg network.gpkg
139
+
140
+ # Area given as W,S,E,N, OSM data from a local extract
141
+ networkforge build --bbox -4.60,54.10,-4.40,54.25 --custom proposed.gpkg \
142
+ --osm-source isle-of-man.osm.pbf --out network.osm.pbf
143
+
144
+ # Time-of-day traffic for Valhalla: the same bands on before and after,
145
+ # each written as a Valhalla graph (.tar; Network Analyst: Graphs > From Tar)
146
+ networkforge traffic --pbf after.osm.pbf --settings traffic.toml --out after.tar
147
+ networkforge traffic --pbf before.osm.pbf --settings traffic.toml --out before.tar
148
+ # ...and single streets of your own, or measured speeds by OSM way id
149
+ # (CSV: osm_id,days,time,speed_kph,direction), which override the road classes
150
+ networkforge traffic --pbf after.osm.pbf --settings traffic.toml \
151
+ --streets busy-streets.gpkg --id-field fid --speed-table speeds.csv --out after.tar
152
+
153
+ networkforge presets # list presets and who can use them
154
+ networkforge info # version, presets, valid tag values, limits
155
+ networkforge build --help # every option
156
+ ```
157
+
158
+ **For programs driving the CLI** (e.g. a QGIS plugin):
159
+
160
+ - `--json` puts one JSON object per line on stdout; readable logs stay on
161
+ stderr. Events: `progress`, `warning` (with the `features` or `fields`
162
+ it's about, and `gaps` - `[west, south, east, north]` boxes - when a
163
+ local OSM file doesn't cover the area), `done` (output paths and
164
+ counts), `error` (with `type`, `message`, `guide`, and `issues`:
165
+ `[{"feature": 3, "message": ...}]`). `guide` is the page and section
166
+ in `docs/` that explains the fix, e.g. `network-rules.md#presets`.
167
+ - `--id-field NAME` names features in warnings and issues by that
168
+ attribute, e.g. `--id-field fid` for a GeoPackage's feature ids, so a
169
+ front end can select the features concerned. Default: row number.
170
+ - `networkforge info --json` describes the engine: presets, network
171
+ types, valid tag values (for building input forms), limits.
172
+ - Exit codes: `0` success, `2` bad usage, `3` unusable input, `4` OSM
173
+ download failed, `5` structural check failed, `1` anything else.
174
+
175
+ ## Outputs
176
+
177
+ | Output | For | What's in it |
178
+ |---|---|---|
179
+ | `.osm.pbf` / `.osm` (`--out`, `write_osm`) | Routing engines: **Valhalla**, **R5**, **GraphHopper**, OpenTripPlanner | Standard OSM data with every routing tag; the engine applies its own profiles |
180
+ | GeoPackage (`--gpkg`, `write_gpkg`) | **QGIS**: viewing, styling, and its network analysis tools | `nodes`, `edges` and `restrictions` layers; edges have ready-made analysis columns |
181
+
182
+ GeoPackage `edges` columns, besides the OSM tags (`highway`, `maxspeed`, ...):
183
+
184
+ | Column | Meaning |
185
+ |---|---|
186
+ | `car`, `bike`, `walk` | Whether that mode may use the edge (same access rules as the engine; `car` excludes service roads) |
187
+ | `speed_kph` | Car speed: `maxspeed`, else a default for the road type |
188
+ | `length_m` | Length in metres |
189
+ | `car_minutes`, `bike_minutes`, `walk_minutes` | Travel time (bikes 15 km/h, walking 5 km/h) |
190
+ | `car_direction`, `bike_direction` | `forward`, `backward` or `both`, relative to the line's direction. Bikes follow `oneway:bicycle=no`. Walking is always both ways. |
191
+ | `custom` | `yes` on your custom edges |
192
+
193
+ Each street is one row, and every edge ends exactly on its nodes, so QGIS
194
+ connects the network correctly. For example, a 10-minute drive area in QGIS:
195
+
196
+ 1. Filter the `edges` layer to `"car"` (layer Properties > Source > Query Builder).
197
+ 2. Processing > Network analysis > **Service area (from point)**:
198
+ path type *Fastest*, travel cost `600` (seconds),
199
+ *Direction field* `car_direction` with values `forward` / `backward` / `both`,
200
+ *Speed field* `speed_kph`.
201
+
202
+ Use `bike` / `bike_direction` (or `walk`, with no direction field) for other
203
+ modes, and `*_minutes` as the cost field in plugins such as QNEAT3.
204
+
205
+ The `nodes` layer has the node tags routers act on (`barrier`,
206
+ `highway` = traffic_signals / crossing / stop ..., `crossing`,
207
+ `railway`, access on a barrier including `hgv` and `bus`, `toll`,
208
+ `maxheight`, `maxwidth`, `maxweight`, `entrance`), a column for each only
209
+ when some node has it. A node your points tagged has `modified` = `yes`
210
+ (an OpenStreetMap node) or `custom` = `yes` (a new node, or one on your
211
+ own line).
212
+
213
+ The `restrictions` layer has one line per turn restriction in the OSM
214
+ file written with it (OpenStreetMap's and yours): 20 m along the street
215
+ you arrive on, through the junction (or along the via way), 20 m along
216
+ the street you leave on. Columns: `relation_id`, `from_way`, `via_node`
217
+ or `via_ways`, `to_way`, `custom` (`yes` on yours), and the relation's
218
+ tags (`restriction`, `restriction:hgv`, `except`, ...). The layer is
219
+ always there, empty when there are none; drawn from the OSM data, it
220
+ matches the PBF exactly.
221
+
222
+ ## Python
223
+
224
+ ```python
225
+ import logging
226
+ import geopandas as gpd
227
+ from networkforge import build_network, write_osm
228
+
229
+ logging.basicConfig(level=logging.INFO) # show build progress
230
+
231
+ bbox = gpd.read_file("extent.gpkg")
232
+ custom = gpd.read_file("proposed.gpkg")
233
+
234
+ # Blanket: every custom line is a 40 mph primary road
235
+ nodes, edges = build_network(bbox, custom, preset="primary_road",
236
+ network_tags={"maxspeed": "40 mph"})
237
+
238
+ # Per feature: each line's own attributes (highway, maxspeed, bridge, ...)
239
+ nodes, edges = build_network(bbox, custom)
240
+
241
+ write_osm(nodes, edges, "network.osm.pbf") # or .osm, .osm.gz, .osm.bz2
242
+ ```
243
+
244
+ **Large areas:** the existing network comes from the shared Overpass
245
+ API, limited to boxes of 1,000 km². For bigger areas (or faster,
246
+ repeatable builds) download an extract, e.g. from
247
+ [Geofabrik](https://download.geofabrik.de), and pass
248
+ `osm_source="region.osm.pbf"`. See [docs/osm-data.md](https://github.com/Yibbzz/networkforge/blob/main/docs/osm-data.md).
249
+
250
+ `write_osm` picks the format from the file name. PBF is OpenStreetMap's
251
+ compressed binary format (open, [documented on the OSM wiki](https://wiki.openstreetmap.org/wiki/PBF_Format)),
252
+ several times smaller than XML and what routers such as GraphHopper
253
+ and Valhalla read. The existing network is written as OpenStreetMap
254
+ has it: every way keeps its id, its nodes and all its tags, and turn
255
+ restrictions are kept, so a router treats the "before" file like
256
+ OpenStreetMap itself. Your lines are added as new ways (tagged
257
+ `nf:custom=yes`), and where one joins an existing street the junction is
258
+ added to that street.
259
+
260
+ The PBF is tested in [Valhalla](https://valhalla.github.io/valhalla/):
261
+ one-way streets, footpaths, low bridges, unpaved roads, bus-only roads,
262
+ bridges over roads and more are each routed on and checked. See
263
+ [docs/network-analyst.md](https://github.com/Yibbzz/networkforge/blob/main/docs/network-analyst.md), which compares the
264
+ result with ArcGIS Network Analyst's network dataset feature by feature.
265
+
266
+ It is also tested in [R5](https://github.com/conveyal/r5) (through
267
+ [r5py](https://r5py.readthedocs.io/), `tests/r5`): new streets and
268
+ paths, removed streets, one-way streets, and a bus trip whose walks to
269
+ and from the stops follow the new streets. R5 reads less than Valhalla
270
+ does, which matters if R5 is your router:
271
+ - it ignores barriers: a bollard or gate you add is in the PBF, but R5
272
+ routes through it;
273
+ - it judges how stressful a street is to cycle on from `cycleway=lane`,
274
+ not `cycleway=track`: draw a cycle track as a line of its own
275
+ (`highway=cycleway`) to open a busy road's route to cyclists;
276
+ - it drops connected pieces of fewer than about 40 streets, so a very
277
+ small network of your own lines may come back empty.
278
+
279
+ A feature's own attributes win over a preset unless you pass
280
+ `overwrite_tags=True`. Who may use each way follows OSM rules: the
281
+ way type plus the access hierarchy (`motorcar` > `motor_vehicle` >
282
+ `vehicle` > `access`, and `bicycle` / `foot` for bikes and walking). Custom lines join every street they cross,
283
+ except motorways, bridges, tunnels and other layers, which they pass
284
+ over or under. Tags are checked against OSM rules before anything is
285
+ downloaded.
286
+
287
+ **[Network rules](https://github.com/Yibbzz/networkforge/blob/main/docs/network-rules.md)**: how lines join the network,
288
+ presets, every supported
289
+ attribute, recipes (bypass, cycle route, bus gate, bridge, slip road),
290
+ and what each error message means.
291
+
292
+ Errors are subclasses of `networkforge.NetworkForgeError` (`InputError`,
293
+ `InvalidTagsError`, `NoIntersectionError`, `OSMDownloadError`,
294
+ `NetworkIntegrityError`). Progress goes to the `networkforge` logger,
295
+ and to an optional `progress(step, total, description)` callback.
296
+
297
+ The exported network contains OpenStreetMap data, © OpenStreetMap
298
+ contributors, available under the
299
+ [Open Database License](https://www.openstreetmap.org/copyright).
300
+
301
+ ## Tests
302
+
303
+ ```bash
304
+ uv run pytest # offline suite (unit, synthetic grid, property-based)
305
+ uv run pytest --cov # ... with a coverage report
306
+ uv run pytest -m network # live OSM tests in real cities (slow, needs internet)
307
+ uv run ruff check . # lint
308
+ ```
309
+
310
+ | Folder | What | Speed |
311
+ |---|---|---|
312
+ | `tests/unit/` | tag rules, CRS selection | milliseconds |
313
+ | `tests/integration/` | full pipeline on a hand-built street grid, plus Hypothesis property tests | seconds |
314
+ | `tests/live/` | real OSM data in Edinburgh, Amsterdam, Manchester | minutes |
315
+ | `tests/qgis/` | the GeoPackage routed with QGIS's own tools (`qgis_process`) | ~1 min, needs QGIS |
316
+
317
+ CI runs lint, the offline suite and the QGIS check (in the official
318
+ `qgis/qgis` Docker image, LTR and latest) on every push and pull request,
319
+ and the live tests plus a 500-example property run nightly.
320
+
321
+ To run the QGIS check locally (with QGIS installed):
322
+
323
+ ```bash
324
+ uv run python -m tests.qgis.make_fixture qgis-fixture
325
+ QT_QPA_PLATFORM=offscreen python3 tests/qgis/check_with_qgis.py qgis-fixture # QGIS's Python
326
+ ```
327
+
328
+ ## Releasing
329
+
330
+ 1. Update `version` in `pyproject.toml` and add a `## [x.y.z]` section to
331
+ [CHANGELOG.md](https://github.com/Yibbzz/networkforge/blob/main/CHANGELOG.md).
332
+ 2. Commit, then tag and push: `git tag vX.Y.Z && git push origin vX.Y.Z`.
333
+
334
+ The release workflow checks the tag matches the version, runs the tests,
335
+ builds the package and publishes a GitHub Release with the changelog
336
+ notes and the wheel attached.
@@ -0,0 +1,25 @@
1
+ networkforge/__init__.py,sha256=kpfE7RMmoepa-uRwmGipWG0SY3gPuTfTp4yXxJYyA9Q,1151
2
+ networkforge/__main__.py,sha256=ciNjAw5WSxLKOtpcTwUZzrqq0XXcULwuho4Ekue1nxU,117
3
+ networkforge/cli.py,sha256=jBhHmcmrbFdec0nAXHsNy2t-EnEa1-GbT5JlTEeszec,31587
4
+ networkforge/coverage.py,sha256=NcKpAEMRclBOGYdIdbzxeprFFXO2IuEb4IH6w3j689k,7357
5
+ networkforge/edits.py,sha256=7jxdtD4qMR9CbOJHZ2eqB66MZKkBRKC4HmvEoRwCPnw,20650
6
+ networkforge/errors.py,sha256=75i5uHl5zKVSSNTDann4GHMeamW03_whEu5iR-aCago,3361
7
+ networkforge/export.py,sha256=fVYKAOE88lLqL6QMvfNZFCkgni41nwuHMxbuxatMKvA,45207
8
+ networkforge/inputs.py,sha256=XUEj-ptiTSkBOBTAYS1xHFMY9v5NBfM_h2s0noiAgYc,10884
9
+ networkforge/modes.py,sha256=OJNG754UU0yxz3ovW6D80bEQ1fiBw_BQnjU8HOzl-wc,13039
10
+ networkforge/network.py,sha256=Re8ohZQQgemdDGDrk3ri1sVoMZOS9iDwRRv5MCyy_Xc,28555
11
+ networkforge/osm.py,sha256=JqUJK-9rZ3tlsbG0BPKhx6ZzGLqU3hndaz_JZXLXoag,19214
12
+ networkforge/paths.py,sha256=4sSzxR4sZ-ZSLSGeDBMQzMtuQ6XmDK_PUlO6PTSTYeI,3282
13
+ networkforge/points.py,sha256=jBjc9i-Ij6wnP4htRn2_4LIrfF7D2lLGPuICmnygMbg,12761
14
+ networkforge/presets.py,sha256=eF1NyKX-zJ9oyC9PTN4w6iD8KGks8xNkj2V6iTJNbiI,2183
15
+ networkforge/projection.py,sha256=4k4aDOuIzPkH1D0fJ1cRG9BnTQBsT0gmp6ACxkbhmjA,2145
16
+ networkforge/tags.py,sha256=ygSjKjhMJOmLcjJIhXrWk3unTXMYMZpHHjlKEZw5OjI,6120
17
+ networkforge/topology.py,sha256=jAVDnf-lURhgIteVHoI2f0cFQFeTUgVl1pEMAz4gXSc,43113
18
+ networkforge/traffic.py,sha256=dcpG-ag4LiN0TU2SCi6EQwrzsUzN8ObY_pyCl43n32A,40695
19
+ networkforge/turns.py,sha256=c1T8WGejhf-sSQugLB06A5iBuKQzcYedhtlG9RTISpw,13494
20
+ networkforge/validation.py,sha256=Tz9L6fLl88kraLucR239mNlPqoiXdp6Sa8KtAGk8Ss4,25797
21
+ networkforge-1.4.1.dist-info/METADATA,sha256=40CjTqzYHvzGffTA4kXE1iZP3_dVaeGBxNSkt_-NEVo,16697
22
+ networkforge-1.4.1.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
23
+ networkforge-1.4.1.dist-info/entry_points.txt,sha256=ap7Nue2HExrCDoMk3MTRv8MWETJ0VKEknYUySaUugZE,55
24
+ networkforge-1.4.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
25
+ networkforge-1.4.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ networkforge = networkforge.cli:main