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