clearing markers after bounds changed, vue cookbook google map - javascript

I have an issue about clearing markers after updating new bounds. New markers get added to the map but old markers stay still. It's a bit awkward because Vuex state renews every time when I post a request with new bounds...
I am giving the references for better understanding
vue cookbook
and the cookbook's codesandbox (not my code but very much similar.)
map-loader.vue
Here I create the map and make request for bounds without a problem. And every time the map is dragged, I get the new markers in the new array in Vuex.
<template>
<div>
<div class="google-map" ref="googleMap"></div>
<template v-if="Boolean(this.google) && Boolean(this.map)">
<slot
:google="google"
:map="map"
/>
</template>
</div>
</template>
<script>
import GoogleMapsApiLoader from 'google-maps-api-loader'
import { mapGetters } from 'vuex'
export default {
props: {
mapConfig: Object,
apiKey: String,
info_and_center: Function,
},
data() {
return {
google: null,
map: null
}
},
async mounted() {
const googleMapApi = await GoogleMapsApiLoader({
apiKey: this.apiKey
})
this.google = googleMapApi
this.initializeMap()
},
watch:{
mapConfig(old_ad, new_ad){
this.initializeMap()
}
},
methods: {
initializeMap() {
const mapContainer = this.$refs.googleMap
this.map = new this.google.maps.Map(
mapContainer, this.mapConfig
)
let self = this;
this.google.maps.event.addListener((this.map), 'idle', function(event) {
self.get_markers();
});
},
get_markers(){
let bounds = this.map.getBounds();
let south_west = bounds.getSouthWest();
let north_east = bounds.getNorthEast();
let payload = {
"from_lat": south_west.lat(),
"to_lat": north_east.lat(),
"from_lng": south_west.lng(),
"to_lng": north_east.lng(),
}
// manually clearing the array of markers
this.$store.state.project.projects = []
console.log(this.get_projects)
// it's cleared
this.$store.dispatch("load_projects_by_coords", payload)
},
},
computed: {
...mapGetters([
"get_projects"
])
}
}
</script>
UPDATED
Normally I don't need to do that, but inside the get_markers() I wrote code to clear get_projects before new dispatch but still, old markers stay still.
map.vue
<template>
<GoogleMapLoader
:mapConfig="mapConfig"
apiKey="my_key"
>
<template slot-scope="{ google, map}">
<GoogleMapMarker
v-for="marker in get_projects"
:key="marker.id"
:marker="marker"
:google="google"
:map="map"
/>
</template>
</GoogleMapLoader>
</template>
<script>
import GoogleMapLoader from './google-map-loader'
import GoogleMapMarker from './google-map-marker'
import { mapSettings } from './helpers/map-setting'
import { mapGetters } from "vuex";
export default {
components: {
GoogleMapLoader,
GoogleMapMarker,
},
computed: {
...mapGetters([
"get_projects",
"get_search_address_lat",
"get_search_address_lng",
]),
mapConfig () {
return {
...mapSettings,
center: this.mapCenter
}
},
mapCenter () {
return {lat: this.get_search_address_lat, lng: this.get_search_address_lng}
},
},
}
As you see, I am iterating over the new markers inside the get_projects without any problem. But old markers stay still, although when I console.log(this.get_projects) only new markers are in there after the bounds have changed. So the question is How can I update the map with the new markers?
markers.vue
<script>
export default {
props: {
google: {
type: Object,
required: true
},
map: {
type: Object,
required: true
},
marker: {
type: Object,
required: true
},
},
mounted() {
let marker = new this.google.maps.Marker({
position: this.marker,
marker: this.marker,
map: this.map,
})
var contentString = "test";
var infowindow = new this.google.maps.InfoWindow({
content: contentString
});
this.google.maps.event.addListener(marker, 'click', function() {
this.map.setCenter(marker.getPosition());
infowindow.setContent(contentString);
infowindow.open(this.map, marker);
});
},
render(){},
}
</script>

I have not worked with this API before. But I see that removing markers is not covered in the cookbook link you posted. What I think is happening is that you are registering a new marker on mounted, which is fine of course, but you're not removing it when the component is destroyed.
The documentation says to use setMap on the Marker to null, in order to remove it. So maybe if you kept the reference to the marker created in the mounted hook, you can remove it in the beforeDestroy hook.
GoogleMapMarker.vue
data: () => ({
mapMarker: null
}),
mounted() {
const { Marker } = this.google.maps;
this.mapMarker = new Marker({
position: this.marker.position,
marker: this.marker,
map: this.map,
icon: POINT_MARKER_ICON_CONFIG
});
},
beforeDestroy() {
this.mapMarker.setMap(null);
},
ps. let self = this; is unnecessary.
let self = this;
this.google.maps.event.addListener((this.map), 'idle', function(event) {
self.get_markers();
});
Anonymous function:
this.google.maps.event.addListener((this.map), 'idle', event => {
this.get_markers();
});

Related

In a nuxt application, console.logging beyond a certain level of nesting returns undefined. How to reach deeply nested elements?

In a nuxtjs application I am trying to access a div in a legend provided by a map from ArcGIS. This div is nested quite deeply in my map element. I gave the map element a ref map in order to reach it.
<template>
<div id="viewDiv" ref="map" class="h-full"></div>
</template>
This console log
console.log(
'inTheMap?',
this.$refs.map.children[0].children[2].children[0].children[1]
.children[0]
)
will return a div but the div that I want to reach is nested deeper. Unfortunately if I console.log just one level deeper (if I add .children[0]), the console.log returns undefined. My question: is there a limit to how deep we can console.log nested elements ? If yes, is there another way to reach deeply nested elements ?
This is (part of) the code for the map:
<template>
<div id="viewDiv" ref="map" class="h-full"></div>
</template>
<script>
import Map from '#arcgis/core/Map'
import MapView from '#arcgis/core/views/MapView'
import esriConfig from '#arcgis/core/config'
import FeatureLayer from '#arcgis/core/layers/FeatureLayer'
import Legend from '#arcgis/core/widgets/Legend'
import Search from '#arcgis/core/widgets/Search'
export default {
props: {
selectedTab: {
type: Number,
default: 3,
},
},
data() {
return {
project:
'https://...',
countries:
'https://...',
projectLyr: undefined,
countryLyr: undefined,
searchWidget: undefined,
legend: undefined,
map: undefined,
view: undefined,
fieldName: 'composite_risk',
renderer: {},
filter: "dev_type = 'road and railway'",
impactId: 0,
hasKmField: true,
buttonDescription: 'Total Environmental Risk',
legendSymbol: [],
}
},
mounted() {
esriConfig.apiKey =
'myToken'
this.map = new Map({ basemap: 'osm-light-gray' })
this.view = new MapView({
map: this.map,
center: [15, 50],
zoom: 3,
container: 'viewDiv',
})
this.projectLyr = new FeatureLayer({
url: this.project,
outFields: ['*'],
title: 'Legend',
})
this.countryLyr = new FeatureLayer({
url: this.countries,
outFields: ['*'],
title: 'Legend',
})
this.view.popup = {
dockOptions: {
position: 'bottom-left',
},
}
this.legend = new Legend({
view: this.view
})
this.view.ui.add(this.legend, 'top-right')
this.updateLayer({
value: 'composite_risk',
hasKmField: true,
title: 'Total Environmental Risk',
})
},
methods: {
updateLayer(value) {
if (typeof value === 'number') {
this.filter =
value === 1
? "dev_type = 'road' OR dev_type = 'railway'"
: "dev_type = 'road and railway'"
value = {
value: 'composite_risk',
hasKmField: true,
title: 'Total Environmental Risk',
}
this.view.popup.close()
this.impactId = 0
}
if (!value.isFilter) {
this.fieldName = value.value
this.hasKmField = value.hasKmField
this.buttonDescription = value.title
} else {
this.filter = `${value.value}`
}
this.$nextTick(() => {
if (this.selectedTab === 0) {
this.map.remove(this.projectLyr)
this.map.add(this.countryLyr)
this.filtering(value)
} else {
this.map.remove(this.countryLyr)
this.map.add(this.projectLyr)
this.filtering(value)
}
this.layer.popupTemplate =
this.selectedTab === 0
? this.popupTemplateCountry
: this.popupTemplateProject
})
console.log(
'inTheMap?',
this.$refs.map.children[0].children[2].children[0].children[1]
.children[0]
)
},
}
This is the div I would like to hide:
In order to reach the element you can start on the container node of the legend.
This is a step by step code sample in order to get to the desire element. There is no exist checks in order to simplify.
// first get all service entries of the legend container
const legend_services = legend.container.querySelectorAll(".esri-legend__service");
// this will have the same order as they have in the map
// in this case we want the fist legend service
const desire_legend_service = legend_services[0];
// now you want the legend layer entry in the legend service
const legend_layer = desire_legend_service.querySelector(".esri-legend__layer");
// now in your case you want to hide the first table of the legend layer
const legend_layer_first_table = legend_layer.querySelector(".esri-legend__layer-table");
legend_layer_first_table.classList = "hide";
hide class is just,
.hide {
display: none;
}
Now, one thing in order for this to work is that the legend needs to be display on screen. You need an strategy to secure this before searching for the div element to hide.

Mapbox + Vue.js - Error: Style is not done loading

I am using Mapbox with Vue.js, and when adding a geojson layer to the map, keep running into this error message:
Uncaught (in promise) Error: Style is not done loading
I have tried many variations to no avail. My question is, how do I ensure the proper order of execution so the layer is always added? I have wrapped the function in a Javascript Promise, with a workaround adding a setTimeout() so the map/style has time to finish loading, even though it is already within a Mapbox listener, but the error creeps its way back every so often. My current component is as follows (have left out certain functions for brevity):
export default {
mounted() {
new Promise(resolve => {
this.loadMap([this.subjectProperty.longitude, this.subjectProperty.latitude])
if(this.mapLoaded === true) resolve()
}).then(() => {
setTimeout(() => {
this.showParcel()
}, 3000)
})
},
methods: {
loadMap(center) {
var self = this
mapBox = new mapboxgl.Map({
container: 'map',
zoom: 12,
pitch: 45,
center: center,
style: 'mapbox://styles/mapbox/streets-v10'
})
mapBox.on('style.load', function() {
self.mapLoaded = true
})
},
showParcel() {
mapBoxBounds = new mapboxgl.LngLatBounds()
this.parcel.geo_json['geometry']['coordinates'][0].forEach((coord) => {
mapBoxBounds.extend(coord)
})
MapBoxObject.addGeoJsonParcels(this.parcel.geo_json)
MapBoxObject.fitBounds()
}
}
}
try code below:
export default {
mounted() {
this.loadMap([
this.subjectProperty.longitude,
this.subjectProperty.latitude
]).then(_=>{
this.showParcel()
})
},
methods: {
loadMap(center) {
return new Promise(resolve=>{
let mapBox = new mapboxgl.Map({
container: 'map',
zoom: 12,
pitch: 45,
center: center,
style: 'mapbox://styles/mapbox/streets-v10'
})
mapBox.on('style.load', _ => {
resolve()
})
})
},
showParcel() {
mapBoxBounds = new mapboxgl.LngLatBounds()
this.parcel.geo_json['geometry']['coordinates'][0].forEach((coord) => {
mapBoxBounds.extend(coord)
})
MapBoxObject.addGeoJsonParcels(this.parcel.geo_json)
MapBoxObject.fitBounds()
}
}
}

Uncaught TypeError: events.forEach is not a function Leaflet and VueJS

I am making a vue project and I want to use leaflet inside of my components. I have the map showing but I run into an error when I try to add a marker to the map. I get
Uncaught TypeError: events.forEach is not a function
at VueComponent.addEvents (VM2537 Map.vue:35)
at e.boundFn (VM2533 vue.esm.js:191)
at HTMLAnchorElement. (leaflet.contextmenu.js:328)
at HTMLAnchorElement.r (leaflet.js:5)
<template>
<div>
<div id="map" class="map" style="height: 781px;"></div>
</div>
</template>
<script>
export default {
data() {
return {
map: [],
markers: null
};
},
computed: {
events() {
return this.$store.state.events;
}
},
watch: {
events(val) {
this.removeEvents();
this.addEvents(val);
}
},
methods: {
addEvents(events) {
const map = this.map;
const markers = L.markerClusterGroup();
const store = this.$store;
events.forEach(event => {
let marker = L.marker(e.latlng, { draggable: true })
.on("click", el => {
store.commit("locationsMap_center", e.latlng);
})
//.bindPopup(`<b> ${event.id} </b> ${event.name}`)
.addTo(this.map);
markers.addLayer(marker);
});
map.addLayer(markers);
this.markers = markers;
},
removeEvent() {
this.map.removeLayer(this.markers);
this.markers = null;
}
},
mounted() {
const map = L.map("map", {
contextmenu: true,
contextmenuWidth: 140,
contextmenuItems: [
{
text: "Add Event Here",
callback: this.addEvents
}
]
}).setView([0, 0], 1);
L.tileLayer("/static/map/{z}/{x}/{y}.png", {
maxZoom: 4,
minZoom: 3,
continuousWorld: false,
noWrap: true,
crs: L.CRS.Simple
}).addTo(map);
this.map = map;
}
};
</script>
New2Dis,
Here is your example running in a jsfiddle.
computed: {
events: function () {
return this.store.events;
}
},
watch: {
events: function (val) {
this.removeEvents();
this.addEvents(val);
}
},
methods: {
addEvents(events) {
console.log("hoi")
const map = this.map;
const markers = L.markerClusterGroup();
const store = this.$store;
events.forEach(event => {
let marker = L.marker(event.latlng, { draggable: true })
.on("click", el => {
//store.commit("locationsMap_center", event.latlng);
})
.bindPopup(`<b> ${event.id} </b> ${event.name}`)
.addTo(this.map);
markers.addLayer(marker);
});
map.addLayer(markers);
this.markers = markers;
},
removeEvents() {
if (this.markers != null) {
this.map.removeLayer(this.markers);
this.markers = null;
}
}
},
I did replace some things to make it works, like the $store as I don't have it, and removeEvent was not written correctly, so I'm not sure what I actually fixed...
I have also created a plugin to make it easy to use Leaflet with Vue.
You can find it here
You will also find a plugin for Cluster group here
Give it a try and let me know what you think.

i can't access data values from methods in my vue.js component?

I can’t access the values lat , lng from data() in maps() method.
my vue.js component
code link : https://gist.github.com/melvin2016/c8082e27b9c50964dcc742ecff853080
console image of lat,lng
enter image description here
<script>
import Vue from 'vue';
import navbarSec from './navbarSec.vue';
export default {
data(){
return{
lat: '',
lng: '',
mapState: window.mapState,
from:'',
to:'',
placesFrom:[],
placesTo:[]
};
},
components:{
'navbar':navbarSec
},
created(){
var token = this.$auth.getToken();
this.$http.post('http://localhost:3000/book',{},{headers: {'auth':token}}).then(function(data){
this.session = true;
})
.catch(function(data){
this.session = false;
this.$auth.destroyToken();
Materialize.toast(data.body.message, 6000,'rounded');
this.$router.push('/login');
});
if(navigator.geolocation){
navigator.geolocation.getCurrentPosition((data)=>{
this.lat = data.coords.latitude;
this.lng = data.coords.longitude;
this.from=data.coords.latitude+' , '+data.coords.longitude;
});
}else{
Materialize.toast("Cannot Get Your Current Location !", 6000,'rounded');
}
},
mounted(){
if (this.mapState.initMap) {// map is already ready
var val = this.mapState.initMap;
console.log(val);
this.maps();
}
},
watch: {
// we watch the state for changes in case the map was not ready when this
// component is first rendered
// the watch will trigger when `initMap` will turn from `false` to `true`
'mapState.initMap'(value){
if(value){
this.maps();
}
},
from : function(val){
if(val){
var autoComplete = new google.maps.places.AutocompleteService();
autoComplete.getPlacePredictions({input:this.from},data=>{
this.placesFrom=data;
});
}
},
to:function(val){
if(val){
var autoComplete = new google.maps.places.AutocompleteService();
autoComplete.getPlacePredictions({input:this.to},data=>{
this.placesTo=data;
});
}
}
},
methods:{
maps(){
var vm = this;
var lati = vm.lat;
var lngi = vm.lng;
console.log(lati+' '+lngi);
var map;
var latlng = {lat: lati, lng:lngi };
console.log(latlng);
this.$nextTick(function(){
console.log('tickkkk');
map = new google.maps.Map(document.getElementById('maplo'), {
zoom: 15,
center: latlng
});
var marker = new google.maps.Marker({
position: latlng,
map: map
});
});
}
}
}
</script>
This is happening because you're calling maps() in the mounted at which point, the navigator.geolocation.getCurrentPosition((data) => {}) code hasn't resolved. With that in mind, call this.maps() within the getCurrentPosition method i.e:
navigator.geolocation.getCurrentPosition((data)=>{
this.lat = data.coords.latitude;
this.lng = data.coords.longitude;
this.from=data.coords.latitude+' , '+data.coords.longitude;
this.maps()
});
I've not looked in detail but you might be able to change the bits within the maps() method to remove the nextTick stuff when you do this as you'll be calling it a lot later in the cycle at which point everything will have been rendered.

GoogleMaps API with Emberjs

I have a GoogleMap with EmberJs view. Everything works good except the data binding.
I want to bind the map markers with ember-data. If something changes at data level it must reflect on Map.
I tried to use observer, and re-run the makeMarkers method to set the marker, but that seems to be a bad solution.
What would be the best way to bind data with GoogleMaps?
View is deprecated on Ember 2.0, they will be removed at Ember 2.5, make a component like: {{g-map markers=models}}
This component have a collection of items, here markers.
You can implement something like this:
import Ember from 'ember';
import MarkerSync from '../mixin/marker-synchronizer';
/**
* Basic Component to display a google map,
* Service & Marker have to be improved
**/
export default Ember.Component.extend(MarkerSync, {
classNames: 'google-map',
googleMap: Ember.inject.service(),
map: null,
mapOptions: function () {
return {
center: new google.maps.LatLng(-34.397, 150.644),
zoom: 8
};
},
didInsertElement: function () {
this.$().height('100%');
this.$().width('100%');
this.displayGmap();
jQuery(window).on('resize', Ember.run.bind(this, this.handleResize));
},
willInsertElement: function () {
this.get('googleMap').loadScript();
},
displayGmap: Ember.observer('googleMap.isLoaded', function () {
if (!this.get('googleMap.isLoaded')) {
return;
}
const mapOptions = this.mapOptions();
this.set('map', new google.maps.Map(this.$()[0], mapOptions));
}),
handleResize: function () {
if (!this.get('googleMap.isLoaded')){
return;
}
const map = this.get('map');
const center = map.getCenter();
google.maps.event.trigger(map, 'resize');
map.setCenter(center);
},
});
import Ember from 'ember';
/**
* Synchronize collection with map from component.
* Care about to display or remove marker from map,
* Be careful this is not optimized.
**/
export
default Ember.Mixin.create({
markers: null,
_gHash: Ember.A(),
init() {
this._super.apply(this, arguments);
/*
* observes markers array.
*/
this.get('markers').addArrayObserver({
arrayWillChange: Ember.run.bind(this, this.markersWillChange),
arrayDidChange: Ember.run.bind(this, this.markersDidChange)
});
},
/*
* Remove marker from array and remove from map
*/
markerRemoved(marker) {
let gMarker = this.get('_gHash').find(function(item) {
return item.related === marker;
});
gMarker.native.setMap(null);
this.get('_gHash').removeObject(gMarker);
},
/*
* Add marker to `synchronized` array and display on map
*/
markerAdded(marker) {
const gMarker = new google.maps.Marker({
position: {
lat: marker.lat,
lng: marker.lng
},
title: marker.title,
map: this.get('map'),
});
this.get('_gHash').push({
native: gMarker,
related: marker
});
},
/*
* Take care about removed item
*/
markersWillChange(markers, start, removeCount, addCount) {
if (removeCount > 0) {
for (let i = start; i < start + removeCount; i++) {
this.markerRemoved(markers.objectAt(i));
}
}
},
/*
* Take care about added item
*/
markersDidChange(markers, start, removeCount, addCount) {
if (addCount > 0) {
for (let i = start; i < start + addCount; i++) {
this.markerAdded(markers.objectAt(i));
}
}
},
});
import Ember from 'ember';
const get = Ember.get;
/**
* This service lazy load googleMap api.
* Ugly but do the job
*/
export default Ember.Service.extend({
scriptUrl: 'https://maps.googleapis.com/maps/api/js',
isLoaded: Ember.computed.equal('state', 'loaded'),
state: 'none',
init: function () {
let config = this.container.lookupFactory('config:environment');
var apiKey = get(config, 'googleMap.apiKey');
this.set('apiKey', apiKey);
},
normalizeUrl: function () {
var url = this.get('scriptUrl');
url += '?' + 'v=3' + '&' + 'libraries=places' + '&' + 'callback=loadGmap';
if (this.get('apiKey')) {
url += '&key=' + this.get('apiKey');
}
return url;
},
loadScript: function () {
if (this.get('state') !== 'none'){
return false;
}
this.set('state', 'loading');
window.loadGmap = Ember.run.bind(this, function () {
this.set('state', 'loaded');
});
var url = this.normalizeUrl();
return Ember.$.getScript(url).fail(function(){
console.log('getScript fail');
});
},
});
This implementation work but you have to 'sanitize' this code :)
1) Create a component, not a view
2) Use didInsertElement to render google map and observer to update it. Don't forget that observers are synchronous (http://guides.emberjs.com/v1.13.0/object-model/observers/) and you need to do smth like:
somethingChanged: Ember.observer('something', function () {
Ember.run.once(this, '_somethingChanged');
}).on('init'),
_somethingChanged: function () {
/* do smth about changed property here */
}

Categories