diff --git a/src/layer/AbstractSentinelHubV3Layer.ts b/src/layer/AbstractSentinelHubV3Layer.ts index 555f1fba..04adbed2 100644 --- a/src/layer/AbstractSentinelHubV3Layer.ts +++ b/src/layer/AbstractSentinelHubV3Layer.ts @@ -1,11 +1,11 @@ import axios from 'axios'; -import { getAuthToken } from 'src/auth'; +import { getAuthToken, isAuthTokenSet } from 'src/auth'; import { BBox } from 'src/bbox'; import { GetMapParams, ApiType } from 'src/layer/const'; import { fetchCached } from 'src/layer/utils'; import { wmsGetMapUrl } from 'src/layer/wms'; -import { processingGetMap } from 'src/layer/processing'; +import { processingGetMap, createProcessingPayload, ProcessingPayload } from 'src/layer/processing'; import { AbstractLayer } from 'src/layer/AbstractLayer'; // this class provides any SHv3-specific functionality to the subclasses: @@ -42,34 +42,39 @@ export class AbstractSentinelHubV3Layer extends AbstractLayer { if (this.instanceId === null || this.layerId === null) { throw new Error('Could not fetch layer params - instanceId and layerId must be set on Layer'); } - const layersParams = await this.fetchLayersParamsFromSentinelHubLayersV3(); - const layerParams = layersParams.find((l: any) => l.layerId === this.layerId); - if (!layerParams) { - throw new Error('Layer params could not be found'); - } - return layerParams; - } - - private async fetchLayersParamsFromSentinelHubLayersV3(forceFetch = false): Promise { - const authToken = getAuthToken(); - if (authToken === null) { - throw new Error('authToken is not set'); - } if (!this.dataset) { throw new Error('This layer does not support Processing API (unknown dataset)'); } + if (!isAuthTokenSet) { + throw new Error('authToken is not set'); + } + const authToken = getAuthToken(); + const url = `${this.dataset.shServiceHostname}configuration/v1/wms/instances/${this.instanceId}/layers`; const headers = { Authorization: `Bearer ${authToken}`, }; - const res = await fetchCached(url, { responseType: 'json', headers: headers }, forceFetch); + const res = await fetchCached(url, { responseType: 'json', headers: headers }, false); const layersParams = res.data.map((l: any) => ({ layerId: l.id, ...l.datasourceDefaults, evalscript: l.styles[0].evalScript, dataProduct: l.styles[0].dataProduct, })); - return layersParams; + + const layerParams = layersParams.find((l: any) => l.layerId === this.layerId); + if (!layerParams) { + throw new Error('Layer params could not be found'); + } + return layerParams; + } + + protected async updateProcessingGetMapPayload(payload: ProcessingPayload): Promise { + // Subclasses should override this method if they wish to supply additional + // parameters to Processing API. + // Typically, if additional layer data is needed for that, this code will be called: + // const layerParams = await this.fetchLayerParamsFromSHServiceV3(); + return payload; } public async getMap(params: GetMapParams, api: ApiType): Promise { @@ -91,7 +96,12 @@ export class AbstractSentinelHubV3Layer extends AbstractLayer { throw new Error(`Could not fetch evalscript / dataProduct from service for layer ${this.layerId}`); } } - return processingGetMap(this.dataset, params, this.evalscript, this.dataProduct); + + // allow subclasses to update payload with their own parameters: + const payload = createProcessingPayload(this.dataset, params, this.evalscript, this.dataProduct); + const updatedPayload = await this.updateProcessingGetMapPayload(payload); + + return processingGetMap(this.dataset.shServiceHostname, updatedPayload); } return super.getMap(params, api); diff --git a/src/layer/LayersFactory.ts b/src/layer/LayersFactory.ts index 91579ba6..46708f00 100644 --- a/src/layer/LayersFactory.ts +++ b/src/layer/LayersFactory.ts @@ -154,24 +154,25 @@ export class LayersFactory { const layersInfos = await LayersFactory.getLayersListFromBaseUrl(baseUrl); const filteredLayersInfos = filterLayers === null ? layersInfos : layersInfos.filter(l => filterLayers(l.layerId, l.dataset)); + return filteredLayersInfos.map(({ layerId, dataset, title, description }) => { - if (dataset) { - const SHLayerClass = LayersFactory.LAYER_FROM_DATASET[dataset.id]; - if (!SHLayerClass) { - throw new Error(`Dataset ${dataset.id} is not defined in LayersFactory.LAYER_FROM_DATASET`); - } - return new SHLayerClass( - LayersFactory.parseSHInstanceId(baseUrl), - layerId, - null, - null, - null, - title, - description, - ); - } else { + if (!dataset) { return new WmsLayer(baseUrl, layerId, title, description); } + + const SHLayerClass = LayersFactory.LAYER_FROM_DATASET[dataset.id]; + if (!SHLayerClass) { + throw new Error(`Dataset ${dataset.id} is not defined in LayersFactory.LAYER_FROM_DATASET`); + } + return new SHLayerClass( + LayersFactory.parseSHInstanceId(baseUrl), + layerId, + null, + null, + null, + title, + description, + ); }); } } diff --git a/src/layer/S1GRDIWAWSLayer.ts b/src/layer/S1GRDIWAWSLayer.ts index 2a471c0c..49088f05 100644 --- a/src/layer/S1GRDIWAWSLayer.ts +++ b/src/layer/S1GRDIWAWSLayer.ts @@ -1,6 +1,7 @@ import { BBox } from 'src/bbox'; import { BackscatterCoeff, PaginatedTiles } from 'src/layer/const'; import { DATASET_AWS_S1GRD_IW } from 'src/layer/dataset'; +import { ProcessingPayload } from 'src/layer/processing'; import { AbstractSentinelHubV3Layer } from 'src/layer/AbstractSentinelHubV3Layer'; @@ -49,52 +50,22 @@ export class S1GRDIWAWSLayer extends AbstractSentinelHubV3Layer { backscatterCoeff: BackscatterCoeff | null = BackscatterCoeff.GAMMA0_ELLIPSOID, ) { super(instanceId, layerId, evalscript, evalscriptUrl, dataProduct, title, description); - if (!polarization) { - throw new Error('Polarization should be set'); - } this.polarization = polarization; this.orthorectify = orthorectify; this.backscatterCoeff = backscatterCoeff; } - private async updateParamsViaService(): Promise { - const params = await this.fetchLayerParamsFromSHServiceV3(); - this.orthorectify = params['orthorectify']; - this.backscatterCoeff = params['backCoeff']; - } + protected async updateProcessingGetMapPayload(payload: ProcessingPayload): Promise { + const layerParams = await this.fetchLayerParamsFromSHServiceV3(); - public async getOrthorectify(): Promise { - if (this.orthorectify) { - return this.orthorectify; - } - try { - await this.updateParamsViaService(); - if (this.orthorectify === null) { - throw new Error('orthorectify should not be null!'); - } - return this.orthorectify; - } catch (ex) { - throw new Error( - `Parameter 'orthorectify' is not specified and there was an error fetching it: ${ex.message}`, - ); - } - } + this.polarization = layerParams['polarization']; + this.backscatterCoeff = layerParams['backCoeff']; + this.orthorectify = layerParams['orthorectify']; - public async getBackscatterCoeff(): Promise { - if (this.backscatterCoeff) { - return this.backscatterCoeff; - } - try { - await this.updateParamsViaService(); - if (this.backscatterCoeff === null) { - throw new Error('backscatterCoeff should not be null!'); - } - return this.backscatterCoeff; - } catch (ex) { - throw new Error( - `Parameter 'backscatterCoeff' is not specified and there was an error fetching it: ${ex.message}`, - ); - } + payload.input.data[0].dataFilter.polarization = this.polarization; + payload.input.data[0].processing.backCoeff = this.backscatterCoeff; + payload.input.data[0].processing.orthorectify = this.orthorectify; + return payload; } public async findTiles( diff --git a/src/layer/dataset.ts b/src/layer/dataset.ts index 3b7affbe..1cdc4bba 100644 --- a/src/layer/dataset.ts +++ b/src/layer/dataset.ts @@ -56,7 +56,7 @@ export const DATASET_AWS_S1GRD_IW: Dataset = { id: 'AWS_S1GRD_IW', shJsonGetCapabilitiesDataset: 'S1GRD', shWmsEvalsource: 'S1GRD', - shProcessingApiDatasourceAbbreviation: 'S1GRDIWAWS', + shProcessingApiDatasourceAbbreviation: 'S1GRD', shServiceHostname: 'https://services.sentinel-hub.com/', searchIndexUrl: 'https://services.sentinel-hub.com/index/v3/collections/S1GRD/searchIndex', }; diff --git a/src/layer/processing.ts b/src/layer/processing.ts index 926f80aa..65e3df01 100644 --- a/src/layer/processing.ts +++ b/src/layer/processing.ts @@ -16,7 +16,7 @@ enum MosaickingOrder { LEAST_CC = 'leastCC', } -type ProcessingPayload = { +export type ProcessingPayload = { input: { bounds: { bbox?: BBoxTurf; @@ -36,10 +36,12 @@ type ProcessingPayload = { maxCloudCoverage: number; previewMode?: PreviewMode; mosaickingOrder?: MosaickingOrder; + [key: string]: any; }; processing?: { upsampling?: Interpolator; downsampling?: Interpolator; + [key: string]: any; }; type: string; } @@ -61,17 +63,12 @@ type ProcessingPayload = { dataProduct?: string; }; -export async function processingGetMap( +export function createProcessingPayload( dataset: Dataset, params: GetMapParams, evalscript: string | null = null, dataProduct: string | null = null, -): Promise { - const authToken = getAuthToken(); - if (!authToken) { - throw new Error('Must be authenticated to use Processing API'); - } - +): ProcessingPayload { const { bbox } = params; const payload: ProcessingPayload = { @@ -151,7 +148,16 @@ export async function processingGetMap( throw new Error('Either evalscript or dataProduct should be defined with Processing API'); } - const response = await axios.post(`${dataset.shServiceHostname}api/v1/process`, payload, { + return payload; +} + +export async function processingGetMap(shServiceHostname: string, payload: ProcessingPayload): Promise { + const authToken = getAuthToken(); + if (!authToken) { + throw new Error('Must be authenticated to use Processing API'); + } + + const response = await axios.post(`${shServiceHostname}api/v1/process`, payload, { headers: { Authorization: 'Bearer ' + authToken, 'Content-Type': 'application/json', diff --git a/stories/index.stories.js b/stories/index.stories.js index 220d8c6b..690658e0 100644 --- a/stories/index.stories.js +++ b/stories/index.stories.js @@ -123,6 +123,37 @@ export const S2GetMapProcessing = () => { return img; }; +export const S1GetMapProcessingFromLayer = () => { + if (!process.env.STORYBOOK_AUTH_TOKEN) { + return '
Please set auth token for Processing API (STORYBOOK_AUTH_TOKEN env var)
'; + } + setAuthToken(process.env.STORYBOOK_AUTH_TOKEN); + + const img = document.createElement('img'); + img.width = '512'; + img.height = '512'; + + // getMap is async: + const perform = async () => { + const layer = new S1GRDIWAWSLayer(instanceId, 'S1GRDIWDV'); + + const bbox = new BBox(CRS_EPSG4326, 19, 20, 20, 21); + const getMapParams = { + bbox: bbox, + fromTime: new Date(Date.UTC(2018, 11 - 1, 22, 0, 0, 0)), + toTime: new Date(Date.UTC(2018, 12 - 1, 22, 23, 59, 59)), + width: 512, + height: 512, + format: MimeTypes.JPEG, + }; + const imageBlob = await layer.getMap(getMapParams, ApiType.PROCESSING); + img.src = URL.createObjectURL(imageBlob); + }; + perform().then(() => {}); + + return img; +}; + export const WmsGetMap = () => { const img = document.createElement('img'); img.width = '512';