Uh oh!
There was an error while loading. Please reload this page.
Graticules and limits read scales - #495
Conversation
SCALE lon/lat FROM [...] now overrides the data-derived bbox for viewport extent, and SCALE lon/lat SETTING breaks => [...] controls graticule line placement. Scale limits sit between data extent and PROJECT bounds in the precedence chain. Per-element fallback allows partial constraints (e.g. FROM (null, 40) constrains only the upper bound). OOB filtering is skipped for map position scales since their limits are in EPSG:4326 while data columns contain projected coordinates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Unit tests for scale_override_bbox (numeric limits, string limits, no explicit limits, mixed null/numeric) and scale_breaks (explicit array, numeric count ignored, absent, wrong aesthetic). Integration tests verify the full DuckDB path: scale limits constrain the projected bbox, and explicit breaks produce the expected number of graticule meridians. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
thomasp85
left a comment
There was a problem hiding this comment.
It would be nice if the graticule_breaks use would flow automatically from some sort of spatial transform on the scale but I can understand why that would be hard to detect for little gain
E.g. SCALE lat VIA spatial would give you a graticule breaks function and the spatial transform was default for lat and long aesthetics...
Identity transform whose `calculate_breaks` delegates to `graticule_breaks`, producing degree-aligned break positions (1°, 2°, 5°, 10°, 15°, 30°, etc.). Registered in the transform system with aliases "geographic" and "geo". Added to the Continuous scale type match arm so `VIA geographic` works. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extracts an `infer_transform` helper to unify the two duplicate transform-inference code paths. Map position scales (pos1/pos2) now receive the Geographic transform automatically when a map projection is active, so graticule breaks use degree-aligned logic. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Propagates `&mut [Scale]` through the projection trait chain so that `build_graticule` can call `resolve()` on position scales with a synthetic ScaleDataContext from the geographic bbox. After resolution, breaks are read via `Scale::numeric_breaks()`. Scales are marked `resolved = true` so the later `resolve_scales` pass skips them. Also adds `ScaleDataContext::from_range()` constructor. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Position aesthetics already default to oob_keep, so the explicit map-position check in apply_scale_oob was unreachable. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests that `SCALE lon SETTING breaks => 4` produces exactly 4 graticule meridians (break count now resolved through geographic transform), and that map position scales are marked resolved after the projection step. Also ensures resolve_map_scale assigns Continuous type and Geographic transform when the scale has none (e.g. spatial-only queries where no column mapping creates position scales). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… clauses Create pos1/pos2 scales in resolve_scale_types_and_transforms when a map projection is active but no position scales exist (spatial layers only map "geometry"). Also fix .any() → .all() in numeric_range validation and strengthen the integration test to assert scale existence. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
teunbrand
commented
Jul 3, 2026
I can see the attraction if having everything encapsulated in the scale system. I've made an effort to integrate the two systems better. I don't know if you want to give it another review @thomasp85, since the changes aren't negligible. |
thomasp85
left a comment
There was a problem hiding this comment.
LGTM once merge conflicts are handled
Uh oh!
There was an error while loading. Please reload this page.
This PR aims to fix#492.
It hooks up the position scales to the map projections. The breaks hook into the graticule and the limits hook into the bbox.
Essentially the priority order of the bbox is now: data < scales < bounds setting. The procedure essentially this: if scale limits exist, then transform data bbox to lon/lat and apply scales independently, then retransform the scaled bbox back to the target CRS. You'll run into some limitations with heavily distorting projections, but at that point it'd be better to use the bounds setting anyway.