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