inocan-group / vue3-google-map

A set of composable components for easy use of Google Maps in your Vue 3 projects.
https://vue3-google-map.com
MIT License
295 stars 60 forks source link

Can't update MarkerCluster (hide/show & add/remove markers) #261

Closed lpares12 closed 3 months ago

lpares12 commented 5 months ago

Is there any way to add v-if and/or v-show directives to a MarkerCluster?

I'd like to be able to toggle on/off a set of markers using a button. The thing is that there's +20k markers and it takes a bit long to load. So I've tried to make it so the markers are only loaded once the user has pressed the button.

<template>
      <GoogleMap :api-key="mapsApiKey" :center="getMapCenter" :zoom="getMapZoom"
        style="width: 100%; height: 85vh"
        :fullscreen-control="false"
        :street-view-control="false"
        :zoom-control="false"
        ref="mapRef">
        <MarkerCluster v-if="show_markers">
          <Marker v-for="(marker, i) in markers" :options="{ position: marker.position }" :key="i" />
        </MarkerCluster>
        <CustomControl position="BOTTOM_CENTER">
          <v-btn icon @click="toggleMarkers">
            <v-icon>{{ show_markers ? 'mdi-eye' : 'mdi-eye-off' }}</v-icon>
          </v-btn>
        </CustomControl>
      </GoogleMap>
</template>
<script setup lang="ts">
const markers = ref<Array<any>>([]);
const show_markers = ref<boolean>(false);

async function toggleMarkers() {
    if(show_markers.value) return;

    markers.value = getMarkers(); //This function is defined in another file and returns the markers.
    show_markers.value = true;
}
</script>

But this does not seem to work. I always get the following error after pressing the button:

vue3-google-map.js?v=59354c9a:1482 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'fromLatLngToDivPixel')
    at at (vue3-google-map.js?v=59354c9a:1482:16)
    at Re (vue3-google-map.js?v=59354c9a:1474:42)
    at Ce (vue3-google-map.js?v=59354c9a:1478:13)
    at pt.calculate (vue3-google-map.js?v=59354c9a:1583:13)
    at yt.render (vue3-google-map.js?v=59354c9a:1758:58)
    at yt.addMarker (vue3-google-map.js?v=59354c9a:1731:66)
    at D.watch.immediate (vue3-google-map.js?v=59354c9a:725:47)
    at callWithErrorHandling (chunk-C4KGUF5A.js?v=59354c9a:1673:19)
    at callWithAsyncErrorHandling (chunk-C4KGUF5A.js?v=59354c9a:1680:17)
    at job (chunk-C4KGUF5A.js?v=59354c9a:7562:9)

And some warnings looking like:

ProjectsDisplay.vue:199 [Vue warn]: Unhandled error during execution of watcher callback 
  at <Marker options= {position: Proxy(Object), clickable: true, visible: true}clickable: trueposition: Proxy(Object) {lat: 42.4230825519172, lng: -4.23315050386541}visible: true[[Prototype]]: Object key=0 > 
  at <MarkerCluster key=0 > 

But if I were to remove the button and call the toggleMarkers() function straight from the setup in <script setup> then I have no problems and I can see the clusters. It seems like adding the clusters later makes it break.

As a workaround, I've tried removing the v-if from the cluster and using a computedProperty to return markers or not, but that does not seem to work either.

lpares12 commented 5 months ago

Found a workaround, but it's not using vue3-google-map, other than for initialization.

Basically the workaround here is to not use the regular Markers and use the AdvancedMarkers. It seems that clusters with markers are a bit buggy.

<template>
      <GoogleMap :api-key="mapsApiKey" :center="getMapCenter" :zoom="getMapZoom"
        style="width: 100%; height: 85vh"
        :fullscreen-control="false"
        :street-view-control="false"
        :zoom-control="false"
        map-id="MY ID"
        ref="mapRef">
        <CustomControl position="BOTTOM_CENTER">
          <v-btn icon @click="toggleMarkers">
            <v-icon>{{ show_markers ? 'mdi-eye' : 'mdi-eye-off' }}</v-icon>
          </v-btn>
        </CustomControl>
      </GoogleMap>
</template>
<script setup lang="ts">
const cluster = ref<any | null>(null);

async function toggleMarkers() {
    if(show_markers.value) {
        cluster.value.markers = [];
        cluster.value.render();
        show_markers.value = false;
        return;
    }

    const { AdvancedMarkerElement } = await google.maps.importLibrary("marker") as google.maps.MarkerLibrary;

    cluster.value.markers = getMarkers().value.map((marker) => {
        return new AdvancedMarkerElement({
          position: new google.maps.LatLng(marker.position.lat, marker.position.lng),//{ lat: marker.position.lat, lng: marker.position.lng },
        });
    })

    cluster.value.render();
    show_markers.value = true;
}

watch(() => mapRef.value?.ready, (ready) => {
  if(!ready) {
    return;
  }

  const markers: Array<any> = tree_markers.value;
  const map = mapRef.value?.map;
  cluster.value = new MarkerClusterer({ markers, map });
})
HusamElbashir commented 3 months ago

We have released an AdvancedMarker component in v0.21.0 if you want to use that instead with clusters.