Skip to content

feat: Add MGRS graticule overlay#91

Merged
ThomasHalwax merged 17 commits intomainfrom
feature/mgrs-graticule
Feb 14, 2026
Merged

feat: Add MGRS graticule overlay#91
ThomasHalwax merged 17 commits intomainfrom
feature/mgrs-graticule

Conversation

@axel-krapotke
Copy link
Contributor

Overview

Implements the MGRS coordinate grid that was listed as MGRS (not implemented) in the View → Graticules menu.

Grid Levels

Level Spacing Visible when Content
Grid Zone Designation 6° × 8° Always Zone boundaries + labels (e.g. 33U)
100 km squares 100 km Resolution ≤ 1200 m/px Grid lines + two-letter IDs (e.g. WP)
10 km squares 10 km Resolution ≤ 120 m/px Grid lines within 100 km squares

Implementation

  • New module src/renderer/components/map/mgrsGraticule.js — renders grid as VectorLayer with features computed for the visible viewport
  • Uses geodesy/mgrs.js (already in project) for UTM ↔ LonLat conversions
  • Grid lines interpolated with multiple waypoints for correct curvature in Web Mercator
  • Recomputes on moveend events, only for visible UTM zones
  • Extended graticules.js to create MGRS graticule alongside existing WGS84
  • Menu label updated from MGRS (not implemented) to MGRS

Scope Limitations

  • UTM special zones (Norway/Svalbard) use standard boundaries — follow-up
  • Polar regions (UPS) out of scope
  • Finer grids (1 km, 100 m) not included but architecture supports extension

Spec & Tests

  • Spec: specs/mgrs-graticule.md
  • Tests: test/renderer/mgrs-graticule-test.js — 20 tests covering UTM zone calculation, band letters, coordinate conversions, 100k square identifiers, and GZD consistency for known cities
  • All 307 tests passing

Implement an MGRS coordinate grid that renders as a vector layer
on the map. The grid shows three levels of detail based on zoom:

- Grid Zone Designations (GZD): always visible, 6°×8° zones
- 100 km squares: visible at resolution ≤ 1200 m/px
- 10 km grid: visible at resolution ≤ 120 m/px

Uses geodesy/mgrs.js for UTM↔LonLat conversions. Grid lines are
interpolated with multiple points to account for curvature in
Web Mercator projection. Only computes features for visible zones.

- Add src/renderer/components/map/mgrsGraticule.js
- Extend graticules.js to handle MGRS type
- Update menu label from 'MGRS (not implemented)' to 'MGRS'
- Add spec: specs/mgrs-graticule.md
- Add tests: test/renderer/mgrs-graticule-test.js (20 tests)
- Switch all grid lines from black/gray to red tones for visibility on dark basemaps
- Vertical lines rendered thicker than horizontal lines at all levels
- Add 1000m grid resolution (THRESHOLD_1K = 12 m/px)
- Labels now use red color matching the grid lines
- Increase line widths: GZD 3px, 100km 2px, 10km 1.5px, 1km 1.2px
- Add 10km square labels (2-digit easting+northing) at square centers
- Add 1km square labels (4-digit easting+northing) at square centers
- Label font sizes: 10km=11px, 1km=9px
- 10km labels: 16px, format '3 · 7'
- 1km labels: 13px, format '32 · 74'
- Middle dot (·) separates easting from northing
- Use full UTM easting range (100k-900k) for 100km grid to prevent
  missing columns at zone boundaries
- For 10km/1km grids, estimate easting from samples with generous
  padding (20km/2km) to cover zone edges
- Use relaxed zone matching for northing estimation (±1 zone)
- Skip vertical lines with easting <= 166000 or >= 834000 (zone edges)
- Clip all line points to zone longitude bounds
- Use easting range 166000-834000 for horizontal line interpolation
- Doubles interpolation steps for horizontal lines for smoother curves
- Applies to all grid levels (100km, 10km, 1km)
Lines now extend all the way to zone edges (like reference implementation).
Convergence at zone boundaries is correct UTM behavior.
Zone clipping via longitude bounds prevents lines from crossing into
adjacent zones.
Lines at extreme eastings (e.g. 800000 in a zone) extend beyond the
zone's longitude bounds. Previously these points were dropped entirely,
creating a gap between the last grid line and the zone boundary.

Now using clipToLonRange() to interpolate line endpoints at the exact
zone boundary longitude, so lines extend seamlessly to the zone edge.
Show 'Outside MGRS/UTM limits' instead of throwing when the cursor
is above 84°N or below 80°S.
- Add getZoneBounds() for actual zone longitude limits per latitude
- Zone 31V narrowed to 0°-3°E, Zone 32V widened to 3°-12°E
- Svalbard: zones 32X/34X/36X don't exist; 31X/33X/35X/37X widened
- GZD boundaries drawn per-band with correct zone edges
- Grid generators use widest zone bounds across visible bands
- Labels placed at actual zone centers
- OSDDriver: graceful fallback outside UTM limits (>84°N / <80°S)
- Add clipToZone() that checks getZoneBounds() at each point's latitude
- Replaces fixed clipToLonRange() in all grid generators
- Labels check actual zone bounds at their latitude, not widest bounds
- Fixes duplicate labels and diagonal lines at Norway zone 32V boundary
Revert 'widest bounds' logic that caused diagonal lines in special
zones. Grid generators now use standard 6° bounds for easting/northing
sampling. clipToZone() handles per-latitude clipping for Norway/Svalbard
special zones, extending or narrowing grid lines at the correct band
boundaries.
Special zone boundaries (Norway/Svalbard) are shown correctly in the
GZD layer. The 100km/10km/1km grids now clip to standard 6° zone
bounds to avoid visual kinks at band transitions where zone width
changes. This matches common MGRS implementations.
Vertical grid lines now get additional sample points near the band
boundaries where zone width changes (56°N, 64°N, 72°N, 84°N). This
ensures smooth clipping transitions instead of abrupt kinks where
clipToZone changes its longitude bounds.
Remove unused functions (getZonesForExtent, bandLetter, clipToLonRange),
fix no-multi-spaces and one-var violations.
@ThomasHalwax ThomasHalwax merged commit 0b0e5b8 into main Feb 14, 2026
2 checks passed
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.

2 participants