Blocks from Streets Come Out Wrong

On this page

Problem statement

Polygonising the street network was supposed to give the city's blocks. Instead there are hundreds of thin slivers along the main roads, tiny triangles at every large junction, a few enormous "blocks" that swallow several real ones, and a count that bears no relation to what a map shows. The polygonisation is doing exactly what it should; the street network is not the clean set of block boundaries it looks like.

Four features of real street data cause almost all of it: dual carriageways mapped as two lines, junctions mapped as several nodes, lines that end without meeting another line, and a boundary set that includes or omits the wrong kinds of way.

Quick answer

Diagnose by shape and size, then fix the network or filter the faces:

import numpy as np, geopandas as gpd, osmnx as ox, momepy
from shapely.geometry import box

ox.settings.http_user_agent = "spatialworkflow-examples/1.0"
ox.settings.overpass_url = "https://maps.mail.ru/osm/tools/overpass/api"   # overpass-api.de mirror
ox.settings.overpass_rate_limit = False
G = ox.graph_from_point((52.0799, 4.3113), dist=1500, network_type="drive")
streets = ox.graph_to_gdfs(ox.convert.to_undirected(G), nodes=False).to_crs(28992)
aoi = gpd.GeoSeries([box(80000, 454000, 82000, 456000)], crs=28992)
faces = momepy.enclosures(streets, limit=aoi)
faces["area"] = faces.area
faces["elong"] = momepy.elongation(faces)

kind = np.select([faces.area < 200, (faces.elong < 0.15) & (faces.area < 5000),
                  faces.area > 100_000],
                 ["junction fragment", "carriageway sliver", "oversized"], "plausible block")
print(faces.assign(kind=kind).groupby("kind").agg(count=("area", "size"),
                                                  median_m2=("area", "median")).round(0).to_string())
                    count  median_m2
kind                                
carriageway sliver    109      951.0
junction fragment     154       78.0
oversized               6   132752.0
plausible block       550     7033.0

Fragments and slivers are street space counted as blocks; oversized faces are usually several blocks merged because a street that should divide them is missing from the network or not connected.

Undirected network, slivers, junction fragments, open ends

1. Use an undirected network

Directed graphs duplicate two-way streets; polygonising two identical lines produces zero-area faces.

2. Find the slivers

Long, thin faces between parallel lines a few metres apart are dual carriageways. Filter them by elongation and area, or collapse the two carriageways to one centreline before polygonising.

3. Find the junction fragments

Complex junctions mapped as several nodes create small triangles and islands. Consolidate nearby intersections, or drop faces below a minimum area.

4. Close the open ends

A street that stops a metre short of the next does not split the block. Extend dangling lines to the nearest line within a tolerance (momepy.extend_lines).

5. Check the boundary set

Service roads, footpaths, railways and rivers change which faces exist. Decide which ways bound a block and apply the choice consistently.

Wrong-looking blocks with the network feature that causes each and the fix. four network features behind wrong blockswhat you seecause β†’ fixlong thin blocks along roadsdual carriageway β†’ elongationfiltertiny triangles at junctionsmulti-node junction β†’ consolidatehuge merged blocksstreet stops short β†’ extend_lineszero-area facesdirected graph β†’ to_undirectedIn Den Haag: 152 fragments and 111 slivers among 815 faces spatialworkflow.io
Four network features behind almost every wrong block.

Consolidating junctions and extending dangling ends

The data used on this page

OpenStreetMap's drive network within 1.5 km of Den Haag's Hofvijver (52.0799Β° N, 4.3113Β° E), fetched by the quick answer through OSMnx and the maps.mail.ru Overpass mirror, and cut to a 2 Γ— 2 km square in the Dutch national grid (EPSG:28992). The setup unpacks that answer as saved on 9 October 2026, so the face counts below stay put while OpenStreetMap is edited.

import io
import os
import requests
import zipfile

# OpenStreetMap as Overpass returned it on 9 October 2026, saved as osmnx's own cache so the counts
# below do not drift as OSM is edited; delete the cache folder to query live instead
if not os.path.exists("cache"):
    reply = requests.get("https://data.spatialworkflow.io/osm-cache-blocks-from-streets-wrong-2026-10-09.zip", timeout=300)
    reply.raise_for_status()
    with zipfile.ZipFile(io.BytesIO(reply.content)) as archive:
        archive.extractall("cache")

Example 1 β€” consolidate junctions before polygonising

Gc = ox.consolidate_intersections(ox.project_graph(G), tolerance=12,
                                  rebuild_graph=True, dead_ends=False)
streets_c = ox.graph_to_gdfs(ox.convert.to_undirected(Gc), nodes=False).to_crs(28992)
faces_c = momepy.enclosures(streets_c, limit=aoi)
for label, f in (("raw network", faces), ("consolidated 12 m", faces_c)):
    print(f"{label:<18} {len(f):>4} faces, {(f.area < 200).sum():>3} under 200 mΒ², "
          f"{((momepy.elongation(f) < 0.15) & (f.area < 5000)).sum():>3} slivers")
raw network         819 faces, 154 under 200 mΒ², 119 slivers
consolidated 12 m   765 faces, 117 under 200 mΒ², 146 slivers

Consolidation merges clustered junction nodes and removes nearly a quarter of the small fragments (154 β†’ 117). It is not free: the sliver count rises from 119 to 146, because moving junction nodes to their cluster centre bends the lines between them, and some fragments become long thin faces instead. Slivers between carriageways further apart than the tolerance are not touched. Consolidate, then still filter.

Example 2 β€” extend dangling ends

extended = momepy.extend_lines(streets, tolerance=15, target=streets)
faces_e = momepy.enclosures(extended, limit=aoi)
print(f"faces before extending: {len(faces)}, after: {len(faces_e)}; "
      f"oversized before {(faces.area > 100_000).sum()}, after {(faces_e.area > 100_000).sum()}")
faces before extending: 819, after: 819; oversized before 6, after 6

Here extending changes nothing: 819 faces and six oversized ones, before and after. The Den Haag drive network has no dangling ends within 15 m of another street, so its six oversized faces are large for another reason β€” a park, or a block divided only by ways that are not in the drive network. The step is still worth running; on hand-traced networks with gaps at junctions it is what closes blocks.

Example 3 β€” filter what remains, and keep the evidence

clean = faces[(faces.area >= 200) & ~((faces.elong < 0.15) & (faces.area < 5000))].copy()
clean["flag_oversized"] = clean.area > 100_000
print(f"{len(faces)} faces β†’ {len(clean)} blocks; {clean.flag_oversized.sum()} flagged as oversized")
print(f"median block {clean.area.median():,.0f} mΒ² (all faces: {faces.area.median():,.0f} mΒ²)")
819 faces β†’ 556 blocks; 6 flagged as oversized
median block 7,123 mΒ² (all faces: 2,332 mΒ²)

Filtering triples the median block size β€” the slivers and fragments were dragging it down. Flagging rather than deleting oversized faces keeps parks and genuinely large blocks while marking them for a check.

Faces from polygonising the Den Haag drive network by kind: plausible blocks, carriageway slivers, junction fragments and oversized faces. 819 faces from 4 kmΒ² of Den Haagplausible block550 Β· median 7,033 mΒ²fragment (< 200 mΒ²)154 Β· median 78 mΒ²carriageway sliver109 Β· median 951 mΒ²oversized (> 10 ha)6 Β· median 13 ha0300600A third of the faces are street space, not blocks spatialworkflow.io
What polygonising a raw street network actually produces.

Dual carriageways, junction clusters and missing lines

Why OSM maps dual carriageways as two lines

OSM represents each direction of a physically separated road as its own way, because they are separate for routing. That is correct for navigation and wrong for block extraction, where the whole road is one boundary.

Why junctions become clusters of nodes

Large junctions have turn lanes, slip roads and islands, each mapped as its own way meeting at its own node. The planar graph therefore has many small faces inside every big junction.

Why oversized faces mean missing lines

A face is bounded by lines; if a street is missing, not connected, or excluded by the network type (a service road, a footpath), the faces on either side merge. Oversized faces are the best pointer to where the network is incomplete for the question.

Faces, fragments and slivers from the raw Den Haag network, after consolidating junctions and after extending dangling lines. what each network fix changed herenetworkfacesunder 200 mΒ²sliversraw819154119consolidated, 12 m765117146extended, 15 m819154119filtered556 blocks00Median block 2,332 mΒ² before filtering, 7,123 mΒ² after spatialworkflow.io
Consolidation trades fragments for slivers; extension changed nothing here; filtering does the work.

Tolerances, roundabouts and motorways

  • Tolerance choices (consolidation, extension) are parameters; report them.
  • Roundabout centres are small round faces; filter them with the fragments.
  • Bridges cross streets without junctions; polygonising 2D lines splits faces at the crossing.
  • Motorways bound very large faces; exclude them from the network if they are not block boundaries in your sense.
  • Validation: overlay the result on imagery for a sample before trusting counts.

FAQ

Why do I get thin sliver blocks along main roads?

Dual carriageways are mapped as two lines; the space between them becomes a face.

Why are there tiny polygons at junctions?

Complex junctions are mapped as clusters of nodes and short ways. Consolidate intersections or filter by area.

Why is one block enormous?

A street that should split it is missing, unconnected or excluded from the network.

How do I close gaps between lines?

Extend dangling ends to the nearest line within a tolerance, for example with momepy.extend_lines.

Should I delete oversized blocks?

Flag them; some are real (parks, campuses) and some reveal missing streets.

Which streets should bound blocks?

It depends on the question β€” traffic blocks or pedestrian blocks β€” and must be consistent.