1<?php use Fisharebest\Webtrees\I18N; ?> 2 3<div class="py-4"> 4 <div class="row gchart osm-wrapper"> 5 <div id="osm-map" class="col-sm-9 wt-ajax-load osm-user-map" dir="ltr"></div> 6 <ul class="col-sm-3 osm-sidebar wt-page-options-value list-unstyled p-0"></ul> 7 </div> 8</div> 9 10<style> 11 .osm-wrapper, .osm-user-map { 12 height: 75vh 13 } 14 15 .osm-sidebar { 16 height: 100%; 17 overflow-y: auto; 18 padding: 0; 19 margin: 0; 20 border: 0; 21 display: none; 22 font-size: small; 23 } 24 25 .osm-sidebar .gchart { 26 margin: 1px; 27 padding: 2px 28 } 29 30 .osm-sidebar .gchart img { 31 height: 15px; 32 width: 25px 33 } 34 35 .osm-sidebar .border-danger:hover { 36 cursor: not-allowed 37 } 38</style> 39 40<script type="application/javascript"> 41 "use strict"; 42 43 window.WT_OSM = (function () { 44 let baseData = { 45 minZoom: 2, 46 providerName: "OpenStreetMap.Mapnik", 47 providerOptions: [], 48 I18N: { 49 zoomInTitle: <?= json_encode(I18N::translate('Zoom in')) ?>, 50 zoomOutTitle: <?= json_encode(I18N::translate('Zoom out')) ?>, 51 reset: <?= json_encode(I18N::translate('Reset to initial map state')) ?>, 52 noData: <?= json_encode(I18N::translate('No mappable items')) ?> 53 } 54 }; 55 56 let map = null; 57 let zoom = null; 58 let markers = L.markerClusterGroup({ 59 showCoverageOnHover: false, 60 }); 61 62 let resetControl = L.Control.extend({ 63 options: { 64 position: "topleft", 65 }, 66 67 onAdd: function (map) { 68 let container = L.DomUtil.create("div", "leaflet-bar leaflet-control leaflet-control-custom"); 69 container.onclick = function () { 70 if (zoom) { 71 map.flyTo(markers.getBounds().getCenter(), zoom); 72 } else { 73 map.flyToBounds(markers.getBounds().pad(0.2)); 74 } 75 let sidebar = $(".osm-sidebar"); 76 sidebar.scrollTo(sidebar.children(":first")); 77 78 return false; 79 }; 80 let anchor = L.DomUtil.create("a", "leaflet-control-reset", container); 81 anchor.href = "#"; 82 anchor.title = baseData.I18N.reset; 83 anchor.role = "button"; 84 $(anchor).attr("aria-label", "reset"); 85 let image = L.DomUtil.create("i", "fas fa-redo", anchor); 86 image.alt = baseData.I18N.reset; 87 88 return container; 89 }, 90 }); 91 92 /** 93 * 94 * @private 95 */ 96 let _drawMap = function () { 97 map = L.map("osm-map", { 98 center: [0, 0], 99 minZoom: baseData.minZoom, // maxZoom set by leaflet-providers.js 100 zoomControl: false, // remove default 101 }); 102 L.tileLayer.provider(baseData.providerName, baseData.providerOptions).addTo(map); 103 L.control.zoom({ // Add zoom with localised text 104 zoomInTitle: baseData.I18N.zoomInTitle, 105 zoomOutTitle: baseData.I18N.zoomOutTitle, 106 }).addTo(map); 107 }; 108 109 let _addLayer = function () { 110 let geoJsonLayer; 111 let domObj = ".osm-sidebar"; 112 let sidebar = ""; 113 114 let data = <?= json_encode($data) ?>; 115 116 geoJsonLayer = L.geoJson(data, { 117 pointToLayer: function (feature, latlng) { 118 return new L.Marker(latlng, { 119 icon: L.BeautifyIcon.icon({ 120 icon: feature.properties.icon["name"], 121 borderColor: "transparent", 122 backgroundColor: feature.properties.icon["color"], 123 iconShape: "marker", 124 textColor: "white", 125 }), 126 title: feature.properties.tooltip, 127 alt: feature.properties.tooltip, 128 id: feature.id, 129 }) 130 .on("popupopen", function (e) { 131 let sidebar = $(".osm-sidebar"); 132 let item = sidebar.children(".gchart[data-id=" + e.target.feature.id + "]"); 133 item.addClass("messagebox"); 134 sidebar.scrollTo(item); 135 }) 136 .on("popupclose", function () { 137 $(".osm-sidebar").children(".gchart") 138 .removeClass("messagebox"); 139 }); 140 }, 141 onEachFeature: function (feature, layer) { 142 layer.bindPopup(feature.properties.summary); 143 let myclass = feature.valid ? "gchart" : "border border-danger"; 144 sidebar += `<li class="${myclass}" data-id=${feature.id}>${feature.properties.summary}</li>`; 145 }, 146 }); 147 148 if (data.features.length > 0) { 149 $(domObj).append(sidebar); 150 markers.addLayer(geoJsonLayer); 151 map 152 .addControl(new resetControl()) 153 .addLayer(markers); 154 155 if (data.features.length === 1) { 156 map.setView(markers.getBounds().getCenter(), data.features[0].properties.zoom); 157 } else { 158 map.fitBounds(markers.getBounds().pad(0.2)) 159 } 160 } else { 161 map.fitWorld(); 162 $(domObj).append("<div class=\"bg-info text-white\">" + baseData.I18N.noData + "</div>"); 163 } 164 165 $(domObj).slideDown(300); 166 }; 167 168 /** 169 * 170 * @param elem 171 * @returns {$} 172 */ 173 174 $.fn.scrollTo = function (elem) { 175 let _this = $(this); 176 _this.animate({ 177 scrollTop: elem.offset().top - _this.offset().top + _this.scrollTop(), 178 }); 179 return this; 180 }; 181 182 return { 183 drawMap: function () { 184 _drawMap(); 185 _addLayer(); 186 187 // Activate marker popup when sidebar entry clicked 188 $(".osm-sidebar") 189 .on("click", ".gchart", function (e) { 190 // first close any existing 191 map.closePopup(); 192 let eventId = $(this).data("id"); 193 //find the marker corresponding to the clicked event 194 let mkrLayer = markers.getLayers().filter(function (v) { 195 return typeof(v.feature) !== "undefined" && v.feature.id === eventId; 196 }); 197 let mkr = mkrLayer.pop(); 198 // Unfortunately zoomToShowLayer zooms to maxZoom 199 // when all marker in a cluster have exactly the 200 // same co-ordinates 201 markers.zoomToShowLayer(mkr, function (e) { 202 mkr.openPopup(); 203 }); 204 return false; 205 }) 206 .on("click", "a", function (e) { // stop click on a person also opening the popup 207 e.stopPropagation(); 208 }); 209 }, 210 }; 211 })(); 212 213 WT_OSM.drawMap(); 214</script> 215