mapbox - why do all my features disappear when I change the map style - mapbox

I am using mapbox gl js to create some maps. To style the map, I use the following code;
// add a styled map to the contianer id map
const map = new mapboxgl.Map({
container: 'map', // container ID
style: 'mapbox://styles/bogdanvectuel/cjq1fxndm5qr02roa06jcfb61',
});
Above is a public map style from bogdanvectuel. I wanted to changed to my own style but when I change over to a mapbox offered style like the outdoors one, all of a sudden all my skiareas points disappear which is defined as follows;
/** Add ski areas but just the center point as source / dot **/
map.addSource('aa_winter_spoorts_points', {
type: 'vector',
// aa_winter_spoorts_points
url: 'mapbox://username.aa_winter_spoorts_points'
});
/** Add ski areas but just the center point as layer / dot **/
map.addLayer({
'id': 'aa_winter_spoorts_points',
'type': 'symbol', // background, fill, line, symbol, raster, circle, fill-extrusion, heatmap, hillshade, sky.
'source': 'aa_winter_spoorts_points',
'source-layer': 'skiareas',
'layout': {
'visibility': 'visible',
'icon-image': 'airfield-11',
'icon-allow-overlap': true
},
});
I cannot understand why that would happen. Any pointers appreciated.

When you use addSource() and addLayer(), you are manually adding the source and layer to the map's style. Changing to a different style after map load essentially starts over from scratch. Using setStyle() will clear the existing style, so anything you added after map load would need to be added again.
One approach may just be to move your addSource() and addLayer() calls to a new function like addCustomSourcesAndLayers(). You could then call that after the first map load, and again after setStyle()
You can see this in action in geojson.io. We allow you to switch to a different style and then re-draw the user-defined layers after the new style loads.

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/.

Nothing showing when plotting vector tile layer from Arcgis server on MapBox GL JS map

I'm trying to plot a supplied vector tile layer onto a map using MapBox GL JS. I've followed the documentation here but nothing apart from the basic map is being output and there are no console errors. In the browser's Network tab I can see lots of .pbf requests being returned with data so it would seem that the endpoint is passing data back, but I don't know how to determine what the problem is in plotting that data onto the map.
The code is as follows:
mapboxgl.accessToken = '[my access token]';
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v10',
zoom: 6,
center: [-0.118092, 51.509865]
});
map.once("load", () => {
map.addSource("ncn", {
type: "vector",
tiles: [
"https://tiles.arcgis.com/tiles/1ZHcUS1lwPTg4ms0/arcgis/rest/services/NCN_Vector_Tile_Package/VectorTileServer/tile/{z}/{y}/{x}.pbf"
]
});
map.addLayer({
id: "ncn-lines",
type: "line",
source: "ncn",
"source-layer": "NCN_Vector_Tile_Package",
"layout": {
"visibility": "visible"
},
'paint': {
'line-color': 'rgb(255, 0, 0)'
}
});
});
I am fairly sure that the type should be line (rather than fill) as the data is supposed to contain route lines rather than vector shapes.
I don't have access to the Arcgis server so can't see how anything is configured at that side. Can anyone suggest what might be wrong here and/or how to debug?
It looks like the value for source-layer is not correct - it should be NCN_2020. Here's a demo showing it working: https://jsbin.com/xunuhibuki/1/edit?html,output
How do you get that value? I'm not quite sure the best way, but the way I found: add ?f=html to your vector tile layer like this: https://tiles.arcgis.com/tiles/1ZHcUS1lwPTg4ms0/arcgis/rest/services/NCN_Vector_Tile_Package/VectorTileServer/?f=html then click "Styles" link at the bottom which gives you an example of how to construct your map.addLayer() commands in your mapboxgl code.

How I can I get geojson boundary array from existing layer using mapbox gl?

I am using one of the custom tileset of tilling service in mapbox. I loaded that custom tile layer in map using below code.
map.addSource('california', {
type: 'vector',
url: 'mapbox://xyz.california'
});
map.addLayer({
'id': 'terrain-data',
'type': 'fill',
'source': 'california',
'source-layer': 'california',
'layout': {},
'paint': {
'fill-color': 'black',
'fill-opacity': 0.5
}
});
Above code is filling out the inner area with black color. But I want to fill out the outer area of that polygon. Only one way I know to do that is getting difference of whole map with that polygon by using turf.js. After that I will be able to fill the outside area.
Now the question is how can I get the geojson ploygon array of above added layer? So I can calculate the difference.
You can't easily get the complete geometry of a polygon from a vector tile set, because it has already been cut up into tiles. You would have to either find a way to merge them together, or get your geometry into the front end as a complete geojson first.

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

Mapbox GL JS: Display map labels above layer?

I'm using Mapbox GL JS, with a Mapbox Streets base layer. I have added a polygon layer with white borders and a transparent fill, but I'm finding it hard to read the basemap labels underneath the polygon layer. See how the labels are covered by the white borders:
Is there any way I can make sure the labels are on top of the polygon layer, or at least not obscured by it?
My code looks like this:
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v9',
center: [-2.839, 54.579],
zoom: 4
});
map.on('load', function () {
map.addSource('constituencies', {
'type': 'vector',
'url': 'mapbox://pcsu.xxx'
});
var constituencyLayer = map.addLayer({
'id': 'constituencies',
'source': 'constituencies',
'source-layer': 'constituencies',
'type': 'fill',
'paint': {
'fill-color': 'rgba(162,181,205,0.6)',
'fill-outline-color': 'white'
},
'layout': {
'visibility': 'visible'
}
});
Have a look at this Mapbox example and use the second argument of map.addLayer(layer, [before]). If this layer before exists, the new layer will be placed before/below it. I think the argument naming is a bit confusing here.
The name of the lowest label layer depends on the style. Most of the time you're looking for housenum-label. There's also a discussion in the Mapbox github issues how to ease this process, e.g by introducing placeholder layers.