Mapbox > Geofencing with Turf.js on rendered elements? - javascript

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);
}

Related

Mapbox map.on('load') is extremely slow

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,
});
});

Activate mapbox draw tool only within layer boundary

I have a geojson mapbox tileset visualized on a map. I am currently able to draw shapes such as polygons,polylines and add points to the map. But I now want to limit the draw tool to only allow drawing within the boundaries of the geojson layer. How would I do this? What I currently has, lets the cursor change to a pan symbol when it's outside of the map, and to a crosshair symbol when inside the map.
But once I click the draw tool buttons, it overrides this and lets the user draw anywhere they want.
mapboxgl.accessToken = config.MAPBOX_PUBLIC;
const map = new mapboxgl.Map({
attributionControl: false,
container: "map", // container ID
style: "mapbox://styles/mapbox/outdoors-v10",
center: [-69.45, 45.07], // starting position [lng, lat]
zoom: 8, // starting zoom
});
export function makeClickableArea() {
map.on("load", () => {
map.addSource('maine', {
'type': 'geojson',
'data': {
'type': 'Feature',
'geometry': {
'type': 'Polygon',
// These coordinates outline Maine.
'coordinates': [
[
[-67.13734, 45.13745],
[-66.96466, 44.8097],
[-68.03252, 44.3252],
[-69.06, 43.98],
[-70.11617, 43.68405],
[-70.64573, 43.09008],
[-70.75102, 43.08003],
[-70.79761, 43.21973],
[-70.98176, 43.36789],
[-70.94416, 43.46633],
[-71.08482, 45.30524],
[-70.66002, 45.46022],
[-70.30495, 45.91479],
[-70.00014, 46.69317],
[-69.23708, 47.44777],
[-68.90478, 47.18479],
[-68.2343, 47.35462],
[-67.79035, 47.06624],
[-67.79141, 45.70258],
[-67.13734, 45.13745]
]
]
}
}
});
// Add a new layer to visualize the polygon.
map.addLayer({
'id': 'maine',
'type': 'fill',
'source': 'maine', // reference the data source
'layout': {},
'paint': {
'fill-color': '#0080ff', // blue color fill
'fill-opacity': 0.5
}
});
// Add a black outline around the polygon.
map.addLayer({
'id': 'outline',
'type': 'line',
'source': 'maine',
'layout': {},
'paint': {
'line-color': '#000',
'line-width': 3
}
});
});
changeMouseSymbol();
}
// add the draw tool
function changeMouseSymbol() {
const modes = MapboxDraw.modes;
var draw = new MapboxDraw({
displayControlsDefault: false,
// Select which mapbox-gl-draw control buttons to add to the map.
controls: {
point: true,
line_string: true,
polygon: true,
},});
map.addControl(draw);
map.on("mouseover", "maine", () => {
map.getCanvas().style.cursor = "crosshair";
});
map.on("mouseleave", "maine", () => {
map.getCanvas().style.cursor = "";
});
}

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.

MapBox GL Javascript - clusters - count not displaying

I'm using MapBox GL JS v1.4.1
Based on the example here: https://docs.mapbox.com/mapbox-gl-js/example/cluster/
I cannot get my cluster count to display.
I have tried replicating the MapBox example directly and also using my own data but whatever I try results in the count not displaying.
This is what I have:
<div id="map"></div>
mapboxgl.accessToken = 'ACCESS_TOKEN';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/dark-v10',
zoom: 1
});
My geoJson data:
var geoData = {
"type": 'FeatureCollection',
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [151.12100, -33.78420]
},
"properties": {
"title" : "title",
"description": "description"
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [151.12100, -33.78420]
},
"properties": {
"title" : "title",
"description": "description"
}
}
]
};
Loading the map, adding geoJSON, clusters etc:
map.on('load', function() {
map.addSource("testMapData", {
type: "geojson",
data: geoData,
cluster: true,
clusterMaxZoom: 14,
clusterRadius: 50
});
map.addLayer({
id: "cluster-count",
type: "symbol",
source: "testMapData",
filter: ["has", "point_count"],
layout: {
"text-field": "{point_count_abbreviated}",
"text-font": ["Arial Unicode MS Bold"],
"text-size": 12,
"text-allow-overlap" : true
}
});
map.addLayer({
id: "clusters",
type: "circle",
source: "testMapData",
filter: ["has", "point_count"],
paint: {
"circle-color": "#f1f075",
"circle-radius": 40
}
});
map.addLayer({
id: "unclustered-point",
type: "circle",
source: "testMapData",
filter: ["!", ["has", "point_count"]],
paint: {
"circle-color": "#51bbd6",
"circle-radius": 8,
"circle-stroke-width": 1,
"circle-stroke-color": "#fff"
}
});
});
Based on the above I should get the cluster count on each of my clusters, but I only see the cluster with no count.
The console also shows no errors.
I can't determine if there's an issue with my geoJSON (it validates via the linter here: http://geojsonlint.com/)... or if the issue lies in how I have added the cluster-count layer... or somewhere else entirely.
Currently you are adding the cluster-count layer before the clusters layer so the latter is covering up the former. If you switch the order you will see both: https:///codepen.io/pj_leonard/pen/bGGgYwv?editors=1000
Update your code to the following:
map.on('load', function() {
map.addSource("testMapData", {
type: "geojson",
data: geoData,
cluster: true,
clusterMaxZoom: 14,
clusterRadius: 50
});
map.addLayer({
id: "clusters",
type: "circle",
source: "testMapData",
filter: ["has", "point_count"],
paint: {
"circle-color": "#f1f075",
"circle-radius": 40
}
});
map.addLayer({
id: "cluster-count",
type: "symbol",
source: "testMapData",
filter: ["has", "point_count"],
layout: {
"text-field": "{point_count_abbreviated}",
"text-font": ["Arial Unicode MS Bold"],
"text-size": 12,
"text-allow-overlap" : true
}
});
map.addLayer({
id: "unclustered-point",
type: "circle",
source: "testMapData",
filter: ["!", ["has", "point_count"]],
paint: {
"circle-color": "#51bbd6",
"circle-radius": 8,
"circle-stroke-width": 1,
"circle-stroke-color": "#fff"
}
});
});
Disclaimer: I work at Mapbox
If the order of the layers is correct, pay attention to text font, eg: "text-font": ["MicrosoftYaHeiRegular"]

defining a polygon fill color based on geojson with mapbox

I am using a mapbox example in order to create multiple polygons on a map, and I have pop-up event for each. My problem is that I need to set each polygon's fill color differently based on it's geojson properties.
This is my example.
I am using the following javascript code:
mapboxgl.accessToken = 'pk.eyJ1IjoibWFoYW5tZWhydmFyeiIsImEiOiJ6SDdSWldRIn0.8zUNm01094D1aoSeHpWYqA';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v9',
center: [51.40545845031738,
35.75069181054449],
zoom: 10
});
map.on('load', function (e) {
// Add a layer showing the state polygons.
map.addLayer({
'id': 'states-layer',
'type': 'fill',
'source': {
'type': 'geojson',
'data': 'geojson.js'
},
'paint': {
'fill-color': 'rgba(200, 100, 240, 0.4)',
'fill-outline-color': 'rgba(200, 100, 240, 1)'
}
});
// When a click event occurs on a feature in the states layer, open a popup at the
// location of the click, with description HTML from its properties.
map.on('click', 'states-layer', function (e) {
new mapboxgl.Popup()
.setLngLat(e.lngLat)
//.setHTML(e.features[0].properties.name)
.setHTML("<h1>"+e.features[0].properties.userone+"</h1>"+e.features[0].properties.name)
.addTo(map);
});
// Change the cursor to a pointer when the mouse is over the states layer.
map.on('mouseenter', 'states-layer', function () {
map.getCanvas().style.cursor = 'pointer';
});
// Change it back to a pointer when it leaves.
map.on('mouseleave', 'states-layer', function () {
map.getCanvas().style.cursor = '';
});
});
Here it loads the colors all the same
'paint': {
'fill-color': 'rgba(200, 100, 240, 0.4)',
'fill-outline-color': 'rgba(200, 100, 240, 1)'
}
On my geojson file I have a key for color:
"type": "Feature",
"properties": {
"userone":"پیروزی",
"name":"North Dafkota",
"featureclass":"Admin-1 scale rank",
"color":"red"
}
I want to use it to define the polygons fill color.
If you just want to use a color that you define in geojson feature properties. Then you could use the layers identity property like this:
map.addLayer({
'id': 'states-layer',
'type': 'fill',
'source': {
'type': 'geojson',
'data': 'geojson.js'
},
'paint': {
'fill-color': {
type: 'identity',
property: 'color',
},
'fill-outline-color': 'rgba(200, 100, 240, 1)'
}
});
Also see: https://www.mapbox.com/mapbox-gl-js/style-spec/#function-type
And: https://www.mapbox.com/mapbox-gl-js/style-spec/#types-color

Categories