Rasterize OpenStreetMap vector features into GeoTIFF rasters
Project description
osm-rasterizer
Convert OpenStreetMap vector features into GeoTIFF rasters. Define feature classes using OSM tags, specify a bounding box and resolution, and get a multi-band or single-layer categorical raster as output. Also supports OpenHistoricalMap for rasterizing places as they existed at any point in history.
Installation
pip install osm-rasterizer
Requires Python 3.12+.
CLI Usage
osm-rasterizer \
--bbox "minx,miny,maxx,maxy" \
--feature 'name:{"osm_key": "value"}' \
--output output.tif \
--resolution 10
Options
| Option | Short | Default | Description |
|---|---|---|---|
--bbox |
-b |
required | Bounding box as minx,miny,maxx,maxy in WGS84 (EPSG:4326) |
--feature |
-f |
required | OSM feature spec (repeatable, see below) |
--output |
-o |
required | Output GeoTIFF path |
--resolution |
-r |
10.0 |
Pixel size in metres |
--single-layer |
False |
Merge all features into one categorical band | |
--fill-nodata |
False |
Fill empty pixels from nearest labelled neighbour | |
--fill-nodata-distance |
unlimited | Max fill distance in pixels (prevents border flooding) | |
--crs |
auto | Output CRS, e.g. EPSG:32630. Auto-detected as best-fit UTM if omitted |
|
--date |
current | Point-in-time date in ISO 8601 format (e.g. 2020-01-01). With osm, queries the OSM database as it existed at that date; with ohm, selects features that existed in the real world at that date |
|
--provider |
-p |
osm |
Data provider: osm (OpenStreetMap) or ohm (OpenHistoricalMap) |
Feature spec format
Each --feature argument is either a bare JSON tag dict or a named spec:
'{"key": value}' # unnamed — name inferred from tags
'name:{"key": value}' # named band/category
Tag values follow the osmnx convention:
'{"building": true}' # any feature with a "building" tag
'{"highway": "residential"}' # exact value match
'{"highway": ["primary", "secondary"]}' # any of these values
A named spec may also be an envelope — a JSON object with a "tags" key plus per-feature options:
'road:{"tags": {"highway": true}, "line_width": 8, "width_from_tags": true}'
Line widths
Linestring features (roads, waterways, paths) have no area, so by default they burn as traces exactly one pixel wide — a motorway at 2 m resolution becomes a 2 m ribbon. Two per-feature options control this:
line_width(metres) — buffer each line to this real-world width (applied aswidth / 2on each side, in the projected CRS).width_from_tags(bool) — derive the width per geometry from its own OSM tags: thewidthtag (metres) if present and parseable, elselanes× 3.5 m, else theline_widthfallback (if given), else unbuffered.
Polygons and points are never buffered.
Output modes
Multi-band (default): one uint8 band per feature, values 0 (absent) or 1 (present).
Single-layer (--single-layer): one uint8 band with 1-based category indices (0 = no data). Features listed later take priority when areas overlap — order your features from least to most important.
Band names are stored in the GeoTIFF metadata under the BAND_NAMES tag. In single-layer mode, category names are stored under CATEGORIES.
Example: Cambridge land cover
osm-rasterizer \
--bbox "-0.24786388455006128, 52.242894345312415, 0.10397291341351336, 52.34506356709806" \
--feature 'bare_ground:{"natural": ["bare_rock", "sand", "scree"], "landuse": ["quarry", "brownfield"]}' \
--feature 'cropland:{"landuse": ["farmland", "orchard", "allotments", "greenhouse_horticulture"]}' \
--feature 'grassland:{"natural": "grassland", "landuse": ["grass", "meadow", "village_green"], "leisure": "park"}' \
--feature 'forest:{"landuse": "forest", "natural": "wood"}' \
--feature 'wetland:{"natural": "wetland"}' \
--feature 'infrastructure:{"building": true, "landuse": ["industrial", "commercial", "retail", "residential", "construction", "railway"]}' \
--feature 'road:{"tags": {"highway": ["motorway", "trunk", "primary", "secondary", "tertiary", "unclassified", "residential", "service", "track", "motorway_link", "trunk_link", "primary_link", "secondary_link", "tertiary_link"]}, "line_width": 8, "width_from_tags": true}' \
--feature 'water:{"natural": "water", "waterway": ["river", "canal", "stream", "drain", "ditch"]}' \
--output cambridge_landcover.tif \
--resolution 10 \
--single-layer \
--fill-nodata \
--fill-nodata-distance 50
This produces a 10 m resolution single-layer categorical raster with 8 land cover classes, with small gaps filled by propagating the nearest label up to 50 pixels away. Roads are burned at their real-world width where OSM width/lanes tags exist, falling back to 8 m otherwise.
Example: Historical data
Use --date to extract OSM data as it existed at a specific point in time:
osm-rasterizer \
--bbox "-0.13,51.49,-0.11,51.51" \
--feature 'building:{"building": true}' \
--output london_buildings_2015.tif \
--date "2015-01-01"
Example: OpenHistoricalMap
The --date option on the default osm provider is limited to the history of the OSM database (2004 onwards). To rasterize old places — cities as they were in 1900, ancient road networks, vanished buildings — use the ohm provider, which fetches from OpenHistoricalMap's Overpass API:
osm-rasterizer \
--bbox "-0.13,51.49,-0.11,51.51" \
--feature 'building:{"building": true}' \
--output london_buildings_1900.tif \
--provider ohm \
--date "1900-01-01"
Notes:
- OHM uses the same tag vocabulary as OSM, so feature specs work unchanged. Data coverage depends on what has been mapped in OHM for your area.
- OHM features carry
start_date/end_datetags describing when they existed in the real world;--datekeeps a feature whenstart_date <= date <= end_date. Features missing astart_date(orend_date) are treated as always existing (or still existing), and unparseable dates never exclude a feature. - Dates may be partial (
1900,1900-06) or BCE (-0500for 500 BCE). - Without
--date, all OHM features of all eras are rasterized together. - OHM data is CC0-licensed.
Python API
from osm_rasterizer import rasterize
result = rasterize(
bbox=(-0.15, 51.48, -0.08, 51.52), # central London
features=[
("building", {"building": True}),
("water", {"natural": "water"}),
("park", {"leisure": "park"}),
# linestrings: burn roads at their OSM-tagged width, else 8 m wide
("road", {"highway": True}, {"line_width": 8.0, "width_from_tags": True}),
],
resolution=10.0,
single_layer=True,
fill_nodata=True,
fill_nodata_distance=30,
)
# result.array — numpy array, shape (1, H, W) in single-layer mode
# result.crs — rasterio CRS
# result.transform — affine transform
# result.categories — ["building", "water", "park", "road"]
# Write directly to a file:
rasterize(
bbox=(-0.15, 51.48, -0.08, 51.52),
features=[("building", {"building": True})],
output_path="buildings.tif",
)
# Historical query:
rasterize(
bbox=(-0.15, 51.48, -0.08, 51.52),
features=[("building", {"building": True})],
output_path="buildings_2018.tif",
date="2018-06-01",
)
# OpenHistoricalMap query:
rasterize(
bbox=(-0.15, 51.48, -0.08, 51.52),
features=[("building", {"building": True})],
output_path="buildings_1900.tif",
provider="ohm",
date="1900-01-01",
)
How it works
- Fetch — Features are downloaded via the Overpass API (using osmnx) from OpenStreetMap or OpenHistoricalMap and clipped to the exact bounding box. An optional
dateparameter queries the historical state of the map (OSM: Overpass[date:]attic query; OHM: filtering bystart_date/end_datetags). - Project — The bbox and geometries are reprojected to the best-fit UTM CRS (or a user-specified CRS).
- Rasterize — Each feature class is burned into a
uint8grid using rasterio. Linestrings are optionally buffered to a real-world width (from theline_widthoption or the features' ownwidth/lanestags) before burning; otherwise they render one pixel wide. - Merge / fill — Bands are optionally merged into a single categorical layer, and empty pixels optionally filled using a Euclidean distance transform (scipy).
- Write — Output is a cloud-optimised, LZW-compressed, tiled GeoTIFF.
Development
git clone https://github.com/ancazugo/osm-rasterizer
cd osm-rasterizer
uv sync
# Run tests (unit tests only, no network)
uv run pytest
# Run including integration tests (requires Overpass network access)
uv run pytest -m integration
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file osm_rasterizer-0.2.0.tar.gz.
File metadata
- Download URL: osm_rasterizer-0.2.0.tar.gz
- Upload date:
- Size: 82.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
542d1e3d55f657d95351be513e8f4008086f0bc1ae644ac1fc4555b18ffe7cc8
|
|
| MD5 |
64d5391000a67ef05f51c28107635032
|
|
| BLAKE2b-256 |
1ed48b0bffcdca1cd368fa221fb996247ff47470c3fc3f36d9ddc4354cb1e173
|
Provenance
The following attestation bundles were made for osm_rasterizer-0.2.0.tar.gz:
Publisher:
publish.yml on ancazugo/osm-rasterizer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
osm_rasterizer-0.2.0.tar.gz -
Subject digest:
542d1e3d55f657d95351be513e8f4008086f0bc1ae644ac1fc4555b18ffe7cc8 - Sigstore transparency entry: 2128091588
- Sigstore integration time:
-
Permalink:
ancazugo/osm-rasterizer@ad74c720b3edd41afb18c5784eec1fcc59b6aa07 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/ancazugo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ad74c720b3edd41afb18c5784eec1fcc59b6aa07 -
Trigger Event:
push
-
Statement type:
File details
Details for the file osm_rasterizer-0.2.0-py3-none-any.whl.
File metadata
- Download URL: osm_rasterizer-0.2.0-py3-none-any.whl
- Upload date:
- Size: 18.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c7ef02ee998faf3dc977a836f3e6afd6424bcf71f5b43f342405610a8c02b135
|
|
| MD5 |
75695f28f9bfde60b08be247411cf185
|
|
| BLAKE2b-256 |
06aa05da9b3ada54816bb75796c7e9515642aa95ac541bbaf541a46a90277cf6
|
Provenance
The following attestation bundles were made for osm_rasterizer-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on ancazugo/osm-rasterizer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
osm_rasterizer-0.2.0-py3-none-any.whl -
Subject digest:
c7ef02ee998faf3dc977a836f3e6afd6424bcf71f5b43f342405610a8c02b135 - Sigstore transparency entry: 2128091701
- Sigstore integration time:
-
Permalink:
ancazugo/osm-rasterizer@ad74c720b3edd41afb18c5784eec1fcc59b6aa07 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/ancazugo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ad74c720b3edd41afb18c5784eec1fcc59b6aa07 -
Trigger Event:
push
-
Statement type: