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