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