From b206d10029acae036b4ea1d10e071f88ae2127a6 Mon Sep 17 00:00:00 2001 From: Pascal Barth Date: Fri, 11 Oct 2024 16:04:27 +0200 Subject: [PATCH] PB-1028 : do not load a COG while importing it, only read its metadata renaming the layer type from GeoTIFF (which we shouldn't support much, too big of a risk of users loading very large files and crashing the app because of lack of memory) and clarifying that we support COG (Cloud Optimized GeoTIFF) Adding a dedicated parser for COG files improvements made during this work package : - read the no data value from the COG metadata (instead of arbitrarily setting it to zero) - re-load COG extent if the layer was added at startup through a URL param --- ...js => CloudOptimizedGeoTIFFLayer.class.js} | 44 ++++++++----- src/api/layers/LayerTypes.enum.js | 2 +- src/modules/i18n/locales/de.json | 8 +-- src/modules/i18n/locales/en.json | 8 +-- src/modules/i18n/locales/fr.json | 8 +-- src/modules/i18n/locales/it.json | 8 +-- src/modules/i18n/locales/rm.json | 8 +-- ...GeoTIFF.vue => OpenLayersCOGTiffLayer.vue} | 14 ++-- .../openlayers/OpenLayersInternalLayer.vue | 6 +- .../CloudOptimizedGeoTIFFParser.class.js | 65 +++++++++++++++++++ .../advancedTools/ImportFile/parser/index.js | 8 ++- .../storeSync/LayerParamConfig.class.js | 7 +- src/store/index.js | 2 + src/store/plugins/load-cog-extent.plugin.js | 51 +++++++++++++++ 14 files changed, 190 insertions(+), 49 deletions(-) rename src/api/layers/{GeoTIFFLayer.class.js => CloudOptimizedGeoTIFFLayer.class.js} (53%) rename src/modules/map/components/openlayers/{OpenLayersGeoTIFF.vue => OpenLayersCOGTiffLayer.vue} (75%) create mode 100644 src/modules/menu/components/advancedTools/ImportFile/parser/CloudOptimizedGeoTIFFParser.class.js create mode 100644 src/store/plugins/load-cog-extent.plugin.js diff --git a/src/api/layers/GeoTIFFLayer.class.js b/src/api/layers/CloudOptimizedGeoTIFFLayer.class.js similarity index 53% rename from src/api/layers/GeoTIFFLayer.class.js rename to src/api/layers/CloudOptimizedGeoTIFFLayer.class.js index bebc437e2..7efb123ac 100644 --- a/src/api/layers/GeoTIFFLayer.class.js +++ b/src/api/layers/CloudOptimizedGeoTIFFLayer.class.js @@ -6,32 +6,41 @@ import LayerTypes from '@/api/layers/LayerTypes.enum' * Metadata for an external Cloud-Optimized GeoTIFF layer * * @WARNING DON'T USE GETTER AND SETTER ! Instances of this class will be used a Vue 3 reactive - * object which SHOULD BE plain javascript object ! For convenience we use class instances but this + * object which SHOULD BE plain javascript object ! For convenience, we use class instances but this * has some limitations and javascript class getter and setter are not correctly supported which * introduced subtle bugs. As rule of thumb we should avoid any public methods with side effects on * properties, properties should change be changed either by the constructor or directly by setting * them, not through a functions that updates other properties as it can lead to subtle bugs due * to Vue reactivity engine. */ -export default class GeoTIFFLayer extends AbstractLayer { +export default class CloudOptimizedGeoTIFFLayer extends AbstractLayer { /** - * @param {String} geoTIFFConfig.fileSource The URL to access the GeoTIFF data. - * @param {Boolean} [geoTIFFConfig.visible=true] If the layer is visible on the map (or hidden). + * @param {String} cogConfig.fileSource The URL to access the COG data. + * @param {Boolean} [cogConfig.visible=true] If the layer is visible on the map (or hidden). * When `null` is given, then it uses the default value. Default is `true` - * @param {Number} [geoTIFFConfig.opacity=1.0] The opacity of this layer, between 0.0 - * (transparent) and 1.0 (opaque). When `null` is given, then it uses the default value. - * Default is `1.0` - * @param {String | null} [geoTIFFConfig.data=null] Data/content of the GeoTIFF file, as a - * string. Default is `null` - * @throws InvalidLayerDataError if no `geoTIFFConfig` is given or if it is invalid + * @param {Number} [cogConfig.opacity=1.0] The opacity of this layer, between 0.0 (transparent) + * and 1.0 (opaque). When `null` is given, then it uses the default value. Default is `1.0` + * @param {String | null} [cogConfig.data=null] Data/content of the COG file, as a string. + * Default is `null` + * @param {Number | null} [cogConfig.noDataValue] Which value will be describing the absence of + * data in this COG. Will be used to create transparency whenever this value is present. + * @param {[Number, Number, Number, Number] | null} [cogConfig.extent] The extent of this COG. + * @throws InvalidLayerDataError if no `cogConfig` is given or if it is invalid */ - constructor(geoTIFFConfig) { - if (!geoTIFFConfig) { - throw new InvalidLayerDataError('Missing GeoTIFF layer data', geoTIFFConfig) + constructor(cogConfig) { + if (!cogConfig) { + throw new InvalidLayerDataError('Missing COG layer data', cogConfig) } - const { fileSource = null, visible = true, opacity = 1.0, data = null } = geoTIFFConfig + const { + fileSource = null, + visible = true, + opacity = 1.0, + data = null, + noDataValue = null, + extent = null, + } = cogConfig if (fileSource === null) { - throw new InvalidLayerDataError('Missing GeoTIFF file source', geoTIFFConfig) + throw new InvalidLayerDataError('Missing COG file source', cogConfig) } const isLocalFile = !fileSource.startsWith('http') const attributionName = isLocalFile ? fileSource : new URL(fileSource).hostname @@ -41,7 +50,7 @@ export default class GeoTIFFLayer extends AbstractLayer { super({ name: fileName, id: fileSource, - type: LayerTypes.GEOTIFF, + type: LayerTypes.COG, baseUrl: fileSource, opacity: opacity ?? 1.0, visible: visible ?? true, @@ -52,7 +61,8 @@ export default class GeoTIFFLayer extends AbstractLayer { }) this.isLocalFile = isLocalFile this.fileSource = fileSource - this.isLoading = false this.data = data + this.noDataValue = noDataValue + this.extent = extent } } diff --git a/src/api/layers/LayerTypes.enum.js b/src/api/layers/LayerTypes.enum.js index bacf20ae5..c774fc7b5 100644 --- a/src/api/layers/LayerTypes.enum.js +++ b/src/api/layers/LayerTypes.enum.js @@ -11,6 +11,6 @@ const LayerTypes = { GPX: 'GPX', VECTOR: 'VECTOR', GROUP: 'GROUP', - GEOTIFF: 'GEOTIFF', + COG: 'COG', } export default LayerTypes diff --git a/src/modules/i18n/locales/de.json b/src/modules/i18n/locales/de.json index ae2b2986a..0f5e992bc 100644 --- a/src/modules/i18n/locales/de.json +++ b/src/modules/i18n/locales/de.json @@ -172,7 +172,7 @@ "drawing_attached": "Zeichnung als Anhang hinzugefügt", "drawing_too_large": "Ihre Zeichnung ist zu gross, entfernen Sie einige Details.", "drop_invalid_url": "URL ist ungültig.", - "drop_me_here": "Datei hier ablegen (KML, KMZ, GPX, GeoTIFF)", + "drop_me_here": "Datei hier ablegen (KML, KMZ, GPX, COG)", "duplicate_layer": "Karte duplizieren", "east": "Ost", "ech": "Geokatalog", @@ -293,8 +293,8 @@ "import": "Importieren", "import_file": "Datei importieren", "import_file_succeeded": "Erfolg", - "import_file_tooltip": "Importieren eine externe KML, KMZ, GPX oder GeoTIFF-Datei", - "import_file_url_placeholder": "GPX KML KMZ GeoTIFF URL", + "import_file_tooltip": "Importieren eine externe KML, KMZ, GPX oder COG-Datei", + "import_file_url_placeholder": "GPX KML KMZ COG URL", "import_kml": "KML Import", "import_maps": "Karten importieren", "import_maps_tooltip": "Importieren Sie externe WMTS- oder WMS-Datenquellen", @@ -309,7 +309,7 @@ "inspire_service_link_label": "geo.admin.ch", "invalid_email": "ungültige E-Mail", "invalid_file": "Datei ungültig", - "invalid_import_file_error": "Ungültige Datei, nur KML, KMZ, GPX oder GeoTIFF-Dateien werden unterstützt", + "invalid_import_file_error": "Ungültige Datei, nur KML, KMZ, GPX oder COG-Dateien werden unterstützt", "invalid_url": "URL ist ungültig.", "invalid_wms_capabilities": "Ungültige WMS-Capabilities-Daten", "invalid_wmts_capabilities": "Ungültige WMTS-Capabilities-Daten", diff --git a/src/modules/i18n/locales/en.json b/src/modules/i18n/locales/en.json index 594556212..620581a64 100644 --- a/src/modules/i18n/locales/en.json +++ b/src/modules/i18n/locales/en.json @@ -172,7 +172,7 @@ "drawing_attached": "Drawing added as attachment", "drawing_too_large": "Your drawing is too large, remove some features", "drop_invalid_url": "URL is not valid.", - "drop_me_here": "Drop file here (KML, KMZ, GPX, GeoTIFF)", + "drop_me_here": "Drop file here (KML, KMZ, GPX, COG)", "duplicate_layer": "Duplicate map", "east": "East", "ech": "Geocatalog", @@ -293,8 +293,8 @@ "import": "Import", "import_file": "Import file", "import_file_succeeded": "Success", - "import_file_tooltip": "Import an external KML, KMZ, GPX or GeoTIFF file", - "import_file_url_placeholder": "GPX KML KMZ GeoTIFF URL", + "import_file_tooltip": "Import an external KML, KMZ, GPX or COG file", + "import_file_url_placeholder": "GPX KML KMZ COG URL", "import_kml": "KML import", "import_maps": "Import maps", "import_maps_tooltip": "Import external WMTS WMS sources", @@ -309,7 +309,7 @@ "inspire_service_link_label": "geo.admin.ch", "invalid_email": "Invalid email", "invalid_file": "Invalid file", - "invalid_import_file_error": "Invalid file, only KML, KMZ, GPX or GeoTIFF file are supported", + "invalid_import_file_error": "Invalid file, only KML, KMZ, GPX or COG file are supported", "invalid_url": "URL is not valid.", "invalid_wms_capabilities": "Invalid WMS Capabilities", "invalid_wmts_capabilities": "Invalid WMTS Capabilities", diff --git a/src/modules/i18n/locales/fr.json b/src/modules/i18n/locales/fr.json index d751324e6..28509e3b6 100644 --- a/src/modules/i18n/locales/fr.json +++ b/src/modules/i18n/locales/fr.json @@ -172,7 +172,7 @@ "drawing_attached": "Dessin ajouté en pièce jointe", "drawing_too_large": "Ton dessin est trop grand, enlève quelques éléments", "drop_invalid_url": "URL non valide.", - "drop_me_here": "Déposer le fichier ici (KML, KMZ, GPX, GeoTIFF)", + "drop_me_here": "Déposer le fichier ici (KML, KMZ, GPX, COG)", "duplicate_layer": "Duplication de carte", "east": "Est", "ech": "Géocatalogue", @@ -293,8 +293,8 @@ "import": "Importer", "import_file": "Importer un fichier", "import_file_succeeded": "Succès", - "import_file_tooltip": "Importer un fichier KML, KMZ, GPX ou GeoTIFF externe", - "import_file_url_placeholder": "GPX KML KMZ GeoTIFF URL", + "import_file_tooltip": "Importer un fichier KML, KMZ, GPX ou COG externe", + "import_file_url_placeholder": "GPX KML KMZ COG URL", "import_kml": "KML import", "import_maps": "Importer des cartes", "import_maps_tooltip": "Importer des données WMTS WMS externes", @@ -309,7 +309,7 @@ "inspire_service_link_label": "geo.admin.ch", "invalid_email": "e-mail invalide", "invalid_file": "Fichier invalide, seuls les fichiers KML ou GPX sont pris en charge", - "invalid_import_file_error": "Fichier invalide, seuls les fichiers KML, KMZ, GPX ou GeoTIFF sont pris en charge", + "invalid_import_file_error": "Fichier invalide, seuls les fichiers KML, KMZ, GPX ou COG sont pris en charge", "invalid_url": "URL non valide.", "invalid_wms_capabilities": "Données WMS Capabilities invalides", "invalid_wmts_capabilities": "Données WMTS Capabilities invalides", diff --git a/src/modules/i18n/locales/it.json b/src/modules/i18n/locales/it.json index 5c4327bb9..eca2f3f57 100644 --- a/src/modules/i18n/locales/it.json +++ b/src/modules/i18n/locales/it.json @@ -172,7 +172,7 @@ "drawing_attached": "Disegno aggiunto come allegato", "drawing_too_large": "Il suo disegno è troppo grande, rimuova alcuni elementi", "drop_invalid_url": "URL non valido", - "drop_me_here": "Lasciare qui il file (KML, KMZ, GPX, GeoTIFF)", + "drop_me_here": "Lasciare qui il file (KML, KMZ, GPX, COG)", "duplicate_layer": "Mappa duplicata", "east": "Est", "ech": "Geocatalogo", @@ -293,8 +293,8 @@ "import": "Importare", "import_file": "Importare file", "import_file_succeeded": "Successo", - "import_file_tooltip": "Importa un file KML, KMZ, GPX o GeoTIFF esterno", - "import_file_url_placeholder": "GPX KML KMZ GeoTIFF URL", + "import_file_tooltip": "Importa un file KML, KMZ, GPX o COG esterno", + "import_file_url_placeholder": "GPX KML KMZ COG URL", "import_kml": "Importare KML", "import_maps": "Importa mappe", "import_maps_tooltip": "Importare dati WMTS WMS esterni", @@ -309,7 +309,7 @@ "inspire_service_link_label": "geo.admin.ch", "invalid_email": "e-mail non valido", "invalid_file": "file non valido", - "invalid_import_file_error": "File non valido, sono supportati solo file KML, KMZ, GPX o GeoTIFF", + "invalid_import_file_error": "File non valido, sono supportati solo file KML, KMZ, GPX o COG", "invalid_url": "URL non valido", "invalid_wms_capabilities": "Dati WMS Capabilities non validi", "invalid_wmts_capabilities": "Dati WMTS Capabilities non validi", diff --git a/src/modules/i18n/locales/rm.json b/src/modules/i18n/locales/rm.json index 019a1e735..bb50f5242 100644 --- a/src/modules/i18n/locales/rm.json +++ b/src/modules/i18n/locales/rm.json @@ -172,7 +172,7 @@ "drawing_attached": "Dissegn è agiuntà sco agiunta.", "drawing_too_large": "Tes dissegn è memia grond, stizza intgins detagls", "drop_invalid_url": "URL è nunvalid", - "drop_me_here": "Dar giu la datoteca (KML, KMZ, GPX, GeoTIFF)", + "drop_me_here": "Dar giu la datoteca (KML, KMZ, GPX, COG)", "duplicate_layer": "Mapa duplicada", "east": "ost", "ech": "Catalog da geodatas", @@ -291,8 +291,8 @@ "import": "Importar", "import_file": "Importar ina datoteca", "import_file_succeeded": "Success", - "import_file_tooltip": "Importar ina datoteca esterna KML, KMZ, GPX u GeoTIFF", - "import_file_url_placeholder": "GPX KML KMZ GeoTIFF URL", + "import_file_tooltip": "Importar ina datoteca esterna KML, KMZ, GPX u COG", + "import_file_url_placeholder": "GPX KML KMZ COG URL", "import_kml": "Importar KML", "import_maps": "Importar charta", "import_maps_tooltip": "Agiuntar in unitad da datas WMTS WMS externa", @@ -307,7 +307,7 @@ "inspire_service_link_label": "geo.admin.ch", "invalid_email": "ungültige E-Mail", "invalid_file": "Datotecadad nun vala", - "invalid_import_file_error": "Datotecadad nun vala, èn ancum suttatschadas ils files KML, KMZ, GPX u GeoTIFF.", + "invalid_import_file_error": "Datotecadad nun vala, èn ancum suttatschadas ils files KML, KMZ, GPX u COG.", "invalid_url": "URL è nunvalid", "invalid_wms_capabilities": "Dadis WMS Capabilitiesinvalid", "invalid_wmts_capabilities": "Dadis WMTS Capabilitiesinvalid", diff --git a/src/modules/map/components/openlayers/OpenLayersGeoTIFF.vue b/src/modules/map/components/openlayers/OpenLayersCOGTiffLayer.vue similarity index 75% rename from src/modules/map/components/openlayers/OpenLayersGeoTIFF.vue rename to src/modules/map/components/openlayers/OpenLayersCOGTiffLayer.vue index 2bb021613..d5aadc21c 100644 --- a/src/modules/map/components/openlayers/OpenLayersGeoTIFF.vue +++ b/src/modules/map/components/openlayers/OpenLayersCOGTiffLayer.vue @@ -3,12 +3,12 @@ import WebGLTileLayer from 'ol/layer/WebGLTile' import GeoTIFFSource from 'ol/source/GeoTIFF' import { computed, inject, toRefs, watch } from 'vue' -import GeoTIFFLayer from '@/api/layers/GeoTIFFLayer.class' +import CloudOptimizedGeoTIFFLayer from '@/api/layers/CloudOptimizedGeoTIFFLayer.class.js' import useAddLayerToMap from '@/modules/map/components/openlayers/utils/useAddLayerToMap.composable' const props = defineProps({ geotiffConfig: { - type: GeoTIFFLayer, + type: CloudOptimizedGeoTIFFLayer, required: true, }, parentLayerOpacity: { @@ -23,11 +23,17 @@ const props = defineProps({ const { geotiffConfig, parentLayerOpacity, zIndex } = toRefs(props) const olMap = inject('olMap') +const noDataValue = computed(() => geotiffConfig.value.noDataValue ?? 0) const source = computed(() => { + const base = { + nodata: noDataValue.value, + } if (geotiffConfig.value.isLocalFile) { - return { blob: geotiffConfig.value.data } + base.blob = geotiffConfig.value.data + } else { + base.url = geotiffConfig.value.fileSource } - return { url: geotiffConfig.value.fileSource } + return base }) const opacity = computed(() => parentLayerOpacity.value ?? geotiffConfig.value.opacity) diff --git a/src/modules/map/components/openlayers/OpenLayersInternalLayer.vue b/src/modules/map/components/openlayers/OpenLayersInternalLayer.vue index 7ffc4d073..438768344 100644 --- a/src/modules/map/components/openlayers/OpenLayersInternalLayer.vue +++ b/src/modules/map/components/openlayers/OpenLayersInternalLayer.vue @@ -9,9 +9,9 @@ import { useStore } from 'vuex' import AbstractLayer from '@/api/layers/AbstractLayer.class' import LayerTypes from '@/api/layers/LayerTypes.enum' +import OpenLayersCOGTiffLayer from '@/modules/map/components/openlayers/OpenLayersCOGTiffLayer.vue' import OpenLayersExternalWMTSLayer from '@/modules/map/components/openlayers/OpenLayersExternalWMTSLayer.vue' import OpenLayersGeoJSONLayer from '@/modules/map/components/openlayers/OpenLayersGeoJSONLayer.vue' -import OpenLayersGeoTIFF from '@/modules/map/components/openlayers/OpenLayersGeoTIFF.vue' import OpenLayersGPXLayer from '@/modules/map/components/openlayers/OpenLayersGPXLayer.vue' import OpenLayersKMLLayer from '@/modules/map/components/openlayers/OpenLayersKMLLayer.vue' import OpenLayersVectorLayer from '@/modules/map/components/openlayers/OpenLayersVectorLayer.vue' @@ -130,8 +130,8 @@ function shouldAggregateSubLayerBeVisible(subLayer) { :parent-layer-opacity="parentLayerOpacity" :z-index="zIndex" /> - coordinateSystem.epsgNumber === imageGeoKey + ) + if (!cogProjection) { + throw new UnknownProjectionError( + `Unknown projection found in COG EPSG:${imageGeoKey}`, + `EPSG:${imageGeoKey}` + ) + } + const cogExtent = firstImage.getBoundingBox() + const intersection = getExtentIntersectionWithCurrentProjection( + cogExtent, + cogProjection, + currentProjection + ) + if (!intersection) { + throw new OutOfBoundsError(`COG is out of bounds of current projection: ${cogExtent}`) + } + return new CloudOptimizedGeoTIFFLayer({ + fileSource: this.isLocalFile(fileSource) ? fileSource.name : fileSource, + visible: true, + opacity: 1.0, + data: fileSource, + noDataValue: firstImage.getGDALNoData(), + extent: flattenExtent(intersection), + }) + } + + async parseUrl(fileUrl, currentProjection, options) { + return this.parseCOGLayer(fileUrl, await fromUrl(fileUrl), currentProjection, options) + } + + async parseLocalFile(file, currentProjection) { + return this.parseCOGLayer(file, await fromBlob(file), currentProjection) + } +} diff --git a/src/modules/menu/components/advancedTools/ImportFile/parser/index.js b/src/modules/menu/components/advancedTools/ImportFile/parser/index.js index e3df410e3..3fbb592f2 100644 --- a/src/modules/menu/components/advancedTools/ImportFile/parser/index.js +++ b/src/modules/menu/components/advancedTools/ImportFile/parser/index.js @@ -2,12 +2,18 @@ import axios from 'axios' import { getContentThroughServiceProxy } from '@/api/file-proxy.api.js' import AbstractLayer from '@/api/layers/AbstractLayer.class' +import { CloudOptimizedGeoTIFFParser } from '@/modules/menu/components/advancedTools/ImportFile/parser/CloudOptimizedGeoTIFFParser.class' import GPXParser from '@/modules/menu/components/advancedTools/ImportFile/parser/GPXParser.class' import { KMLParser } from '@/modules/menu/components/advancedTools/ImportFile/parser/KMLParser.class' import KMZParser from '@/modules/menu/components/advancedTools/ImportFile/parser/KMZParser.class' import log from '@/utils/logging.js' -const allParsers = [new KMZParser(), new KMLParser(), new GPXParser()] +const allParsers = [ + new CloudOptimizedGeoTIFFParser(), + new KMZParser(), + new KMLParser(), + new GPXParser(), +] /** * @param {Object} config diff --git a/src/router/storeSync/LayerParamConfig.class.js b/src/router/storeSync/LayerParamConfig.class.js index 47d1d6a8c..b7acf3a06 100644 --- a/src/router/storeSync/LayerParamConfig.class.js +++ b/src/router/storeSync/LayerParamConfig.class.js @@ -1,9 +1,9 @@ import { getStandardValidationResponse } from '@/api/errorQueues.api' import getFeature from '@/api/features/features.api' +import CloudOptimizedGeoTIFFLayer from '@/api/layers/CloudOptimizedGeoTIFFLayer.class.js' import ExternalWMSLayer from '@/api/layers/ExternalWMSLayer.class' import ExternalWMTSLayer from '@/api/layers/ExternalWMTSLayer.class' import GeoAdminWMSLayer from '@/api/layers/GeoAdminWMSLayer.class' -import GeoTIFFLayer from '@/api/layers/GeoTIFFLayer.class.js' import GPXLayer from '@/api/layers/GPXLayer.class' import KMLLayer from '@/api/layers/KMLLayer.class' import LayerTypes from '@/api/layers/LayerTypes.enum' @@ -76,13 +76,14 @@ export function createLayerObject(parsedLayer, currentLayer, store, featuresRequ } else { // we can't re-load GPX files loaded through a file import; this GPX file is ignored } - } else if (parsedLayer.type === LayerTypes.GEOTIFF) { + } else if (parsedLayer.type === LayerTypes.COG) { // format is GEOTIFF|FILE_URL if (parsedLayer.baseUrl.startsWith('http')) { - layer = new GeoTIFFLayer({ + layer = new CloudOptimizedGeoTIFFLayer({ fileSource: parsedLayer.baseUrl, visible: parsedLayer.visible, opacity: parsedLayer.opacity ?? defaultOpacity, + isLoading: false, }) } } diff --git a/src/store/index.js b/src/store/index.js index 620faaee8..106b71d45 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -21,6 +21,7 @@ import appReadinessPlugin from '@/store/plugins/app-readiness.plugin' import clickOnMapManagementPlugin from '@/store/plugins/click-on-map-management.plugin' import loadExternalLayerAttributes from '@/store/plugins/external-layers.plugin' import geolocationManagementPlugin from '@/store/plugins/geolocation-management.plugin' +import loadCOGExtent from '@/store/plugins/load-cog-extent.plugin' import loadGeojsonStyleAndData from '@/store/plugins/load-geojson-style-and-data.plugin' import loadGpxDataAndMetadata from '@/store/plugins/load-gpx-data.plugin' import loadKmlDataAndMetadata from '@/store/plugins/load-kml-kmz-data.plugin' @@ -50,6 +51,7 @@ const store = createStore({ loadGeojsonStyleAndData, loadKmlDataAndMetadata, loadGpxDataAndMetadata, + loadCOGExtent, ], modules: { app, diff --git a/src/store/plugins/load-cog-extent.plugin.js b/src/store/plugins/load-cog-extent.plugin.js new file mode 100644 index 000000000..9b7ec6801 --- /dev/null +++ b/src/store/plugins/load-cog-extent.plugin.js @@ -0,0 +1,51 @@ +import { toValue } from 'vue' + +import CloudOptimizedGeoTIFFLayer from '@/api/layers/CloudOptimizedGeoTIFFLayer.class' +import { CloudOptimizedGeoTIFFParser } from '@/modules/menu/components/advancedTools/ImportFile/parser/CloudOptimizedGeoTIFFParser.class' + +const cogParser = new CloudOptimizedGeoTIFFParser() + +async function loadExtentAndUpdateLayer(store, layer) { + const layerWithExtent = await cogParser.parse( + { + fileSource: layer.fileSource, + currentProjection: toValue(store.state.position.projection), + }, + { + allowServiceProxy: false, + } + ) + store.dispatch('updateLayer', { + layerId: layer.id, + values: { + extent: layerWithExtent.extent, + }, + }) +} + +/** + * COG loaded through a URL param at startup didn't go through the file parser that loads the extent + * of the COG from the file. + * + * This plugin aims to do just that, check if any added COG is missing its extent, and if so run the + * file parser on it to extract this extent. + * + * @param {Vuex.Store} store + */ +export default function loadCOGExtent(store) { + store.subscribe((mutation) => { + const addLayerSubscriber = (layer) => { + if (layer instanceof CloudOptimizedGeoTIFFLayer && !layer.extent) { + loadExtentAndUpdateLayer(store, layer) + } + } + if (mutation.type === 'addLayer') { + addLayerSubscriber(mutation.payload.layer) + } + if (mutation.type === 'setLayers') { + mutation.payload.layers?.forEach((layer) => { + addLayerSubscriber(layer) + }) + } + }) +}