xref: /webtrees/resources/views/modules/places/tab.phtml (revision 8121b9bec19818120092699199161a1357bb8f3f)
1<?php use Fisharebest\Webtrees\I18N; ?>
2
3<div class="py-4">
4    <div class="row gchart osm-wrapper">
5        <div id="osm-map" class="col-sm-9 wt-ajax-load osm-user-map"></div>
6        <ul class='col-sm-3 osm-sidebar wt-page-options-value'></ul>
7    </div>
8</div>
9
10<style>
11    .osm-wrapper, .osm-user-map {
12        height: 45vh
13    }
14
15    .osm-admin-map {
16        height: 55vh;
17        border: 1px solid darkGrey
18    }
19
20    .osm-sidebar {
21        height: 100%;
22        overflow-y: auto;
23        padding: 0;
24        margin: 0;
25        border: 0;
26        display: none;
27        font-size: small;
28    }
29
30    .osm-sidebar .gchart {
31        margin: 1px;
32        padding: 2px
33    }
34
35    .osm-sidebar .gchart img {
36        height: 15px;
37        width: 25px
38    }
39
40    .osm-sidebar .border-danger:hover {
41        cursor: not-allowed
42    }
43
44    [dir=rtl] .leaflet-right {
45        right: auto;
46        left: 0
47    }
48
49    [dir=rtl] .leaflet-right .leaflet-control {
50        margin-right: 0;
51        margin-left: 10px
52    }
53
54    [dir=rtl] .leaflet-left {
55        left: auto;
56        right: 0
57    }
58
59    [dir=rtl] .leaflet-left .leaflet-control {
60        margin-left: 0;
61        margin-right: 10px
62    }
63</style>
64
65<script type="application/javascript">
66  "use strict";
67
68  window.WT_OSM = (function () {
69    let baseData = {
70      minZoom:         2,
71      providerName:    "OpenStreetMap.Mapnik",
72      providerOptions: [],
73      I18N:            {
74        zoomInTitle: <?= json_encode(I18N::translate('Zoom in')) ?>,
75        zoomOutTitle: <?= json_encode(I18N::translate('Zoom out')) ?>,
76        reset: <?= json_encode(I18N::translate('Reset to initial map state')) ?>,
77        noData: <?= json_encode(I18N::translate('No mappable items')) ?>
78      }
79    };
80
81    let map     = null;
82    let zoom    = null;
83    let markers = L.markerClusterGroup({
84      showCoverageOnHover: false,
85    });
86
87    let resetControl = L.Control.extend({
88      options: {
89        position: "topleft",
90      },
91
92      onAdd: function (map) {
93        let container     = L.DomUtil.create("div", "leaflet-bar leaflet-control leaflet-control-custom");
94        container.onclick = function () {
95          if (zoom) {
96            map.flyTo(markers.getBounds().getCenter(), zoom);
97          } else {
98            map.flyToBounds(markers.getBounds().pad(0.2));
99          }
100          return false;
101        };
102        let anchor        = L.DomUtil.create("a", "leaflet-control-reset", container);
103        anchor.href       = "#";
104        anchor.title      = baseData.I18N.reset;
105        anchor.role       = "button";
106        $(anchor).attr("aria-label", "reset");
107        let image = L.DomUtil.create("i", "fas fa-redo", anchor);
108        image.alt = baseData.I18N.reset;
109
110        return container;
111      },
112    });
113
114    /**
115     *
116     * @private
117     */
118    let _drawMap = function () {
119      map = L.map("osm-map", {
120        center:      [0, 0],
121        minZoom:     baseData.minZoom, // maxZoom set by leaflet-providers.js
122        zoomControl: false, // remove default
123      });
124      L.tileLayer.provider(baseData.providerName, baseData.providerOptions).addTo(map);
125      L.control.zoom({ // Add zoom with localised text
126        zoomInTitle:  baseData.I18N.zoomInTitle,
127        zoomOutTitle: baseData.I18N.zoomOutTitle,
128      }).addTo(map);
129    };
130
131    let _addLayer = function () {
132      let geoJsonLayer;
133      let domObj  = ".osm-sidebar";
134      let sidebar = "";
135
136      let data = <?= json_encode($data) ?>;
137
138      geoJsonLayer = L.geoJson(data, {
139        pointToLayer:  function (feature, latlng) {
140          return new L.Marker(latlng, {
141            icon:  L.BeautifyIcon.icon({
142              icon:            feature.properties.icon["name"],
143              borderColor:     "transparent",
144              backgroundColor: feature.valid ? feature.properties.icon["color"] : "transparent",
145              iconShape:       "marker",
146              textColor:       feature.valid ? "white" : "transparent",
147            }),
148            title: feature.properties.tooltip,
149            alt:   feature.properties.tooltip,
150            id:    feature.id,
151          })
152            .on("popupopen", function (e) {
153              let sidebar = $(".osm-sidebar");
154              let item    = sidebar.children(".gchart[data-id=" + e.target.feature.id + "]");
155              item.addClass("messagebox");
156              sidebar.scrollTo(item);
157            })
158            .on("popupclose", function () {
159              $(".osm-sidebar").children(".gchart")
160                .removeClass("messagebox");
161            });
162        },
163        onEachFeature: function (feature, layer) {
164          if (feature.properties.polyline) {
165            let pline = L.polyline(feature.properties.polyline.points, feature.properties.polyline.options);
166            markers.addLayer(pline);
167          }
168          layer.bindPopup(feature.properties.summary);
169          let myclass = feature.valid ? "gchart" : "border border-danger";
170          sidebar += `<li class="${myclass}" data-id=${feature.id}>${feature.properties.summary}</li>`;
171        },
172      });
173
174      if (data.features.length > 0) {
175        $(domObj).append(sidebar);
176        markers.addLayer(geoJsonLayer);
177        map
178          .addControl(new resetControl())
179          .addLayer(markers);
180
181        if (data.features.length === 1) {
182          map.setView(markers.getBounds().getCenter(), data.features[0].properties.zoom);
183        } else {
184          map.fitBounds(markers.getBounds().pad(0.2))
185        }
186      } else {
187        map.fitWorld();
188        $(domObj).append("<div class=\"bg-info text-white\">" + baseData.I18N.noData + "</div>");
189      }
190
191      $(domObj).slideDown(300);
192    };
193
194    /**
195     *
196     * @param elem
197     * @returns {$}
198     */
199
200    $.fn.scrollTo = function (elem) {
201      let _this = $(this);
202      _this.animate({
203        scrollTop: elem.offset().top - _this.offset().top + _this.scrollTop(),
204      });
205      return this;
206    };
207
208    return {
209      drawMap: function () {
210        _drawMap();
211        _addLayer();
212
213        // Activate marker popup when sidebar entry clicked
214        $(".osm-sidebar")
215          .on("click", ".gchart", function (e) {
216            // first close any existing
217            map.closePopup();
218            let eventId  = $(this).data("id");
219            //find the marker corresponding to the clicked event
220            let mkrLayer = markers.getLayers().filter(function (v) {
221              return typeof(v.feature) !== "undefined" && v.feature.id === eventId;
222            });
223            let mkr      = mkrLayer.pop();
224            // Unfortunately zoomToShowLayer zooms to maxZoom
225            // when all marker in a cluster have exactly the
226            // same co-ordinates
227            markers.zoomToShowLayer(mkr, function (e) {
228              mkr.openPopup();
229            });
230            return false;
231          })
232          .on("click", "a", function (e) { // stop click on a person also opening the popup
233            e.stopPropagation();
234          });
235      },
236    };
237  })();
238
239  WT_OSM.drawMap();
240</script>
241