Feat/mapcontrol plugin - #115
Conversation
…-IMPACT/MMGIS into feat/mapcontrol-plugin
Standalone React component + pure helpers + themeable SCSS, mounted by a bus-only wrapper. Adds per-feature configure toggles.
There was a problem hiding this comment.
can you use the skill i shared to make this component align with the reusable components pattern?
There was a problem hiding this comment.
Refractored to monorepo pattern in this
There was a problem hiding this comment.
some of the files in this folder can be shared between tools, see current development branch for pattern
✅ Version Already UpdatedThis PR includes a manual version update to No automatic version bump needed. |
|
🤖 Version Auto-BumpedThe version has been automatically incremented to This commit was added to your PR branch. When you merge this PR, the new version will be included. If you want a different version, update |
|
Fixed the must-fix and should-fix findings from the self-review in 578aba2: Must-fix
Should-fix |
Description
Adds MapControl, a floating toolbar over the map with four features. Each can be
enabled/disabled per-mission from the Configure page.
The plugin is bus-only: the React component is MMGIS-independent and a thin wrapper
translates its callbacks into
mmgisAPIcalls. Map operations that didn't exist yet areadded in core (
Map_.js) and exposed over the event bus so any future plugin can reuse them.Geocode search
On each keystroke we hit the Nominatim API with the URL-encoded query — debounced 300ms,
only firing for queries ≥2 chars,
limit=5. No API key or backend (public endpoint). Onselecting a result we reorder Nominatim's bbox (
[min_lat,max_lat,min_lon,max_lon]) intoLeaflet order (
[[S,W],[N,E]]), fly to it viamap:fitBounds, and drop a pin viamap:createLayer.Basemap switching
Styles come from
msv.basemap.styles[]in the mission config (configurable per mission);if none are defined we fall back to provider defaults, which differ by engine (Leaflet →
raster XYZ tiles, deck.gl → vector style JSON). The active style is matched against
msv.basemap.style. On select we callmap:setBasemap, which calls the engine'ssetBasemapStyle()— deck.gl swaps the vector style in place; Leaflet rebuilds the tilelayer (handling
mapbox://, raw{z}/{x}/{y}templates, and an OSM fallback).Measure
We subscribe to
map:click/map:mousemove, collect two points, and draw the line +endpoints as a vector layer via
map:createLayer. Distance is computed with Turf; thelabel is positioned by projecting the segment midpoint via
map:latLngToContainerPoint.Zoom
map:zoomIn/map:zoomOut, clamped to the engine's min/max zoom.Core changes (beyond the plugin)
New
map:*handlers inMap_.js, also exposed as documented methods onmmgisAPI:setBasemap/getBasemap/getBasemapStyles,zoomIn/zoomOut,latLngToContainerPoint, and themap:click/map:mousemoveevent streams.Two supporting changes were required:
LeafletAdaptergained runtime basemap tile rendering (it hadnone before);
DeckGLAdaptergainedsetStyleand normalized{lat,lng}pointer-eventemission.
new missions get a default
view.Dependency on #90 (imapengine-drawing)
Vector overlays (the measure line and the search pin) are drawn via
map:createLayer/map:removeLayer, which are provided by #90. We deliberately do not define our ownoverlay channels — an earlier version added
map:addOverlay/map:removeOverlay, whichcollided by name with #90's (their
addOverlayis a different operation: an HTML elementanchored at a point). Using
map:createLayeris the operation we actually need and removesthe duplication. Until #90 merges these two features no-op (guarded, no crash); the rest of
the plugin is self-contained.
Known limitation / future work
MapControl is a floating overlay, not a docked panel, so it bypasses the normal panel-mount
path: it self-mounts at bundle load by polling for the map container and rendering into a
position:fixedlayer ondocument.body. This works but should be replaced by a proper"floating tool" mount path in the Modern layout so overlays don't have to self-mount.
Testing
Manual only:
handlers) and switching restyles the map on both engines.