diff --git a/src/layer/AbstractDEMLayer.ts b/src/layer/AbstractDEMLayer.ts index 65c1d24e..a6f114dd 100644 --- a/src/layer/AbstractDEMLayer.ts +++ b/src/layer/AbstractDEMLayer.ts @@ -21,6 +21,7 @@ export interface ConstructorParameters { demInstance?: DEMInstanceType | null; egm?: boolean | null; clampNegative?: boolean | null; + highlights?: AbstractSentinelHubV3Layer['highlights']; } export class AbstractDEMLayer extends AbstractSentinelHubV3Layer { diff --git a/src/layer/AbstractSentinelHubV3Layer.ts b/src/layer/AbstractSentinelHubV3Layer.ts index 4eabcec2..55de4023 100644 --- a/src/layer/AbstractSentinelHubV3Layer.ts +++ b/src/layer/AbstractSentinelHubV3Layer.ts @@ -22,6 +22,7 @@ import { PaginatedTiles, Stats, SUPPORTED_DATA_PRODUCTS_PROCESSING, + Highlight, } from './const'; import { createProcessingPayload, processingGetMap, ProcessingPayload } from './processing'; import { wmsGetMapUrl } from './wms'; @@ -46,6 +47,7 @@ interface ConstructorParameters { upsampling?: Interpolator | null; downsampling?: Interpolator | null; legendUrl?: string | null; + highlights?: Highlight[] | null; } // this class provides any SHv3-specific functionality to the subclasses: @@ -59,6 +61,7 @@ export class AbstractSentinelHubV3Layer extends AbstractLayer { public mosaickingOrder: MosaickingOrder | null; // public because ProcessingDataFusionLayer needs to read it directly public upsampling: Interpolator | null; public downsampling: Interpolator | null; + public highlights?: Highlight[] | null; public constructor({ instanceId = null, @@ -72,6 +75,7 @@ export class AbstractSentinelHubV3Layer extends AbstractLayer { upsampling = null, downsampling = null, legendUrl = null, + highlights = null, }: ConstructorParameters) { super({ title, description, legendUrl }); if ( @@ -92,6 +96,7 @@ export class AbstractSentinelHubV3Layer extends AbstractLayer { this.mosaickingOrder = mosaickingOrder; this.upsampling = upsampling; this.downsampling = downsampling; + this.highlights = highlights; } public getLayerId(): string { @@ -117,11 +122,11 @@ export class AbstractSentinelHubV3Layer extends AbstractLayer { if (!this.dataset) { throw new Error('This layer does not support Processing API (unknown dataset)'); } - const layersParams = await fetchLayerParamsFromConfigurationService( - this.getSHServiceRootUrl(), - this.instanceId, + const layersParams = await fetchLayerParamsFromConfigurationService({ + shServiceHostName: this.getSHServiceRootUrl(), + instanceId: this.instanceId, reqConfig, - ); + }); const layerParams = layersParams.find((l: any) => l.layerId === this.layerId); if (!layerParams) { diff --git a/src/layer/BYOCLayer.ts b/src/layer/BYOCLayer.ts index fde5cd96..161ff51f 100644 --- a/src/layer/BYOCLayer.ts +++ b/src/layer/BYOCLayer.ts @@ -40,6 +40,7 @@ interface ConstructorParameters { locationId?: LocationIdSHv3 | null; subType?: BYOCSubTypes | null; shServiceRootUrl?: string; + highlights?: AbstractSentinelHubV3Layer['highlights']; } type BYOCFindTilesDatasetParameters = { @@ -68,8 +69,9 @@ export class BYOCLayer extends AbstractSentinelHubV3Layer { locationId = null, subType = null, shServiceRootUrl = SH_SERVICE_ROOT_URL.default, + highlights = null, }: ConstructorParameters) { - super({ instanceId, layerId, evalscript, evalscriptUrl, dataProduct, title, description }); + super({ instanceId, layerId, evalscript, evalscriptUrl, dataProduct, title, description, highlights }); this.collectionId = collectionId; this.locationId = locationId; this.subType = subType; diff --git a/src/layer/HLSAWSLayer.ts b/src/layer/HLSAWSLayer.ts index 610186a8..7dad4112 100644 --- a/src/layer/HLSAWSLayer.ts +++ b/src/layer/HLSAWSLayer.ts @@ -16,6 +16,7 @@ interface ConstructorParameters { legendUrl?: string | null; maxCloudCoverPercent?: number | null; constellation?: HLSConstellation | null; + highlights?: AbstractSentinelHubV3WithCCLayer['highlights']; } type HLSFindTilesDatasetParameters = { diff --git a/src/layer/LayersFactory.ts b/src/layer/LayersFactory.ts index 4743c2bd..5e67877d 100644 --- a/src/layer/LayersFactory.ts +++ b/src/layer/LayersFactory.ts @@ -215,6 +215,7 @@ export class LayersFactory { overrideConstructorParams?: Record | null, reqConfig?: RequestConfiguration, preferGetCapabilities: boolean = true, + includeHighlights: boolean = false, ): Promise { const returnValue = await ensureTimeout(async (innerReqConfig) => { for (let hostname of SH_SERVICE_HOSTNAMES_V3) { @@ -225,6 +226,7 @@ export class LayersFactory { overrideConstructorParams, innerReqConfig, preferGetCapabilities, + includeHighlights, ); } } @@ -252,12 +254,14 @@ export class LayersFactory { overrideConstructorParams: Record | null, reqConfig: RequestConfiguration, preferGetCapabilities: boolean = true, + includeHighlights: boolean = false, ): Promise { const filteredLayersInfos = await this.getSHv3LayersInfo( baseUrl, reqConfig, filterLayers, preferGetCapabilities, + includeHighlights, ); return filteredLayersInfos.map( @@ -295,6 +299,7 @@ export class LayersFactory { reqConfig: RequestConfiguration, filterLayers: Function, preferGetCapabilities: boolean = true, + includeHighlights: boolean = false, ): Promise { let layersInfos; //also check if auth token is present @@ -303,11 +308,12 @@ export class LayersFactory { // use configuration if possible if (authToken && preferGetCapabilities === false) { try { - const layers = await fetchLayerParamsFromConfigurationService( - getSHServiceRootUrlFromBaseUrl(baseUrl), - parseSHInstanceId(baseUrl), + const layers = await fetchLayerParamsFromConfigurationService({ + shServiceHostName: getSHServiceRootUrlFromBaseUrl(baseUrl), + instanceId: parseSHInstanceId(baseUrl), + includeHighlights, reqConfig, - ); + }); layersInfos = layers.map((l: any) => ({ ...l, dataset: LayersFactory.matchDatasetFromGetCapabilities(l.type, baseUrl), diff --git a/src/layer/ProcessingDataFusionLayer.ts b/src/layer/ProcessingDataFusionLayer.ts index feb26f29..8b9089b7 100644 --- a/src/layer/ProcessingDataFusionLayer.ts +++ b/src/layer/ProcessingDataFusionLayer.ts @@ -32,6 +32,7 @@ interface ConstructorParameters { layers: DataFusionLayerInfo[]; title?: string | null; description?: string | null; + highlights?: AbstractSentinelHubV3Layer['highlights']; } export type DataFusionLayerInfo = { @@ -54,8 +55,9 @@ export class ProcessingDataFusionLayer extends AbstractSentinelHubV3Layer { evalscript = null, evalscriptUrl = null, layers, + highlights = null, }: ConstructorParameters) { - super({ title, description, evalscript, evalscriptUrl }); + super({ title, description, evalscript, evalscriptUrl, highlights }); this.layers = layers; } diff --git a/src/layer/S1GRDAWSEULayer.ts b/src/layer/S1GRDAWSEULayer.ts index 3cfb3d8a..eb4c8661 100644 --- a/src/layer/S1GRDAWSEULayer.ts +++ b/src/layer/S1GRDAWSEULayer.ts @@ -71,6 +71,7 @@ interface ConstructorParameters { backscatterCoeff?: BackscatterCoeff | null; orbitDirection?: OrbitDirection | null; speckleFilter?: SpeckleFilter | null; + highlights?: AbstractSentinelHubV3Layer['highlights']; } type S1GRDFindTilesDatasetParameters = { @@ -112,8 +113,19 @@ export class S1GRDAWSEULayer extends AbstractSentinelHubV3Layer { orbitDirection = null, speckleFilter = null, mosaickingOrder = null, + highlights = null, }: ConstructorParameters) { - super({ instanceId, layerId, evalscript, evalscriptUrl, dataProduct, title, description, legendUrl }); + super({ + instanceId, + layerId, + evalscript, + evalscriptUrl, + dataProduct, + title, + description, + legendUrl, + highlights, + }); this.acquisitionMode = acquisitionMode; this.polarization = polarization; this.resolution = resolution; diff --git a/src/layer/S3SLSTRLayer.ts b/src/layer/S3SLSTRLayer.ts index 2e053bf1..e98b8806 100644 --- a/src/layer/S3SLSTRLayer.ts +++ b/src/layer/S3SLSTRLayer.ts @@ -24,6 +24,7 @@ interface ConstructorParameters { legendUrl?: string | null; maxCloudCoverPercent?: number | null; view?: S3SLSTRView | null; + highlights?: AbstractSentinelHubV3WithCCLayer['highlights']; } export enum S3SLSTRView { diff --git a/src/layer/S5PL2Layer.ts b/src/layer/S5PL2Layer.ts index 657a418e..69447a90 100644 --- a/src/layer/S5PL2Layer.ts +++ b/src/layer/S5PL2Layer.ts @@ -37,6 +37,7 @@ interface ConstructorParameters { productType?: ProductType | null; maxCloudCoverPercent?: number | null; minQa?: number | null; + highlights?: AbstractSentinelHubV3Layer['highlights']; } type S5PL2FindTilesDatasetParameters = { @@ -63,8 +64,19 @@ export class S5PL2Layer extends AbstractSentinelHubV3Layer { productType = null, maxCloudCoverPercent = 100, minQa = null, + highlights = null, }: ConstructorParameters) { - super({ instanceId, layerId, evalscript, evalscriptUrl, dataProduct, title, description, legendUrl }); + super({ + instanceId, + layerId, + evalscript, + evalscriptUrl, + dataProduct, + title, + description, + legendUrl, + highlights, + }); this.productType = productType; this.maxCloudCoverPercent = maxCloudCoverPercent; this.minQa = minQa; diff --git a/src/layer/__tests__/fixtures.BYOCLayer.ts b/src/layer/__tests__/fixtures.BYOCLayer.ts index c5ba96b8..6d076a06 100644 --- a/src/layer/__tests__/fixtures.BYOCLayer.ts +++ b/src/layer/__tests__/fixtures.BYOCLayer.ts @@ -395,6 +395,7 @@ export function constructFixtureUpdateLayerFromServiceIfNeeded({ legend: undefined as any, title: 'byoc3', description: '', + highlights: undefined as any, }; return { diff --git a/src/layer/const.ts b/src/layer/const.ts index 2c3bd2a6..ad1f5bdb 100644 --- a/src/layer/const.ts +++ b/src/layer/const.ts @@ -301,3 +301,15 @@ export const XmlParserOptions = Object.freeze({ return isA; }, }); + +export interface Highlight { + id: string; + layerId: string; + instanceId: string; + title: string; + description: string; + areaOfInterest: object; + fromTime: string; + toTime: string; + orderHint: string; +} diff --git a/src/layer/utils.ts b/src/layer/utils.ts index 8071f0db..5937448d 100644 --- a/src/layer/utils.ts +++ b/src/layer/utils.ts @@ -188,17 +188,27 @@ export function getSHServiceRootUrlFromBaseUrl(baseUrl: string): string { return getSHServiceRootUrl(host); } -export async function fetchLayerParamsFromConfigurationService( - shServiceHostName: string, - instanceId: string, - reqConfig: RequestConfiguration, -): Promise { +export async function fetchLayerParamsFromConfigurationService({ + shServiceHostName, + instanceId, + includeHighlights, + reqConfig, +}: { + shServiceHostName: string; + instanceId: string; + includeHighlights?: boolean; + reqConfig: RequestConfiguration; +}): Promise { const authToken = reqConfig && reqConfig.authToken ? reqConfig.authToken : getAuthToken(); if (!authToken) { throw new Error('Must be authenticated to fetch layer params'); } const configurationServiceHostName = shServiceHostName ?? SH_SERVICE_ROOT_URL.default; - const url = `${configurationServiceHostName}api/v2/configuration/instances/${instanceId}/layers`; + const url = new URL(`${configurationServiceHostName}api/v2/configuration/instances/${instanceId}/layers`); + if (includeHighlights) { + url.searchParams.set('listHighlights', 'true'); + } + const headers = { Authorization: `Bearer ${authToken}`, }; @@ -218,7 +228,7 @@ export async function fetchLayerParamsFromConfigurationService( headers: headers, ...getAxiosReqParams(reqConfigWithMemoryCache, null), }; - const res = await axios.get(url, requestConfig); + const res = await axios.get(url.toString(), requestConfig); const layersParams = res.data.map((l: any) => { const defaultStyle = l.styles.find((s: any) => s.name === l.defaultStyleName) ?? l.styles[0]; @@ -236,6 +246,7 @@ export async function fetchLayerParamsFromConfigurationService( ? `${configurationServiceHostName}api/v2/configuration/datasets/${l.collectionType}/dataproducts/${defaultStyle.dataProductId}` : undefined, legend: defaultStyle ? defaultStyle.legend : null, + highlights: l.highlights?.member, }; }); return layersParams;