UI improvements: split screen, faster profile, smoother tiles#59
Merged
Conversation
Frontend: - Two-slot tile model: pending layer loads at full opacity behind active; atomic swap on 'load' event eliminates mixed-tile seam artifacts - Loading spinner driven by tileloadstart/load events across all layers (basemaps + raster), with a ref counter for concurrent loads - Screenshot: transparent background via CSS-variable injection on the live element before toPng; white/black font toggle on colorbar + LOS - Toolbar hide now also hides the Points panel (lifted toolbarsVisible to App.tsx) - Reference marker visibility decoupled from refEnabled (new refMarkerVisible state + TOGGLE_REF_MARKER_VISIBLE action) - complexMode useEffect no longer overwrites localStorage-restored vmin/vmax when switching datasets (removed currentDataset from deps) - Histogram: module-level browser cache keyed by dataset:timeIndex Backend: - tifs-to-geozarr: pre-compute per-time-step histogram stats at conversion time and embed as bowser_histogram zarr attr; server returns cached stats instantly, falls back to full compute on old stores - Layer mask time-index clamping: isel safe_idx = min(time_idx, size-1) with fallback to index 0 when no time context, fixes IndexError when mask has fewer time steps than the main dataset Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Split-screen mode lets users compare two datasets side-by-side with an independent draggable divider, plus its own dataset/colormap/vmin-vmax/ opacity/time-step controls and histogram in a dedicated "Right Layer" sidebar section. The pixel inspector reads the correct dataset and time index based on which side of the divider the cursor is on. Other UI: coordinate grid moved into a dedicated Leaflet pane so it sits above tiles but below markers; reference and point-picking tools moved to the left toolbar so the right one stays focused on visualization; toolbar background uses CSS variables so it follows the dark/light theme. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The binned-median path was calling pyproj.transform + xarray.sel(method= 'nearest') once per sample point, which scaled with bins x along-steps x perpendicular-offsets and dominated extraction latency on zarr-backed datasets. Add _read_lonlat_batch() that collapses all per-bin lookups into a single vectorized transform and a single xarray sel, with array- based bounds masking for off-grid points. COG mode falls back to per- point reads (rasterio window reads are not easily batchable here). Also restore the start->end gradient line on the median series when sampling_interval > 0 - it was hidden behind showLine=false, so binned profiles only showed disconnected dots without the directional cue. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Export the active layer as a GeoTIFF from a new sidebar EXPORT section, applying the same masking shown on the map (recommended, layer, and custom masks). Works in both MD (GeoZarr) and COG modes via a shared masking helper; masked pixels become NaN nodata at native resolution. - Add geozarr.storage_options_for() so s3:// stores open anonymously by default (set BOWSER_S3_ANON=0 to use the normal credential chain for private buckets); wire it through state and the bbox sniffer. - ProfileChart: the close (X) button now deactivates the profile, not just clears it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
storage_options_for() returned {} for non-s3:// paths, and the callers
passed it as storage_options={} to xr.open_zarr / zarr.open_group. On
zarr >= 3.1 that raises TypeError("'storage_options' was provided but
unused"), breaking every local store (bowser run --stack-file cube.zarr,
local catalogs, the pip/uvx quickstart). The Docker image masked it by
pinning an older zarr.
Return None for non-s3:// URIs so callers pass nothing for local paths;
s3:// still gets {"anon": ...}. Verified both local and anonymous-s3
opens on zarr 3.1.6 / xarray 2026.4.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Surfaces the BOWSER_S3_ANON setting as a CLI flag. Default (flag omitted) leaves the env/anon-by-default behavior unchanged; --no-s3-anon signs s3:// reads with the normal AWS credential chain for private buckets. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jun 17, 2026
Closed
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.
UI improvements: split screen, faster profile, smoother tiles
A grab-bag of frontend + backend polish on top of
main, packaged together so the underlying refactors (custom Leaflet panes, two-slot tile model, sharedRasterTileLayerprops) only land once.New features
L:/R:).state.annotations.L:/R:. Issue ticket: Hover to show value #35load; no more mixed-tile seam artifacts when scrubbing time / colormap / vmin-vmax. A bottom-right spinner reflects in-flight tile counts across basemaps + raster.pyproj.transform+xarray.sel(method='nearest')calls into a single batched read — 20–100× faster on zarr-backed datasets. The start→end gradient line was restored in binned mode (was hidden behindshowLine: false). X button on the panel now closes it instead of just clearing data. Awkward shape was addressed before: "radius" round profile is strangely shaped #37tifs-to-geozarrembeds per-time-step histogram stats (min/max/percentiles/bins) as abowser_histogramzarr attr; the/histogramendpoint returns cached stats instantly. Falls back to a full compute for older stores.Smaller polish
refEnabled(newrefMarkerVisiblestate + toggle).useEffectno longer overwrites thelocalStorage-restoredvmin/vmaxwhen switching datasets.dataset:timeIndex(no refetch when scrubbing).toolbarsVisibletoApp.tsx).Backend fixes
iselsmin(time_idx, size-1)(falling back to0when there's no time context) instead of raisingIndexError.Notes for reviewers
RasterTileLayerwas generalized to acceptpaneandoverrideDataset/colormap/vmin/vmax/opacity/timeIndexprops so the split-screen right layer reuses the same fetch/swap pipeline. No duplicated tile logic.splitLeft,splitRight,graticule) are created synchronously in the render function of their respective components —useEffect/useLayoutEffectran after React-Leaflet'saddLayer, causinggetPane() is undefinedcrashes. This is the only reliable ordering with react-leaflet ^4.2.clip-pathfor the split panes usespolygon()with absolute pixel values derived from the map-pane's CSS translate (updated onmove/zoom/resize); percentage-basedinset()was relative to the pane's full tile-buffer extent, not the viewport.Testing
Split-screen
