I'm new to Vue JS and I'm trying to make a map with marker clusters because I'm having a great number of markers in my map. I wrote a code based on the Vue2leaflet documentation, but it doesn't show the clusters and there is no error.
I tried to use l-marker,but I was struggling with it, I was having "Cannot read property 'lat' of undefined" error and even though I was really trying to resolve this problem, at the end I decided to use l-geo-json to show the markers on the map.
<template lang="html">
<section class="charts-highcharts">
<grid-block title="Map">
<div class="-map" id="map" >
<l-map :zoom="8" :center="[47.41322, -1.219482]" :options="{ gestureHandling: true }" ref="map">
<l-tile-layer url="http://{s}.tile.osm.org/{z}/{x}/{y}.png"></l-tile-layer>
<l-marker-cluster :options="clusterOptions">
<l-geo-json v-for="l in locations" :key="l.id" :geojson="l.location" :options="options"></l-geo-json>
</l-marker-cluster>
</l-map>
</div>
</grid-block>
</section>
</template>
<script lang="js">
import L from "leaflet";
import { LMap, LTileLayer, LMarker, LGeoJson, LPopup } from "vue2-leaflet";
import { GestureHandling } from "leaflet-gesture-handling";
import Vue2LeafletMarkercluster from 'vue2-leaflet-markercluster';
import 'vue2-leaflet-markercluster/dist/leaflet.markercluster.js';
import 'vue2-leaflet-markercluster/dist/leaflet.markercluster-src.js';
export default {
name: 'Maps',
components: {
LMap,
LTileLayer,
LMarker,
LGeoJson,
LPopup,
"l-marker-cluster": Vue2LeafletMarkercluster,
},
data () {
return {
geojson: null,
clusterOptions: { disableClusteringAtZoom: 11},
locations: [],
options: {
onEachFeature: function onEachFeature(feature, layer) {
layer.bindPopup(feature.properties.name);
},
},
};
},
created () {
L.Map.addInitHook("addHandler","gestureHandling", GestureHandling);
},
mounted() {
let geojson = response.data.features,
id = 0,
tmpLocations = [];
for (let l of geojson) {
tmpLocations.push({
id: id,
location: l,
});
id++;
}
this.locations = tmpLocations;
});
},
};
</script>
<style scoped lang="scss">
#import "~leaflet.markercluster/dist/MarkerCluster.css";
#import "~leaflet.markercluster/dist/MarkerCluster.Default.css";
.charts-highcharts {
}
.-map {
width: 100%;
height: 800px;
}
</style>
Do you know why the clusters aren't showing?
Related
I have a GeoJson file and added it to my project file, but I don't know how to import and use it in my map component. I tried the code below, but it didn't work.
<template>
<div class="locationMap">
<l-map
:zoom="6"
:center="[47.31322, -1.319482]"
style="height: 800px; width: 1000px"
>
<l-tile-layer :url="url" :attribution="attribution" />
<l-geo-json
:geojson="geojson"
:options="options"
:options-style="styleFunction"
/>
</l-map>
</div>
</template>
<script>
import geojson from "../components/provinces.json";
export default {
name: "locationMap",
data() {
return {
url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
attribution:
'© <a target="_blank" href="http://osm.org/copyright">OpenStreetMap</a>contributors',
geojson: null,
};
},
mounted() {
this.geojson = geojson;
},
};
</script>
Try to assign this.geojson before mounting and load the file with the fetch method.
async created () {
const jsonFile = await fetch('../components/provinces.json')
this.geojson = await jsonFile.json()
}
This example is available in Vue Leaflet documentation.
https://vue2-leaflet.netlify.app/components/LGeoJson.html#demo
So I still couldn't find a solution to import the GeoJson file using vue2-leaflet package , but I was able to import the file using the code below:
<template>
<div class="container">
<div id="mapContainer">
</div>
</div>
</template>
<script>
import "leaflet/dist/leaflet.css";
import L from "leaflet";
import geojson from "../components/provinces.json"
export default{
name: "locationMap",
data() {
return{
center: [32.87255939010237, 53.781741816799745],
}
},
methods: {
setupLeafletMap: function () {
const mapDiv = L.map("mapContainer").setView(this.center, 5);
L.tileLayer(
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
{
attribution: '© <a target="_blank" href="http://osm.org/copyright">OpenStreetMap</a> contributors',
// maxZoom: 18,
}
).addTo(mapDiv);
var myStyle = {
"fillColor": "#818181",
"color": "black",
"weight": 2,
"opacity": 0.65,
"fillOpacity": 0.6
};
L.geoJSON(geojson,{
style: myStyle,
}
}
}
mounted() {
this.setupLeafletMap()
console.log(mockData.colors[1].id)
},
}
</script>
We're building an app using Ionic 6 / Vue 3 / Capacitor as our framework. On one of the pages we need to display a form which, among other inputs, contain 2 map components. The user can tap to pinpoint a geographical location in each of the maps. This is our map component:
<template>
<ion-item>
<ion-label position="stacked" color="tertiary" style="margin-bottom: 10px"
>{{ controlLabel }} (Tap to choose position)</ion-label
>
<div class="mapBox">
<div
:id="'map'+randomId"
class="map"
#click="getCoord($event)"
></div>
</div>
<div>
<ion-label color="tertiary" position="stacked">{{
$lang.Lengdegrad
}}</ion-label>
<ion-input
v-model="lon"
#change="setMarker($event)"
:controlIdLat="controlIdLat"
:data-value="valueLat"
/>
</div>
<div>
<ion-label color="tertiary" position="stacked">{{
$lang.Breddegrad
}}</ion-label>
<ion-input
v-model="lat"
#change="setMarker($event)"
:controlIdLon="controlIdLon"
:data-value="valueLon"
/>
</div>
</ion-item>
</template>
<script>
import { defineComponent } from "#vue/runtime-core";
import { IonInput, IonItem, IonLabel } from "#ionic/vue";
import Map from "ol/Map";
import View from "ol/View";
import Feature from "ol/Feature";
import Point from "ol/geom/Point";
import { Style, Icon } from "ol/style";
import { OSM, Vector as VectorSource } from "ol/source";
import { Tile as TileLayer, Vector as VectorLayer } from "ol/layer";
import { fromLonLat, toLonLat } from "ol/proj";
import XYZ from "ol/source/XYZ";
import "ol/ol.css";
import PinImg from "../../resources/icons8-pin-48.png";
import { Geolocation } from "#capacitor/geolocation";
export default defineComponent({
props: [
"controlLabel",
"controlIdLat",
"controlIdLon",
"lonLatFields",
"valueLat",
"valueLon",
"setStartMarker",
],
components: {
IonInput,
IonItem,
IonLabel,
},
data() {
return {
mainMap: null,
lat: null,
lon: null,
pinLayer: null,
pinFeat: null,
$lang: this.$lang,
isOnline: this.$isOnline,
isVisible: false,
randomId: Math.random().toString(36).substr(2, 5),
};
},
mounted() {
console.log("randomid",this.randomId)
this.source = new VectorSource();
setTimeout(async () => {
if (document.readyState === "loading") {
document.addEventListener(
"DOMContentLoaded",
await this.getLocation()
);
} else {
await this.getLocation();
}
this.myMap();
this.$nextTick(() => {
if (this.setStartMarker) {
this.setMarker();
}
view.setCenter(fromLonLat([this.lon, this.lat]));
});
}, 100);
},
methods: {
async getLocation() {
const position = await Geolocation.getCurrentPosition();
this.lat = position.coords.latitude.toFixed(3);
this.lon = position.coords.longitude.toFixed(3);
},
getCoord(event) {
let lonlat = toLonLat(this.mainMap.getEventCoordinate(event));
this.lat = lonlat[1].toFixed(3);
this.lon = lonlat[0].toFixed(3);
this.$nextTick(() => {
this.setMarker();
});
},
myMap() {
this.mainMap = new Map({
layers: [
new TileLayer({
source: new OSM(),
}),
new TileLayer({
source: new XYZ({
url: "https://opencache.statkart.no/gatekeeper/gk/gk.open_gmaps?layers=sjokartraster&zoom={z}&x={x}&y={y}",
attributions:
'Kartverket',
}),
}),
(this.pinLayer = new VectorLayer({
source: new VectorSource({
features: [],
}),
style: new Style({
image: new Icon({
anchor: [0.5, 46],
anchorXUnits: "fraction",
anchorYUnits: "pixels",
src: PinImg,
}),
}),
})),
],
target: "map" + this.randomId,
view: view,
});
console.log("map", this.mainMap);
},
setMarker() {
let p = new Point(fromLonLat([this.lon, this.lat]));
console.log("setmarker", p);
if (!this.pinLayer.getSource().getFeatures().length) {
this.pinFeat = new Feature({
geometry: p,
});
this.pinLayer.getSource().addFeature(this.pinFeat);
} else {
this.pinFeat.setGeometry(p);
}
let vals = {
[this.controlIdLat]: this.lat,
[this.controlIdLon]: this.lon,
};
this.$emit("input", vals);
console.log(p.getCoordinates());
view.setCenter(p.getCoordinates());
},
},
});
const view = new View({
center: fromLonLat([13.486, 68.131]),
zoom: 10,
constrainResolution: true,
});
</script>
<style scoped>
div.map {
border: 5px solid white;
margin: 0 auto;
height: 100%;
width: 100%;
}
div.mapBox {
width: 100%;
height: 40vh;
}
</style>
Testing in the browser, this kind of works. The user can tap each of the two maps and two separate locations will be saved to the variables. However, when panning or zooming one of the maps, the other one follows so that they both show exactly the same map area.
As you can see, I've tried assigning a random id to each map to separate them from each other. That didn't work.
Testing on an Android phone, only the latter of the two maps are displayed. The first mainMap is left undefined.
My guess is that if I can figure out the first nuisance, then the second problem will also be solved. Any tips?
Edit: I tried making a new identical map component and use one for each map. Now the maps work as expected in the browser, being controlled separately. However the first map is still undefined on Android.
I am trying to build a map application with the vuemapbox library , but the strange thing is , all the markers are showing at the bottom of the page which is strange , can any one help me with it ?
<template>
<div class="home">
<MglMap :accessToken="accessToken" :mapStyle="mapStyle" #load="onMapLoaded">
<MglMarker
:coordinates.sync="locations"
color="blue"
v-for="(location, index) in pins"
:key="index"
/>
</MglMap>
</div>
</template>
<script>
// # is an alias to /src
import Mapbox from "mapbox-gl";
import {
MglMap,
MglMarker
} from "vue-mapbox";
export default {
name: "Home",
components: { MglMap, MglMarker },
data() {
return {
accessToken:"xxx",
mapStyle: "mapbox://styles/mapbox/streets-v11",
pins: [
[20.341979525348204, 85.8345150468384],
[25.581627613058714, 80.87795332144299],
[25.199370930176993, 95.86881932189225],
],
};
},
beforeMount() {
this.$store.dispatch("set_user_location");
},
computed: {
locations() {
return this.$store.getters.getThisUserMarker;
},
},
created() {
this.mapbox = Mapbox;
},
methods: {
async onMapLoaded(event) {
// in component
this.map = event.map;
const asyncActions = event.component.actions;
await asyncActions.flyTo({
center: this.locations,
zoom: 4,
speed: 1,
});
},
},
};
</script>
This is what i have done so far , but having this strange issue , please help , Every help is appreciated .
Make shure that you have loaded mabpox css.
Try to set height for MglMap
<MglMap
width="100%"
height="100%"
...
/>
Hi do somebody know or have some samples of code how to add searchbar for vue2-leaflet map, I have tried the following https://www.npmjs.com/package/vue2-leaflet-geosearch but failed, do you know any alternatives how to resolve it,hope for your help, thanks . This is code that works and I would like to add searchbar in the code .
<template>
<div>
<b-modal size="md" :visible="visible" #hidden="$emit('clear')" #shown="modalShown" title="Event details">
<div class="foobar1">
<l-map :minZoom="3" :zoom="13" ref="mymap" #click="addMarker">
<l-tile-layer :url="url" :attribution="attribution"></l-tile-layer>
<!-- <l-marker :lat-lng="center"></l-marker> -->
</l-map>
</div>
<template slot="modal-footer">
<b-btn variant="danger" #click="">Delete</b-btn>
</template>
</b-modal>
</div>
</template>
<style scoped>
.foobar1 {
width: 450px;
height: 400px;
align: center;
}
</style>
<script>
import {LMap, LMarker, LTileLayer} from "vue2-leaflet";
import L from "leaflet"
export default {
name: "loc",
components: {
LMap,
LMarker,
LTileLayer,
L
},
data() {
return {
marker: L.latLng(77, 154.0),
visible: true,
url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}",
attribution:
'© OpenStreetMap contributors'
};
},
methods: {
plotCurrentLocation(map) {
var vm = this;
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function (position) {
var currLocation = new L.latLng(position.coords.latitude, position.coords.longitude);
if (vm.marker)
map.removeLayer(vm.marker);
vm.marker = L.marker([position.coords.latitude, position.coords.longitude], {}).addTo(map);
map.setView(currLocation, 11, {animation: true});
map.panTo(currLocation);
}, err => {
// alert(JSON.stringify(err))
}, {timeout: 30000, enableHighAccuracy: true, maximumAge: 75000})
} else {
alert("no");
}
},
async modalShown() {
var map = this.$refs.mymap.mapObject;
map.invalidateSize();
this.plotCurrentLocation(map);
},
addMarker(e) {
var map = this.$refs.mymap.mapObject;
// alert(JSON.stringify(this.marker.getLatLng()));
if (this.marker)
map.removeLayer(this.marker);
this.marker = L.marker([e.latlng.lat, e.latlng.lng], {}).addTo(map);
map.panTo(e.latlng);
}
}
}
</script>
Instructions on the npm package works fine. Something like this should work.
Remember to install and import the necessary libraries.
<template>
<div style="height: 300px; width: 100%" class="text-grey-10">
<l-map #click="addMarker" :zoom="zoom" :center="center">
<l-tile-layer :url="url" :attribution="attribution" />
<l-geosearch :options="geosearchOptions"/>
<l-marker v-if="locationMarker" :latlng="locationMarker"/>
</l-map>
</div>
</template>
<script>
import { LMap, LTileLayer, LMarker } from "vue2-leaflet";
import { OpenStreetMapProvider } from "leaflet-geosearch";
import LGeosearch from "vue2-leaflet-geosearch";
export default {
components: {
LMap,
LTileLayer,
LMarker,
LGeosearch
},
data: () => {
geosearchOptions: {
provider: new OpenStreetMapProvider()
}
}
}
</script>
I'm try to get the marker position in the map when the user stop dragging it. I use getLanLng() method but ever returns an error:
TypeError: this.marker.getLanLng is not a function
Error code:
vue.runtime.esm.js?2b0e:619 [Vue warn]: Error in v-on handler:
"TypeError: this.marker.getLanLng is not a function"
found in
---> <LMarker>
<LMap>
<MapComponent>
<VOnsCol>
<VOnsPage>
<UserLocation> at src/pages/Preferences/UserLocation.vue
<VOnsNavigator>
<AppNavigator> at src/AppNavigator.vue
<Root>
vue.runtime.esm.js?2b0e:1888
TypeError: this.marker.getLanLng is not a function
at VueComponent.onDragEnd (MapComponent.vue?a926:145)
at invokeWithErrorHandling (vue.runtime.esm.js?2b0e:1854)
at NewClass.invoker (vue.runtime.esm.js?2b0e:2179)
at NewClass.handler (leaflet-src.js?e11e:2661)
at NewClass.fire (leaflet-src.js?e11e:593)
at NewClass._onDragEnd (leaflet-src.js?e11e:7370)
at NewClass.fire (leaflet-src.js?e11e:593)
at NewClass.finishDrag (leaflet-src.js?e11e:5991)
at NewClass._onUp (leaflet-src.js?e11e:5966)
at handler (leaflet-src.js?e11e:2661)
MapComponent.vue(child):
<template>
<v-map
ref="map"
:zoom="zoom"
:center="center"
>
<v-tile-layer
:url="maps.url"
:atribution="maps.atribution"
/>
<v-marker
ref="marker"
class="map__marker"
alt="user position"
:lat-lng="center"
:draggable="true"
#dragend="onDragEnd"
>
</v-marker>
</v-map>
</template>
<script>
import { LMap, LTileLayer, LMarker } from 'vue2-leaflet'
export default {
name: 'mapComponent',
components: {
'v-map': LMap,
'v-tile-layer': LTileLayer,
'v-marker': LMarker
},
data() {
return {
maps: {
url: 'http://{s}.tile.stamen.com/terrain/{z}/{x}/{y}.jpg',
atribution: '© Stamen Design'
},
center: [40.5005, -3.66739],
zoom: 17,
bounds: null,
marker: null
}
},
mounted() {
this.$nextTick(() => {
this.marker = this.$refs.marker
console.log('marker: ' + this.marker)
})
},
methods: {
onDragEnd(event) {
if (this.marker) {
console.log(event.distance)
console.log(this.marker)
console.log(this.marker.getLanLng())
} else {
console.log('Error: NO MARKER')
}
}
}
}
</script>
UserLocation.vue(parent):
<template>
<v-ons-page id="userLocation">
<div class="map">
<map-component />
</div>
</v-ons-page>
</template>
<script>
import mapComponent from '#components/User/mapComponent'
export default {
name: 'userLocation',
components: {
mapComponent
}
}
</script>
<style scoped>
.map {
width: 100%;
height: 100%;
}
</style>
event.distance and this.marker works fine. The errors are with the methods.
I've used this.$refs.marker and this.$refs.marker.mapObject and the error message returns is the same