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