Skip to content

python-geospatial/spatial-join-lab

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

spatial-join-lab

Upload two spatial datasets and interactively run buffers, spatial joins and nearest-neighbor queries — with a live map and downloadable results.

CI Python License: MIT

spatial-join-lab is a small Streamlit app (plus a cleanly separated, testable core library) for hands-on spatial analysis. Load two vector datasets — or the built-in Oslo sample — pick an operation, and see the result on an interactive map you can download as GeoJSON or GeoParquet. It is a working demo of the analysis workflows taught on python-geospatial.com.

Maintained by python-geospatial.com, a knowledge base for the modern Python geospatial stack.

Why

Buffers, spatial joins and nearest-neighbor queries are the everyday verbs of GIS, but they are also where people quietly get the units wrong — buffering in degrees, or measuring distances in Web Mercator metres that are stretched by tens of percent. This lab does the metric work correctly by auto-selecting a UTM zone from your data and shows you which EPSG code it used, so the right way becomes the obvious way.

Install (from the repo)

Not published to PyPI — install straight from the repository:

pip install "git+https://github.com/python-geospatial/spatial-join-lab.git"

Or clone and install in editable mode:

git clone https://github.com/python-geospatial/spatial-join-lab.git
cd spatial-join-lab
pip install -e ".[dev]"

Run the app

spatial-join-lab                    # launches Streamlit
# or, equivalently
python -m spatial_join_lab
# or point Streamlit at the bundled app directly
streamlit run src/spatial_join_lab/app.py

Streamlit opens a browser tab. The sidebar walks you through it:

  1. Data — click Load sample data (Oslo sensors + parcels), or upload Dataset A and Dataset B. Supported formats: GeoJSON, GeoPackage (.gpkg), GeoParquet (.parquet) and zipped Shapefiles (.zip).
  2. Operation — pick Buffer, Spatial Join or Nearest Neighbor and set its parameters (buffer distance in metres; join predicate + how; k + optional max distance).
  3. Click Run. The map shows Dataset A (blue), Dataset B (green) and the result (red), a table previews the output, and download buttons export GeoJSON / GeoParquet.

A green banner reports the projected CRS used for metric work, e.g.:

Metric operations used EPSG:32632 (an auto-selected UTM zone from your data
centroid) — not Web Mercator, whose distances are distorted.

Use the core library directly

The analysis functions are import-safe (no Streamlit needed):

from spatial_join_lab import operations, sample_data

sensors, parcels = sample_data.load_sample()

# Which metric CRS fits this data?
_, epsg = operations.to_metric_crs(sensors)
print(epsg)                      # 32632  (UTM 32N, central Oslo)

# 100 m buffers around each sensor (computed in metres, returned in EPSG:4326)
zones = operations.buffer(sensors, distance_m=100)

# Which parcel does each sensor fall in?
joined = operations.spatial_join(sensors, parcels, predicate="intersects", how="inner")
print(joined[["sensor_id", "parcel_id", "land_use"]].to_string(index=False))
# sensor_id parcel_id    land_use
#        S1        P1 residential
#        S2        P2  commercial
#        S3        P3        park
#        S4        P4  industrial

# Nearest parcel to each sensor, with true metre distances
near = operations.nearest_neighbor(sensors, parcels, k=1)
print(near[["sensor_id", "parcel_id", "distance_m"]].round(1))

Features

  • Correct CRS handling. Metric operations run in an auto-selected UTM zone (northern 326xx / southern 327xx), never Web Mercator. The chosen EPSG is surfaced in the UI.
  • Three core operations. Buffer, spatial join (intersects / within / contains / overlaps; inner / left / right), and k-nearest-neighbor with optional max-distance.
  • Real distances. Nearest-neighbor output carries a distance_m column in metres.
  • Many formats in. GeoJSON, GeoPackage, GeoParquet and zipped Shapefiles, from an upload or a path.
  • Downloadable results. Export any result as GeoJSON or GeoParquet.
  • Testable core. All logic lives in operations.py with no Streamlit import, covered by a network-free pytest suite.

How it works

to_metric_crs reprojects the data to EPSG:4326, takes the centroid to get a longitude/latitude, derives the UTM zone (zone = int((lon + 180) / 6) + 1), and picks 32600 + zone (north) or 32700 + zone (south). Buffers and nearest-neighbor distances are computed in that projected CRS and the geometry is returned in EPSG:4326. Spatial joins align both layers' CRS first and delegate to geopandas.sjoin; nearest-neighbor uses geopandas.sjoin_nearest with distance_col="distance_m". Transformers follow always_xy=True discipline to avoid axis-order surprises.

Learn more

Deep dives on python-geospatial.com behind each operation in this lab:

Development

git clone https://github.com/python-geospatial/spatial-join-lab.git
cd spatial-join-lab
pip install -e ".[dev]"
ruff check .
pytest -q

The test suite runs without network access and without launching Streamlit.

License

MIT © 2026 python-geospatial.com

About

Interactive Streamlit lab for spatial joins, buffers and nearest-neighbor queries — upload two datasets, run the analysis on a map, download the result. Maintained by python-geospatial.com.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages