My Map.vue is below, with parts of the code abbreviated so I don't expose any information. The Map is a component of my app and I would like to add some interactions to it. However, my method won't work, saying read property 'getPitch' of undefined. How can I initialize the map so that Mapbox's functions are still available later on?
<template>
<div id="map" class="map">
<button #click="getPitch">Test</button>
</div>
</template>
<script>
import mapboxgl from 'mapbox-gl';
export default {
name: "Map",
data() {
return {
};
},
props: {
mapdata: {
type: Object,
default: () => { return {}; }
}
},
beforeUpdate() {
mapboxgl.accessToken = "...";
const map = new mapboxgl.Map({
container: "map",
style: "...",
center: ...,
zoom: 11.85,
});
let mapRecords = this.mapdata.data;
map.once('styledata', function() {
map.addSource('...', {
...
});
map.addLayer({
...
})
});
},
methods: {
getPitch() {
map.getPitch();
}
}
};
</script>
<style scoped>
...
</style>
Solved
First, changed all map instances in beforeUpdate() to this.map. Then, in the getPitch function, added this line: let map = this.map;.
beforeUpdate does not look like the right hook. You probably should be using mounted.
Also, your variable map is out of scope for your getPitch method.
Related
I'm trying to update a map to my current location using a vue onClick which updates props and sends them to my map component. I am using a :key to rerender my map component when my map data changes and I get some new x,y for my map center. (based on the esri/arcgis example I would need to rebuild the map, if anyone knows this to be wrong let me know please)
VUE js arcgis starting documentation:
https://developers.arcgis.com/javascript/latest/guide/vue/
for some reason my map does render again and seems like it's about to load but then it just stays blank.
maybe someone can tell me if this is an issue with the component still persisting in some way after I force it to render again?
my app.vue
<template>
<div id="app">
<web-map v-bind:centerX="lat" v-bind:centerY="long" ref="mapRef"/>
<div class="center">
<b-button class="btn-block" #click="getLocation" variant="primary">My Location</b-button>
</div>
</div>
</template>
<script>
import WebMap from './components/webmap.vue';
export default {
name: 'App',
components: { WebMap },
data(){
return{
lat: -118,
long: 34,
}
},
methods:{
showPos(pos){
this.lat = pos.coords.latitude
this.long = pos.coords.longitude
this.$refs.mapRef.updateCoordinates()
console.log('new location',this.lat,this.long, this.$refs)
},
getLocation(){
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(this.showPos);
} else {
console.log("Geolocation is not supported by this browser.");
}
},
},
};
</script>
my map component
<template>
<div></div>
</template>
<script>
import { loadModules } from 'esri-loader';
export default {
name: 'web-map',
props:['centerX', 'centerY'],
data: function(){
return{
X: this.centerX,
Y: this.centerY,
view: null
}
},
mounted() {
console.log('new data',this.X,this.Y)
// lazy load the required ArcGIS API for JavaScript modules and CSS
loadModules(['esri/Map', 'esri/views/MapView'], { css: true })
.then(([ArcGISMap, MapView]) => {
const map = new ArcGISMap({
basemap: 'topo-vector'
});
this.view = new MapView({
container: this.$el,
map: map,
center: [this.X,this.Y], ///USE PROPS HERE FOR NEW CENTER
zoom: 8
});
});
},
beforeDestroy() {
if (this.view) {
// destroy the map view
this.view.container = null;
}
},
methods:{
updateCoordinates(){
this.view.centerAt([this.X,this.Y])
}
}
};
</script>
I don't think the key you're passing as a prop to web-map serves any purpose since it's not being used inside the component.
You could try, instead, to force update the component as such:
<web-map v-bind:centerX="lat" v-bind:centerY="long" ref="mapRef" />
this.refs.mapRef.$forceUpdate()
This ensures that you're force updating the whole component, but maybe there's a better solution. Instead of re-rendering the entire component, which means having to create the map once again, you could instead keep the component alive and just use an event to update the coordinates.
Based on https://developers.arcgis.com/javascript/3/jsapi/map-amd.html#centerat, you can re-center the map using the centerAt method.
That way the map component has a method like:
updateCoordinates(coord){
this.view.centerAt(coord)
}
And you can call it on the parent with
this.refs.mapRef.updateCoordinates(newCenter)
Hope it helps, let me know if you do any progress.
I think you can test Watch with setInterVal() for a loop to check your location each 1sec
I am trying to create a map using Leaflet and a Vue component. For some reason the "center: " attribute is not accepting my array coordinates for Latitude and Longitude? When I use it in the template html {{ latlng }} I get an array with the correct coordinates. Any help would be greatly appreciated.
<template>
<div id="mapContainer">{{ latlng }}</div>
</template>
<script>
import "leaflet/dist/leaflet.css";
import L from "leaflet";
import axios from 'axios';
export default {
name: "Map",
data() {
return {
map: null,
latlng: []
};
},
methods: {
get_lat_lng: function(){
axios.get('http://127.0.0.1:5000/api/get_latitude')
.then(res => this.latlng.push(res.data))
axios.get('http://127.0.0.1:5000/api/get_longitude')
.then(res => this.latlng.push(res.data))
}
},
created: function(){
this.get_lat_lng()
},
mounted() {
this.map = L.map("mapContainer", {
center: this.latlng,
zoom: 12,
});
L.tileLayer("http://{s}.tile.osm.org/{z}/{x}/{y}.png", {
attribution:
'© OpenStreetMap contributors'
}).addTo(this.map);
},
beforeDestroy() {
if (this.map) {
this.map.remove();
}
}
};
</script>
<style scoped>
#mapContainer {
width: 50vw;
height: 50vh;
}
</style>
You have hit a race condition here. The mounted() hook is called before the axios.get asynchronous call have been completed.
The sequence of calls is probably this 1.created(), 2.mounted(), 3.axios.then()
So, you will need to slightly change your logic so the map is initialized when both the creation has completed and the mount hook has been invoked.
Something like this,
add a flag for the mounted call been executed
data() {
return {
map: null,
mounted: false,
latlng: null
}
move the map creation code into a method
createMap: function() {
this.map = L.map("mapContainer", { center: this.latlng ...
at creation go fetch the data, and if already mounted, create the map
created(){
axios.all([axios.get('http://127.0.0.1:5000/api/get_longitude');
axios.get('http://127.0.0.1:5000/api/get_latitude')])
.then((longresp,latresp)=> {
this.latlng=[latresp.data,longresp.data];
if (this.mounted) this.createMap()
})
and then at mounted, check if by chance the data is already available, and if not, set the flag for the creation of the map
mounted() {
if (this.latlng) this.createMap()
this.mounted = true;
...
Create the Map when the data is available ( when axios promise is successful ).
More on promises: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
Use a indicator until content not ready.
I think there is no need for separate requests, so i merged the 2 axios requests. Your backend should return the [lat, long] array.
<template>
<div>
<!--
I also added a simple loading indicator,
obviously you can go for something more fancy like a spinner
--!>
<p v-if="loading">Loading...</p>
<div id="mapContainer">{{ latlng }}</div>
</div>
</template>
<script>
import "leaflet/dist/leaflet.css";
import L from "leaflet";
import axios from 'axios';
export default {
name: "Map",
data() {
return {
map: null,
loading: false,
latlng: []
};
},
methods: {
setupLeafletMap: function () {
this.map = L.map("mapContainer", {
center: this.latlng,
zoom: 12,
});
L.tileLayer("http://{s}.tile.osm.org/{z}/{x}/{y}.png", {
attribution:
'© OpenStreetMap contributors'
}).addTo(this.map);
}
},
mounted() {
this.loading = true;
axios.get('http://127.0.0.1:5000/api/get_latlng').then(res => {
this.latlng = res.data;
this.setupLeafletMap(); // Creating the Map here ensures that data is already loaded from server side
this.loading = false;
}).catch(error => console.error(error));
},
beforeDestroy() {
if (this.map) {
this.map.remove();
}
}
};
</script>
<style scoped>
#mapContainer {
width: 50vw;
height: 50vh;
}
</style>
Good afternoon, please tell me who knows, I am now learning to add a Google map to the site and do it with this article. I did everything as it was indicated, but the card does not work, please tell me why? Where did I make a mistake? Api-key is correct 100%. And I turn on the map in Google Cloud Platform. I use Vue cli Webpack-simple. My project on GitHub
Screenshot of a broken map
Console Error
Code of index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>google_map</title>
</head>
<body>
<div id="app"></div>
<script src="/dist/build.js"></script>
<script src="https://maps.googleapis.com/maps/api/js?key=[MY_API_KEY]"></script>
</body>
</html>
Component Google.map:
<template>
<div class="google-map" :id="name"></div>
</template>
<script>
export default {
name: 'google-map',
props: ['name'],
data: function () {
return {
map: ''
}
},
computed: {
mapMarkers: function () {
return this.markers
}
},
mounted: function () {
const element = document.getElementById(this.name)
const options = {
zoom: 14,
center: new google.maps.LatLng(59.93, 30.32)
}
this.map = new google.maps.Map(element, options)
},
methods: {}
}
</script>
<style scoped>
.google-map {
width: 640px;
height: 480px;
margin: 0 auto;
background: gray;
}
</style>
Code of App.vue:
<template>
<div class="container">
<google-map :name="name"></google-map>
</div>
</template>
<script>
import googleMap from './components/googleMap.vue'
export default {
data: function () {
return {
name: 'map',
}
},
mounted: function () {
},
components: {googleMap}
}
</script>
Looking at your original code, before you started trying to use GoogleMapsLoader, your error was very simple; you were trying to use window.google in the compiled JS, before it was loaded through googleapis.com. To fix, in your index.html, simply change this:
<script src="/dist/build.js"></script>
<script src="https://maps.googleapis.com/maps/api/js?key=[MY_API_KEY]"></script>
To this:
<script src="https://maps.googleapis.com/maps/api/js?key=[MY_API_KEY]"></script>
<script src="/dist/build.js"></script>
If you want to use GoogleMapsLoader, please follow the documentation they provide; you should be wrapping any calls to the Google Maps API inside a callback function that the loader calls. There are a bunch of different approaches to how to do this, and almost definitely a better way to set up a global instance, but to at least make your code work, this is how you would need to code your mounted function:
mounted: function () {
GoogleMapsLoader.KEY = '[MY_API_KEY]';
GoogleMapsLoader.load(function(google){
const element = document.getElementById(this.name)
const options = {
zoom: 14,
center: new google.maps.LatLng(59.93, 30.32)
}
this.map = new google.maps.Map(element, options)
}.bind(this));
}
I'm trying to add a custom control on my map using the OpenLayers with Vue.js.
I have the component Explore.vue that creates my "map" (olmap) using the OL and I pass it through binding to the child component LeftSideBar2.vue.
When I try to add a new control in my map, the Vue shows the following error:
[Vue warn]: Error in mounted hook: "TypeError: this.olmap.addControl is not a function"
Does someone know what is happening?
My files are:
Explore.vue:
Template:
<explore-left-side-bar2 v-bind:olmap="olmap"/>
Script:
export default {
name: 'Explore',
data () {
return {
olmap: {}
}
},
methods: {
initComponent: function () {
// eslint-disable-next-line
this.olmap = new Map({
target: 'map',
layers: [
baseLayerGroup
],
view: new View({
projection: 'EPSG:4326',
center: [0, 0],
zoom: 5
})
})
}
},
mounted: function () {
this.initComponent()
},
components: {
ExploreLeftSideBar2
}
}
LeftSidebar2.vue:
Script:
export default {
name: 'LeftSideBar2',
props: ['olmap'],
data () {
return {
}
},
methods: {
initComponent: function () {
var sidebar = new Sidebar({ element: 'ol-sb-sidebar', position: 'left' })
this.olmap.addControl(sidebar)
}
},
mounted: function () {
this.initComponent()
},
components: {
LeftSideBarLayerTree
}
}
It looks like you have bound an Object olmap={}to a component which in returns calls a function this.olmap.addControls() that doesn't exist on the object which you pass as a prop. I think you are trying to do instead, is call addControls() on the OpenLayers.Map instance.
As this answer explains, mounted hooks are called for child components before their parents, meaning that LeftSidebar2.vue will call this.olmap.addControl(sidebar) when olmap is still the default empty object declared in the data method of Explore.vue.
There are a cuople of ways you could work around this:
You could initialise olmap in the created hook of Explore.vue rather than the mounted hook.
or
You could use a v-if to exclude LeftSidebar2.vue until after olmap has been initialised.
I'm currently using a Leaflet map (with vue2leaflet).
What I do is pretty much standard:
A list of places is imported from a REST Api in the app store (vuex)
Then on the map initialization, the markers are generated using these informations in the store
So basically my Map.vue calls the map:
<v-map ref="map" :zoom="zoom" :center="center">
<v-tilelayer url="http://{s}.tile.osm.org/{z}/{x}/{y}.png"></v-tilelayer>
<v-marker-cluster :options="clusterOptions">
<v-marker v-for="(marker, index) in markers"
:key="index"
:lat-lng="makeCoords(marker.location.lat, marker.location.lng)"
v-on:l-click="showSpot(marker._id, marker.slug, marker.location.lat, marker.location.lng)">
</v-marker>
</v-marker-cluster>
</v-map>
Markers is coming from the store ($store.map.markers):
computed: {
markers () {
return this.$store.state.map.markers
}
}
So in the same Template, if I want to get a reference to the map, I just need to do this:
this.$refs.map
But I would need to do the same from another file (let's say "AddMarker.vue", in order to place new markers on the map, using this method:
L.marker([datas.location.lat, datas.location.lng]).addTo(mymap);
where "mymap" should be the object defined in Map.vue
Of course, as the map is not in the same file, this.$refs.map results in "undefined".
I tried to add the map reference in the store, but it's not working and fires an error (call stack), I guess it's not made to store components.
I tried to just commit the new marker in the store, but the map won't just magically adapt and add it. I guess I really need to call the addTo() method for this.
Here's the store:
export const state = () => ({
markers: null
})
export const mutations = {
setMarkers(state, markers) {
state.markers = markers
},
addMarker(state, marker) {
state.markers.push(marker)
}
}
export const actions = {
async init({ commit }) {
let { data } = await this.$axios.get(process.env.api.spots)
commit('setMarkers', data)
}
}
And here's how I call the mutation:
that.$store.commit('map/addMarker', {
title: values.title,
description: values.description,
location: {
city: that.$store.state.position.infos.city,
country: that.$store.state.position.infos.country,
lat: that.$store.state.position.coords.lat,
lng: that.$store.state.position.coords.lng
}
});
The marker is perfectly added in the store, yet nothing happen on the map.
If anyone know how to deal with this?
Thanks!
Your actual problem is: "how do I add another marker to markers?" If you define markers as a computed that is based on the store, then it's a matter of adding a marker to the store.
Vue.component('v-map', Vue2Leaflet.Map);
Vue.component('v-tilelayer', Vue2Leaflet.TileLayer);
Vue.component('v-marker', Vue2Leaflet.Marker);
const store = new Vuex.Store({
state: {
markers: [
[47.42, -1.25],
[47.41, -1.21],
[47.43, -1.22]
].map(p => L.latLng(...p))
},
mutations: {
addMarker(state, payload) {
state.markers.push(payload);
}
},
actions: {
addMarker({
commit
}, payload) {
commit('addMarker', payload)
}
}
})
const v = new Vue({
el: '#app',
store,
data() {
return {
zoom: 13,
center: [47.413220, -1.219482],
url: 'http://{s}.tile.osm.org/{z}/{x}/{y}.png',
attribution: '© OpenStreetMap contributors',
}
},
computed: {
markers() {
return this.$store.state.markers;
}
}
});
setTimeout(() => {
store.dispatch('addMarker', L.latLng(47.412, -1.24));
}, 1400);
html,
body,
#app {
height: 100%;
margin: 0;
}
<script src="//unpkg.com/vue#latest/dist/vue.js"></script>
<script src="//unpkg.com/leaflet#1.0.3/dist/leaflet.js"></script>
<script src="//unpkg.com/vue2-leaflet#0.0.57/dist/vue2-leaflet.js"></script>
<script src="//unpkg.com/vuex#latest/dist/vuex.js"></script>
<link href="//unpkg.com/leaflet#1.0.3/dist/leaflet.css" rel="stylesheet" />
<div id="app">
<v-map :zoom="zoom" :center="center">
<v-tilelayer :url="url" :attribution="attribution"></v-tilelayer>
<v-marker v-for="marker in markers" :lat-lng="marker"></v-marker>
</v-map>
</div>
Consider an event bus for this situation; you've got components that can add markers on a map, say a list of addresses and when you click one a pin drops to it's location.
// bus.js
import Vue from 'vue';
export const EventBus = new Vue();
// address-list.js
import { EventBus } from './bus.js';
methods: {
onClick () {
EventBus.$emit('add-marker', {x:123,y:345});
}
}
// map.js
import { EventBus } from './bus.js';
EventBus.$on('add-marker', coords => {
this.addMarker(coords).then(() => this.redrawMap())
});
Straightforward, not a lot of code. Being a global bus, obviously you can re-use in any component necessary.