Marshall Frith

Projects

Live Bathymetry for Google Earth, ATAK, and a Chartplotter

Pick water, pull real NOAA depth data, get a colour-ramped depth map. One render, three containers, because every viewer disagrees about what KML means.

Pick an area of water and pull the real, charted depths for it: NOAA ENC soundings by default, with BlueTopo survey grids, GMRT global bathymetry, and USACE eHydro channel surveys as alternate sources. The sparse points get interpolated into a colour-ramped depth map and exported for whatever you navigate with: Google Earth, ATAK, QGIS, a Garmin chartplotter, or a printable PDF. It is one Python file, 12,049 lines, with requests as the only hard dependency, and it runs as either a local map UI or a CLI.

No account, no API key. Every source is a public government or academic endpoint.

One render, three containers

The constraint that shaped the exports: every viewer renders the "same" KML differently. The first ATAK export looked perfect in Google Earth. In ATAK, the water-mask polygons drew as stray blue lines because ATAK ignores KML folder visibility, and the ground-overlay PNG rendered with wrong colours. So ATAK gets a points-only KMZ plus a GeoTIFF twin of the exact same pixels, Google Earth gets the full overlay KMZ, and QGIS gets the GeoTIFF. Same render, three containers, because the alternative is an export that is quietly wrong on someone else's screen.

The subtlest bug in the project lived here too. Sizing the grid with ceil() looks harmless, but it rounds the pixel count up past the declared corner of the overlay box, so the image is a fraction of a cell larger than the box that claims to contain it, and every viewer silently stretches it into place.

python
# Pin north/east to the *actual* pixel-grid extent, or a GroundOverlay's
# LatLonBox ends up a fraction of a cell smaller than its raster.
north = south + nrows * dlat
east = west + ncols * dlon

Pessimistic is the right kind of wrong

Depths stay at charted MLLW, the conservative datum. The current tide is shown as text but never recolours the map, because ENC soundings are deliberately shoal-biased and a depth map should inherit that bias, not launder it. The optional composite mode goes further and keeps the least water any source ever measured for a cell.

The auto-computed "seaward" bearing is a good example of knowing a number's worth: measured at St. Augustine inlet it pointed 180 degrees, into the scour hole in the inlet throat, when the Atlantic is nearer 100. Off by 60 to 80 degrees. So it is printed as a suggestion and never applied automatically; you confirm it with a flag.

Living with other people's servers

Everything HTTP goes through a disk cache with retry underneath. Offline mode serves any cached copy regardless of age and fails a miss immediately instead of burning 35 seconds of retries, and what it deliberately does not do is interpolate from a neighbouring area and pretend. Field lessons that ended up as code:

  • Both of api.weather.gov's structured forecast endpoints answer "Marine Forecast Not Supported" for marine zones, confirmed live. The marine weather panel parses the legacy free-text Coastal Waters Forecast bulletin, because that is the one that works.
  • eHydro's index keeps every historical survey: one stretch of the St. Johns River returned 88 overlapping records. Dedupe to the newest survey per channel, cap the ZIP downloads at five.
  • A wide area at a fine cell size silently allocates hundreds of megabytes and then spends minutes in the contour pass, which looks exactly like a hang. A 1.5 million cell cap coarsens the grid instead, and says so.
  • The geofence tracer, on its first attempt, treated land as missing data: channels with land on both banks produced broken contours and the main channel got discarded, confining a hypothetical USV to a puddle. Learned by rendering the first attempt and looking at it.

Run it yourself

bash
python3 install.py --no-icon
bash
python3 noaa_soundings_kml.py

That opens the map UI at http://127.0.0.1:8642, local only unless you ask for LAN. The CLI does the same from a terminal:

bash
python3 noaa_soundings_kml.py --point 29.895 -81.284 --radius 3 --out inlet

There are 828 offline tests, and a separate opt-in live suite whose docstring says the honest thing: a failure there means go look, not the build is broken, because it is testing other people's servers.

What I would tell you before you start: golden-file tests earn their keep the day a cosmetic-looking diff appears. Those files get parsed by ATAK, Garmin, QGIS, and Mission Planner, none of which this repo can run, so a diff you cannot explain line by line is a bug report from a device you do not own.

Comments

Plain text only. Held for review before it appears.