Skip to content

Graticules and limits read scales#495

Open
teunbrand wants to merge 9 commits into
posit-dev:mainfrom
teunbrand:scale_to_projection
Open

Graticules and limits read scales#495
teunbrand wants to merge 9 commits into
posit-dev:mainfrom
teunbrand:scale_to_projection

Conversation

@teunbrand

Copy link
Copy Markdown
Collaborator

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.

teunbrand and others added 3 commits June 29, 2026 14:18
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>
@teunbrand teunbrand requested a review from thomasp85 June 29, 2026 13:03

@thomasp85 thomasp85 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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...

teunbrand and others added 6 commits July 3, 2026 11:31
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

Copy link
Copy Markdown
Collaborator Author

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.

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.

Hook up lon/lat scales to map projections

2 participants