Skip to main content

PyCanopy

PyPI version Total downloads Python versions CI License: MIT Docs Open In Colab

A spatial query layer for Polars. Rust core, Python API.


[!NOTE] Highly competitive on Apache SpatialBench (single-node spatial query benchmark): fastest on 11/24 testcases, within 5% of the fastest time on 14/24 testcases

PyCanopy vs SedonaDB, DuckDB, and GeoPandas on Apache SpatialBench SF1

Apache SpatialBench SF1 · lower is better · bars past the cap truncated with their value · TIMEOUT / ERROR annotated


Installation

pip install pycanopy

Pre-built wheels for Linux, macOS, and Windows. No Rust toolchain required.

import polars as pl
from pycanopy import SpatialFrame

sf = SpatialFrame(pl.read_parquet("cities.parquet"), x_col="lon", y_col="lat")
result = sf.lazy().filter(pl.col("population") > 100_000).range_query(-10.0, 35.0, 40.0, 70.0).collect()

Why PyCanopy

The driving motivator behind creating this library was to provide the optimizations of relational DBs (query planning, indexing, etc) in a fast, Polars-like interface meant for in-memory spatial work.

Capability PyCanopy GeoPandas DuckDB SedonaDB Spatial Polars
Uses Polars DataFrames directly
Spatial-aware query planning
Automatically accelerates spatial joins with an index
Explicit cost-based choice between scanning and building an index
Selects among multiple spatial index types by workload

Example Operations

Optimized range query

lf = (
    sf.lazy()
    .range_query(min_x=-10.0, min_y=35.0, max_x=40.0, max_y=70.0)
    .filter(pl.col("population") > 100_000)
)
print(lf.explain())
# RANGE_QUERY [(-10, 35) → (40, 70)]
# FROM
#   FILTER [(col("population")) > (dyn int: 100000)]
#   FROM
#     DF [N=100,000; path: EXPR]

The optimizer runs the scalar filter first. On the EXPR path, the surviving original row indices are passed to Rust, which returns a spatial Boolean mask over those candidates.

kNN join

query_df = pl.DataFrame({"qx": [2.35, 13.4], "qy": [48.85, 52.5]})

result = sf.lazy().knn_join(query_df, x_col="qx", y_col="qy", k=3).collect()

For each row in query_df, returns the 3 nearest rows in the SpatialFrame. Large probes are streamed in morsels automatically.

Point-in-polygon join with aggregation

import pycanopy as pc

zones = SpatialFrame.from_wkb_polygons(
    pl.read_parquet("zones.parquet"),
    geometry_col="geometry",
)
trips = pl.read_parquet("trips.parquet")

stats = (
    zones.lazy()
    .within_join(trips, x_col="lon", y_col="lat")
    .group_by(["zone_id"])
    .agg(trip_count=pc.agg.count(), avg_fare=pc.agg.mean("fare"))
)

Each query-side batch is joined and aggregated before the next begins, so the complete pair frame is never materialized.

[!NOTE] For the full operation catalog, index modes, streaming joins, and API reference see the docs site.


Benchmarks

Apache SpatialBench

Run on a single m7i.2xlarge (8 vCPU, 32 GB), the same hardware used by Apache SpatialBench. PyCanopy is measured live with index_mode="auto". Results were produced using the benchmark harness in bench/spatial_bench.

PyCanopy is fastest on 11/24 testcases and lands within 5% of the fastest time on 14/24 testcases (there is some variance among benchmark runs).

SF1 (~6M trips)

PyCanopy vs SedonaDB, DuckDB, and GeoPandas on Apache SpatialBench SF1

Apache SpatialBench SF1 · lower is better · linear axis, bars past the cap truncated with their value · TIMEOUT / ERROR annotated

SF10 (~60M trips)

PyCanopy vs SedonaDB, DuckDB, and GeoPandas on Apache SpatialBench SF10

Apache SpatialBench SF10 · lower is better · linear axis, bars past the cap truncated with their value · TIMEOUT / ERROR annotated

SedonaDB, DuckDB, and GeoPandas baselines come from published SpatialBench results. See the full per-query results and methodology.


How It Works

Spatial-aware Logical Planning

  • Predicate pushdown: moves compatible scalar filters ahead of spatial predicates
  • Filter fusion: combines eligible range and contains predicates in a single Rust call
  • Projection pushdown: narrows join inputs before gathering rows
  • Join orientation: flips supported joins based on relative input sizes

Physical Planning

  • IO path: directly queries the spatial index and slices source rows for selective predicates
  • EXPR path: applies scalar filters in Polars before evaluating broader spatial predicates in Rust
  • Morsel streaming: processes large query-side joins in batches for incremental collection, lazy pipelines, or direct Parquet sinks
  • Streaming aggregation: aggregates each join batch as it is produced, avoiding materialization of the complete join result

Automatic Indexing

  • Tracks dataset extent, spatial distribution, and density to estimate query selectivity
  • Uses a cost model to compare brute-force scanning, reusing an existing index, and building and probing a new index
  • Selects among grid, KD-tree, and R-tree indexes based on the query and workload

Why Rust

The hot paths benefit from packed immutable index structures, parallel loops, and efficient access to contiguous NumPy buffers. PyO3 and Maturin provide direct Python bindings and cross-platform extension packaging.

For a detailed overview of PyCanopy's design, see the How It Works documentation.


Acknowledgements

Some works that inspired this project:

  • Polars: a columnar DataFrame engine that PyCanopy builds on
  • geo-index: provides packed, immutable, zero-copy KD-tree and R-tree structures used
  • Spatial Polars: an earlier effort to bring spatial functionality to Polars
  • Apache Sedona: state-of-the-art spatial SQL engine + benchmark for evals

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pycanopy-0.4.0.tar.gz (409.4 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

pycanopy-0.4.0-cp310-abi3-win_amd64.whl (868.1 kB view details)

Uploaded CPython 3.10+Windows x86-64

pycanopy-0.4.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.0 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ x86-64

pycanopy-0.4.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (944.8 kB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ ARM64

pycanopy-0.4.0-cp310-abi3-macosx_11_0_arm64.whl (901.7 kB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

pycanopy-0.4.0-cp310-abi3-macosx_10_12_x86_64.whl (959.5 kB view details)

Uploaded CPython 3.10+macOS 10.12+ x86-64

File details

Details for the file pycanopy-0.4.0.tar.gz.

File metadata

  • Download URL: pycanopy-0.4.0.tar.gz
  • Upload date:
  • Size: 409.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for pycanopy-0.4.0.tar.gz
Algorithm Hash digest
SHA256 bc83fcd3448d1abcc79d1dca0b963bb913f509ac868de20b6396e9b087e94ba0
MD5 599c29527492f0f53e9feca2a6f7e652
BLAKE2b-256 c1543967e6117ced7df87c1b51f5d3b29d93e7940de106dc773a3ed344d770af

See more details on using hashes here.

Provenance

The following attestation bundles were made for pycanopy-0.4.0.tar.gz:

Publisher: release.yml on pranav-walimbe/PyCanopy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pycanopy-0.4.0-cp310-abi3-win_amd64.whl.

File metadata

  • Download URL: pycanopy-0.4.0-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 868.1 kB
  • Tags: CPython 3.10+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for pycanopy-0.4.0-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 fac4ee80070dfec327b1332e813eb5410c4cc5183fa7ad8776ca7875964343ea
MD5 942247e46c3c6108b6c4d060aeacbfe2
BLAKE2b-256 79a7d9c76a6699b111d8d6593f4902d357f08f6b87bab85b82126006724a1383

See more details on using hashes here.

Provenance

The following attestation bundles were made for pycanopy-0.4.0-cp310-abi3-win_amd64.whl:

Publisher: release.yml on pranav-walimbe/PyCanopy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pycanopy-0.4.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for pycanopy-0.4.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 cc10c83de81a2374390f8ded47198113be041879a52bb3a2e77c2abf41f1c271
MD5 751bc20fc9e2b7246dd46de0fca9c1e5
BLAKE2b-256 c0d57df727f4679ab7cfb615e93cd9737f3817cf268cd8701dfa1995bd36ef62

See more details on using hashes here.

Provenance

The following attestation bundles were made for pycanopy-0.4.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on pranav-walimbe/PyCanopy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pycanopy-0.4.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for pycanopy-0.4.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 eed91dfb233aec4641d203ba412fa07c763ebae7f392e80d9bcdc9829360ca1c
MD5 29fe26dc58ea90e4e6d009be7030ad15
BLAKE2b-256 0469cfbacb55610c2e1869ca655a166a0285aa6b6d5303eed1a314641df7d104

See more details on using hashes here.

Provenance

The following attestation bundles were made for pycanopy-0.4.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: release.yml on pranav-walimbe/PyCanopy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pycanopy-0.4.0-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for pycanopy-0.4.0-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 71fccd663f4ae3e301e52951fb09fb9b2c5ead50c050bb9c1178291b18b42ab8
MD5 e865f3512fa46c06dad5786e97aa0555
BLAKE2b-256 17e3b5812291993fad9ae1197eedcaa951191cbf11c1fdd1b88d9cc7f835733b

See more details on using hashes here.

Provenance

The following attestation bundles were made for pycanopy-0.4.0-cp310-abi3-macosx_11_0_arm64.whl:

Publisher: release.yml on pranav-walimbe/PyCanopy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pycanopy-0.4.0-cp310-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for pycanopy-0.4.0-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 77bd96509379feb440ca255b0928270cfe22dc8d0450daf2b5affd702782afbe
MD5 19068dfe12ed6ad8535baf6f867df25f
BLAKE2b-256 bd954430d0b6aa258c3f3211c30e5c8ebb16bbed34d374eca293049dbc479fc3

See more details on using hashes here.

Provenance

The following attestation bundles were made for pycanopy-0.4.0-cp310-abi3-macosx_10_12_x86_64.whl:

Publisher: release.yml on pranav-walimbe/PyCanopy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.4.1

6 files

This release

0.4.0 This release

6 files

0.3.4

6 files

0.3.3

6 files

0.3.2

6 files

0.3.1

6 files

0.3.0

6 files

0.2.2

6 files

0.2.1

6 files

0.2.0

6 files

0.1.2

6 files

0.1.0

6 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page