Leaflet: Toggle GeoJSON layers with Checkboxes - javascript

I would like to be able to use checkboxes to toggle on/off layers on a map. The map will be loaded without any layers.
Right now my buttons look like this:
<label><input type="checkbox" name="points" value="addressPoints" /> ADDRESS POINTS</label>
I'm calling my GeoJSON layers like this:
L.geoJson(schoolDistricts, {
style: defaultStyle,
onEachFeature: function (feature, layer) {
layer.bindPopup("<h4>School District: " + feature.properties.name);
layer.setStyle(defaultStyle);
(function(layer, properties) {
layer.on("mouseover", function (e) {
layer.setStyle(highlightStyle);
});
layer.on("mouseout", function (e) {
layer.setStyle(defaultStyle);
});
})(layer, feature.properties.name);
}
});
I know I need to add a change event to the checkbox code. I just don't really know how to go about writing a function to toggle the layer on and off. I have about 10 layers I will need to do this for so I'd like to be able to have a function that I can use for them all.
Hopefully this is enough information to go on.
Thanks!

Leaflet has this capability built into the layers control via an overlay object. The layer control can accept an input for a basemap object and an overlay object. The basemaps are toggled with radio buttons, the overlays are toggled with checkboxes. Here's a simple example:
var basemapObj = {
"First Basemap": L.tileLayer(...),
"Second Basemap": L.tileLayer(...)
}
var overlayObj = {
"LayerA": L.geoJson(...),
"LayerB": L.geoJson(...)
}
L.control.layers(basemapObj, overlayObj).addTo(map);
The link listed in the comments above does the same thing as the built-in control, but requires that you write the code yourself and style the elements, etc. Using the base Leaflet controls is much simpler.

Related

How to extend Leaflet Icon Class to add data-open attribute to marker HTML?

I'm trying to trigger some functionality based on the click of a marker on a GeoJSON layer in Leaflet. The eventual functionality I'm trying to implement is a flyout, or scroll out type modal populated from the individual feature's JSON attributes. Essentially, I'm trying to implement the functionality in this Tutsplus Tutorial with dynamic feature content based on the marker click.
I THINK I've figured out most of the pieces I need, but I'm struggling with how to add a data attribute, specifically data-open, to the individual marker. Building on an earlier question of mine I've realized it's not enough to just update a DOM element's CSS, but rather my app should be implementing changes based on data attributes to fully get the functionality I want.
From this question I know that this should be done by extending the L.Icon class that Leaflet provides, but the answer is a bit too terse for my current JS skills. I apologize for this effectively being a "ELI5" of a previously asked question, but I'm not sure where the options and slug come into function. I think they're implied by the question, rather than the answer I'm citing and being set on the marker itself.
Here's a simplified version of the the click handler on my markers, which grabs and zooms to location, gets feature info, and populates that info to a div. The zoom functionality works, as does extracting and placing the feature info, but I'm struggling with how to connect the functionality to trigger the modal and place the div with the feature info over the map.
function zoomToFeature(e) {
var latLngs = [e.target.getLatLng()];
var markerBounds = L.latLngBounds(latLngs);
var street = e.target.feature.properties.str_addr;
document.getElementById('street').textContent = street;
mymap.fitBounds(markerBounds);
//where the modal trigger should be
document.getElementById('infoBox').classList.add('is-visible');
}
Here are the event listeners taken from the linked tutorial, which are currently not firing, but I have them working in a standalone implementation:
const openEls = document.querySelectorAll("[data-open]");
const closeEls = document.querySelectorAll("[data-close]");
const isVisible = "is-visible";
//this is the event I want to trigger on marker click
for (const el of openEls) {
el.addEventListener("click", function() {
const modalId = this.dataset.open;
console.log(this);
document.getElementById(modalId).classList.add(isVisible);
});
}
for (const el of closeEls) {
el.addEventListener("click", function() {
this.parentElement.parentElement.parentElement.classList.remove(isVisible);
});
}
document.addEventListener("click", e => {
if (e.target == document.querySelector(".modal.is-visible")) {
document.querySelector(".modal.is-visible").classList.remove(isVisible);
}
});
So, where I'm trying to get is that when my markers are clicked, the trigger the modal to appear over the map. So, I think I'm missing connecting the marker click event with the event that triggers the modal. I think what's missing is adding the data attribute to the markers, or some way chain the events without the data attributes. As there's no direct way to add an attribute to the markers, I try to add slug option on my circle markers:
var circleMarkerOptions = {
radius: 2,
weight: 1,
opacity: 1,
fillOpacity: 0.8,
slug: 'open',
}
and If I read the previously asked question's answer correctly, than extending the Icon Class this way should add a data-open attribute.
L.Icon.DataMarkup = L.Icon.extend({
_setIconStyles: function(img, name) {
L.Icon.prototype._setIconStyles.call(this, img, name);
if (options.slug) {
img.dataset.slug = options.slug;
}
}
});
A stripped down version of my code is here (thanks #ghybs). My full implementation pulls the markers from a PostGIS table. It's a bit hard to see in the Plunker, but this code adds my class to my modal, but doesn't trigger the functionality. It does trigger the visibility if the class is manually updated to modal.is-visible, but the current implementation which renders modal is-visbile doesn't, which I think is because the CSS is interpreted on page load(?) and not in response to the update via the dev tools, while the concatenated css class matches extactly(?). When I do trigger the modal via the dev tools, the close modal listeners don't seem to work, so I'm also missing that piece of the puzzle.
So, it's a work-around to setting the data attribute, but I realized I was shoe-horning a solution where it wasn't needed. Assuming someone ends up with the same mental block. Appropriate listeners on the modal close button and another function passed to the existing marker click listener produce the desired functionality.
const closeM = document.querySelector(".close-modal");
closeM.addEventListener("click", closeMe);
var modal = document.getElementById('infoBox');
and
function modalAction(){
modal.style.display = 'block';
}
function closeMe(){
modal.style.display = 'none';
}

How to show modal window on marker click?

I am using leafletjs to build a web map and trying to figure out how to show a modal window when a marker is clicked (instead of the default popup method).
Here's my setup:
var myAirports = L.geoJson(myData, {
pointToLayer: function(latlng){
..snip..
},
onEachFeature: function(feature,layer){
$('#myModalOne').modal(options);
}
});
myAirports.addTo(map);
My HTML is like so:
<div id="myModalOne">....</div>
<div id="myModalTwo">....</div>
Lets say my data has a featurecollection with a key of 'name' (i.e., 'name': 'Bush Airport') for each feature. Would I just add a switch statement to my onEachFeature function?
Just need a little guidance,thanks.
Note: I am using Bootstrap for the modal windows
If I understand you correctly, you don't need to set the pointToLayer option which is useful if you want to display something else than a marker.
What you need is to catch the click event on the markers and display a modal window. There is no popup by default.
var myAirports = L.geoJson(myData, {
onEachFeature: function(feature,layer){
layer.on('click', function(e){
$('#myModal'+feature.properties.name).modal(options);
// or whatever that opens the right modal window
});
}
});
myAirports.addTo(map);

How to dynamically load the right markers and print them on a Leaflet map?

I'm making an application that dynamically fills dropdowns from the database. You can select different tables that have different datasources in them. So one table could be just points, but unother table could have polygons. So at the moment I have the following piece of code to generate the standard markers and pop-up information. But I want to add a specific style to the markers of different tables. But I'm not sure what's the best approach to do this. I was thinking of making some variables and declaring the styles in there and then, depending on the selection of the user, use the right variable.
This is my dynamic code so far:
window["mapDataLayer"] = L.geoJson(geojson, {
onEachFeature: function (feature, layer){
layer.on({
click: function showResultsInDiv() {
var d = document.getElementById('tab4');
d.innerHTML = "";
for (prop in feature.properties){
d.innerHTML += prop+": "+feature.properties[prop]+"<br>";
}
$('.nav-tabs a[href="#tab4"]').tab('show');
console.log(d.innerHTML);
}
}); }
}).addTo(map);

Leaflet trigger popup on feature

I have a geojson layer in leaflet and I would like to trigger a popup on a specific feature. When declaring the geoJson layer I already have an onEachFeature property which will trigger the popup on click. This works great.
onEachFeature: function (feature, layer) {
layer.bindPopup('<div><h1>' + feature.properties.name + '</h1></div><div>' + feature.properties.description + '</div>');
}
I would like to trigger this popup programmatically though, say on an event. How would I go about that?
Thanks!
You have to select the needed layer and call .openPoup() method. For example:
var geoJson = L.geoJson(geoJsonData, {
onEachFeature: onEachFeature
}).addTo(map);
geoJson.getLayer(layerId).openPopup()
I've made a fiddle for you: http://jsfiddle.net/wz3Lj7v4/15/ . The main issue is how you get the layer that you need. You can look towards .getLayer(), .getLayers() and .eachLayer() methods.

Opening a leaflet popup on a layerGroup

I am trying to draw country shapes on a leaflet map using L.GeoJSON(data).addTo(map). I then want to bind a popup to the click event of that country shape...
new L.GeoJSON(data, {
onEachFeature: function(feature, layer) {
layer['on']('click', popupFunction);
}
}).addTo(this.map);
popupFunction = function(event) {
var layer = event.target;
// Open the 'add' popup and get our content node
var bound = layer.bindPopup(
"<div>Hello World!</div>"
).openPopup();
// Ugly hack to get the HTML content node for the popup
// because I need to do things with it
var contentNode = $(bound['_popup']['_contentNode']);
}
Now this works fine when the data is a single polygon, because then the layer attribute passed to the onEachFeature function is just that: a layer.
However if the data is a multipolygon (i.e. the US) this stops working because the "layer" is now a layerGroup (it has a _layers) attribute and therefore has no _popup attribute and so I can't get the _contentNode for the popup.
It seems like this should be quite a common thing, wanting a popup on a layerGroup. Why does it have no _popup attribute?
short answer: layergroup does not support popup
plan B:
you should consider using FeatureGroup, it extends LayerGroup and has the bindPopup method and this is an example
L.featureGroup([marker1, marker2, polyline])
.bindPopup('Hello world!')
.on('click', function() { alert('Clicked on a group!'); })
.addTo(map);
You cannot bind a L.Popup to anything else than a L.Layer because the popup will some coordinates to anchor on.
For a L.Marker it will be the position (L.Latlng), for the L.Polygon it will be the center (look at the code to see how it is calculated).
As for the other cases (like yours), you can open a popup but you will have to decide where the popup opens:
var popup = L.popup()
.setLatLng(latlng)
.setContent('<p>Hello world!<br />This is a nice popup.</p>')
.openOn(map);
First of all, you should be able to bind popUp in similar way to Using GeoJSON with Leaflet tutorial. Something like this:
var geoJsonLayer = L.geoJson(data, {
onEachFeature: function(feature, layer) {
layer.bindPopup('<div>Hello World!</div>');
}
}).addTo(map);
How to further process your popUps depends on your usecase. Maybe this can be enough for you:
geoJsonLayer.eachLayer(function(layer) {
var popUp = layer._popup;
// process popUp, maybe with popUp.setContent("something");
});
Hope this helps..
I used this code to open all popups in a layer group:
markers.eachLayer(marker => marker.openPopup());
While dealing with layer groups, you might want to consider passing { autoClose: false, closeOnClick: false } options while binding popup, so popups won't get closed while opening new popup, or if user clicks on the map:
marker.bindPopup(item.name, { autoClose: false, closeOnClick: false });

Categories

Resources