From 2a70b529dded005e694fb50c3b2b5fef76c1d4e5 Mon Sep 17 00:00:00 2001 From: Diego Pino Navarro Date: Thu, 27 Feb 2020 20:29:49 -0500 Subject: [PATCH] Issue-61: Let's travel around the globe (#62) GEOJSON and Leaflet + SBF. All working together and flying in tourist class --- .../schema/format_strawberryfield.schema.yml | 66 +- format_strawberryfield.libraries.yml | 33 + js/leaflet_strawberry.js | 115 +++ .../FieldFormatter/StrawberryMapFormatter.php | 803 ++++++++++++++++++ 4 files changed, 1016 insertions(+), 1 deletion(-) create mode 100644 js/leaflet_strawberry.js create mode 100644 src/Plugin/Field/FieldFormatter/StrawberryMapFormatter.php diff --git a/config/schema/format_strawberryfield.schema.yml b/config/schema/format_strawberryfield.schema.yml index 4888898f..601f9a10 100644 --- a/config/schema/format_strawberryfield.schema.yml +++ b/config/schema/format_strawberryfield.schema.yml @@ -271,7 +271,7 @@ field.formatter.settings.strawberry_pdf_formatter: label: 'First Page to display per PDF' field.formatter.settings.strawberry_mirador_formatter: type: mapping - label: 'Specific Config for strawberry_pdf_formatter' + label: 'Specific Config for strawberry_mirador_formatter' mapping: iiif_base_url: type: string @@ -343,6 +343,70 @@ format_strawberryfield.viewmodemapping_settings.mapping: type: integer label: 'Order in which this is evaluated' +field.formatter.settings.strawberry_map_formatter: + type: mapping + label: 'Specific Config for strawberry_map_formatter' + mapping: + json_key_source: + type: string + label: 'JSON or JMESPATH that conditions rendering. If empty will always render' + iiif_base_url: + type: string + label: 'Custom Public IIIF Server URL' + iiif_base_url_internal: + type: string + label: 'Custom internal IIIF Server URL' + metadataexposeentity: + type: string + label: 'Machine name of the exposed Metadata Config Entity endpoint' + mediasource: + type: mapping + label: 'Sources for GeoJSON URL' + mapping: + geojsonnodelist: + type: string + label: 'If geojsonnodelist is being used' + metadataexposeentity: + type: string + label: 'If metadataexposeentity is being used' + geojsonurl: + type: string + label: 'If geojsonurl is being used' + main_mediasource: + type: string + label: 'Primary GeoJSON Source used' + metadataexposeentity_source: + type: string + label: 'metadataexpose_entity machine name' + geojsonnodelist_json_key_source: + type: string + label: 'Strawberryfield/JSON key containing NODE ids or UUIDs from which to generate GeoJSON URLs' + geojsonurl_json_key_source: + type: string + label: 'Strawberryfield/JSON key containing GeoJSON URLs to display' + max_width: + type: string + label: 'Max with for the Map. 0 to force 100% width' + max_height: + type: string + initial_zoom: + type: integer + label: 'Initial Zoom for the Map' + max_zoom: + type: integer + label: 'Max Zoom for the Map' + min_zoom: + type: integer + label: 'Min Zoom for the Map' + tilemap_url: + type: string + label: 'Tile Map URL' + tilemap_attribution: + type: string + label: 'Tile Map Attribution String' + use_iiif_globals: + type: string + label: 'Whether to use global IIIF settings or not.' # Given any DS field plugin formatter key a type ds.field_plugin.*: type: mapping diff --git a/format_strawberryfield.libraries.yml b/format_strawberryfield.libraries.yml index 2c3ce390..50dc5aae 100644 --- a/format_strawberryfield.libraries.yml +++ b/format_strawberryfield.libraries.yml @@ -152,3 +152,36 @@ mirador_strawberry: - core/drupalSettings - format_strawberryfield/mirador_projectmirador - format_strawberryfield/mirador_font + +leaflet_core: + version: 1.6.0 + license: + name: BSD-2-Clause + url: https://cdn.jsdelivr.net/npm/leaflet@1.6.0/LICENSE + gpl-compatible: true + js: + https://cdn.jsdelivr.net/npm/leaflet@1.6.0/dist/leaflet-src.min.js: { external: true, minified: true, preprocess: false} + css: + component: + https://cdn.jsdelivr.net/npm/leaflet@1.6.0/dist/leaflet.css: { external: true} + +leaflet_ajax: + version: 2.1.0 + license: + name: MIT + url: https://cdn.jsdelivr.net/npm/leaflet-ajax@2.1.0/license.md + gpl-compatible: true + js: + https://cdn.jsdelivr.net/npm/leaflet-ajax@2.1.0/dist/leaflet.ajax.min.js: { external: true, minified: true, preprocess: false} + dependencies: + - format_strawberryfield/leaflet_core + +leaflet_strawberry: + version: 1.0 + js: + js/leaflet_strawberry.js: {minified: false} + dependencies: + - core/jquery + - core/drupal + - core/drupalSettings + - format_strawberryfield/leaflet_ajax \ No newline at end of file diff --git a/js/leaflet_strawberry.js b/js/leaflet_strawberry.js new file mode 100644 index 00000000..e8196694 --- /dev/null +++ b/js/leaflet_strawberry.js @@ -0,0 +1,115 @@ +(function ($, Drupal, drupalSettings, L) { + + 'use strict'; + + Drupal.behaviors.format_strawberryfield_leaflet_initiate = { + attach: function(context, settings) { + $('.strawberry-leaflet-item[data-iiif-infojson]').once('attache_leaflet') + .each(function (index, value) { + + var $featurecount = 0; + + function popUpFeature(feature, layer){ + var popupText = feature.properties.name +"
"; + layer.bindPopup(popupText); + } + var markerArray = []; + + function onEachFeature(feature, layer) { + popUpFeature(feature, layer); + } + // Get the node uuid for this element + var element_id = $(this).attr("id"); + // Check if we got some data passed via Drupal settings. + if (typeof(drupalSettings.format_strawberryfield.leaflet[element_id]) != 'undefined') { + + $(this).height(drupalSettings.format_strawberryfield.leaflet[element_id]['height']); + $(this).width(drupalSettings.format_strawberryfield.leaflet[element_id]['width']); + // Defines our basic options for leaflet GEOJSON + + var $initialzoom = 5; + + if (drupalSettings.format_strawberryfield.leaflet[element_id]['initialzoom'] || drupalSettings.format_strawberryfield.leaflet[element_id]['initialzoom'] === 0) { + $initialzoom = drupalSettings.format_strawberryfield.leaflet[element_id]['initialzoom']; + } + // initialize the map + var map = L.map(element_id).setView([40.1, -100], $initialzoom); + // Use current's user lat/long + // Does not work without HTTPS + // map.locate({setView: true, maxZoom: 8}); + + var geojsonLayer = L.geoJson.ajax(drupalSettings.format_strawberryfield.leaflet[element_id]['geojsonurl'],{ + onEachFeature: onEachFeature, + pointToLayer: function (feature, latlng) { + markerArray.push(L.marker (latlng)); + return L.marker (latlng); + }, + }); + // The tilemap url in /{z}/{x}/{y}.png format. Can have a key after a ? if provided by the user. + // Defaults, should never be needed, in case wants to get around of restricted forms? + // See https://operations.osmfoundation.org/policies/tiles/ and consider contributing if you + // are reading this. + + var $tilemap = { + url:'https://a.tile.openstreetmap.org/{z}/{x}/{y}.png', + attribution: '© OpenStreetMap contributors' + } + var $minzoom = 0; + var $maxzoom = 10; + + if (drupalSettings.format_strawberryfield.leaflet[element_id]['tilemap_url']) { + $tilemap.url = drupalSettings.format_strawberryfield.leaflet[element_id]['tilemap_url']; + $tilemap.attribution = drupalSettings.format_strawberryfield.leaflet[element_id]['tilemap_attribution']; + } + + if (drupalSettings.format_strawberryfield.leaflet[element_id]['minzoom'] || drupalSettings.format_strawberryfield.leaflet[element_id]['minzoom'] === 0) { + $minzoom = drupalSettings.format_strawberryfield.leaflet[element_id]['minzoom']; + } + if (drupalSettings.format_strawberryfield.leaflet[element_id]['maxzoom'] || drupalSettings.format_strawberryfield.leaflet[element_id]['maxzoom'] === 0) { + $maxzoom = drupalSettings.format_strawberryfield.leaflet[element_id]['maxzoom']; + } + + // load a tile layer + L.tileLayer($tilemap.url, + { + attribution: $tilemap.attribution, + maxZoom: $maxzoom, + minZoom: $minzoom + }).addTo(map); + + map.on('layeradd', function (e) { + if (markerArray.length > 0) { + var geojsongroup = new L.featureGroup(markerArray); + if (markerArray.length == 1) { + map.setView(geojsongroup.getBounds().getCenter(), $initialzoom); + } + else { + map.fitBounds(geojsongroup.getBounds()); + } + + } + }); + + var $firstgeojson = [drupalSettings.format_strawberryfield.leaflet[element_id]['geojsonurl']]; + var $allgeojsons = $firstgeojson.concat(drupalSettings.format_strawberryfield.leaflet[element_id]['geojsonother']); + var $secondgeojson = drupalSettings.format_strawberryfield.leaflet[element_id]['geojsonother'].find(x=>x!==undefined); + + if (Array.isArray($allgeojsons) && $allgeojsons.length && typeof($secondgeojson) != 'undefined') { + + $allgeojsons.forEach(geojsonURL => { + // TODO Provider, rights, etc should be passed by metadata at + // \Drupal\format_strawberryfield\Plugin\Field\FieldFormatter\StrawberryMapFormatter + // Deal with this for Beta3 + // Not a big issue if GeoJSON has that data. We can iterate over all Feature keys + // And print them on the overlay? + geojsonLayer.addUrl("geojsonURL");//we now have 2 layers + }) + } + //@TODO add an extra geojsons key with every other one so people can select the others. + // load a tile layer + geojsonLayer.addTo(map); + + console.log('initializing leaflet 1.6.0') + } + })}} +})(jQuery, Drupal, drupalSettings, window.L); \ No newline at end of file diff --git a/src/Plugin/Field/FieldFormatter/StrawberryMapFormatter.php b/src/Plugin/Field/FieldFormatter/StrawberryMapFormatter.php new file mode 100644 index 00000000..d1ffa776 --- /dev/null +++ b/src/Plugin/Field/FieldFormatter/StrawberryMapFormatter.php @@ -0,0 +1,803 @@ +currentUser = $current_user; + $this->entityTypeManager = $entity_type_manager; + } + + /** + * {@inheritdoc} + */ + public static function create( + ContainerInterface $container, + array $configuration, + $plugin_id, + $plugin_definition + ) { + return new static( + $plugin_id, + $plugin_definition, + $configuration['field_definition'], + $configuration['settings'], + $configuration['label'], + $configuration['view_mode'], + $configuration['third_party_settings'], + $container->get('config.factory'), + $container->get('current_user'), + $container->get('entity_type.manager') + ); + } + + + /** + * {@inheritdoc} + */ + public static function defaultSettings() { + return parent::defaultSettings() + [ + 'json_key_source' => 'geographic_location', + 'tilemap_url' => 'https://a.tile.openstreetmap.org/{z}/{x}/{y}.png', + 'tilemap_attribution' => '© OpenStreetMap contributors', + 'mediasource' => [ + 'metadataexposeentity' => 'metadataexposeentity', + ], + 'main_mediasource' => 'metadataexposeentity', + 'metadataexposeentity_source' => NULL, + 'geojsonnodelist_json_key_source' => 'locatedat', + 'geojsonurl_json_key_source' => 'geojson', + 'max_width' => 720, + 'max_height' => 480, + 'max_zoom' => 10, + 'min_zoom' => 2, + 'initial_zoom' => 5, + ]; + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + //@TODO document that 2 base urls are just needed when developing (localhost syndrom) + + $entity = NULL; + if ($this->getSetting('metadataexposeentity_source')) { + $entity = $this->entityTypeManager->getStorage( + 'metadataexpose_entity' + )->load($this->getSetting('metadataexposeentity_source')); + } + $options_for_mainsource = is_array( + $this->getSetting('mediasource') + ) && !empty($this->getSetting('mediasource')) ? $this->getSetting( + 'mediasource' + ) : self::defaultSettings()['mediasource']; + + if (($triggering_element = $form_state->getTriggeringElement( + )) && isset($triggering_element['#ajax']['callback'])) { + // We are getting the actual checkbox value pressed in the parents array. + // so we need to slice by 1 at the end. + // if Ajax class of the triggering element is this class then process + if ($triggering_element['#ajax']['callback'][0] == get_class($this)) { + $parents = array_slice($triggering_element['#parents'], 0, -1); + $options_for_mainsource = $form_state->getValue($parents); + } + } + $all_options_form_source = [ + 'metadataexposeentity' => $this->t( + 'GeoJSON generated by a Metadata Display template' + ), + 'geojsonurl' => $this->t( + 'Strawberryfield JSON Key with one or more GeoJSON URLs' + ), + 'geojsonnodelist' => $this->t( + 'Strawberryfield JSON Key with one or more Node IDs or UUIDs' + ), + ]; + $options_for_mainsource = array_filter($options_for_mainsource); + $options_for_mainsource = array_intersect_key( + $options_for_mainsource, + $all_options_form_source + ); + + // Define #ajax callback. + $ajax = [ + 'callback' => [get_class($this), 'ajaxCallbackMainSource'], + 'wrapper' => 'main-mediasource-ajax-container', + ]; + // Because main media source needs to update its choices based on + // Media Source checked options, we need to recalculate its default + // Value also. + $default_value_main_mediasoruce = ($this->getSetting( + 'main_mediasource' + ) && array_key_exists( + $this->getSetting('main_mediasource'), + $options_for_mainsource + )) ? $this->getSetting('main_mediasource') : reset( + $options_for_mainsource + ); + // We can not use the url type for tilemap_url since the curly brackets don't validate! + // So sad. + $settings_form = [ + 'json_key_source' => [ + '#type' => 'textfield', + '#title' => t('JSON Key or JMESPath search string that needs to exist and not be empty to render a map.'), + '#description' => t('Leave empty to render always a map. If present and value is empty, Map will not be rendered'), + '#default_value' => trim($this->getSetting('json_key_source')), + ], + 'tilemap_url' => [ + '#type' => 'textfield', + '#title' => t('Base Map (Tiles) URL to use on this Map'), + '#description' => t('E.g https://a.tile.openstreetmap.org/{z}/{x}/{y}.png'), + '#default_value' => trim($this->getSetting('tilemap_url')), + '#required' => TRUE, + ], + 'tilemap_attribution' => [ + '#type' => 'textfield', + '#title' => t('Attribution HTML string for the Base Map.'), + '#description' => t('E.g &copy; <a href="https://openstreetmap.org/copyright">OpenStreetMap contributors</a>'), + '#default_value' => trim($this->getSetting('tilemap_attribution')), + '#required' => TRUE, + ], + 'mediasource' => [ + '#type' => 'checkboxes', + '#title' => $this->t('Source for your GeoJSON URLs.'), + '#options' => $all_options_form_source, + '#default_value' => $this->getSetting('mediasource'), + '#required' => TRUE, + '#attributes' => [ + 'data-formatter-selector' => 'mediasource', + ], + '#ajax' => $ajax, + ], + 'main_mediasource' => [ + '#type' => 'select', + '#title' => $this->t( + 'Select which Source will be handled as the primary one.' + ), + '#options' => $options_for_mainsource, + '#default_value' => $default_value_main_mediasoruce, + '#required' => FALSE, + '#prefix' => '
', + '#suffix' => '
', + ], + 'metadataexposeentity_source' => [ + '#type' => 'entity_autocomplete', + '#target_type' => 'metadataexpose_entity', + '#title' => $this->t( + 'Select which Exposed Metadata Endpoint will generate the GeoJSON' + ), + '#description' => $this->t( + 'This value is used for Metadata Exposed Entities and also for Node Lists as Processing source for GeoJSON' + ), + '#selection_handler' => 'default', + '#validate_reference' => TRUE, + '#default_value' => $entity, + '#states' => [ + [ + 'visible' => [ + ':input[data-formatter-selector="mediasource"][value="metadataexposeentity"]' => ['checked' => TRUE], + ] + ], + [ + 'visible' => [ + ':input[data-formatter-selector="mediasource"][value="geojsonnodelist"]' => ['checked' => TRUE], + ] + ] + ], + ], + 'geojsonurl_json_key_source' => [ + '#type' => 'textfield', + '#title' => t( + 'JSON Key from where to fetch one or more GeoJSON URLs. URLs can be external.' + ), + '#default_value' => $this->getSetting('geojsonurl_json_key_source'), + '#states' => [ + 'visible' => [ + ':input[data-formatter-selector="mediasource"][value="geojsonurl"]' => ['checked' => TRUE], + ], + ], + ], + + 'geojsonnodelist_json_key_source' => [ + '#type' => 'textfield', + '#title' => t( + 'JSON Key from where to fetch one or more Nodes. Values can be either NODE IDs (Integers) or UUIDs (Strings). But all of the same type.' + ), + '#default_value' => $this->getSetting( + 'geojsonnodelist_json_key_source' + ), + '#states' => [ + 'visible' => [ + ':input[data-formatter-selector="mediasource"][value="geojsonnodelist"]' => ['checked' => TRUE], + ], + ], + ], + 'max_width' => [ + '#type' => 'number', + '#title' => $this->t('Maximum width'), + '#description' => $this->t('Use 0 to force 100% width'), + '#default_value' => $this->getSetting('max_width'), + '#size' => 5, + '#maxlength' => 5, + '#field_suffix' => $this->t('pixels'), + '#min' => 0, + '#required' => TRUE + ], + 'max_height' => [ + '#type' => 'number', + '#title' => $this->t('Maximum height'), + '#default_value' => $this->getSetting('max_height'), + '#size' => 5, + '#maxlength' => 5, + '#field_suffix' => $this->t('pixels'), + '#min' => 0, + '#required' => TRUE + ], + 'initial_zoom' => [ + '#type' => 'number', + '#title' => $this->t('Initial Zoom'), + '#description' => $this->t('Only applies when a single Point is in the map. When more fit to bounds apply.'), + '#default_value' => $this->getSetting('initial_zoom'), + '#size' => 2, + '#maxlength' => 2, + '#min' => 1, + '#max' => 22, + ], + 'min_zoom' => [ + '#type' => 'number', + '#title' => $this->t('Minimum possible Zoom'), + '#default_value' => $this->getSetting('min_zoom'), + '#size' => 2, + '#maxlength' => 2, + '#min' => 0, + '#max' => 22, + ], + 'max_zoom' => [ + '#type' => 'number', + '#title' => $this->t('Maximum possible Zoom'), + '#default_value' => $this->getSetting('max_zoom'), + '#size' => 2, + '#maxlength' => 2, + '#min' => 0, + '#max' => 22, + ], + ] + parent::settingsForm($form, $form_state); + if (empty($options_for_mainsource)) { + // let's give people a hint of what they are doing wrong + $settings_form['main_mediasource']['#empty_option'] = t( + '- No Source for your GeoJSON Urls. Please check one! -' + ); + + } + return $settings_form; + } + + /** + * Ajax callback. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return array + * An associative array containing entity reference details element. + */ + public static function ajaxCallbackMainSource( + array $form, + FormStateInterface $form_state + ) { + $form_parents = $form_state->getTriggeringElement()['#array_parents']; + $form_parents = array_slice($form_parents, 0, -2); + $form_parents[] = 'main_mediasource'; + return NestedArray::getValue($form, $form_parents); + } + + + /** + * {@inheritdoc} + */ + public function settingsSummary() { + $summary[] = $this->t( + 'Displays Map using the Leaflet and GeoJSON Sources
' + ); + $summary[] = $this->t( + 'JSON Key or JMESPATH that needs to return a value for Map to render: %key', + [ + '%key' => !empty(trim($this->getSetting('json_key_source'))) ? trim($this->getSetting('json_key_source')) : 'No restriction set' + + ] + ); + + $summary[] = $this->t( + 'Base Map (Tiles) URL: %map', + [ + '%map' => $this->getSetting('tilemap_url'), + ] + ); + + $summary[] = $this->t( + 'Attribution for Base map: %attributions', + [ + '%attributions' => $this->getSetting('tilemap_attribution'), + ] + ); + + $main_mediasource = $this->getSetting( + 'main_mediasource' + ) ? $this->getSetting('main_mediasource') : NULL; + if ($this->getSetting('mediasource') && is_array($this->getSetting('mediasource'))) { + $mediasource = $this->getSetting('mediasource'); + foreach ($mediasource as $source => $enabled) { + $on = (string)$enabled; + if ($on == "metadataexposeentity") { + $entity = NULL; + if ($this->getSetting('metadataexposeentity_source')) { + $entity = $this->entityTypeManager->getStorage( + 'metadataexpose_entity' + )->load($this->getSetting('metadataexposeentity_source')); + if ($entity) { + $label = $entity->label(); + $summary[] = $this->t( + 'GeoJSON generated by the "%metadatadisplayentity" Metadata Data Expose Endpoint%primary.', + [ + '%metadatadisplayentity' => $label, + '%primary' => ($main_mediasource == $on) ? '(PRIMARY)' : '', + ] + ); + } + else { + $summary[] = $this->t( + 'GeoJSON generated by a non existing "%metadatadisplayentity" Metadata Data Expose Endpoint%primary. Please correct this.', + [ + '%metadatadisplayentity' => $this->getSetting( + 'metadataexposeentity_source' + ), + '%primary' => ($main_mediasource == $on) ? '(PRIMARY)' : '', + ] + ); + } + } + else { + $summary[] = $this->t( + 'GeoJSON generated by the Metadata Data Expose Endpoint%primary but none set. Please setup this correctly', + [ + '%primary' => ($main_mediasource == $on) ? '(PRIMARY)' : '', + ] + ); + } + continue 1; + } + if ($on == "geojsonurl") { + $summary[] = $this->t( + 'GeoJSON URL fetched from JSON "%geojsonurl_json_key_source" key%primary.', + [ + '%geojsonurl_json_key_source' => $this->getSetting( + 'geojsonurl_json_key_source' + ), + '%primary' => ($main_mediasource == $on) ? '(PRIMARY)' : '', + ] + ); + continue 1; + } + if ($on == "geojsonnodelist") { + $summary[] = $this->t( + 'GeoJSON generated from Node IDs fetched from JSON "%geojsonnodelist_json_key_source" key%primary.', + [ + '%geojsonnodelist_json_key_source' => $this->getSetting( + 'geojsonnodelist_json_key_source' + ), + '%primary' => ($main_mediasource == $on) ? '(PRIMARY)' : '', + ] + ); + continue 1; + } + } + } + else { + $summary[] = $this->t('This formatter still needs to be setup'); + } + + $summary[] = $this->t( + 'Zoom Levels: Min(%min_zoom)|Max(%max_zoom)|Initial(%initial_zoom)', + [ + '%min_zoom' => (int) $this->getSetting('min_zoom'), + '%max_zoom' => $this->getSetting('max_zoom'), + '%initial_zoom' => $this->getSetting('initial_zoom'), + ] + ); + + return array_merge($summary, parent::settingsSummary()); + } + + + /** + * {@inheritdoc} + */ + public function viewElements(FieldItemListInterface $items, $langcode) { + $elements = []; + $max_width = $this->getSetting('max_width'); + $max_width_css = empty($max_width) || $max_width == 0 ? '100%' : $max_width .'px'; + $max_height = $this->getSetting('max_height'); + $mediasource = is_array($this->getSetting('mediasource')) ? $this->getSetting('mediasource') : []; + $main_mediasource = $this->getSetting('main_mediasource'); + + /* @var \Drupal\file\FileInterface[] $files */ + // Fixing the key to extract while coding to 'Media' + + // This little one is a bit different to the Open Seadragon viewer. + // Needs to deal with as type:Image and as type Document + // Since people can setup this to a key we will handle both. + // Main difference is how we generate the IIIF image sequence. + // So we have at least 4 ways. + // For type:Image its pretty much the same as Media Formatter + // For type:Document we will use number of pages as default + // But also allow a Table of Content if such structure exists. + // We also allow a Twig template / Media Display to be used + // To generate an on the Fly geojson. We coded our JS to read from geojsons + // Finally we allow also an geojson URL to be passed. + + $nodeuuid = $items->getEntity()->uuid(); + /* @var \Drupal\strawberryfield\Plugin\Field\FieldType\StrawberryFieldItem $item */ + foreach ($items as $delta => $item) { + $main_property = $item->getFieldDefinition() + ->getFieldStorageDefinition() + ->getMainPropertyName(); + $value = $item->{$main_property}; + if (empty($value)) { + continue; + } + /* @var array $jsondata */ + $jsondata = json_decode($item->value, TRUE); + // @TODO use future flatversion precomputed at field level as a property + $json_error = json_last_error(); + if ($json_error != JSON_ERROR_NONE) { + return $elements[$delta] = ['#markup' => $this->t('ERROR')]; + } + + $json_key = trim($this->getSetting('json_key_source')); + // Check if we have a json key and it returns an actual value + // @TODO if the key has no . (dots) we can simply evaluate as an array key + // That is faster. + if (empty($json_key) || !empty($item->searchPath($json_key))) { + foreach ($mediasource as $iiifsource) { + $pagestrategy = (string)$iiifsource; + switch ($pagestrategy) { + case 'metadataexposeentity': + $geojsons['metadataexposeentity'] = $this->processGeoJsonforMetadataExposeEntity( + $jsondata, + $item + ); + continue 2; + case 'geojsonurl': + $geojsons['geojsonurl'] = $this->processGeoJsonforURL( + $jsondata, + $item + ); + continue 2; + case 'geojsonnodelist': + $geojsons['geojsonnodelist'] = $this->processGeoJsonforNodeList( + $jsondata, + $item + ); + continue 2; + } + } + + // Check which one is our main source and if it really exists + if (isset($geojsons[$main_mediasource]) && !empty($geojsons[$main_mediasource])) { + // Take only the first since we could have more + $main_geojsonurl = array_shift($geojsons[$main_mediasource]); + $all_geojsonurl = array_reduce($geojsons,'array_merge',[]); + } else { + // reduce flattens and applies a merge. Basically we get a simple list. + $all_geojsonurl = array_reduce($geojsons,'array_merge',[]); + $main_geojsonurl = array_shift($all_geojsonurl); + } + + // Only process is we got at least one geojson + if (!empty($main_geojsonurl)) { + + $groupid = 'iiif-' . $item->getName( + ) . '-' . $nodeuuid . '-' . $delta . '-media'; + $htmlid = $groupid; + + $elements[$delta]['media'] = [ + '#type' => 'container', + '#default_value' => $htmlid, + '#attributes' => [ + 'id' => $htmlid, + 'class' => [ + 'strawberry-leaflet-item', + 'leafletViewer', + 'field-iiif', + 'container', + ], + 'style' => "width:{$max_width_css}; height:{$max_height}px", + 'width' => $max_width, + 'height' => $max_height, + ], + ]; + + // get the URL to our Metadata Expose Endpoint, we will get a string here. + + $elements[$delta]['media']['#attributes']['data-iiif-infojson'] = ''; + $elements[$delta]['media']['#attached']['drupalSettings']['format_strawberryfield']['leaflet'][$htmlid]['nodeuuid'] = $nodeuuid; + $elements[$delta]['media']['#attached']['drupalSettings']['format_strawberryfield']['leaflet'][$htmlid]['geojsonurl'] = $main_geojsonurl; + $elements[$delta]['media']['#attached']['drupalSettings']['format_strawberryfield']['leaflet'][$htmlid]['geojsonother'] = is_array($all_geojsonurl) ? $all_geojsonurl : []; + $elements[$delta]['media']['#attached']['drupalSettings']['format_strawberryfield']['leaflet'][$htmlid]['width'] = $max_width_css; + $elements[$delta]['media']['#attached']['drupalSettings']['format_strawberryfield']['leaflet'][$htmlid]['height'] = max( + $max_height, + 480 + ); + $elements[$delta]['media']['#attached']['drupalSettings']['format_strawberryfield']['leaflet'][$htmlid]['maxzoom'] = $this->getSetting('max_zoom'); + $elements[$delta]['media']['#attached']['drupalSettings']['format_strawberryfield']['leaflet'][$htmlid]['minzoom'] = $this->getSetting('min_zoom'); + $elements[$delta]['media']['#attached']['drupalSettings']['format_strawberryfield']['leaflet'][$htmlid]['initialzoom'] = $this->getSetting('initial_zoom'); + + $elements[$delta]['media']['#attached']['drupalSettings']['format_strawberryfield']['leaflet'][$htmlid]['tilemap_url'] = $this->getSetting('tilemap_url'); + $elements[$delta]['media']['#attached']['drupalSettings']['format_strawberryfield']['leaflet'][$htmlid]['tilemap_attribution'] = $this->getSetting('tilemap_attribution'); + $elements[$delta]['#attached']['library'][] = 'format_strawberryfield/leaflet_strawberry'; + if (isset($item->_attributes)) { + $elements[$delta] += ['#attributes' => []]; + $elements[$delta]['#attributes'] += $item->_attributes; + // Unset field item attributes since they have been included in the + // formatter output and should not be rendered in the field template. + } + } + } + } + unset($item->_attributes); + return $elements; + } + + /** + * Generates URL string for a Twig generated geojson for the current Node. + * + * @param array $jsondata + * @param \Drupal\Core\Field\FieldItemInterface $item + * @return array + * A List of URLs pointing to a IIIF geojson for this node. + * We are using an array even if we only return one + * to match other processgeojson Functions and have a single way + * of Processing them. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + */ + public function processGeoJsonforMetadataExposeEntity( + array $jsondata, + FieldItemInterface $item + ) { + $entity = NULL; + $nodeuuid = $item->getEntity()->uuid(); + $geojsons = []; + + if ($this->getSetting('metadataexposeentity_source' + )) { + /* @var $entity \Drupal\format_strawberryfield\Entity\MetadataExposeConfigEntity */ + $entity = $this->entityTypeManager->getStorage( + 'metadataexpose_entity' + )->load($this->getSetting('metadataexposeentity_source')); + if ($entity) { + $url = $entity->getUrlForItemFromNodeUUID($nodeuuid, TRUE); + $geojsons[] = $url; + } + } + return $geojsons; + } + + /** + * Fetches geojson URLs from a JSON Key. + * + * @param array $jsondata + * @param \Drupal\Core\Field\FieldItemInterface $item + * @return array + * A List of URLs pointing to a IIIF geojson for this node. + * We are using an array even if we only return one + * to match other processgeojson Functions and have a single way + * of Processing them. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + */ + public function processGeoJsonforURL( + array $jsondata, + FieldItemInterface $item + ) { + + $geojsons = []; + + if ($this->getSetting('geojsonurl_json_key_source' + )) { + $jsonkey = $this->getSetting('geojsonurl_json_key_source'); + + if (isset($jsondata[$jsonkey])) { + if (is_array($jsondata[$jsonkey])) { + foreach ($jsondata[$jsonkey] as $url) { + if (is_string($url) && UrlHelper::isValid($url, TRUE)) { + $geojsons[] = $url; + } + } + } + else { + if (is_string($jsondata[$jsonkey]) && UrlHelper::isValid( + $jsondata[$jsonkey], + TRUE + )) { + $geojsons[] = $jsondata[$jsonkey]; + } + } + } + } + return $geojsons; + } + + /** + * Generates geojson URLs from a JSON Key containing a list of nodes. + * + * This function reuses 'metadataexposeentity_json_key_source' + * + * @param array $jsondata + * @param \Drupal\Core\Field\FieldItemInterface $item + * @return array + * A List of URLs pointing to a IIIF geojson for this node. + * We are using an array even if we only return one + * to match other processgeojson Functions and have a single way + * of Processing them. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + */ + public function processGeoJsonforNodeList( + array $jsondata, + FieldItemInterface $item + ) { + $geojsons = []; + $cleannodelist = []; + if ($this->getSetting('geojsonnodelist_json_key_source') && $this->getSetting('metadataexposeentity_source')) { + $jsonkey = $this->getSetting('geojsonnodelist_json_key_source'); + $entity = $this->entityTypeManager->getStorage( + 'metadataexpose_entity' + )->load($this->getSetting('metadataexposeentity_source')); + if ($entity) { + $access_manager = \Drupal::service('access_manager'); + if (isset($jsondata[$jsonkey])) { + if (is_array($jsondata[$jsonkey])) { + $cleannodelist = []; + foreach ($jsondata[$jsonkey] as $nodeid) { + if (is_integer($nodeid)) { + $cleannodelist[] = $nodeid; + } + } + } + else { + if (is_integer($jsondata[$jsonkey])) { + $cleannodelist[] = $jsondata[$jsonkey]; + } + } + + foreach ($this->entityTypeManager->getStorage('node')->loadMultiple( + $cleannodelist + ) as $node) { + $has_access = $access_manager->checkNamedRoute( + 'format_strawberryfield.metadatadisplay_caster', + [ + 'node' => $node->uuid(), + 'metadataexposeconfig_entity' => $entity->id(), + 'format' => 'geojson.json' + ], + $this->currentUser + ); + if ($has_access) { + $geojsons[] = $entity->getUrlForItemFromNodeUUID( + $node->uuid(), + TRUE + ); + } + } + } + } + } + return $geojsons; + } +}