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 38 window.WT_OSM = (function () { 39 const config = <?= json_encode($leaflet_config) ?>; 40 const minZoom = 2; 41 42 let map = null; 43 let sidebar = $('.osm-sidebar'); 44 45 // Map components 46 let markers = L.markerClusterGroup({ 47 showCoverageOnHover: false, 48 }); 49 50 let resetControl = L.Control.extend({ 51 options: { 52 position: 'topleft', 53 }, 54 onAdd: function (map) { 55 let container = L.DomUtil.create('div', 'leaflet-bar leaflet-control leaflet-control-custom'); 56 container.onclick = function () { 57 map.flyToBounds(markers.getBounds(), { padding: [50, 30], maxZoom: 15 }); 58 sidebar.scrollTo(sidebar.children(':first')); 59 60 return false; 61 }; 62 let reset = config.i18n.reset; 63 let anchor = L.DomUtil.create('a', 'leaflet-control-reset', container); 64 anchor.setAttribute('aria-label', reset); 65 anchor.href = '#'; 66 anchor.title = reset; 67 anchor.role = 'button'; 68 let image = L.DomUtil.create('i', 'fas fa-redo', anchor); 69 image.alt = reset; 70 71 return container; 72 }, 73 }); 74 75 /** 76 * @private 77 */ 78 let _drawMap = function () { 79 map = webtrees.buildLeafletJsMap('osm-map', config) 80 .addControl(new resetControl()); 81 }; 82 83 /** 84 * @private 85 */ 86 let _buildMapData = function () { 87 let sidebar_content = ''; 88 let geoJson_data = <?= json_encode($data) ?>; 89 90 if (geoJson_data.features.length === 0) { 91 map.fitWorld(); 92 sidebar_content += '<div class="bg-info text-white text-center">' + <?= json_encode(I18N::translate('Nothing to show')) ?> + '</div>'; 93 } else { 94 let geoJsonLayer = L.geoJson(geoJson_data, { 95 pointToLayer: function (feature, latlng) { 96 return new L.Marker(latlng, { 97 icon: L.BeautifyIcon.icon({ 98 icon: 'bullseye fas', 99 borderColor: 'transparent', 100 backgroundColor: feature.properties.iconcolor, 101 iconShape: 'marker', 102 textColor: 'white', 103 }), 104 title: feature.properties.tooltip, 105 alt: feature.properties.tooltip, 106 id: feature.id, 107 }) 108 .on('popupopen', function (e) { 109 let item = sidebar.children('.gchart[data-wt-feature-id=' + e.target.feature.id + ']'); 110 item.addClass('messagebox'); 111 sidebar.scrollTo(item); 112 }) 113 .on('popupclose', function () { 114 sidebar.children('.gchart').removeClass('messagebox'); 115 }); 116 }, 117 onEachFeature: function (feature, layer) { 118 if (feature.properties.polyline) { 119 let pline = L.polyline(feature.properties.polyline.points, feature.properties.polyline.options); 120 markers.addLayer(pline); 121 } 122 layer.bindPopup(feature.properties.summary); 123 sidebar_content += `<li class="gchart px-md-2" data-wt-feature-id=${feature.id}>${feature.properties.summary}</li>`; 124 }, 125 }); 126 markers.addLayer(geoJsonLayer); 127 map.addLayer(markers); 128 map.fitBounds(markers.getBounds(), { padding: [50, 30], maxZoom: 15 }); 129 } 130 sidebar.append(sidebar_content); 131 }; 132 133 /** 134 * @param elem 135 * @returns {$} 136 */ 137 $.fn.scrollTo = function (elem) { 138 let _this = $(this); 139 _this.animate({ 140 scrollTop: elem.offset().top - _this.offset().top + _this.scrollTop(), 141 }); 142 return this; 143 }; 144 145 // Activate marker popup when sidebar entry clicked 146 $(function () { 147 sidebar 148 // open marker popup if sidebar event is clicked 149 .on('click', '.gchart', function (e) { 150 // first close any existing 151 map.closePopup(); 152 let eventId = $(this).data('id'); 153 //find the marker corresponding to the clicked event 154 let mkrLayer = markers.getLayers().filter(function (v) { 155 return typeof (v.feature) !== 'undefined' && v.feature.id === eventId; 156 }); 157 let mkr = mkrLayer.pop(); 158 // Unfortunately zoomToShowLayer zooms to maxZoom 159 // when all marker in a cluster have exactly the 160 // same co-ordinates 161 markers.zoomToShowLayer(mkr, function (e) { 162 mkr.openPopup(); 163 }); 164 165 return false; 166 }) 167 .on('click', 'a', function (e) { // stop click on a person also opening the popup 168 e.stopPropagation(); 169 }); 170 }); 171 172 _drawMap(); 173 _buildMapData(); 174 175 return 'Leaflet map interface for webtrees-2'; 176 })(); 177</script> 178<?php View::endpush() ?> 179