1<?php 2 3use Fisharebest\Webtrees\I18N; 4use Fisharebest\Webtrees\Individual; 5use Fisharebest\Webtrees\View; 6 7/** 8 * @var Individual $individual 9 * @var int $generations 10 */ 11?> 12 13<div class="py-4"> 14 <div class="row gchart osm-wrapper"> 15 <div id="osm-map" class="col-sm-9 wt-ajax-load osm-user-map" dir="ltr"></div> 16 <ul class="col-sm-3 osm-sidebar wt-page-options-value list-unstyled px-md-1"></ul> 17 </div> 18</div> 19 20<?php View::push('styles') ?> 21<style> 22 .osm-wrapper, .osm-user-map { 23 height: 70vh 24 } 25 26 .osm-sidebar { 27 height: 100%; 28 overflow-y: auto; 29 font-size: small; 30 } 31 32</style> 33<?php View::endpush() ?> 34 35<?php View::push('javascript') ?> 36<script type="application/javascript"> 37 "use strict"; 38 39 window.WT_OSM = (function() { 40 let baseData = { 41 minZoom: 2, 42 providerName: "OpenStreetMap.Mapnik", 43 providerOptions: [], 44 }; 45 46 let map = null; 47 let zoom = null; 48 let sidebar = $('.osm-sidebar'); 49 let markers = L.markerClusterGroup({ 50 showCoverageOnHover: false 51 }); 52 53 let resetControl = L.Control.extend({ 54 options: { 55 position: 'topleft' 56 }, 57 58 onAdd: function (map) { 59 let container = L.DomUtil.create('div', 'leaflet-bar leaflet-control leaflet-control-custom'); 60 container.onclick = function () { 61 if (zoom) { 62 map.flyTo(markers.getBounds().getCenter(), zoom); 63 } else { 64 map.flyToBounds(markers.getBounds().pad(0.2)); 65 } 66 sidebar.scrollTo(sidebar.children(":first")); 67 68 return false; 69 }; 70 let anchor = L.DomUtil.create('a', 'leaflet-control-reset', container); 71 let reset = <?= json_encode(I18N::translate('Reset to initial map state')) ?>; 72 anchor.href = '#'; 73 anchor.title = reset; 74 anchor.role = 'button'; 75 $(anchor).attr('aria-label', 'reset'); 76 let image = L.DomUtil.create('i', 'fas fa-redo', anchor); 77 image.alt = reset; 78 79 return container; 80 }, 81 }); 82 83 /** 84 * 85 * @private 86 */ 87 let _drawMap = function () { 88 map = L.map('osm-map', { 89 center : [0, 0], 90 minZoom : baseData.minZoom, // maxZoom set by leaflet-providers.js 91 zoomControl: false, // remove default 92 }); 93 L.tileLayer.provider(baseData.providerName, baseData.providerOptions).addTo(map); 94 L.control.zoom({ // Add zoom with localised text 95 zoomInTitle : <?= json_encode(I18N::translate('Zoom in')) ?>, 96 zoomOutTitle: <?= json_encode(I18N::translate('Zoom out')) ?>, 97 }).addTo(map); 98 }; 99 100 /** 101 * @param Generations 102 * @private 103 */ 104 let _addLayer = function (Generations) { 105 let geoJsonLayer; 106 let sidebar_content = ''; 107 108 $.getJSON(<?= json_encode(route('module', ['module' => 'pedigree-map', 'action' => 'MapData', 'tree' => $individual->tree()->name(), 'xref' => $individual->xref()])) ?>, { 109 generations: Generations 110 }) 111 .done(function (data, textStatus, jqXHR) { 112 if (jqXHR.status === 200 && data.features.length === 1) { 113 zoom = data.features[0].properties.zoom; 114 } 115 geoJsonLayer = L.geoJson(data, { 116 pointToLayer : function (feature, latlng) { 117 return new L.Marker(latlng, { 118 icon : L.BeautifyIcon.icon({ 119 icon : 'bullseye fas', 120 borderColor : 'transparent', 121 backgroundColor: feature.properties.iconcolor, 122 iconShape : 'marker', 123 textColor : 'white', 124 }), 125 title: feature.properties.tooltip, 126 alt : feature.properties.tooltip, 127 id : feature.id 128 }) 129 .on('popupopen', function (e) { 130 let item = sidebar.children(".gchart[data-id=" + e.target.feature.id + "]"); 131 item.addClass('messagebox'); 132 sidebar.scrollTo(item); 133 }) 134 .on('popupclose', function () { 135 sidebar.children(".gchart") 136 .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 }) 149 .fail(function (jqXHR, textStatus, errorThrown) { 150 console.log(jqXHR, textStatus, errorThrown); 151 }) 152 .always(function (data_jqXHR, textStatus, jqXHR_errorThrown) { 153 switch (jqXHR_errorThrown.status) { 154 case 200: // Success 155 sidebar.append(sidebar_content); 156 markers.addLayer(geoJsonLayer); 157 map 158 .addControl(new resetControl()) 159 .addLayer(markers) 160 .fitBounds(markers.getBounds().pad(0.2)); 161 if (zoom) { 162 map.setView(markers.getBounds().getCenter(), zoom); 163 } 164 break; 165 case 204: // No data 166 map.fitWorld(); 167 sidebar.append('<div class="bg-info text-white">' + <?= json_encode(I18N::translate('No mappable items')) ?> + '</div>'); 168 break; 169 default: // Anything else 170 map.fitWorld(); 171 sidebar.append('<div class="bg-danger text-white">' + <?= json_encode(I18N::translate('An unknown error occurred')) ?> + '</div>'); 172 } 173 }); 174 }; 175 176 /** 177 * 178 * @param elem 179 * @returns {$} 180 */ 181 182 $.fn.scrollTo = function (elem) { 183 let _this = $(this); 184 _this.animate({ 185 scrollTop: elem.offset().top - _this.offset().top + _this.scrollTop() 186 }); 187 return this; 188 }; 189 190 /** 191 * @param generations integer 192 * @private 193 */ 194 let _initialize = function (generations) { 195 // Activate marker popup when sidebar entry clicked 196 $(function () { 197 sidebar 198 // open marker popup if sidebar event is clicked 199 .on('click', '.gchart', function (e) { 200 // first close any existing 201 map.closePopup(); 202 let eventId = $(this).data('id'); 203 //find the marker corresponding to the clicked event 204 let mkrLayer = markers.getLayers().filter(function (v) { 205 return typeof(v.feature) !== 'undefined' && v.feature.id === eventId; 206 }); 207 let mkr = mkrLayer.pop(); 208 // Unfortunately zoomToShowLayer zooms to maxZoom 209 // when all marker in a cluster have exactly the 210 // same co-ordinates 211 markers.zoomToShowLayer(mkr, function (e) { 212 mkr.openPopup(); 213 }); 214 return false; 215 }) 216 .on('click', 'a', function (e) { // stop click on a person also opening the popup 217 e.stopPropagation(); 218 }); 219 }); 220 221 _drawMap(); 222 _addLayer(generations); 223 }; 224 225 return { 226 /** 227 * @param generations integer 228 */ 229 drawMap: function (generations) { 230 _initialize(generations); 231 } 232 }; 233 })(); 234 235 WT_OSM.drawMap(<?= json_encode($generations) ?>); 236</script> 237<?php View::endpush() ?> 238