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.
Related
I need to be able to select the state first and then select the counties of the united states from Mapbox. For that highlight all state boundaries first. When we select a state from the map. we need to highlight the boundaries of the counties in that state then we can able to select the counties from that state.
Now I am getting whole counties boundaries of the united states. I need only selected state counties' boundaries only. Here is my code
map.current.on('load', () => {
map.current.addSource('states', {
type: 'geojson',
data: 'https://docs.mapbox.com/mapbox-gl-js/assets/us_states.geojson'})
map.current.addLayer({
id: 'state-fills',
type: 'fill',
source: 'states',
layout: {},
paint: {
'fill-color': '#627BC1',
'fill-opacity': [
'case',
['boolean', ['feature-state', 'hover'], false],
0.4,
0.2
]
}
})
map.current.addLayer({
id: 'state-borders',
type: 'line',
source: 'states',
layout: {},
paint: {
'line-color': '#627BC1',
'line-width': 2
}
})
map.current.addSource('counties', {
type: 'vector',
url: 'mapbox://mapbox.82pkq93d'
})
map.current.on('click', 'state-fills', (e) => {
console.log(e)
console.log(e.features)
if (e.features.length > 0) {
if (hoveredStateId !== null) {
console.log(hoveredStateId)
map.current.jumpTo({ center: [e.lngLat.lng, e.lngLat.lat] })
map.current.zoomTo(6)
// map.current.removeSource('counties')
// map.current.removeLayer('counties')
map.current.setFeatureState(
{ source: 'states', id: hoveredStateId },
{ hover: false }
)
}
hoveredStateId = e.features[0].id
map.current.jumpTo({ center: [e.lngLat.lng, e.lngLat.lat] })
map.current.zoomTo(6)
map.current.setFeatureState(
{ source: 'states', id: hoveredStateId },
{ hover: true }
)
map.current.addLayer(
{
id: 'counties',
type: 'fill',
source: 'counties',
'source-layer': 'original',
paint: {
'fill-outline-color': 'rgba(0,0,0,0.4)',
'fill-color': 'rgba(0,0,0,0.1)'
}
},
// Place polygons under labels, roads and buildings.
'building'
)
map.current.addLayer(
{
id: 'counties-highlighted',
type: 'fill',
source: 'counties',
'source-layer': 'original',
paint: {
'fill-outline-color': '#484896',
'fill-color': '#6e599f',
'fill-opacity': 0.75
},
filter: ['in', 'FIPS', '']
},
// Place polygons under labels, roads and buildings.
'building'
)
// const sourceObject = map.current.getSource('counties')
// console.log(sourceObject)
}
})
map.current.on('click', (e) => {
// Set `bbox` as 5px reactangle area around clicked point.
const bbox = [
[e.point.x - 5, e.point.y - 5],
[e.point.x + 5, e.point.y + 5]
]
// Find features intersecting the bounding box.
const selectedFeatures = map.current.queryRenderedFeatures(bbox, {
layers: ['counties']
})
const fips = selectedFeatures.map(
(feature) => feature.properties.FIPS
)
// Set a filter matching selected features by FIPS codes
// to activate the 'counties-highlighted' layer.
map.current.setFilter('counties-highlighted', ['in', 'FIPS', ...fips])
})
})`
I have a web app I am creating that maps 30k+ symbols, but my issue is that on initialization, the map.on('load') is way too slow. I've added timers all throughout my code and it is specifically this line of code that takes 10+ seconds to run.
I've looked through all of Mapbox's resources on performance but none of the suggestions work or are relevant, (e.g., minimize the amount of layers used even though my layers take 0.1 seconds to load, using vector tiles instead of GeoJSON data, changing the properties of the data source, minimizing features etc.).
Has anyone else had this issue and figured out how to resolve it? I've been trying for days to optimize this map but I can't figure it out!
// Set map properties with the predefined configuration
mapboxgl.accessToken = 'my access token';
const map = new mapboxgl.Map({
container: 'fullMap',
style: 'mapbox://styles/mapbox/light-v10',
center: [-100.83, 39.22],
zoom: 5,
});
startTime = performance.now();
map.on('load', () => {
endTime = performance.now(); // Ends up being 10+ seconds...
map.addSource('gc_offices', {
type: 'geojson',
data: geojsonData,
buffer: 0,
cluster: true,
clusterMaxZoom: 12, // Max zoom to cluster points on
clusterRadius: 50, // Radius of each cluster when clustering points
});
map.addLayer({
id: 'clusters',
type: 'circle',
source: 'gc_offices',
paint: {
'circle-color': [
'step',
['get', 'point_count'],
'#2288f2',
5, '#3d97f5',
25, '#64abf5',
50, '#83bcf7',
100, '#a3ccf7',
250, '#cfe4fa',
500, '#eb4034'
],
'circle-radius': [
'step',
['get', 'point_count'],
13,
20, 18,
50, 25,
100, 30,
250, 40,
500, 50,
]
}
});
map.addLayer({
id: 'cluster-count',
type: 'symbol',
source: 'gc_offices',
filter: ['has', 'point_count'],
layout: {
'text-field': '{point_count_abbreviated}',
'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Bold'],
'text-size': 12,
'text-allow-overlap': true
}
});
map.addLayer({
id: 'locationData',
type: 'symbol',
source: 'gc_offices',
minzoom: 10,
layout: gc_office_layout_properties,
paint: gc_office_paint_properties,
});
});
I am trying to use Mapbox/Turfjs to understand how many points are in a polygon. I have rendered my polygon and points in map.onload Is then possible to call Turf.js from another function AFTER the polygon and points have been rendered to the map?
Something like this...?
$(document).ready(function(){
mapboxgl.accessToken = 'eeeeeeeeee';
map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v9',
center: [ 32.62939453125,1.7355743631421197],
zoom: 6.5,
pitch: 40,
maxZoom: 17
});
map.on('load', function () {
//geofence data
map.addSource('fencedata', {
type: 'geojson',
data: 'data/fence.geojson'
});
map.addLayer({
id: 'fence',
type: 'fill',
"source": "fencedata",
'layout': {},
'paint': {
'fill-color': '#FF0000',
'fill-opacity': 0.3
}
});
//points data
map.addSource("pointdata", {
type: "geojson",
data: 'data/points.geojson',
cluster: true,
clusterRadius: 20
});
map.addLayer({
"id": "points",
"type": "circle",
"source": "pointdata",
"paint": {
'circle-color': 'rgba(255, 255, 46, 1.0)',
'circle-radius': 8
}
});
});
map.addControl(new mapboxgl.NavigationControl());
});
geofence();
function geofence(){
var ptsWithin = turf.within(points, fence);
}
You have your points as GeoJSON, and your polygon as GeoJSON - so, yes, you can use TurfJS to find out which points are within the polygon. It looks like the code you've proposed is correct. The fact you're using Mapbox is irrelevant to this particular task.
If you're experiencing a problem with this approach, indicate it in your question.
Try to add the geofence() function inside the map on-load function after adding those layers, By that way you can make sure that the geofence() function is called after the layers have been loaded
map.on('load', function () {
//geofence data
map.addSource('fencedata', {
type: 'geojson',
data: 'data/fence.geojson'
});
map.addLayer({
id: 'fence',
type: 'fill',
"source": "fencedata",
'layout': {},
'paint': {
'fill-color': '#FF0000',
'fill-opacity': 0.3
}
});
//points data
map.addSource("pointdata", {
type: "geojson",
data: 'data/points.geojson',
cluster: true,
clusterRadius: 20
});
map.addLayer({
"id": "points",
"type": "circle",
"source": "pointdata",
"paint": {
'circle-color': 'rgba(255, 255, 46, 1.0)',
'circle-radius': 8
}
});
geofence();
});
map.addControl(new mapboxgl.NavigationControl());
});
function geofence() {
var ptsWithin = turf.within(points, fence);
}
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.
Im using jvectormap to create a map and im trying to add links to the pins to link off to websites. Ive tried a few different things but nothing seems to be working. I currently have this:
$(function(){
var markers = [
{ latLng: [-35.727014, 174.320861], name: "Whangarei", image: 'pin.png', style: {image: 'themes/startertoplight/img/pin.png'} },
];
/* map parameters */
var wrld = {
map: 'nz_mill_en',
backgroundColor: '#28343B',
onRegionClick: function(event, code) {
if (code === 'Whangarei') {
window.location = 'http://www.google.com'
}
},
zoomButtons: false,
zoomOnScroll: false,
focusOn: {
x: 0.3,
y: 0.7,
scale: 3
},
markerStyle: {
initial: {
fill: '#F8E23B',
stroke: '#383f47'
}
},
markers: markers,
};
$('#nz-map').vectorMap(wrld);
});
Any idea how i can get this to work? Thanks