xref: /webtrees/resources/views/modules/place-hierarchy/map.phtml (revision ff7d8543881296197e75932d82c1b0443f8306ad)
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            if (data.features.length === 1) {
112                zoom = data.features[0].properties.zoom;
113            }
114            let geoJsonLayer = L.geoJson(data, {
115                pointToLayer: function (feature, latlng) {
116                    return new L.Marker(latlng, {
117                        icon:  L.BeautifyIcon.icon({
118                            icon           : 'bullseye fas',
119                            borderColor    : "transparent",
120                            backgroundColor: '#1e90ff',
121                            iconShape      : "marker",
122                            textColor      : "white",
123                        }),
124                        title: feature.properties.tooltip,
125                        alt  : feature.properties.tooltip,
126                        id   : feature.id,
127                    })
128                        .on("popupopen", function (e) {
129                            let item = sidebar.children(".mapped[data-id=" + e.target.feature.id + "]");
130                            item.addClass("messagebox");
131                            sidebar.scrollTo(item);
132                        })
133                        .on("popupclose", function () {
134                            sidebar.children(".mapped")
135                                .removeClass("messagebox");
136                        });
137                },
138                onEachFeature: function (feature, layer) {
139                    layer.bindPopup(feature.properties.popup);
140                },
141            });
142
143            if (data.features.length > 0) {
144                markers.addLayer(geoJsonLayer);
145                map.addLayer(markers)
146
147                if (zoom) {
148                    map.setView(markers.getBounds().getCenter(), zoom);
149                } else {
150                    map.fitBounds(markers.getBounds().pad(0.2));
151                }
152            } else {
153                map.fitWorld();
154            }
155            sidebar.append(<?= json_encode($data['sidebar']) ?>);
156        };
157
158        /**
159         * @param   elem
160         * @returns {$}
161         */
162        $.fn.scrollTo = function (elem) {
163            let _this = $(this);
164            _this.animate({
165                scrollTop: elem.offset().top - _this.offset().top + _this.scrollTop(),
166            });
167            return this;
168        };
169
170        // Activate marker popup when sidebar entry clicked
171        $(function () {
172            sidebar
173            // open marker popup if sidebar event is clicked
174                .on("click", ".mapped", function (e) {
175                    // first close any existing
176                    map.closePopup();
177                    let eventId = $(this).data("id");
178                    //find the marker corresponding to the clicked event
179                    let mkrLayer = markers.getLayers().filter(function (v) {
180                        return typeof (v.feature) !== "undefined" && v.feature.id === eventId;
181                    });
182                    let mkr = mkrLayer.pop();
183                    // Unfortunately zoomToShowLayer zooms to maxZoom
184                    // when all marker in a cluster have exactly the
185                    // same co-ordinates
186                    markers.zoomToShowLayer(mkr, function (e) {
187                        mkr.openPopup();
188                    });
189                    return false;
190                })
191                .on("click", "a", function (e) { // stop click on a person also opening the popup
192                    e.stopPropagation();
193                });
194        });
195
196        _drawMap();
197        _buildMapData();
198
199    return "Leaflet map interface for webtrees-2";
200    })();
201</script>
202<?php View::endpush() ?>
203