Skip to main content

An open-source tool for reading OpenStreetMap PBF files using DuckDB

Project description


Generated using DALLยทE 3 model with this prompt: A logo for a python library with White background, high quality, 8k. Cute duck and globe with cartography elements. Library for reading OpenStreetMap data using DuckDB.

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 its Spatial[^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 ogr2ogr clipping before operation.
  • Can filter data based on OSM tags.
  • 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

With beautiful CLI

pip install quackosm[cli]

Required Python version?

QuackOSM supports Python >= 3.9

Dependencies

Required:

  • duckdb (>=0.9.2)
  • pyarrow (>=13.0.0)
  • geoarrow-pyarrow (>=0.1.1)
  • geopandas
  • shapely (>=2.0)
  • typeguard

Optional:

  • typer[all] (click, colorama, rich, shellingham)

Usage

Load data as a GeoDataFrame

>>> import quackosm as qosm
>>> qosm.get_features_gdf(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_gpq(monaco_pbf_path)
>>> gpq_path.as_posix()
'files/monaco_nofilter_noclip_compact.geoparquet'

Inspect the file with duckdb

>>> import duckdb
>>> duckdb.load_extension('spatial')
>>> duckdb.read_parquet(str(gpq_path)).project(
...     "* REPLACE (ST_GeomFromWKB(geometry) AS geometry)"
... ).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/33] Reading nodes โ€ข 0:00:00
โ ‹ [   2/33] Filtering nodes - intersection โ€ข 0:00:00
โ ‹ [   3/33] Filtering nodes - tags โ€ข 0:00:00
โ ‹ [   4/33] Calculating distinct filtered nodes ids โ€ข 0:00:00
โ ธ [   5/33] Reading ways โ€ข 0:00:00
โ ™ [   6/33] Unnesting ways โ€ข 0:00:00
โ ‹ [   7/33] Filtering ways - valid refs โ€ข 0:00:00
โ ‹ [   8/33] Filtering ways - intersection โ€ข 0:00:00
โ ‹ [   9/33] Filtering ways - tags โ€ข 0:00:00
โ ‹ [  10/33] Calculating distinct filtered ways ids โ€ข 0:00:00
โ ‹ [  11/33] Reading relations โ€ข 0:00:00
โ ‹ [  12/33] Unnesting relations โ€ข 0:00:00
โ ‹ [  13/33] Filtering relations - valid refs โ€ข 0:00:00
โ ‹ [  14/33] Filtering relations - intersection โ€ข 0:00:00
โ ‹ [  15/33] Filtering relations - tags โ€ข 0:00:00
โ ‹ [  16/33] Calculating distinct filtered relations ids โ€ข 0:00:00
โ ‹ [  17/33] Loading required ways - by relations โ€ข 0:00:00
โ ‹ [  18/33] Calculating distinct required ways ids โ€ข 0:00:00
โ ™ [  19/33] Saving filtered nodes with geometries โ€ข 0:00:00
โ ธ [  20/33] Saving required nodes with structs โ€ข 0:00:00
โ ผ [  21/33] Grouping filtered ways โ€ข 0:00:00
  [  22/33] Saving filtered ways with linestrings 100% โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” 1/1 โ€ข 0:00:00 < 0:00:00 โ€ข
โ ™ [  23/33] Grouping required ways โ€ข 0:00:00
  [  24/33] Saving required ways with linestrings 100% โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” 1/1 โ€ข 0:00:00 < 0:00:00 โ€ข
โ ด [  25/33] Saving filtered ways with geometries โ€ข 0:00:00
โ น [  26/33] Saving valid relations parts โ€ข 0:00:00
โ ‹ [27.1/33] Saving relations inner parts - valid geometries โ€ข 0:00:00
โ ‹ [27.2/33] Saving relations inner parts - invalid geometries โ€ข 0:00:00
โ ‹ [28.1/33] Saving relations outer parts - valid geometries โ€ข 0:00:00
โ ‹ [28.2/33] Saving relations outer parts - invalid geometries โ€ข 0:00:00
โ ‹ [  29/33] Saving relations outer parts with holes โ€ข 0:00:00
โ ‹ [  30/33] Saving relations outer parts without holes โ€ข 0:00:00
โ ‹ [  31/33] Saving filtered relations with geometries โ€ข 0:00:00
โ ธ [32.1/33] Saving valid features โ€ข 0:00:00
โ ™ [  33/33] Saving final geoparquet file โ€ข 0:00:00
files/monaco_nofilter_noclip_compact.geoparquet

CLI Help output: CLI Help output

You can find full API + more examples in the docs.

How does it work?

Basic logic

QuackOSM utilizes ST_ReadOSM function from DuckDB's Spatial extension to read raw data from the PBF file:

  • Nodes with coordinates and tags;
  • Ways with nodes refs and tags;
  • Relations with nodes and ways refs, ref roles and tags.

Library contains a logic to construct geometries (points, linestrings, polygons) from those raw features.

  1. Read nodes from the PBF file, save them to the parquet file.
    1. (Optional) Filter nodes based on geometry filter
    2. (Optional) Filter nodes based on tags filter
  2. Read ways from the PBF file, save them to the parquet file.
    1. Select all nodes refs and join them with previously read nodes.
    2. (Optional) Filter ways based on geometry filter - join intersecting nodes
    3. (Optional) Filter ways based on tags filter
  3. Read relations from the PBF file, save them to the parquet file.
    1. Select all ways refs and join them with previously read ways.
    2. (Optional) Filter relations based on geometry filter - join intersecting ways
    3. (Optional) Filter relations based on tags filter
  4. Select ways required by filtered relations
  5. Select nodes required by filtered and required ways
  6. Save filtered nodes with point geometries
  7. Group ways with nodes geometries and contruct linestrings
  8. Save filtered ways with linestrings and polygon geometries (depending on tags values)
  9. Divide relation parts into inner and outer polygons
  10. Group relation parts into full (multi)polygons and save them
  11. Fix invalid geometries
  12. Return final GeoParquet file

Memory usage

DuckDB queries requiring JOIN, GROUP and ORDER BY operations are very memory intensive. Because of that, some steps are divided into chunks (groups) with a set number of rows per chunk.

QuackOSM has been roughly tuned to different workloads. The rows_per_bucket variable is set based on an available memory in the system:

Memory Rows per group
< 8 GB 100 000
8 - 16 GB 500 000
16 - 24 GB 1 000 000
> 24 GB 5 000 000

WSL usage: sometimes code can break since DuckDB is trying to use all available memory, that can be occupied by Windows.

Disk usage

The algorithm depends on saving intermediate .parquet files between queries. As a rule of thumb, when parsing a full file without filtering, you should have at least 10x more free space on disk than the base file size (100MB pbf file -> 1GB free space to parse it).

Below you can see the chart of disk usage during operation. Generated on a machine with Intel i7-4790 CPU with 32 GB of RAM. Red dotted line represents the size of the base file.

Monaco

PBF file size: 525 KB

Geofabrik link

Monaco PBF file result

Estonia

PBF file size: 100 MB

Geofabrik link

Estonia PBF file result

Poland

PBF file size: 1.7 GB

Geofabrik link

Poland PBF file result

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

quackosm-0.3.3.tar.gz (676.3 kB view hashes)

Uploaded Source

Built Distribution

quackosm-0.3.3-py3-none-any.whl (41.4 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page