Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2718d32
leaflet basemap rendering
BhattaraiSijan Apr 14, 2026
524ff46
basemap style switcher fixed
BhattaraiSijan Apr 14, 2026
4616a20
basemap switcher APIs setup
BhattaraiSijan Apr 20, 2026
b6b2837
zoom apis added
BhattaraiSijan Apr 21, 2026
0694875
APIs for measure tool
BhattaraiSijan Apr 22, 2026
54bb007
route overlay + click APIs through engine adapter
BhattaraiSijan Apr 22, 2026
b7f2934
basemap as tilelayer
BhattaraiSijan Apr 22, 2026
319533b
fix:style urls in maplibre
BhattaraiSijan Apr 22, 2026
7ffc66f
changing style urls
BhattaraiSijan Apr 22, 2026
cd67ceb
maplibre dark style change
BhattaraiSijan Apr 22, 2026
036be3d
fix:min zoom set in mapbox leaflet
BhattaraiSijan Apr 22, 2026
dda2fe9
mouseover event emit, for draw
BhattaraiSijan Apr 22, 2026
a3b1a3d
fix(deck.gl): normalize Leaflet-style keys in vector layers
BhattaraiSijan Apr 22, 2026
cbc4536
fix(deck.gl): include latlng in normalized pointer events
BhattaraiSijan Apr 22, 2026
ae72021
clean up
BhattaraiSijan Apr 23, 2026
2eb8c4e
revert: drop default pointToLayer from vector layer builder
BhattaraiSijan Apr 23, 2026
d9fc146
Merge branch 'feat/add-basemap-to-leaflet' of https://github.com/NASA…
BhattaraiSijan Apr 23, 2026
9627997
mapcontrol tool WIP
BhattaraiSijan Apr 24, 2026
28cd536
merge branch development
BhattaraiSijan May 28, 2026
e6b98c9
refactor(MapControl): decouple component from MMGIS wrapper
BhattaraiSijan Jun 5, 2026
589012e
removed duplicate addoverlay methods
BhattaraiSijan Jun 8, 2026
0d1a8c9
clean up
BhattaraiSijan Jun 8, 2026
40ada65
refractor:monorepo pattern
BhattaraiSijan Jun 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ const NewMissionModal = (props) => {

const config = {
msv: {
view: [39, -98, 4],
radius: {
major: planetRadius.major,
minor: planetRadius.minor,
Expand All @@ -165,7 +166,7 @@ const NewMissionModal = (props) => {
},
};

if (selectedEngine === "deckgl" && basemapProvider !== "none" && basemapStyle) {
if (basemapProvider !== "none" && basemapStyle) {
config.msv.basemap = {
provider: basemapProvider,
style: basemapStyle,
Expand Down Expand Up @@ -341,13 +342,13 @@ const NewMissionModal = (props) => {
onChange={(e) => setBasemapProvider(e.target.value)}
label="Basemap"
>
<MenuItem value="none">None (transparent background)</MenuItem>
<MenuItem value="none">None (no basemap)</MenuItem>
<MenuItem value="maplibre">MapLibre GL (open-source)</MenuItem>
<MenuItem value="mapbox">Mapbox GL (requires access token)</MenuItem>
</Select>
</FormControl>
<Typography className={c.subtitle2}>
{`Optional vector-tile basemap rendered beneath deck.gl layers. Can be changed later.`}
{`Optional vector-tile basemap rendered beneath map layers. Can be changed later.`}
</Typography>
{basemapProvider !== "none" && (
<>
Expand Down
2 changes: 1 addition & 1 deletion configure/src/metaconfigs/tab-ui-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@
{
"field": "msv.basemap.provider",
"name": "Provider",
"description": "Renders a vector-tile basemap beneath deck.gl layers using MapboxOverlay. Has no effect on Leaflet-engine missions. 'maplibre' is open-source and requires no token. 'mapbox' requires an access token.",
"description": "Renders a vector-tile basemap beneath map layers using MapLibre or Mapbox GL. Works with both Leaflet and deck.gl engine missions. 'maplibre' is open-source and requires no token. 'mapbox' requires an access token.",
"type": "dropdown",
"options": ["none", "maplibre", "mapbox"],
"default": "none",
Expand Down
40 changes: 40 additions & 0 deletions src/essence/Basics/MapEngines/Adapters/DeckGLAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ interface BasemapInstance {
off(type: string, handler: (...args: unknown[]) => void): unknown
/** Recalculate the map size from its container element. */
resize(): void
/** Switch the map to a different style URL at runtime. */
setStyle(styleUrl: string): unknown
}

/**
Expand Down Expand Up @@ -324,6 +326,12 @@ export class DeckGLAdapter implements IMapEngine<Deck, Layer, PickingInfo> {
return this._basemap
}

setBasemapStyle(styleUrl: string): void {
if (this._basemap) {
this._basemap.setStyle(styleUrl)
}
}

getContainer(): HTMLElement {
return this._container
}
Expand Down Expand Up @@ -823,9 +831,11 @@ export class DeckGLAdapter implements IMapEngine<Deck, Layer, PickingInfo> {
},
onClick: (info: PickingInfo) => {
this._featureClickHandler?.(pickInfoToResult(info))
this._emitClick(info)
},
onHover: (info: PickingInfo) => {
this._featureHoverHandler?.(pickInfoToResult(info))
this._emitMouseMove(info)
},
} as any)
}
Expand Down Expand Up @@ -890,9 +900,11 @@ export class DeckGLAdapter implements IMapEngine<Deck, Layer, PickingInfo> {
layers: [],
onClick: (info: PickingInfo) => {
this._featureClickHandler?.(pickInfoToResult(info))
this._emitClick(info)
},
onHover: (info: PickingInfo) => {
this._featureHoverHandler?.(pickInfoToResult(info))
this._emitMouseMove(info)
},
})

Expand Down Expand Up @@ -988,6 +1000,34 @@ export class DeckGLAdapter implements IMapEngine<Deck, Layer, PickingInfo> {
private _emitEvent(name: string, data?: unknown): void {
this._eventListeners.get(name)?.forEach((h) => h(data as PickingInfo))
}

private _emitClick(info: PickingInfo): void {
if (!info?.coordinate) return
this._eventListeners.get('click')?.forEach(
(h) => h(this._buildNormalizedPointerEvent(info) as unknown as PickingInfo)
)
}

private _emitMouseMove(info: PickingInfo): void {
if (!info?.coordinate) return
this._eventListeners.get('mousemove')?.forEach(
(h) => h(this._buildNormalizedPointerEvent(info) as unknown as PickingInfo)
)
}

private _buildNormalizedPointerEvent(info: PickingInfo): Record<string, unknown> {
const lat = info.coordinate![1]
const lng = info.coordinate![0]
return {
lat,
lng,
latlng: { lat, lng },
containerPoint:
info.x != null && info.y != null
? { x: info.x, y: info.y }
: undefined,
}
}
}

export default DeckGLAdapter
90 changes: 90 additions & 0 deletions src/essence/Basics/MapEngines/Adapters/LeafletAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
FitBoundsOptions,
MapInitOptions,
ProjectionOptions,
BasemapOptions,
} from '../types/view'
import { LayerOptions, TileLayerOptions, MarkerOptions } from '../types/layers'
import { IMapEngineMarkers } from '../IMapEngineMarkers'
Expand Down Expand Up @@ -79,6 +80,9 @@ export default class LeafletAdapter implements IMapEngine<any, any, any>, IMapEn
*/
private _initOptions: MapInitOptions | null = null

private _basemapLayer: any = null
private _basemapAccessToken: string | undefined

/**
* Initialize the Leaflet map instance
*/
Expand Down Expand Up @@ -154,6 +158,10 @@ export default class LeafletAdapter implements IMapEngine<any, any, any>, IMapEn
if (attributionControl) {
attributionControl.remove()
}

if (options.basemap && options.basemap.provider && options.basemap.provider !== 'none') {
this._initBasemapTileLayer(options.basemap)
}
}

/**
Expand Down Expand Up @@ -247,6 +255,8 @@ export default class LeafletAdapter implements IMapEngine<any, any, any>, IMapEn
destroy(): void {
if (!this._map) return

this._removeBasemapLayer()

this._eventHandlers.forEach((handler, eventName) => {
this._map.off(eventName, handler)
})
Expand All @@ -268,6 +278,10 @@ export default class LeafletAdapter implements IMapEngine<any, any, any>, IMapEn
return this._map
}

getBasemap(): any {
return this._basemapLayer
}

/**
* Get the container element
*/
Expand Down Expand Up @@ -939,4 +953,80 @@ export default class LeafletAdapter implements IMapEngine<any, any, any>, IMapEn
}
return null
}

// ========================================
// BASEMAP TILE LAYER METHODS
// ========================================

private _initBasemapTileLayer(basemap: BasemapOptions): void {
this._basemapAccessToken = basemap.accessToken
const spec = this._resolveBasemapTileSpec(basemap)
this._basemapLayer = L.tileLayer(spec.url, spec.options)
this._basemapLayer.addTo(this._map)
this._basemapLayer.bringToBack()

const specMinZoom = (spec.options as { minZoom?: number }).minZoom
if (typeof specMinZoom === 'number' && specMinZoom > this._map.getMinZoom()) {
this._map.setMinZoom(specMinZoom)
}
}

setBasemapStyle(styleUrl: string): void {
if (!this._map) return
const spec = this._resolveBasemapTileSpec({
provider: this._inferProvider(styleUrl),
style: styleUrl,
accessToken: this._basemapAccessToken,
})
this._removeBasemapLayer()
this._basemapLayer = L.tileLayer(spec.url, spec.options)
this._basemapLayer.addTo(this._map)
this._basemapLayer.bringToBack()
}

private _removeBasemapLayer(): void {
if (this._basemapLayer && this._map) {
this._map.removeLayer(this._basemapLayer)
}
this._basemapLayer = null
}

private _resolveBasemapTileSpec(basemap: BasemapOptions): {
url: string
options: Record<string, unknown>
} {
const style = basemap.style || ''

const mapboxMatch = style.match(/^mapbox:\/\/styles\/([^/]+)\/(.+)$/)
if (mapboxMatch) {
const [, user, styleId] = mapboxMatch
const token = basemap.accessToken || this._basemapAccessToken || ''
return {
url: `https://api.mapbox.com/styles/v1/${user}/${styleId}/tiles/{z}/{x}/{y}?access_token=${token}`,
options: {
tileSize: 512,
zoomOffset: -1,
minZoom: 1,
attribution: '© Mapbox © OpenStreetMap',
},
}
}

if (style.includes('{z}') && style.includes('{x}') && style.includes('{y}')) {
return { url: style, options: {} }
}

return {
url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
options: {
subdomains: 'abc',
attribution: '© OpenStreetMap contributors',
},
}
}

private _inferProvider(styleUrl: string): BasemapOptions['provider'] {
if (styleUrl.startsWith('mapbox://')) return 'mapbox'
return 'maplibre'
}
}
48 changes: 39 additions & 9 deletions src/essence/Basics/MapEngines/types/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,38 @@ export interface FitBoundsOptions extends ViewOptions {
}

/**
* Supported basemap providers for deck.gl overlay mode.
* Supported basemap providers for deck.gl and Leaflet overlay mode.
*
* `'maplibre'` uses MapLibre GL JS (open-source, no access token required).
* `'mapbox'` uses Mapbox GL JS (requires a valid {@link BasemapOptions.accessToken}).
*/
export type BasemapProvider = 'mapbox' | 'maplibre'
export type BasemapProvider = 'mapbox' | 'maplibre' | 'none'

/**
* Configuration for an optional vector-tile basemap rendered beneath deck.gl layers
* via `@deck.gl/mapbox`'s `MapboxOverlay`. When present, the adapter runs in overlay
* mode; when absent it falls back to a standalone `Deck` instance with a transparent
* background.
* A named basemap style preset for the in-map style switcher control.
* Each entry maps a user-friendly display name to a MapLibre/Mapbox style URL.
*
* @example
* ```ts
* { name: 'Streets', style: 'https://demotiles.maplibre.org/style.json' }
* ```
*/
export interface BasemapStyleEntry {
/** Display name shown in the style switcher. */
name: string
/** MapLibre/Mapbox style URL for this entry. */
style: string
}

/**
* Configuration for an optional vector-tile basemap rendered beneath map layers
* for both the deck.gl and Leaflet adapters.
*
* **deck.gl**: Uses `@deck.gl/mapbox`'s `MapboxOverlay` to composite deck.gl layers
* on top of a MapLibre/Mapbox GL basemap.
*
* **Leaflet**: Creates a MapLibre/Mapbox GL map behind a transparent Leaflet canvas
* and synchronises pan/zoom events so the basemap and Leaflet layers stay aligned.
*
* The chosen provider's stylesheet must be imported in the application entry point:
* `import 'maplibre-gl/dist/maplibre-gl.css'` or `import 'mapbox-gl/dist/mapbox-gl.css'`.
Expand All @@ -69,6 +89,7 @@ export interface BasemapOptions {
provider: BasemapProvider
/**
* Map style URL or a Mapbox/MapLibre style JSON object URL.
* This is the default/initial style that the basemap loads with.
* @example 'https://demotiles.maplibre.org/style.json'
* @example 'mapbox://styles/mapbox/streets-v12'
*/
Expand All @@ -78,6 +99,12 @@ export interface BasemapOptions {
* Ignored for `'maplibre'`.
*/
accessToken?: string
/**
* Optional array of named style presets for the in-map style switcher control.
* When provided, a floating UI selector lets the user switch between basemap
* styles (e.g. Streets, Satellite, Terrain) at runtime.
*/
styles?: BasemapStyleEntry[]
}

/**
Expand All @@ -102,9 +129,12 @@ export interface MapInitOptions {
editable?: boolean
projection?: ProjectionOptions
/**
* Optional vector-tile basemap to render beneath deck.gl layers via `MapboxOverlay`.
* When absent the DeckGL adapter operates in standalone mode with a transparent background.
* Has no effect on the Leaflet adapter.
* Optional vector-tile basemap to render beneath map layers.
*
* - **deck.gl**: renders via `MapboxOverlay` (overlay mode).
* - **Leaflet**: renders a MapLibre/Mapbox GL map behind a transparent Leaflet canvas.
*
* When absent, both adapters fall back to their default (no basemap) behaviour.
*/
basemap?: BasemapOptions
}
Expand Down
Loading