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