MapBox Hide Layer Button Text Change - mapbox

Sorry of this is a simple question but I have a Mapbox map with 2 layers running on my website. Each layer can be toggled on or off using the example code on the MapBox support site.
However it uses the name of the layers directly for the text of the menu buttons. Is there a way to change this to a more aesthetically pleasing title?
Many thanks
Col
Code added below :
map.on('load', function() {
// add source and layer for 2020 Tour
map.addSource('pjuk-2020-tour', {
type: 'vector',
url: 'mapbox://styles/silentwitness/cka13nxh44g1v1iqd7xxr1nys'
});
map.addLayer({
'id': 'pjuk-2020-tour',
'type': 'circle',
'source': 'pjuk-2020-tour',
'layout': {
// make layer visible by default
'visibility': 'visible'
},
'source-layer': 'pjuk-2020-tour'
});
map.addSource('pjuk-previous-tours', {
type: 'vector',
url: 'mapbox://styles/silentwitness/cka13nxh44g1v1iqd7xxr1nys'
});
map.addLayer({
'id': 'pjuk-previous-tours',
'type': 'circle',
'source': 'pjuk-previous-tours',
'layout': {
// make layer visible by default
'visibility': 'visible'
},
'source-layer': 'pjuk-previous-tours'
});
});
// enumerate ids of the layers
var toggleableLayerIds = ['pjuk-2020-tour', 'pjuk-previous-tours'];
// set up the corresponding toggle button for each layer
for (var i = 0; i < toggleableLayerIds.length; i++) {
var id = toggleableLayerIds[i];
var link = document.createElement('a');
link.href = '#';
link.className = 'active';
link.textContent = id;

Using this example as a model, there are many ways which you could go about doing this. One is to create a variable storing a mapping from the desired title to the name of the layer, so that clicking on the relevant button will use the textContent as a key in this mapping to retrieve a value representing the name of the layer. For example:
var titleToLayerName = {
'PJUK Previous Tours': 'pjuk_previous_tours',
'PJUK 2020 Tour': 'pjuk_2020_tour'
}
...
link.onclick = function(e) {
var clickedLayer = titleToLayerName[this.textContent];
...
};

Related

Update style of individual feature from single geoJSON source on Mapbox map, when clicked

I'm working with Mapbox GL JS to plot geoJSON data on a map using their external geoJSON example as a starting point. The geoJSON file contains lots of features which are plotted as individual markers on the same layer. I would like to highlight the clicked marker by changing its colour from red to blue. I have adapted the example to show a pop-up with the point id when clicked (just as a proof of concept that the markers can fire events when clicked), however, I'm struggling to find a way to change the styling of the individual clicked marker.
The code is currently as follows:
mapboxgl.accessToken = 'pk.eyJ1IjoiZGFuYnJhbWFsbCIsImEiOiJjbDB3ODFveHYxOG5rM2pubWpwZ2R1Y2xuIn0.yatzJHqBTjQ6F3DHASlriw';
const map = new mapboxgl.Map({
container: 'map', // container ID
style: 'mapbox://styles/mapbox/satellite-v9', // style URL
zoom: 7, // starting zoom
center: [138.043, 35.201] // starting center
});
map.on('load', () => {
map.addSource('earthquakes', {
type: 'geojson',
data: 'https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson'
});
map.addLayer({
'id': 'earthquakes-layer',
'type': 'circle',
'source': 'earthquakes',
'paint': {
'circle-radius': 8,
'circle-stroke-width': 2,
'circle-color': 'red',
'circle-stroke-color': 'white'
}
});
});
map.on('click', 'earthquakes-layer', (e) => {
new mapboxgl.Popup()
.setLngLat(e.lngLat)
.setHTML('Id: ' + e.features[0].properties.id)
.addTo(map);
});
Here is a codepen: https://codepen.io/danb1/pen/BaYjOyx
Is it the case that it's actually not possible to use this approach, and instead each feature from the geoJSON file needs to be plotted as a separate layer? I'm struggling to find any examples of this and am not able to modify the geoJSON source — it has to come from one single file (rather than loading multiple geoJSON files separately on separate layers).
This is possible using feature-state. The first thing to do is to ensure the layer data contains ids for each feature (in the example the source data doesn't so we need to add generateId: true to the map.addSource method).
We then need to add mousemove and mouseleave events to the map to store the moused-over feature id (if there is one, i.e. if the mouse is hovering over a feature):
let hoveredEarthquakeId = null;
map.on('mousemove', 'earthquakes-layer', (e) => {
map.getCanvas().style.cursor = 'pointer';
if (e.features.length > 0) {
map.setFeatureState(
{ source: 'earthquakes', id: e.features[0].id },
{ hover: true }
);
hoveredEarthquakeId = e.features[0].id;
}
});
map.on('mouseleave', 'earthquakes-layer', () => {
map.getCanvas().style.cursor = '';
if (hoveredEarthquakeId !== null) {
map.setFeatureState(
{ source: 'earthquakes', id: hoveredEarthquakeId },
{ hover: false }
);
}
hoveredEarthquakeId = null;
});
Finally, in the layer properties, the colour setting of the circle needs to be updated to reflect the hover value stored against the feature:
'circle-color': [
'case',
['boolean', ['feature-state', 'hover'], false],
'#00f',
'#f00'
],
The final thing can be seen in the modified pen. There is also a MapBox tutorial covering this kind of thing in a slightly more complicated way, which I hadn't come across until now: https://docs.mapbox.com/help/tutorials/create-interactive-hover-effects-with-mapbox-gl-js/.

Highlight Feature on 1st click, then "unhighlight" on 2nd click

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.

Re-add an icon (symbol) after changing the map style

Does somebody know how you can re-add an icon (symbol) after changing the map style?
The situation is as follows:
I created a map with two views: a street view and satellite view. On the street view I also added an icon (symbol) that show the place where someone lives. When I switch between the two views the icon disappears and doesn't come back again. How can I reload the icon again?
Basically I want to combine the following two examples:
Change a map's style
https://docs.mapbox.com/mapbox-gl-js/example/setstyle/
and
Display a popup on click
https://docs.mapbox.com/mapbox-gl-js/example/popup-on-click/
How can it be done?
Thank you very much for your answer!!
I looked at your code and compared it with my code and the only thing that I had to change was map.on('load', ...) to map.on('style.load', ... ) and that did the trick!
Here is the complete code:
HTML
<div id='openstreetmap' style='height: 420px'>
<div id="mapbox-menu">
<input id="streets-v11" type="radio" name="rtoggle" value="streets" checked="checked" />
<label for="streets">Kaart</label>
<input id="satellite-v9" type="radio" name="rtoggle" value="satellite" />
<label for="satellite">Satelliet</label>
</div>
</div>
JavaScript
// Set an access token.
mapboxgl.accessToken = '';
// Create a map object.
var map = new mapboxgl.Map({
container: 'openstreetmap',
style: 'mapbox://styles/mapbox/streets-v11',
center: [5.880299, 51.834706],
zoom: 15
});
// Set the controls.
map.addControl(new mapboxgl.NavigationControl({showCompass: false}), 'top-right');
// Create and add a list of places.
map.on('style.load', function() {
map.addSource('places', {
'type': 'geojson',
'data': {
'type': 'FeatureCollection',
'features': [
{
'type': 'Feature',
'properties': {
'description':
'<strong>Header</strong><p>text</p>',
'icon': 'music'
},
'geometry': {
'type': 'Point',
'coordinates': [5.880299, 51.834706]
}
}
]
}
});
// Add a layer showing the places.
map.addLayer({
'id': 'places',
'type': 'symbol',
'source': 'places',
'layout': {
'icon-image': '{icon}-15',
'icon-size': 1.25,
'icon-allow-overlap': true
}
});
// Show a popup.
map.on('click', 'places', function(e) {
var coordinates = e.features[0].geometry.coordinates.slice();
var description = e.features[0].properties.description;
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
}
new mapboxgl.Popup()
.setLngLat(coordinates)
.setHTML(description)
.addTo(map);
});
// Change the cursor to a pointer.
map.on('mouseenter', 'places', function() {
map.getCanvas().style.cursor = 'pointer';
});
// Reset the cursor.
map.on('mouseleave', 'places', function() {
map.getCanvas().style.cursor = '';
});
});
// Switch between street view and satellite view.
var layerList = document.getElementById('mapbox-menu');
var inputs = layerList.getElementsByTagName('input');
function switchLayer(layer) {
var layerId = layer.target.id;
map.setStyle('mapbox://styles/mapbox/' + layerId);
}
for (var i = 0; i < inputs.length; i++) {
inputs[i].onclick = switchLayer;
}
</script>
The only (small) problem is that the close button doesn't work anymore. Have an idea about that?
Mapbox-GL-JS doesn't distinguish between layers that are part of the basemap, and layers that you added. So if you want to "retain" additional layers, you really just have to add them again.
Write a function to add your additional layers. Then, whenever you change basemap, just call that function again.
Thanks again!!
I moved the following three block out of the function map.on('style.load', ...):
map.on('click', 'places', ...)
map.on('mouseenter', 'places', ...)
map.on('mouseleave', 'places', ...)
And that was it!
Thank you for helping me!

How to add Turf.js squareGrid layer to mapbox gl?

I am new to this and trying to add a grid layer, using Mapbox GL. Would appreciate some help.
var bbox = [-95, 30 ,-85, 40];
var cellSide = 50;
var options = {units: 'miles'};
var squareGrid = turf.squareGrid(bbox, cellSide, options);
What you have so far gives you a GeoJSON object that you can add to your map. Assuming you have a created a map (follow the getting started example), you now need to add a GeoJSON source, then a layer that renders it.
Something like
map.on('load', function() {
map.addSource('grid', {
'type': "geojson",
'data': squareGrid
});
map.addLayer({
'id': 'grid',
'type': 'line',
'source': 'grid',
'paint': {
'line-color': 'red',
}
});
});

Check if a GeoJSON source is present in mapbox viewport

I have a map with several layers of GeoJSON each with their own unique layer name:
var map = new mapboxgl.Map({
container: 'map',
center: [-97.5651505, 37.89549,],
zoom: 4
});
var sources = {
'ord': 'chicago',
'pit': 'pittsburgh',
'atl': 'atlanta'
};
map.on('load', function () {
for (var s in sources) {
map.addSource(s, { type: 'geojson', data: `/geojson/${s}.json` });
map.addLayer({
'id': sources[s],
'type': 'fill',
'source': s,
'layout': {
'visibility': 'visible'
},
'paint': {
'fill-color': '#088',
'fill-opacity': 0.5
}
});
}
});
I would like to check if a user has zoomed in past zoom level 13 evaluate if any of these three layers is in the viewport. If it is I'll take action to add a button to the overlay. However, I'm having issues finding any documentation other than leaflet on how to check if a layer is inside the viewport. I've found some mention of markers that that doesn't seem to apply.
You can achieve this with queryRenderedFeatures which returns an array of features rendered within a given bounding box. However, if you omit the bounding box argument, queryRenderedFeatures will query within the entire viewport. You can also use the options.layers argument to limit your query to specific layers to avoid getting a bunch of features that are in the underlying style (for example, streets and lakes). You can do this query in a zoomend event listener to achieve your desired outcome. Putting it all together would look something like this:
map.on('zoomend', () => {
if (map.getZoom() > 13) {
const visibleFeatures = map.queryRenderedFeatures(null, {layers: ['ord', 'pit', 'atl']});
// if none of the layers are visible, visibleFeatures will be an empty array
if (visibleFeatures.length) {
// figure out which layers are showing and add your button
}
}
});