How to change geojson style by clicking a button - javascript

Hello based on this leaflet tutorial http://leafletjs.com/examples/choropleth.html I am trying to create 20 buttons so that I can change line :
fillColor:getColor(feature.properties.density) to sth different (for example by pressing btn #1 fillColor:getColor(feature.properties.btn1),
btn #2 fillColor:getColor(feature.properties.btn1), etc
function style(feature) {
return {
fillColor: getColor(feature.properties.density),
weight: 2,
opacity: 1,
color: 'white',
dashArray: '3',
fillOpacity: 0.7
};
}
L.geoJson(statesData, {style: style}).addTo(map);
All I want is when a button is pressed, a different geojson property to be displayed.

Something along these lines
First, turn your layer into a variable
var mylayer = L.geoJson(statesData, {style: style});
map.addLayer(mylayer);
Create a new function for getting the new style based on your buttons id
function newStyle(id) {
return {
fillColor: getColor(id),
weight: 2,
opacity: 1,
color: 'white',
dashArray: '3',
fillOpacity: 0.7
};
}
Then listen for the click and get the btn id and set the new style
$('body').on('click', 'btn', function (btn) {
var id = $(btn).attr('id');
var new_style = newStyle(id);
mylayer.setStyle(new_style);
});
UPDATE:
Updated the getcolor to only be getColor(id). You should make your buttons correspond to the colors in the getColor function on the example. So id="11" will return #FED976 from the example, id="21" will return #FEB24C and so on.
Alternatively you could just set your button ids to the color (id="#FED976") and then change the fillColor: getColor(id) to fillColor: id
UPDATE2:
function style1(feature) {
return {
fillColor: getColor(feature.properties.btn1)
};
}
function style2(feature) {
return {
fillColor: getColor(feature.properties.btn2)
};
}
var mylayer = L.geoJson(statesData, {style: style});
map.addLayer(mylayer);
$('body').on('click', 'btn', function (btn) {
var id = $(btn).attr('id');
switch (id) {
case "btn1":
map.removeLayer(mylayer);
mylayer = L.geoJson(statesData, {style: style1});
map.addLayer(mylayer);
break;
case "btn2":
map.removeLayer(mylayer);
mylayer = L.geoJson(statesData, {style: style2});
map.addLayer(mylayer);
break;
}
});

Related

Make data layer disappear and different one appear on click in Leaflet

I'm mapping COVID vaccinations per state and per county in the MXN. I have state data as well as county data displaying properly.
I'm trying to make it so that when I click on a state it zooms in to the selected state, turns off the state data layer and turns on the county data layer. So basically it goes from showing state data to county data.
How do I do this? Would I need to add something to the zoomTofeature function?
var map = L.map("map", {
center: [24.0376106, -102.9590598],
zoom: 4.5,
});
// add basemap
var Stamen_TonerLite = L.tileLayer(
"https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png",
{
attribution:
'© OpenStreetMap'
maxZoom: 18,
minZoom: 3,
}
).addTo(map);
//zoom to a state, turn off state data, turn on county data
function zoomToFeature(e) {
map.fitBounds(e.target.getBounds());
}
//zoom to county
function zoomToFeature1(e) {
map.fitBounds(e.target.getBounds());
}
function highlightFeature(e) {
var layer = e.target;
layer.setStyle({
weight: 4,
opacity: 1,
color: "#dbff4d",
});
layer.bringToFront();
}
function highlightFeature1(e) {
var layer = e.target;
layer.setStyle({
weight: 4,
opacity: 1,
color: "#dbff4d",
});
layer.bringToFront();
}
//reset the hightlighted states on mouseout
function resetHighlight(e) {
geojsonStates.resetStyle(e.target);
}
//reset the hightlighted counties on mouseout
function resetHighlight1(e) {
geojsonCounties.resetStyle(e.target);
}
//add these events to the layer object
function onEachFeature(feature, layer) {
layer.on({
mouseover: highlightFeature,
click: zoomToFeature,
mouseout: resetHighlight,
});
}
//add these events to the layer object
function onEachFeature1(feature, layer) {
layer.on({
mouseover: highlightFeature1,
click: zoomToFeature1,
mouseout: resetHighlight1,
});
}
function getColor(d) {
return d > 70
? "#2c7bb6"
: d >= 60
? "#abd9e9"
: d >= 50
? "#fdae61"
: d < 50
? "#d7191c"
: "#5f5f5f";
}
function getColor1(d) {
return d >= 75
? "#1a9641"
: d >= 50
? "#a6d96a"
: d >= 25
? "#fdae61"
: d > 0
? "#d7191c"
: "#5f5f5f";
}
function states(feature) {
return {
weight: 3,
opacity: 1,
color: "whitesmoke",
fillOpacity: 0.8,
fillColor: getColor(feature.properties.vaxData_states_Series_Complete),
};
}
function counties(feature) {
return {
weight: 0.5,
opacity: 0.5,
color: "whitesmoke",
fillOpacity: 0.75,
fillColor: getColor1(feature.properties.vaxData_Series_Complete),
};
}
// add state borders
var geojsonStates = L.geoJson.ajax("data/us_states.geojson", {
style: states,
onEachFeature: onEachFeature,
});
geojsonStates.addTo(map);
//add county geojson
var geojsonCounties = L.geoJson.ajax("data/counties.geojson", {
style: counties,
onEachFeature: onEachFeature1,
});
geojsonCounties.addTo(map);
I hope you can help me and I have had this issue for more than a week and I don't know what the problem could be.

How can you add style and onEachFeature functions to multi L.GeoJSON.AJAX layers with Bootstrap multiselect option?

I am trying to create a map with multiple selection option of multiple L.GeoJSON.AJAX layers. I have difficulty incorporating specific style and onEachFeature functions to each layer. Can anyone please help me and see where I went wrong in my coding. So basically, when a user selects one or more geojson layers from a Bootstrap Navbar multiselect bar, s/he needs to be able to see and interact with the selected layers' style and other functions. Here is the section of my code with which I need help:
<body>
<li class="nav-item">
<select multiple="multiple" id="donnees">
<option value="ecoterritoires.geojson">Écoterritoires</option>
<option value="grands-parcs.geojson">Grands parcs</option>
<option value="friches.geojson">Friches</option>
<option value="densite-de-construction.geojson">Densité urbain</option>
</select>
</li>
</body>
<script>
$(document).ready(function () {
$("#donnees").multiselect({
buttonClass: "custom-select",
nonSelectedText: "Sélectionnez une ou plusieurs couches",
allSelectedText: "Toutes les couches",
onChange: function () {
// Retirer la couche précédemment chargée
myMap.eachLayer(function (layer) {
if (layer instanceof L.GeoJSON) myMap.removeLayer(layer);
});
var couches_selectionnees = this.$select.val();
for (var i = 0; i < couches_selectionnees.length; i++) {
new L.GeoJSON.AJAX("data/" + couches_selectionnees[i], {
style: function (feature) {
if (couches_selectionnees[i] === "ecoterritoires.geojson") {
return {
color: "#006600",
fillColor: "#00ff00",
fillOpacity: 0,
dashArray: 1,
weight: 0.8,
};
} else if (couches_selectionnees[i] === "friches.geojson") {
return {
color: "#006600",
fillColor: "#00cc00",
fillOpacity: 1,
dashArray: 3,
weight: 0.5,
};
} else if (couches_selectionnees[i] === "grands-parcs.geojson") {
return {
color: "#006600",
fillColor: "#b3b300",
fillOpacity: 0.6,
dashArray: 2,
weight: 0.3,
};
} else if (couches_selectionnees[i] === "densite-de-construction.geojson") {
return {
color: "grey",
fillColor: classifier(feature.properties.indice),
fillOpacity: 0.8,
dashArray: 1,
weight: 0.5,
};
}
},
}).addTo(myMap);
}
},
});
});
</script>
Similarly to Using an iterator within L.marker, you should be good by simply replacing for (var i by for (let i
With var i, you have a function scope variable, so it is actually the same one for all your loops. In particular, at the end of the loop, i is equal to couches_selectionnees.length. And once your GeoJSON data arrives and executes your style function callback, it does not match any of your conditions, hence does not return anything, hence you only get the default style.
With let i, you now have a block scope variable, i.e. a different one for each of your loops block.
BTW you could also use for (const i in JavaScript, but TypeScript would complain that the later increment i++ is illegal.

Update map filtering with html form value

Alright, I don't really know where the issue in my code lies, but maybe it will be obvious to some of you.
I have an html form that includes names of all US states that correspond to numerical values in a geoJSON of all US counties. I use jquery function to get the user selected value, and JS to filter by state and only add those counties to my choropleth map.
// get value from form
var value;
$('select').on('click', function() {
value = this.value;
return value;
})
//filter by state value
var filtered = L.geoJson(counties, {
filter: function (features, layer) {
return features.properties.STATE == value
}
});
I then assign those counties a color based on the Count field (which is default gray) from the geoJSON and have listeners that highlight the county and update an info div on mouseover.
// create gradient
function getColor(d) {
return d > 1000 ? '#800026' :
d > 500 ? '#BD0026' :
d > 200 ? '#E31A1C' :
d > 100 ? '#FC4E2A' :
d > 50 ? '#FD8D3C' :
d > 20 ? '#FEB24C' :
d > 10 ? '#FED976' :
'#FFEDA0';
}
// determine fill
function style(feature) {
if (feature.properties.Count >= 1 && feature.properties.STATE == value) {
return {
fillColor: getColor(feature.properties.Count),
weight: 2,
opacity: 1,
color: 'white',
dashArray: '3',
fillOpacity: 0.7
};
} else if (feature.properties.value != value && feature.properties.Count >= 1) {
return {
fillColor: '#DDD',
weight: 2,
opacity: 1,
color: 'white',
dashArray: '3',
fillOpacity: 0.7
}
} else {
return {
fillColor: '#DDD',
weight: 2,
opacity: 1,
color: 'white',
dashArray: '3',
fillOpacity: 0.7
}
}
}
function highlightFeature(e) {
var layer = e.target;
layer.setStyle({
weight: 5,
color: '#666',
dashArray: '',
fillOpacity: 0.7
});
if (!L.Browser.ie && !L.Browser.opera && !L.Browser.edge) {
layer.bringToFront();
}
info.update(layer.feature.properties);
}
function resetHighlight(e) {
geojson.resetStyle(e.target);
info.update();
}
var geojson;
// listeners
geojson = filtered;
function zoomToFeature(e) {
map.fitBounds(e.target.getBounds());
}
function onEachFeature(feature, layer) {
layer.on({
mouseover: highlightFeature,
mouseout: resetHighlight,
click: zoomToFeature
});
}
geojson = L.geoJson(counties, {
style: style,
onEachFeature: onEachFeature
}).addTo(mymap);
var info = L.control();
info.onAdd = function (mymap) {
this._div = L.DomUtil.create('div', 'info'); // create info div
this.update();
return this._div;
};
// update info on hover
info.update = function (props) {
this._div.innerHTML = '<h4>2020 Sales</h4>' + (props ?
props.NAME + ' county: '
+ props.Count + ' sales / county' :
'Hover over a state/county');
};
info.addTo(mymap);
</script>
My problem is it only updates the filtered county fill color on mouseover of the county when a state is selected from the form, whereas I want to update the entirety of the filtered county's fill color on the state select. I have a bit more code I can include on request (css and some map setup js), but I think included the pertinent stuff.
Also, this is based on the leaflet choropleth example.
EDIT
Here is the google drive link: https://drive.google.com/drive/folders/1YMQZWgQb1UJga0SnXkYT50067KSz5il3?usp=sharing , let me know if there is an issue.
The code is too large to host live. Select a state from the list on the bottom left of the page, e.i. California, and you will see my problem

Google Maps Api: cannot click on clickable polygon behind datalayer

Hi I am using google maps api(JavaScript) to build an interactive world map. It went really well until I ran into this problem. I am using polygons to show to outline of a country. These polygons trigger a modal showing information about the country when clicked on. This worked until I started to use "Data Layer: Earthquake data". Instead of using earthquake data I use sales information of the company I work at. So if a large share of our customers are from the Netherlands then the datalayer assigned to the Netherlands will be very large. The problem is that because of the datalayers the countries are no longer clickable. I can not click "through" the datalayer. Is there a possibility that I can trigger the event behind the datalayer?
This code displays the datalayers:
map.data.loadGeoJson('./data/test.json');
map.data.setStyle(function(feature) {
var percentage = parseFloat(feature.getProperty('percentage'));
return ({
icon: {
path: google.maps.SymbolPath.CIRCLE,
scale: percentage,
fillColor: '#00ff00',
fillOpacity: 0.35,
strokeWeight: 0
}
})
});
map.data.addListener('mouseover', function(event) {
map.data.overrideStyle(event.feature, {
title: 'Hello, World!'
});
});
map.data.addListener('mouseout', function(event) {
map.data.revertStyle();
});
function eqfeed_callback(data) {
map.data.addGeoJson(data);
}
This code displays the polygons:
function drawMap(data) {
var rows = data['rows'];
for (var i in rows) {
if (rows[i][0] != 'Antarctica') {
var newCoordinates = [];
var geometries = rows[i][1]['geometries'];
if (geometries) {
for (var j in geometries) {
newCoordinates.push(constructNewCoordinates(geometries[j]));
}
} else {
newCoordinates = constructNewCoordinates(rows[i][1]['geometry']);
}
var country = new google.maps.Polygon({
paths: newCoordinates,
strokeColor: 'transparent',
strokeOpacity: 1,
strokeWeight: 0.3,
fillColor: '#cd0000',
fillOpacity: 0,
name: rows[i][0]
});
google.maps.event.addListener(country, 'mouseover', function() {
this.setOptions({
fillOpacity: 0.3
});
});
google.maps.event.addListener(country, 'mouseout', function() {
this.setOptions({
fillOpacity: 0
});
});
google.maps.event.addListener(country, 'click', function() {
var countryName = this.name;
var code = convert(countryName); // Calls a function that converts the name of the country to its official ISO 3166-1 alpha-2 code.
var modal = document.querySelector('.modal');
var instance = M.Modal.init(modal);
instance.open();
});
country.setMap(map);
}
}
If read in the documentation that changing the zIndex won't work because "Markers are always displayed in front of line-strings and polygons."
Is there a way to click on a polygon behind a datalayer?
EDIT
I tried to give the polygon a higher zIndex and I made the datalayer not clickable
map.data.loadGeoJson('./data/test.json');
map.data.setStyle(function(feature) {
var percentage = parseFloat(feature.getProperty('percentage'));
return ({
icon: {
path: google.maps.SymbolPath.CIRCLE,
scale: percentage,
fillColor: '#00ff00',
fillOpacity: 0.35,
strokeWeight: 0,
clickAble: false,
zIndex: 50
}
})
});
function eqfeed_callback(data) {
map.data.addGeoJson(data);
}
function drawMap(data) {
var rows = data['rows'];
for (var i in rows) {
if (rows[i][0] != 'Antarctica') {
var newCoordinates = [];
var geometries = rows[i][1]['geometries'];
if (geometries) {
for (var j in geometries) {
newCoordinates.push(constructNewCoordinates(geometries[j]));
}
} else {
newCoordinates = constructNewCoordinates(rows[i][1]['geometry']);
}
var country = new google.maps.Polygon({
paths: newCoordinates,
strokeColor: 'transparent',
strokeOpacity: 1,
strokeWeight: 0.3,
fillColor: '#cd0000',
fillOpacity: 0,
name: rows[i][0],
zIndex: 100
});
google.maps.event.addListener(country, 'mouseover', function() {
this.setOptions({
fillOpacity: 0.3
});
});
google.maps.event.addListener(country, 'mouseout', function() {
this.setOptions({
fillOpacity: 0
});
});
google.maps.event.addListener(country, 'click', function() {
var countryName = this.name;
var code = convert(countryName); // Calls a function that converts the name of the country to its official ISO 3166-1 alpha-2 code.
var modal = document.querySelector('.modal');
var instance = M.Modal.init(modal);
instance.open();
});
country.setMap(map);
}
}
//console.log(map);
//test(map)
}
EDIT
Apparently the datalayer wasn't the problem, but the icon was. That is why it didn't work when I did this:
map.data.setStyle(function(feature) {
var percentage = parseFloat(feature.getProperty('percentage'));
return ({
icon: {
path: google.maps.SymbolPath.CIRCLE,
scale: percentage,
fillColor: '#00ff00',
fillOpacity: 0.35,
strokeWeight: 0,
clickable: false
}
})
});
The correct way to do it is this:
map.data.setStyle(function(feature) {
var percentage = parseFloat(feature.getProperty('percentage'));
return ({
icon: {
path: google.maps.SymbolPath.CIRCLE,
scale: percentage,
fillColor: '#00ff00',
fillOpacity: 0.35,
strokeWeight: 0
},
clickable: false
})
});
You basically have 2 options here:
Set the zIndex of your Polygons to a higher number than the data layer. Your Polygons will be clickable but obviously will appear above the data layer, which might not be what you want.
Set the clickable property of the data layer to false so that you can click elements that are below. This will work if you don't need to react to clicks on the data layer...
Option 2 example code:
map.data.setStyle({
clickable: false
});
Edit: Full working example below, using option 2. As you can see the Polygon is below the data layer but you can still click it.
function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 4,
center: {
lat: -28,
lng: 137
}
});
var polygon = new google.maps.Polygon({
strokeOpacity: 0,
strokeWeight: 0,
fillColor: '#00FF00',
fillOpacity: .6,
paths: [
new google.maps.LatLng(-26, 139),
new google.maps.LatLng(-23, 130),
new google.maps.LatLng(-35, 130),
new google.maps.LatLng(-26, 139)
],
map: map
});
polygon.addListener('click', function() {
console.log('clicked on polygon');
});
// Load GeoJSON
map.data.loadGeoJson('https://storage.googleapis.com/mapsdevsite/json/google.json');
// Set style
map.data.setStyle({
fillColor: '#fff',
fillOpacity: 1,
clickable: false
});
}
#map {
height: 200px;
}
<script async defer src="https://maps.googleapis.com/maps/api/js?callback=initMap"></script>
<div id="map"></div>
I have found that after, setting the z-order, the maps api does not reliably send clicks to polygon feature in the top layer when there are many polygons.
I had one data layer of regions where each feature is a precinct boundary. When you click on one feature, it loads another data layer on top. The top layer consists of polygons inside the region with a higher z-order, representing house title boundaries within that region.
After the houses are loaded, clicking on a house should send the click to the house polygon, not the region. But this sometimes failed - especially if there are many houses.
To resolve the issue, after clicking on a region feature, I set that feature to be non clickable. Then the clicks always propagate to the correct house feature. You can still click on other features of the lower layer, just not the selected one. This solution should work if your data and presentation follows a similar pattern.
/* private utility is only called by this.hideOnlyMatchingFeaturesFromLayer() */
_overrideStyleOnFeature(feature, layer, key, value, overrideStyle, defaultStyle) {
if (feature.getProperty(key) === value) {
if (this.map) {
layer.overrideStyle(feature, overrideStyle);
}
} else {
if (this.map) {
layer.overrideStyle(feature, defaultStyle);
}
}
}
/* Apply an overrideStyle style to features in a data layer that match key==value
* All non-matching features will have the default style applied.
* Otherwise all features except the matching feature is hidden!
* Examples:
* overrideStyle = { clickable: false,strokeWeight: 3}
* defaultStyle = { clickable: true,strokeWeight: 1}
*/
overrideStyleOnMatchingFeaturesInLayer(layer, key, value, overrideStyle, defaultStyle) {
layer.forEach((feature) => {
if (Array.isArray(feature)) {
feature.forEach((f) => {
_overrideStyleOnFeature(f, layer, key, value, overrideStyle, defaultStyle);
});
} else {
_overrideStyleOnFeature(feature, layer, key, value, overrideStyle, defaultStyle);
}
});
}
/* example usage */
overrideStyleOnMatchingFeaturesInLayer(
theRegionsDataLayer,
'PROP_NAME',
propValue,
{ clickable: false, strokeWeight: 3},
{ clickable: true, strokeWeight: 1}
);

How to pass 2 values to json style in leaflet

I need to pass 2 styles, i currently have:
First style:
function style(feature) {
return {
weight: 2,
opacity: 1,
color: 'white',
dashArray: '3',
fillOpacity: 0.7,
fillColor: getColor(feature.properties.density)
};
}
The I do:
var classNameMap = <?php echo JSON_encode($classesForCountries); ?>;
geojson = L.geoJson(statesData, {
style: style,
style: function(feature) {
var classes = classNameMap[feature.properties.name];
return {className: classes};
},
onEachFeature: onEachFeature
}).addTo(map);
But that ignores the first style
I tried by passing it as an array:
geojson = L.geoJson(statesData, {
style: [style, function(){
var classes = classNameMap[feature.properties.name];
return {className: classes};
}],
onEachFeature: onEachFeature
}).addTo(map);
But yet, first style is ignored.
leaflet docs if this can help, here
This is the solution:
var classNameMap = <?php echo JSON_encode($classesForCountries); ?>;
function style(feature) {
var classes = classNameMap[feature.properties.name];
return {
weight: 2,
opacity: 1,
color: 'white',
dashArray: '3',
fillOpacity: 0.7,
fillColor: getColor(feature.properties.density),
className: classes
};
}
geojson = L.geoJson(statesData, {
style: style,
onEachFeature: onEachFeature
}).addTo(map);
Not familiar with leaflet, but looking from js perspective using duplicate key will definitely override its value with the last key entry.
If you are trying append the style1 and style2, since both the functions of style returns an object, you can do so by $.extend.
function style_1(feature) {
return {
weight: 2,
opacity: 1,
color: 'white',
dashArray: '3',
fillOpacity: 0.7,
fillColor: getColor(feature.properties.density)
};
}
...
style: function(feature) {
// Now the logic is a simple hashmap look-up
var style1 = style_1(feature);
var classes = classNameMap[feature.properties.name];
var finalStyle = $.extend(style1, {className: classes});
return finalStyle;
}
...
You're putting duplicate keys in the object initializer. Don't.
See How to generate a JSON object dynamically with duplicate keys? , Finding duplicate keys in JavaScript object

Categories

Resources