Skip to content

Add USGS 3DEP as a new DEM data source#24

Merged
scottstanie merged 11 commits intomasterfrom
claude/add-usgs-lidar-source-Q4jB5
Feb 16, 2026
Merged

Add USGS 3DEP as a new DEM data source#24
scottstanie merged 11 commits intomasterfrom
claude/add-usgs-lidar-source-Q4jB5

Conversation

@scottstanie
Copy link
Copy Markdown
Owner

Add support for downloading elevation data from the USGS 3D Elevation
Program (3DEP), which provides high-resolution LiDAR-derived DEMs
(up to 1m) across the US.

Data is fetched via the 3DEP ImageServer exportImage REST endpoint
using requests (no new dependencies). Large areas are automatically
chunked into manageable tiles. Vertical datum conversion from NAVD88
to WGS84 ellipsoidal heights uses GDAL (same optional dep as COP).

Usage: sardem --bbox -105.1 40.0 -105.0 40.1 --data-source 3DEP

https://claude.ai/code/session_013H2JBDQ3gKG8JgdeDUiAi6

scottstanie and others added 11 commits February 5, 2026 17:46
The NISAR DEM is a Copernicus-derived DEM prepared by JPL for the NISAR
mission with EGM2008-to-WGS84 conversion pre-applied and ocean gaps
filled. It is accessed via a hierarchical VRT and requires NASA Earthdata
credentials for authentication.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add support for downloading elevation data from the USGS 3D Elevation
Program (3DEP), which provides high-resolution LiDAR-derived DEMs
(up to 1m) across the US.

Data is fetched via the 3DEP ImageServer exportImage REST endpoint
using requests (no new dependencies). Large areas are automatically
chunked into manageable tiles. Vertical datum conversion from NAVD88
to WGS84 ellipsoidal heights uses GDAL (same optional dep as COP).

Usage: sardem --bbox -105.1 40.0 -105.0 40.1 --data-source 3DEP

https://claude.ai/code/session_013H2JBDQ3gKG8JgdeDUiAi6
…s to source pixel grid

When gdal.Warp is called with integer-degree outputBounds (e.g. -104,31,-103,32),
the output pixel centers land exactly on the boundary between two source pixels.
Nearest-neighbour resampling must break a tie, and the result depends on
floating-point rounding of the source VRT origin. The COP and NISAR global VRTs
have different origins (-179 vs -180 degrees), causing the rounding to go
opposite ways -- COP picks source pixel N while NISAR picks N+1, producing a
systematic 1-pixel shift in both X and Y (up to 25m elevation error).

The fix snaps outputBounds outward to the nearest source pixel *edge* via
utils.align_bounds_to_pixel_grid(). This makes output pixel centers coincide
with source pixel centers, so the warp becomes a pure pixel copy -- bit-for-bit
identical to reading the raw tiles. The function is idempotent: bounds already
on pixel edges pass through unchanged.

Result: COP vs NISAR |max| drops from 25.1m to 0.008m (sub-cm, limited by the
difference between GDAL's and JPL's EGM2008-to-WGS84 conversion). For a 1-degree
integer bbox the output grows from 3600x3600 to 3601x3601 to include boundary pixels.

Also adds debug_pixel_shift.py which demonstrates the issue and validates the fix.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@scottstanie scottstanie merged commit d49354e into master Feb 16, 2026
3 checks passed
@scottstanie scottstanie deleted the claude/add-usgs-lidar-source-Q4jB5 branch February 16, 2026 18:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants