OpenStreetMap history — building footprints over time (ohsome)¶
This notebook teaches the ohsome half of the earthlens.osm backend: OSM is
history-aware, so you can ask what the map looked like at a point in time. We pull building
footprints for two yearly snapshots of the same area and compare them.
Like the Overpass path, ohsome returns a vector FeatureCollection (EPSG:4326) and emits the
ODbL LicenseWarning. Unlike Overpass, an ohsome query needs a time (start= / end=).
Setup¶
Imports and a small output directory. The same tiny Heidelberg bbox as the quickstart.
from pathlib import Path
import matplotlib.pyplot as plt
from earthlens import EarthLens
OUT_DIR = Path('osm_output')
OUT_DIR.mkdir(exist_ok=True)
LAT_LIM = [49.40, 49.42]
LON_LIM = [8.67, 8.71]
A single historical snapshot¶
ohsome:buildings returns building footprints as they existed at the start date. ohsome's
own columns ride along: @osmId (the element id) and @snapshotTimestamp (the snapshot
instant). Here we ask for the state on 1 January 2018.
buildings_2018 = EarthLens(
data_source='osm',
variables=['ohsome:buildings'],
lat_lim=LAT_LIM,
lon_lim=LON_LIM,
start='2018-01-01',
path=str(OUT_DIR),
).download()
len(buildings_2018), buildings_2018['@snapshotTimestamp'].iloc[0]
2026-06-28 18:17:39 | INFO | pyramids.base.config | Logging is configured.
2026-06-28 18:17:40.873 | INFO | earthlens.osm.backend:_fetch_ohsome:470 - Querying ohsome for 'ohsome:buildings' over bbox (8.67,49.4,8.71,49.42) at time '2018-01-01'
2026-06-28 18:18:58.173 | INFO | earthlens.osm.backend:_fetch:381 - ohsome:buildings: fetched 5235 feature(s)
C:\gdrive\algorithms\remote-sensing\earthlens\.claude\worktrees\osm\src\earthlens\earthlens.py:1693: LicenseWarning: OpenStreetMap data is licensed under the Open Database License (ODbL 1.0), which carries attribution and share-alike obligations: credit '(c) OpenStreetMap contributors' and license any derived database under ODbL when redistributing. return self.datasource.download(*args, progress_bar=progress_bar, **kwargs)
2026-06-28 18:18:58 | INFO | pyogrio._io | Created 5,235 records
2026-06-28 18:18:58.552 | INFO | earthlens.osm.backend:download:552 - OSM download summary: 5235 feature(s) written to C:\gdrive\algorithms\remote-sensing\earthlens\.claude\worktrees\osm\docs\examples\osm\osm_output\osm_ohsome-buildings.geojson
(5235, Timestamp('2018-01-01 00:00:00'))
Map the 2018 footprints¶
The footprints are polygons; plot them filled.
ax = buildings_2018.plot(facecolor='#9ecae1', edgecolor='#3182bd', linewidth=0.3, figsize=(7, 6))
ax.set_title(f'{len(buildings_2018)} building footprints — 2018-01-01 (ohsome)')
ax.set_xlabel('longitude'); ax.set_ylabel('latitude')
plt.show()
Compare two snapshots — how the map grew¶
Because ohsome is history-aware, the same query at a later date shows how mapping progressed. We pull the 2023 snapshot and compare the building counts — OSM coverage grows over time as contributors add detail.
buildings_2023 = EarthLens(
data_source='osm',
variables=['ohsome:buildings'],
lat_lim=LAT_LIM,
lon_lim=LON_LIM,
start='2023-01-01',
path=str(OUT_DIR),
).download()
counts = {'2018': len(buildings_2018), '2023': len(buildings_2023)}
counts
2026-06-28 18:18:59.538 | INFO | earthlens.osm.backend:_fetch_ohsome:470 - Querying ohsome for 'ohsome:buildings' over bbox (8.67,49.4,8.71,49.42) at time '2023-01-01'
2026-06-28 18:19:15.333 | INFO | earthlens.osm.backend:_fetch:381 - ohsome:buildings: fetched 5513 feature(s)
C:\gdrive\algorithms\remote-sensing\earthlens\.claude\worktrees\osm\src\earthlens\earthlens.py:1693: LicenseWarning: OpenStreetMap data is licensed under the Open Database License (ODbL 1.0), which carries attribution and share-alike obligations: credit '(c) OpenStreetMap contributors' and license any derived database under ODbL when redistributing. return self.datasource.download(*args, progress_bar=progress_bar, **kwargs)
2026-06-28 18:19:15 | INFO | pyogrio._io | Created 5,513 records
2026-06-28 18:19:15.741 | INFO | earthlens.osm.backend:download:552 - OSM download summary: 5513 feature(s) written to C:\gdrive\algorithms\remote-sensing\earthlens\.claude\worktrees\osm\docs\examples\osm\osm_output\osm_ohsome-buildings.geojson
{'2018': 5235, '2023': 5513}
A quick bar chart makes the growth between the two snapshots legible.
fig, ax = plt.subplots(figsize=(4, 4))
ax.bar(list(counts), list(counts.values()), color=['#9ecae1', '#3182bd'])
ax.set_title('Mapped building footprints, same bbox')
ax.set_ylabel('feature count')
plt.show()
Raw ohsome filter (power users)¶
Pass your own ohsome filter via
filter= to query any tag combination — here, parks (leisure=park) as polygons at the
2023 snapshot.
parks = EarthLens(
data_source='osm',
variables=['ohsome:buildings'], # still required to route to ohsome
lat_lim=LAT_LIM,
lon_lim=LON_LIM,
start='2023-01-01',
filter='leisure=park and geometry:polygon',
path=str(OUT_DIR),
).download()
len(parks)
2026-06-28 18:19:15.845 | INFO | earthlens.osm.backend:_fetch_ohsome:470 - Querying ohsome for 'ohsome:buildings' over bbox (8.67,49.4,8.71,49.42) at time '2023-01-01'
2026-06-28 18:19:30.061 | INFO | earthlens.osm.backend:_fetch:381 - ohsome:buildings: fetched 23 feature(s)
C:\gdrive\algorithms\remote-sensing\earthlens\.claude\worktrees\osm\src\earthlens\earthlens.py:1693: LicenseWarning: OpenStreetMap data is licensed under the Open Database License (ODbL 1.0), which carries attribution and share-alike obligations: credit '(c) OpenStreetMap contributors' and license any derived database under ODbL when redistributing.
return self.datasource.download(*args, progress_bar=progress_bar, **kwargs)
2026-06-28 18:19:30 | INFO | pyogrio._io | Created 23 records
2026-06-28 18:19:30.069 | INFO | earthlens.osm.backend:download:552 - OSM download summary: 23 feature(s) written to C:\gdrive\algorithms\remote-sensing\earthlens\.claude\worktrees\osm\docs\examples\osm\osm_output\osm_ohsome-buildings.geojson
23
Takeaway¶
- ohsome adds a time axis to OSM: pass
start=(and optionallyend=) to get the map as it was, or a range. - The result carries
@osmId/@snapshotTimestampso you can track features across time. - A raw
filter=queries any tag combination; the ODbL obligation applies to ohsome data too.
See quickstart.ipynb for the current-state Overpass protocol.