1<?php 2 3use Fisharebest\Webtrees\I18N; 4use Fisharebest\Webtrees\View; 5 6/** 7 * @var array<mixed> $data 8 * @var object $leaflet_config 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> 36'use strict'; 37 38window.WT_OSM = (function () { 39 const config = <?= json_encode($leaflet_config) ?>; 40 const minZoom = 2; 41 42 let map = null; 43 let zoom = null; 44 let sidebar = $('.osm-sidebar'); 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 = config.i18n.reset; 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 /** 81 * @private 82 */ 83 let _drawMap = function() { 84 map = webtrees.buildLeafletJsMap('osm-map', config) 85 .addControl(new resetControl()); 86 }; 87 88 /** 89 * @private 90 */ 91 let _buildMapData = function () { 92 let sidebar_content = ''; 93 let geoJson_data = <?= json_encode($data) ?>; 94 95 if (geoJson_data.features.length === 0) { 96 map.fitWorld(); 97 sidebar_content += '<div class="bg-info text-white text-center">' + <?= json_encode(I18N::translate('Nothing to show')) ?> +'</div>'; 98 } else { 99 if (geoJson_data.features.length === 1) { 100 //fudge factor - maps zooms to maximum permitted otherwise 101 zoom = geoJson_data.features[0].properties.zoom; 102 } 103 let geoJsonLayer = L.geoJson(geoJson_data, { 104 pointToLayer: function (feature, latlng) { 105 return new L.Marker(latlng, { 106 icon: L.BeautifyIcon.icon({ 107 icon: 'bullseye fas', 108 borderColor: 'transparent', 109 backgroundColor: feature.properties.iconcolor, 110 iconShape: 'marker', 111 textColor: 'white', 112 }), 113 title: feature.properties.tooltip, 114 alt: feature.properties.tooltip, 115 id: feature.id, 116 }) 117 .on('popupopen', function (e) { 118 let item = sidebar.children('.gchart[data-id=' + e.target.feature.id + ']'); 119 item.addClass('messagebox'); 120 sidebar.scrollTo(item); 121 }) 122 .on('popupclose', function () { 123 sidebar.children('.gchart').removeClass('messagebox'); 124 }); 125 }, 126 onEachFeature: function (feature, layer) { 127 if (feature.properties.polyline) { 128 let pline = L.polyline(feature.properties.polyline.points, feature.properties.polyline.options); 129 markers.addLayer(pline); 130 } 131 layer.bindPopup(feature.properties.summary); 132 sidebar_content += `<li class="gchart px-md-2" data-id=${feature.id}>${feature.properties.summary}</li>`; 133 }, 134 }); 135 markers.addLayer(geoJsonLayer); 136 map.addLayer(markers); 137 if (zoom) { 138 map.setView(markers.getBounds().getCenter(), zoom); 139 } else { 140 map.fitBounds(markers.getBounds(), {padding: [50, 30]}); 141 } 142 } 143 sidebar.append(sidebar_content); 144 }; 145 146 /** 147 * @param elem 148 * @returns {$} 149 */ 150 $.fn.scrollTo = function (elem) { 151 let _this = $(this); 152 _this.animate({ 153 scrollTop: elem.offset().top - _this.offset().top + _this.scrollTop(), 154 }); 155 return this; 156 }; 157 158// Activate marker popup when sidebar entry clicked 159 $(function () { 160 sidebar 161 // open marker popup if sidebar event is clicked 162 .on('click', '.gchart', function (e) { 163 // first close any existing 164 map.closePopup(); 165 let eventId = $(this).data('id'); 166 //find the marker corresponding to the clicked event 167 let mkrLayer = markers.getLayers().filter(function (v) { 168 return typeof (v.feature) !== 'undefined' && v.feature.id === eventId; 169 }); 170 let mkr = mkrLayer.pop(); 171 // Unfortunately zoomToShowLayer zooms to maxZoom 172 // when all marker in a cluster have exactly the 173 // same co-ordinates 174 markers.zoomToShowLayer(mkr, function (e) { 175 mkr.openPopup(); 176 }); 177 178 return false; 179 }) 180 .on('click', 'a', function (e) { // stop click on a person also opening the popup 181 e.stopPropagation(); 182 }); 183 }); 184 185 _drawMap(); 186 _buildMapData(); 187 188 return 'Leaflet map interface for webtrees-2'; 189})(); 190</script> 191<?php View::endpush() ?> 192