CalTopo-native GeoJSON folders, and shaded relief for map backdrops - #5
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two modules, plus the small changes that let them share existing code.
caltopo.py— one checkbox per layer, not per segmentCalTopo 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 theobjects 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 hasneither — 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), keepsthe third, and reopens the file as 3-D with
z=0. Harmless for CalTopo, which isthe consumer this file is for;
coord_len=2produces a QGIS-clean strict file.Both behaviours are pinned by tests, so neither can drift silently.
relief.py— shaded relief on an arbitrary display griddem.hillshadeshades a DEM you already hold; this shades a display grid straightfrom 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_demwas biasing slope ~1.8° and halving plan curvature.Verification
test_caltopo.py,test_relief.py,and additions to the export/plot suites), entirely offline.
import stormscapeclean, all 18 subcommands still parse.🤖 Generated with Claude Code