Analyze possibility of incorporating vue-maplibre-gl #123
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
We're currently using our own custom single-file components for integrating maplibre-gl into our VueJS frontend. We're missing a lot of features because we've built it ourselves. I'm considering switching over to using vue-maplibre-gl. The documentation is pretty short, so I need a thorough analysis to determine if it has the features we need.
I'd like you to audit our current usage of maplibre-gl throughout the codebase. List all the different patterns that you find, and group them together by which parts of the system are using the different patterns. Then I'd like you to analyze what capabilities that vue-maplibre-gl has. See if you can create a mapping from our existing patterns to ways we can achieve the same behavior, but with maplibre-gl. This will serve as both a report to aid in making the decision, and as an implementation guide for engineers who will be doing the porting work.
Audit of Current maplibre-gl Usage & vue-maplibre-gl Analysis
Overview
I've audited every file referencing maplibre-gl in the codebase. There are 6 distinct architectural patterns in use, spanning legacy vanilla-JS Web Components, custom Vue SFCs, and a newer component-composition approach. Below I group them by system area and pattern, then map each to vue-maplibre-gl equivalents.
Part 1: Current Usage Patterns by System Area
Pattern A: Legacy Vanilla JS Web Components (static/js/)
These are plain custom elements using Shadow DOM, injected directly into non-Vue pages or legacy views.
map-locator.jsmap-locator-ro.jsmap-cell.jsmap-admin.jsmap-routing.jsmap-aggregate.jsmap-multipoint.jsmap-service-area.jsmap-arcgis-tile.jsmap-proxied-arcgis-tile.jsaddress-suggestion.jsCharacteristics:
connectedCallback→setTimeout(initializeMap, 0)maplibregl.Map()with hard-coded Stadia style URLsaddSource/addLayerinmap.on('load', ...)getAttribute('latitude'),getAttribute('centroid'), etc.)Verdict: These won't benefit from vue-maplibre-gl (they're non-Vue). They'd need to be converted to Vue components or remain as-is.
Pattern B: Monolithic SFC Map Components (ts/components/Map*.vue)
Self-contained components that create and manage their own
maplibregl.Mapinstance directly.Group: Core Map Components
MapLocator.vueMapLocatorDisplay.vueMapAggregate.vueMapMultipoint.vueMapProxiedArcgisTile.vueMapServiceArea.vueMapOperations.vueCharacteristics:
new maplibregl.Map()independently@import url(...)from unpkg CDN orimport "maplibre-gl/dist/maplibre-gl.css"addSource/addLayerinmap.on('load', ...)mapMarkersMap)update:modelValuewithCameraobjects)isInternalUpdateflag on move/zoom events to prevent feedback loopsPattern C: Component Composition System (ts/map/)
A newer, more reusable pattern — renderless components using Vue
provide/inject.Core files:
Map.vue(355 lines) — providesmapinstance viaprovide('map', ...), plus registration functions for child components (registerSource,registerLayer,registerOn,registerOnce)Layer.vue(123 lines) — renderless, registers itself via inject, handles events (click, mouseenter, mouseleave), watches paint/layout changesSource.vue(49 lines) — renderless, registers vector/raster/geojson source configSourceGeoJSON.vue— thin wrapper aroundSourcewithtype="geojson"and:datapropSourceTegola.vue— thin wrapper computing tegola tile URLs from session storeutil.ts— utility functions (bounds helpers, marker bounds)Used by views:
Dash.vue<Map>,<Source>,<Layer>(5 layers: mosquito_source, parcel, service_request, trap, service-area)sudo/UI.vue<Map>,<SourceGeoJSON>,<Layer>sudo/PlanetItemDetail.vue<Map>,<SourceGeoJSON>,<Layer>(fill + line)sudo/PlanetDetail.vue<Map>sudo/Parcel.vue<Map>sudo/ParcelDetail.vue<Map>sudo/SitusDetail.vueCell.vue<Map>but template is stubCharacteristics:
<Map><Source/><Layer/><Layer/>...</Map>Mapvia injected callbacksregisterOn/registerOnceinjectssetPaintPropertyunregisterSourceandunregisterLayerare commented out — cleanup on unmount is brokenPattern D: LayersControl Custom IControl (ts/components/LayersControl.ts)
A standalone TypeScript class implementing
maplibregl.IControlfor toggling layer visibility with checkbox/radio/opacity controls. Based onmaplibre-gl-layers-control.Used by:
MapProxiedArcgisTile.vue(currently commented out:_map.addControl(new LayersControl({...})))Pattern E: Direct Imperative in View Scripts
Some view scripts import maplibregl directly for utility functions:
Communication.vue— importsmaplibreglforboundsForServiceArea/boundsWithPaddingfrom@/map/utilts/types.ts— importsMap as MapLibreMapfrom maplibre-gl for type definitionsPattern F: CSS Only
vite/sync/main.ts— imports"maplibre-gl/dist/maplibre-gl.css"globally<style>blocks import CSS via@import url(...)or@use "~maplibre-gl/dist/maplibre-gl.css"Part 2: vue-maplibre-gl Capabilities
Package:
razorness/vue-maplibre-glv5.6.1 (npm)Dependencies: maplibre-gl ^5.17.0, mitt ^3.0.1, vue ^3.5.27
Also installs peer: vue (already have)
Additional optional: Turf.js (for draw plugin)
Available Components
Map:
<MglMap>Props: Every
MapOptionsfrom maplibre-gl (center,zoom,bounds,style→mapStyle,projection,language, etc.) plusfitBoundsOptions,mapStyle,mapKey(for multi-map).Events: All maplibre-gl events prefixed with
map:(@map:load,@map:click,@map:moveend,@map:zoomend, etc.)Slots: default (for child components)
Controls:
<MglNavigationControl>— zoom +/- buttons<MglGeolocationControl>— locate user<MglFullscreenControl>— fullscreen toggle<MglScaleControl>— scale bar<MglFrameRateControl>— FPS monitor<MglStyleSwitchControl>— style picker<MglAttributionControl>— attribution<MglCustomControl>— custom button controlSources (nestable inside MglMap):
<MglGeoJsonSource>—source-id,data, clustering props<MglVectorSource>—source-id,tiles[],url,bounds, etc.<MglRasterSource>— raster tile sources<MglRasterDemSource>— DEM/elevation<MglImageSource>— single image source<MglCanvasSource>— canvas source<MglVideoSource>— video sourceLayers (nestable inside sources):
<MglFillLayer>—layer-id,source,source-layer,paint,layout,filter<MglLineLayer>— same pattern<MglCircleLayer>— same pattern<MglSymbolLayer>— same pattern, with text/icon props<MglBackgroundLayer>— background<MglHeatmapLayer>— heatmap<MglHillshadeLayer>— hillshade<MglRasterLayer>— raster<MglFillExtrusionLayer>— 3D extrusionMarkers:
<MglMarker>—coordinates,color,scale,offset,anchor,rotation,rotationAlignment,pitchAlignmentdraggableprop is listed in source but commented out (not exposed yet)Draw Plugin (separate build):
<MglDrawControl>— draw polygons, circles, static circles;v-modelforDrawModelComposable API:
useMap(key?)— get map instance and reactive state (isLoaded,isMounted,language)MglDefaults— configure global defaults for the mapKey Features:
mapKey+useMap()languageprop.setData()/.setTiles()/.setUrl()via watchersPart 3: Mapping — Current Patterns → vue-maplibre-gl Equivalents
3.1: Pattern C (Component Composition) — Best Fit
This is the cleanest migration path since it already uses renderless child components.
Current (our code):
vue-maplibre-gl equivalent:
Key differences:
Map.vue→MglMap(mapStyle prop instead ofstyle)Source→MglVectorSource/MglRasterSource/MglGeoJsonSource(propid→source-id)Layer→ typed layers (MglFillLayer,MglLineLayer,MglCircleLayer, etc.) (propid→layer-id,sourceLayer→source-layer)@click→@click(same, but layer components emit MapLayerMouseEvent directly)@mouseenter,@mouseleave,@clickfrom LayerLib sharedAffected views:
Dash.vuesudo/UI.vuesudo/PlanetItemDetail.vuesudo/PlanetDetail.vue<Map/>with no children3.2: Pattern B (Monolithic SFCs) — Migration Path
Each of these components encapsulates specific interactive behaviors beyond just map display.
MapLocator.vue / MapLocatorDisplay.vue:
Current behavior: lock/unlock interaction, draggable markers, camera v-model, marker frame-on-add.
vue-maplibre-gl mapping:
Blockers:
MglMarkerdoesn't exposedraggableprop currently. Would need a custom wrapper or to contribute upstream.MapProxiedArcgisTile.vue:
Current: vector (tegola) + raster (flyover) sources + markers + NavigationControl.
vue-maplibre-gl mapping:
MapServiceArea.vue:
Current: GeoJSON service area + satellite surveillance area.
vue-maplibre-gl mapping:
MapOperations.vue:
Current: 3 GeoJSON line sources for routes, 3 stop-point sources, circle layers, symbol layers with labels, popups on click.
vue-maplibre-gl mapping:
MapAggregate.vue / MapMultipoint.vue:
Similar pattern — tegola vector source + fill layers + markers or click handling.
Consideration for Pattern B: These are more tightly coupled to their domain logic. A phased migration would extract the map initialization into MglMap first, then replace sources/layers piece by piece.
3.3: Pattern A (Vanilla JS Web Components) — Not Migrating
These live outside Vue entirely. They would need full rewrite to Vue components to benefit from vue-maplibre-gl. Not recommended to convert unless those pages are being migrated to Vue anyway.
3.4: Pattern D (LayersControl) — Replace with MglStyleSwitchControl?
The custom
LayersControlprovides checkbox/radio toggle + opacity.MglStyleSwitchControlis for switching full map styles (not individual layers). If layer visibility toggling is needed, could implement with a custom component usinguseMap()to access the map instance directly.3.5: Pattern E (Direct Imperative) — No Change Needed
Utility imports are backward-compatible since vue-maplibre-gl is a wrapper, not a replacement of maplibre-gl.
import maplibregl from "maplibre-gl"continues to work.Part 4: Feature Gap Analysis
mapStylepropMglDefaults.style = "..."or per-instanceMglVectorSourcewithtilespropMglRasterSourcewithtilespropMglGeoJsonSourcewithdataprop.setData()via watcherMglFillLayerMglLineLayerMglCircleLayerMglSymbolLayerMglMarkerdraggableprop exists in source model but commented out in component. Could fork or contribute.MglMarkerhascolorprop, but custom element would need a different approachMglNavigationControlMglStyleSwitchControl@map:moveend+@map:zoomend+ reactivecenter/zoompropsfitBoundsOptionsprop,:boundspropuseOnBoundsUpdateoptionuseMap():interactiveprop or:scrollZoom/:dragPanpropsPart 5: Decision Support Summary
Strengths of vue-maplibre-gl:
useMap(key)for apps with multiple mapsWeaknesses / Gaps:
Recommendation:
The component composition pattern (Pattern C —
Dash.vue,sudo/*) should be the first migration target since it's already architecturally aligned. The monolithic SFCs (Pattern B) are higher effort but still feasible in phases. The legacy Web Components (Pattern A) can be left as-is or converted when those pages are modernized.