更改边界后,Google Maps Markers异常刷新

时间:2019-06-21 06:45:08

标签: javascript google-maps vue.js vue-cli

我已经完成了有关使用Vue和Google Maps的餐厅应用程序的项目。 一切正常,但是我仍然有一个无法通过标记纠正的错误。

当我在地图上移动且边界发生变化时,一些标记消失了。当我回到他们的位置时,它们不会一直出现。 如果我用光标快速移动,它们很有可能再次出现在地图上。

我在Chrome的Vue控制台中签到了,这里一切都很好。 即使在屏幕上并不总是可见,标记也会消失并出现在地图范围内。

API分为三个组件,我也使用VueX。

Google地图组件(创建地图)

<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地方信息):

<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标记和MainMap中,标记是使用Computed处理的。 我无法确定问题出在哪里。

总结:如果标记的位置是否在边界之内,则动态删除和创建这些标记(对于Vue,在Chrome开发者控制台中可见),则它们不一定在地图上可见。

2 个答案:

答案 0 :(得分:0)

如果在更改边界时重建了标记,则所有标记都必须先删除/删除,然后在地图上重置。

答案 1 :(得分:0)

这是解决此问题的方法:

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

只需添加:key即可纠正此错误,并且标记现在可以正确显示在地图上。