Quackosm
QuackOSM: an open-source Python and CLI tool for reading OpenStreetMap PBF files using DuckDB
Install / Use
/learn @kraina-ai/QuackosmREADME
QuackOSM
An open-source tool for reading OpenStreetMap PBF files using DuckDB.
What is QuackOSM 🦆?
- Scalable reader for OpenStreetMap ProtoBuffer (
pbf) files. - Is based on top of
DuckDB[^1] with itsSpatial[^2] extension. - Saves files in the
GeoParquet[^3] file format for easier integration with modern cloud stacks. - Utilizes multithreading unlike GDAL that works in a single thread only.
- Can filter data based on geometry without the need for
ogr2ogrclipping before operation. - Can filter data based on OSM tags (with negations and wildcards).
- Can automatically download required PBF files for a given geometry.
- Utilizes caching to reduce repeatable computations.
- Can be used as Python module as well as a beautiful CLI based on
Typer[^4].
[^1]: DuckDB Website [^2]: DuckDB Spatial extension repository [^3]: GeoParquet data format [^4]: Typer docs
Installing
As pure Python module
pip install quackosm
# or
uv pip install quackosm
With beautiful CLI
pip install quackosm[cli]
# or
uv pip install quackosm[cli]
From conda-forge
# Automatically installs with CLI
conda install conda-forge::quackosm
Required Python version?
QuackOSM supports Python >= 3.10
Dependencies
Required:
-
duckdb (>=1.1.2): For all DuckDB operations on PBF files and sorting result file (withspatialextension) -
pyarrow (>=16.0.0): For parquet files wrangling -
geoarrow-pyarrow (>=0.1.2): For GeoParquet IO operations and transforming Arrow data to Shapely objects -
geopandas (>=0.6): For returning GeoDataFrames and reading Geo files -
shapely (>=2.0): For parsing WKT and GeoJSON strings and fixing geometries -
typeguard (>=3.0): For internal validation of types -
psutil (>=5.6.2): For automatic scaling of parameters based on available resources -
pooch (>=1.6.0): For downloading*.osm.pbffiles and precalculated OSM indexes -
rich (>=12.0.0)&tqdm (>=4.42.0): For showing progress bars -
requests: For iterating OSM PBF files services -
beautifulsoup4: For parsing HTML files and scraping required information -
geopy (>=2.0.0): For geocoding of strings
Optional:
-
typer[all] (>=0.9.0)(click, colorama, rich, shellingham): Required in CLI -
h3extension forduckdb: For transforming H3 indexes into geometries. Required in CLI -
s2sphere (>=0.2.5): For transforming S2 indexes into geometries. Required in CLI
Usage
If you already have downloaded the PBF file 📁🗺️
Load data as a GeoDataFrame
>>> import quackosm as qosm
>>> qosm.convert_pbf_to_geodataframe(monaco_pbf_path)
tags geometry
feature_id
node/10005045289 {'shop': 'bakery'} POINT (7.42245 43.73105)
node/10020887517 {'leisure': 'swimming_pool', ... POINT (7.41316 43.73384)
node/10021298117 {'leisure': 'swimming_pool', ... POINT (7.42777 43.74277)
node/10021298717 {'leisure': 'swimming_pool', ... POINT (7.42630 43.74097)
node/10025656383 {'ferry': 'yes', 'name': 'Qua... POINT (7.42550 43.73690)
... ... ...
way/990669427 {'amenity': 'shelter', 'shelt... POLYGON ((7.41461 43.7338...
way/990669428 {'highway': 'secondary', 'jun... LINESTRING (7.41366 43.73...
way/990669429 {'highway': 'secondary', 'jun... LINESTRING (7.41376 43.73...
way/990848785 {'addr:city': 'Monaco', 'addr... POLYGON ((7.41426 43.7339...
way/993121275 {'building': 'yes', 'name': ... POLYGON ((7.43214 43.7481...
[7906 rows x 2 columns]
Just convert PBF to GeoParquet
>>> import quackosm as qosm
>>> gpq_path = qosm.convert_pbf_to_parquet(monaco_pbf_path)
>>> gpq_path.as_posix()
'files/monaco_nofilter_noclip_compact.parquet'
Inspect the file with duckdb
>>> import duckdb
>>> duckdb.load_extension('spatial')
>>> duckdb.read_parquet(str(gpq_path)).order("feature_id")
┌──────────────────┬──────────────────────┬──────────────────────────────────────────────┐
│ feature_id │ tags │ geometry │
│ varchar │ map(varchar, varch… │ geometry │
├──────────────────┼──────────────────────┼──────────────────────────────────────────────┤
│ node/10005045289 │ {shop=bakery} │ POINT (7.4224498 43.7310532) │
│ node/10020887517 │ {leisure=swimming_… │ POINT (7.4131561 43.7338391) │
│ node/10021298117 │ {leisure=swimming_… │ POINT (7.4277743 43.7427669) │
│ node/10021298717 │ {leisure=swimming_… │ POINT (7.4263029 43.7409734) │
│ node/10025656383 │ {ferry=yes, name=Q… │ POINT (7.4254971 43.7369002) │
│ node/10025656390 │ {amenity=restauran… │ POINT (7.4269287 43.7368818) │
│ node/10025656391 │ {name=Capitainerie… │ POINT (7.4272127 43.7359593) │
│ node/10025656392 │ {name=Direction de… │ POINT (7.4270392 43.7365262) │
│ node/10025656393 │ {name=IQOS, openin… │ POINT (7.4275175 43.7373195) │
│ node/10025656394 │ {artist_name=Anna … │ POINT (7.4293446 43.737448) │
│ · │ · │ · │
│ · │ · │ · │
│ · │ · │ · │
│ way/986864693 │ {natural=bare_rock} │ POLYGON ((7.4340482 43.745598, 7.4340263 4… │
│ way/986864694 │ {barrier=wall} │ LINESTRING (7.4327547 43.7445382, 7.432808… │
│ way/986864695 │ {natural=bare_rock} │ POLYGON ((7.4332994 43.7449315, 7.4332912 … │
│ way/986864696 │ {barrier=wall} │ LINESTRING (7.4356006 43.7464325, 7.435574… │
│ way/986864697 │ {natural=bare_rock} │ POLYGON ((7.4362767 43.74697, 7.4362983 43… │
│ way/990669427 │ {amenity=shelter, … │ POLYGON ((7.4146087 43.733883, 7.4146192 4… │
│ way/990669428 │ {highway=secondary… │ LINESTRING (7.4136598 43.7334433, 7.413640… │
│ way/990669429 │ {highway=secondary… │ LINESTRING (7.4137621 43.7334251, 7.413746… │
│ way/990848785 │ {addr:city=Monaco,… │ POLYGON ((7.4142551 43.7339622, 7.4143113 … │
│ way/993121275 │ {building=yes, nam… │ POLYGON ((7.4321416 43.7481309, 7.4321638 … │
├──────────────────┴──────────────────────┴──────────────────────────────────────────────┤
│ 7906 rows (20 shown) 3 columns │
└────────────────────────────────────────────────────────────────────────────────────────┘
Use as CLI
$ quackosm monaco.osm.pbf
⠋ [ 1/32] Reading nodes • 0:00:00
⠋ [ 2/32] Filtering nodes - intersection • 0:00:00
⠋ [ 3/32] Filtering nodes - tags • 0:00:00
⠋ [ 4/32] Calculating distinct filtered nodes ids • 0:00:00
⠋ [ 5/32] Reading ways • 0:00:00
⠋ [ 6/32] Unnesting ways • 0:00:00
⠋ [ 7/32] Filtering ways - valid refs • 0:00:00
⠋ [ 8/32] Filtering ways - intersection • 0:00:00
⠋ [ 9/32] Filtering ways - tags • 0:00:00
⠋ [ 10/32] Calculating distinct filtered wa
Related Skills
node-connect
347.9kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
claude-opus-4-5-migration
108.7kMigrate prompts and code from Claude Sonnet 4.0, Sonnet 4.5, or Opus 4.1 to Opus 4.5
frontend-design
108.7kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
model-usage
347.9kUse CodexBar CLI local cost usage to summarize per-model usage for Codex or Claude, including the current (most recent) model or a full model breakdown. Trigger when asked for model-level usage/cost data from codexbar, or when you need a scriptable per-model summary from codexbar cost JSON.
