xref: /webtrees/resources/views/modules/place-hierarchy/map.phtml (revision e72c24d6f8af5daa6dc0f4942f8c8f018f99ab41)
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 type="application/javascript">
38    "use strict";
39
40    window.WT_OSM = (function () {
41        let baseData = {
42            minZoom: 2,
43            providerName: "OpenStreetMap.Mapnik",
44            providerOptions: [],
45        };
46
47        let map     = null;
48        let zoom    = null;
49        let sidebar = $(".osm-sidebar");
50        let markers = L.markerClusterGroup({
51            showCoverageOnHover: false,
52        });
53
54        let resetControl = L.Control.extend({
55            options: {
56                position: "topleft",
57            },
58
59            onAdd: function (map) {
60                let container = L.DomUtil.create("div", "leaflet-bar leaflet-control leaflet-control-custom");
61                container.onclick = function () {
62                    if (zoom) {
63                        map.flyTo(markers.getBounds().getCenter(), zoom);
64                    } else {
65                        map.flyToBounds(markers.getBounds().pad(0.2));
66                    }
67                    sidebar.scrollTo(sidebar.children(":first"));
68
69                    return false;
70                };
71                let anchor = L.DomUtil.create("a", "leaflet-control-reset", container);
72                let reset = <?= json_encode(I18N::translate('Reset to initial map state')) ?>;
73                anchor.href = "#";
74                anchor.title = reset;
75                anchor.role = "button";
76                $(anchor).attr("aria-label", "reset");
77                let image = L.DomUtil.create("i", "fas fa-redo", anchor);
78                image.alt = reset;
79
80                return container;
81            },
82        });
83
84        let _drawMap = function () {
85            map = L.map("osm-map", {
86                center: [0, 0],
87                minZoom: baseData.minZoom, // maxZoom set by leaflet-providers.js
88                zoomControl: false, // remove default
89            });
90            L.tileLayer.provider(baseData.providerName, baseData.providerOptions).addTo(map);
91            L.control.zoom({ // Add zoom with localised text
92                zoomInTitle: <?= json_encode(I18N::translate('Zoom in')) ?>,
93                zoomOutTitle: <?= json_encode(I18N::translate('Zoom out')) ?>,
94            }).addTo(map);
95        };
96
97        let _addLayer = function () {
98            let geoJsonLayer;
99            let data = <?= json_encode($data['markers']) ?>;
100
101            if (data.features.length === 1) {
102                zoom = data.features[0].properties.zoom;
103            }
104            geoJsonLayer = L.geoJson(data, {
105                pointToLayer: function (feature, latlng) {
106                    return new L.Marker(latlng, {
107                        icon:  L.BeautifyIcon.icon({
108                            icon: 'bullseye fas',
109                            borderColor: "transparent",
110                            backgroundColor: '#1e90ff',
111                            iconShape: "marker",
112                            textColor: "white",
113                        }),
114                        title: feature.properties.tooltip,
115                        alt: feature.properties.tooltip,
116                        id: feature.id,
117                    })
118                        .on("popupopen", function (e) {
119                            let item = sidebar.children(".mapped[data-id=" + e.target.feature.id + "]");
120                            item.addClass("messagebox");
121                            sidebar.scrollTo(item);
122                        })
123                        .on("popupclose", function () {
124                            sidebar.children(".mapped")
125                                .removeClass("messagebox");
126                        });
127                },
128                onEachFeature: function (feature, layer) {
129                    layer.bindPopup(feature.properties.popup);
130                },
131            });
132
133            if (data.features.length > 0) {
134                markers.addLayer(geoJsonLayer);
135                map
136                    .addControl(new resetControl())
137                    .addLayer(markers)
138                    .fitBounds(markers.getBounds().pad(0.2));
139                if (zoom) {
140                    map.setView(markers.getBounds().getCenter(), zoom);
141                }
142            } else {
143                map.fitWorld();
144            }
145            sidebar.append(<?= json_encode($data['sidebar']) ?>);
146        };
147
148        /**
149         *
150         * @param elem
151         * @returns {$}
152         */
153
154        $.fn.scrollTo = function (elem) {
155            let _this = $(this);
156            _this.animate({
157                scrollTop: elem.offset().top - _this.offset().top + _this.scrollTop(),
158            });
159            return this;
160        };
161
162        /**
163         * @private
164         */
165        let _initialize = function () {
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            _addLayer();
194        };
195
196        return {
197            drawMap: function () {
198                _initialize();
199            },
200        };
201    })();
202
203    WT_OSM.drawMap();
204</script>
205<?php View::endpush() ?>
206