diff --git a/docs/geotiff.md b/docs/geotiff.md index 625abb82..5075d75a 100644 --- a/docs/geotiff.md +++ b/docs/geotiff.md @@ -16,10 +16,10 @@ What is required by back-ends to give users an ideal experience with GeoTiff ima 5. The [`PhotometricInterpretation`](https://www.awaresystems.be/imaging/tiff/tifftags/photometricinterpretation.html) of the image should be set to `1` (BlackIsZero) if an RGB interpretation is not clear. If RGB is set as interpretation (`2`) and you have more than 3 samples per pixel ([`SamplesPerPixel`](https://www.awaresystems.be/imaging/tiff/tifftags/samplesperpixel.html)), the [`ExtraSamples`](https://www.awaresystems.be/imaging/tiff/tifftags/extrasamples.html) should be set. 6. [`ColorMap`](https://www.awaresystems.be/imaging/tiff/tifftags/colormap.html)s are supported. 7. For batch jobs, the STAC metadata is recommended to contain per asset: - 1. The no-data value either in `file:nodata` (deprecated) or in `nodata` in `raster:bands` - 2. The `minimum` and `maximum` values per band in the `statistics` object in `raster:bands` - 3. A band `name` either in `raster:bands` (unspecified) or `eo:bands` - 4. The projection in `proj:epsg` (recommended), `proj:wkt2` (not well suported by OpenLayers) or `proj:proj4` (deprecated by STAC) + 1. The no-data value either in `file:nodata` (deprecated) or in `nodata` in `bands` + 2. The `minimum` and `maximum` values per band in the `statistics` object in `bands` + 3. A band `name` in `bands` + 4. The projection in `proj:code` (recommended), `proj:wkt2` (not well suported by OpenLayers) or `proj:proj4` (deprecated by STAC) 5. The `type` must be set to the corresponding media type (see below) 8. For synchronous execution, the `Content-Type` in the header of the response must be set to the corresponding media type (see below) diff --git a/package.json b/package.json index 53bf8923..d911664d 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,10 @@ "@musement/iso-duration": "^1.0.0", "@openeo/js-client": "^2.6.0", "@openeo/js-commons": "^1.4.1", - "@openeo/js-processgraphs": "^1.3.0", - "@openeo/vue-components": "^2.16.0", + "@openeo/js-processgraphs": "^1.4.0", + "@openeo/vue-components": "^2.17.0", + "@radiantearth/stac-fields": "^1.5.0-beta.2", + "@radiantearth/stac-migrate": "^2.0.0-beta.1", "@tmcw/togeojson": "^5.5.0", "ajv": "^6.12.6", "axios": "^1.0.0", @@ -62,7 +64,7 @@ "core-js": "^3.7.0", "jsonlint-mod": "^1.7.6", "luxon": "^2.4.0", - "node-polyfill-webpack-plugin": "^2.0.0", + "node-polyfill-webpack-plugin": "^4.0.0", "ol": "^9.2.0", "ol-ext": "^4.0.21", "proj4": "^2.7.5", diff --git a/src/components/JobPanel.vue b/src/components/JobPanel.vue index 7ef02054..42cd43b0 100644 --- a/src/components/JobPanel.vue +++ b/src/components/JobPanel.vue @@ -29,6 +29,7 @@ import Utils from '../utils.js'; import { Job } from '@openeo/js-client'; import { cancellableRequest, showCancellableRequestError, CancellableRequestError } from './cancellableRequest'; import FieldMixin from './FieldMixin'; +import StacMigrate from '@radiantearth/stac-migrate'; const WorkPanelMixinInstance = WorkPanelMixin('jobs', 'batch job', 'batch jobs'); @@ -261,6 +262,7 @@ export default { if (updatedJob.status === 'finished') { try { result = await updatedJob.getResultsAsStac(); + result = StacMigrate.stac(result, false); } catch (error) { Utils.exception(this, error, "Load Results Error: " + Utils.getResourceTitle(updatedJob)); } @@ -342,6 +344,7 @@ export default { // Doesn't need to go through job store as it doesn't change job-related data try { let stac = await job.getResultsAsStac(); + stac = StacMigrate.stac(stac, false); this.broadcast('viewJobResults', stac, job); } catch(error) { Utils.exception(this, error, 'View Result Error: ' + Utils.getResourceTitle(job)); @@ -351,6 +354,7 @@ export default { // Doesn't need to go through job store as it doesn't change job-related data try { let result = await job.getResultsAsStac(); + result = StacMigrate.stac(result, false); if(Utils.size(result.assets) == 0) { Utils.error(this, 'No results available for job "' + Utils.getResourceTitle(job) + '".'); return; @@ -363,6 +367,7 @@ export default { async shareResults(job) { if (this.canShare) { let result = await job.getResultsAsStac(); + result = StacMigrate.stac(result, false); let url; let link; if (Array.isArray(result.links)) { diff --git a/src/components/datatypes/SelectBox.vue b/src/components/datatypes/SelectBox.vue index 275b4950..2cda0f14 100644 --- a/src/components/datatypes/SelectBox.vue +++ b/src/components/datatypes/SelectBox.vue @@ -47,7 +47,7 @@ export default { let collection = this.$store.state.collections.find(c => c.id == this.context); if (Utils.isObject(collection)) { try { - state = collection.summaries['eo:bands'].map(band => band.name); + state = collection.summaries['bands'].map(band => band.name); } catch (error) {} if (state.length === 0 && Utils.isObject(collection['cube:dimensions'])) { try { diff --git a/src/components/maps/projManager.js b/src/components/maps/projManager.js index 684fc3dc..f7231250 100644 --- a/src/components/maps/projManager.js +++ b/src/components/maps/projManager.js @@ -37,8 +37,8 @@ export default class ProjManager { // Get projection details from STAC (todo: add collection support) static async addFromStac(stac) { if (Utils.isObject(stac) && Utils.isObject(stac.properties)) { - if (stac.properties['proj:epsg']) { - return await ProjManager.get(stac.properties['proj:epsg']); + if (stac.properties['proj:code']) { + return await ProjManager.get(stac.properties['proj:code']); } else if (stac.properties['proj:wkt2']) { return ProjManager.add(stac.id, stac.properties['proj:wkt2']); @@ -68,9 +68,9 @@ export default class ProjManager { } // Get projection from database - let proj = await import('../../assets/epsg-proj.json'); - if (id in proj) { - return ProjManager.add(code, proj[id][0], proj[id][1]); + let epsg = await import('../../assets/epsg-proj.json'); + if (id in epsg) { + return ProjManager.add(code, epsg[id][0], epsg[id][1]); } // No projection found diff --git a/src/components/modals/CollectionModal.vue b/src/components/modals/CollectionModal.vue index 3ed48b75..c3d95f48 100644 --- a/src/components/modals/CollectionModal.vue +++ b/src/components/modals/CollectionModal.vue @@ -21,6 +21,7 @@ import Modal from './Modal.vue'; import Collection from '../Collection.vue'; import Utils from '../../utils.js'; +import StacMigrate from '@radiantearth/stac-migrate'; export default { name: 'CollectionModal', @@ -82,7 +83,7 @@ export default { } let next = await this.itemsIterator.next(); if (next && next.value && !next.done) { - this.items.push(next.value); + this.items.push(StacMigrate.item(next.value, null, false)); } } } diff --git a/src/components/wizards/SpectralIndices.vue b/src/components/wizards/SpectralIndices.vue index 2f197889..7f041c4d 100644 --- a/src/components/wizards/SpectralIndices.vue +++ b/src/components/wizards/SpectralIndices.vue @@ -128,7 +128,7 @@ export default { return false; } - if (c.summaries && !c.summaries["eo:bands"]) { + if (c.summaries && !c.summaries["bands"]) { // Has summaries (so is likely fully loaded), but has no bands that we can work with return false; } @@ -196,7 +196,7 @@ export default { return b.toJSON(); }, getAvailableBands(collection) { - let bands = collection?.summaries && collection?.summaries["eo:bands"]; + let bands = collection?.summaries && collection?.summaries["bands"]; if (Array.isArray(bands)) { let availableBands = {}; const stacNames = Object.values(MAPPING); @@ -206,7 +206,7 @@ export default { if (!band.name) { continue; // Ignore bands without a name } - let i = stacNames.indexOf(band['common_name']); + let i = stacNames.indexOf(band['eo:common_name']); if (i !== -1) { availableBands[asiNames[i]] = band; } diff --git a/src/formats/geotiff.js b/src/formats/geotiff.js index 4602252c..7f31e89a 100644 --- a/src/formats/geotiff.js +++ b/src/formats/geotiff.js @@ -16,8 +16,8 @@ class GeoTIFF extends SupportedFormat { constructor(asset, stac) { super(asset, "MapViewer", 'fa-map', { removableLayers: true }); - this.bands = []; - this.nodata = []; + this._bands = []; + this._nodata = []; this.img = null; this.projection = null; this.extent = null; @@ -53,17 +53,12 @@ class GeoTIFF extends SupportedFormat { // Get nodata from STAC file:nodata if (Array.isArray(this['file:nodata']) && this['file:nodata'].length > 0) { - this.nodata = Utils.parseNodata(this['file:nodata']); + this._nodata = Utils.parseNodata(this['file:nodata']); } - // Get band names from STAC eo:bands - if (Array.isArray(this['eo:bands']) && this['eo:bands'].length > 0) { - this['eo:bands'].forEach((band, i) => this.setBandInfo(i, { name: band.name })); - } - - // Get min/max/nodata from STAC raster:bands - if (Array.isArray(this['raster:bands']) && this['raster:bands'].length > 0) { - this['raster:bands'].forEach((band, i) => { + // Get min/max/nodata from STAC bands + if (Array.isArray(this.bands) && this.bands.length > 0) { + this.bands.forEach((band, i) => { // Get name from band if (band.name) { this.setBandInfo(i, { @@ -80,8 +75,8 @@ class GeoTIFF extends SupportedFormat { } // per-band no-data values are not supported, simply read the no-data from the first occurance if not defined yet - if (this.nodata.length === 0 && typeof band.nodata !== 'undefined') { - this.nodata.push(Utils.parseNodata(band.nodata)); + if (this._nodata.length === 0 && typeof band.nodata !== 'undefined') { + this._nodata.push(Utils.parseNodata(band.nodata)); } }); } @@ -103,10 +98,10 @@ class GeoTIFF extends SupportedFormat { // Use min/max for data type (as fallback) try { let dummy = this.img.getArrayForSample(i); - if (!Number.isFinite(this.bands[i].min)) { + if (!Number.isFinite(this._bands[i].min)) { data.min = this.getMinForDataType(dummy); } - if (!Number.isFinite(this.bands[i].max)) { + if (!Number.isFinite(this._bands[i].max)) { data.max = this.getMaxForDataType(dummy); } } catch (error) {} @@ -129,8 +124,8 @@ class GeoTIFF extends SupportedFormat { // get no-data values if needed let nodata = this.img.getGDALNoData(); - if (this.nodata.length === 0 && nodata !== null) { - this.nodata.push(nodata); + if (this._nodata.length === 0 && nodata !== null) { + this._nodata.push(nodata); } } @@ -176,7 +171,7 @@ class GeoTIFF extends SupportedFormat { Math.trunc(map[i] / 65536 * 256), Math.trunc(map[i + greenOffset] / 65536 * 256), Math.trunc(map[i + blueOffset] / 65536 * 256), - this.nodata.includes(i) ? 0 : 1 + this._nodata.includes(i) ? 0 : 1 ]); } } @@ -196,11 +191,11 @@ class GeoTIFF extends SupportedFormat { } setBandInfo(i, data) { - if (this.bands[i]) { - Object.assign(this.bands[i], data); + if (this._bands[i]) { + Object.assign(this._bands[i], data); } else { - this.bands.push(Object.assign({ id: i + 1 }, data)); + this._bands.push(Object.assign({ id: i + 1 }, data)); } } @@ -209,7 +204,7 @@ class GeoTIFF extends SupportedFormat { } getNoData() { - return this.nodata; + return this._nodata; } getContext() { @@ -217,7 +212,7 @@ class GeoTIFF extends SupportedFormat { } getBands() { - return this.bands; + return this._bands; } getProjection() { diff --git a/src/store/index.js b/src/store/index.js index 15b026b0..50c94725 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -3,6 +3,7 @@ import Vuex from 'vuex'; import { OpenEO, FileTypes, Formula } from '@openeo/js-client'; import { ProcessRegistry } from '@openeo/js-commons'; +import StacMigrate from '@radiantearth/stac-migrate'; import Utils from '../utils.js'; import ProcessRegistryExtension from '../registryExtension.js'; import Config from '../../config'; @@ -272,6 +273,7 @@ export default new Vuex.Store({ let collection = cx.state.collections.find(c => c.id === id); if (!collection || !collection._loaded) { collection = await cx.state.connection.describeCollection(id); + collection = StacMigrate.collection(collection, false); cx.commit('fillCollection', collection); } return collection; @@ -384,6 +386,7 @@ export default new Vuex.Store({ }, collections(state, data) { state.collections = data.collections + .map(c => StacMigrate.collection(c, false)) .filter(c => (typeof c.id === 'string')) .sort(Utils.sortById); },