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.1.tar.gz (415.6 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.1-cp310-abi3-win_amd64.whl (886.5 kB view details)

Uploaded CPython 3.10+Windows x86-64

pycanopy-0.4.1-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.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (963.0 kB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ ARM64

pycanopy-0.4.1-cp310-abi3-macosx_11_0_arm64.whl (918.1 kB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

pycanopy-0.4.1-cp310-abi3-macosx_10_12_x86_64.whl (977.8 kB view details)

Uploaded CPython 3.10+macOS 10.12+ x86-64

File details

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

File metadata

  • Download URL: pycanopy-0.4.1.tar.gz
  • Upload date:
  • Size: 415.6 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.1.tar.gz
Algorithm Hash digest
SHA256 cb009771eb38050c371afaef33004ed673f197bd296ecdfdcf26b5af12144010
MD5 22aeda3be7dfa3956f08989408d11314
BLAKE2b-256 178cd1a4cc98867a31bf8ed114cfc5d9d2deb164943aaabe2818e8bbf1559a00

See more details on using hashes here.

Provenance

The following attestation bundles were made for pycanopy-0.4.1.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.1-cp310-abi3-win_amd64.whl.

File metadata

  • Download URL: pycanopy-0.4.1-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 886.5 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.1-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 ed320550240da9a86d744ab65f711918a53e6ef012f4490b228032f8208a6b59
MD5 24c3368668afcf011686961d35d80081
BLAKE2b-256 5f19c4dcd2d4500bcdbf7430bd3d90773a5d5e5375bc548564d6e45a55c5fb5e

See more details on using hashes here.

Provenance

The following attestation bundles were made for pycanopy-0.4.1-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.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for pycanopy-0.4.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 bb2fcd8cd8f0bd3a63c690ad8c028cf20bbe4f9b979923b4c5a443b59541288f
MD5 a1d548f0b228d41a997b7bd0d11d4b40
BLAKE2b-256 0195c47a46b0f6e8db6dc1ca27d95f97ecf73d231e9e77d01222b34fdcd2a845

See more details on using hashes here.

Provenance

The following attestation bundles were made for pycanopy-0.4.1-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.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for pycanopy-0.4.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 fbf1c0b09cbbeb700d0435ec6b88ac45d15c87a25f58ec2413e14483b245c235
MD5 30152095075a30983c34137bfb4cc751
BLAKE2b-256 d134b3b7c8ce3fb0b8d019f8be59a3e6f48d4a828240198486fbc1ee213b0a68

See more details on using hashes here.

Provenance

The following attestation bundles were made for pycanopy-0.4.1-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.1-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for pycanopy-0.4.1-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 e5c4870c825dd04725ec97f8e890548061b28e1686192e2204063cca8fa2b940
MD5 15627f8c2edf1f319126e517a7188f45
BLAKE2b-256 d0f817de183c7afde3057bccfe5f43bf161b4fc547648f1bf74a1f3e88ff080a

See more details on using hashes here.

Provenance

The following attestation bundles were made for pycanopy-0.4.1-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.1-cp310-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for pycanopy-0.4.1-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 213b63dd81c15ce09c470a2fa53c05ff4308e4b29624a034b38930ccb10a84bb
MD5 3effb4066eecde62926ae4c1c4ebcebd
BLAKE2b-256 f93614c826174fa688f05f2bf50d63e1f6750eebc4118d7613ea9d27cba3ed80

See more details on using hashes here.

Provenance

The following attestation bundles were made for pycanopy-0.4.1-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

This release

0.4.1 This release

6 files

0.4.0

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