Skip to content

Commit

Permalink
Merge pull request #249 from sentinel-hub/feature/support-cdse-byoc-1
Browse files Browse the repository at this point in the history
Support cdse byoc
  • Loading branch information
dgostencnik authored Jul 25, 2023
2 parents 05ab666 + 56d55b6 commit bd182d2
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 29 deletions.
8 changes: 6 additions & 2 deletions src/layer/AbstractSentinelHubV3Layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { Effects } from '../mapDataManipulation/const';
import { runEffectFunctions } from '../mapDataManipulation/runEffectFunctions';
import { CACHE_CONFIG_30MIN, CACHE_CONFIG_NOCACHE } from '../utils/cacheHandlers';
import { getStatisticsProvider, StatisticsProviderType } from '../statistics/StatisticsProvider';
import { fetchLayerParamsFromConfigurationService, getConfigurationServiceHostFromBaseUrl } from './utils';
import { fetchLayerParamsFromConfigurationService, getSHServiceRootUrl } from './utils';
interface ConstructorParameters {
instanceId?: string | null;
layerId?: string | null;
Expand Down Expand Up @@ -115,7 +115,7 @@ export class AbstractSentinelHubV3Layer extends AbstractLayer {
throw new Error('This layer does not support Processing API (unknown dataset)');
}
const layersParams = await fetchLayerParamsFromConfigurationService(
getConfigurationServiceHostFromBaseUrl(this.dataset.shServiceHostname),
this.getSHServiceRootUrl(),
this.instanceId,
reqConfig,
);
Expand Down Expand Up @@ -679,4 +679,8 @@ export class AbstractSentinelHubV3Layer extends AbstractLayer {
datasetParameters: null,
};
}

public getSHServiceRootUrl(): string {
return getSHServiceRootUrl(this.dataset.shServiceHostname);
}
}
14 changes: 12 additions & 2 deletions src/layer/BYOCLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
BYOCBand,
FindTilesAdditionalParameters,
BYOCSubTypes,
SH_SERVICE_ROOT_URL,
} from './const';
import { DATASET_BYOC } from './dataset';
import { AbstractSentinelHubV3Layer } from './AbstractSentinelHubV3Layer';
Expand All @@ -25,6 +26,7 @@ import { getAxiosReqParams, RequestConfiguration } from '../utils/cancelRequests
import { ensureTimeout } from '../utils/ensureTimeout';
import { CACHE_CONFIG_30MIN } from '../utils/cacheHandlers';
import { StatisticsProviderType } from '../statistics/StatisticsProvider';
import { getSHServiceRootUrl } from './utils';

interface ConstructorParameters {
instanceId?: string | null;
Expand All @@ -37,6 +39,7 @@ interface ConstructorParameters {
collectionId?: string | null;
locationId?: LocationIdSHv3 | null;
subType?: BYOCSubTypes | null;
shServiceRootUrl?: string;
}

type BYOCFindTilesDatasetParameters = {
Expand All @@ -49,6 +52,7 @@ export class BYOCLayer extends AbstractSentinelHubV3Layer {
public collectionId: string;
public locationId: LocationIdSHv3;
public subType: BYOCSubTypes;
public shServiceRootUrl: string;

public constructor({
instanceId = null,
Expand All @@ -61,11 +65,13 @@ export class BYOCLayer extends AbstractSentinelHubV3Layer {
collectionId = null,
locationId = null,
subType = null,
shServiceRootUrl = SH_SERVICE_ROOT_URL.default,
}: ConstructorParameters) {
super({ instanceId, layerId, evalscript, evalscriptUrl, dataProduct, title, description });
this.collectionId = collectionId;
this.locationId = locationId;
this.subType = subType;
this.shServiceRootUrl = getSHServiceRootUrl(shServiceRootUrl);
}

public async updateLayerFromServiceIfNeeded(reqConfig?: RequestConfiguration): Promise<void> {
Expand Down Expand Up @@ -105,7 +111,7 @@ export class BYOCLayer extends AbstractSentinelHubV3Layer {

if (this.locationId === null) {
if (this.subType !== BYOCSubTypes.ZARR) {
const url = `https://services.sentinel-hub.com/api/v1/metadata/collection/${this.getTypeId()}`;
const url = `${this.getSHServiceRootUrl()}api/v1/metadata/collection/${this.getTypeId()}`;
const headers = { Authorization: `Bearer ${getAuthToken()}` };
const res = await axios.get(url, {
responseType: 'json',
Expand Down Expand Up @@ -266,7 +272,7 @@ export class BYOCLayer extends AbstractSentinelHubV3Layer {
throw new Error('Fetching available bands for ZARR not supported.');
}

const url = `https://services.sentinel-hub.com/api/v1/metadata/collection/${this.getTypeId()}`;
const url = `${this.getSHServiceRootUrl()}api/v1/metadata/collection/${this.getTypeId()}`;
const headers = { Authorization: `Bearer ${getAuthToken()}` };
const res = await axios.get(url, {
responseType: 'json',
Expand All @@ -277,4 +283,8 @@ export class BYOCLayer extends AbstractSentinelHubV3Layer {
}, reqConfig);
return bandsResponseData;
}

public getSHServiceRootUrl(): string {
return this.shServiceRootUrl;
}
}
6 changes: 4 additions & 2 deletions src/layer/LayersFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
fetchGetCapabilitiesJson,
parseSHInstanceId,
fetchLayerParamsFromConfigurationService,
getConfigurationServiceHostFromBaseUrl,
getSHServiceRootUrlFromBaseUrl,
fetchGetCapabilitiesXml,
GetCapabilitiesWmsXml,
isWMSCapabilities,
Expand Down Expand Up @@ -242,6 +242,7 @@ export class LayersFactory {
if (!SHLayerClass) {
throw new Error(`Dataset ${dataset.id} is not defined in LayersFactory.LAYER_FROM_DATASET`);
}
const shServiceRootUrl = getSHServiceRootUrlFromBaseUrl(baseUrl);
return new SHLayerClass({
instanceId: parseSHInstanceId(baseUrl),
layerId,
Expand All @@ -255,6 +256,7 @@ export class LayersFactory {
// We must pass the maxCloudCoverPercent (S-2) or others (S-1) from legacyGetMapFromParams to the Layer
// otherwise the default values from layer definition on the service will be used.
...overrideConstructorParams,
shServiceRootUrl: shServiceRootUrl,
});
},
);
Expand All @@ -274,7 +276,7 @@ export class LayersFactory {
if (authToken && preferGetCapabilities === false) {
try {
const layers = await fetchLayerParamsFromConfigurationService(
getConfigurationServiceHostFromBaseUrl(baseUrl),
getSHServiceRootUrlFromBaseUrl(baseUrl),
parseSHInstanceId(baseUrl),
reqConfig,
);
Expand Down
12 changes: 10 additions & 2 deletions src/layer/ProcessingDataFusionLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
ApiType,
PaginatedTiles,
MosaickingOrder,
DEFAULT_SH_SERVICE_HOSTNAME,
SH_SERVICE_ROOT_URL,
} from './const';
import {
createProcessingPayload,
Expand Down Expand Up @@ -127,8 +127,16 @@ export class ProcessingDataFusionLayer extends AbstractSentinelHubV3Layer {
)
) {
shServiceHostname = bogusFirstLayer.getShServiceHostname();
}
//check if all layers use same root url and use it for request
else if (
this.layers.every(
layer => layer.layer.getSHServiceRootUrl() === bogusFirstLayer.getSHServiceRootUrl(),
)
) {
shServiceHostname = bogusFirstLayer.getSHServiceRootUrl();
} else {
shServiceHostname = DEFAULT_SH_SERVICE_HOSTNAME;
shServiceHostname = SH_SERVICE_ROOT_URL.default;
}

let blob = await processingGetMap(shServiceHostname, payload, innerReqConfig);
Expand Down
59 changes: 58 additions & 1 deletion src/layer/__tests__/BYOCLayer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BBox, CRS_EPSG4326, setAuthToken, LocationIdSHv3, BYOCLayer } from '../../index';
import { SHV3_LOCATIONS_ROOT_URL, BYOCSubTypes } from '../const';
import { SHV3_LOCATIONS_ROOT_URL, BYOCSubTypes, SH_SERVICE_ROOT_URL } from '../const';
import { constructFixtureFindTilesSearchIndex, constructFixtureFindTilesCatalog } from './fixtures.BYOCLayer';

import {
Expand Down Expand Up @@ -230,3 +230,60 @@ describe('Test updateLayerFromServiceIfNeeded for ZARR', () => {
expect(layer.locationId).toEqual(LocationIdSHv3.creo);
});
});

describe.only('shServiceRootUrl', () => {
beforeEach(async () => {
setAuthToken(AUTH_TOKEN);
mockNetwork.reset();
});
const mockedLayerId = 'LAYER_ID';

test.each([
[
'default sh service url is used if not set',
{
instanceId: 'INSTANCE_ID',
layerId: mockedLayerId,
collectionId: 'mockCollectionId',
subType: BYOCSubTypes.BYOC,
},
SH_SERVICE_ROOT_URL.default,
],
[
'default sh service url is used if set',
{
instanceId: 'INSTANCE_ID',
layerId: mockedLayerId,
collectionId: 'mockCollectionId',
subType: BYOCSubTypes.BYOC,
shServiceRootUrl: SH_SERVICE_ROOT_URL.default,
},
SH_SERVICE_ROOT_URL.default,
],
[
'cdse sh service url is used if set',
{
instanceId: 'INSTANCE_ID',
layerId: mockedLayerId,
collectionId: 'mockCollectionId',
subType: BYOCSubTypes.BYOC,
shServiceRootUrl: SH_SERVICE_ROOT_URL.cdse,
},
SH_SERVICE_ROOT_URL.cdse,
],
[
'default sh service url is used for unknown shServiceRootUrl',
{
instanceId: 'INSTANCE_ID',
layerId: mockedLayerId,
collectionId: 'mockCollectionId',
subType: BYOCSubTypes.BYOC,
shServiceRootUrl: 'random url',
},
SH_SERVICE_ROOT_URL.default,
],
])('%p', async (_title, layerParams, expected) => {
const layer = new BYOCLayer(layerParams);
expect(layer.getSHServiceRootUrl()).toBe(expected);
});
});
8 changes: 4 additions & 4 deletions src/layer/__tests__/ProcessingDataFusionLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
DEMInstanceTypeOrthorectification,
LocationIdSHv3,
SHV3_LOCATIONS_ROOT_URL,
DEFAULT_SH_SERVICE_HOSTNAME,
SH_SERVICE_ROOT_URL,
} from '../const';
import { constructFixtureGetMapRequest } from './fixtures.ProcessingDataFusionLayer';
import { AcquisitionMode, Resolution } from '../S1GRDAWSEULayer';
Expand Down Expand Up @@ -54,7 +54,7 @@ describe("Test data fusion uses correct URL depending on layers' combination", (

test.each([
[[shServicesLayer, shServicesLayer], shServicesLayer.dataset.shServiceHostname],
[[creodiasLayer, shServicesLayer, usWestLayer], DEFAULT_SH_SERVICE_HOSTNAME],
[[creodiasLayer, shServicesLayer, usWestLayer], SH_SERVICE_ROOT_URL.default],
[[creodiasLayer, creodiasLayer], creodiasLayer.dataset.shServiceHostname],
[[shServicesLayer, byocLayer], shServicesLayer.dataset.shServiceHostname],
[
Expand All @@ -69,8 +69,8 @@ describe("Test data fusion uses correct URL depending on layers' combination", (
],
SHV3_LOCATIONS_ROOT_URL[byocLayer.locationId],
],
[[byocLayer, shServicesLayer, creodiasLayer], DEFAULT_SH_SERVICE_HOSTNAME],
[[byocLayerMundi, byocLayer], DEFAULT_SH_SERVICE_HOSTNAME],
[[byocLayer, shServicesLayer, creodiasLayer], SH_SERVICE_ROOT_URL.default],
[[byocLayerMundi, byocLayer], SH_SERVICE_ROOT_URL.default],
[
[
byocLayerMundi,
Expand Down
20 changes: 18 additions & 2 deletions src/layer/__tests__/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { OgcServiceTypes } from '../const';
import { createGetCapabilitiesXmlUrl } from '../utils';
import { OgcServiceTypes, SH_SERVICE_ROOT_URL } from '../const';
import { createGetCapabilitiesXmlUrl, getSHServiceRootUrlFromBaseUrl } from '../utils';

const cases = [
{
Expand All @@ -26,3 +26,19 @@ describe("'add' utility", () => {
expect(url).toStrictEqual(expected);
});
});

describe('getSHServiceRootUrlFromBaseUrl', () => {
test.each([
['https://services.sentinel-hub.com/ogc/wms/instanceId', SH_SERVICE_ROOT_URL.default],
['https://services-uswest2.sentinel-hub.com/ogc/wms/instanceId', SH_SERVICE_ROOT_URL.default],
['https://creodias.sentinel-hub.com/ogc/wms/instanceId', SH_SERVICE_ROOT_URL.default],
['https://shservices.mundiwebservices.com/ogc/1wms/instanceId', SH_SERVICE_ROOT_URL.default],
['https://sh.dataspace.copernicus.eu/wms/instance', SH_SERVICE_ROOT_URL.cdse],
['', SH_SERVICE_ROOT_URL.default],
[null, SH_SERVICE_ROOT_URL.default],
[undefined, SH_SERVICE_ROOT_URL.default],
['not url', SH_SERVICE_ROOT_URL.default],
])('getSHServiceRootUrlFromBaseUrl %p', async (baseUrl, expected) => {
expect(getSHServiceRootUrlFromBaseUrl(baseUrl)).toBe(expected);
});
});
7 changes: 6 additions & 1 deletion src/layer/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,16 +161,21 @@ export enum LocationIdSHv3 {
creo = 'creo',
mundi = 'mundi',
gcpUsCentral1 = 'gcp-us-central1',
cdse = 'cdse',
}
export const SHV3_LOCATIONS_ROOT_URL: Record<LocationIdSHv3, string> = {
[LocationIdSHv3.awsEuCentral1]: 'https://services.sentinel-hub.com/',
[LocationIdSHv3.awsUsWest2]: 'https://services-uswest2.sentinel-hub.com/',
[LocationIdSHv3.creo]: 'https://creodias.sentinel-hub.com/',
[LocationIdSHv3.mundi]: 'https://shservices.mundiwebservices.com/',
[LocationIdSHv3.gcpUsCentral1]: 'https://services-gcp-us-central1.sentinel-hub.com/',
[LocationIdSHv3.cdse]: 'https://sh.dataspace.copernicus.eu/',
};

export const DEFAULT_SH_SERVICE_HOSTNAME = 'https://services.sentinel-hub.com/';
export const SH_SERVICE_ROOT_URL = {
default: SHV3_LOCATIONS_ROOT_URL[LocationIdSHv3.awsEuCentral1],
cdse: SHV3_LOCATIONS_ROOT_URL[LocationIdSHv3.cdse],
};

export type GetStatsParams = {
fromTime: Date;
Expand Down
36 changes: 23 additions & 13 deletions src/layer/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import axios, { AxiosRequestConfig } from 'axios';
import { stringify, parseUrl, stringifyUrl } from 'query-string';
import { parseStringPromise } from 'xml2js';

import { DEFAULT_SH_SERVICE_HOSTNAME, OgcServiceTypes, SH_SERVICE_HOSTNAMES_V3 } from './const';
import { OgcServiceTypes, SH_SERVICE_HOSTNAMES_V3, SH_SERVICE_ROOT_URL } from './const';
import { getAxiosReqParams, RequestConfiguration } from '../utils/cancelRequests';
import { CACHE_CONFIG_30MIN, CACHE_CONFIG_30MIN_MEMORY } from '../utils/cacheHandlers';
import { GetCapabilitiesWmtsXml } from './wmts.utils';
Expand Down Expand Up @@ -148,23 +148,33 @@ export function parseSHInstanceId(baseUrl: string): string {
throw new Error(`Could not parse instanceId from URL: ${baseUrl}`);
}

export function getConfigurationServiceHostFromBaseUrl(baseUrl: string): string {
let host = baseUrl;

if (/\ogc\/wms/.test(baseUrl)) {
host = baseUrl.substring(0, baseUrl.indexOf('/ogc/wms') + 1);
}
export function getSHServiceRootUrl(host: string): string {
const shServiceRootUrl = Object.values(SH_SERVICE_ROOT_URL).find(url => {
const regex = new RegExp(url);
if (regex.test(host)) {
return url;
}
});

// Copernicus datasets require different endpoint
if (/dataspace.copernicus.eu/.test(host)) {
return host;
if (shServiceRootUrl) {
return shServiceRootUrl;
}

// The endpoint for fetching the list of layers is typically
// https://services.sentinel-hub.com/, even for creodias datasets.
// However there is an exception for Copernicus datasets, which have a different
// However there is an exception for Copernicus datasets, which have
// a different endpoint for fetching the list of layers
return DEFAULT_SH_SERVICE_HOSTNAME;
return SH_SERVICE_ROOT_URL.default;
}

export function getSHServiceRootUrlFromBaseUrl(baseUrl: string): string {
let host = baseUrl;

if (/\ogc\/wms/.test(baseUrl)) {
host = baseUrl.substring(0, baseUrl.indexOf('/ogc/wms') + 1);
}

return getSHServiceRootUrl(host);
}

export async function fetchLayerParamsFromConfigurationService(
Expand All @@ -176,7 +186,7 @@ export async function fetchLayerParamsFromConfigurationService(
if (!authToken) {
throw new Error('Must be authenticated to fetch layer params');
}
const configurationServiceHostName = shServiceHostName ?? DEFAULT_SH_SERVICE_HOSTNAME;
const configurationServiceHostName = shServiceHostName ?? SH_SERVICE_ROOT_URL.default;
const url = `${configurationServiceHostName}configuration/v1/wms/instances/${instanceId}/layers`;
const headers = {
Authorization: `Bearer ${authToken}`,
Expand Down

0 comments on commit bd182d2

Please sign in to comment.