xref: /webtrees/resources/views/modules/places/tab.phtml (revision b0fcccb03b84c12d54b4ae85014c3af52863d08c)
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 type="application/javascript">
27  "use strict";
28
29  window.WT_OSM = (function () {
30    let baseData = {
31      minZoom:         2,
32      providerName:    "OpenStreetMap.Mapnik",
33      providerOptions: [],
34    };
35
36    let map     = null;
37    let zoom    = null;
38    let sidebar = $(".osm-sidebar");
39    let markers = L.markerClusterGroup({
40      showCoverageOnHover: false,
41    });
42
43    let resetControl = L.Control.extend({
44      options: {
45        position: "topleft",
46      },
47
48      onAdd: function (map) {
49        let container     = L.DomUtil.create("div", "leaflet-bar leaflet-control leaflet-control-custom");
50        container.onclick = function () {
51          if (zoom) {
52            map.flyTo(markers.getBounds().getCenter(), zoom);
53          } else {
54            map.flyToBounds(markers.getBounds().pad(0.2));
55          }
56          sidebar.scrollTo(sidebar.children(":first"));
57
58          return false;
59        };
60        let anchor        = L.DomUtil.create("a", "leaflet-control-reset", container);
61        let reset = <?= json_encode(I18N::translate('Reset to initial map state')) ?>;
62        anchor.href       = "#";
63        anchor.title      = reset;
64        anchor.role       = "button";
65        $(anchor).attr("aria-label", "reset");
66        let image = L.DomUtil.create("i", "fas fa-redo", anchor);
67        image.alt = reset;
68
69        return container;
70      },
71    });
72
73    /**
74     *
75     * @private
76     */
77    let _drawMap = function () {
78      map = L.map("osm-map", {
79        center:      [0, 0],
80        minZoom:     baseData.minZoom, // maxZoom set by leaflet-providers.js
81        zoomControl: false, // remove default
82      });
83      L.tileLayer.provider(baseData.providerName, baseData.providerOptions).addTo(map);
84      L.control.zoom({ // Add zoom with localised text
85        zoomInTitle:  <?= json_encode(I18N::translate('Zoom in')) ?>,
86        zoomOutTitle: <?= json_encode(I18N::translate('Zoom out')) ?>,
87      }).addTo(map);
88    };
89
90    let _addLayer = function () {
91      let geoJsonLayer;
92      let sidebar_content = "";
93
94      let data = <?= json_encode($data) ?>;
95
96      geoJsonLayer = L.geoJson(data, {
97        pointToLayer:  function (feature, latlng) {
98          return new L.Marker(latlng, {
99            icon:  L.BeautifyIcon.icon({
100              icon:            feature.properties.icon["name"],
101              borderColor:     "transparent",
102              backgroundColor: feature.properties.icon["color"],
103              iconShape:       "marker",
104              textColor:       "white",
105            }),
106            title: feature.properties.tooltip,
107            alt:   feature.properties.tooltip,
108            id:    feature.id,
109          })
110            .on("popupopen", function (e) {
111              let item    = sidebar.children(".gchart[data-id=" + e.target.feature.id + "]");
112              item.addClass("messagebox");
113              sidebar.scrollTo(item);
114            })
115            .on("popupclose", function () {
116              sidebar.children(".gchart")
117                .removeClass("messagebox");
118            });
119        },
120        onEachFeature: function (feature, layer) {
121          layer.bindPopup(feature.properties.summary);
122          sidebar_content += `<li class="gchart px-md-2" data-id=${feature.id}>${feature.properties.summary}</li>`;
123        },
124      });
125
126      if (data.features.length > 0) {
127        sidebar.append(sidebar_content);
128        markers.addLayer(geoJsonLayer);
129        map
130          .addControl(new resetControl())
131          .addLayer(markers);
132
133        if (data.features.length === 1) {
134          map.setView(markers.getBounds().getCenter(), data.features[0].properties.zoom);
135        } else {
136          map.fitBounds(markers.getBounds().pad(0.2))
137        }
138      } else {
139        map.fitWorld();
140        sidebar.append('<div class="bg-info text-white">' + <?= json_encode(I18N::translate('No mappable items')) ?> + "</div>");
141      }
142    };
143
144    /**
145     *
146     * @param elem
147     * @returns {$}
148     */
149
150    $.fn.scrollTo = function (elem) {
151      let _this = $(this);
152      _this.animate({
153        scrollTop: elem.offset().top - _this.offset().top + _this.scrollTop(),
154      });
155      return this;
156    };
157
158    return {
159      drawMap: function () {
160        _drawMap();
161        _addLayer();
162
163        // Activate marker popup when sidebar entry clicked
164        sidebar
165          .on("click", ".gchart", function (e) {
166            // first close any existing
167            map.closePopup();
168            let eventId  = $(this).data("id");
169            //find the marker corresponding to the clicked event
170            let mkrLayer = markers.getLayers().filter(function (v) {
171              return typeof(v.feature) !== "undefined" && v.feature.id === eventId;
172            });
173            let mkr      = mkrLayer.pop();
174            // Unfortunately zoomToShowLayer zooms to maxZoom
175            // when all marker in a cluster have exactly the
176            // same co-ordinates
177            markers.zoomToShowLayer(mkr, function (e) {
178              mkr.openPopup();
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  })();
188
189  WT_OSM.drawMap();
190</script>
191