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