{"id":16528416,"url":"https://github.com/danvk/extract-raster-network","last_synced_at":"2025-09-12T19:31:42.786Z","repository":{"id":41407713,"uuid":"413880960","full_name":"danvk/extract-raster-network","owner":"danvk","description":"Extract a network graph (nodes and edges) from a raster image","archived":false,"fork":false,"pushed_at":"2021-10-14T14:12:51.000Z","size":229,"stargazers_count":15,"open_issues_count":0,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-01-03T03:50:12.900Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/danvk.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-10-05T15:45:31.000Z","updated_at":"2024-05-18T20:03:58.000Z","dependencies_parsed_at":"2022-08-27T05:52:52.319Z","dependency_job_id":null,"html_url":"https://github.com/danvk/extract-raster-network","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danvk%2Fextract-raster-network","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danvk%2Fextract-raster-network/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danvk%2Fextract-raster-network/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danvk%2Fextract-raster-network/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/danvk","download_url":"https://codeload.github.com/danvk/extract-raster-network/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":232780145,"owners_count":18575474,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-11T17:40:20.010Z","updated_at":"2025-01-06T19:59:50.087Z","avatar_url":"https://github.com/danvk.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Extract Raster Network\n\nExtract a network graph (nodes and edges) from a raster image.\nSee this [Stack Overflow question][1] for details.\n\n## Quickstart\n\n    python3 -m venv venv\n    source venv/bin/activate\n    pip install -r requirements.txt\n    ./extract_network.py samples/grid1.png '(0, 0, 255)'\n\nThis writes `samples/grid1.grid.geojson` and `samples/grid1.grid.png`.\n\n## What this does\n\nThis takes a raster (PNG) image containing some kind of network (e.g. a street grid):\n\n![Raster containing street grid](samples/grid1.png)\n\nand finds the nodes (e.g. intersections) and edges between them (e.g. streets):\n\n![Image showing extracted street grid](samples/grid1.grid.png)\n\nHere the large circle are extracted nodes and the lines and small circles indicate the\nextracted polylines for the edges between them.\n\nHere are some more complex examples including multiple paths between two nodes and a\nself-loop:\n\n![More complex grid feature two paths between nodes](images/grid2.grid.png)\n![More complex grid featuring a self-loop](images/grid3.grid.png)\n\n## Algorithm\n\nThe general approach is:\n\n1. Binarize the image by finding pixels matching a specific color.\n2. [Skeletonize][skel] the image to make the \"streets\" 1px wide.\n3. Find candidate nodes (see below).\n4. Repeat until no two nodes are too close together:\n   1. Use breadth-first search (flood fill) to connect nodes.\n   2. If two connected nodes are within D of each other, merge them.\n5. Run shapely's [`simplify`][simplify] on the paths between nodes to get polylines.\n\n\"Find candidate nodes\" and \"Use breadth-first search\" are the interesting bits.\nThis code largely follows the approach from [NEFI][] with a few modifications:\n\n- This uses the \"Zhang-Suen\" nodes (those with exactly 1 or 3+ neighbors) as a\n  starting point (same as NEFI). This depends heavily on the skeleton being _exactly_\n  1px wide, which is not always the case for complex intersections. To address this\n  situation, we add candidate nodes at locally dense (2x2 or larger) locations in the\n  skeleton.\n\n- We do a breadth-first search from the candidate nodes, same as NEFI.\n  The departures are that we:\n  - Allow self-loops and multiple edges between the same pair of nodes.\n    The image below contains examples of both of these.\n  - Iteratively merge nodes that are too close to one another.\n\nThe final shapely `simplify` step does not change the network topology but is convenient\nfor visualizing and working with the resulting polyline.\n\n### Visualization of the steps\n\nStarting image is the same as above ([`grid1.png`](/samples/grid1.png)).\n\nHere's the binarized version:\n\n![Binary version](images/grid1.bin.png)\n\nHere's the result of skeletonization (yellow line overlaid on the original image):\n\n![Skeleton](images/grid1.skel.png)\n\nHere are the Zhang-Suen nodes:\n\n![Zhang-Suen nodes](images/grid1.zhang-suen.png)\n\nIt's a bit hard to see because the nodes are so close together, but there are two nodes\nat the four-way intersection in the center of the image and three nodes at two of the\nfour-way intersections at the bottom of the image. These will have to be merged later.\n\nHere's the results of the initial breadth-first search (flood fill):\n\n![Paths, first take](images/grid1.paths-take1.png)\n\nNot bad! Now it's time to merge nearby nodes. First the duplicated nodes at the four-way\nintersection in the middle-left of the image, which are only 1px apart, and then the one\nin the center of the image:\n\n    Merged (112, 180) and (112, 181), d=1\n    Merged (261, 173) and (263, 172), d=2\n\nHere's a GIF showing the results of these two merges:\n\n![Merge one and two](images/merge-nodes.gif)\n\nThis repeats until no nodes are left to merge:\n\n![Merge sequence](images/merging.gif)\n\nAt this point we have all our nodes and edges. But the paths are long sequences of 1px\nsegments. For example:\n\n    \u003e path.wkt\n    LINESTRING (173 262, 172 263, 172 264, 172 265, 172 266, 172 267, 172 268, 172 269, 172 270, 172 271, 172 272, 172 273, 172 274, 172 275, 171 276, 171 277, 171 278, 171 279, 171 280, 171 281, 171 282, 171 283, 171 284, 171 285, 171 286, 171 287, 171 288, 171 289, 171 290, 171 291, 171 292, 170 293, 170 294, 170 295, 170 296, 170 297, 170 298, 170 299, 170 300, 170 301, 170 302, 170 303, 170 304, 170 305, 170 306, 170 307, 170 308, 169 309, 169 310, 169 311, 169 312, 169 313, 169 314, 169 315, 169 316, 169 317, 169 318, 169 319, 169 320, 169 321, 169 322, 169 323, 169 324, 168 325, 168 326, 168 327, 168 328, 168 329, 168 330, 168 331, 168 332, 168 333, 168 334, 168 335, 168 336, 168 337, 168 338, 168 339, 168 340, 168 341, 167 342, 167 343, 167 344, 167 345, 167 346, 167 347, 167 348, 167 349, 167 350, 167 351, 167 352, 167 353, 167 354, 167 355, 167 356, 167 357, 167 358, 167 359, 167 360, 167 361)\n\nUsing shapely's simplify greatly shrinks this with little loss of information. In fact,\nit may be a better reflection of the street's true (vector) shape before rasterization:\n\n    \u003e path.simplify(1).wkt\n    LINESTRING (173 262, 168 325, 167 361)\n\nHere's the final result:\n\n![Final result of network extraction](images/grid1.final.png)\n\n## Development\n\nTo run the tests:\n\n    pytest\n\n## References\n\n- [Stack Overflow question][1]\n- [NEFI2][2], a GUI app for Network Extraction. See also [their 2018 paper][3].\n\nThis repo includes code from NEFI2; see their [license][].\n\n[1]: https://stackoverflow.com/questions/69398683/extract-street-network-from-a-raster-image\n[2]: https://github.com/05dirnbe/nefi\n[3]: https://arxiv.org/pdf/1502.05241.pdf\n[skel]: https://scikit-image.org/docs/stable/auto_examples/edges/plot_skeleton.html\n[simplify]: https://shapely.readthedocs.io/en/stable/manual.html#object.simplify\n[nefi]: https://github.com/05dirnbe/nefi/blob/260b2717ebc5fb94b2a241c5b73540b41f3dc6bf/nefi2/model/algorithms/guo_hall.py#L63\n[license]: https://github.com/05dirnbe/nefi/blob/master/LICENSE\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanvk%2Fextract-raster-network","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanvk%2Fextract-raster-network","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanvk%2Fextract-raster-network/lists"}