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