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.
Files changed (54) hide show
  1. pygeospace-0.6.1/LICENSE +21 -0
  2. pygeospace-0.6.1/MANIFEST.in +4 -0
  3. pygeospace-0.6.1/PKG-INFO +249 -0
  4. pygeospace-0.6.1/README.md +171 -0
  5. pygeospace-0.6.1/ROADMAP.md +83 -0
  6. pygeospace-0.6.1/examples/crop_health.py +58 -0
  7. pygeospace-0.6.1/examples/quickstart.py +64 -0
  8. pygeospace-0.6.1/examples/terrain_3d.py +48 -0
  9. pygeospace-0.6.1/examples/workflow_demo.py +162 -0
  10. pygeospace-0.6.1/pygeospace/__init__.py +49 -0
  11. pygeospace-0.6.1/pygeospace/analytics/__init__.py +32 -0
  12. pygeospace-0.6.1/pygeospace/analytics/clustering.py +117 -0
  13. pygeospace-0.6.1/pygeospace/analytics/contours.py +86 -0
  14. pygeospace-0.6.1/pygeospace/analytics/raster.py +157 -0
  15. pygeospace-0.6.1/pygeospace/analytics/spatial.py +74 -0
  16. pygeospace-0.6.1/pygeospace/analytics/spectral.py +133 -0
  17. pygeospace-0.6.1/pygeospace/analytics/stats.py +143 -0
  18. pygeospace-0.6.1/pygeospace/api/__init__.py +5 -0
  19. pygeospace-0.6.1/pygeospace/api/rest.py +80 -0
  20. pygeospace-0.6.1/pygeospace/cli.py +250 -0
  21. pygeospace-0.6.1/pygeospace/core/__init__.py +15 -0
  22. pygeospace-0.6.1/pygeospace/core/camera3d.py +137 -0
  23. pygeospace-0.6.1/pygeospace/core/layer.py +317 -0
  24. pygeospace-0.6.1/pygeospace/core/map.py +547 -0
  25. pygeospace-0.6.1/pygeospace/core/view.py +79 -0
  26. pygeospace-0.6.1/pygeospace/io/__init__.py +5 -0
  27. pygeospace-0.6.1/pygeospace/io/reader.py +205 -0
  28. pygeospace-0.6.1/pygeospace/plugins/__init__.py +5 -0
  29. pygeospace-0.6.1/pygeospace/plugins/manager.py +80 -0
  30. pygeospace-0.6.1/pygeospace/render/__init__.py +6 -0
  31. pygeospace-0.6.1/pygeospace/render/deckgl.py +86 -0
  32. pygeospace-0.6.1/pygeospace/render/flow.py +88 -0
  33. pygeospace-0.6.1/pygeospace/render/pyvista3d.py +449 -0
  34. pygeospace-0.6.1/pygeospace/render/raster2d.py +147 -0
  35. pygeospace-0.6.1/pygeospace/streaming/__init__.py +5 -0
  36. pygeospace-0.6.1/pygeospace/streaming/source.py +134 -0
  37. pygeospace-0.6.1/pygeospace/web/__init__.py +5 -0
  38. pygeospace-0.6.1/pygeospace/web/server.py +53 -0
  39. pygeospace-0.6.1/pygeospace.egg-info/PKG-INFO +249 -0
  40. pygeospace-0.6.1/pygeospace.egg-info/SOURCES.txt +52 -0
  41. pygeospace-0.6.1/pygeospace.egg-info/dependency_links.txt +1 -0
  42. pygeospace-0.6.1/pygeospace.egg-info/entry_points.txt +2 -0
  43. pygeospace-0.6.1/pygeospace.egg-info/requires.txt +64 -0
  44. pygeospace-0.6.1/pygeospace.egg-info/top_level.txt +1 -0
  45. pygeospace-0.6.1/pyproject.toml +91 -0
  46. pygeospace-0.6.1/setup.cfg +4 -0
  47. pygeospace-0.6.1/tests/conftest.py +80 -0
  48. pygeospace-0.6.1/tests/test_3d.py +168 -0
  49. pygeospace-0.6.1/tests/test_advanced.py +127 -0
  50. pygeospace-0.6.1/tests/test_analytics.py +119 -0
  51. pygeospace-0.6.1/tests/test_convenience.py +159 -0
  52. pygeospace-0.6.1/tests/test_core.py +87 -0
  53. pygeospace-0.6.1/tests/test_io.py +57 -0
  54. pygeospace-0.6.1/tests/test_render.py +131 -0
@@ -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,4 @@
1
+ include README.md ROADMAP.md LICENSE pyproject.toml
2
+ recursive-include pygeospace *.py
3
+ recursive-include examples *.py
4
+ recursive-include tests *.py
@@ -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
+ | ![terrain](docs/gallery/terrain.png) 3D terrain (elevation colormap) | ![mountain](docs/gallery/terrain_mountain.png) Terrain from GeoTIFF |
246
+ | ![pointcloud](docs/gallery/pointcloud_classified.png) Classified point cloud | ![ndvi](docs/gallery/ndvi.png) NDVI spectral index |
247
+ | ![true_color](docs/imagery/true_color.png) True color satellite imagery | ![false_color_ir](docs/imagery/false_color_ir.png) False color infrared (FCIR) |
248
+ | ![agriculture](docs/imagery/agriculture.png) Agriculture/Vegetation analysis | ![nyc_map](docs/imagery/nyc_map.png) 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
+ | ![terrain](docs/gallery/terrain.png) 3D terrain (elevation colormap) | ![mountain](docs/gallery/terrain_mountain.png) Terrain from GeoTIFF |
168
+ | ![pointcloud](docs/gallery/pointcloud_classified.png) Classified point cloud | ![ndvi](docs/gallery/ndvi.png) NDVI spectral index |
169
+ | ![true_color](docs/imagery/true_color.png) True color satellite imagery | ![false_color_ir](docs/imagery/false_color_ir.png) False color infrared (FCIR) |
170
+ | ![agriculture](docs/imagery/agriculture.png) Agriculture/Vegetation analysis | ![nyc_map](docs/imagery/nyc_map.png) 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()