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