Skip to main content

Powerful, honest, open-source geospatial visualization with true 3D.

Project description

PyGeoSpace

Powerful, honest, open-source geospatial visualization for Python.

PyGeoSpace reads the common vector and raster formats, renders interactive 2D maps, true 3D scenes, and an interactive 3D globe, runs spatial + spectral analytics, and exports interactive HTML, static images, or real 3D model files (glTF / GLB / STL / PLY / OBJ). It pairs with the PyGeoFetch / PyGeoVision ecosystem through a dedicated integration layer.

This is 0.8.0 (Beta) — "leafmap-style Notebook API". It is deliberately honest about its boundaries: features listed under What works today are implemented, and anything not yet built either says so here or raises a clear, actionable error instead of silently stubbing. A full, per-component verification record ships in PACKAGE_STATUS.md.


Two engines, one API

Engine Import Renders Best for
deck.gl / PyVista / Cesium (default) import pygeospace as pgspgs.Map() 2D WebGL maps, true 3D (PyVista), 3D globe (CesiumJS) imagery, terrain, large data, 3D
folium (Leaflet) from pygeospace.folium import Map 2D Leaflet maps + plugins classic web maps, choropleths, clustering, draw/measure

The default engine is deck.gl-based. The folium engine is additive and selected explicitly. The integration adapters are engine-agnostic — they produce a Map on whichever engine you use.


Install

pip install pygeospace                 # core: vector IO, analytics, deck.gl, CLI
pip install "pygeospace[raster]"       # + GeoTIFF / COG / JPEG2000 (rasterio)
pip install "pygeospace[3d]"           # + true 3D (PyVista, trame, meshio)
pip install "pygeospace[folium]"       # + folium backend (folium, branca, jinja2)
pip install "pygeospace[classify]"     # + true Fisher-Jenks breaks (jenkspy)
pip install "pygeospace[osm]"          # + OpenStreetMap extraction (osmnx)
pip install "pygeospace[stac]"         # + Planetary Computer (pystac-client, planetary-computer)
pip install "pygeospace[overture]"     # + Overture Maps (overturemaps)
pip install "pygeospace[earthengine]"  # + Google Earth Engine (earthengine-api, geemap)
pip install "pygeospace[all]"          # everything pip-installable without accounts

Python 3.10+.


Quickstart (2D, under 5 minutes)

import pygeospace as pgs

m = pgs.Map(title="Cities")
m.add_basemap("carto-light")                      # registry name or raw XYZ URL
m.add_layer("cities.geojson").style(get_fill_color=[255, 90, 0, 200], get_radius=3000)

m.fit().save("map.html")                          # interactive deck.gl page
m.save("map.png", dpi=300)                        # static export

# A Map behaves like a container of its layers:
print(m)                                          # <Map mode=2d · 2 layers: [basemap, cities]>
print(m.layer_names, len(m), m["cities"])

Command line:

pygeospace visualize cities.geojson -o map.html --style choropleth --attribute pop --method jenks
pygeospace export cities.geojson -o map.png --dpi 300
pygeospace serve cities.geojson --port 8000       # live preview
pygeospace info data.gpkg

Satellite imagery — one call per scene

read_bands folds the whole discover / window / reproject / scale / stack pipeline into a single call, returning a stacked RasterLayer in [blue, green, red, nir, swir] order.

import pygeospace as pgs
from pygeospace.analytics.spectral import spectral_index

stack = pgs.read_bands("data/satellite/", bbox=(-74.05, 40.68, -73.90, 40.82))

m = pgs.Map().add_basemap()
m.add_composite(stack, "false_color")             # true_color / false_color / agriculture
m.save("scene.html")

ndvi = spectral_index(stack, "ndvi")              # band order already correct
# one-liner: m.add_bands("data/satellite/", "true_color", bbox=...)

True 3D

import pygeospace as pgs

m = pgs.Map(mode="3d")
m.add_terrain("srtm.tif", exaggeration=2.0, cmap="gist_earth")
m.export_3d("terrain.html")                       # interactive
m.render_3d("terrain.png")                        # static
m.export_3d("terrain.gltf")                       # model for Blender / Unity / printing

cam = pgs.Camera3D(position=(0, 0, 5000), focal_point=(0, 0, 0))
cam.fly_to(4000, 4000, 2000, duration=3.0).orbit(45).tilt(20)
m.render_3d("flyover.png", camera=cam)

3D globe (CesiumJS)

The same map on an interactive 3D globe, with a built-in 2D / 2.5D / 3D scene toggle. Token-free by default (open imagery); a Cesium Ion token is optional for world terrain.

import pygeospace as pgs

m = pgs.Map(title="NYC")
m.add_layer(gdf, name="Landmarks")
m.add_bands("data/satellite/", "true_color", bbox=(-74.05, 40.68, -73.90, 40.82))
m.save("globe.html", engine="cesium")             # no token required
# premium terrain (optional): m.to_cesium_html(ion_token="…", terrain=True)

folium backend (notebook-first)

Renders inline in Jupyter / Colab and shows both raster and vector on one map.

from pygeospace.folium import (Map, VectorLayer, RasterLayer, ChoroplethLayer,
                               HeatmapLayer, ClusterLayer, SplitMapLayer, TimeSeriesLayer)

m = Map(location=(5.6, -0.2), zoom_start=11, basemap="carto-dark", title="Accra")
m.add_layer(RasterLayer(ndvi_array, bounds=(-0.3, 5.5, -0.1, 5.7), cmap="RdYlGn"))  # raster overlay
m.add_layer(VectorLayer("cities.geojson", tooltip="name", popup=["name", "pop"], fill_color="#e53e3e"))
m.add_layer(ChoroplethLayer("districts.geojson", attribute="pop", classification="quantile"))
m.add_layer(HeatmapLayer("events.csv"))
m.add_layer(ClusterLayer("points.geojson"))

m.add_controls().fit()
m.show()                                            # inline in a notebook cell
# m.save("accra.html")  /  m.to_png("accra.png")    # to_png needs [export] + headless Chrome

Notebook layers: RasterLayer (numpy array / pygeospace raster / GeoTIFF / XYZ-COG overlay), SplitMapLayer (draggable before/after divider), TimeSeriesLayer (animated slider), CustomLayer (HTML overlay). Classification methods: quantile, equal_interval, natural_breaks (Fisher-Jenks via jenkspy if installed, else a dependency-free k-means fallback), std_mean.

Controls & panels

The folium Map carries a full set of custom controls — real Leaflet controls anchored in the map corners — chainable in one expression:

(m.add_title("Vegetation index", subtitle="NDVI · 2024")
  .add_legend({"Dense": "#1a9850", "Sparse": "#fee08b", "Built-up": "#d73027"})
  .add_colorbar(-0.4, 0.9, cmap="RdYlGn", caption="NDVI")   # continuous scale for rasters
  .add_info_panel("<p>How to read this map…</p>", title="About", collapsed=True)
  .add_search()             # Nominatim geocoder box
  .add_locate()             # geolocation button
  .add_scale()              # scale bar
  .add_basemap_switcher(["carto-light", "carto-dark", "esri-satellite"]))

m.add_full_ui()             # layer control + fullscreen + minimap + coords + search + locate + scale

Panels: add_title, add_legend, add_colorbar, add_info_panel (collapsible), add_panel (arbitrary HTML). Controls: add_search, add_locate, add_scale, add_basemap_switcher, plus add_layer_control/add_fullscreen/add_measure/ add_draw/add_mouse_position/add_minimap.

leafmap-style methods

If you know leafmap, the folium Map speaks the same dialect:

from pygeospace.folium import Map, colormaps as cm

m = Map(center=(40, -100), zoom=4)
m.add_geojson("cities.geojson", layer_name="Cities", style_callback=lambda f: {"color": f["properties"]["c"]})
m.add_gdf(gdf); m.add_shp("states.zip"); m.add_vector("regions.gpkg")
m.add_csv("places.csv", x="lon", y="lat", popup=["name"])
m.add_raster("dem.tif", cmap="terrain")               # single band
m.add_raster("landsat.tif", band=[4, 3, 2], vmin=1, vmax=100)
m.add_cog_layer(cog_url); m.add_stac_layer(stac_url, bands=["B3", "B2", "B1"])
m.add_marker_cluster(pts); m.add_heatmap(pts); m.add_choropleth(geo, "population")
m.add_labels("states.geojson", "name", font_color="blue")
m.add_legend(builtin_legend="NLCD")                   # or legend_dict={...}
m.add_colorbar(0, 4000, cmap="terrain", caption="Elevation")
m.split_map("carto-light", "esri-satellite", left_label="A", right_label="B")
m.add_vector_tile_layer(mvt_url, attribution="Microsoft")

cm.get_palette("terrain", n_class=8)                  # colormaps module

add_cog_layer / add_stac_layer use a titiler endpoint and add_vector_tile_layer uses Leaflet.VectorGrid, so those three need network at view time; everything else renders offline.

Basemap registry

46 attributed XYZ basemaps (27 key-free), shared by both engines.

import pygeospace as pgs
from pygeospace.basemaps import basemap_url

pgs.list_basemaps(category="satellite", free_only=True)   # ['esri-satellite', 'usgs-imagery', ...]
pgs.get_basemap("carto-dark").attribution

# folium engine resolves registry names directly:
from pygeospace.folium import Map
Map(basemap="esri-satellite")
# deck.gl engine: pass a resolved URL
pgs.Map().add_basemap(basemap_url("esri-satellite"))

Key-required providers (Mapbox, Stadia/Stamen, Thunderforest, OpenWeatherMap) are flagged and fail loudly without a key rather than serving dead tiles.

Integrations

Import-light adapters that turn other tools' outputs into a PyGeoSpace map. Heavy or account-bound dependencies load lazily with clear error messages.

from pygeospace.integrations import geofetch, geovision, osm, pc, overture
from pygeospace.integrations import ee as earth_engine
from pygeospace.integrations import available

available()   # which integrations are usable right now

geofetch.visualize_search(results).save("search.html")        # PyGeoFetch footprints by provider
geofetch.visualize_downloads(downloaded).save("status.html")  # coloured by ok / failed / corrupt
geovision.visualize_predictions(preds).save("preds.html")     # model predictions, per-class layers

osm.from_place("Accra, Ghana", tags={"amenity": "hospital"})  # needs [osm]
pc.search("sentinel-2-l2a", bbox=bbox, datetime="2024-06")    # needs [stac]
overture.buildings((-0.21, 5.55, -0.17, 5.58))                # needs [overture]
earth_engine.tile_url(ee_image, {"min": 0, "max": 3000})      # needs [earthengine] + auth

What works today (0.7.0)

Area Capability Status
IO Shapefile, GeoJSON, GeoPackage, KML, GPX, GML; CSV with coords; PostGIS; format autodetect
GeoTIFF / COG / JPEG2000 read [raster]
LAS/LAZ point cloud read [pointcloud]
read_bands() — per-band satellite discover/window/reproject/scale/stack [raster]
2D viz deck.gl scatter / GeoJSON / heatmap / hexagon; rich interactive HTML (layer panel, basemap switch, measure, coords, legend)
Choropleth: quantile, equal-interval, Jenks
Georeferenced raster display + static PNG/PDF
3D globe CesiumJS globe, 2D/2.5D/3D toggle, token-free imagery
True 3D Terrain, point clouds (LAS classes), polygon extrusion, cut planes [3d]
Export glTF/GLB/STL/PLY/OBJ/VTK; Camera3D fly/orbit/tilt [3d]
Raster analytics Spectral indices NDVI/NDWI/NDBI/SAVI/EVI; band composites; slope; reproject; mosaic
Vector analytics Buffer (true meters via UTM), intersection, difference, dissolve; KMeans/DBSCAN; H3/grid binning; contours; O-D flow
Basemaps 46-entry registry with attribution + key-guarding
folium engine Map, Vector/Choropleth/Heatmap/Cluster; Raster overlays (array/COG/tiles), Split, TimeSeries, Custom; classification [folium]
Inline Jupyter/Colab display (show); panels: title, legend, colorbar, info; controls: search, locate, scale, basemap-switcher, draw, measure, fullscreen, minimap [folium]
leafmap-style API: add_geojson/gdf/shp/vector/csv, add_raster, add_cog_layer, add_stac_layer, add_labels, split_map, linked_maps, colormaps, built-in legends [folium]
Integrations geofetch, geovision (ecosystem); osm, pc, overture, ee (lazy) ✅ (extras per backend)
Streaming WebSocket & MQTT clients; trail buffer; alerts [streaming]
Publishing Interactive HTML, static PNG/PDF, Jupyter _repr_html_, FastAPI REST server
Extensibility Decorator plugin system

The deck.gl engine ships with a pytest suite (105 tests passing as of the 0.6.x API work). The 0.7.0 additions (basemaps, folium engine, integrations) were verified when this build was assembled — see PACKAGE_STATUS.md for the exact matrix, including what was not re-verified in that environment.

Not built yet / roadmap

Where the public API touches these, it raises a clear error rather than pretending.

Planned Feature
folium engine to_png/to_pdf (selenium/playwright, weasyprint) — wired, needs a headless browser; ipywidgets / Streamlit widgets; full Jinja2 theme system; raster TimeSeriesLayer frames
More backends ipyleaflet, plotly
Scale COG tile server with disk cache; vector tiles; WebGPU path for multi-million-point scenes
Analytics Zonal statistics charts; Getis-Ord Gi* hotspots; OSMnx routing

Design principles

  • Honesty over surface area. A smaller set of things that genuinely work beats a large set that breaks on first use. Unimplemented features error clearly; they are never silently stubbed.
  • Real units. Buffers reproject to the local UTM zone so "250 m" means 250 meters, not 250 degrees.
  • Token-free by default. Open imagery for both the 2D maps and the globe; paid keys are optional and, when required, fail loudly.
  • Lazy optional deps. Heavy/optional libraries import only when used, with an install hint on failure.

Examples & notebooks

Runnable notebooks live in examples/notebooks/ and double as integration tests:

  • 00_complete_tour.ipynbthe full tour: every layer type, the leafmap-style API, and all advanced custom controls/panels in one notebook
  • 01_quickstart.ipynb — vectors, tooltips, basemaps
  • 02_raster_and_vector.ipynb — raster overlays + colorbar + vector, RGB composite, before/after split
  • 03_controls_legends_colormaps.ipynb — title/legend/colorbar/info panels, search/locate/scale, colormaps

They open directly in Jupyter or Colab (the folium engine renders inline).

Development

pip install -e ".[folium,dev]"
pytest                       # core suite + notebook execution tests
pytest tests/test_notebooks.py   # just run the example notebooks end-to-end
ruff check .

The notebook tests execute every example in a fresh kernel and fail if any cell raises — they skip automatically if the notebook stack isn't installed.

License

MIT.

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

pygeospace-0.8.1.tar.gz (116.3 kB view details)

Uploaded Source

Built Distribution

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

pygeospace-0.8.1-py3-none-any.whl (115.4 kB view details)

Uploaded Python 3

File details

Details for the file pygeospace-0.8.1.tar.gz.

File metadata

  • Download URL: pygeospace-0.8.1.tar.gz
  • Upload date:
  • Size: 116.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for pygeospace-0.8.1.tar.gz
Algorithm Hash digest
SHA256 aa72e42728e42afd5f489ed2389f90566f6afe048691a91902df7baeaf2cfb1a
MD5 4197f562175078833e88bdfc2b0c5462
BLAKE2b-256 365a5bd48e2f319f1b682216bbbddc4611abddcf672a5c885464135aef7791ce

See more details on using hashes here.

File details

Details for the file pygeospace-0.8.1-py3-none-any.whl.

File metadata

  • Download URL: pygeospace-0.8.1-py3-none-any.whl
  • Upload date:
  • Size: 115.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for pygeospace-0.8.1-py3-none-any.whl
Algorithm Hash digest
SHA256 6a1be921bb37958747618cbf7d46fcbababbce546a11917bdb1bc9aa944e74cb
MD5 0ede0ffa35206d179dc8b87f3a1c4bc1
BLAKE2b-256 c3e7a4dd6ccbcbaf6205e07176be08c9cf5f8d66516c24195ee0bc96c9184e36

See more details on using hashes here.

Supported by

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