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