Skip to content

CalTopo-native GeoJSON folders, and shaded relief for map backdrops - #5

Merged
scottwmccoy merged 4 commits into
mainfrom
caltopo-export
Aug 19, 2026
Merged

CalTopo-native GeoJSON folders, and shaded relief for map backdrops#5
scottwmccoy merged 4 commits into
mainfrom
caltopo-export

Conversation

@scottwmccoy

Copy link
Copy Markdown
Owner

Two modules, plus the small changes that let them share existing code.

caltopo.py — one checkbox per layer, not per segment

CalTopo imports plain GeoJSON flat: every LineString becomes its own map object
with its own checkbox. Hand it a debris-flow network of 1,400 segments and the
sidebar gets 1,400 rows, with no way to switch the layer off as a unit.

This writes CalTopo's own container structure instead — a geometry-less Folder
feature, with every shape naming it in folderId. One checkbox per folder, and the
objects stay individually clickable for their attributes.

A deliberate spec deviation, documented rather than hidden. CalTopo writes
4-element positions — [lon, lat, elevation, timestamp], zero-filled when it has
neither — while RFC 7946 says SHOULD NOT go past three. Matching CalTopo means GDAL
warns on the way back (too many members in array ... At most 3 are handled), keeps
the third, and reopens the file as 3-D with z=0. Harmless for CalTopo, which is
the consumer this file is for; coord_len=2 produces a QGIS-clean strict file.
Both behaviours are pinned by tests, so neither can drift silently.

relief.py — shaded relief on an arbitrary display grid

dem.hillshade shades a DEM you already hold; this shades a display grid straight
from a local 3DEP tile store, which is what a map figure actually needs. It sits in
stormscape rather than downstream because a terrain backdrop is not specific to one
science question — firescape draws hazard over it and tracescape draws change over
it, and neither should own the other's hillshade.

Hillshading differentiates elevation, so it is the loudest possible display of a
resampling mistake. The module's rules are each paid for: warp every tile exactly
once from its native grid with bilinear (a decimating pre-pass cost 2.2 m
elevation RMS
), and mosaic elevations into one continuous surface before shading,
since shading tiles separately prints a hard seam at every tile edge. This is the
same error class the package removed on 2026-07-31, when a nearest warp inside
py3dep.get_dem was biasing slope ~1.8° and halving plan curvature.

Verification

  • 612 tests pass (557 → 612; 55 new across test_caltopo.py, test_relief.py,
    and additions to the export/plot suites), entirely offline.
  • import stormscape clean, all 18 subcommands still parse.
  • No secrets, personal paths, or large files in the diff.

🤖 Generated with Claude Code

scottwmccoy and others added 4 commits August 17, 2026 11:23
Map labels fail three ways that fixed offsets cannot fix - they land on
each other in clusters, they run off the frame (clip_on cut them in
half), and once displaced far enough to read they stop being attached to
anything. Labeller places each one at the first offset clearing every
label already placed, every registered marker, and the panel frame, and
draws an explicit leader once the offset exceeds the label's own box
width. That threshold is the reason annotate's arrows looked absent:
they are drawn from the text bounding box, which at a short offset
already reaches the anchor.

Also public: shorten() truncating at a word boundary, interior_point()
anchoring a line label away from the frame, and longest_per_name(), all
of which firescape had grown its own copy of. add_reference's place
labels now go through a Labeller.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CalTopo imports GeoJSON flat: a 1,400-segment debris-flow network arrives as
1,400 independent map objects with 1,400 checkboxes and no way to hide the
layer as a unit. That is the complaint this module answers.

CalTopo's own container is a geometry-less feature with class=Folder; shapes
that name it in folderId land inside it, and the folder gets a single
checkbox. stormscape.caltopo writes that structure from GeoDataFrames --
Layer per folder, classify() for per-feature colours on the USGS-style
breaks, chosen columns folded into the description (CalTopo has no attribute
table, so that is the only channel into the field), metric-CRS simplify, and
summary() to read back what CalTopo will actually show before a trip.

Two findings worth keeping. CalTopo writes 4-element positions
[lon, lat, 0, 0]; matching it costs a GDAL warning and reopens the file as
3-D geometry with z=0 -- harmless in CalTopo, visible in QGIS, and coord_len=2
is the way out. And GDAL keeps the null-geometry folder rows on read rather
than dropping them, so a QGIS check shows one extra empty row per folder.

24 tests; suite 596 passed, 1 skipped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Second caller (firescape's BRISK severity layers), so the private helper
becomes API. Its docstring now says why the ColorInterp.alpha tag matters:
without it CalTopo paints the layer's whole rectangle over the basemap
instead of letting the untouched cells through. One test covers the tag.

Suite 597 passed, 1 skipped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A terrain backdrop is not specific to a science question. firescape draws
hazard over it and tracescape (the imagery change-detection tool split out
of firescape today) draws change over it, and neither should have to import
the other to get a hillshade.

dem.hillshade shades a DEM you already hold; relief.shaded_relief shades an
arbitrary display grid straight from the 3DEP tile store, which is what a map
figure actually needs. The four resampling rules in its docstring were each
paid for in a real figure and come across intact, with their 13 tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@scottwmccoy
scottwmccoy merged commit 16aab33 into main Aug 19, 2026
2 checks passed
@scottwmccoy
scottwmccoy deleted the caltopo-export branch August 19, 2026 17:18
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.

1 participant