I am working on a leaflet map that shows property ownership over time with polygons and a time slider (https://github.com/jeffjb4488/GeoJASON_test). However, some of my polygons overlap and only the popup for the topmost layer appears.
I came across a solution for a popup that shows tabs for multiple geojson layers (http://www.gistechsolutions.com/leaflet/DEMO/pointsinpoly/indextab.html).
I am wondering if there was a way to make this work to display tabs for multiple polygons in the same geojson layer?
Code snippet:
<body>
<div id="map"></div>
<script id="rendered-js" >
var basemap_color = 'https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png';
var basemap_satellite = 'https://api.maptiler.com/tiles/satellite-v2/{z}/{x}/{y}.jpg?key=kUPH9bhggOn8sGeOtVCW';
var overlay_1890 = 'https://map-tile.server.burne138.msu.domains/Plan_Map_of_the_Highlands_1890_best_imagery_4326_largest/{z}/{x}/{y}.png';
var map = L.map('map', {
editable: true,
minZoom: 13,
maxZoom: 20,
zoomControl: false}).setView([41.461070584121636, -70.56516015402862], 16);
var basemapLayerColor = L.tileLayer(basemap_color, {
noWrap: true,
minZoom: 13,
maxZoom: 20,
attribution: '<div id="content-attribution">© <a href="https://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a> </div>'
}).addTo(map);
var basemapLayerSatellite = L.tileLayer(basemap_satellite,{
noWrap: true,
tileSize: 512,
zoomOffset: -1,
maxZoom: 20,
minZoom: 13,
attribution: '<div id="content-attribution">© <a href="https://www.maptiler.com/" target="_blank">MapTiler</a> contributors JJB |</div>',
crossOrigin: true
}).addTo(map);
var overlayLayer1890 = L.tileLayer(overlay_1890, {
noWrap: true,
maxZoom: 20,
minZoom: 13,
opacity: 0.6,
attribution: '<div id="content-attribution">Historic Map, <a href="https://collections.leventhalmap.org/search/commonwealth:x059cd109" target="_blank">Boston Public Library Norman B. Leventhal Map Center</div>',
}).addTo(map);
var baseMaps = {
"Satellite": basemapLayerSatellite,
"Standard": basemapLayerColor
};
var overlayMaps = {
"Historic Map": overlayLayer1890
};
var mql = window.matchMedia('(max-width:991px)');
let mobileView = mql.matches;
var mobileZoomBox = L.Control.extend({
options: {
position: 'topright'
},
onAdd: function (map) {
var container = L.DomUtil.create('div');
container.type="div";
container.title="Mobile Zoom Box";
container.textContent = 'Pinch To Zoom';
container.style.backgroundColor = 'white';
container.style.fontSize = "22px";
container.style.fontWeight = "700";
container.style.textAlign = "center";
container.style.backgroundSize = "100px 65px";
container.style.width = '100px';
container.style.height = '65px';
return container;
}
});
if (mobileView) {
map.addControl(new mobileZoomBox());;
} else {
var zoom_bar = new L.Control.ZoomBar({position: 'topright'}).addTo(map);
}
L.control.layers(baseMaps, overlayMaps).addTo(map);
var infoDesktop = L.control();
var infoMobile = L.control();
infoDesktop.onAdd = function (map) {
this._div = L.DomUtil.create('div', 'info'); // create a div with a class "info"
this.update();
return this._div;
};
infoMobile.onAdd = function (map) {
this._div = L.DomUtil.create('div', 'info'); // create a div with a class "info"
this.update();
return this._div;
};
// method that we will use to update the control based on feature properties passed
infoDesktop.update = function (props) {
this._div.innerHTML = '<div id="content-desktop"><h3>Lot Information</h3>' + (props ?
'<b>' + 'Lot #' + props.lot_number + ' owned by ' + props.grantee + ' starting on ' + props.time
: 'Hover over a lot</div>');
};
infoMobile.update = function (props) {
this._div.innerHTML = '<div id="content-mobile"><h2>Lot Information</h2>' + (props ?
'<b>' : '<h3>Click on a lot</h3></div>');
};
if (mobileView) {
infoMobile.addTo(map);
} else {
infoDesktop.addTo(map);
}
function style(feature) {
return {
weight: 2,
opacity: 1,
color: 'black',
dashArray: '3',
fillOpacity: 0.7,
fillColor: 'green',
};
}
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();
};
infoDesktop.update(layer.feature.properties);
infoMobile.update(layer.feature.properties);
}
var polygons
function resetHighlight(e) {
polygons.resetStyle(e.target);
infoDesktop.update();
infoMobile.update();
}
function zoomToFeature(e) {
map.fitBounds(e.target.getBounds());
}
function onEachFeature (feature, layer) {
layer.on({
mouseover: highlightFeature,
mouseout: resetHighlight,
dblclick: zoomToFeature,
});
var content = "<div style='clear: both'></div><div><center><img src='images/" + feature.properties.picture + "' style='width:200px;height:300x;'></center> <br><h4>" + 'Lot number ' + feature.properties.lot_number + "</h4><p>" + feature.properties.time + "</p><p>" + feature.properties.grantee + "</p><a href='" + feature.properties.link + "' target='_blank'>" + "Click here for more info" + "</a>" + "</p></div>";
layer.bindPopup(content, {closeButton: true});
var p = layer.feature.properties;
p.index = "Lot " + p.lot_number + " | " + p.grantee + " | " + p.time;
};
$.getJSON("data/test_polygon_wgs84_5.geojson", function (json){
// add GeoJSON layer to the map once the file is loaded
polygons = L.geoJson(json ,{
style: style,
onEachFeature: onEachFeature,
});map.fitBounds(polygons.getBounds());
// Set the sliderControl options
var sliderControl = L.control.sliderControl({
layer: polygons, //REQUIRED
alwaysShowDate: true,
range: true,
sameDate: true,
showAllPopups: false, // to show all popups, instead of one, same as popup option "autoClose: false"
showPopups: false, // to set it so, when a marker is generated by the slider, its popup doesn't automatically display
showAllOnStart: true
});
var searchControl = new L.Control.Search({
layer: polygons,
propertyName: 'index',
marker: false,
moveToLocation: function(latlng, title, map) {
//map.fitBounds( latlng.layer.getBounds() );
var zoom = map.getBoundsZoom(latlng.layer.getBounds());
map.setView(latlng, zoom); // access the zoom
}
});
searchControl.on('search:locationfound', function(e) {
//console.log('search:locationfound', );
//map.removeLayer(this._markerSearch)
e.layer.setStyle({fillColor: '#3f0', color: '#0f0'});
if(e.layer._popup)
e.layer.openPopup();
}).on('search:collapsed', function(e) {
polygons.eachLayer(function(layer) { //restore feature color
polygons.resetStyle(layer);
});
});
map.addControl( searchControl ); //inizialize search control
// Add the slider to the map
map.addControl(sliderControl);
// Initialize the slider
sliderControl.startSlider();
});
var sidebar = L.control.sidebar({ container: 'sidebar' })
.addTo(map)
.open('home');
</script>
</body>
Best Answer
On way to do it is to use your click processing function
zoomToFeature
and there with the help of turf.js library functionturf.booleanPointInPolygon
check which features contain clicked point coordinate and then build and open your popup there, instead of defining popup inonEachFeature
function.Relevant part of the code could then look something like this: