How can I show the count of points in the particular latitude and longitude in Mapboxgl? - mapbox

I want to show the marker as well as the number of points available in a particular latitude and longitude.
I expect something Similar as above.
But i have added another 2 layers to show different color and number on it. If I do this way, I am getting "undefined" error while showing popup. Since the data takes from the other layer. If it takes from layer "locations" it works as expected. But when we have multiple occurences popup content shows "undefined". Below is my implementation output and code
map.on('load', function () {
map.addSource("place", {
type: "geojson",
data: liveData,
cluster: true,
clusterMaxZoom: 14,
clusterRadius: 50
});
map.addLayer({
"id": "locations",
"type": "circle",
"source": "location",
"paint": {
"circle-radius": 7,
"circle-color": "#FFF000",
"circle-stroke-width": 4,
"circle-stroke-color": "#FFFFFF"
}
});
map.addLayer({
id: "clusters",
type: "circle",
source: "location",
filter: ["has", "point_count"],
paint: {
"circle-color": {
property: "point_count",
type: "interval",
stops: [
[0, "#FFF000"],
[2, "#DC143C"],
]
},
"circle-radius": {
property: "point_count",
type: "interval",
stops: [
[0, 7],
[2, 7],
]
}
}
});
map.addLayer({
id: "cluster-count",
type: "symbol",
source: "location",
filter: ["has", "point_count"],
layout: {
"text-field": "{point_count_abbreviated}",
"text-font": ["DIN Offc Pro Medium", "Arial Unicode MS Bold"],
"text-size": 12,
"text-offset": [0, 5]
}
});
map.on('click', 'locations', function (e) {
let htmlString = "";
for (let i = 0; i < e.features.length; i++) {
htmlString = htmlString + e.features[i].properties.description;
if (i != e.features.length - 1) {
htmlString = htmlString + "</br>";
}
}
new mapboxgl.Popup()
.setLngLat(e.features[0].geometry.coordinates)
.setHTML(htmlString)
.addTo(map);
});
}
My working fiddle
I want to achieve this as first picture or popup should work in my approach?

As far as I know getting all features that get combined in the cluster is currently not possible via mapbox's cluster feature.
See here: https://github.com/mapbox/mapbox-gl-js/issues/3318

Related

How to use react-map-gl to draw line between two point

I am trying to draw a line between two points using react-map-gl library. I can not find example from the official document, So I am trying to reproduce same behavior from following code snippet which use Mapbox library
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v11',
center: [-122.486052, 37.830348],
zoom: 15
});
map.on('load', function() {
map.addSource('route', {
'type': 'geojson',
'data': {
'type': 'Feature',
'properties': {},
'geometry': {
'type': 'LineString',
'coordinates': [
[-122.483696, 37.833818],
[-122.493782, 37.833683]
]
}
}
});
map.addLayer({
'id': 'route',
'type': 'line',
'source': 'route',
'layout': {
'line-join': 'round',
'line-cap': 'round'
},
'paint': {
'line-color': '#888',
'line-width': 8
}
});
});
Here is the sandbox, I do not see any errors on the console but the line is not displayed:
https://codesandbox.io/s/draw-line-between-two-point-v0mbc?file=/src/index.js:214-226
The code in the sandbox actually works (for me anyway), but is misleading because the line drawn is nowhere near the viewport.
A couple of things to note are that coordinates are an array given in [long, lat] which may not be what most people would assume. For example, if you cut and paste [lat,long] from google maps for San Fransisco, you get [37.77909036739809, -122.41510269913951]. Then you'll have to reverse those and put them in:
const dataOne = {
type: "Feature",
properties: {},
geometry: {
type: "LineString",
coordinates: [
[-122.41510269913951, 37.77909036739809],
[39.5423, -77.0564]
]
}
};
Also, the sample code has some cruft in it. Edit the variable dataOne not the other unused place.
Now you'll see a line from San Fransisco to some random spot in the middle of Antarctica that was really easy to miss.
Just in case the link goes bad, the full code is:
import React, { Component } from "react";
import { render } from "react-dom";
import ReactMapGL, { Source, Layer } from "react-map-gl";
class App extends Component {
constructor(props) {
super(props);
this.state = {
viewport: {
latitude: 38.63738602787579,
longitude: -121.23576311149986,
zoom: 6.8,
bearing: 0,
pitch: 0,
dragPan: true,
width: 600,
height: 600
}
};
}
render() {
const { viewport } = this.state;
const MAPBOX_TOKEN =
"pk.eyJ1Ijoic21peWFrYXdhIiwiYSI6ImNqcGM0d3U4bTB6dWwzcW04ZHRsbHl0ZWoifQ.X9cvdajtPbs9JDMG-CMDsA";
const dataOne = {
type: "Feature",
properties: {},
geometry: {
type: "LineString",
coordinates: [
[-122.41510269913951, 37.77909036739809],
[39.5423, -77.0564]
]
}
};
return (
<ReactMapGL
{...viewport}
mapboxApiAccessToken={MAPBOX_TOKEN}
onViewportChange={(newViewport) => {
this.setState({ viewport: newViewport });
}}
>
<Source id="polylineLayer" type="geojson" data={dataOne}>
<Layer
id="lineLayer"
type="line"
source="my-data"
layout={{
"line-join": "round",
"line-cap": "round"
}}
paint={{
"line-color": "rgba(3, 170, 238, 0.5)",
"line-width": 5
}}
/>
</Source>
</ReactMapGL>
);
}
}
render(<App />, document.getElementById("root"));

Mapbox proportional symbol feature radius

I'm new to Mapbox GL JS and struggling with data-driven styling. I'd like to scale my circle features proportionally based on a value in my data source (a Google Sheet). Here's a code snippet:
map.addLayer({
id: 'csvData',
type: 'circle',
source: {
type: 'geojson',
data: data
},
paint: {
'circle-color': '#dd502c',
'circle-stroke-width': 1,
'circle-stroke-color': '#dd502c',
'circle-opacity': 0.5,
"circle-radius": {
property: 'value',
stops: [
[2, 5],
[1000, 10],
[8000, 20],
]
}
}
});
Many Thanks,
Looks like AverageNitrate (I assume this was the property you were trying to plot) was a string. Converting to int did the trick.
Also I referenced it in the styling expression instead of the generic 'value'
data.features.forEach((datum)=>{
datum.properties.AverageNitrate = parseInt(datum.properties.AverageNitrate)
})
//Add the the layer to the map
map.addLayer({
id: 'csvData',
type: 'circle',
source: {
type: 'geojson',
data: data
},
paint: {
'circle-color': '#dd502c',
'circle-stroke-width': 1,
'circle-stroke-color': '#dd502c',
'circle-opacity': 0.5,
"circle-radius": {
property: 'AverageNitrate',
stops: [
[2, 5],
[1000, 10],
[8000, 20],
]
}
}
});
Here is a link to the edited fiddle: https://jsfiddle.net/mojsc6pn/35/

Highlighting Fill-Extrude Features on Hover after tilting?

I want to highlight fill-extrusion features when hovered over them.
The styling related to this is straight-forward using expressions and feature state, but I am having trouble retrieving the correct features.
There is code available online to change the feature state when hovered over, and it seems straight-forward enough, so I adapted it:
var hover_id = null;
const feature_state = { hover: true }
map.on('mousemove', '3d-buildings', (e) => {
// Get features under cursor, following render order
const features = map.queryRenderedFeatures(e.point);
// Check that features are not empty
if (features.length > 0) {
// Clean up previously hovered feature
if (hover_id) {
map.removeFeatureState({source: "composite", sourceLayer: 'building', id: hover_id});
}
// Set feature state of the new hovered feature
hover_id = features[0].id;
map.setFeatureState({source: 'composite', sourceLayer: 'building', id: hover_id}, feature_state);
console.log(hover_id)
}
});
While this works well initially, it stops working as soon as I tilt the camera using the right mouse button. After tilting, the foremost element no longer gets selected (something else seems to get selected and an ID gets printed out, but nothing shows up on the map and no error is thrown).
On a related note, the correct feature only gets selected after zooming in quite far - there is a large zoom range where the buildings already get rendered to the screen, but seem to not get picked up by queryRenderedFeatures. Is this expected behaviour?
Expected behaviour:
map.queryRenderedFeatures(...)[0] selects the foremost feature, independent of the camera tilt.
What could be a possible reason for the camera tilt influencing the feature selection? Is this a bug or am I misusing the API?
I think the issue you’re facing has nothing to do with tilt but with the fact that you’re adding and removing the state instead of changing the value of the state. The state must be declared in the layer definition, change the color with a expression, and then you only need to change the value of the state.
Here you have a fiddle I have created to show how to change color of fill extrusions on mouse over/out
Relevant code is this:
let mapConfig = {
NYC: {
origin: [-74.044514, 40.689259, 39],
center: [-74.0137, 40.70346, 0],
zoom: 16.2,
pitch: 60,
bearing: 35
}
}
mapboxgl.accessToken = 'PUT YOUR TOKEN HERE';
let point = mapConfig.NYC;
var map = new mapboxgl.Map({
style: 'mapbox://styles/mapbox/streets-v11',
center: point.center,
zoom: point.zoom,
pitch: point.pitch,
bearing: point.bearing,
container: 'map',
antialias: true,
hash: true
});
map.on('style.load', function() {
if (map.getSource('composite')) {
map.addLayer({
'id': '3d-buildings',
'source': 'composite',
'source-layer': 'building',
'type': 'fill-extrusion',
'minzoom': 14,
'paint': {
'fill-extrusion-color': [
'case',
['boolean', ['feature-state', 'hover'], false],
'#ff0000',
'#ddd'
],
'fill-extrusion-height': ["number", ["get", "height"], 5],
'fill-extrusion-base': ["number", ["get", "min_height"], 0],
'fill-extrusion-opacity': 1
}
}, 'road-label');
}
let fHover;
map.on('mousemove', function(e) {
//157001066
var features = map.queryRenderedFeatures(e.point, {
layers: ['3d-buildings']
});
if (features[0]) {
mouseout();
mouseover(features[0]);
} else {
mouseout();
}
});
map.on('mouseout', function(e) {
mouseout();
});
function mouseout() {
if (!fHover) return;
map.getCanvasContainer().style.cursor = 'default';
map.setFeatureState({
source: fHover.source,
sourceLayer: fHover.sourceLayer,
id: fHover.id
}, {
hover: false
});
}
function mouseover(feature) {
fHover = feature;
map.getCanvasContainer().style.cursor = 'pointer';
map.setFeatureState({
source: fHover.source,
sourceLayer: fHover.sourceLayer,
id: fHover.id
}, {
hover: true
});
}
});
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.
#jscastro This works fine: My requirement is I need to change the color of few buildings with lat and lng. I have achieved getting buildings id's from lat and lng by using below API
https://api.mapbox.com/v4/mapbox.mapbox-streets-v8,mapbox.mapbox-terrain-v2/tilequery/55.26365875255766,25.188400365955193.json?radius=30&limit=10&dedupe&access_token=.
I am facing one issue here, The colors are changing only after zoom level 17. I want to change the color on zoom level 15.
map.on("style.load", function () {
if (map.getSource("composite")) {
const layers = map.getStyle().layers;
const labelLayerId = layers.find(
(layer) => layer.type === "symbol" && layer.layout["text-field"]
).id;
map.addLayer(
{
id: "3d-buildings",
source: "composite",
"source-layer": "building",
filter: ["==", "extrude", "true"],
type: "fill-extrusion",
minzoom: 15,
zoom: 15,
pitch: 60,
bearing: -60,
layout: {
// Make the layer visible by default.
visibility: "visible",
},
paint: {
"fill-extrusion-color": [
"case",
["boolean", ["feature-state", "hover"], false],
"#00ff00",
"#AED0EC",
],
"fill-extrusion-height": [
"interpolate",
["linear"],
["zoom"],
15,
0,
15.5,
["get", "height"],
],
"fill-extrusion-base": [
"interpolate",
["linear"],
["zoom"],
15,
0,
15.05,
["get", "min_height"],
],
"fill-extrusion-opacity": 1,
},
},
labelLayerId
);
}
map.getCanvasContainer().style.cursor = "pointer";
map.setFeatureState(
{
source: "composite",
sourceLayer: "building",
id: "4411722601841895",
},
{
hover: true,
}
);
map.getCanvasContainer().style.cursor = "pointer";
map.setFeatureState(
{
source: "composite",
sourceLayer: "building",
id: "1315660041727095",
},
{
hover: true,
}
);
map.getCanvasContainer().style.cursor = "pointer";
map.setFeatureState(
{
source: "composite",
minzoom: 15,
sourceLayer: "building",
id: "3957345234349675",
},
{
hover: true,
}
);
map.getCanvasContainer().style.cursor = "pointer";
map.setFeatureState(
{
source: "composite",
sourceLayer: "building",
id: "5328485811",
},
{
hover: true,
}
);
});

Mapbox ID layer cannot be selected

I have added layer in mapbox, and then add click on it to trigger popups. That works fine and looks like this:
map.addLayer({
"id": "circle",
"type": "circle",
"source": "companies",
"paint": {
"circle-radius": 20,
"circle-color": "#C6DB3E",
"circle-opacity": {
"stops": [[3, 0.1], [22, 0.8]]
}
}
});
And here I select that layer for triggering popup:
map.on('click', function (e) {
var features = map.queryRenderedFeatures(e.point, {
layers: ["circle"]
});
if (!features.length) {
return;
}
var feature = features[0];
console.log(feature);
// Populate the popup and set its coordinates and content
var popup = new mapboxgl.Popup()
.setLngLat(feature.geometry.coordinates)
.setHTML('...')
.addTo(map);
});
But problem appears when I changed layer to use dynamic circle-radius, and layer now looks like this:
map.addLayer({
"id": "circle",
"type": "circle",
"source": "companies",
"paint": {
"circle-radius": {
property: 'Size',
type: 'identity'
},
"circle-color": "#C6DB3E",
"circle-opacity": {
"stops": [[3, 0.1], [22, 0.8]]
}
}
});
This layers is also printed properly to the map. But I cannot click on it to get a popup. So after changing circle-radius, ID is not clickable.
Funny is that if I consoleLog ID's with map.getStyle().layers, ID appears in console, with all other layers.
No errors.
The style syntax for circle-radius is not valid. See Mapbox Style Spec for expressions or this other answer.
Also: You can simplify the click handler by providing the id of the layer as the second parameter:
map.on('click', 'circle', function (e) {
var features = e.features;
if (!features.length) {
return;
}
var feature = e.features[0];
var popup = new mapboxgl.Popup()
.setLngLat(feature.geometry.coordinates)
.setHTML(feature.properties.someProperty)
.addTo(map);
});
Mapbox has an example of this on their site: https://www.mapbox.com/mapbox-gl-js/example/popup-on-click/
I updated mapbox and it worked fine, with code I posted in question. I also tried what u suggested and it works too. Thanks #Eczajk! At the end I ended up with this code for circle radius:
"circle-radius": {
property: 'Size',
type: 'exponential',
stops: [
[4, 4],
[170, 170]
]
}
And here is explained example: https://www.mapbox.com/help/gl-dds-map-tutorial/

fill-extrusion not displaying correctly with mapbox-gl-js

This is what my dataset looks like:
Seattle Crime Dataset
What I want to do is change the extrusion height based on the frequency column. I can successfully display these as points but I'm struggling with it whenever I use fill-extrusion. Can you help point me in the right direction?
map.addLayer({
'id': 'total',
'type': 'circle',
'source': {
type: 'vector',
url: 'mapbox://askakdagr8.9tklrr8g'
},
'source-layer': 'GroupedOutput-9i6ink',
'paint': {
// make circles larger as the user zooms from z12 to z22
'circle-radius': {
'base': 1.75,
'stops': [
[12, 2],
[22, 180]
]
},
'circle-color': '#ff7770'
}
});
Since the mapbox-gl-js does not currently have functionality for extruding a circle, you need to replace the points with a polygon, and interpolating the circle, for example, by a function turf.circle:
map.on('sourcedata', function(e) {
if (e.sourceId !== 'total') return
if (e.isSourceLoaded !== true) return
var data = {
"type": "FeatureCollection",
"features": []
}
e.source.data.features.forEach(function(f) {
var object = turf.centerOfMass(f)
var center = object.geometry.coordinates
var radius = 10;
var options = {
steps: 16,
units: 'meters',
properties: object.properties
};
data.features.push(turf.circle(center, radius, options))
})
map.getSource('extrusion').setData(data);
})
[ http://jsfiddle.net/zjLek40n/ ]