TLDR; How do I add a popup to a mapbox "line" type layer?
I have a function loading a mapbox map and adding a layer of lines to it.
My goal is to add a popup on clicking on that line.
I followed the examples and added an on click event. But this gives me an error. Any pointers on what I am doing wrong?
function renderLineLayer(layerName,data) {
map.on('load', function() {
map.addLayer({
"id": layerName,
"type": "line",
"source": {
"type": "geojson",
"data": data
},
"layout": {
"line-join": "round",
"line-cap": "round",
"visibility":"visible"
},
"paint": {
"line-color": "blue",
"line-width": 8
}
});
console.log(map.getLayer(layerName));
map.on('click', layerName, function (e) {
console.log('click');
new mapboxgl.Popup()
.setLngLat(e.lngLat)
.setHTML(e.features[0].properties.name)
.addTo(map);
});
});
}
The error I get is.
TypeError: listeners[i].call is not a function[Learn More] mapbox-gl-dev.js:29779:13
This is a bit of a guess, but the on('click') event API changed recently. Previously it didn't have the layer argument (IIRC). If you're using an older version of the Mapbox-GL-JS library, it would be trying to call your second argument (the layer name) as if it were a function and it would give that error message.
Solution: update to the latest Mapbox-GL-JS library version (0.36).
(I don't think the process for responding to mouse clicks on line features is any different than for points or polygons.)
Related
I'd like to render a layer from geoserver with using WMTS.
I'm using mapbox-gl to render a map and layers.
The website I'm referring to is https://docs.geoserver.org/stable/en/user/styling/mbstyle/source.html.
This shows how to create a layer on geoserver and render it on a mapbox map.
However, I'm getting an error TileOutOfRange when I request tiles.
For example, when I request tiles with this url,
http://<my_server>:8080/geoserver/gwc/service/wmts?REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0&LAYER=myspace:japan&STYLE=&TILEMATRIX=EPSG:4326:8&TILEMATRIXSET=EPSG:4326&FORMAT=application/vnd.mapbox-vector-tile&TILECOL=221&TILEROW=102.
I get this error message.
Column 221 is out of range, min: 440 max:462.
Here below is my code to fetch a layer that I created on geoserver.
map.on("load", () => {
map.addSource("test", {
type: "vector",
tiles: ["http://<my_server>:8080/geoserver/gwc/service/wmts?REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0&LAYER=myspace:japan&STYLE=&TILEMATRIX=EPSG:4326:{z}&TILEMATRIXSET=EPSG:4326&FORMAT=application/vnd.mapbox-vector-tile&TILECOL={x}&TILEROW={y}"],
generateId: true,
minZoom: 0,
maxZoom: 14,
});
map.addLayer({
"id": "test",
"type": "fill",
"source": "test",
"source-layer": "japan",
"layout": { "visibility": "visible" },
"paint": {}
});
});
It would be much appreciated if anyone gave me a way to solve this.
Thank you.
I made sure that I checked this box application/vnd.mapbox-vector-tile in the tile caching settings.
I left out 900913 from the default gridset settings, since my data is EPSG:4326.
When creating a layer, I made sure that I clicked Compute from data and Compute from native bounds to set bounding boxes.
Using my styled Mapbox GL JS map, with multistring lines from my geojson file, when a user clicks one of the lines I need it highlighted, this part I have working using:
map.on('click', 'route', function(e) {
var features = map.queryRenderedFeatures(e.point, { layers: ['route'] });
if (!features.length) {
return;
}
if (typeof map.getLayer('selectedRoad') !== "undefined" ){
map.removeLayer('selectedRoad')
map.removeSource('selectedRoad');
}
var feature = features[0];
console.log(feature.toJSON());
map.addSource('selectedRoad', {
"type":"geojson",
"data": feature.toJSON()
});
map.addLayer({
"id": "selectedRoad",
"type": "line",
"source": "selectedRoad",
"layout": {
"line-join": "round",
"line-cap": "round"
},
"paint": {
"line-color": "yellow",
"line-width": 8
}
});
Example of my geojson entries:
{"type": "FeatureCollection", "features": [{"type":"Feature","geometry":
{"type":"MultiLineString","coordinates":[[[-117.252069,32.748772],[-117.25169,32.749212],
[-117.25135,32.749608]]]},"properties":{"displaylabel":"1950-1999 GRAND
ST","sidedaytime1":"West Side Fri 7 am-10 am","sidedaytime2":"7am"}},
{"type":"Feature","geometry":{"type":"MultiLineString","coordinates":
[[[-117.25135,32.749608],[-117.250981,32.750037],[-117.250621,32.750452]]]},"properties":
{"displaylabel":"2000-2049 GRAND ST","sidedaytime1":"West Side Fri 7 am-10
am","sidedaytime2":"East Side Wed 7 am-10 am"}}
I need the user to be able to click and highlight 1 to 4 lines, and all 1 to 4 stay highlighted. Additionally, when a user 2nd clicks a highlighted line, I need it "unhighlighted. I cannot find any examples anywhere. Help is much appreciated!
The easiest way to accomplish this is to attach an event handler to the layer containing these lines, and then edit the feature state when a feature is clicked. When you load the layer to the map, you can use an expression to style each feature based on its feature state. Here's how I've handled this situation before:
/*loading the layer using an expression that defines a feature's color based on the
value located in the 'click' property*/
map.on('load', function() {
map.addLayer({
'id': 'route',
'type': 'line',
'source': {/*your source data*/},
'paint': {
'line-color': [
'case',
['boolean', ['feature-state', 'click'], false],
'#0f397d',
'#2e6fd9',
]}
});
});
/*this function would then be used to change the 'click' feature state
when the feature is clicked on*/
map.on('click', 'route', function (e) {
var features = map.queryRenderedFeatures(e.point, { layers: ['route'] });
for (var i = 0; i < features.length; i++) {
map.setFeatureState({source: 'route', id: features[i].id}, {click: true});
}
});
The above code will set any clicked feature to have a 'click' feature state of true, which will then style it using the second color listed in the expression. If you want to change it back on another click, you can set it back to false.
I am trying to add a popup to my map icons in Mapbox GL JS. So far I have been unsuccessful.
When I create a layer, in the layer's data, I have specified several properties. However when I try and add a popup to the icon, all of the properties are not present. Attempting to access them simply returns undefined.
Adding the layer:
function addRedAirports() {
map.addSource('hoggitRed', {
type: 'geojson',
cluster: true,
clusterMaxZoom: 14, // Max zoom to cluster points on
clusterRadius: 10, // Radius of each cluster when clustering points (defaults to 50)
data: redAirportArray[0]
});
map.addLayer({
"id": 'reds',
"type": "symbol",
"source": "hoggitRed",
"layout": {
"icon-image": "redIcon",
"icon-size": 0.075,
"icon-anchor": "bottom",
"icon-allow-overlap": true
}
});
Here is the contents of the data (redAirportArray[0]). I am looping through an api to get this data.
When I pass this data to mapbox, the properties are complete and correct. However when I try access them for a popup, I get undefined. Console logging the mapbox layer shows none of the inputted properties present..
(I have condensed this code slightly.. every loop I create a feature and then push it to the feature collection. I combined the two in this snippet for the sake of simplicity)
let redAirportArray = [{
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": { //SETTING THE PROPERTIES
"test": 'test',
"ID": airportsRed[x].Id,
"team": airportsRed[x].Coalition
},
"geometry": {
"type": "Point",
"coordinates": [airportsRed[x].LatLongAlt.Long, airportsRed[x].LatLongAlt.Lat]
}
}]
Adding a popup on click
map.on('click', 'reds', function (e) {
var coordinates = e.features[0].geometry.coordinates.slice();
let team = e.features[0].properties.ID;
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
}
new mapboxgl.Popup()
.setLngLat(coordinates)
.setHTML(team)
.addTo(map);
});
Thanks in advance and I hope you can help!
With the way your layer is currently being added, you're looking for properties in the wrong location. e.features[0] is not defined since e is the feature you just clicked. Your pop up code should look something like this:
map.on('click', 'reds', function (e) {
var coordinates = e.geometry.coordinates.slice(); // Changed
let team = e.properties.ID; // Changed
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
}
new mapboxgl.Popup()
.setLngLat(coordinates)
.setHTML(team)
.addTo(map);
});
I would like to add a custom marker to my map. I am using a mapbox gl script.
The only documentation that I found related to this topic is this one https://www.mapbox.com/mapbox-gl-js/example/geojson-markers/.
I tried to customize given example and I managed to add a marker and modify it a little changing the size, but since I don't understand all the parameters, I don't know how to add my own marker. Is there any documentation that is more detailed?
Here is my code:
<script>
mapboxgl.accessToken = 'pk.eyJ1IjoiYWl3YXRrbyIsImEiOiJjaXBncnI5ejgwMDE0dmJucTA5aDRrb2wzIn0.B2zm8WB9M_qiS1tNESWslg';
var map = new mapboxgl.Map({
container: 'map', // container id
style: 'mapbox://styles/aiwatko/cipo0wkip002ddfm1tp395va4', //stylesheet location
center: [7.449932,46.948856], // starting position
zoom: 14.3, // starting zoom
interactive: true
});
map.on('load', function () {
map.addSource("markers", {
"type": "geojson",
"data": {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [7.4368330, 46.9516040]
},
"properties": {
"title": "Duotones",
"marker-symbol": "marker",
}
}]
}
});
map.addLayer({
"id": "markers",
"type": "symbol",
"source": "markers",
"layout": {
"icon-image": "{marker-symbol}-15",
"icon-size": 3
}
});
});
</script>
Thanks in advance!
Oktawia
There are two ways to customize markers in Mapbox.
Raster Markers in Mapbox
See this link on Mapbox.com for Custom marker icons. That example shows how to use a .png as a marker.
SVG Markers in Mapbox
You are pretty close to modifying the icons, but take some time to familiarize yourself with the parameters.
The icon-image may be the harder one to understand. It takes the property "marker-symbol": "marker" from the GeoJson, and "icon-image": "{marker-symbol}-15", to create the final result of marker-15.
This brings up a further question: where/how are the markers defined?!?
The markers also come from Mapbox and are called Maki Icons. You can change the "marker-symbol" to aquarium or cafe to see the results.
From the Mapbox GL Style Reference
icon-size — Scale factor for icon. 1 is original size, 3 triples the size.
icon-image — A string with {tokens} replaced, referencing the data property to pull from.
I'm using L.geoJson and adding layer to my map, then with items.on('click', function (event) {}) displaying info of selected object which is stored in event.layer (getting info with toGeoJSON()).
Problem is, when there are some of the items, everything seems to work, but now when there are >1000 polygons, some of the data when using on('click') does not contain my info of the feature inside event.layer.
What could be a problem?
ADDITIONAL INFO:
Our GeoJSON looks something like this, it has additional data like ID and various properties.
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": 1,
"geometry": {"type": "Point", "coordinates": [102.0, 0.5]},
"properties": {"prop1": "value1"}
},
{
"type": "Feature",
"id": 2,
"geometry": {"type": "Point", "coordinates": [142.0, 15.5]},
"properties": {"prop1": "value2"}
}
]
}
I put everything on a map:
data = L.geoJson(data);
allItems.clearLayers().addLayer(data);
Features are displayed on a map.
Then I listen for clicks on the features on the map:
allItems.on('click', function (event) {
// On many of the features this is empty,
// on some data can be retrieved.
// On some that doesn't have ID, properties
// are empty too
console.log(event.layer.toGeoJSON().id);
});
GeoJSON has been checked and ID and properties ARE THERE.
Here's a little explanation about how to handle click events on L.GeoJSON layer and/or it's features. I've commented the code to explain what is going on and added an example on Plunker for you so you can test the concept.
Example on Plunker: http://plnkr.co/edit/TcyDGH?p=preview
Code:
var geoJsonLayer = L.geoJson(data, {
// Executes on each feature in the dataset
onEachFeature: function (featureData, featureLayer) {
// featureData contains the actual feature object
// featureLayer contains the indivual layer instance
featureLayer.on('click', function () {
// Fires on click of single feature
console.log('Clicked feature layer ID: ' + featureData.id);
});
}
}).on('click', function () {
// Fires on each feature in the layer
console.log('Clicked GeoJSON layer');
});
As for your code, i'm quite confused as to where the allItems comes from. A layerGroup or something like that? Trying to capture clicks on individual features in the GeoJSON layer on that object won't work, because it won't be able to differentiate between the features. Same goes for the handler on the GeoJSON layer, it will fire, but won't know which feature you clicked. The handler in the onEachFeature function will. But i'm assuming you understand by now if your understanding the code/example above.