Mapbox-gl in a Vue.js single file component (Quasar-Framework) - javascript

I would like to implement a Mapbox-gl-js Map within a Quasar Framework (Vue) single file component, but I do not get it working. I found some code on Googlemaps with Vue, and some stuff on Mapbox with React, and try to pull it together from that. With below map initialisation parameters I can get the map showing fine in the index.html (with the mapzen tiles), but want it in the component.
I try to follow this [https://laracasts.com/discuss/channels/vue/google-maps-and-vue-js](link url) and then adjust it for Mapbox:
proj/src/components/maplayout.vue :
<template>
<quasar-layout>
<h3>Map</h3>
<div id='map'></div>
</quasar-layout>
</template>
<script>
import mapboxgl from '../app'
export default {
data () {
return {}
},
create () {
this.createMap()
},
methods: {
createMap: function () {
mapboxgl.accessToken = '{{yourmapboxaccestokenkey}}'
var simple = {
'version': 8,
'sources': {
'osm': {
'type': 'vector',
'tiles': ['https://vector.mapzen.com/osm/all/{z}/{x}/{y}.mvt?api_key=vector-tiles-{{yourmapzenapikey}}']
}
},
'layers': [{
'id': 'background',
'type': 'background',
'paint': {
'background-color': '#adddd2'
}
}, {
'id': 'majorroad',
'source': 'osm',
'source-layer': 'roads',
'type': 'line'
}, {
'id': 'buildings',
'type': 'fill',
'source': 'osm',
'source-layer': 'buildings'
}]
}
// initialize the map
this.map = new mapboxgl.Map({
container: 'map',
style: simple,
center: [-1.83, -78.183],
zoom: 5.5
})
}
}
}
</script>
<style>
</style>
By the way, for mapbox with webpack you need certain loaders, see:
[https://mikewilliamson.wordpress.com/2016/02/24/using-mapbox-gl-and-webpack-together/](link url)
But as I got Mapbox working with Webpack before (without vue), I think I have that ok. Actually I do not get any errors in the browser console (but obviously no map appears).
In the app.js file I do not know how to deal with the suggested (maybe not necessary as googlemaps needs a callback, dunno about mapbox/mapzen?!):
var App = window.App = new Vue ({
//code
})
As in Quasar initialization is done like this:
Quasar.start(() => {
Router.start(Vue.extend({}), '#quasar-app')
})
Which I do not really get...
Any suggestions how to get this working are welcome!

I was close. This actually works:
<template>
<quasar-layout>
<h3>Map</h3>
<div id='map'></div>
</quasar-layout>
</template>
<script>
import mapboxgl from 'mapbox-gl'
console.dir(mapboxgl)
export default {
data () {
return {}
},
ready () {
this.createMap()
},
methods: {
createMap: function () {
mapboxgl.accessToken = '{{yourmapboxaccestokenkey}}'
var simple = {
'version': 8,
'sources': {
'osm': {
'type': 'vector',
'tiles': ['https://vector.mapzen.com/osm/all/{z}/{x}/{y}.mvt?api_key=vector-tiles-{{yourmapzenapikey}}']
}
},
'layers': [{
'id': 'background',
'type': 'background',
'paint': {
'background-color': '#bbccd2'
}
},
{
'id': 'majorroad',
'source': 'osm',
'source-layer': 'roads',
'type': 'line'
},
{
'id': 'buildings',
'type': 'fill',
'source': 'osm',
'source-layer': 'buildings'
}]
}
// init the map
this.map = new mapboxgl.Map({
container: 'map',
style: simple,
minzoom: 1.3,
center: [-74.0073, 40.7124], // Manhattan
zoom: 16
})
this.map.addControl(new mapboxgl.Navigation())
}
}
}
</script>
<style>
</style>
I did not change my Vue initialisation.

<template>
<div class="hello">
<div id='map'></div>
</div>
</template>
<script>
import mapboxgl from 'mapbox-gl';
require('../node_modules/mapbox-gl/dist/mapbox-gl.css');
export default {
name: 'HelloWorld',
data() {
return {
apiKey: {{ your api key }},
};
},
mounted() {
this.createMap();
},
methods: {
createMap() {
mapboxgl.accessToken = this.apiKey;
// init the map
this.map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v9',
minzoom: 1.3,
center: [-74.0073, 40.7124], // Manhattan
zoom: 16,
});
this.map.addControl(new mapboxgl.Navigation());
},
},
};
</script>
This might help, I guess!

First thing I noticed: You are initializing the map before DOM is injected into the document. Instead of ‘created()’ method use ‘ready()’.

Quasar is Based on Vue2 version. So ready method is deprecated so instead of that use mounted method.

Related

Web App crashes when trying to plot a timeline chart in Apexcharts with Vue 3

I have a web application where I am using ApexCharts with Vue 3 to plot some graphics. I didn't have any trouble using the scatter plot, but when I try to plot a timeline like this example of the website, it completely crashes and I don't know why. maybe I am doing something wrong, but I can't see any error. I would appreciate a lot if you can give me some help because it is important!
I attach here the code of the view:
<template>
<apexchart type="rangeBar" height="350" :options="chartOptions" :series="series"></apexchart>
</template>
<script>
import axios from "../../../services/api.js";
export default {
data() {
return {
chartOptions: {
chart: {
type: 'rangeBar'
},
plotOptions: {
bar: {
horizontal: true,
}
},
fill: {
type: 'solid'
},
xaxis: {
type: 'datetime'
},
},
series: [
{
name: 'Prueba',
data: [
{
x: 'Code',
y: [
new Date('2019-03-02').getTime(),
new Date('2019-03-04').getTime()
]
},
{
x: 'Test',
y: [
new Date('2019-03-04').getTime(),
new Date('2019-03-08').getTime()
]
},
{
x: 'Validation',
y: [
new Date('2019-03-08').getTime(),
new Date('2019-03-12').getTime()
]
},
{
x: 'Deployment',
y: [
new Date('2019-03-12').getTime(),
new Date('2019-03-18').getTime()
]
},
]
},
], //end series
}; //end return
}, //end data
}
</script>
<style scoped>
</style>
There is something wrong in library
If you deep clone series it seems to work
<apexchart type="rangeBar" height="350" :options="chartOptions" :series="JSON.parse(JSON.stringify(series))"></apexchart>

Mapbox GL JS does not render layers after a certain zoom/distance

I have a map rendered with mapbox-gl with only two layers, a marker and a circle centered on a point. the distance is dynamic as it is based on a predefined distance in meters.
However after zooming in and moving away from the center, the layer abruptly disappears from the map render. I have tried to implement maxzoom and set tolerance to 0 on the geojson source, but the layers seem to still disappear after a certain zoom/distance from the center.
GIF to show what happens when you pan: https://imgur.com/a/fzfX6z7
Have I missed out some attribute to improve/enlarge the rendering distance? I don't intend to use many points, so performance should not be an issue for this use case.
<template>
<div id="mapContainer" class="basemap"></div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import mapboxgl, { CircleLayer } from 'mapbox-gl'
export default defineComponent({
name: 'Map',
mounted: function () {
mapboxgl.accessToken =
'abc'
const map: mapboxgl.Map = new mapboxgl.Map({
container: 'mapContainer',
style: 'mapbox://styles/mapbox/streets-v11',
center: [101.58165, 3.03837],
zoom: 13,
maxZoom: 17
})
const geofence = [
{
name: 'Location 1',
lat: 3.037479466090297,
lng: 101.58102250675641,
distance: 1000
}
]
map.on('load', function () {
map.loadImage('https://docs.mapbox.com/mapbox-gl-js/assets/custom_marker.png', function (error, image) {
if (error) throw error
if (image) {
map.addImage('custom-marker', image)
map.addSource('points', {
type: 'geojson',
data: {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [geofence[0].lng, geofence[0].lat]
},
properties: {
title: geofence[0].name,
distance: geofence[0].distance
}
},
maxzoom: 22,
tolerance: 0
})
map.addLayer({
id: 'points',
type: 'symbol',
source: 'points',
layout: {
'icon-image': 'custom-marker',
'text-field': ['get', 'title'],
'text-font': ['Open Sans Semibold'],
'text-offset': [0, 1.25],
'text-anchor': 'top'
}
})
const circleLayer: CircleLayer = {
id: 'geofence',
type: 'circle',
source: 'points',
paint: {
'circle-radius': {
stops: [
[0, 0],
[21, metersToPixelsAtMaxZoom(geofence[0].distance, geofence[0].lat)]
],
base: 2
},
'circle-opacity': 0.5,
'circle-color': 'blue',
'circle-stroke-width': 2,
'circle-stroke-color': 'black'
}
}
map.addLayer(circleLayer)
}
})
})
}
})
const metersToPixelsAtMaxZoom = (meters: any, latitude: any) => meters / 0.075 / Math.cos((latitude * Math.PI) / 180)
</script>
<style lang="scss">
#mapContainer {
width: auto;
height: 400px;
}
</style>
Code shown is in TypeScript, but removing the types would render it just as well I am sure.
I think you're seeing an edge case that Mapbox GL JS doesn't really support.
The better way to handle this would be to use TurfJS to create a polygon with circle() and add that as a geojson layer.

Change icon opacity when hover(symbol layer), in Mapbox GL JS

I see in the documentation that icon-opacity supports feature-state,
https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/#paint-symbol-icon-opacity
Therefore I use this code for making a change of opacity when user hovers over a icon:
map.addLayer({
type: 'symbol',
layout: {
'icon-image': 'point',
...
},
paint: {
'icon-opacity':
['case', ['boolean', ['feature-state','hover'], false], 1, 0.3]
}
});
The problem is that it takes the original opacity right(0.3) but does not change on hover.
Any idea?
Thanks.
The code you´re posting is right, but it's only half of the solution.
You already implemented the definition of the layer paint behavior for any feature (by default 0.3 as you say), the second part is to change the feature-state hover to true on mouseover.
The state of the features on 'hover' in your symbol layer don´t change automatically, you need to change it to true in a map.on('mousemove', 'youLayerId'... method, and again to false in a map.on('mouseout', 'youLayerId'... .
Check out this fiddle I have created for you on how to change opacity of an icon.
The relevant code is below
mapboxgl.accessToken = 'PUT HERE YOUR TOKEN';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v11'
});
let fHover = null;
map.on('load', function() {
map.loadImage(
'https://upload.wikimedia.org/wikipedia/commons/7/7c/201408_cat.png',
function(error, image) {
if (error) throw error;
map.addImage('cat', image);
map.addSource('point', {
'type': 'geojson',
'generateId': true,
'data': {
'type': 'FeatureCollection',
'features': [{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [0, 0]
}
}]
}
});
map.addLayer({
'id': 'points',
'type': 'symbol',
'source': 'point',
'layout': {
'icon-image': 'cat',
'icon-size': 0.25,
},
'paint': {
'icon-opacity': [
'case',
['boolean', ['feature-state', 'hover'], false],
1,
0.3
]
}
});
}
);
map.on('mousemove', 'points', function(e) {
if (e.features[0]) {
mouseover(e.features[0]);
} else {
mouseout();
}
});
map.on('mouseout', 'points', function(e) {
mouseout();
});
function mouseover(feature) {
fHover = feature;
map.getCanvasContainer().style.cursor = 'pointer';
map.setFeatureState({
source: 'point',
id: fHover.id
}, {
hover: true
});
}
function mouseout() {
if (!fHover) return;
map.getCanvasContainer().style.cursor = 'default';
map.setFeatureState({
source: 'point',
id: fHover.id
}, {
hover: false
});
fHover = null;
}
});
Important, to change a feature state every feature must have an id in the source, so I strongly recommend to set always 'generateId': true in the addSource method.
PS.- If this answer solves your question, please mark it as answer accepted, in that way it will also help other users to know it was the right solution.

Cannot display a popup on hover over a route in mapbox

I have a custom line in mapbox that displays a popup when you click it, how can I make it so it shows only when you hover it ? I changed from "click" to "mouseenter" but it did not closed the popup. What needs changing ?
Here is a jsfiddle example : https://jsfiddle.net/andrre/jb4czmew/4/
JS
mapboxgl.accessToken = 'pk.eyJ1IjoibWFya2V0aW5nYnNvIiwiYSI6ImNrYnYwZmk3YjAxZjgyem1wY2Zmc3F4Y2EifQ.gMF-eCCaAHHgWIUoRcnfkg';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v11',
center: [-0.066985, 51.503363],
zoom: 9
});
map.on('load', function() {
map.addSource('routeThree', {
'type': 'geojson',
'data': {
'type': 'Feature',
'properties': {},
'geometry': {
'type': 'LineString',
'coordinates': [
[-0.066985, 51.503363],
[-3.550610, 40.390555 ],
[-77.140217, 38.801481],
[-118.175979, 34.008447]
]
}
}
});
map.addLayer({
'id': 'routeThree',
'type': 'line',
'source': 'routeThree',
'layout': {
'line-join': 'round',
'line-cap': 'round'
},
'paint': {
'line-color': '#04A5BD',
'line-width': 8
}
});
map.on('click', 'routeThree', function(e) {
new mapboxgl.Popup()
.setLngLat(e.lngLat)
.setHTML("<h2>This is the third line that will explain something</h2>")
.addTo(map);
});
});
Your code is right, but there's a small mistake in it, you chose the same id for the Source and for the Layer... just call it differently in the map.addLayer and click event, and it works fiddle
mapboxgl.accessToken = 'pk.eyJ1IjoibWFya2V0aW5nYnNvIiwiYSI6ImNrYnYwZmk3YjAxZjgyem1wY2Zmc3F4Y2EifQ.gMF-eCCaAHHgWIUoRcnfkg';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v11',
center: [-0.066985, 51.503363],
zoom: 9
});
map.on('load', function () {
var popup;
map.addSource('routeThree', {
'type': 'geojson',
'data': {
'type': 'Feature',
'properties': {},
'geometry': {
'type': 'LineString',
'coordinates': [
[-0.066985, 51.503363],
[-3.550610, 40.390555],
[-77.140217, 38.801481],
[-118.175979, 34.008447]
]
}
}
});
map.addLayer({
'id': 'routeThree2',
'type': 'line',
'source': 'routeThree',
'layout': {
'line-join': 'round',
'line-cap': 'round'
},
'paint': {
'line-color': '#04A5BD',
'line-width': 8
}
});
map.on('mouseover', 'routeThree2', function (e) {
popup = new mapboxgl.Popup()
.setLngLat(e.lngLat)
.setHTML("<h2>This is the third line that will explain something</h2>")
.addTo(map);
});
map.on('mouseout', 'routeThree2', function (e) {
if (popup) popup.remove();
});
});

Style is not done loading error: Mapbox GL JS

I have built a website using Mapbox GL JS to display a number of layered routes and points on a map to track different teams progress on a route. However when testing on a large number of page reloads the tracks on the map along with a number of other page elements sometimes don't load and I get a Style is not done loading error.
Code Extract:
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/outdoors-v11',
center: [15, 50],
zoom: 4 // zoom range is 0 (the whole world) to 22 (street level)
});
var distanceContainer = document.getElementById('distance');
// GeoJSON object to hold our measurement features
var geojson = {
'type': 'FeatureCollection',
'features': []
};
var finishPoint = {
'type': 'FeatureCollection',
'features': []
};
var progressLine = {
'type': 'FeatureCollection',
'features': []
};
map.on('load', function () {
var routeLineString = getRoute();
var line = routeLineString;
var options = {units: 'kilometers'};
var endPoint = turf.along(line, turf.length(line), options);
map.addSource('route-source', {
'type': 'geojson',
'data': line
});
// Add styles to the map. Style layer: A style layer ties together the source and image and specifies how they are displayed on the map.
// 'line-color': '#0C7CBB', // dark cyan
// NHS blue: 005EB8
map.addLayer({
'id': 'route',
'type': 'line',
'source': 'route-source',
'layout': {
'line-join': 'round',
'line-cap': 'round'
},
'paint': {
'line-color': '#000000',
'line-width': 3
}
},"team1RouteProgress"); // placement of this line below the progress line layer
// destination marker
map.loadImage("https://i.imgur.com/MK4NUzI.png", function (error, image) {
if (error) throw error;
map.addImage("finish-flag", image);
map.addSource('finish-source', {
'type': 'geojson',
'data': finishPoint
});
map.addLayer({
id: 'finish',
type: 'symbol',
source: 'finish-source',
layout: {
"icon-image": "finish-flag",
'icon-anchor': "bottom"
}
});
});
finishPoint.features.push(endPoint);
//map.getSource('geojson').setData(geojson);
});
$(document).ready(function() {
// Clear the Distance container to populate it with a new value
distanceContainer.innerHTML = '';
var line = getRoute();
var options = {units: 'kilometers'};
$.ajax({ //create an ajax request to getProgress.php
type: "GET",
url: "./php/getTeamProgress.php",
dataType: "json", //expect json to be returned
data: {
//access_key: access_key,
},
success: function (response) {
var Teams_arr = [];
var dist_arr = [];
var colour_arr = [];
response.data.forEach(function (dat) {
Teams_arr.push(dat.team);
dist_arr.push(dat.distance);
//Colour Setting
if(dat.team === "Typhoon Squadron"){
colour_arr.push("purple");
}else if (dat.team === "Gloucester Penguins; for Ben"){
colour_arr.push("red");
}else if (dat.team === "Community"){
colour_arr.push("yellow");
}else if(dat.team === "HMS Grimsby"){
colour_arr.push("navy");
}else if(dat.team === "Thunderer Squadron"){
colour_arr.push("blue");
}else{
colour_arr.push("grey");
}
});
//alert(response.totaldist);
var distStart = 0;
//Team 1
var team1DistAlongRoute = dist_arr[0]
var team1Along = turf.along(line, team1DistAlongRoute, options);
if (team1DistAlongRoute<0.1) {
team1DistAlongRoute = 0.1; // prevent error in lineSliceAlong if dist = 0
}
var team1SliceLine = turf.lineSliceAlong(line, distStart, team1DistAlongRoute, {units: 'kilometers'});
map.addSource('team1progress-source', {
'type': 'geojson',
'data': team1SliceLine
});
map.addLayer({
'id': 'team1RouteProgress',
'type': 'line',
'source': 'team1progress-source',
'layout': {
'line-join': 'round',
'line-cap': 'round'
},
'paint': {
'line-color': colour_arr[0],
'line-width': 5
}
});
// Progress marker- Purple
// Image: An image is loaded and added to the map.
map.loadImage("./assets/black.png", function (error, image) {
if (error) throw error;
map.addImage("team1custom-marker", image);
map.addSource('geojson', {
'type': 'geojson',
'data': geojson
});
map.addLayer({
id: 'team1Progress',
type: 'symbol',
source: 'geojson',
layout: {
"icon-image": "team1custom-marker",
'icon-anchor': "bottom"
}
});
});
geojson.features.push(team1Along);
progressLine.features.push(team1SliceLine);
//Team 2
var team2DistAlongRoute = dist_arr[1]
var team2Along = turf.along(line, team2DistAlongRoute, options);
if (team2DistAlongRoute<0.1) {
team2DistAlongRoute = 0.1; // prevent error in lineSliceAlong if dist = 0
}
var team2SliceLine = turf.lineSliceAlong(line, distStart, team2DistAlongRoute, {units: 'kilometers'});
map.addSource('team2progress-source', {
'type': 'geojson',
'data': team2SliceLine
});
map.addLayer({
'id': 'team2RouteProgress',
'type': 'line',
'source': 'team2progress-source',
'layout': {
'line-join': 'round',
'line-cap': 'round'
},
'paint': {
'line-color': colour_arr[1],
'line-width': 5
}
});
geojson.features.push(team2Along);
progressLine.features.push(team2SliceLine);
etc etc
The quick fix:
map = new Mapboxgl.map({ ... })
map.on('load', () => {
...add all your `addSource()`, `addLayer()` etc here.
});
The 'Style is not done loading' error is thrown by the _checkLoaded method in Mapbox GL JS's style.js. This method is called each time a modification is made to the style -- for example when the Map#addSource and Map#addLayer methods are called. In addition to making use of the map#on('load', function() {}) listener to ensure that all necessary map resources are loaded before attempting to make a modification to the map's style, you could also take a look at some of these examples from our documentation, which demonstrate strategies for updating map sources and layers dynamically:
Update a feature in realtime.
Add live realtime data.
Using the GeoJSONSource#setData method to update an existing source's data and re-render the map.
Change a layer's color with buttons.

Categories