Closed Gugustinette closed 1 month ago
Hey, as we were talking about, I made this implementation using @vue-leaflet/vue-leaflet
and leaflet
packages :
Step 1 : I made a plugin to define everything you need :
// plugins/leaflet.client.ts
import {YourComponent} from '@vue-leaflet/vue-leaflet';
import * as L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import 'leaflet.markercluster';
import 'leaflet.markercluster/dist/MarkerCluster.css';
import 'leaflet.markercluster/dist/MarkerCluster.Default.css';
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.component('YourComponent', YourComponent);
return {
provide: {
L,
},
};
});
I had to provide L aswell.
Step 2 : In my component.
Note that I wrapped it with <client-only>
as I had issues with SSR (which is sad when I see how much markers I have to put on my map).
<template>
<l-map
ref="map"
:center="leafletOptions.center"
:max-zoom="leafletOptions.maxZoom"
:min-zoom="leafletOptions.minZoom"
:options="{ tap: false }"
:use-global-leaflet="true"
:zoom="leafletOptions.zoom"
:zoom-animation="true"
@ready="onLeafletReady"
>
<template v-if="leafletReady">
<l-tile-layer :url="leafletOptions.url" />
</template>
</l-map>
</template>
<script lang="ts" setup>
import * as L from 'leaflet';
import {MarkerClusterGroup} from 'leaflet.markercluster';
import {LMap, LTileLayer} from '@vue-leaflet/vue-leaflet';
import {isClient} from '@vueuse/shared';
const leafletReady = ref(false);
const leafletObject = ref();
const map = ref();
// Using it to be sure that the map is correctly instanciated, maybe there's a better way ?
const onLeafletReady = async () => {
await nextTick();
leafletObject.value = map.value;
leafletReady.value = true;
};
/**
* The magic goes here,
* but I got a Typescript issue with the new MarkerClusterGroup().
*/
const createMarkers = async () => {
const markerCluster = new MarkerClusterGroup();
let markers: any[] = [];
const markerIcon = L.divIcon({
className: 'location-marker',
html: '<img src="/icons/location.svg" alt="">'
});
locations.value.forEach((location: any) => {
const options = {title: location.nom, clickable: true, draggable: false, data: location, icon: markerIcon};
// Just a regex check to ensure that I don't create a broken marker
// (as it completely break the behavior of the whole map)
if (checkCoordinates(location.lat, location.long)) {
const marker = L.marker([location.lat, location.long], options);
... // just a bit of logic for my own purpose
// I had my marker to the marker array then I add them as layer
markers.push(marker);
markerCluster.addLayers(markers);
}
});
// Then I add the whole cluster layer to the map
map.value.leafletObject.addLayer(markerCluster);
};
// I watch for leafletReady to create markers after, but as I said maybe there's a better way to do.
watch(leafletReady, async () => {
if (isClient) {
await createMarkers();
...
}
});
</script>
And the result is :
But thing is I have some client issues due to this solution, You have to put a placeholder before the map completely load.
Reported type issue with leaflet.markercluster here https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/69707
Issue was closed because of to the branch being merged, but the feature isn't correctly working yet.
The following composable doesn't work in production :
import type { MarkerOptions, Map } from 'leaflet';
interface MarkerProps {
name?: string;
lat: number;
lng: number;
options?: MarkerOptions;
}
interface Props {
leafletObject: Map;
markers: MarkerProps[];
}
export const useMarkerCluster = async (props: Props) => {
// Get Leaflet from the window object
const L = window.L;
// Lazy-load leaflet.markercluster
// Importing it at the top level will cause errors because it could be loaded before the Leaflet library
const { MarkerClusterGroup } = await import('leaflet.markercluster');
// Initialize marker cluster
const markerCluster = new MarkerClusterGroup();
// For each marker in props
props.markers.forEach((location: any) => {
// Create a Leaflet marker
const marker = L.marker([location.lat, location.lng], {
title: location.name,
...location.options,
});
// Add the marker to the cluster
markerCluster.addLayer(marker);
});
// Add the marker cluster to the map
props.leafletObject.addLayer(markerCluster);
}
For now, it shows TypeError: Cannot add property MarkerClusterGroup, object is not extensible
in the browser console after accessing the map in production build.
The error comes from this line in the Leaflet.markercluster plugin :
export var MarkerClusterGroup = L.MarkerClusterGroup = L.FeatureGroup.extend({
...
Leaflet.markercluser assumes Leaflet was already imported and initialized in the browser, which seems to be fine. But in production, the L object seems frozen for some reason ?
We will probably face the same problem with other plugins such as Leafet.heat, Leaflet.VectorGrid and others, that uses the same method.
We should support Leaflet.markercluster.
Either by documenting a way to use the plugin (similar to Leaflet.draw documentation) or providing Vue components.