Leaflet Layer Control: Ordering - leaflet

We use the standard "layer control" from Leaflet. Instantiation is as follows:
this.controls[id] = L.control.layers({}, {}, {
collapsed: false
});
Layers are added dynamically and in random order (depending on when xhr requests complete). However it seems that leaflet implicity uses the _leaflet_id of the respective layer for ordering in the layer control and thus our layers appear in random order.
Is there any way to tell leaflet in which order it should display the layers in the control? E.g. by passing some additional parameter when calling the .addOverlay or .addBaseLayer methods?
Leaflet version is 1.0-dev

I found some hack-fix for the problem. However this is not a clean solution so feel free to post better answers.
Leaflet uses the _leaflet_id of the layer object for sorting. Thus the layer created first will always be on top. In order to sort the layers you will have to wrap the actual layer in a layer/feature group.
|- dummy layer group
|- some dummy geojson layer
|- your actual layer (will be added later)
Programatically:
var oFeatureGroup = L.featureGroup([
L.geoJson({
"type": "FeatureCollection",
"features": []
})
]);
Then we do some async loading and once we are ready we add the layer to the - more or less empty - layer group:
oFeatureGroup.addLayer(oMyMagicAsyncLoadedLayer)
We can then add the layer to the layer control and it will be sorted as "expected" where the order depends on the order of creation of the oFeatureGroup layers instead of the oMyMagicAsyncLoadedLayer.

Related

How to get the feature geojson from the result of queryRenderedFeatures on a layer whose source is a vector tile in mapbox gl js?

I have a layer called "Searched LayerX" having a vector tile source. I am having a simple requirement of highlighting a feature inside this "Searched LayerX" at runtime.
I was thinking of using the result of queryRenderedFeatures on "Searched LayerX" with the filter of unique ID of this particular feature and using this feature's geojson as a separate source to the new layer which I will be adding as "Selected LayerX".
var features = mapBox.queryRenderedFeatures({layers:['Searched LayerX'], filter : ["==",'gid','7818_2_CA']})
var selectedFeature = features[0];
Resultant feature set does not provide any geojson which I can use to create a new geojson source.
So my question is, how do I use the result as a different source to my "Selected LayerX"?
You can use the method described in the first link below - but understand that the returned feature is not the same as the source GeoJSON feature - it is the vector tile representation of that feature at that zoom level, which means it might be highly simplified.
https://gis.stackexchange.com/questions/186533/highlight-feature-with-click-in-mapbox-gl
Another method is to add another layer with the same source, and use the filter function for the highlight as shown in the two links below -
http://www.mapbox.com.s3-website-us-east-1.amazonaws.com/mapbox-gl-js/example/query-similar-features/
highlighting polyline features in mapbox-gl.js
Try this post, I have added the code which will let you have the features using querySourceFeatures() https://stackoverflow.com/a/66308173/9185662

Using a custom Mapbox Style's internal GeoJSON data as a source

I'm currently trying to render a map, with data coming from shapefiles (transformed into GeoJSON), upon uploading to Mapbox's servers.
My demo output can be viewed at:
https://ciatph.github.io/amia-lowres-hover.html
I would like to inquire if I have rendered the map efficiently, as used in the demo page. So far, I have:
Uploaded a GeoJSON Dataset
Exported the Dataset into a Tileset
Added the Tileset into a Style
Used the Style to load an initial base map
Used the uploaded Dataset as a data source for another Layer (on top of the initial map). This Layer listens and responds to mouse hover and click events
Used the uploaded Dataset as a data source for another Layer with Filters to color the hovered region differently for the created Layer in step #5.
Screenshot of relevant Mapbox script
My questions for this approach are:
Is there a way to use the Style's internal (GeoJSON) Dataset for the map.addSource() part, such that it doesn't need to be re-loaded or redefined again for creating interactive Layers? I'm concerned of network activity if its being re-downloaded again at this point. I'm also interested to know if this is possible, because we have large GeoJSON data that are almost 100MB in size. This gets automatically converted into Tileset upon moving from Amazon S3 temporary servers to mapbox, and there are no Dataset created to play with the map.addSource() part
I can use Mapbox's default styles for basemap, (i.e., mapbox://styles/mapbox/streets-v9), and omit step #4. If I go for this approach, will the Dataset loading (for step #5) be efficient and fast enough for large data, as opposed if its used or loaded via Style?
I hope you can help me with my queries and enlighten me of more salable and efficient approaches. Thank you.
Let's start by clarifying your current situation:
Your style contains a vector tileset with id ciatph.cj64in9zo1ksx32mr7ke3g7vb-93srz, referred to in your style as amia-lowres-tileset within the composite vector tile source.
It is also accessible as a dataset through the ID ciatph/cj64in9zo1ksx32mr7ke3g7vb, since you uploaded it as a dataset.
Your script is loading the dataset at runtime.
I don't see any reason you need to refer to the dataset instead of the tileset. So, remove the code that adds the dataset, and update the two styles to refer to the tileset (source: "composite") instead.
// Only used for coloring hover effect. Data informatiion be retrieved from styles alone
/*
map.addSource("dataSource", {
"type": "geojson",
'data': 'https://api.mapbox.com/datasets/v1/ciatph/cj64in9zo1ksx32mr7ke3g7vb/features?access_token=pk.eyJ1IjoiY2lhdHBoIiwiYSI6ImNqNXcyeTNhcTA5MzEycHFpdG90enFxMG8ifQ.gwZ6uo6pvx4-RZ1lHODcBQ'
});
*/
// add layer to color the raw source data
map.addLayer({
'id': 'municipalities',
"type": "fill",
"source": "composite",
"source-layer": "amia-lowres-tileset",
"layout": {},
"paint": {
"fill-color": "#627BC1",
"fill-opacity": 0.5
}
});
// add a conditional layer to play over the source data on hover event
map.addLayer({
"id": "state-fills-hover",
"type": "fill",
"source": "composite",
"source-layer": "amia-lowres-tileset",
"layout": {},
"paint": {
"fill-color": "#ff4",
"fill-opacity": 1
},
"filter": ["==", "MUNI_CITY", ""]
});
https://codepen.io/stevebennett/pen/OjvMWO

react-leaflet add layers dynamically

I'm starting to use react-leaflet and I came across a question: In my application the user fills out a form that then requests a rest service that returns a GeoJSON, which is then added as a new layer on my map. My question: How do I implement adding layers dynamically in react-leaflet?
Thank you.
The best approach is to create GeoJSON Layer wrapper for react-leaflet. There is a similar implementation of GeoJSON layer with clustering available in react-leaflet's plugins section.
Then you should add this layer to your map component and change it's data when you need to. So there is no need to add the layer dynamically but dynamically change data for it.
Check the leaflet's GeoJSON example to get the idea http://leafletjs.com/examples/geojson/.
The approach will work if you have one layer with changing data. But if you have different data sets you will need to add a GeoJSON layer for each of them.
<Map ...>
{this.props.datasets.map((ds, ix) => {
return (<GeoJSONOverlay data={ds} key={ix} />);
})}
</Map>
I have similar problem. I would like to clear and create marker layers dynamically. I think you can do it by getting reference to the actual map view react refs e.g.
<Map ref={Map => this.map = Map}>
later on you can then use this.map with normal Leaflet functions. Other option could be that you create layers in JSX same way I create markers:
{this.props.markers.map((i,index) => {
return (
<Marker key={i} position={i}>
<Popup>
<span>Great marker!</span>
</Popup>
</Marker>);
})}

Markercluster in Mapbox

I am following markercluster examples from Mapbox library, but can't solve my problem. If you guys take a look at my working example here, you will notice this line of code:
L.mapbox.featureLayer(markerLayer).on('ready', function(e) {
What I initally thought was I could put markers inside of markercluster featureLayer, but I guess it was a wrong approach. Any solutions? Thanks.
Example following here
The mapbox example you refer to makes an AJAX call to retrieve the GeoJSON data, hence it needs to attach an on "ready" listener.
In your case your GeoJSON data is defined in your scripts, so the "ready" event will not be triggered (besides, you should use L.mapbox.featureLayer with your GeoJSON object directly, not a Feature Layer).
You can simply use the eachLayer method to iterate through all created markers within the Feature Layer, and add them into your Marker Cluster Group.
var clusterGroup = new L.MarkerClusterGroup();
var markerLayer = L.mapbox.featureLayer(markers).eachLayer(function(layer) {
clusterGroup.addLayer(layer);
});
map.addLayer(clusterGroup);
Updated Plunker: http://plnkr.co/edit/fN6xYcn1Lg532eLe39IS?p=preview

Leaflet: How to specifiy order of overlays in the layers control?

Is it possible to order overlays within the layers control?
Here's my issue. When I do this with my overlays:
var overlays = {
"Apples": apples,
"Bananas": bananas,
"Peaches": peaches
};
L.control.layers(baseLayers, overlays).addTo(map);
Leaflet adds the overlays to the layer control on the map in a random fashion (due to iteration):
- Bananas
- Apples
- Peaches
Is it possible to specificy the order I want to show the overlays? *(In my case, I'd like to have it alphabetical. Also, I am using the Mapbox API)
Thank you.
I am not sure if Mapbox API has changed the default Layers Control code / behaviour, but the latter does not provide a 100% reliable way of displaying overlays in a specific order. See that post for details.
The explanation is that the Layers Control iterates overlays by their "stamp" (a unique identifying integer that is created using L.stamp(myLayer)). Depending on your type of layer, this stamp is assigned automatically but at different moments.
You can force its creation right after having instantiated your layer. For example in your case:
var apples = L.layerGroup();
L.stamp(apples);
var bananas = L.layerGroup();
L.stamp(bananas);
var peaches = L.layerGroup();
L.stamp(peaches);
Unfortunately the order of iteration is not guaranteed as per JS spec, hence the impossibility to have a 100% reliability in this behaviour.
That being said, in all browsers I have tested, the order is in accordance with the natural number order, so as of today, this trick works.
If you want more confidence, you should look for Control plugins that explicitly provide you with the functionality to specify manually the order of listing.