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