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