Runtime workflows and lifecycle description based on the actual implementation.
All five services must be constructed first, then LayersControl:
const eventEmitter = new EventEmitter();
const stateService = new StateService(eventEmitter, 'my-app');
const mapService = new MapService(eventEmitter);
const uiService = new UIService(stateService, mapService, eventEmitter);
const businessLogicService = new BusinessLogicService(stateService, eventEmitter);
const layersControl = new LayersControl(options, {
stateService, uiService, mapService, businessLogicService, eventEmitter
});LayersControl constructor validates services, merges option defaults, and calls _setupPublicEventForwarding().
mapService.setMap(map)— registers map with the serviceuiService.setOptions(options),uiService.setMap(map),uiService.setContainer(container)— wire up UI- Overlay states initialised from config via
stateService.initOverlay() - Initial base set in state if no persisted base exists
businessLogicService.initialize({ map, stateService, uiService, mapService, eventEmitter, options })uiService.setBusinessLogicService(businessLogicService)— gives UI the orchestrator referenceuiService.render()— builds the full control DOM- Map
moveendlistener attached (debounced viewport save) _restoreMapState()— applies persisted base style and re-activates visible overlays
User interactions and programmatic API calls flow through LayersControl → BusinessLogicService → UIManager + StateService. Events are emitted on the shared EventEmitter and received by external subscribers.
onRemove(): detaches map event listeners, nulls map/container references, removes DOM elementdestroy(): full teardown — removes all overlays, tears down all services, removes control from map if still attached
Trigger: user clicks a base radio button, or layersControl.setBaseLayer(id) is called.
BusinessLogicService.setBaseLayer(id)validates the ID againstoptions.baseStylesStateService.setBase(id)— persists new base ID, emitsbasechangeUIManager.applyBaseStyle(id):- Destroys the existing deck.gl
MapboxOverlay - Calls
map.setStyle(newStyle) - Listens for the
styledataevent
- Destroys the existing deck.gl
- On
styledata:- Recreates the deck.gl
MapboxOverlaywithinterleaved: true - Re-activates all currently-visible overlays (50 ms delay)
- Emits
styleloadwith{ baseId }
- Recreates the deck.gl
UIManager.updateBaseUI()— updates radio button state immediately (snappier UX)
Important: Never manually re-activate overlays after calling setBaseLayer. The styledata callback handles it automatically.
Trigger: user checks an overlay checkbox, or layersControl.showOverlay(id) is called.
BusinessLogicService.showOverlay(id)checks if already visible (noop if so)StateService.setOverlayVisibility(id, true)— persists stateUIManager.activateOverlay(id, isUserInteraction):- If
forcedBaseLayerIddiffers from current base: apply viewport now, then callBusinessLogicService.setBaseLayer()and return early (base switch'sstyledatacallback will re-activate the overlay) - Apply
viewportconfig (fitBounds / center+zoom / bearing / pitch) ifisUserInteraction - If
overlay.onCheckedexists: call_executeOnChecked()(emitsloading, awaits callback, emitssuccessorerror) - Build deck.gl layer instances from
overlay.deckLayers - Apply zoom filter check
- Add layers to the deck overlay via
_updateDeckOverlay() - Update UI state (checkbox, opacity slider)
- If
For overlays with an onChecked async callback:
UIManager._executeOnChecked(overlayId, overlay, isUserInteraction)is called- Emits
loadingevent - Builds a
contextobject (see ONECHECKED.md) and callsoverlay.onChecked(context) - Inside the callback, the consumer typically:
- Checks
context.getCache()to skip if already loaded - Fetches data
- Calls
context.setOverlayConfig({ deckLayers: [...] })to inject layers into the overlay config - Calls
context.setCache({ loaded: true })to mark as done
- Checks
- After
onCheckedresolves,UIManagerreads the (now-updated)overlay.deckLayersand creates deck.gl layer instances - Emits
successevent
On error: emits error event, overlay is not shown. The UI displays a retry button.
Group ON (checking the parent checkbox):
BusinessLogicService.setGroupVisibility(id, true)- Finds all overlays where
overlay.group === id - Checks if any are individually marked
visible: true- If none: sets all to
visible: true(first-click → activate all) - If some: activates only those that were individually visible
- If none: sets all to
- Calls
uiManager.activateOverlay()for each qualifying overlay - Emits
overlaygroupchange { id, visible: true }
Group OFF (unchecking the parent checkbox):
- Sets all child overlay individual state to
visible: false - Calls
uiManager.deactivateOverlay()for each - Updates group and overlay UI
- Emits
overlaygroupchange { id, visible: false }
State is saved to localStorage automatically:
- Overlay/base changes: saved immediately (synchronous state mutation, async debounced write)
- Viewport: saved 500 ms after the last
moveendevent (debounced inLayersControl._setupMapEventListeners) - Key: the second argument to
new StateService(eventEmitter, key)
State restored in _restoreMapState():
- Loads persisted
baseID → triggers base style switch (which restores visible overlays viastyledata) - If persisted base doesn't exist in current
baseStyles: resets todefaultBaseId, updates UI - Viewport: applies 400 ms after base switch (or 100 ms if no switch)
- Overlays: if no base switch was triggered, manually activates each visible overlay (200 ms delay)
- Adds/updates config in
options.overlays BusinessLogicService.addOverlay(config)— callsstateService.initOverlay()(respectsdefaultVisible)- If the new overlay's state is
visible: true, activates it immediately uiService.updateOverlays()— re-renders the overlays section of the UI
BusinessLogicService.removeOverlay(id)— deactivates deck.gl layers, clears loading/error state, removes fromStateService- Removes from
options.overlays uiService.updateOverlays()— re-renders the overlays section
When an overlay has minZoomLevel or maxZoomLevel:
- On activation: if current zoom is out of range, overlay is hidden and
zoomfilter { id, filtered: true }is emitted - On map zoom:
UIManagerchecks all active overlays; shows/hides as they enter/leave range; emitszoomfilteraccordingly
User interaction / API call
│
▼
LayersControl (public facade)
│
▼
BusinessLogicService (orchestrator)
│
├──► StateService (state mutation + emit basechange/overlaychange/…)
│
└──► UIManager (DOM + deck.gl update + emit loading/success/error/zoomfilter/styleload)
│
└──► EventEmitter (shared bus — external subscribers receive all events)
All events flow through the single shared EventEmitter instance. External subscribers register via layersControl.on(event, callback).