Repository navigation
Map: scale bar on the 2D map and in PNG/PDF exports - #574
Draft
sandesh-sp wants to merge 6 commits into
Draft
sandesh-sp wants to merge 6 commits into
sandesh-sp wants to merge 6 commits into
Conversation
Adds a ScaleBar tool that shows a metric or imperial scale bar over the map, and draws a matching bar, measured in image pixels, onto Share exports while the tool is in the layout. - IMapEngine gains getMetersPerPixel() and getChromeInsets(); the deck.gl adapter derives the scale from Web Mercator at the view centre, and the Leaflet adapter reports none. - Screenshots carry metersPerPixel in image pixels, so exports scale correctly on high-DPI displays. - Bottom float zones sit above the basemap's logo and attribution.
The map scale is the ground distance between two nearby screen points,
measured on the mission's planet, so it works for deck.gl and Leaflet
alike and holds at the bar's own position rather than the view centre.
- mapScale.ts measures metres per CSS pixel at a page position (or near
the bottom-left corner) and stamps screenshots with the scale in image
pixels, read before the capture starts.
- map:getMetersPerPixel takes an optional { clientX, clientY }; the
ScaleBar asks at its own position.
- Remove the Web Mercator formula and getMetersPerPixel from IMapEngine
and the adapters.
- No scale while the map is tilted more than 10 degrees, where no single
bar fits the view.
- Indent the bar's label 8px on screen and in exports.
The bottom float zones' clearance above the basemap logo and attribution is a layout change for every bottom panel, so it moves to a separate branch.
- Read the scale on handler-ready, map:moveend and window resize, and at most once per frame on map:move, instead of retrying null reads on a timer. - Provide the export settings with units as an effect dependency. - ScaleBarSettings reuses ScaleUnits; LegendDeps becomes DecorationDeps. - Drop the unused tuple branch in metersPerPixelAt. - Shorten the mapScale docstrings, the bus API row and the tool's Configure description.
This was referenced Oct 15, 2026
- The on-screen bar gives the scale under the pointer, or at the map's
centre while the pointer is off the map or off a Web Mercator world. It
shows at any zoom and tilt, since the scale at one point holds there.
- map:getMetersPerPixel takes a container point { x, y }, centre by
default. Engines report the pointer leaving the map as 'mouseout'
(deck.gl on container mouseleave), re-emitted as map:mouseout.
- Exports measure where the on-screen bar sits and carry no bar when the
map is tilted past 10° or the scale varies more than 1.5x down the view,
sampled on five rows so a world view never passes.
- The bar re-reads on every map:move, skips renders that would look the
same, and ignores reads that settle out of order.
- deck.gl emits 'move' during side-by-side pane drags and standalone
camera changes, and shares one viewport per camera for projections.
…al export - deck.gl without a basemap reports jumps made in code, and raises 'moveend' once the camera settles instead of on every frame - On Leaflet the bar measures at the map centre, since Leaflet can miss the pointer leaving the map across a hoverable feature - Engines report Web Mercator through IMapEngine.isWebMercator() rather than a global another engine may have left behind - Exports ask 'export:getScaleBar', which answers only while a bar is drawn and anchors at the bar's backing box - The adapter keeps the built model in state; exports without a scale by design no longer log - Document the map pointer events, map:getScreenshot and export:getScaleBar
sandesh-sp
marked this pull request as draft
October 20, 2026 14:14
This branch has not been deployed
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.
Closes #570
Summary
src/essence/Tools/ScaleBar/) shows a metric or imperial scale bar, by default in afloat-bottom-leftpanel. The bar is labelled with a round distance (1, 2, 5, 10, …) and switches between m/km or ft/mi.mapScale.tsunprojects two points a few pixels apart and measures the great-circle distance between them on the mission's planet. It works for deck.gl and Leaflet alike, and for non-Mercator projections.map:getMetersPerPixeltakes an optional{ x, y }map container point and returns ground metres per CSS pixel there (the map's centre when omitted).map:mouseoutreports the pointer leaving the map; deck.gl raises it on the container'smouseleave, Leaflet natively. Documented inEvent-Bus-API.md.map:getScreenshotaccepts{ scaleAt }and returnsmetersPerPixelin image pixels plus thescaleAnchor, read before the capture starts, so the bar is correct on high-DPI displays.map:moveandmap:moveend, pointer moves, and window resize (once per frame). Reads that would draw the same bar skip the render, and stale reads that settle out of order are dropped.moveon every camera frame: overlay mode, standalone mode, and side-by-side pane drags.map:movesubscribers andaddOverlay's reposition listener now track the camera during a gesture. Projections share one viewport per camera.Behaviour and limits
plugin:scalebar:getSettings). Making this a plugin-neutral contract is deferred to the plugin-to-plugin communication work.feature/570-map-chrome-insets), since it changes every bottom float panel. Until it lands, a bottom-left bar can sit close to the MapLibre logo.Test plan
npx vitest run: 215 files, 3,666 tests passaddOverlay) still track the camera while panning in overlay mode