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 pgs → pgs.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.ipynb— the full tour: every layer type, the leafmap-style API, and all advanced custom controls/panels in one notebook01_quickstart.ipynb— vectors, tooltips, basemaps02_raster_and_vector.ipynb— raster overlays + colorbar + vector, RGB composite, before/after split03_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.
Metadata
Release files for pygeospace 0.8.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| pygeospace-0.8.1.tar.gz | 116.3 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pygeospace-0.8.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 231.7 kB
Release files / pygeospace-0.8.1.tar.gz
| Download URL | pygeospace-0.8.1.tar.gz |
|---|---|
| Size | 116.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
aa72e42728e42afd5f489ed2389f90566f6afe048691a91902df7baeaf2cfb1a
|
|
BLAKE2b-256 checksum How to use checksums |
365a5bd48e2f319f1b682216bbbddc4611abddcf672a5c885464135aef7791ce
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.15
|
Release files / pygeospace-0.8.1-py3-none-any.whl
| Download URL | pygeospace-0.8.1-py3-none-any.whl |
|---|---|
| Size | 115.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6a1be921bb37958747618cbf7d46fcbababbce546a11917bdb1bc9aa944e74cb
|
|
BLAKE2b-256 checksum How to use checksums |
c3e7a4dd6ccbcbaf6205e07176be08c9cf5f8d66516c24195ee0bc96c9184e36
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.15
|
Security for pygeospace 0.8.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.