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