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