Changing the style of each feature in a Leaflet GeoJSON layer - javascript

I have been studying the Leaflet Chloropleth example.
In my Leaflet application, I have a jQuery dropdown that, when selected, fires a function that takes the name of a state as an argument. I want to use that state name to update the Chloropleth map.
What is the pattern for changing the style of a Leaflet GeoJSON layer? Should I recreate the layer I created with L.geoJson() a second time? It seems as though the layers are drawing on top of each other with that approach.
I can provide a Fiddle if needed.

To expand on the answer by #tmcw, the strategy is to pass a function to geojsonLayer.eachLayer() that changes the style for each feature instance within geojsonLayer.
Here is some code that demonstrates this strategy that I lifted and simplified from the code posted on the Mapbox example page referenced by #tmcw. My code example changes the style of each of the feature instances within geojsonLayer based on the value of a specified property on each feature instance.
var geojsonLayer = L.geoJson(...); // a GeoJSON layer declared in the outer scope
function restyleLayer(propertyName) {
geojsonLayer.eachLayer(function(featureInstanceLayer) {
propertyValue = featureInstanceLayer.feature.properties[propertyName];
// Your function that determines a fill color for a particular
// property name and value.
var myFillColor = getColor(propertyName, propertyValue);
featureInstanceLayer.setStyle({
fillColor: myFillColor,
fillOpacity: 0.8,
weight: 0.5
});
});
}
restyleLayer('myProperty');

Here's an example of changing a choropleth to classify on different properties - the trick is to call setStyle again with different values.

The official documentation of Leaflet explains that:
https://leafletjs.com/examples/geojson/
The style option can be used to style features two different ways. First, we can pass a
simple object that styles all paths (polylines and polygons) the same way
...
Alternatively, we can pass a function that styles individual features based on their
properties. In the example below we check the "party" property and style our polygons
accordingly ...
L.geoJSON(states, {
style: function(feature) {
switch (feature.properties.party) {
case 'Republican': return {color: "#ff0000"};
case 'Democrat': return {color: "#0000ff"};
}
}
}).addTo(map);
Unfortunately, the names of the styles are not equal to css style names.
For example, instead of 'stroke' use 'color' and instead of 'stroke-width' use 'weight':
https://leafletjs.com/reference-1.6.0.html#path-option

Related

Which is base layer out of two tile layers

I can't find docs about it, consider:
// base map
L.tileLayer('http://tiles.mapc.org/basemap/{z}/{x}/{y}.png',
{
attribution: 'Tiles by MAPC,
Data by MassGIS',
maxZoom: 17,
minZoom: 9
}).addTo(map);
// bike lanes
L.tileLayer('http://tiles.mapc.org/trailmap-onroad/{z}/{x}/{y}.png',
{
maxZoom: 17,
minZoom: 9
}).addTo(map);
So we added two tile layers to the map right? But which of the above two is set as a base layer now?
Can a map have multiple base layers at once? (because I read somewhere base layers normally should be mutually exclusive).
Extra: Can I change a base layer without using the layer control mechanism? (e.g. only programatically).
So we added two tile layers to the map right? But which of the above
two is set as a base layer now? Can a map have multiple base layers at
once? (because I read somewhere base layers normally should be
mutually exclusive).
Yes they are mutually exclusive in display so that means you can display only one layer at a time. It's not documented but the last layer added will become the base layer which is being displayed.
Extra: Can I change a base layer without using the layer control
mechanism? (e.g. only programatically).
Yes you can using addLayer/removeLayer method.
map.removeLayer(base map layer name here);
map.addLayer(bike lane layer name here);
So we added two tile layers to the map right?
Yes.
But which of the above two is set as a base layer now?
Leaflet has no concept of base layer, so the answer is "none".
Can a map have multiple base layers at once? (because I read somewhere base layers normally should be mutually exclusive).
Depends on what you consider to be a "base layer".
I can, for example, have a map with several sets of opaque tiles, each of them being able to function as a base layer of a map, and make one of them semitransparent.
Usually, map frameworks assume that a tilelayer (or one tilelayer in a set of tilelayers) can be the basic information for your map, and in those circumstances, such set of tilelayers should be exclusive. Hence the wording of L.Control.Layers.
Leaflet does not constrain you to have a fully-opaque exclusive L.TileLayer. You can even have a Leaflet map without a single L.TileLayer. And of course, you can control visibility of your layers with map.removeLayer(lyr), map.addLayer(lyt), layer.addTo(map) and layer.remove(). Ultimately, the logic to control layer exclusivity (and opacity) is up to you.

Custom Overlays in OpenLayers 3

Currently I am working on Openlayers 3.
I used custom overlays in Google maps Javascript api v3 to add customized markers as html div on the map. And these markers are grouped and plotted in different custom overlays.
Now I am trying to implement the same in OpenLayers 3, but I couldn't find any solution as the overlays in OpenLayers 3 takes one marker in one overlay.
Can I group overlays in OpenLayers 3 in order to group the markers? Or Is there any other options available?
You have multiple possible options.
A) If you have only one dataset, then you could use a StyleFunction. See this ol3 vector example, more specifically this section of code:
var vectorLayer = new ol.layer.Vector({
source: new ol.source.Vector({
url: 'https://openlayers.org/en/v3.20.1/examples/data/geojson/countries.geojson',
format: new ol.format.GeoJSON()
}),
style: function(feature, resolution) {
style.getText().setText(resolution < 5000 ? feature.get('name') : '');
return style;
}
});
See the style property? It can be a ol.style.Style or a style function, like demonstrated above. The function receives the feature and current resolution of the map view as argument and is called every time the feature gets rendered (or re-rendered). Returning a ol.style.Style or array of style objects will render the feature using the/these styles.
The feature can have unique properties, i.e. feature.getProperties(). Using as many properties within the feature(s), you can return a unique array of unique style objects.
Here's a more complex ol3 example featuring style functions that you can look and have an example of dynamic styling depending on the resolution. That could give you a better idea of what you could do with the feature properties.
B) If you have multiple datasets, then you can create one vector layer per dataset and define a unique style object on the layer, which would render the features all the same.

OpenLayers 3 hover to highlight only one specified object per time

Original issue:
OpenLayers 3 (tested also newest 3.14.1) should have some sense in the way it selects features that overlap each other. When multi=false it selects only one feature, but picks it quite randomly. When you hover over certain features from different directions, different feature is selected.
I have tried to tangle this by setting z-index to features to tell Openlayers the desired order. Indexing is set to order point > line > polygon but still I am not getting point selected first on hover.
Z-Index is applied to layer with:
rendererOptions: { zIndexing: true }
on layer and
zIndex: x
in style for different feature types.
What happened first:
Z-indexing did not work so I was not able to tangle the situation for select. I kept multi=true and filtered results manually from list.
I had a function getSelectedRemovableFeature(event.selected) that took event.selected and returned only one feature out of it.
So, my selection had a code like follows:
var selectionInteraction = new ol.interaction.Select({
layers: [layersModule.getTargetLayer()],
condition: ol.events.condition.click,
multi: true
});
and more..
selectionInteraction.on('select', function(event) {
var selectedFeature = null;
selectedFeature = getSelectedRemovableFeature(event.selected);
if(selectedFeature){
.. some logic..
layersModule.getTargetLayer().getSource().removeFeature(selectedFeature);
}
});
Then I got stucked:
I had
var hoverInteraction = new ol.interaction.Select({
layers: [layersModule.getTargetLayer()],
condition: ol.events.condition.pointerMove,
multi: true
});
..and..
hoverInteraction.on('select', function(event) {
var selectedFeature = null;
selectedFeature = getSelectedRemovableFeature(event.selected);
// here I didn't know what to do..
}
});
I had there a logic like this:
selectControl = new OpenLayers.Control.SelectFeature(vectorLayer);
...
map.addControls([selectControl]);
selectControl.activate();
// unselect any specific feature...
selectControl.unselect(vectorLayer.features[0]);
(https://gis.stackexchange.com/questions/41017/how-can-i-unselect-a-feature-in-openlayers)
.. but when it was there, no selections were removed, it worked like same as without this part.
Back to the origins:
Now I realized, that tangling with the hoverSelection data is of no use, because new multi=true type event is thrown there before I could calculate how to select one only and show that. I believe that is the case, because nothing changes even I manually filter the results and remove features.
In short finally:
How to determine explicit order where the hover / select selection goes?
Hit detection of features on a map works in opposite rendering order. By default, points are rendered last, so they get hit detected (and selected) first. When no zIndex is set on any style, the rendering order is polygons, lines, points, texts.
When you set zIndex on an ol.style.Style, you override this fixed rendering order, allowing you to e.g. render polygons on top of points.
Within these four groups (polygons, lines, points, texts), you can control the rendering order by setting a renderOrder function on your ol.layer.Vector. That is an array sort function called with two features:
new ol.layer.Vector({
renderOrder: function(a, b) {
if (a.get('importance') < b.get('importance')) {
return -1;
} else if (b.get('importance') > a.get('importance')) {
return 1;
}
return 0;
}
});
The above assumes that your features have a numeric 'importance' property. Now when you hit detect (select) features of the same geometry type (e.g. points), those with a higher 'importance' value will be selected first, because they are rendered last.
Also note that there are no rendererOptions like you state in your question.
I noticed that from some reason the draw styles included heavily z-indexing and what made this problem to occur was the following:
Reason:
From some historical reason selected style was forced on top by zIndex: 100. Normal selection had zIndex: 1.
That caused a problem: when you had point and line on top of each other and you select line, it's zIndex rose up to 100. That broke the natural highlight order, because 100 is far more than 1. Thus natural order, which #ahocevar pointed out, gets broken and highlighting becomes dependent of direction where you come near to the feature on map.
Solution:
I put selected and default style both to zIndex: 1 and everything seemed to work ok event with that.
I will need to check still the reason of selected style on zIndex so high, but if there is no reason to continue its usage, this thing solves the problem.
Side note:
Answer is a community wiki, because half of solution is from comment of another user.

Setting the selected layer on a Leaflet/Mapbox L.Control.Layers

I have a map with a few base layers. Users can choose the base layer and then save the map. After saving the map, the system loads it with the new base layer. That base layer should be selected in the L.Control.Layers control. However, there's no way in the API to select a base layer.
Anyone knows a way around this, or a different plug in?
UPDATE: Here is the code I use. MapConfigs has the ids in MapBox, and can create the map that L.control.layers requires.
var map = L.mapbox.map( components.mapDivId , MapConfigs.idFor(baseLayerName) );
map.addControl( L.control.layers(
MapConfigs.toBaseLayersControlMap(map)
).setPosition("topright"));
Thanks!
Why not store references to all the base layers available in a hash, then use addLayer or removeLayer (http://leafletjs.com/reference.html#map-addlayer) as needed to programmatically select base layers?. Something like below.
var tileLayers = {light: L.tileLayer('lightUrl'),dark: L.tileLayer('darkUrl')}

OpenLayers overriding point radius for all rendering intents

In OpenLayers, I'm trying to override the point radius for all of the point style rendering intents (default, select and temporary). Currently I do this:
var styleMap = new OpenLayers.StyleMap({
"default": OpenLayers.Util.applyDefaults({pointRadius: radius},OpenLayers.Feature.Vector.style['default']),
"select": OpenLayers.Util.applyDefaults({pointRadius: radius},OpenLayers.Feature.Vector.style['select']),
"temporary": OpenLayers.Util.applyDefaults({pointRadius: radius},OpenLayers.Feature.Vector.style['temporary'])
});
It seems like there should be a way to just say to override point radius in all of them, but I can't figure out how to do that. I would have hoped that by default the point radius is inherited from default into select and temporary, but if I override it in just default (without overriding anything in select and temporary), they use the original default point size.
Try calling vectorLayer.redraw(); afterward.

Categories

Resources