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 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 return false; 76 }; 77 let anchor = L.DomUtil.create("a", "leaflet-control-reset", container); 78 anchor.href = "#"; 79 anchor.title = baseData.I18N.reset; 80 anchor.role = "button"; 81 $(anchor).attr("aria-label", "reset"); 82 let image = L.DomUtil.create("i", "fas fa-redo", anchor); 83 image.alt = baseData.I18N.reset; 84 85 return container; 86 }, 87 }); 88 89 /** 90 * 91 * @private 92 */ 93 let _drawMap = function () { 94 map = L.map("osm-map", { 95 center: [0, 0], 96 minZoom: baseData.minZoom, // maxZoom set by leaflet-providers.js 97 zoomControl: false, // remove default 98 }); 99 L.tileLayer.provider(baseData.providerName, baseData.providerOptions).addTo(map); 100 L.control.zoom({ // Add zoom with localised text 101 zoomInTitle: baseData.I18N.zoomInTitle, 102 zoomOutTitle: baseData.I18N.zoomOutTitle, 103 }).addTo(map); 104 }; 105 106 let _addLayer = function () { 107 let geoJsonLayer; 108 let domObj = ".osm-sidebar"; 109 let sidebar = ""; 110 111 let data = <?= json_encode($data) ?>; 112 113 geoJsonLayer = L.geoJson(data, { 114 pointToLayer: function (feature, latlng) { 115 return new L.Marker(latlng, { 116 icon: L.BeautifyIcon.icon({ 117 icon: feature.properties.icon["name"], 118 borderColor: "transparent", 119 backgroundColor: feature.valid ? feature.properties.icon["color"] : "transparent", 120 iconShape: "marker", 121 textColor: feature.valid ? "white" : "transparent", 122 }), 123 title: feature.properties.tooltip, 124 alt: feature.properties.tooltip, 125 id: feature.id, 126 }) 127 .on("popupopen", function (e) { 128 let sidebar = $(".osm-sidebar"); 129 let item = sidebar.children(".gchart[data-id=" + e.target.feature.id + "]"); 130 item.addClass("messagebox"); 131 sidebar.scrollTo(item); 132 }) 133 .on("popupclose", function () { 134 $(".osm-sidebar").children(".gchart") 135 .removeClass("messagebox"); 136 }); 137 }, 138 onEachFeature: function (feature, layer) { 139 if (feature.properties.polyline) { 140 let pline = L.polyline(feature.properties.polyline.points, feature.properties.polyline.options); 141 markers.addLayer(pline); 142 } 143 layer.bindPopup(feature.properties.summary); 144 let myclass = feature.valid ? "gchart" : "border border-danger"; 145 sidebar += `<li class="${myclass}" data-id=${feature.id}>${feature.properties.summary}</li>`; 146 }, 147 }); 148 149 if (data.features.length > 0) { 150 $(domObj).append(sidebar); 151 markers.addLayer(geoJsonLayer); 152 map 153 .addControl(new resetControl()) 154 .addLayer(markers); 155 156 if (data.features.length === 1) { 157 map.setView(markers.getBounds().getCenter(), data.features[0].properties.zoom); 158 } else { 159 map.fitBounds(markers.getBounds().pad(0.2)) 160 } 161 } else { 162 map.fitWorld(); 163 $(domObj).append("<div class=\"bg-info text-white\">" + baseData.I18N.noData + "</div>"); 164 } 165 166 $(domObj).slideDown(300); 167 }; 168 169 /** 170 * 171 * @param elem 172 * @returns {$} 173 */ 174 175 $.fn.scrollTo = function (elem) { 176 let _this = $(this); 177 _this.animate({ 178 scrollTop: elem.offset().top - _this.offset().top + _this.scrollTop(), 179 }); 180 return this; 181 }; 182 183 return { 184 drawMap: function () { 185 _drawMap(); 186 _addLayer(); 187 188 // Activate marker popup when sidebar entry clicked 189 $(".osm-sidebar") 190 .on("click", ".gchart", function (e) { 191 // first close any existing 192 map.closePopup(); 193 let eventId = $(this).data("id"); 194 //find the marker corresponding to the clicked event 195 let mkrLayer = markers.getLayers().filter(function (v) { 196 return typeof(v.feature) !== "undefined" && v.feature.id === eventId; 197 }); 198 let mkr = mkrLayer.pop(); 199 // Unfortunately zoomToShowLayer zooms to maxZoom 200 // when all marker in a cluster have exactly the 201 // same co-ordinates 202 markers.zoomToShowLayer(mkr, function (e) { 203 mkr.openPopup(); 204 }); 205 return false; 206 }) 207 .on("click", "a", function (e) { // stop click on a person also opening the popup 208 e.stopPropagation(); 209 }); 210 }, 211 }; 212 })(); 213 214 WT_OSM.drawMap(); 215</script> 216