Маркеры Google Maps странно обновляются при изменении границ - PullRequest
0 голосов
/ 21 июня 2019

Я закончил свой проект о приложении для ресторана, используя Vue и Google Maps. Все работает, но у меня все еще есть ошибка, которую мне не удалось исправить с помощью маркеров.

Когда я двигаюсь по карте и границы меняются, некоторые маркеры исчезают. Когда я вернусь на свою позицию, они не будут появляться снова и снова. Если я двигаюсь быстро с курсором, есть большая вероятность, что они снова появятся на карте.

Я проверил в консоли Vue Chrome, и все здесь хорошо. Маркеры исчезают и появляются в границах карты, даже если они не всегда видны на экране.

API разделен на три компонента, и я также использую VueX.

Компонент Google Map (создать карту)

<template>
  <div class="main">
    <div class="google-map" v-bind:id="mapName" ref="mainMap">
    </div>
<!--    Tuto ici : https://itnext.io/new-unnamed-post-8da9cdbf5df3-->
    <template v-if="Boolean(this.google) && Boolean(this.map)">
      <slot :google="google" :map="map"></slot>
    </template>
  </div>
</template>

<script>
  // Utilisation du plugin pour charger de manière asynchrone l'API
  const GoogleMapsAPILoader = require('google-maps-api-loader');

  export default {
    name: 'google-map',
    props: [
      'name',
      'defaultCenter'
    ],
    data: function() {
      return {
        google: null,
        mapName: this.name + "-map",
        userCoord: {},
        markers: [],
        map: null,
        bounds: null,
        infoWindow: null,
      }
    },
    // Petit plugin pour loader de manière asynchrone l'API Google et éviter des erreurs
    async mounted() {
      const google = await GoogleMapsAPILoader({
        apiKey: 'APIKEY&libraries=places'
      })
      this.google = google
      // Appel de InitMap, et des listeners
      this.initMap();
      this.addChangeBoundsListener();
      this.openAddRestaurant();
    },
    methods: {
      // Initialise la carte
      initMap() {
        // Pour y faire référence plus facilement
        const element = this.$refs.mainMap
        const options = {
          center: this.defaultCenter,
          zoom: 12,
        }
        this.map = new this.google.maps.Map(element, options);
        this.infoWindow = new this.google.maps.InfoWindow;
        // Emet google et map à MainMap
        this.$emit('map-initialized', {
          google: this.google,
          map: this.map
        })
      },
      addChangeBoundsListener() {
        // Pour utiliser les bounds pour l'affichage des restaurants dans la liste
        google.maps.event.addListener(this.map, 'bounds_changed', (event) => {
          this.$emit('map-bounds-changed')
        })
      },
      openAddRestaurant() {
        // Emet l'event pour ajouter un restaurant au click sur la carte
        google.maps.event.addListener(this.map, 'click', (event) => {
          this.$emit('map-clicked', event);
        })
      },
    }
  };
</script>

<style scoped>
  @media screen and (min-width: 446px) and (max-width: 1200px) {
    .main {
      margin-bottom: 1rem;
    }
  }

  .google-map {
    width: 100%;
    height: 600px;
    margin: 0 auto;
    border: 2px solid #26A65B;
    border-radius: 2rem;
  }
</style>

Компонент маркеров Google (показать маркеры):

<template>
  <div class="google-markers">

  </div>
</template>


<script>
  export default {
    name: 'google-markers',
    props: {
      google: {
        type: Object,
        required: true
      },
      map: {
        type: Object,
        required: true
      },
      marker: {
        type: Object,
        required: true
      }
    },
    data() {
      return {
        mapMarker: null
      }
    },
    mounted() {
      // Création des markers
      this.mapMarker = new this.google.maps.Marker({
        position: this.marker.position,
        map: this.map,
        marker: this.marker,
        icon: this.getIconUrl(this.marker.type)
      })
      // Ajout du listener click sur icon ouvre composant ReadComments
      this.mapMarker.addListener('click', () => {
          if (this.marker.type !== 'user') {
            this.$router.push({
              path: `/read-comments/${this.marker.id}`
            });
          }
        });
    },
    // Pour supprimer les markers avant de les redessiner
    beforeDestroy() {
      if (this.marker.type === 'user') console.log('je disparais');

      this.mapMarker.setMap(null)
    },
    methods: {
      // Dessiner les markers
      getIconUrl(markerType) {
        let icon
        switch (this.marker.type) {
          case 'restaurant':
            icon = 'https://img.icons8.com/ios/50/000000/restaurant-table.png';
            break;
          case 'user':
            console.log('user')
            icon = 'https://img.icons8.com/color/48/000000/marker.png';
            break;
          default:
            icon = 'https://img.icons8.com/ultraviolet/48/000000/record.png';
            break;
        }
        return icon
      }
    },
    computed: {
      // Redessine les markers
      refreshIcon() {
        this.getIconUrl(this.marker.type);
      }
    }
  }
</script>


<style scoped>

</style>

MainMap (используйте два других компонента, задайте геолокацию, создайте маркеры, позвоните в Google Places):

<template>
  <google-map :center="customCenter" :defaultCenter="defaultCenter" @map-initialized="initialize" @map-bounds-changed="selectVisibleMarker" @map-clicked="openAddRestaurant">
    <template slot-scope="{ google, map }">
      <google-markers v-for="marker in markers" :marker="marker" :map="map" :google="google"></google-markers>
      <google-markers v-if="userMarker !== {}" :marker="userMarker" :map="map" :google="google"></google-markers>
    </template>
  </google-map>
</template>

<script>
  import GoogleMap from './GoogleMap'
  import GoogleMarkers from './GoogleMarkers'

  export default {
    components: {
      GoogleMap,
      GoogleMarkers
    },
    data: function() {
      return {
        google: null,
        mapName: this.name + "-map",
        userCoord: {},
        userMarker: {
          type: 'user'
        },
        marker: null,
        map: null,
        bounds: null,
        infoWindow: null,
        position: {
          lat: null,
          lng: null
        },
        defaultCenter: {
          lat: 48.842702,
          lng: 2.328434
        },
        customCenter: {
          lat: null,
          lng: null
        }
      }
    },
    methods: {
      // Vient de GoogleMap
      initialize(data) {
        this.map = data.map
        this.google = data.google

        this.askGeolocation()
      },
      // Demande si l'utilisateur accepte la géolocalisation, et recentre la carte sur sa position si acceptée.
      askGeolocation() {
        if (navigator.geolocation) {
          navigator.geolocation.getCurrentPosition((position) => {
            const pos = {
              lat: position.coords.latitude,
              lng: position.coords.longitude
            };
            this.customCenter = pos
            this.userCoord = pos
            this.userMarker = {
              ...this.userMarker,
              position: pos,
            }
            this.map.setCenter(this.customCenter)
            this.setPlaces(pos);

          }, () => {
            this.handleLocationError(true, this.defaultCenter);
            this.setPlaces(this.defaultCenter);
          });
        } else {
          this.handleLocationError(false, this.defaultCenter);
          this.setPlaces(this.defaultCenter);
        }
      },
      handleLocationError(browserHasGeolocation, pos) {
        this.map.setCenter(pos)
      },
      // selectVisibleRestaurant dépend du tri et de la zone d'affichage de la carte, et est utilisé par Map et List
      selectVisibleMarker() {
        this.$store.commit('setBoundsValue', this.map.getBounds())
        this.$store.commit('selectVisibleRestaurant')
      },
      // ouvre le composant AddRestaurant avec lat et lng en query
      openAddRestaurant(event) {
        this.$router.push({
          path: '/add-restaurant/',
          query: {
            lat: event.latLng.lat(),
            lng: event.latLng.lng()
          }
        });
      },
      // Google Places
      setPlaces(location) {
        const service = new google.maps.places.PlacesService(this.map);
        // Appel l'action getData du Store
        this.$store.dispatch('getData', {
          service,
          location
        })
      }
    },
    computed: {
      // Génère les markers
      markers() {
        const markersArray = [
          ...this.$store.getters.getRestaurantList.map((restaurant, index) => {
            return {
              id: restaurant.ID,
              position: {
                lat: parseFloat(restaurant.lat),
                lng: parseFloat(restaurant.long),
              },
              type: 'restaurant'
            }
          })
        ]
        if (this.userMarker !== {}) {
          markersArray.push(this.userMarker)
        }
        return markersArray
      }
    }
  }
</script>

Магазин:

import Vue from 'vue';
import Vuex from 'vuex';

import restaurantFactory from '../interfaces/restaurantFactory';

Vue.use(Vuex);

export const store = new Vuex.Store({
  state: {
    restaurantList: [],
    visibleRestaurant: [],
    sortValue: [],
    boundsValue: {}
  },
  getters: {
    // Obtenir l'ID des restaurants
    getRestaurantById: (state) => {
      return (id) => {
        const restaurantIndex = getRestaurantIndex(state.restaurantList, id);
        console.log({
          id,
          restaurantIndex
        });
        return state.restaurantList[restaurantIndex];
      };
    },
    getRestaurantList: state => {
      return state.visibleRestaurant;
    },
    getSortValue: (state) => {
      return state.sortValue;
    },
    getBoundsValue: (state) => {
      return state.boundsValue;
    },
    // Calcul de la moyenne des notes données en commentaires
    // getRestaurantAvgRating: (state) => {
    //   return (id) => {
    //     const restaurantIndex = getRestaurantIndex(state.restaurantList, id);
    //     const {
    //       ratings
    //     } = state.restaurantList[restaurantIndex];

    //     return computeAvgRatings(ratings)
    //   };
    // }
  },
  mutations: {
    setRestaurantList: (state, {
      list
    }) => {
      state.restaurantList = list;
    },
    // Définit les restaurants à afficher en fonction des limites de la carte et du tri par moyenne
    selectVisibleRestaurant(state) {
      const bounds = state.boundsValue;
      const range = state.sortValue;
      state.visibleRestaurant = state.restaurantList.filter((restaurant) => {
        let shouldBeVisible = true;
        let isInMap = true;
        let isInRange = true;
        // Limites cartes
        if (bounds) {
          isInMap = restaurant.long >= bounds.ga.j && restaurant.long <= bounds.ga.l && restaurant.lat >= bounds.na.j && restaurant.lat <= bounds.na.l;
          shouldBeVisible = shouldBeVisible && isInMap;
        }
        // Moyenne des notes
        if (range && range.length === 2) {
          isInRange = restaurant.avgRating >= range[0] && restaurant.avgRating <= range[1];
          shouldBeVisible = shouldBeVisible && isInRange;
        }

        return shouldBeVisible;
      });
    },
    setBoundsValue: (state, bounds) => {
      state.boundsValue = bounds;
    },
    setSortValue: (state, range) => {
      state.sortValue = range;
    },
    // Ajoute un restaurant en ajoutant automatiquement un champ avgRating et un ID (le dernier +1)
    addRestaurant: (state, { newRestaurant }) => {

      const ratings = newRestaurant.ratings || []

      const restaurantToAdd = {
        ...newRestaurant,
        ratings,
        avgRating: computeAvgRatings(ratings),
        ID: getLastId()
      }

      state.restaurantList.push(restaurantToAdd)
      state.visibleRestaurant.push(restaurantToAdd)

      function getLastId() {
        const lastId = state.restaurantList.reduce((acc, restaurant) => {
          if (acc < restaurant.ID) {
            return restaurant.ID
          }
          return acc
        }, 0)

        return lastId + 1
      }
    },
    // Ajoute un commentaire
    addComment: (state, {
      restaurantId,
      comment
    }) => {
      const restaurantIndex = getRestaurantIndex(state.restaurantList, restaurantId);

      state.restaurantList[restaurantIndex].ratings.push({
        ...comment
      })

      const restaurantRating = computeAvgRatings(state.restaurantList[restaurantIndex].ratings);
      state.restaurantList[restaurantIndex].avgRating = restaurantRating;
    }
  },
  // Fait appel à restaurantFactory et ajoute les restaurants de la liste JSON et de GooglePlaces
  actions: {
    getData: async function (context, { service, location }) {
      const restaurantList = await restaurantFactory.getRestaurantList(service, location)

      restaurantList.forEach((newRestaurant) => context.commit('addRestaurant', { newRestaurant }))
    },
  }
});

// Fonction helper pour getRestaurantById
function getRestaurantIndex(restaurantList, id) {
  return restaurantList
    .findIndex((restaurant) => restaurant.ID === parseInt(id))
}
// Fonction helper pour getRestaurantAvgRating
function computeAvgRatings (ratings) {
  const avgRating = ratings.reduce((acc, rating) => {
    return acc + (rating.stars / ratings.length);
  }, 0);
  return Math.round(avgRating);
}

В Google Markers и MainMap маркеры обрабатываются с помощью Computed. Я не могу определить, откуда возникла моя проблема.

Подводя итог: если маркеры динамически удаляются и создаются (для Vue и видимы в консоли разработчика Chrome), когда их местоположение находится в границах или нет, они не обязательно отображаются на карте.

Ответы [ 2 ]

0 голосов
/ 22 июня 2019

Вот решение этой проблемы:

<google-markers v-for="marker in markers" :key='marker' :marker="marker" :map="map" :google="google"></google-markers>

Просто добавление ключа: исправило эту ошибку, и маркеры теперь правильно отображаются на карте.

0 голосов
/ 21 июня 2019

Если маркеры восстанавливаются при изменении границ, то все маркеры сначала нужно удалить / удалить, а затем сбросить на карте.

...