xref: /webtrees/resources/views/modules/place-hierarchy/map.phtml (revision 43f2f523bcb6d4090564d23802872c0679ede6bc)
1<?php
2
3use Fisharebest\Webtrees\View;
4
5/**
6 * @var array<mixed> $data
7 * @var object       $leaflet_config
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 px-md-1"></ul>
16    </div>
17</div>
18
19<?php View::push('styles') ?>
20<style>
21    .osm-wrapper, .osm-user-map {
22        height: 70vh
23    }
24
25    .osm-sidebar {
26        height: 100%;
27        overflow-x: hidden;
28        overflow-y: auto;
29        font-size: small;
30    }
31
32    .flag {
33        border: 1px solid grey !important;
34        height: 15px;
35        width: 25px;
36    }
37</style>
38<?php View::endpush() ?>
39
40<?php View::push('javascript') ?>
41<script>
42  'use strict';
43
44  window.WT_OSM = (function () {
45    const config = <?= json_encode($leaflet_config, JSON_THROW_ON_ERROR) ?>;
46
47    let map = null;
48    let sidebar = $('.osm-sidebar');
49
50    // Map components
51    let markers = L.markerClusterGroup({
52      showCoverageOnHover: false,
53    });
54
55    let resetControl = L.Control.extend({
56      options: {
57        position: 'topleft',
58      },
59
60      onAdd: function (map) {
61        let container = L.DomUtil.create('div', 'leaflet-bar leaflet-control leaflet-control-custom');
62        container.onclick = function () {
63          map.flyToBounds(markers.getBounds(), { padding: [50, 30], maxZoom: 15 });
64          sidebar.scrollTo(sidebar.children(':first'));
65          return false;
66        };
67        let anchor = L.DomUtil.create('a', 'leaflet-control-reset', container);
68        let reset = config.i18n.reset;
69        anchor.setAttribute('aria-label', reset);
70        anchor.href = '#';
71        anchor.title = reset;
72        anchor.role = 'button';
73        let image = L.DomUtil.create('i', 'fas fa-redo', anchor);
74        image.alt = reset;
75
76        return container;
77      },
78    });
79
80    /**
81     * @private
82     */
83    let _drawMap = function () {
84      map = webtrees.buildLeafletJsMap('osm-map', config)
85        .addControl(new resetControl());
86    };
87
88    /**
89     * @private
90     */
91    let _buildMapData = function () {
92      let data = <?= json_encode($data['markers'], JSON_THROW_ON_ERROR) ?>;
93
94      let geoJsonLayer = L.geoJson(data, {
95        pointToLayer: function (feature, latlng) {
96          return new L.Marker(latlng, {
97            icon: L.BeautifyIcon.icon({
98              icon: 'bullseye fas',
99              borderColor: 'transparent',
100              backgroundColor: '#1e90ff',
101              iconShape: 'marker',
102              textColor: 'white',
103            }),
104            title: feature.properties.tooltip,
105            alt: feature.properties.tooltip,
106            id: feature.id,
107          })
108            .on('popupopen', function (e) {
109              let item = sidebar.children('.mapped[data-wt-feature-id=' + e.target.feature.id + ']');
110              item.addClass('messagebox');
111              sidebar.scrollTo(item);
112            })
113            .on('popupclose', function () {
114              sidebar.children('.mapped')
115                .removeClass('messagebox');
116            });
117        },
118        onEachFeature: function (feature, layer) {
119          layer.bindPopup(feature.properties.popup);
120        },
121      });
122
123      if (data.features.length > 0) {
124        markers.addLayer(geoJsonLayer);
125        map.addLayer(markers);
126      }
127
128      map.fitBounds(<?= json_encode($data['bounds'], JSON_THROW_ON_ERROR) ?>, { padding: [50, 30] });
129      sidebar.append(<?= json_encode($data['sidebar'], JSON_THROW_ON_ERROR) ?>);
130    };
131
132    /**
133     * @param   elem
134     * @returns {$}
135     */
136    $.fn.scrollTo = function (elem) {
137      let _this = $(this);
138      _this.animate({
139        scrollTop: elem.offset().top - _this.offset().top + _this.scrollTop(),
140      });
141      return this;
142    };
143
144    // Activate marker popup when sidebar entry clicked
145    $(function () {
146      sidebar
147        // open marker popup if sidebar event is clicked
148        .on('click', '.mapped', function (e) {
149          // first close any existing
150          map.closePopup();
151          let eventId = $(this).data('id');
152          //find the marker corresponding to the clicked event
153          let mkrLayer = markers.getLayers().filter(function (v) {
154            return typeof (v.feature) !== 'undefined' && v.feature.id === eventId;
155          });
156          let mkr = mkrLayer.pop();
157          // Unfortunately zoomToShowLayer zooms to maxZoom
158          // when all marker in a cluster have exactly the
159          // same co-ordinates
160          markers.zoomToShowLayer(mkr, function (e) {
161            mkr.openPopup();
162          });
163          return false;
164        })
165        .on('click', 'a', function (e) { // stop click on a person also opening the popup
166          e.stopPropagation();
167        });
168    });
169
170    _drawMap();
171    _buildMapData();
172
173    return 'Leaflet map interface for webtrees-2';
174  })();
175</script>
176<?php View::endpush() ?>
177