Examples¶
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.
Splitting one tag class by attribute¶
osmnx ORs tag keys, so {"leisure": "pitch", "surface": "grass"} means pitch or anything
grass-surfaced — it cannot express "pitches whose surface is grass". Instead, fetch the broad tag
class and narrow the returned rows with a per-feature filter. A filter dict keeps a row only when
every column matches (AND); a cell matches when its value — split on ;, so OSM multi-values like
sport="soccer;basketball" work — is in the allowed list.
from osm_rasterizer import rasterize
# One categorical raster with football pitches split into three surface bands.
result = rasterize(
bbox=(-0.15, 51.48, -0.08, 51.52),
features=[
("pitch_hard", {"leisure": "pitch"}, {"filter": {"sport": ["soccer"], "surface": ["asphalt", "concrete", "paving_stones"]}}),
("pitch_artificial", {"leisure": "pitch"}, {"filter": {"sport": ["soccer"], "surface": ["artificial_turf", "artificial"]}}),
("pitch_grass", {"leisure": "pitch"}, {"filter": {"sport": ["soccer"], "surface": ["grass"]}}),
],
resolution=10.0,
single_layer=True, # later features win: grass beats artificial beats hard
)
# result.categories → ["pitch_hard", "pitch_artificial", "pitch_grass"]
To avoid re-fetching leisure=pitch once per band, fetch it once and pass pre-fetched
GeoDataFrames as features (Python API only) — any pandas filtering works, including things a filter
dict can't express:
from osm_rasterizer import rasterize, fetch_features
bbox = (-0.15, 51.48, -0.08, 51.52)
pitches = fetch_features(bbox, {"leisure": "pitch"})
soccer = pitches[pitches.get("sport", "").fillna("").str.contains("soccer")]
def by_surface(values):
return soccer[soccer.get("surface").isin(values)]
rasterize(
bbox,
features=[
("pitch_hard", by_surface(["asphalt", "concrete", "paving_stones"])),
("pitch_artificial", by_surface(["artificial_turf", "artificial"])),
("pitch_grass", by_surface(["grass"])),
],
resolution=10.0,
single_layer=True,
output_path="pitches_by_surface.tif",
)
For a fully worked, map-and-plot walkthrough of the filter option on live OSM data — an OR
example (restaurants by cuisine) and an AND example (footways by surface, then rasterized) — see the
Filtering by attribute notebook.
The same split from the CLI, using the filter dict in a feature envelope:
osm-rasterizer \
--bbox "-0.15,51.48,-0.08,51.52" \
--feature 'pitch_hard:{"tags": {"leisure": "pitch"}, "filter": {"sport": ["soccer"], "surface": ["asphalt", "concrete", "paving_stones"]}}' \
--feature 'pitch_artificial:{"tags": {"leisure": "pitch"}, "filter": {"sport": ["soccer"], "surface": ["artificial_turf", "artificial"]}}' \
--feature 'pitch_grass:{"tags": {"leisure": "pitch"}, "filter": {"sport": ["soccer"], "surface": ["grass"]}}' \
--output pitches_by_surface.tif \
--resolution 10 \
--single-layer
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"
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"
Note
- 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",
)
See the Python API reference for full signatures.