demetrius 0.1.0a1__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.
- demetrius-0.1.0a1/LICENSE +21 -0
- demetrius-0.1.0a1/PKG-INFO +550 -0
- demetrius-0.1.0a1/README.md +511 -0
- demetrius-0.1.0a1/pyproject.toml +55 -0
- demetrius-0.1.0a1/setup.cfg +4 -0
- demetrius-0.1.0a1/src/demetrius/__init__.py +3 -0
- demetrius-0.1.0a1/src/demetrius/cli.py +471 -0
- demetrius-0.1.0a1/src/demetrius/clipper.py +129 -0
- demetrius-0.1.0a1/src/demetrius/cog.py +80 -0
- demetrius-0.1.0a1/src/demetrius/coverage.py +118 -0
- demetrius-0.1.0a1/src/demetrius/crs.py +112 -0
- demetrius-0.1.0a1/src/demetrius/data/__init__.py +1 -0
- demetrius-0.1.0a1/src/demetrius/data/projections/__init__.py +1 -0
- demetrius-0.1.0a1/src/demetrius/downloader.py +180 -0
- demetrius-0.1.0a1/src/demetrius/elevation_converter.py +196 -0
- demetrius-0.1.0a1/src/demetrius/filtering.py +113 -0
- demetrius-0.1.0a1/src/demetrius/inspector.py +142 -0
- demetrius-0.1.0a1/src/demetrius/manifest.py +175 -0
- demetrius-0.1.0a1/src/demetrius/merger.py +213 -0
- demetrius-0.1.0a1/src/demetrius/models.py +325 -0
- demetrius-0.1.0a1/src/demetrius/mosaicker.py +145 -0
- demetrius-0.1.0a1/src/demetrius/parser.py +90 -0
- demetrius-0.1.0a1/src/demetrius/priority.py +101 -0
- demetrius-0.1.0a1/src/demetrius/project_boundaries.py +246 -0
- demetrius-0.1.0a1/src/demetrius/projections.py +44 -0
- demetrius-0.1.0a1/src/demetrius/reprojector.py +104 -0
- demetrius-0.1.0a1/src/demetrius/snapper.py +289 -0
- demetrius-0.1.0a1/src/demetrius/sources.py +35 -0
- demetrius-0.1.0a1/src/demetrius/tnm.py +231 -0
- demetrius-0.1.0a1/src/demetrius.egg-info/PKG-INFO +550 -0
- demetrius-0.1.0a1/src/demetrius.egg-info/SOURCES.txt +35 -0
- demetrius-0.1.0a1/src/demetrius.egg-info/dependency_links.txt +1 -0
- demetrius-0.1.0a1/src/demetrius.egg-info/entry_points.txt +2 -0
- demetrius-0.1.0a1/src/demetrius.egg-info/requires.txt +13 -0
- demetrius-0.1.0a1/src/demetrius.egg-info/top_level.txt +1 -0
- demetrius-0.1.0a1/tests/test_core.py +373 -0
- demetrius-0.1.0a1/tests/test_integration.py +315 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 WSP-Internal-Repos
|
|
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,550 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: demetrius
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: High-resolution DEM assembly from USGS 3DEP data for engineering applications
|
|
5
|
+
Author-email: Thomas Williams <thomas.williams@wsp.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/wsp-watertech/demetrius
|
|
8
|
+
Project-URL: Repository, https://github.com/wsp-watertech/demetrius
|
|
9
|
+
Project-URL: Issues, https://github.com/wsp-watertech/demetrius/issues
|
|
10
|
+
Keywords: dem,usgs,3dep,elevation,hydraulic-modeling,gis
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Natural Language :: English
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
23
|
+
Requires-Python: >=3.12
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: click>=8.0
|
|
27
|
+
Requires-Dist: geopandas>=1.1.4
|
|
28
|
+
Requires-Dist: httpx>=0.28.1
|
|
29
|
+
Requires-Dist: pydantic>=2.0
|
|
30
|
+
Requires-Dist: pyarrow>=15.0
|
|
31
|
+
Requires-Dist: pyproj>=3.6
|
|
32
|
+
Requires-Dist: shapely>=2.0
|
|
33
|
+
Requires-Dist: gdal>=3.8
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
36
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
37
|
+
Requires-Dist: ruff>=0.1; extra == "dev"
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
|
|
40
|
+
# demetrius
|
|
41
|
+
|
|
42
|
+
High-resolution DEM assembly from USGS 3DEP data for engineering applications.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
demetrius process --aoi watershed.shp --project-bounds boundaries.gpkg --output dem.tif
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**demetrius** is a Python library and CLI tool that:
|
|
49
|
+
- Accepts a polygon Area of Interest (AOI)
|
|
50
|
+
- Queries the USGS TNM Access API for 3DEP 1m DEM tiles
|
|
51
|
+
- Selects the most recent available data
|
|
52
|
+
- Downloads only required tiles
|
|
53
|
+
- Mosaics them into a single Cloud-Optimized GeoTIFF (COG)
|
|
54
|
+
|
|
55
|
+
Perfect for hydraulic modeling workflows where data integrity and reproducibility are critical.
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install demetrius
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Or install from source for development:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
git clone https://github.com/wsp-watertech/demetrius.git
|
|
67
|
+
cd demetrius
|
|
68
|
+
pip install -e ".[dev]"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Installation with Conda (recommended):**
|
|
72
|
+
```bash
|
|
73
|
+
conda create -n demetrius python=3.12 gdal
|
|
74
|
+
conda activate demetrius
|
|
75
|
+
pip install demetrius
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### System Requirements
|
|
79
|
+
|
|
80
|
+
- **Python**: 3.12 or higher
|
|
81
|
+
- **GDAL**: Command-line tools (`gdalbuildvrt`, `gdalwarp`, `gdal_translate`)
|
|
82
|
+
|
|
83
|
+
## Quick Start
|
|
84
|
+
|
|
85
|
+
### Basic Usage
|
|
86
|
+
|
|
87
|
+
Process an AOI polygon and generate a DEM:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
demetrius process --aoi site.shp --output dem.tif --project-bounds boundaries.gpkg
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Required arguments:
|
|
94
|
+
- `--aoi`: Path to AOI geometry (shapefile, GeoJSON, or GeoPackage)
|
|
95
|
+
- `--output`: Output raster path
|
|
96
|
+
- `--project-bounds`: Path to project boundaries (shapefile, GeoPackage, etc.)
|
|
97
|
+
|
|
98
|
+
### Preview Tiles (No Download)
|
|
99
|
+
|
|
100
|
+
Inspect what tiles would be used without downloading:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
demetrius inspect --aoi site.shp --project-bounds boundaries.gpkg
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Output:
|
|
107
|
+
```
|
|
108
|
+
======================================================================
|
|
109
|
+
demetrius Inspection Report
|
|
110
|
+
======================================================================
|
|
111
|
+
|
|
112
|
+
Area of Interest:
|
|
113
|
+
Bounds: (-74.4500, 40.0500) → (-74.4100, 40.0900)
|
|
114
|
+
Buffer distance: 0 m
|
|
115
|
+
|
|
116
|
+
Tiles Discovered:
|
|
117
|
+
Total: 3 tile(s)
|
|
118
|
+
Datasets: 1
|
|
119
|
+
|
|
120
|
+
Dataset Priority Order (newest first):
|
|
121
|
+
[0] PA_3_County_South_Central_2018_D18
|
|
122
|
+
Tiles: 3
|
|
123
|
+
Publication dates: 2018-06-15 → 2018-06-15
|
|
124
|
+
|
|
125
|
+
Coverage Estimate:
|
|
126
|
+
AOI area: 0.00 sq degrees
|
|
127
|
+
Tile coverage: 0.01 sq degrees
|
|
128
|
+
Coverage: 100.0%
|
|
129
|
+
======================================================================
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Using demetrius as a Python Library
|
|
133
|
+
|
|
134
|
+
Beyond the CLI, you can use demetrius directly in Python scripts:
|
|
135
|
+
|
|
136
|
+
### Basic Example
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from pathlib import Path
|
|
140
|
+
from demetrius.models import AOI
|
|
141
|
+
from demetrius.project_boundaries import ProjectBoundaries
|
|
142
|
+
from demetrius.tnm import TNMTileSource
|
|
143
|
+
from demetrius.filtering import filter_tiles_by_aoi
|
|
144
|
+
from demetrius.priority import prioritize_datasets
|
|
145
|
+
from demetrius.downloader import TileDownloader
|
|
146
|
+
from demetrius.mosaicker import VRTMosaicker
|
|
147
|
+
from demetrius.merger import DatasetMerger
|
|
148
|
+
from demetrius.reprojector import Reprojector
|
|
149
|
+
from demetrius.snapper import Snapper
|
|
150
|
+
from demetrius.clipper import Clipper
|
|
151
|
+
from demetrius.elevation_converter import ElevationConverter
|
|
152
|
+
from demetrius.cog import COGGenerator
|
|
153
|
+
|
|
154
|
+
# Load AOI and project boundaries
|
|
155
|
+
aoi = AOI.from_file("site.shp")
|
|
156
|
+
proj_bounds = ProjectBoundaries.from_file("boundaries.gpkg")
|
|
157
|
+
|
|
158
|
+
# Query TNM for tiles
|
|
159
|
+
source = TNMTileSource()
|
|
160
|
+
tiles = source.search(aoi.bounds())
|
|
161
|
+
|
|
162
|
+
# Filter and prioritize
|
|
163
|
+
tiles = filter_tiles_by_aoi(tiles, aoi, proj_bounds)
|
|
164
|
+
tiles = prioritize_datasets(tiles)
|
|
165
|
+
|
|
166
|
+
# Download
|
|
167
|
+
downloader = TileDownloader()
|
|
168
|
+
for tile in tiles:
|
|
169
|
+
downloader.download_tile(tile)
|
|
170
|
+
|
|
171
|
+
# Mosaic & merge datasets
|
|
172
|
+
mosaicker = VRTMosaicker()
|
|
173
|
+
mosaic = mosaicker.create_mosaic_vrt(tiles)
|
|
174
|
+
|
|
175
|
+
merger = DatasetMerger()
|
|
176
|
+
merged = merger.merge(mosaic)
|
|
177
|
+
|
|
178
|
+
# Reproject & snap
|
|
179
|
+
reprojector = Reprojector()
|
|
180
|
+
snapper = Snapper()
|
|
181
|
+
output_crs = "EPSG:32111"
|
|
182
|
+
cellsize = 1.0
|
|
183
|
+
|
|
184
|
+
reprojected = reprojector.reproject(merged, output_crs, cellsize=cellsize)
|
|
185
|
+
snapped = snapper.snap(reprojected, cellsize=cellsize)
|
|
186
|
+
|
|
187
|
+
# Clip to AOI
|
|
188
|
+
clipper = Clipper()
|
|
189
|
+
clipped = clipper.clip(snapped, aoi)
|
|
190
|
+
|
|
191
|
+
# Convert units & generate COG
|
|
192
|
+
converter = ElevationConverter()
|
|
193
|
+
converted = converter.convert(clipped, "converted.tif", output_crs)
|
|
194
|
+
|
|
195
|
+
cog_gen = COGGenerator()
|
|
196
|
+
cog_gen.generate(converted, "dem.tif")
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Accessing Cached Data
|
|
200
|
+
|
|
201
|
+
Downloaded tiles are cached in `~/.demetrius/`:
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
from demetrius.downloader import TileDownloader
|
|
205
|
+
|
|
206
|
+
# Get cache directory
|
|
207
|
+
cache_dir = TileDownloader.get_default_data_dir()
|
|
208
|
+
print(f"Tiles cached in: {cache_dir}")
|
|
209
|
+
|
|
210
|
+
# List downloaded tiles
|
|
211
|
+
for tile_file in cache_dir.glob("**/*.tif"):
|
|
212
|
+
print(tile_file)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Inspect Workflow Without Downloading
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
from demetrius.inspector import InspectionReport
|
|
219
|
+
|
|
220
|
+
# Preview tiles that would be downloaded
|
|
221
|
+
report = InspectionReport.from_aoi_and_bounds("site.shp", "boundaries.gpkg")
|
|
222
|
+
print(report)
|
|
223
|
+
# Shows tiles, datasets, coverage %
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Working with Manifests
|
|
227
|
+
|
|
228
|
+
Manifests track tile provenance for reproducibility:
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
from demetrius.manifest import Manifest
|
|
232
|
+
|
|
233
|
+
# Load manifest from previous run
|
|
234
|
+
manifest = Manifest.load("dem.tif.manifest.json")
|
|
235
|
+
|
|
236
|
+
# Access tile information
|
|
237
|
+
print(f"Tiles used: {len(manifest.tiles)}")
|
|
238
|
+
print(f"Buffer distance: {manifest.buffer} m")
|
|
239
|
+
print(f"Output CRS: {manifest.output_crs}")
|
|
240
|
+
|
|
241
|
+
# Inspect original AOI geometry
|
|
242
|
+
print(manifest.aoi.bounds())
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Processing Modes
|
|
246
|
+
|
|
247
|
+
### Full Pipeline (default)
|
|
248
|
+
Runs complete workflow: discover → download → mosaic → reproject → clip → COG
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
demetrius process --aoi site.shp --output dem.tif
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Download Only
|
|
255
|
+
Stop after downloading tiles; saves manifest for later processing
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
demetrius process --aoi site.shp --mode download-only
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Outputs: `dem.tif.manifest.json` and tiles in `~/.demetrius/`
|
|
262
|
+
|
|
263
|
+
### Process Only
|
|
264
|
+
Use previously downloaded tiles; skip TNM query and download
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
demetrius process --aoi site.shp --mode process-only
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Requires manifest from previous `download-only` run (named `{output}.manifest.json`).
|
|
271
|
+
|
|
272
|
+
### Batch Processing Multiple Areas
|
|
273
|
+
Process multiple polygons from a vector file by looping through features. No new functionality needed—just iterate and process each polygon separately:
|
|
274
|
+
|
|
275
|
+
```python
|
|
276
|
+
import geopandas as gpd
|
|
277
|
+
from src.demetrius.cli import process
|
|
278
|
+
from pathlib import Path
|
|
279
|
+
|
|
280
|
+
# Load polygons from GeoPackage, Shapefile, GeoJSON, etc.
|
|
281
|
+
polygons = gpd.read_file("study_areas.gpkg")
|
|
282
|
+
|
|
283
|
+
output_dir = Path("./output_dems")
|
|
284
|
+
output_dir.mkdir(exist_ok=True)
|
|
285
|
+
|
|
286
|
+
for idx, row in polygons.iterrows():
|
|
287
|
+
aoi_name = row.get("name", f"area_{idx}")
|
|
288
|
+
|
|
289
|
+
# Create temporary GeoJSON for this feature
|
|
290
|
+
feature_path = output_dir / f"{aoi_name}_aoi.geojson"
|
|
291
|
+
gpd.GeoDataFrame([row], crs=polygons.crs).to_file(feature_path, driver="GeoJSON")
|
|
292
|
+
|
|
293
|
+
# Process this feature
|
|
294
|
+
output_dem = output_dir / f"{aoi_name}_dem.tif"
|
|
295
|
+
process(
|
|
296
|
+
aoi=str(feature_path),
|
|
297
|
+
output=str(output_dem),
|
|
298
|
+
output_crs="EPSG:32111",
|
|
299
|
+
buffer=500,
|
|
300
|
+
cellsize=1.0
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# Manifest saved as {output}.manifest.json for reproducibility
|
|
304
|
+
print(f"✓ Generated {output_dem} with metadata in {output_dem}.manifest.json")
|
|
305
|
+
|
|
306
|
+
# Clean up temporary feature file
|
|
307
|
+
feature_path.unlink()
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Each DEM gets its own manifest file (`{name}_dem.tif.manifest.json`) recording tiles, buffer, cellsize, and other parameters for reproducibility.
|
|
311
|
+
|
|
312
|
+
## Options
|
|
313
|
+
|
|
314
|
+
```
|
|
315
|
+
--aoi PATH Path to AOI (shapefile, GeoJSON, GeoPackage)
|
|
316
|
+
--output PATH Output file path [default: dem.tif]
|
|
317
|
+
--output-crs EPSG:CODE Target CRS (e.g., EPSG:32618) [default: auto-detect]
|
|
318
|
+
--buffer METERS Buffer for tile discovery and clipping [default: 0]
|
|
319
|
+
--cellsize FLOAT Output cellsize in target CRS units [default: 1m converted to CRS units]
|
|
320
|
+
--no-snap Disable grid snapping [default: enabled]
|
|
321
|
+
--require-full-coverage Fail if AOI not fully covered [default: True]
|
|
322
|
+
--mode {full,download-only,process-only}
|
|
323
|
+
Processing mode [default: full]
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Default Cellsize and Grid Snapping
|
|
327
|
+
|
|
328
|
+
By default, DEMs are generated with:
|
|
329
|
+
- **Cellsize**: 1 meter, automatically converted to output CRS units
|
|
330
|
+
- UTM zones (meters): 1.0 meter
|
|
331
|
+
- State Plane (feet): 3.28083... feet
|
|
332
|
+
- Other units: automatic conversion
|
|
333
|
+
- **Grid snapping**: Enabled, aligning to multiples of the cellsize
|
|
334
|
+
|
|
335
|
+
This means all DEMs (even with different CRS selections) have equivalent 1-meter resolution in their native units. The actual pixel size appears different in different CRS (3.28... feet vs 1m) but represents the same geographic resolution.
|
|
336
|
+
|
|
337
|
+
To override:
|
|
338
|
+
- `--cellsize VALUE`: Use explicit cellsize instead of 1m default
|
|
339
|
+
- `--no-snap`: Disable grid snapping (rarely needed)
|
|
340
|
+
|
|
341
|
+
Both cellsize resampling and grid snapping use the same computed value for consistent alignment.
|
|
342
|
+
|
|
343
|
+
## AOI Geometry Formats
|
|
344
|
+
|
|
345
|
+
- **Shapefiles**: `.shp` + `.shx` + `.dbf`
|
|
346
|
+
- **GeoJSON**: `.geojson`
|
|
347
|
+
- **GeoPackage**: `.gpkg`
|
|
348
|
+
- **GIS formats**: Any format readable by geopandas (WKT, etc.)
|
|
349
|
+
|
|
350
|
+
Multi-feature files automatically unionize to single geometry.
|
|
351
|
+
|
|
352
|
+
## Output
|
|
353
|
+
|
|
354
|
+
### Cloud-Optimized GeoTIFF (COG)
|
|
355
|
+
|
|
356
|
+
Final output is COG-compliant GeoTIFF with:
|
|
357
|
+
- Internal tiling (512x512)
|
|
358
|
+
- Overviews for efficient zoom
|
|
359
|
+
- DEFLATE compression
|
|
360
|
+
- Valid NODATA values
|
|
361
|
+
- Georeferencing in output CRS
|
|
362
|
+
|
|
363
|
+
### Manifest
|
|
364
|
+
|
|
365
|
+
JSON file recording all inputs and selected tiles for reproducibility:
|
|
366
|
+
|
|
367
|
+
```json
|
|
368
|
+
{
|
|
369
|
+
"aoi": {
|
|
370
|
+
"bounds": {"min_x": -74.45, "min_y": 40.05, "max_x": -74.41, "max_y": 40.09},
|
|
371
|
+
"crs": "EPSG:4326"
|
|
372
|
+
},
|
|
373
|
+
"buffer": 1000,
|
|
374
|
+
"cellsize": 10.5,
|
|
375
|
+
"tile_count": 3,
|
|
376
|
+
"tiles": [
|
|
377
|
+
{
|
|
378
|
+
"dataset_id": "PA_3_County_South_Central_2018_D18",
|
|
379
|
+
"tile_id": "x38y448",
|
|
380
|
+
"priority": 0,
|
|
381
|
+
"url": "https://...",
|
|
382
|
+
"bounds": {...},
|
|
383
|
+
"local_path": "/home/user/.demetrius/dataset_PA_3/tile_x38y448.tif"
|
|
384
|
+
},
|
|
385
|
+
...
|
|
386
|
+
]
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
Note: `cellsize` is only included if specified; otherwise the field is omitted.
|
|
391
|
+
|
|
392
|
+
## How It Works
|
|
393
|
+
|
|
394
|
+
### 1. AOI Preparation
|
|
395
|
+
|
|
396
|
+
Original geometry is buffered by `--buffer` distance for tile discovery AND for final clipping.
|
|
397
|
+
|
|
398
|
+
The buffer is applied in the output CRS coordinate space to ensure accurate meter-based (or relevant units) buffering. This means:
|
|
399
|
+
- If no `--output-crs` is specified, it's auto-detected based on initial unbuffered tile discovery
|
|
400
|
+
- The buffered geometry is then used for both tile filtering and final clip operations
|
|
401
|
+
|
|
402
|
+
### 1b. Output Resolution (Cellsize)
|
|
403
|
+
|
|
404
|
+
If `--cellsize` is specified, the final DEM will be resampled to that resolution during reprojection. The cellsize units are in the target CRS units (e.g., meters for UTM zones, feet for State Plane feet zones).
|
|
405
|
+
|
|
406
|
+
If `--cellsize` is not specified, native tile resolution is preserved (typically 1 meter for USGS 3DEP 1m DEM).
|
|
407
|
+
|
|
408
|
+
### 2. TNM Query
|
|
409
|
+
|
|
410
|
+
Tiles are discovered using [USGS TNM Access API](https://tnmaccess.nationalmap.gov/):
|
|
411
|
+
|
|
412
|
+
```
|
|
413
|
+
https://tnmaccess.nationalmap.gov/api/v1/products
|
|
414
|
+
?bbox=minx,miny,maxx,maxy
|
|
415
|
+
&datasets=Digital Elevation Model (DEM) 1 meter
|
|
416
|
+
&prodFormats=GeoTIFF
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### 3. Tile Selection
|
|
420
|
+
|
|
421
|
+
**Filtering**: Tiles are filtered to those intersecting the buffered AOI.
|
|
422
|
+
|
|
423
|
+
**Prioritization**: Datasets are ranked by `publication_date` (newest first). When the same tile appears in multiple datasets, only the newest version is kept.
|
|
424
|
+
|
|
425
|
+
**Coverage Validation**: Original (unbuffered) AOI must be fully covered. Fails with `--require-full-coverage=true`.
|
|
426
|
+
|
|
427
|
+
### 4. Parallel Download
|
|
428
|
+
|
|
429
|
+
Tiles are downloaded in parallel (configurable worker pool) with automatic retry on failure.
|
|
430
|
+
|
|
431
|
+
### 5. Mosaicking
|
|
432
|
+
|
|
433
|
+
Tiles are merged using GDAL Virtual Raster (VRT) for efficiency:
|
|
434
|
+
|
|
435
|
+
```bash
|
|
436
|
+
gdalbuildvrt dataset_priority_0.vrt tile1.tif tile2.tif ...
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
No in-memory raster loading; efficient for thousands of tiles.
|
|
440
|
+
|
|
441
|
+
### 6. Multi-CRS Handling
|
|
442
|
+
|
|
443
|
+
If tiles span multiple UTM zones, each zone is mosaicked separately, then reprojected to common zone.
|
|
444
|
+
|
|
445
|
+
### 7. Reprojection
|
|
446
|
+
|
|
447
|
+
All tiles reprojected to target CRS (auto-detected or user-specified), with optional resampling to specified cellsize:
|
|
448
|
+
|
|
449
|
+
```bash
|
|
450
|
+
gdalwarp -t_srs EPSG:32618 -r bilinear -multi ...
|
|
451
|
+
# With cellsize:
|
|
452
|
+
gdalwarp -t_srs EPSG:32618 -tr 10.0 10.0 -r bilinear -multi ...
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
- **Resampling**: Minimum bilinear (nearest neighbor forbidden)
|
|
456
|
+
- **Multi-threaded**: Uses all available CPU cores
|
|
457
|
+
- **Cellsize**: Optional target resolution (applied via `-tr` flag)
|
|
458
|
+
|
|
459
|
+
### 8. Clipping
|
|
460
|
+
|
|
461
|
+
Final raster clipped to original AOI using shapely geometry:
|
|
462
|
+
|
|
463
|
+
```bash
|
|
464
|
+
gdalwarp -cutline aoi.geojson -crop_to_cutline ...
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### 9. Grid Snapping (Optional, Default Enabled)
|
|
468
|
+
|
|
469
|
+
If snapping is enabled (default), the raster is snapped to a regular grid where all pixel boundaries are exact multiples of the snap distance:
|
|
470
|
+
|
|
471
|
+
```bash
|
|
472
|
+
# Snapping distance = 1m converted to output CRS units
|
|
473
|
+
gdalwarp -te <snapped_minx> <snapped_miny> <snapped_maxx> <snapped_maxy> ...
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
**CRS-Aware Snapping:**
|
|
477
|
+
- **Meters (UTM/projected)**: Snap distance is 1.0 meter
|
|
478
|
+
- **US Survey Feet (State Plane)**: Snap distance is 3.28083... feet
|
|
479
|
+
- **Other units**: Automatically converted from 1 meter
|
|
480
|
+
|
|
481
|
+
This ensures:
|
|
482
|
+
- Pixel boundaries align to multiples of snap distance in the output CRS
|
|
483
|
+
- Consistent reproducible output across runs
|
|
484
|
+
- Compatibility with downstream grid-based processing
|
|
485
|
+
- CRS-native coordinate precision
|
|
486
|
+
|
|
487
|
+
Snapping can be disabled with `--no-snap` or overridden with explicit `--cellsize`.
|
|
488
|
+
|
|
489
|
+
### 10. COG Output
|
|
490
|
+
|
|
491
|
+
Result converted to Cloud-Optimized GeoTIFF:
|
|
492
|
+
|
|
493
|
+
```bash
|
|
494
|
+
gdal_translate -of COG -co COMPRESS=DEFLATE ...
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
## Examples
|
|
498
|
+
|
|
499
|
+
### Multi-county with Specific CRS
|
|
500
|
+
|
|
501
|
+
```bash
|
|
502
|
+
demetrius process \
|
|
503
|
+
--aoi counties.geojson \
|
|
504
|
+
--output counties_dem.tif \
|
|
505
|
+
--output-crs EPSG:2272 \
|
|
506
|
+
--buffer 5000
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Resampled DEM with Specific Resolution
|
|
510
|
+
|
|
511
|
+
```bash
|
|
512
|
+
demetrius process \
|
|
513
|
+
--aoi site.shp \
|
|
514
|
+
--output dem_10m.tif \
|
|
515
|
+
--cellsize 10.0
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
With this command, the output DEM will be resampled to 10-meter resolution in the target CRS units, and snapped to multiples of 10.0.
|
|
519
|
+
|
|
520
|
+
### Snapping with CRS-Specific Units
|
|
521
|
+
|
|
522
|
+
```bash
|
|
523
|
+
# EPSG:32111 (meters) - snaps to multiples of 1.0 meter
|
|
524
|
+
demetrius process \
|
|
525
|
+
--aoi site.shp \
|
|
526
|
+
--output dem_utm.tif \
|
|
527
|
+
--output-crs EPSG:32111
|
|
528
|
+
|
|
529
|
+
# EPSG:2286 (US survey feet) - snaps to multiples of 3.28... feet
|
|
530
|
+
demetrius process \
|
|
531
|
+
--aoi site.shp \
|
|
532
|
+
--output dem_feet.tif \
|
|
533
|
+
--output-crs EPSG:2286
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
Both commands snap to the equivalent of 1 meter, but in their respective CRS units. This ensures consistent, reproducible grid alignment regardless of CRS.
|
|
537
|
+
|
|
538
|
+
### Two-stage processing (separate network/compute)
|
|
539
|
+
|
|
540
|
+
**Stage 1: Discovery & Download (on server with network)**
|
|
541
|
+
```bash
|
|
542
|
+
demetrius process --aoi site.shp --mode download-only --output dem.tif --project-bounds boundaries.gpkg
|
|
543
|
+
# Output: dem.tif.manifest.json, ~/.demetrius/
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
**Stage 2: Processing (offline or different machine)**
|
|
547
|
+
```bash
|
|
548
|
+
# Copy dem.tif.manifest.json and tiles to processing machine
|
|
549
|
+
demetrius process --aoi site.shp --mode process-only --output dem.tif --project-bounds boundaries.gpkg
|
|
550
|
+
```
|