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