Extract Clipping & Boundary Polygons Jump to heading
Almost no production pipeline processes the planet. It processes a region — a country, a metropolitan area, a service territory drawn by a business rather than by a border — and the first stage of that pipeline is a cut: taking a larger .osm.pbf and producing a smaller one covering exactly the area of interest. The operation sounds like a spatial filter and is routinely implemented as one, which is where the trouble starts. OpenStreetMap objects are not independent geometries that can be tested individually against a polygon; they are a reference graph in which a way is nothing but a list of node identifiers, and a relation is nothing but a list of member identifiers. Cutting that graph with a polygon means deciding what to do with every edge that crosses the line, and the decision is not made for you.
This topic covers the mechanics of that cut. It defines the boundary formats OSM tooling accepts, walks the four osmium extract strategies and what each one keeps, quantifies the cost of choosing the wrong one, and sets out how to verify that the extract you produced is actually self-contained. It sits under OSM Data Fundamentals & Architecture because clipping is a format-level operation on the reference model described in the Node, Way & Relation Data Model — if the way-references-node relationship is unfamiliar, read that first, because every decision below is about preserving it.
Prerequisite concepts Jump to heading
Three ideas from elsewhere on this site do most of the work here. The first is the reference model: ways store node identifiers, not coordinates, so a way is only renderable if every node it names is present in the same file. The second is the PBF File Structure Deep Dive view of a file as ordered blocks of nodes, then ways, then relations, which is what makes a two-pass clip possible without holding the file in memory. The third is the bounding box in the file header, described in the same page, which lets a pipeline reject an extract that cannot possibly cover the area requested before spending twenty minutes proving it.
Boundary formats: bbox, .poly and GeoJSON Jump to heading
The cheapest boundary is a bounding box, given as four numbers in the order left, bottom, right, top. It is exact, it costs one comparison per node, and it is almost always the wrong shape. Countries are not rectangles, and a bounding box around a country with an awkward outline pulls in large parts of its neighbours — a bbox around Norway includes most of Sweden, and a bbox around Chile includes most of Argentina.
The Osmosis .poly format is the format OSM tooling has settled on for real boundaries. It is a plain-text file: a name line, then one or more polygon sections, each opened by an identifier line, followed by coordinate pairs as longitude then latitude, one pair per line, closed by END, with the whole file closed by a final END. A section identifier prefixed with ! marks a hole rather than an outer ring, which is how enclaves are expressed. The format has no CRS declaration; coordinates are always WGS 84 degrees, the same convention described in Coordinate Reference Systems in OSM.
ireland
1
-1.06E+01 5.14E+01
-5.30E+00 5.14E+01
-5.30E+00 5.55E+01
-1.06E+01 5.55E+01
-1.06E+01 5.14E+01
END
END
Recent osmium versions also accept GeoJSON directly, which is usually easier to produce because it comes straight out of the tools that already hold your boundaries. A GeoJSON boundary must be a single Feature or FeatureCollection containing Polygon or MultiPolygon geometry; a collection of separate features is interpreted as a multi-region request rather than as one boundary, which is a useful behaviour and an easy accident.
Vertex count matters more than the format. Every node tested against the boundary pays a point-in-polygon cost proportional to the number of edges, so a boundary simplified from an administrative relation with 180 000 vertices to one with 2 000 vertices runs roughly two orders of magnitude faster and, for a clipping operation, describes the same region to well within the accuracy anyone needs. Simplify boundaries with a tolerance of around 100 metres before using them to cut.
The four strategies Jump to heading
osmium extract takes a --strategy flag with four values, and this flag decides what “inside” means.
The simple strategy keeps a node if it is inside the polygon and a way only if every one of its nodes is inside. It is the fastest and produces the smallest file, and it truncates every feature that crosses the boundary. A coastline way running along the edge of the region disappears entirely; a road crossing the border is dropped rather than clipped. Use it only when the consumer genuinely does not care about edge features — a point-of-interest count, say.
The complete_ways strategy keeps a way if any of its nodes is inside, and then keeps every node that way references, including the ones outside the polygon. This is the strategy most pipelines actually want: features are geometrically complete, no way has a dangling reference, and the file is a few percent larger than simple. The output extends slightly beyond the boundary wherever a long way crosses it, which is a property to know about rather than a defect.
The smart strategy does everything complete_ways does, then additionally keeps relations that reference the kept objects and, in turn, the members those relations need. This is what makes multipolygon buildings, route relations and boundary relations survive a cut intact. It produces the largest output and takes the longest, and it is the correct choice whenever relations matter — which includes any routing, boundary or landuse work.
The referenced strategy inverts the question: it starts from relations you want and pulls in the ways and nodes they need. It is the right tool for extracting a specific set of features, such as one bus network, rather than a geographic area.
The numbers make the choice straightforward. Cutting Ireland out of a Europe extract, simple saves fifteen megabytes over complete_ways on a two-hundred-megabyte output and hands the next stage four thousand broken way references. Nothing downstream benefits from that trade.
Running the cut Jump to heading
A single-region cut is one command. The --strategy flag is the important one; the rest is plumbing.
osmium extract \
--polygon boundaries/ireland.poly \
--strategy=smart \
--output-header="osmosis_replication_base_url=https://planet.osm.org/replication/minute/" \
--overwrite \
-o extracts/ireland.osm.pbf \
europe-latest.osm.pbf
Two details in that command are worth dwelling on. --overwrite is required because osmium refuses to clobber an existing output, which is correct behaviour and inconvenient in an automated pipeline; set it deliberately rather than discovering it at 03:00. And --output-header is how replication metadata survives the cut. By default an extract carries the header fields of its parent, and if the parent was itself an extract with no replication anchor, the child has none either, which means the resulting file cannot be caught up by the diff-sync workflow described in OSM Replication & Diff Sync. Set the base URL and, where you know it, the sequence number, at cut time.
Cutting many regions from the same input should be one pass, not many. osmium extract accepts a JSON configuration listing every output and its boundary, and reads the input once:
{
"directory": "extracts",
"extracts": [
{ "output": "ireland.osm.pbf", "polygon": { "file_name": "boundaries/ireland.poly", "file_type": "poly" } },
{ "output": "scotland.osm.pbf", "polygon": { "file_name": "boundaries/scotland.geojson", "file_type": "geojson" } },
{ "output": "wales.osm.pbf", "bbox": [-5.35, 51.35, -2.63, 53.44] }
]
}
osmium extract --config extracts.json --strategy=smart europe-latest.osm.pbf
The saving is not marginal. Twelve separate runs over a 28 GB input read 336 GB; one configured run reads 28 GB. On network-backed storage that is the difference between a nightly job and a job that does not finish overnight.
Validation and error-handling matrix Jump to heading
| Condition | Root cause | How it surfaces | Action |
|---|---|---|---|
| Ways render as straight lines between distant points | simple strategy dropped intermediate nodes |
Visible only when rendered | Re-cut with complete_ways |
| Multipolygon buildings missing their holes | Relation members outside the polygon were dropped | Courtyards filled in | Re-cut with smart |
| Output is empty but the command succeeded | Boundary coordinates in latitude, longitude order | Zero objects written, exit code 0 | Swap to longitude, latitude |
| Extract cannot be caught up with diffs | Replication header not carried through | osmium fileinfo shows no sequence |
Re-cut with --output-header |
| Cut takes hours on a small region | Boundary has tens of thousands of vertices | CPU-bound, single core pinned | Simplify the boundary first |
| Coastline broken along the edge | Coastline ways truncated at the boundary | Rendering shows land bleeding into sea | Use complete_ways and re-run the coastline build |
The empty-output row deserves emphasis because it is silent. A .poly file written in latitude, longitude order describes a polygon somewhere off the coast of Somalia for most European regions, and osmium will happily cut it, write a valid PBF containing nothing, and exit zero. The check is trivial — assert that the output object count is non-zero — and it is not done by default.
Performance and scale considerations Jump to heading
Clipping is dominated by reading the input, not by the geometric test. A well-simplified boundary costs a few hundred nanoseconds per node; reading and inflating a 28 GB input costs minutes. Three consequences follow. First, cut from the smallest input that contains your region: cutting a German state out of the Germany extract rather than out of Europe reads 3 GB instead of 28 GB. Second, batch every region you need into one configured run. Third, spend effort on boundary simplification only when a profile shows the point-in-polygon test actually mattering, which it does only for pathological boundaries.
Memory behaviour differs sharply by strategy. simple streams in constant memory. complete_ways and smart need two passes and an identifier set: the first pass determines which ways and relations are wanted, and the second collects the nodes they reference. That identifier set is the memory cost, and it scales with the number of objects near the boundary rather than with the size of the input, so a long, convoluted boundary costs more than a compact one of the same area.
Failure modes and gotchas Jump to heading
The subtle failure is the one that produces a file that looks right. An extract cut with simple opens fine, renders mostly correctly, and passes a naive object count. Its damage shows up two stages later, when a routing graph built from it has roads that stop at the boundary and a connectivity check reports components that do not exist in the real network — a defect the Routing-Graph Topology QA topic treats as a data-quality problem when it is actually a clipping problem.
A second gotcha concerns re-cutting. Applying diffs to a clipped extract does not keep the clip honest: an object edited upstream so that it now falls inside your boundary will not appear in your extract, because the diff stream contains the edit but your file never had the object. Over months, a diff-updated regional extract drifts away from what a fresh cut of the same boundary would contain. The fix is to re-cut periodically from a current parent rather than to trust the diff stream to maintain the boundary.
Third, boundaries that cross the antimeridian are handled by neither .poly nor GeoJSON in a way tools agree on. Split such a region into two polygons either side of 180 degrees and merge the outputs.
Integration points Jump to heading
The output of a clip is the input to everything else on this site. Feed it to the parsers surveyed in Choosing an OSM Parser; anchor it to a replication stream using the header fields covered in Replication Sequence Numbers & State Tracking; index it using one of the schemes compared in Spatial Index Selection.
The wiring that matters is a verification step between the cut and everything after it:
import logging
import subprocess
import json
logger = logging.getLogger(__name__)
def verify_extract(path: str, min_nodes: int = 1) -> dict[str, int]:
"""Assert a freshly cut extract is non-empty and carries a replication anchor."""
raw = subprocess.run(
["osmium", "fileinfo", "--extended", "--json", path],
capture_output=True, text=True, check=True,
).stdout
info = json.loads(raw)
counts = info["data"]["count"]
if counts["nodes"] < min_nodes:
raise ValueError(f"{path}: {counts['nodes']} nodes — boundary is probably inverted")
header = info["header"]["options"]
if "osmosis_replication_base_url" not in header:
logger.warning("%s: no replication anchor — this extract cannot be caught up", path)
logger.info("%s: %d nodes, %d ways, %d relations",
path, counts["nodes"], counts["ways"], counts["relations"])
return counts
In this section Jump to heading
- Clipping an OSM Extract with a .poly Boundary — the end-to-end procedure for a single region, including writing the boundary file.
- Choosing complete_ways vs smart in osmium extract — how to decide between the two strategies that both produce valid output.
- Splitting a Planet File into Regional Extracts — one pass, many outputs, and how to keep the run within a disk budget.
Frequently Asked Questions Jump to heading
Which strategy should I use if I am not sure?
Use smart. It is the only strategy that guarantees no dangling references of any kind, including relation members, and on a country-sized cut it costs under twenty percent more output size than the cheapest option. The cases where a smaller strategy is genuinely better are narrow — a point-of-interest count that ignores geometry, or a disk budget so tight that a few percent matters — and in those cases you will know.
Why does my extract contain objects outside the boundary polygon?
Because complete_ways and smart deliberately keep them. A way with one node inside the polygon is kept entire, which means every node of that way comes too, including the ones several kilometres outside. This is what makes the feature renderable and routable. If you need output strictly bounded, clip the resulting geometries in your own processing rather than asking the extractor for it.
Can I clip a file that has already been clipped?
Yes, and it is often the fastest route — cutting a city out of its country extract reads far less data than cutting it out of a continent. The caveat is referential: if the parent was cut with simple, it already has dangling references, and no strategy applied to it can restore the nodes that are not there. Cut from parents produced with complete_ways or smart.
Does clipping preserve the replication sequence number?
Only if you ask for it. osmium extract copies the parent header, so the sequence survives when the parent had one, but many published extracts do not carry one. Set --output-header explicitly with the base URL and, where known, the sequence number and timestamp, so the extract can be caught up later rather than only re-cut.
How do I clip to an administrative boundary from OSM itself?
Extract the boundary relation, assemble it into a polygon, simplify it, and write it out as GeoJSON. The assembly is the multipolygon problem described in Understanding OSM Multipolygon Relations for GIS — administrative boundaries are frequently open or mis-roled and need the same containment-based ring classification as any other multipolygon.
Related Jump to heading
- OSM Data Fundamentals & Architecture — the section this topic belongs to.
- Node, Way & Relation Data Model — the reference graph that clipping has to cut without breaking.
- PBF File Structure Deep Dive — the block ordering that makes a two-pass clip possible.
- Choosing an OSM Parser — where osmium-tool sits among the alternatives.
- Replication Sequence Numbers & State Tracking — the header fields a cut must carry forward.
- Routing-Graph Topology QA — where a bad clip shows up as a phantom connectivity defect.
Up one level: OSM Data Fundamentals & Architecture.