Skip to content

Feat/mapcontrol plugin - #115

Open
BhattaraiSijan wants to merge 32 commits into
developmentfrom
feat/mapcontrol-plugin
Open

Feat/mapcontrol plugin#115
BhattaraiSijan wants to merge 32 commits into
developmentfrom
feat/mapcontrol-plugin

Conversation

@BhattaraiSijan

Copy link
Copy Markdown
Collaborator

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 mmgisAPI calls. Map operations that didn't exist yet are
added 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). On
selecting a result we reorder Nominatim's bbox ([min_lat,max_lat,min_lon,max_lon]) into
Leaflet order ([[S,W],[N,E]]), fly to it via map:fitBounds, and drop a pin via
map: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 call map:setBasemap, which calls the engine's
setBasemapStyle() — deck.gl swaps the vector style in place; Leaflet rebuilds the tile
layer (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; the
label 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 in Map_.js, also exposed as documented methods on mmgisAPI:
setBasemap / getBasemap / getBasemapStyles, zoomIn / zoomOut,
latLngToContainerPoint, and the map:click / map:mousemove event streams.

Two supporting changes were required:

  • Engine adaptersLeafletAdapter gained runtime basemap tile rendering (it had
    none before); DeckGLAdapter gained setStyle and normalized {lat,lng} pointer-event
    emission.
  • Configure page — basemap config now applies to any engine (was deck.gl-only), and
    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 own
overlay channels — an earlier version added map:addOverlay/map:removeOverlay, which
collided by name with #90's (their addOverlay is a different operation: an HTML element
anchored at a point). Using map:createLayer is the operation we actually need and removes
the 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:fixed layer on document.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:

  • Toggle each of the four features from the Configure page; confirm each appears/hides.
  • Test on both a Leaflet and a deck.gl mission.
  • Basemap switcher: confirm the list populates (it retries until the map registers its
    handlers) and switching restyles the map on both engines.
  • Search: type a place, confirm results, select one, confirm fly-to + pin.
  • Measure: click two points, confirm line + distance label. (Requires feat(core): drawing primitives + overlay + bus surface on IMapEngine #90 for the overlay.)
  • Zoom: confirm in/out and that it stops at min/max.

@BhattaraiSijan
BhattaraiSijan requested a review from slesaad June 8, 2026 16:03

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

can you use the skill i shared to make this component align with the reusable components pattern?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Refractored to monorepo pattern in this

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

some of the files in this folder can be shared between tools, see current development branch for pattern

@slesaad slesaad linked an issue Jul 15, 2026 that may be closed by this pull request
@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown

✅ Version Already Updated

This PR includes a manual version update to 4.2.19-20260720

No automatic version bump needed.

@BhattaraiSijan

Copy link
Copy Markdown
Collaborator Author
  • a52be12 — merged latest development (brings in the _shared adapters and the floating panel system).
  • 0cbe080 + 806580c — promoted useMMGISToolVars / useMMGISEvent into src/essence/Tools/_shared/adapters/ and deleted MapControl's local copies. Migrating LayerManager's duplicates is tracked in LayerManager: adopt the shared adapter hooks instead of local copies #202.
  • 833d58d — MapControl now mounts through the modern layout's floating panel system (make(targetId), preferredPosition: float-top-right) instead of self-mounting a fixed overlay on document.body. This closes the "known limitation" from the PR description. The measure label portals onto the map container so its pixel coords anchor correctly.
  • Multiple tools sharing one float zone (e.g. ShareExport + MapControl both in float-top-right) currently render as stacked cards — design discussion for that is Design discussion: multiple tools sharing one floating panel zone #203.

@github-actions

Copy link
Copy Markdown

🤖 Version Auto-Bumped

The version has been automatically incremented to 4.2.20-20260721

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 package.json manually and push to this PR.

@BhattaraiSijan

Copy link
Copy Markdown
Collaborator Author

Fixed the must-fix and should-fix findings from the self-review in 578aba2:

Must-fix

  1. Leaflet layer leakLeafletAdapter.createLayer now removes an existing layer with the same id before re-adding. The measure preview no longer strands ghost lines on the map (it re-draws per mousemove), and repeated searches no longer leak pins.
  2. Broken basemap on token-less Leaflet missions_resolveBasemapTileSpec returns null (with a console warning) for mapbox:// styles with an empty access token, so missions that inherit the template's gap-filled mapbox default skip the basemap instead of rendering a grid of 401 tiles and silently raising minZoom.

Should-fix
3. No more silent OSM substitution — GL style.json URLs on the Leaflet engine now warn and skip instead of quietly rendering hardcoded OSM streets while claiming the chosen style is active.
4. map:setBasemap/map:getBasemap tell the truth — both engine adapters' setBasemapStyle now return booleans; the bus handler only advances the active style (and returns true) when the engine actually applied it, and warns otherwise. A configured custom style URL that isn't in the resolved style list is prepended as "Default" so getBasemap reports what's actually displayed.
5. NewMissionModal — switching the map engine resets the basemap fields, so values entered under deck.gl can't silently ship inside a Leaflet mission's config; and the [39, -98, 4] default view is now Earth-only (Mars/Moon missions inherit the template's neutral view).
6. Measure label tracks the map — it's now an engine-anchored map:addOverlay overlay instead of a pixel-projected portal, so it stays glued to the segment midpoint through pan/zoom. This also removes the plugin's getElementById('mapScreen') DOM coupling and the per-mousemove latLngToContainerPoint round-trips; the MeasureLabel React component is deleted.
7. Geocode race — in-flight Nominatim requests are aborted when superseded (AbortController), so a slow older response can't overwrite newer results.
8. Basemap discovery no longer gives up silently — the hand-rolled 20×300ms poll is replaced with the shared useMMGISHandlerReady hook: it fetches once when core registers the handler (warning on timeout instead of vanishing), and an empty style list is treated as "no basemap configured" rather than retried 20 times.

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.

MapControl plugin

2 participants