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,
});
});
Related
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.
When hovering over a line in billboardjs you can see a marker which follows the mouse (a tall vertical line). Is there a function for putting a marker on the x-line which can be used without triggering an automatic marker via onmousemove/hovering over data-points?
var chart = bb.generate({
data: {
columns: [
["data1", 30, 200, 100, 400, 150, 250],
["data2", 50, 20, 10, 40, 15, 25]
],
type: "line", // for ESM specify as: line()
},
bindto: "#lineChart"
});
https://naver.github.io/billboard.js/demo/#Chart.LineChart
So to exemplify. I use an onclick (in the data object) in the chart which defocuses the view and I still want the marker to remain.
So the code would look something like:
var chart = bb.generate({
data: {
columns: [
["data1", 30, 200, 100, 400, 150, 250],
["data2", 50, 20, 10, 40, 15, 25]
],
type: "line", // for ESM specify as: line()
onclick: function (d) {
focusElsewhere()
showMarker(d.x)
}
},
bindto: "#lineChart"
});
So the question is if there is a function for this, or an obvious fix?
I have looked through https://naver.github.io/billboard.js/release/latest/doc/Chart.html but I may of course have missed something.
I found that using xgrids did the trick. I don't think that the documentation gives a good example of how to use it. But basically you can use the "value" field to give which point the line should be on and add a class to show different kinds of lines.
var chart = bb.generate({
data: {
columns: [
["data1", 30, 200, 100, 400, 150, 250],
["data2", 50, 20, 10, 40, 15, 25]
],
type: "line", // for ESM specify as: line()
onclick: function (d) {
focusElsewhere()
this.xgrids.add({ value: d.x, class: "hover-line" }); //showMarker(d.x)
}
},
bindto: "#lineChart"
});
To remove the line or reset the billboard for continued use so to say, you can use
xgrids․remove({}) and add an object with some parameters of what kind of lines you want to remove.
The geojson layer does not display when I load the geojson through d3.json as a data object. However, if I reference the geojson as a URL, then it displays properly.
I am using deck.gl: 6.3.2, mapbox-gl: v0.52.0. I am using vanilla javascript.
When I run this code, it does not display. I checked the 'geomdata'. It is fine.
d3.json("roads.json", function(geomdata) {
deckgl = new deck.DeckGL({
container,
map: mapboxgl,
mapboxApiAccessToken:
"<myMapBoxKey>,
mapStyle: "mapbox://styles/mapbox/light-v9",
longitude: -98.58,
latitude: 39.82,
zoom: 4,
layers: [
new deck.GeoJsonLayer({
geomdata,
stroked: true,
filled: true,
lineWidthMinPixels: 2,
opacity: 0.4,
getLineColor: [255, 100, 100],
getFillColor: [200, 160, 0, 180]
})
]
});
});
However, when I run this where the geojson is a URL, it displays just fine.
d3.json("roads.json", function(geomdata) {
var US_MAP_GEOJSON =
"https://raw.githubusercontent.com/uber-common/deck.gl-data/master/examples/highway/roads.json";
deckgl = new deck.DeckGL({
container,
map: mapboxgl,
mapboxApiAccessToken:
"<myMapBoxAccessKey>",
mapStyle: "mapbox://styles/mapbox/light-v9",
longitude: -98.58,
latitude: 39.82,
zoom: 4,
layers: [
new deck.GeoJsonLayer({
data: US_MAP_GEOJSON ,
stroked: true,
filled: true,
lineWidthMinPixels: 2,
opacity: 0.4,
getLineColor: [255, 100, 100],
getFillColor: [200, 160, 0, 180]
})
]
});
});
Any hints/ideas?
Thanks
It looks like you have to pass an object into your GeoJsonLayer. But your first field is not key: value combo, it is just a value. Try setting your data like this data: geomdata.
new deck.GeoJsonLayer({
data: geomdata,
stroked: true,
filled: true,
lineWidthMinPixels: 2,
opacity: 0.4,
getLineColor: [255, 100, 100],
getFillColor: [200, 160, 0, 180]
})
I'm using mapbox and I've clustered my data. Now I want to show a
background image (instead of a black background!) when a user has zoomed to a point.
I add it like this:
this.map.addLayer({
id: "unclustered-point",
type: "circle",
source: "earthquakes",
filter: ["!has", "point_count"],
paint: {
"circle-color": "black",
"circle-radius": 8,
"circle-stroke-width": 4,
"circle-stroke-color": "#fff",
}
});
I'm loading my geojson like this:
this.map.addSource("earthquakes", {
type: "geojson",
data: this.locations,
cluster: true,
clusterMaxZoom: 14, // Max zoom to cluster points on
clusterRadius: 50 // Radius of each cluster when clustering points (defaults to 50)
});
this.map.addLayer({
id: "clusters",
type: "circle",
source: "earthquakes",
filter: ["has", "point_count"],
paint: {
"circle-color": [
"step",
["get", "point_count"],
"#002e5b",
100,
"#002e5b",
750,
"#002e5b"
],
"circle-radius": [
"step",
["get", "point_count"],
20,
100,
30,
750,
40
],
}
});
So how can I get a background image instead of a black background?
You can add another layer of points with your custom image that becomes visible at z14. This way the custom image will cover the black circles.
I recommend making the black circle extremely small or nonexistent so it doesn't conflict with the custom images.
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);
}