--- name: maplibre-layers description: >- Use when adding map layers, managing GeoJSON sources, styling markers, or handling layer interactions. Load for layer registration patterns, markRaw usage, MglGeoJsonSource components, data-driven styling, and layer event handling. Covers the mapLayers store and dynamic layer composition. --- # MapLibre Layers Layer management patterns for map visualization. > **Announce:** "I'm using maplibre-layers to implement map layers correctly." ## Layer Registration Pattern Views register layers via the `mapLayers` store: ```typescript // src/stores/mapLayers.ts import { markRaw } from 'vue' export const useMapLayersStore = defineStore('mapLayers', () => { const layers = ref([]) function setLayers(newLayers: MapLayer[]) { // CRITICAL: markRaw prevents Vue from making components reactive layers.value = newLayers.map(l => ({ ...l, component: markRaw(l.component) })) } function clearLayers() { layers.value = [] } return { layers, setLayers, clearLayers } }) ``` **In a view:** ```typescript // GameView.vue import CandidatesLayer from '@/components/map/CandidatesLayer.vue' import { MAP_KEY } from '@/composables/map/useMapCamera' const mapLayersStore = useMapLayersStore() onMounted(() => { mapLayersStore.setLayers([{ key: 'candidates', component: CandidatesLayer, props: { candidates: candidates, mapKey: MAP_KEY } }]) }) onUnmounted(() => { mapLayersStore.clearLayers() }) ``` **In BaseMap.vue:** ```vue ``` ## GeoJSON Layer Pattern Use `MglGeoJsonSource` with layer components: ```vue ``` ## Data-Driven Styling Use MapLibre expressions for dynamic styling: ```typescript // Interpolate color based on confidence const paintConfig = { 'circle-color': [ 'interpolate', ['linear'], ['get', 'confidence'], 0, '#gray', 0.5, '#yellow', 1.0, '#green' ], 'circle-radius': [ 'interpolate', ['linear'], ['zoom'], 2, 3, // At zoom 2, radius 3 10, 15 // At zoom 10, radius 15 ] } ``` ## Layer Event Handling Handle clicks and hovers on layers: ```vue ``` ## HTML Markers for Labels Use HTML markers for complex labels (Vue components): ```vue ``` **When to use what:** - **Native layers** (MglCircleLayer, MglFillLayer): Thousands of points, GPU-accelerated - **HTML markers** (MglMarker): Complex styling, Vue components, limited count ## Anti-Patterns ### DON'T: Skip markRaw ```typescript // WRONG: Vue makes component reactive (bad performance) layers.value = [{ component: MyLayer }] // CORRECT: markRaw prevents reactivity layers.value = [{ component: markRaw(MyLayer) }] ``` ### DON'T: Forget Cleanup ```typescript // WRONG: Event listeners leak onMounted(() => { map.on('click', 'layer', handler) }) // CORRECT: Clean up on unmount onUnmounted(() => { map.off('click', 'layer', handler) }) ``` ### DON'T: Mutate GeoJSON Directly ```typescript // WRONG: Mutating doesn't trigger reactivity candidatesGeoJson.value.features.push(newFeature) // CORRECT: Create new object candidatesGeoJson.value = { ...candidatesGeoJson.value, features: [...candidatesGeoJson.value.features, newFeature] } ``` ## Layer Ordering Layers render in order added. Control z-order with `beforeId`: ```vue /> ``` ## References See `references/layer-examples.md` for more patterns.