xref: /webtrees/resources/views/modules/place-hierarchy/map.phtml (revision 9b152ff9230017d2c03aa1bf603a98b18250446d)
1<?php
2
3use Fisharebest\Webtrees\I18N;
4use Fisharebest\Webtrees\View;
5
6?>
7
8<div class="py-4">
9    <div class="row 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 list-unstyled"></ul>
12    </div>
13</div>
14
15<?php View::push('styles') ?>
16<style>
17    .osm-wrapper, .osm-user-map {
18        height: 75vh
19    }
20
21    .osm-sidebar {
22        height: 100%;
23        overflow-y: auto;
24        padding: 0;
25        margin: 0;
26        border: 0;
27        display: none;
28        font-size: small;
29    }
30
31    .osm-sidebar .gchart {
32        margin: 1px;
33        padding: 2px
34    }
35
36    .flag {
37        border: 1px solid grey !important;
38        height: 15px;
39        width: 25px;
40    }
41</style>
42<?php View::endpush() ?>
43
44<?php View::push('javascript') ?>
45<script type="application/javascript">
46    "use strict";
47
48    window.WT_OSM = (function () {
49        let baseData = {
50            minZoom: 2,
51            providerName: "OpenStreetMap.Mapnik",
52            providerOptions: [],
53        };
54
55        let map     = null;
56        let zoom    = null;
57        let markers = L.markerClusterGroup({
58            showCoverageOnHover: false,
59        });
60
61        let resetControl = L.Control.extend({
62            options: {
63                position: "topleft",
64            },
65
66            onAdd: function (map) {
67                let container = L.DomUtil.create("div", "leaflet-bar leaflet-control leaflet-control-custom");
68                container.onclick = function () {
69                    if (zoom) {
70                        map.flyTo(markers.getBounds().getCenter(), zoom);
71                    } else {
72                        map.flyToBounds(markers.getBounds().pad(0.2));
73                    }
74                    let sidebar = $(".osm-sidebar");
75                    sidebar.scrollTo(sidebar.children(":first"));
76
77                    return false;
78                };
79                let anchor = L.DomUtil.create("a", "leaflet-control-reset", container);
80                let reset = <?= json_encode(I18N::translate('Reset to initial map state')) ?>;
81                anchor.href = "#";
82                anchor.title = reset;
83                anchor.role = "button";
84                $(anchor).attr("aria-label", "reset");
85                let image = L.DomUtil.create("i", "fas fa-redo", anchor);
86                image.alt = reset;
87
88                return container;
89            },
90        });
91
92        let _drawMap = function () {
93            map = L.map("osm-map", {
94                center: [0, 0],
95                minZoom: baseData.minZoom, // maxZoom set by leaflet-providers.js
96                zoomControl: false, // remove default
97            });
98            L.tileLayer.provider(baseData.providerName, baseData.providerOptions).addTo(map);
99            L.control.zoom({ // Add zoom with localised text
100                zoomInTitle: <?= json_encode(I18N::translate('Zoom in')) ?>,
101                zoomOutTitle: <?= json_encode(I18N::translate('Zoom out')) ?>,
102            }).addTo(map);
103        };
104
105        let _addLayer = function () {
106            let geoJsonLayer;
107            let domObj = ".osm-sidebar";
108            let sidebar = <?= json_encode($data['sidebar']) ?>;
109            let data = <?= json_encode($data['markers']) ?>;
110
111            if (data.features.length === 1) {
112                zoom = data.features[0].properties.zoom;
113            }
114            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 sidebar = $(".osm-sidebar");
130                            let item = sidebar.children(".mapped[data-id=" + e.target.feature.id + "]");
131                            item.addClass("messagebox");
132                            sidebar.scrollTo(item);
133                        })
134                        .on("popupclose", function () {
135                            $(".osm-sidebar").children(".mapped")
136                                .removeClass("messagebox");
137                        });
138                },
139                onEachFeature: function (feature, layer) {
140                    layer.bindPopup(feature.properties.popup);
141                },
142            });
143
144            if (data.features.length > 0) {
145                markers.addLayer(geoJsonLayer);
146                map
147                    .addControl(new resetControl())
148                    .addLayer(markers)
149                    .fitBounds(markers.getBounds().pad(0.2));
150                if (zoom) {
151                    map.setView(markers.getBounds().getCenter(), zoom);
152                }
153            } else {
154                map.fitWorld();
155            }
156            $(domObj)
157                .append(sidebar)
158                .slideDown(300);
159        };
160
161        /**
162         *
163         * @param elem
164         * @returns {$}
165         */
166
167        $.fn.scrollTo = function (elem) {
168            let _this = $(this);
169            _this.animate({
170                scrollTop: elem.offset().top - _this.offset().top + _this.scrollTop(),
171            });
172            return this;
173        };
174
175        /**
176         * @private
177         */
178        let _initialize = function () {
179            // Activate marker popup when sidebar entry clicked
180            $(function () {
181                $(".osm-sidebar")
182                // open marker popup if sidebar event is clicked
183                    .on("click", ".mapped", function (e) {
184                        // first close any existing
185                        map.closePopup();
186                        let eventId = $(this).data("id");
187                        //find the marker corresponding to the clicked event
188                        let mkrLayer = markers.getLayers().filter(function (v) {
189                            return typeof (v.feature) !== "undefined" && v.feature.id === eventId;
190                        });
191                        let mkr = mkrLayer.pop();
192                        // Unfortunately zoomToShowLayer zooms to maxZoom
193                        // when all marker in a cluster have exactly the
194                        // same co-ordinates
195                        markers.zoomToShowLayer(mkr, function (e) {
196                            mkr.openPopup();
197                        });
198                        return false;
199                    })
200                    .on("click", "a", function (e) { // stop click on a person also opening the popup
201                        e.stopPropagation();
202                    });
203            });
204
205            _drawMap();
206            _addLayer();
207        };
208
209        return {
210            drawMap: function () {
211                _initialize();
212            },
213        };
214    })();
215
216    WT_OSM.drawMap();
217</script>
218<?php View::endpush() ?>
219