From 98986802da3d9d2cf0dfe2e5e474fd36f9a450b9 Mon Sep 17 00:00:00 2001 From: Jim Safley Date: Wed, 6 May 2026 23:51:22 -0400 Subject: [PATCH 1/9] Add polygon, polyline, and rectangle support Stores all location types as GeoJSON in a new geometry_json column, with latitude and longitude computed from it for backward-compatible address search. Enables the Leaflet.draw shape tools on the edit form, adds Leaflet.deflate to collapse small shapes at low zoom, and updates the browse map, item show page, exhibit layout, and static site export to render shapes alongside point markers. --- GeolocationPlugin.php | 61 +++--- config_form.php | 4 +- .../geolocation-locations.js | 11 +- ...ocation-marker.css => geolocation-map.css} | 0 models/Api/Location.php | 14 +- models/Location.php | 28 ++- views/helpers/GeolocationMapBrowse.php | 4 +- views/helpers/GeolocationMapOptions.php | 8 +- views/helpers/GeolocationMapSingle.php | 13 +- ...ocation-marker.css => geolocation-map.css} | 2 +- .../geolocation-map/layout.php | 11 +- .../javascripts/leaflet-deflate/L.Deflate.js | 1 + views/shared/javascripts/map.js | 181 +++++++++++------- views/shared/map/browse-json.php | 1 + views/shared/map/input-partial.php | 8 +- 15 files changed, 206 insertions(+), 141 deletions(-) rename libraries/Geolocation/StaticSiteExport/omeka-geolocation/{geolocation-marker.css => geolocation-map.css} (100%) rename views/shared/css/{geolocation-marker.css => geolocation-map.css} (98%) create mode 100644 views/shared/javascripts/leaflet-deflate/L.Deflate.js diff --git a/GeolocationPlugin.php b/GeolocationPlugin.php index 649440f..e193f90 100644 --- a/GeolocationPlugin.php +++ b/GeolocationPlugin.php @@ -58,6 +58,7 @@ public function hookInstall() `zoom_level` INT NOT NULL , `address` TEXT NOT NULL , `label` VARCHAR( 255 ) NOT NULL DEFAULT '' , + `geometry_json` TEXT NOT NULL , INDEX (`item_id`)) ENGINE = InnoDB"; $db->query($sql); @@ -71,6 +72,7 @@ public function hookInstall() set_option('geolocation_basemap', self::DEFAULT_BASEMAP); set_option('geolocation_geocoder', self::DEFAULT_GEOCODER); set_option('geolocation_item_map_enable', '1'); + set_option('geolocation_auto_fit_browse', '1'); } public function hookUninstall() @@ -163,7 +165,9 @@ public function hookUpgrade($args) } if (version_compare($args['old_version'], '4.0', '<')) { $db = get_db(); - $db->query("ALTER TABLE `$db->Location` ADD COLUMN `label` VARCHAR(255) NOT NULL DEFAULT '' AFTER `address`, DROP COLUMN `map_type`"); + $db->query("ALTER TABLE `$db->Location` ADD COLUMN `label` VARCHAR(255) NOT NULL DEFAULT '' AFTER `address`, DROP COLUMN `map_type`, ADD COLUMN `geometry_json` TEXT NULL"); + $db->query("UPDATE `$db->Location` SET `geometry_json` = CONCAT('{\"type\":\"Point\",\"coordinates\":[', `longitude`, ',', `latitude`, ']}')"); + $db->query("ALTER TABLE `$db->Location` MODIFY COLUMN `geometry_json` TEXT NOT NULL"); } } @@ -263,13 +267,9 @@ private function _head() $version = Zend_Registry::get('plugin_loader')->getPlugin('Geolocation')->getIniVersion(); queue_css_file('leaflet/leaflet', null, null, 'javascripts', $version); queue_css_file('leaflet-draw/leaflet.draw', null, null, 'javascripts', $version); - queue_css_file('geolocation-marker', null, null, 'css', $version); - queue_js_file(['leaflet/leaflet', 'leaflet/leaflet-providers', 'leaflet-draw/leaflet.draw', 'map'], 'javascripts', [], $version); - - if (get_option('geolocation_cluster')) { - queue_css_file(['MarkerCluster', 'MarkerCluster.Default'], null, null, 'javascripts/leaflet-markercluster', $version); - queue_js_file('leaflet-markercluster/leaflet.markercluster', 'javascripts', [], $version); - } + queue_css_file('geolocation-map', null, null, 'css', $version); + queue_css_file(['MarkerCluster', 'MarkerCluster.Default'], null, null, 'javascripts/leaflet-markercluster', $version); + queue_js_file(['leaflet/leaflet', 'leaflet/leaflet-providers', 'leaflet-draw/leaflet.draw', 'leaflet-deflate/L.Deflate', 'leaflet-markercluster/leaflet.markercluster', 'map'], 'javascripts', [], $version); } public function hookAfterSaveItem($args) @@ -281,7 +281,7 @@ public function hookAfterSaveItem($args) $item = $args['record']; // geolocation_form_shown is a sentinel set by input-partial.php. Its // presence means the map form was rendered, so an empty geolocation_locations - // value means all markers were deleted, not that the form was absent. + // value means all locations were deleted, not that the form was absent. if (!isset($post['geolocation_form_shown'])) { return; } @@ -295,7 +295,7 @@ public function hookAfterSaveItem($args) } foreach (json_decode($post['geolocation_locations'] ?? '[]', true) as $entry) { - if (!is_numeric($entry['latitude'] ?? null) || !is_numeric($entry['longitude'] ?? null)) { + if (empty($entry['geometry_json'])) { continue; } $id = !empty($entry['id']) ? (int) $entry['id'] : null; @@ -648,9 +648,9 @@ public function geolocationShortcode($args) $options = []; if (isset($args['fit'])) { - $options['fitMarkers'] = $booleanFilter->filter($args['fit']); + $options['fitLocations'] = $booleanFilter->filter($args['fit']); } else { - $options['fitMarkers'] = '1'; + $options['fitLocations'] = '1'; } if (isset($args['type'])) { @@ -700,27 +700,29 @@ protected function _mapForm($item, $label = '', $view = null) $existingLocations = []; if (isset($_POST['geolocation_form_shown'])) { foreach (json_decode($_POST['geolocation_locations'] ?? '[]', true) as $entry) { - if (!is_numeric($entry['latitude'] ?? null) || !is_numeric($entry['longitude'] ?? null)) { + if (empty($entry['geometry_json'])) { continue; } $existingLocations[] = [ - 'id' => !empty($entry['id']) ? (int) $entry['id'] : null, - 'latitude' => (float) $entry['latitude'], - 'longitude' => (float) $entry['longitude'], - 'zoom_level' => (int) ($entry['zoom_level'] ?? 0), - 'address' => $entry['address'] ?? '', - 'label' => $entry['label'] ?? '', + 'id' => !empty($entry['id']) ? (int) $entry['id'] : null, + 'latitude' => (float) ($entry['latitude'] ?? 0), + 'longitude' => (float) ($entry['longitude'] ?? 0), + 'zoom_level' => (int) ($entry['zoom_level'] ?? 0), + 'address' => $entry['address'] ?? '', + 'label' => $entry['label'] ?? '', + 'geometry_json' => $entry['geometry_json'], ]; } } elseif ($item && $item->id) { foreach ($this->_db->getTable('Location')->findBy(['item_id' => $item->id]) as $loc) { $existingLocations[] = [ - 'id' => $loc->id, - 'latitude' => $loc->latitude, - 'longitude' => $loc->longitude, - 'zoom_level' => $loc->zoom_level, - 'address' => $loc->address, - 'label' => $loc->label, + 'id' => $loc->id, + 'latitude' => $loc->latitude, + 'longitude' => $loc->longitude, + 'zoom_level' => $loc->zoom_level, + 'address' => $loc->address, + 'label' => $loc->label, + 'geometry_json' => $loc->geometry_json, ]; } } @@ -803,7 +805,7 @@ public function filterStaticSiteExportOmekaShortcodeCallbacks($callbacks) // @see GeolocationPlugin::geolocationShortcode() $callbacks['geolocation'] = function ($args, $frontMatter, $job) { $frontMatter['css'][] = 'vendor/leaflet/leaflet.css'; - $frontMatter['css'][] = 'vendor/omeka-geolocation/geolocation-marker.css'; + $frontMatter['css'][] = 'vendor/omeka-geolocation/geolocation-map.css'; $frontMatter['js'][] = 'vendor/jquery/jquery.js'; $frontMatter['js'][] = 'vendor/leaflet/leaflet.js'; $frontMatter['js'][] = 'vendor/omeka-geolocation/geolocation-locations.js'; @@ -825,7 +827,7 @@ public function hookStaticSiteExportSiteExportPost($args) 'title' => __('Map'), 'css' => [ 'vendor/leaflet/leaflet.css', - 'vendor/omeka-geolocation/geolocation-marker.css', + 'vendor/omeka-geolocation/geolocation-map.css', ], 'js' => [ 'vendor/jquery/jquery.js', @@ -869,7 +871,7 @@ public function hookStaticSiteExportItemBundle($args) } $frontMatterPage['css'][] = 'vendor/leaflet/leaflet.css'; - $frontMatterPage['css'][] = 'vendor/omeka-geolocation/geolocation-marker.css'; + $frontMatterPage['css'][] = 'vendor/omeka-geolocation/geolocation-map.css'; $frontMatterPage['js'][] = 'vendor/jquery/jquery.js'; $frontMatterPage['js'][] = 'vendor/leaflet/leaflet.js'; $frontMatterPage['js'][] = 'vendor/omeka-geolocation/geolocation-locations.js'; @@ -916,7 +918,7 @@ public function hookExhibitBuilderStaticSiteExportExhibitPageBlock($args) $attachments = $exhibitPageBlock->getAttachments(); $frontMatterExhibitPage['css'][] = 'vendor/leaflet/leaflet.css'; - $frontMatterExhibitPage['css'][] = 'vendor/omeka-geolocation/geolocation-marker.css'; + $frontMatterExhibitPage['css'][] = 'vendor/omeka-geolocation/geolocation-map.css'; $frontMatterExhibitPage['js'][] = 'vendor/jquery/jquery.js'; $frontMatterExhibitPage['js'][] = 'vendor/leaflet/leaflet.js'; $frontMatterExhibitPage['js'][] = 'vendor/omeka-geolocation/geolocation-locations.js'; @@ -945,6 +947,7 @@ private function _locationToStaticSiteExportArray(Location $location, Item $item { $file = $item->getFile(); return [ + 'geometry_json' => $location->geometry_json, 'latitude' => $location->latitude, 'longitude' => $location->longitude, 'zoomLevel' => $location->zoom_level, diff --git a/config_form.php b/config_form.php index 0aa02ac..7a34919 100644 --- a/config_form.php +++ b/config_form.php @@ -258,10 +258,10 @@
- +
-

+

formCheckbox('cluster', true, ['checked' => (bool) get_option('geolocation_cluster')]); ?>
diff --git a/libraries/Geolocation/StaticSiteExport/omeka-geolocation/geolocation-locations.js b/libraries/Geolocation/StaticSiteExport/omeka-geolocation/geolocation-locations.js index 02e2349..0d5b479 100644 --- a/libraries/Geolocation/StaticSiteExport/omeka-geolocation/geolocation-locations.js +++ b/libraries/Geolocation/StaticSiteExport/omeka-geolocation/geolocation-locations.js @@ -12,6 +12,7 @@ document.addEventListener('DOMContentLoaded', function(event) { const featureGroup = L.featureGroup(); // Get the locations data and add the locations to the map. + let lastGeometry = null; locationsData.forEach((locationData) => { const popupDiv = document.createElement('div'); const popupHeading = document.createElement('h2'); @@ -27,14 +28,14 @@ document.addEventListener('DOMContentLoaded', function(event) { popupDiv.appendChild(popupImg); } - const marker = L.marker([locationData.latitude, locationData.longitude]); - marker.bindPopup(popupDiv); - marker.addTo(featureGroup); + lastGeometry = JSON.parse(locationData.geometry_json); + const layer = L.geoJSON(lastGeometry); + layer.bindPopup(popupDiv); + layer.addTo(featureGroup); }); map.fitBounds(featureGroup.getBounds()); - if (locationsData.length === 1) { - // Set the zoom level if there is only one location. + if (locationsData.length === 1 && lastGeometry.type === 'Point') { map.setZoom(locationsData[0].zoomLevel ?? 15); } diff --git a/libraries/Geolocation/StaticSiteExport/omeka-geolocation/geolocation-marker.css b/libraries/Geolocation/StaticSiteExport/omeka-geolocation/geolocation-map.css similarity index 100% rename from libraries/Geolocation/StaticSiteExport/omeka-geolocation/geolocation-marker.css rename to libraries/Geolocation/StaticSiteExport/omeka-geolocation/geolocation-map.css diff --git a/models/Api/Location.php b/models/Api/Location.php index cf6273c..b517b7a 100644 --- a/models/Api/Location.php +++ b/models/Api/Location.php @@ -22,6 +22,7 @@ public function getRepresentation(Omeka_Record_AbstractRecord $record) $representation = [ 'id' => $record->id, 'url' => $this->getResourceUrl("/geolocations/{$record->id}"), + 'geometry_json' => $record->geometry_json, 'latitude' => $record->latitude, 'longitude' => $record->longitude, 'zoom_level' => $record->zoom_level, @@ -63,11 +64,14 @@ public function setPutData(Omeka_Record_AbstractRecord $record, $data) private function _applyLocationFields(Omeka_Record_AbstractRecord $record, $data) { - if (isset($data->latitude)) { - $record->latitude = $data->latitude; - } - if (isset($data->longitude)) { - $record->longitude = $data->longitude; + if (isset($data->geometry_json)) { + $record->geometry_json = $data->geometry_json; + } elseif (isset($data->latitude) && isset($data->longitude)) { + // Fallback for pre-4.0 API clients that post lat/lng without geometry_json + $record->geometry_json = json_encode([ + 'type' => 'Point', + 'coordinates' => [(float) $data->longitude, (float) $data->latitude], + ]); } if (isset($data->zoom_level)) { $record->zoom_level = $data->zoom_level; diff --git a/models/Location.php b/models/Location.php index 9477393..530195b 100644 --- a/models/Location.php +++ b/models/Location.php @@ -12,6 +12,7 @@ class Location extends Omeka_Record_AbstractRecord implements Zend_Acl_Resource_ public $zoom_level; public $address; public $label; + public $geometry_json; /** * Executes before the record is saved. @@ -24,6 +25,22 @@ protected function beforeSave($args) if (is_null($this->label)) { $this->label = ''; } + $geometry = json_decode($this->geometry_json, true); + if ($geometry) { + if ($geometry['type'] === 'Point') { + $this->longitude = $geometry['coordinates'][0]; + $this->latitude = $geometry['coordinates'][1]; + } else { + // Polygon coordinates[0] is the outer boundary; LineString coordinates is the points array directly + $coords = $geometry['type'] === 'Polygon' + ? $geometry['coordinates'][0] + : $geometry['coordinates']; + $lngs = array_column($coords, 0); + $lats = array_column($coords, 1); + $this->longitude = (min($lngs) + max($lngs)) / 2; + $this->latitude = (min($lats) + max($lats)) / 2; + } + } } /** @@ -38,14 +55,9 @@ protected function _validate() if (!$this->getTable('Item')->exists($this->item_id)) { $this->addError('item_id', __('Location requires a valid item ID.')); } - if (!is_numeric($this->latitude)) { - $this->addError('latitude', __('Location requires a latitude.')); - } - if (!is_numeric($this->longitude)) { - $this->addError('longitude', __('Location requires a longitude.')); - } - if (!is_numeric($this->zoom_level)) { - $this->addError('zoom_level', __('Location requires a zoom level.')); + $geometry = json_decode($this->geometry_json, true); + if (!$geometry || !in_array($geometry['type'] ?? '', ['Point', 'LineString', 'Polygon'])) { + $this->addError('geometry_json', __('Location requires a valid geometry.')); } } diff --git a/views/helpers/GeolocationMapBrowse.php b/views/helpers/GeolocationMapBrowse.php index ccc16a5..41455ec 100644 --- a/views/helpers/GeolocationMapBrowse.php +++ b/views/helpers/GeolocationMapBrowse.php @@ -22,8 +22,8 @@ public function geolocationMapBrowse($divId = 'map', $options = [], $attrs = [], $options['uri'] = url('geolocation/map/browse-json'); } - if (!array_key_exists('fitMarkers', $options)) { - $options['fitMarkers'] = (bool) get_option('geolocation_auto_fit_browse'); + if (!array_key_exists('fitLocations', $options)) { + $options['fitLocations'] = (bool) get_option('geolocation_auto_fit_browse'); } $class = 'map geolocation-map'; diff --git a/views/helpers/GeolocationMapOptions.php b/views/helpers/GeolocationMapOptions.php index c30605e..5195023 100644 --- a/views/helpers/GeolocationMapOptions.php +++ b/views/helpers/GeolocationMapOptions.php @@ -28,8 +28,12 @@ public function geolocationMapOptions($options = []) $options['custom_map'] = json_decode((string) get_option('geolocation_custom_map'), true); $options['strings'] = [ - 'fitAllMarkers' => __('Fit all markers'), - 'label' => __('Label'), + 'fitAllLocations' => __('Fit all locations'), + 'label' => __('Label'), + 'editLocations' => __('Edit locations'), + 'noLocationsToEdit' => __('No locations to edit'), + 'deleteLocations' => __('Delete locations'), + 'noLocationsToDelete' => __('No locations to delete'), ]; return js_escape($options); diff --git a/views/helpers/GeolocationMapSingle.php b/views/helpers/GeolocationMapSingle.php index 8058f97..c58afb7 100644 --- a/views/helpers/GeolocationMapSingle.php +++ b/views/helpers/GeolocationMapSingle.php @@ -12,7 +12,7 @@ public function geolocationMapSingle($item = null, $width = '200px', $height = ' } // For single-location items this sets the initial zoom correctly. - // For multi-location items fitMarkers() overrides the center after all points are added. + // For multi-location items fitLocations() overrides the center after all points are added. $center = [ 'latitude' => $locations[0]->latitude, 'longitude' => $locations[0]->longitude, @@ -22,13 +22,11 @@ public function geolocationMapSingle($item = null, $width = '200px', $height = ' $points = []; foreach ($locations as $loc) { $point = [ - 'latitude' => $loc->latitude, - 'longitude' => $loc->longitude, - 'zoomLevel' => $loc->zoom_level, - 'label' => $loc->label, + 'geometry_json' => $loc->geometry_json, + 'label' => $loc->label, ]; if ($loc->label !== '') { - $point['markerHtml'] = '
' + $point['popupHtml'] = '
' . '
' . html_escape($loc->label) . '
' . '
'; } @@ -37,7 +35,8 @@ public function geolocationMapSingle($item = null, $width = '200px', $height = ' $options = []; $options['basemap'] = get_option('geolocation_basemap'); - $options['points'] = $points; + $options['locations'] = $points; + $options['cluster'] = true; $options = $this->view->geolocationMapOptions($options); $center = js_escape($center); $varDivId = Inflector::variablize($divId); diff --git a/views/shared/css/geolocation-marker.css b/views/shared/css/geolocation-map.css similarity index 98% rename from views/shared/css/geolocation-marker.css rename to views/shared/css/geolocation-map.css index 1b05bb5..751cb61 100644 --- a/views/shared/css/geolocation-marker.css +++ b/views/shared/css/geolocation-map.css @@ -1,6 +1,6 @@ #omeka-map-form { width: 100%; - height: 300px; + height: 500px; clear: both; } #geolocation_address { diff --git a/views/shared/exhibit_layouts/geolocation-map/layout.php b/views/shared/exhibit_layouts/geolocation-map/layout.php index 85a0a7d..b4dfd36 100644 --- a/views/shared/exhibit_layouts/geolocation-map/layout.php +++ b/views/shared/exhibit_layouts/geolocation-map/layout.php @@ -26,8 +26,7 @@ . $body . '
'; $locations[] = [ - 'lat' => $location->latitude, - 'lng' => $location->longitude, + 'geometry_json' => $location->geometry_json, 'html' => $html, ]; endforeach; @@ -43,13 +42,9 @@ var map_locations = ; for (var i = 0; i < map_locations.length; i++) { var locationData = map_locations[i]; - geolocation_map.addMarker( - [locationData.lat, locationData.lng], - {}, - locationData.html - ); + geolocation_map.addLayerFromGeometry(JSON.parse(locationData.geometry_json), {}, locationData.html); } - geolocation_map.fitMarkers(); + geolocation_map.fitLocations(); });
diff --git a/views/shared/javascripts/leaflet-deflate/L.Deflate.js b/views/shared/javascripts/leaflet-deflate/L.Deflate.js new file mode 100644 index 0000000..09782e0 --- /dev/null +++ b/views/shared/javascripts/leaflet-deflate/L.Deflate.js @@ -0,0 +1 @@ +"use strict";L.Layer.include({_originalRemove:L.Layer.prototype.remove,remove:function(){if(this.marker){this.marker.remove()}return this._originalRemove()}});L.Map.include({_originalRemoveLayer:L.Map.prototype.removeLayer,removeLayer:function(layer){if(layer.marker){layer.marker.remove()}return this._originalRemoveLayer(layer)}});L.Deflate=L.FeatureGroup.extend({options:{minSize:10,markerOptions:{},markerType:L.marker,greedyCollapse:true},initialize:function(options){L.Util.setOptions(this,options);this._layers=[];this._needsPrepping=[];this._featureLayer=this._getFeatureLayer(options)},_getFeatureLayer:function(){if(this.options.markerLayer){return this.options.markerLayer}return L.featureGroup(this.options)},_getBounds:function(path){if(path instanceof L.Circle){path.addTo(this._map);const bounds=path.getBounds();this._map.removeLayer(path);return bounds}return path.getBounds()},_isCollapsed:function(path,zoom){const bounds=path.computedBounds;const northEastPixels=this._map.project(bounds.getNorthEast(),zoom);const southWestPixels=this._map.project(bounds.getSouthWest(),zoom);const width=Math.abs(northEastPixels.x-southWestPixels.x);const height=Math.abs(southWestPixels.y-northEastPixels.y);if(this.options.greedyCollapse){return height 0) { - this.map.fitBounds(this.markerBounds, {padding: [25, 25]}); + } else if (this.locationBounds.isValid()) { + this.map.fitBounds(this.locationBounds, {padding: [25, 25]}); } }, + addShapeLayer: function (geojson, bindHtml) { + var layer = L.GeoJSON.geometryToLayer(geojson); + if (bindHtml) { + layer.bindPopup(bindHtml, {autoPanPadding: [50, 50]}); + } + this.deflateGroup.addLayer(layer); + this.locationBounds.extend(layer.getBounds()); + return layer; + }, + + addLayerFromGeometry: function (geometry, markerOptions, bindHtml) { + if (geometry.type === 'Point') { + return this.addMarker([geometry.coordinates[1], geometry.coordinates[0]], markerOptions, bindHtml); + } + return this.addShapeLayer(geometry, bindHtml); + }, + initMap: function () { var customMap = this.options.custom_map; @@ -81,7 +89,8 @@ OmekaMap.prototype = { } this.map = L.map(this.mapDivId).setView([this.center.latitude, this.center.longitude], this.center.zoomLevel); - this.markerBounds = L.latLngBounds(); + this.locationBounds = L.latLngBounds(); + this.markers = []; L.tileLayer.provider(this.options.basemap, this.options.basemapOptions).addTo(this.map); @@ -100,9 +109,15 @@ OmekaMap.prototype = { this.clusterGroup = L.markerClusterGroup({ showCoverageOnHover: false }); - this.map.addLayer(this.clusterGroup); } + this.deflateGroup = L.deflate({ + minSize: 10, + markerLayer: this.clusterGroup, + greedyCollapse: false, + }); + this.map.addLayer(this.deflateGroup); + jQuery(this.map.getContainer()).trigger('o:geolocation:init_map', this); new OmekaFitControl({ position: 'topleft', omekaMap: this }).addTo(this.map); @@ -115,14 +130,14 @@ var OmekaFitControl = L.Control.extend({ var container = L.DomUtil.create('div', 'leaflet-bar leaflet-control leaflet-control-fit-all'); var link = L.DomUtil.create('a', '', container); link.href = '#'; - link.title = omekaMap.options.strings.fitAllMarkers; + link.title = omekaMap.options.strings.fitAllLocations; link.setAttribute('role', 'button'); - link.setAttribute('aria-label', omekaMap.options.strings.fitAllMarkers); + link.setAttribute('aria-label', omekaMap.options.strings.fitAllLocations); link.innerHTML = ''; L.DomEvent.on(link, 'click', function (e) { L.DomEvent.preventDefault(e); L.DomEvent.stopPropagation(e); - omekaMap.fitMarkers(); + omekaMap.fitLocations(); }); this._link = link; return container; @@ -142,8 +157,8 @@ function OmekaMapBrowse(mapDivId, center, options) { OmekaMapBrowse.prototype = { afterLoadItems: function () { - if (this.options.fitMarkers) { - this.fitMarkers(); + if (this.options.fitLocations) { + this.fitLocations(); } if (!this.options.list) { @@ -177,14 +192,14 @@ OmekaMapBrowse.prototype = { }, buildLayerFromLocation: function (locationData) { - this.addMarker( - [locationData.latitude, locationData.longitude], - {title: locationData.title, alt: locationData.title}, - this.buildMarkerContent(locationData) - ); + var geometry = JSON.parse(locationData.geometry_json); + var layer = this.addLayerFromGeometry(geometry, {title: locationData.title, alt: locationData.title}, this.buildLocationContent(locationData)); + if (geometry.type !== 'Point') { + layer._geolocationTitle = locationData.title || ''; + } }, - buildMarkerContent: function (locationData) { + buildLocationContent: function (locationData) { var balloon = jQuery('
'); var titleLink = jQuery('').addClass('view-item').attr('href', locationData.itemUrl).text(locationData.title); balloon.append(jQuery('
').append(titleLink)); @@ -204,25 +219,8 @@ OmekaMapBrowse.prototype = { var list = jQuery('
    '); list.appendTo(container); - // Loop through all the markers jQuery.each(this.markers, function (index, marker) { - var listElement = jQuery('
  • '); - - // Make an
    tag, give it a class for styling - var link = jQuery(''); - link.addClass('item-link'); - - // Links open up the markers on the map, clicking them doesn't actually go anywhere - link.attr('href', 'javascript:void(0);'); - link.attr('role', 'button'); - - // Each
  • starts with the title of the item - link.text(marker.options.title); - - // Clicking the link should take us to the map - link.bind('click', {}, function (event) { - link.toggleClass('current'); - + that._buildListItem(list, marker.options.title, function () { if (that.clusterGroup) { that.clusterGroup.zoomToShowLayer(marker, function () { marker.fire('click'); @@ -234,10 +232,29 @@ OmekaMapBrowse.prototype = { that.map.flyTo(marker.getLatLng()); } }); + }); + + jQuery.each(this.deflateGroup.getLayers(), function (index, layer) { + that._buildListItem(list, layer._geolocationTitle, function () { + that.map.once('moveend', function () { + layer.openPopup(); + }); + that.map.fitBounds(layer.getBounds()); + }); + }); + }, - link.appendTo(listElement); - listElement.appendTo(list); + _buildListItem: function (list, title, onClick) { + var link = jQuery('') + .addClass('item-link') + .attr('href', 'javascript:void(0);') + .attr('role', 'button') + .text(title); + link.bind('click', {}, function () { + link.toggleClass('current'); + onClick(); }); + jQuery('
  • ').append(link).appendTo(list); } }; @@ -245,12 +262,12 @@ function OmekaMapSingle(mapDivId, center, options) { var omekaMap = new OmekaMap(mapDivId, center, options); jQuery.extend(true, this, omekaMap); this.initMap(); - if (options.points && options.points.length) { - for (var i = 0; i < options.points.length; i++) { - var pt = options.points[i]; - this.addMarker([pt.latitude, pt.longitude], {title: pt.label, alt: pt.label}, pt.markerHtml); + if (options.locations && options.locations.length) { + for (var i = 0; i < options.locations.length; i++) { + var pt = options.locations[i]; + this.addLayerFromGeometry(JSON.parse(pt.geometry_json), {title: pt.label, alt: pt.label}, pt.popupHtml); } - this.fitMarkers(); + this.fitLocations(); } } @@ -266,13 +283,18 @@ function OmekaMapForm(mapDivId, center, options) { this.drawnItems = new L.FeatureGroup(); this.map.addLayer(this.drawnItems); + L.drawLocal.edit.toolbar.buttons.edit = options.strings.editLocations; + L.drawLocal.edit.toolbar.buttons.editDisabled = options.strings.noLocationsToEdit; + L.drawLocal.edit.toolbar.buttons.remove = options.strings.deleteLocations; + L.drawLocal.edit.toolbar.buttons.removeDisabled = options.strings.noLocationsToDelete; + var drawControl = new L.Control.Draw({ position: 'topleft', draw: { marker: true, - polyline: false, - polygon: false, - rectangle: false, + polyline: true, + polygon: true, + rectangle: true, circle: false, circlemarker: false, }, @@ -285,16 +307,23 @@ function OmekaMapForm(mapDivId, center, options) { this.map.addControl(drawControl); this.map.on(L.Draw.Event.CREATED, function (event) { - var latlng = event.layer.getLatLng(); - var marker = that.addLocation(latlng.lat, latlng.lng, that.map.getZoom(), null, '', ''); - marker.openPopup(); + if (event.layerType === 'marker') { + var latlng = event.layer.getLatLng(); + var marker = that.addLocation(latlng.lat, latlng.lng, that.map.getZoom(), null, '', ''); + marker.openPopup(); + } else { + that.addShape(JSON.stringify(event.layer.toGeoJSON().geometry)); + } }); this.map.on(L.Draw.Event.EDITED, function (event) { event.layers.eachLayer(function (layer) { - var latlng = layer.getLatLng(); - layer._locationData.latitude = latlng.lat; - layer._locationData.longitude = latlng.lng; + layer._locationData.geometry_json = JSON.stringify(layer.toGeoJSON().geometry); + if (layer instanceof L.Marker) { + var latlng = layer.getLatLng(); + layer._locationData.latitude = latlng.lat; + layer._locationData.longitude = latlng.lng; + } }); }); @@ -320,21 +349,33 @@ OmekaMapForm.prototype = { var marker = L.marker([lat, lng]); this.drawnItems.addLayer(marker); this.markers.push(marker); - this.markerBounds.extend([lat, lng]); + this.locationBounds.extend([lat, lng]); - marker._locationData = {id: id, latitude: lat, longitude: lng, zoom_level: zoom, address: address, label: label}; + marker._locationData = {id: id, latitude: lat, longitude: lng, zoom_level: zoom, address: address, label: label, geometry_json: JSON.stringify({type: 'Point', coordinates: [lng, lat]})}; - var labelInput = jQuery('').val(label); - var popupContent = jQuery('
    ') - .append(jQuery('').text(this.options.strings.label + ': ').append(labelInput)); + this._bindLabelPopup(marker, label); + + return marker; + }, + + addShape: function (geometryJson, id, label) { + var layer = L.GeoJSON.geometryToLayer(JSON.parse(geometryJson)); + this.drawnItems.addLayer(layer); + this.locationBounds.extend(layer.getBounds()); - marker.bindPopup(popupContent[0], {autoPanPadding: [50, 50]}); + layer._locationData = {id: id || null, geometry_json: geometryJson, zoom_level: 0, address: '', label: label || ''}; + + this._bindLabelPopup(layer, label || ''); + }, + _bindLabelPopup: function (layer, initialLabel) { + var labelInput = jQuery('').val(initialLabel); + var popupContent = jQuery('
    ') + .append(jQuery('').text(this.options.strings.label + ': ').append(labelInput)); + layer.bindPopup(popupContent[0], {autoPanPadding: [50, 50]}); labelInput.on('input', function () { - marker._locationData.label = jQuery(this).val(); + layer._locationData.label = jQuery(this).val(); }); - - return marker; }, getLocationCount: function () { diff --git a/views/shared/map/browse-json.php b/views/shared/map/browse-json.php index 174a43c..cc26e66 100644 --- a/views/shared/map/browse-json.php +++ b/views/shared/map/browse-json.php @@ -15,6 +15,7 @@ 'zoom_level' => (int) $location->zoom_level, 'address' => $location->address, 'label' => $location->label, + 'geometry_json' => $location->geometry_json, 'title' => $displayTitle, 'thumbnailUrl' => $thumbnailUrl, 'snippet' => $snippet, diff --git a/views/shared/map/input-partial.php b/views/shared/map/input-partial.php index b745b30..2140201 100644 --- a/views/shared/map/input-partial.php +++ b/views/shared/map/input-partial.php @@ -25,7 +25,11 @@