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