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