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 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.
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aa72e42728e42afd5f489ed2389f90566f6afe048691a91902df7baeaf2cfb1a
|
|
| MD5 |
4197f562175078833e88bdfc2b0c5462
|
|
| BLAKE2b-256 |
365a5bd48e2f319f1b682216bbbddc4611abddcf672a5c885464135aef7791ce
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6a1be921bb37958747618cbf7d46fcbababbce546a11917bdb1bc9aa944e74cb
|
|
| MD5 |
0ede0ffa35206d179dc8b87f3a1c4bc1
|
|
| BLAKE2b-256 |
c3e7a4dd6ccbcbaf6205e07176be08c9cf5f8d66516c24195ee0bc96c9184e36
|