pygeospace 0.6.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pygeospace-0.6.1/LICENSE +21 -0
- pygeospace-0.6.1/MANIFEST.in +4 -0
- pygeospace-0.6.1/PKG-INFO +249 -0
- pygeospace-0.6.1/README.md +171 -0
- pygeospace-0.6.1/ROADMAP.md +83 -0
- pygeospace-0.6.1/examples/crop_health.py +58 -0
- pygeospace-0.6.1/examples/quickstart.py +64 -0
- pygeospace-0.6.1/examples/terrain_3d.py +48 -0
- pygeospace-0.6.1/examples/workflow_demo.py +162 -0
- pygeospace-0.6.1/pygeospace/__init__.py +49 -0
- pygeospace-0.6.1/pygeospace/analytics/__init__.py +32 -0
- pygeospace-0.6.1/pygeospace/analytics/clustering.py +117 -0
- pygeospace-0.6.1/pygeospace/analytics/contours.py +86 -0
- pygeospace-0.6.1/pygeospace/analytics/raster.py +157 -0
- pygeospace-0.6.1/pygeospace/analytics/spatial.py +74 -0
- pygeospace-0.6.1/pygeospace/analytics/spectral.py +133 -0
- pygeospace-0.6.1/pygeospace/analytics/stats.py +143 -0
- pygeospace-0.6.1/pygeospace/api/__init__.py +5 -0
- pygeospace-0.6.1/pygeospace/api/rest.py +80 -0
- pygeospace-0.6.1/pygeospace/cli.py +250 -0
- pygeospace-0.6.1/pygeospace/core/__init__.py +15 -0
- pygeospace-0.6.1/pygeospace/core/camera3d.py +137 -0
- pygeospace-0.6.1/pygeospace/core/layer.py +317 -0
- pygeospace-0.6.1/pygeospace/core/map.py +547 -0
- pygeospace-0.6.1/pygeospace/core/view.py +79 -0
- pygeospace-0.6.1/pygeospace/io/__init__.py +5 -0
- pygeospace-0.6.1/pygeospace/io/reader.py +205 -0
- pygeospace-0.6.1/pygeospace/plugins/__init__.py +5 -0
- pygeospace-0.6.1/pygeospace/plugins/manager.py +80 -0
- pygeospace-0.6.1/pygeospace/render/__init__.py +6 -0
- pygeospace-0.6.1/pygeospace/render/deckgl.py +86 -0
- pygeospace-0.6.1/pygeospace/render/flow.py +88 -0
- pygeospace-0.6.1/pygeospace/render/pyvista3d.py +449 -0
- pygeospace-0.6.1/pygeospace/render/raster2d.py +147 -0
- pygeospace-0.6.1/pygeospace/streaming/__init__.py +5 -0
- pygeospace-0.6.1/pygeospace/streaming/source.py +134 -0
- pygeospace-0.6.1/pygeospace/web/__init__.py +5 -0
- pygeospace-0.6.1/pygeospace/web/server.py +53 -0
- pygeospace-0.6.1/pygeospace.egg-info/PKG-INFO +249 -0
- pygeospace-0.6.1/pygeospace.egg-info/SOURCES.txt +52 -0
- pygeospace-0.6.1/pygeospace.egg-info/dependency_links.txt +1 -0
- pygeospace-0.6.1/pygeospace.egg-info/entry_points.txt +2 -0
- pygeospace-0.6.1/pygeospace.egg-info/requires.txt +64 -0
- pygeospace-0.6.1/pygeospace.egg-info/top_level.txt +1 -0
- pygeospace-0.6.1/pyproject.toml +91 -0
- pygeospace-0.6.1/setup.cfg +4 -0
- pygeospace-0.6.1/tests/conftest.py +80 -0
- pygeospace-0.6.1/tests/test_3d.py +168 -0
- pygeospace-0.6.1/tests/test_advanced.py +127 -0
- pygeospace-0.6.1/tests/test_analytics.py +119 -0
- pygeospace-0.6.1/tests/test_convenience.py +159 -0
- pygeospace-0.6.1/tests/test_core.py +87 -0
- pygeospace-0.6.1/tests/test_io.py +57 -0
- pygeospace-0.6.1/tests/test_render.py +131 -0
pygeospace-0.6.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PyGeoSpace contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pygeospace
|
|
3
|
+
Version: 0.6.1
|
|
4
|
+
Summary: Powerful, honest, open-source geospatial visualization with true 3D.
|
|
5
|
+
Author: PyGeoSpace contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/pygeospace/pygeospace
|
|
8
|
+
Project-URL: Documentation, https://github.com/pygeospace/pygeospace#readme
|
|
9
|
+
Project-URL: Roadmap, https://github.com/pygeospace/pygeospace/blob/main/ROADMAP.md
|
|
10
|
+
Keywords: gis,geospatial,visualization,deck.gl,mapping
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: geopandas>=1.0
|
|
24
|
+
Requires-Dist: shapely>=2.0
|
|
25
|
+
Requires-Dist: pyproj>=3.5
|
|
26
|
+
Requires-Dist: pydeck>=0.9
|
|
27
|
+
Requires-Dist: matplotlib>=3.7
|
|
28
|
+
Requires-Dist: pillow>=10.0
|
|
29
|
+
Requires-Dist: scikit-learn>=1.3
|
|
30
|
+
Requires-Dist: fastapi>=0.110
|
|
31
|
+
Requires-Dist: uvicorn>=0.27
|
|
32
|
+
Requires-Dist: click>=8.1
|
|
33
|
+
Requires-Dist: pydantic>=2.0
|
|
34
|
+
Requires-Dist: numpy>=1.24
|
|
35
|
+
Requires-Dist: pandas>=2.0
|
|
36
|
+
Requires-Dist: scipy>=1.10
|
|
37
|
+
Provides-Extra: raster
|
|
38
|
+
Requires-Dist: rasterio>=1.3; extra == "raster"
|
|
39
|
+
Provides-Extra: 3d
|
|
40
|
+
Requires-Dist: pyvista>=0.45; extra == "3d"
|
|
41
|
+
Requires-Dist: meshio>=5.3; extra == "3d"
|
|
42
|
+
Requires-Dist: trame>=3.0; extra == "3d"
|
|
43
|
+
Requires-Dist: trame-vtk>=2.8; extra == "3d"
|
|
44
|
+
Requires-Dist: trame-vuetify>=2.7; extra == "3d"
|
|
45
|
+
Provides-Extra: pointcloud
|
|
46
|
+
Requires-Dist: laspy[lazrs]>=2.5; extra == "pointcloud"
|
|
47
|
+
Provides-Extra: streaming
|
|
48
|
+
Requires-Dist: websockets>=12.0; extra == "streaming"
|
|
49
|
+
Requires-Dist: paho-mqtt>=2.0; extra == "streaming"
|
|
50
|
+
Provides-Extra: clustering-h3
|
|
51
|
+
Requires-Dist: h3>=4.0; extra == "clustering-h3"
|
|
52
|
+
Provides-Extra: network
|
|
53
|
+
Requires-Dist: osmnx>=2.0; extra == "network"
|
|
54
|
+
Provides-Extra: threed
|
|
55
|
+
Requires-Dist: pyvista>=0.45; extra == "threed"
|
|
56
|
+
Requires-Dist: meshio>=5.3; extra == "threed"
|
|
57
|
+
Requires-Dist: trame>=3.0; extra == "threed"
|
|
58
|
+
Requires-Dist: trame-vtk>=2.8; extra == "threed"
|
|
59
|
+
Requires-Dist: trame-vuetify>=2.7; extra == "threed"
|
|
60
|
+
Provides-Extra: distributed
|
|
61
|
+
Requires-Dist: dask[complete]>=2024.1; extra == "distributed"
|
|
62
|
+
Provides-Extra: all
|
|
63
|
+
Requires-Dist: rasterio>=1.3; extra == "all"
|
|
64
|
+
Requires-Dist: laspy[lazrs]>=2.5; extra == "all"
|
|
65
|
+
Requires-Dist: websockets>=12.0; extra == "all"
|
|
66
|
+
Requires-Dist: paho-mqtt>=2.0; extra == "all"
|
|
67
|
+
Requires-Dist: h3>=4.0; extra == "all"
|
|
68
|
+
Requires-Dist: pyvista>=0.45; extra == "all"
|
|
69
|
+
Requires-Dist: meshio>=5.3; extra == "all"
|
|
70
|
+
Requires-Dist: trame>=3.0; extra == "all"
|
|
71
|
+
Requires-Dist: trame-vtk>=2.8; extra == "all"
|
|
72
|
+
Requires-Dist: trame-vuetify>=2.7; extra == "all"
|
|
73
|
+
Provides-Extra: dev
|
|
74
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
75
|
+
Requires-Dist: pytest-cov>=4.1; extra == "dev"
|
|
76
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
77
|
+
Dynamic: license-file
|
|
78
|
+
|
|
79
|
+
# PyGeoSpace
|
|
80
|
+
|
|
81
|
+
**Powerful, honest, open-source geospatial visualization for Python.**
|
|
82
|
+
|
|
83
|
+
PyGeoSpace is a 2D / true-3D mapping library built on a real geospatial stack
|
|
84
|
+
(GeoPandas, Shapely, pyproj, deck.gl, PyVista). It reads the common vector and
|
|
85
|
+
raster formats, renders interactive 2D maps and **true 3D terrain, point clouds,
|
|
86
|
+
and extruded geometry**, runs spatial + spectral analytics, and exports
|
|
87
|
+
interactive HTML, static images, or 3D model files (glTF/STL/PLY/OBJ).
|
|
88
|
+
|
|
89
|
+
This is the **0.6.0 (Beta)** release — "Advanced Visualization & True 3D". It is
|
|
90
|
+
deliberately honest about its boundaries: every capability under "What works
|
|
91
|
+
today" is implemented and covered by **81 passing tests**. Capabilities under
|
|
92
|
+
"Roadmap" are **not silently stubbed** — calling them raises a clear error that
|
|
93
|
+
names exactly what to install or that the feature is planned.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Install
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
pip install pygeospace # core: vector IO, analytics, deck.gl, web, CLI
|
|
101
|
+
pip install "pygeospace[raster]" # + GeoTIFF/COG/JPEG2000 reading (rasterio)
|
|
102
|
+
pip install "pygeospace[3d]" # + true 3D (PyVista, trame, meshio)
|
|
103
|
+
pip install "pygeospace[streaming]" # + WebSocket & MQTT clients
|
|
104
|
+
pip install "pygeospace[all]" # everything optional
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Python 3.10+.
|
|
108
|
+
|
|
109
|
+
## True 3D quickstart (0.6.0)
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
import pygeospace as pgs
|
|
113
|
+
|
|
114
|
+
# Terrain straight from an elevation GeoTIFF — interactive in the browser.
|
|
115
|
+
m = pgs.Map(mode="3d")
|
|
116
|
+
m.add_terrain("srtm_everest.tif", exaggeration=2.0, cmap="gist_earth")
|
|
117
|
+
m.export_3d("terrain.html") # interactive vtk.js page
|
|
118
|
+
m.render_3d("terrain.png") # static render
|
|
119
|
+
m.export_3d("terrain.gltf") # 3D model for Blender/Unity/printing
|
|
120
|
+
|
|
121
|
+
# Classified LIDAR point cloud in 3D.
|
|
122
|
+
pgs.Map(mode="3d").add_pointcloud_3d("forest.las").render_3d("canopy.png")
|
|
123
|
+
|
|
124
|
+
# Extrude building footprints by a height attribute.
|
|
125
|
+
pgs.Map(mode="3d").add_extruded("buildings.shp", "height", factor=1.0).export_3d("city.gltf")
|
|
126
|
+
|
|
127
|
+
# Scripted camera flythrough.
|
|
128
|
+
cam = pgs.Camera3D(position=(0, 0, 5000), focal_point=(0, 0, 0))
|
|
129
|
+
cam.fly_to(4000, 4000, 2000, duration=3.0).orbit(45).tilt(20)
|
|
130
|
+
m.render_3d("flyover.png", camera=cam)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
pygeospace 3d terrain elevation.tif --exaggeration 2.0 -o terrain.html
|
|
135
|
+
pygeospace 3d pointcloud data.las --classify -o cloud.html
|
|
136
|
+
pygeospace spectral sentinel.tif --indices ndvi,ndwi -o indices/
|
|
137
|
+
pygeospace flow od_data.csv -o flow.html
|
|
138
|
+
pygeospace export-gltf elevation.tif -o scene.gltf
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Quickstart (under 5 minutes)
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
import pygeospace as pgs
|
|
145
|
+
|
|
146
|
+
m = pgs.Map()
|
|
147
|
+
|
|
148
|
+
# Read any vector format; style fluently; results chain.
|
|
149
|
+
cities = m.add_layer("cities.geojson")
|
|
150
|
+
cities.style(get_fill_color=[255, 90, 0, 200], get_radius=3000)
|
|
151
|
+
|
|
152
|
+
# Classify an attribute into a choropleth (quantiles / equal_interval / jenks).
|
|
153
|
+
from pygeospace.analytics import choropleth
|
|
154
|
+
districts = m.add_layer("districts.shp")
|
|
155
|
+
choropleth(districts, "population", method="jenks", k=5)
|
|
156
|
+
|
|
157
|
+
# A geometry pipeline, returning new layers at each step.
|
|
158
|
+
buffered = m.add_layer("rivers.gpkg").buffer(250) # 250 m, in true meters
|
|
159
|
+
|
|
160
|
+
m.fit().save("map.html") # interactive, offline-capable deck.gl page
|
|
161
|
+
m.save("map.png", dpi=300) # static export
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Command line:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
pygeospace visualize cities.geojson -o map.html --style choropleth --attribute pop --method jenks
|
|
168
|
+
pygeospace export cities.geojson -o map.png --dpi 300
|
|
169
|
+
pygeospace serve cities.geojson --port 8000 # live preview in the browser
|
|
170
|
+
pygeospace serve --api --port 8000 # REST CRUD API
|
|
171
|
+
pygeospace info data.gpkg
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## What works today (0.5.0)
|
|
175
|
+
|
|
176
|
+
| Area | Capability | Status |
|
|
177
|
+
|------|-----------|--------|
|
|
178
|
+
| **IO** | Shapefile, GeoJSON, GeoPackage, KML, GPX, GML (via GeoPandas/OGR) | ✅ |
|
|
179
|
+
| | CSV with coordinate columns | ✅ |
|
|
180
|
+
| | PostGIS query → layer | ✅ |
|
|
181
|
+
| | Format autodetect (extension + magic bytes) | ✅ |
|
|
182
|
+
| | GeoTIFF / COG / JPEG2000 read | ✅ with `[raster]` |
|
|
183
|
+
| | LAS/LAZ point cloud read | ✅ with `[pointcloud]` |
|
|
184
|
+
| **Vector viz** | deck.gl scatter / GeoJSON / heatmap / hexagon-bin | ✅ |
|
|
185
|
+
| | Choropleth: quantiles, equal-interval, **Jenks natural breaks** | ✅ |
|
|
186
|
+
| | pandas-query attribute filtering (instant, no reload) | ✅ |
|
|
187
|
+
| | CRS auto-reprojection to EPSG:4326 | ✅ |
|
|
188
|
+
| **Raster** | Multi-band arrays, on-the-fly NDVI, hillshade | ✅ |
|
|
189
|
+
| **Analytics** | Buffer (true meters via UTM), intersection, difference, dissolve | ✅ |
|
|
190
|
+
| | KMeans / DBSCAN clustering, hex/grid binning (H3 if installed) | ✅ |
|
|
191
|
+
| **3D (0.6.0)** | True 3D terrain from rasters (exaggeration, elevation colormap) | ✅ `[3d]` |
|
|
192
|
+
| | 3D point clouds with LAS-classification coloring; polygon extrusion; cut planes | ✅ `[3d]` |
|
|
193
|
+
| | Render to PNG + interactive HTML; export glTF/GLB/STL/PLY/OBJ/VTK | ✅ `[3d]` |
|
|
194
|
+
| | `Camera3D`: fly_to / orbit / tilt / path recording | ✅ `[3d]` |
|
|
195
|
+
| | Tilted (pitch/bearing) pseudo-3D deck.gl view | ✅ |
|
|
196
|
+
| **Raster (0.6.0)** | Spectral indices NDVI/NDWI/NDBI/SAVI/EVI; band compositing; slope | ✅ |
|
|
197
|
+
| | On-the-fly reprojection + multi-raster mosaicing | ✅ `[raster]` |
|
|
198
|
+
| **Vector (0.6.0)** | Contour lines from scattered points; O-D flow maps (arcs); H3 hexbins | ✅ |
|
|
199
|
+
| **Streaming** | WebSocket & MQTT clients; trail buffer; threshold alerts | ✅ with `[streaming]`¹ |
|
|
200
|
+
| **Publishing** | Interactive HTML, static PNG/PDF, Jupyter inline (`_repr_html_`) | ✅ |
|
|
201
|
+
| | FastAPI dev server + REST CRUD API | ✅ |
|
|
202
|
+
| **Extensibility** | Decorator plugin system (data sources, renderers, tools) | ✅ |
|
|
203
|
+
|
|
204
|
+
¹ The clients are real and connect to any reachable endpoint. Connecting to a
|
|
205
|
+
specific public broker requires network access to that host.
|
|
206
|
+
|
|
207
|
+
## Roadmap — what is *not* in 0.5.0
|
|
208
|
+
|
|
209
|
+
These are tracked in [`ROADMAP.md`](ROADMAP.md). Where the public API touches
|
|
210
|
+
them, it raises a clear, actionable error rather than pretending.
|
|
211
|
+
|
|
212
|
+
| Planned for | Feature |
|
|
213
|
+
|-------------|---------|
|
|
214
|
+
| **0.6.0** | True PyVista 3D globe + terrain extrusion; raster↔vector opacity blending in the static renderer; vector-tile generation |
|
|
215
|
+
| **0.7.0** | Getis-Ord Gi\* hotspot maps with significance shading; zonal statistics charts; OSMnx shortest-path routing |
|
|
216
|
+
| **1.0.0** | WebGPU renderer for multi-million-point scenes; COG tile server with LRU disk cache; Dask-parallel distributed loading; flow-map & time-slider widgets as first-class UI |
|
|
217
|
+
|
|
218
|
+
## Design notes
|
|
219
|
+
|
|
220
|
+
- **Honesty over surface area.** A smaller set of things that genuinely work
|
|
221
|
+
beats a large set that breaks on first use.
|
|
222
|
+
- **Real units.** Buffers reproject to the local UTM zone so "250 m" means 250
|
|
223
|
+
meters, not 250 degrees.
|
|
224
|
+
- **Lazy optional deps.** Heavy/optional libraries (rasterio, laspy, paho-mqtt,
|
|
225
|
+
h3, pyvista) are imported only when used, with install hints on failure.
|
|
226
|
+
|
|
227
|
+
## Development
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
pip install -e ".[dev]"
|
|
231
|
+
pytest # 43 tests
|
|
232
|
+
ruff check .
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## License
|
|
236
|
+
|
|
237
|
+
MIT.
|
|
238
|
+
|
|
239
|
+
## 3D & 2D Gallery
|
|
240
|
+
|
|
241
|
+
Rendered headlessly by the test/example suite (see `examples/`):
|
|
242
|
+
|
|
243
|
+
| | |
|
|
244
|
+
|---|---|
|
|
245
|
+
|  3D terrain (elevation colormap) |  Terrain from GeoTIFF |
|
|
246
|
+
|  Classified point cloud |  NDVI spectral index |
|
|
247
|
+
|  True color satellite imagery |  False color infrared (FCIR) |
|
|
248
|
+
|  Agriculture/Vegetation analysis |  2D NYC interactive map |
|
|
249
|
+
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# PyGeoSpace
|
|
2
|
+
|
|
3
|
+
**Powerful, honest, open-source geospatial visualization for Python.**
|
|
4
|
+
|
|
5
|
+
PyGeoSpace is a 2D / true-3D mapping library built on a real geospatial stack
|
|
6
|
+
(GeoPandas, Shapely, pyproj, deck.gl, PyVista). It reads the common vector and
|
|
7
|
+
raster formats, renders interactive 2D maps and **true 3D terrain, point clouds,
|
|
8
|
+
and extruded geometry**, runs spatial + spectral analytics, and exports
|
|
9
|
+
interactive HTML, static images, or 3D model files (glTF/STL/PLY/OBJ).
|
|
10
|
+
|
|
11
|
+
This is the **0.6.0 (Beta)** release — "Advanced Visualization & True 3D". It is
|
|
12
|
+
deliberately honest about its boundaries: every capability under "What works
|
|
13
|
+
today" is implemented and covered by **81 passing tests**. Capabilities under
|
|
14
|
+
"Roadmap" are **not silently stubbed** — calling them raises a clear error that
|
|
15
|
+
names exactly what to install or that the feature is planned.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install pygeospace # core: vector IO, analytics, deck.gl, web, CLI
|
|
23
|
+
pip install "pygeospace[raster]" # + GeoTIFF/COG/JPEG2000 reading (rasterio)
|
|
24
|
+
pip install "pygeospace[3d]" # + true 3D (PyVista, trame, meshio)
|
|
25
|
+
pip install "pygeospace[streaming]" # + WebSocket & MQTT clients
|
|
26
|
+
pip install "pygeospace[all]" # everything optional
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Python 3.10+.
|
|
30
|
+
|
|
31
|
+
## True 3D quickstart (0.6.0)
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
import pygeospace as pgs
|
|
35
|
+
|
|
36
|
+
# Terrain straight from an elevation GeoTIFF — interactive in the browser.
|
|
37
|
+
m = pgs.Map(mode="3d")
|
|
38
|
+
m.add_terrain("srtm_everest.tif", exaggeration=2.0, cmap="gist_earth")
|
|
39
|
+
m.export_3d("terrain.html") # interactive vtk.js page
|
|
40
|
+
m.render_3d("terrain.png") # static render
|
|
41
|
+
m.export_3d("terrain.gltf") # 3D model for Blender/Unity/printing
|
|
42
|
+
|
|
43
|
+
# Classified LIDAR point cloud in 3D.
|
|
44
|
+
pgs.Map(mode="3d").add_pointcloud_3d("forest.las").render_3d("canopy.png")
|
|
45
|
+
|
|
46
|
+
# Extrude building footprints by a height attribute.
|
|
47
|
+
pgs.Map(mode="3d").add_extruded("buildings.shp", "height", factor=1.0).export_3d("city.gltf")
|
|
48
|
+
|
|
49
|
+
# Scripted camera flythrough.
|
|
50
|
+
cam = pgs.Camera3D(position=(0, 0, 5000), focal_point=(0, 0, 0))
|
|
51
|
+
cam.fly_to(4000, 4000, 2000, duration=3.0).orbit(45).tilt(20)
|
|
52
|
+
m.render_3d("flyover.png", camera=cam)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pygeospace 3d terrain elevation.tif --exaggeration 2.0 -o terrain.html
|
|
57
|
+
pygeospace 3d pointcloud data.las --classify -o cloud.html
|
|
58
|
+
pygeospace spectral sentinel.tif --indices ndvi,ndwi -o indices/
|
|
59
|
+
pygeospace flow od_data.csv -o flow.html
|
|
60
|
+
pygeospace export-gltf elevation.tif -o scene.gltf
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Quickstart (under 5 minutes)
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import pygeospace as pgs
|
|
67
|
+
|
|
68
|
+
m = pgs.Map()
|
|
69
|
+
|
|
70
|
+
# Read any vector format; style fluently; results chain.
|
|
71
|
+
cities = m.add_layer("cities.geojson")
|
|
72
|
+
cities.style(get_fill_color=[255, 90, 0, 200], get_radius=3000)
|
|
73
|
+
|
|
74
|
+
# Classify an attribute into a choropleth (quantiles / equal_interval / jenks).
|
|
75
|
+
from pygeospace.analytics import choropleth
|
|
76
|
+
districts = m.add_layer("districts.shp")
|
|
77
|
+
choropleth(districts, "population", method="jenks", k=5)
|
|
78
|
+
|
|
79
|
+
# A geometry pipeline, returning new layers at each step.
|
|
80
|
+
buffered = m.add_layer("rivers.gpkg").buffer(250) # 250 m, in true meters
|
|
81
|
+
|
|
82
|
+
m.fit().save("map.html") # interactive, offline-capable deck.gl page
|
|
83
|
+
m.save("map.png", dpi=300) # static export
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Command line:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pygeospace visualize cities.geojson -o map.html --style choropleth --attribute pop --method jenks
|
|
90
|
+
pygeospace export cities.geojson -o map.png --dpi 300
|
|
91
|
+
pygeospace serve cities.geojson --port 8000 # live preview in the browser
|
|
92
|
+
pygeospace serve --api --port 8000 # REST CRUD API
|
|
93
|
+
pygeospace info data.gpkg
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## What works today (0.5.0)
|
|
97
|
+
|
|
98
|
+
| Area | Capability | Status |
|
|
99
|
+
|------|-----------|--------|
|
|
100
|
+
| **IO** | Shapefile, GeoJSON, GeoPackage, KML, GPX, GML (via GeoPandas/OGR) | ✅ |
|
|
101
|
+
| | CSV with coordinate columns | ✅ |
|
|
102
|
+
| | PostGIS query → layer | ✅ |
|
|
103
|
+
| | Format autodetect (extension + magic bytes) | ✅ |
|
|
104
|
+
| | GeoTIFF / COG / JPEG2000 read | ✅ with `[raster]` |
|
|
105
|
+
| | LAS/LAZ point cloud read | ✅ with `[pointcloud]` |
|
|
106
|
+
| **Vector viz** | deck.gl scatter / GeoJSON / heatmap / hexagon-bin | ✅ |
|
|
107
|
+
| | Choropleth: quantiles, equal-interval, **Jenks natural breaks** | ✅ |
|
|
108
|
+
| | pandas-query attribute filtering (instant, no reload) | ✅ |
|
|
109
|
+
| | CRS auto-reprojection to EPSG:4326 | ✅ |
|
|
110
|
+
| **Raster** | Multi-band arrays, on-the-fly NDVI, hillshade | ✅ |
|
|
111
|
+
| **Analytics** | Buffer (true meters via UTM), intersection, difference, dissolve | ✅ |
|
|
112
|
+
| | KMeans / DBSCAN clustering, hex/grid binning (H3 if installed) | ✅ |
|
|
113
|
+
| **3D (0.6.0)** | True 3D terrain from rasters (exaggeration, elevation colormap) | ✅ `[3d]` |
|
|
114
|
+
| | 3D point clouds with LAS-classification coloring; polygon extrusion; cut planes | ✅ `[3d]` |
|
|
115
|
+
| | Render to PNG + interactive HTML; export glTF/GLB/STL/PLY/OBJ/VTK | ✅ `[3d]` |
|
|
116
|
+
| | `Camera3D`: fly_to / orbit / tilt / path recording | ✅ `[3d]` |
|
|
117
|
+
| | Tilted (pitch/bearing) pseudo-3D deck.gl view | ✅ |
|
|
118
|
+
| **Raster (0.6.0)** | Spectral indices NDVI/NDWI/NDBI/SAVI/EVI; band compositing; slope | ✅ |
|
|
119
|
+
| | On-the-fly reprojection + multi-raster mosaicing | ✅ `[raster]` |
|
|
120
|
+
| **Vector (0.6.0)** | Contour lines from scattered points; O-D flow maps (arcs); H3 hexbins | ✅ |
|
|
121
|
+
| **Streaming** | WebSocket & MQTT clients; trail buffer; threshold alerts | ✅ with `[streaming]`¹ |
|
|
122
|
+
| **Publishing** | Interactive HTML, static PNG/PDF, Jupyter inline (`_repr_html_`) | ✅ |
|
|
123
|
+
| | FastAPI dev server + REST CRUD API | ✅ |
|
|
124
|
+
| **Extensibility** | Decorator plugin system (data sources, renderers, tools) | ✅ |
|
|
125
|
+
|
|
126
|
+
¹ The clients are real and connect to any reachable endpoint. Connecting to a
|
|
127
|
+
specific public broker requires network access to that host.
|
|
128
|
+
|
|
129
|
+
## Roadmap — what is *not* in 0.5.0
|
|
130
|
+
|
|
131
|
+
These are tracked in [`ROADMAP.md`](ROADMAP.md). Where the public API touches
|
|
132
|
+
them, it raises a clear, actionable error rather than pretending.
|
|
133
|
+
|
|
134
|
+
| Planned for | Feature |
|
|
135
|
+
|-------------|---------|
|
|
136
|
+
| **0.6.0** | True PyVista 3D globe + terrain extrusion; raster↔vector opacity blending in the static renderer; vector-tile generation |
|
|
137
|
+
| **0.7.0** | Getis-Ord Gi\* hotspot maps with significance shading; zonal statistics charts; OSMnx shortest-path routing |
|
|
138
|
+
| **1.0.0** | WebGPU renderer for multi-million-point scenes; COG tile server with LRU disk cache; Dask-parallel distributed loading; flow-map & time-slider widgets as first-class UI |
|
|
139
|
+
|
|
140
|
+
## Design notes
|
|
141
|
+
|
|
142
|
+
- **Honesty over surface area.** A smaller set of things that genuinely work
|
|
143
|
+
beats a large set that breaks on first use.
|
|
144
|
+
- **Real units.** Buffers reproject to the local UTM zone so "250 m" means 250
|
|
145
|
+
meters, not 250 degrees.
|
|
146
|
+
- **Lazy optional deps.** Heavy/optional libraries (rasterio, laspy, paho-mqtt,
|
|
147
|
+
h3, pyvista) are imported only when used, with install hints on failure.
|
|
148
|
+
|
|
149
|
+
## Development
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
pip install -e ".[dev]"
|
|
153
|
+
pytest # 43 tests
|
|
154
|
+
ruff check .
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
MIT.
|
|
160
|
+
|
|
161
|
+
## 3D & 2D Gallery
|
|
162
|
+
|
|
163
|
+
Rendered headlessly by the test/example suite (see `examples/`):
|
|
164
|
+
|
|
165
|
+
| | |
|
|
166
|
+
|---|---|
|
|
167
|
+
|  3D terrain (elevation colormap) |  Terrain from GeoTIFF |
|
|
168
|
+
|  Classified point cloud |  NDVI spectral index |
|
|
169
|
+
|  True color satellite imagery |  False color infrared (FCIR) |
|
|
170
|
+
|  Agriculture/Vegetation analysis |  2D NYC interactive map |
|
|
171
|
+
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# PyGeoSpace Roadmap
|
|
2
|
+
|
|
3
|
+
PyGeoSpace follows one rule: **ship what works, name what doesn't.** This file
|
|
4
|
+
is the single source of truth for which features are real in each release. If a
|
|
5
|
+
feature is listed as planned, the corresponding code path raises a clear error
|
|
6
|
+
(often an `ImportError` naming an extra, or a message pointing here) instead of
|
|
7
|
+
returning a fake result.
|
|
8
|
+
|
|
9
|
+
## 0.5.0 — current (Beta)
|
|
10
|
+
|
|
11
|
+
Implemented and tested:
|
|
12
|
+
|
|
13
|
+
- Vector IO for Shapefile, GeoJSON, GeoPackage, KML, GPX, GML, CSV-with-coords,
|
|
14
|
+
PostGIS; format autodetection by extension and magic bytes.
|
|
15
|
+
- Raster IO (GeoTIFF/COG/JPEG2000) via the `[raster]` extra.
|
|
16
|
+
- LAS/LAZ point-cloud reading via the `[pointcloud]` extra.
|
|
17
|
+
- deck.gl rendering: scatter, GeoJSON, heatmap, hexagon binning.
|
|
18
|
+
- Choropleth classification: equal-interval, quantiles, Fisher-Jenks natural
|
|
19
|
+
breaks (implemented from scratch, no optional dependency).
|
|
20
|
+
- pandas-query attribute filtering.
|
|
21
|
+
- Spatial analytics: metric buffer (UTM reprojection), intersection, difference,
|
|
22
|
+
dissolve — all returning chainable layers.
|
|
23
|
+
- Clustering: KMeans, DBSCAN, hex/grid binning (H3 via `[clustering-h3]`).
|
|
24
|
+
- Raster analytics: NDVI, hillshade.
|
|
25
|
+
- Streaming: WebSocket and MQTT clients with rolling trail buffers and threshold
|
|
26
|
+
alerts; message handling fully unit-tested without a network (`[streaming]`).
|
|
27
|
+
- Publishing: interactive offline HTML, static PNG/PDF, Jupyter inline repr,
|
|
28
|
+
FastAPI dev server, REST CRUD API.
|
|
29
|
+
- Plugin system: decorator registration of custom data sources, renderers, tools.
|
|
30
|
+
- CLI: `visualize`, `export`, `serve`, `stream`, `info`.
|
|
31
|
+
|
|
32
|
+
## 0.6.0 — current (Beta): Advanced Visualization & True 3D
|
|
33
|
+
|
|
34
|
+
**Delivered and tested** (headless via PyVista's offscreen GL context):
|
|
35
|
+
|
|
36
|
+
- True 3D engine (PyVista): terrain extrusion from elevation rasters with
|
|
37
|
+
vertical exaggeration and elevation colormapping; 3D point clouds with LAS
|
|
38
|
+
classification coloring; polygon extrusion to 3D blocks; 3D polylines; cut
|
|
39
|
+
planes; a graticuled globe (`mode="3d_globe"`).
|
|
40
|
+
- 3D rendering to PNG, interactive HTML (vtk.js/trame), and export to
|
|
41
|
+
glTF / GLB / STL / PLY / OBJ / VTK.
|
|
42
|
+
- `Camera3D` API: `fly_to` (with flythrough keyframes), `orbit`, `tilt`,
|
|
43
|
+
`get_position`, `save_path`.
|
|
44
|
+
- Advanced raster: spectral indices (NDVI, NDWI, NDBI, SAVI, EVI), multi-band
|
|
45
|
+
true/false-color compositing, slope analysis, on-the-fly reprojection, and
|
|
46
|
+
multi-raster mosaicing (the last two via the `[raster]` extra).
|
|
47
|
+
- Advanced vector: contour lines from scattered points (SciPy interpolation +
|
|
48
|
+
isolines), origin-destination flow maps (deck.gl arcs), H3 hex binning.
|
|
49
|
+
- CLI: `3d terrain`, `3d pointcloud`, `spectral`, `flow`, `export-gltf`.
|
|
50
|
+
- Test suite grown from 43 to 81.
|
|
51
|
+
|
|
52
|
+
**Honestly deferred from 0.6.0** (these were in the original 0.6.0 wishlist but
|
|
53
|
+
are not yet real, so they are not shipped as stubs):
|
|
54
|
+
|
|
55
|
+
- Satellite-textured globe (needs bundled basemap imagery).
|
|
56
|
+
- Animated flow particles / time-series **GIF/MP4** export (needs an offscreen
|
|
57
|
+
animation+encoding pipeline that isn't validated yet) → 0.7.0.
|
|
58
|
+
- Interactive browser widgets: drag-drop layer panel, colormap editor, measure
|
|
59
|
+
tool, draw/annotate, lasso brushing (need a full JS frontend) → 0.7.0.
|
|
60
|
+
- Synchronized split 2D/3D view → 0.7.0.
|
|
61
|
+
- LOD / tile-based 3D streaming / octree billion-point clouds / explicit
|
|
62
|
+
GPU-FPS guarantees → 1.0.0 (research-grade systems work).
|
|
63
|
+
- Proportional pie/bar chart markers, curved-label placement engine → 0.7.0.
|
|
64
|
+
|
|
65
|
+
## 0.7.0
|
|
66
|
+
|
|
67
|
+
- Getis-Ord Gi\* hotspot detection with significance visualization.
|
|
68
|
+
- Zonal statistics (raster summarized under vector polygons) with chart output.
|
|
69
|
+
- Network analysis: shortest path on OSM data via OSMnx.
|
|
70
|
+
- Animated flow maps and origin-destination rendering.
|
|
71
|
+
|
|
72
|
+
## 1.0.0
|
|
73
|
+
|
|
74
|
+
- WebGPU renderer targeting multi-million-point interactivity.
|
|
75
|
+
- COG-native tiling with an LRU disk tile cache.
|
|
76
|
+
- Dask-parallel distributed data loading.
|
|
77
|
+
- First-class interactive widgets: time slider, draggable legends, color-scale
|
|
78
|
+
editors, layer toggles.
|
|
79
|
+
|
|
80
|
+
## Explicitly out of scope (for now)
|
|
81
|
+
|
|
82
|
+
AR/VR, a full Google Earth Engine equivalent, and a hosted SaaS. These are not
|
|
83
|
+
on a dated milestone and will not be stubbed.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Crop Health (spectral) — runnable with synthetic Sentinel-like bands.
|
|
2
|
+
|
|
3
|
+
python examples/crop_health.py
|
|
4
|
+
|
|
5
|
+
Computes NDVI, NDWI, and a false-color composite from a synthetic 5-band raster
|
|
6
|
+
and saves colorized PNGs. Swap the synthetic array for
|
|
7
|
+
`read_file("sentinel.tif")` to analyze real imagery.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
import matplotlib
|
|
14
|
+
|
|
15
|
+
matplotlib.use("Agg")
|
|
16
|
+
import matplotlib.pyplot as plt
|
|
17
|
+
|
|
18
|
+
from pygeospace.core.layer import RasterLayer
|
|
19
|
+
from pygeospace.analytics.spectral import spectral_index, composite
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def synthetic_scene() -> RasterLayer:
|
|
23
|
+
"""5 bands (blue, green, red, nir, swir). A vegetated patch + a water body."""
|
|
24
|
+
rng = np.random.default_rng(11)
|
|
25
|
+
h = w = 128
|
|
26
|
+
veg = np.zeros((h, w), bool)
|
|
27
|
+
veg[20:90, 30:110] = True
|
|
28
|
+
water = np.zeros((h, w), bool)
|
|
29
|
+
water[95:120, 10:50] = True
|
|
30
|
+
|
|
31
|
+
blue = rng.uniform(0.05, 0.15, (h, w))
|
|
32
|
+
green = rng.uniform(0.08, 0.2, (h, w))
|
|
33
|
+
red = np.where(veg, 0.08, 0.25) + rng.uniform(0, 0.03, (h, w))
|
|
34
|
+
nir = np.where(veg, 0.55, 0.2)
|
|
35
|
+
nir = np.where(water, 0.05, nir) + rng.uniform(0, 0.03, (h, w))
|
|
36
|
+
swir = np.where(water, 0.04, 0.3) + rng.uniform(0, 0.03, (h, w))
|
|
37
|
+
arr = np.stack([blue, green, red, nir, swir]).astype("float32")
|
|
38
|
+
return RasterLayer(arr, transform=None, crs="EPSG:4326", name="field")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def main() -> None:
|
|
42
|
+
scene = synthetic_scene()
|
|
43
|
+
|
|
44
|
+
ndvi = spectral_index(scene, "ndvi")
|
|
45
|
+
ndwi = spectral_index(scene, "ndwi")
|
|
46
|
+
plt.imsave("crop_ndvi.png", ndvi.array[0], cmap="RdYlGn", vmin=-1, vmax=1)
|
|
47
|
+
plt.imsave("crop_ndwi.png", ndwi.array[0], cmap="Blues", vmin=-1, vmax=1)
|
|
48
|
+
|
|
49
|
+
false_color = composite(scene, [4, 3, 2]) # NIR-R-G highlights vegetation red
|
|
50
|
+
plt.imsave("crop_falsecolor.png", np.transpose(false_color.array, (1, 2, 0)))
|
|
51
|
+
|
|
52
|
+
veg_frac = float((ndvi.array[0] > 0.3).mean())
|
|
53
|
+
print(f"NDVI mean {ndvi.array.mean():.3f}; vegetated fraction {veg_frac:.0%}")
|
|
54
|
+
print("Wrote crop_ndvi.png, crop_ndwi.png, crop_falsecolor.png")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
main()
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Runnable end-to-end example. No external files needed.
|
|
2
|
+
|
|
3
|
+
python examples/quickstart.py
|
|
4
|
+
|
|
5
|
+
Produces quickstart_map.html (interactive) and quickstart_map.png (static) in
|
|
6
|
+
the current directory, and demonstrates the chaining API, choropleth
|
|
7
|
+
classification, clustering, and offline streaming logic.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
import geopandas as gpd
|
|
14
|
+
|
|
15
|
+
import pygeospace as pgs
|
|
16
|
+
from pygeospace.analytics import choropleth, kmeans
|
|
17
|
+
from pygeospace.core.layer import StreamLayer
|
|
18
|
+
from pygeospace.streaming.source import BaseSource
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def make_data() -> gpd.GeoDataFrame:
|
|
22
|
+
rng = np.random.default_rng(7)
|
|
23
|
+
n = 500
|
|
24
|
+
lon = rng.normal(-0.18, 0.05, n) # around Accra
|
|
25
|
+
lat = rng.normal(5.60, 0.04, n)
|
|
26
|
+
return gpd.GeoDataFrame(
|
|
27
|
+
{"incidents": rng.integers(1, 100, n)},
|
|
28
|
+
geometry=gpd.points_from_xy(lon, lat),
|
|
29
|
+
crs="EPSG:4326",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def main() -> None:
|
|
34
|
+
gdf = make_data()
|
|
35
|
+
|
|
36
|
+
# 1) Build a map, classify a choropleth, and cluster.
|
|
37
|
+
m = pgs.Map(title="Accra incidents")
|
|
38
|
+
layer = m.add_vector(gdf, name="incidents")
|
|
39
|
+
choropleth(layer, "incidents", method="jenks", k=5)
|
|
40
|
+
|
|
41
|
+
clustered = kmeans(layer, n_clusters=6)
|
|
42
|
+
print("cluster counts:\n", clustered.data["cluster"].value_counts())
|
|
43
|
+
|
|
44
|
+
# 2) Chaining: buffer hotspots and add as a new red layer.
|
|
45
|
+
hot = layer.filter("incidents > 80").buffer(300).style(get_fill_color=[220, 30, 30, 90])
|
|
46
|
+
m.add_layer(hot)
|
|
47
|
+
|
|
48
|
+
m.fit().save("quickstart_map.html")
|
|
49
|
+
m.save("quickstart_map.png", dpi=150)
|
|
50
|
+
print("Wrote quickstart_map.html and quickstart_map.png")
|
|
51
|
+
|
|
52
|
+
# 3) Streaming logic (no network): feed synthetic messages.
|
|
53
|
+
stream = StreamLayer(name="buses", trail_length=10)
|
|
54
|
+
stream.add_alert("speed", 80, ">")
|
|
55
|
+
src = BaseSource(stream)
|
|
56
|
+
for speed in (30, 50, 95, 40):
|
|
57
|
+
alerts = src.handle_raw({"id": "bus_1", "lon": -0.18, "lat": 5.6, "speed": speed})
|
|
58
|
+
for a in alerts:
|
|
59
|
+
print(a)
|
|
60
|
+
print(f"stream received {src.received}, buffered {len(stream)}")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if __name__ == "__main__":
|
|
64
|
+
main()
|