diff --git a/src/app/components/shared/on-demand-add-menu/on-demand-add-menu.component.ts b/src/app/components/shared/on-demand-add-menu/on-demand-add-menu.component.ts index 829689263..2dc56a1b0 100644 --- a/src/app/components/shared/on-demand-add-menu/on-demand-add-menu.component.ts +++ b/src/app/components/shared/on-demand-add-menu/on-demand-add-menu.component.ts @@ -96,9 +96,9 @@ export class OnDemandAddMenuComponent implements OnInit { ); } - public queueAllOnDemand(products: models.CMRProduct[][], job_type: models.Hyp3JobType): void { - const jobs: models.QueuedHyp3Job[] = products.map(product => ({ - granules: [...product].sort((a, b) => { + public queueAllOnDemand(productsGroups: models.CMRProduct[][], job_type: models.Hyp3JobType): void { + const jobs: models.QueuedHyp3Job[] = productsGroups.map(products => ({ + granules: [...products].sort((a, b) => { if (a.metadata.date < b.metadata.date) { return -1; } diff --git a/src/app/models/hyp3/hyp3-job-type.model.ts b/src/app/models/hyp3/hyp3-job-type.model.ts index 531f307b6..6dbb1fa2c 100644 --- a/src/app/models/hyp3/hyp3-job-type.model.ts +++ b/src/app/models/hyp3/hyp3-job-type.model.ts @@ -17,6 +17,7 @@ export interface Hyp3JobProductType { productTypes: string[]; beamModes: string[]; polarizations: string[]; + dateRange?: Range; } export interface Hyp3JobOption { diff --git a/src/app/models/hyp3/hyp3-jobs.model.ts b/src/app/models/hyp3/hyp3-jobs.model.ts index b06bc25ae..093ae1494 100644 --- a/src/app/models/hyp3/hyp3-jobs.model.ts +++ b/src/app/models/hyp3/hyp3-jobs.model.ts @@ -278,11 +278,37 @@ export const AutoRift: Hyp3JobType = { options: [] }; +export const OperaRtcS1JobType: Hyp3JobType = { + id: 'OPERA_RTC_S1', + name: 'OPERA RTC S1', + infoUrl: 'https://hyp3-docs.asf.alaska.edu/guides/opera_rtc_product_guide/', + description: 'OPERA_RTC_S1_DESC', + numProducts: 1, + productTypes: [{ + dataset: sentinel_1_bursts, + productTypes: [ + 'BURST', + ], + beamModes: ['IW'], + polarizations: [ + 'VV', 'HH', 'HV', 'VH' + ], + dateRange: { + // Disallow IPF version < 002.70 according to the dates given at https://sar-mpc.eu/processor/ipf/ + start: new Date('2016/04/14 00:00:00 UTC'), + // Opera RTC forward processing start date + end: new Date('2022/01/1 00:00:00 UTC') + }, + }], + options: [] +} + export const hyp3JobTypes = { RTC_GAMMA: RtcGammaJobType, INSAR_GAMMA: InsarGammaJobType, INSAR_ISCE_BURST: InsarIsceBurstJobType, - AUTORIFT: AutoRift + AUTORIFT: AutoRift, + OPERA_RTC_S1: OperaRtcS1JobType }; export const hyp3JobTypesList = Object.values(hyp3JobTypes); diff --git a/src/app/services/hyp3/hyp3-api.service.ts b/src/app/services/hyp3/hyp3-api.service.ts index 45adbabcb..5b7f7a872 100644 --- a/src/app/services/hyp3/hyp3-api.service.ts +++ b/src/app/services/hyp3/hyp3-api.service.ts @@ -223,6 +223,17 @@ export class Hyp3ApiService { hyp3ableProducts.forEach(product => { const prodType = product[0].metadata.productType; + + if (models.OperaRtcS1JobType.id === jobType.id) { + product = product.map(p => { + if (this.isCrossPolBurst(p)) { + return this.makeCoPolBurst(p); + } else { + return p; + } + }) + } + byProdType[prodType].push(product?.sort((a, b) => { if (a.metadata.date < b.metadata.date) { return -1; @@ -232,9 +243,10 @@ export class Hyp3ApiService { })); }); - const byProductType: models.Hyp3ableByProductType[] = Object.entries(byProdType).map(([productType, prods]) => ({ - productType, products: prods - })); + const byProductType: models.Hyp3ableByProductType[] = Object.entries(byProdType) + .map(([productType, prods]) => { + return ({ productType, products: prods }); + }); return { jobType, @@ -250,6 +262,31 @@ export class Hyp3ApiService { return ({ byJobType, total }); } + private isCrossPolBurst(product: models.CMRProduct) { + return product.metadata.polarization === 'HV' || + product.metadata.polarization === 'VH'; + } + + private makeCoPolBurst(product: models.CMRProduct) { + const crossPol = product.metadata.polarization; + const polarization = crossPol === 'VH' ? 'VV' : 'HH'; + + const name = product.name.replace(`_${crossPol}_`, `_${polarization}_`); + const file = product.file.replace(`_${crossPol}_`, `_${polarization}_`); + const id = product.id.replace(`_${crossPol}_`, `_${polarization}_`); + const downloadUrl = product.downloadUrl.replace(`/${crossPol}/`, `/${polarization}/`); + + const coPolProduct = { + ...product, + name, file, id, downloadUrl, + metadata: { + ...product.metadata, polarization + }, + }; + + return coPolProduct; + } + public getValidJobTypes(product: models.CMRProduct[]): models.Hyp3JobType[] { return models.hyp3JobTypesList.filter(jobType => this.isHyp3able(product, jobType)); } @@ -261,15 +298,37 @@ export class Hyp3ApiService { const types = new Set(productType.productTypes); const pols = new Set(productType.polarizations); const beamModes = new Set(productType.beamModes); + return products.every(product => - types.has(product.metadata.productType) && - pols.has(product.metadata.polarization) && - beamModes.has(product.metadata.beamMode) + types.has(product.metadata.productType) && + pols.has(product.metadata.polarization) && + beamModes.has(product.metadata.beamMode) && + this.withinDateRange(product.metadata.date.toDate(), productType.dateRange) && + product.dataset !== 'Sentinel-1C' ); }) ); } + public withinDateRange(check: Date, dateRange: models.DateRange | null) { + if (!dateRange) { + return true; + } + + const { start, end } = dateRange; + let isAfterStart = true; + let isBeforeEnd = true; + + if (start !== null) { + isAfterStart = check.getTime() >= start.getTime(); + } + if (end !== null) { + isBeforeEnd = check.getTime() <= end.getTime(); + } + + return isAfterStart && isBeforeEnd; + } + public getExpiredHyp3ableObject(scene: models.CMRProduct): { byJobType: models.Hyp3ableProductByJobType[]; total: number } { const job_types = models.hyp3JobTypes; const job_type = Object.keys(job_types).find(id => { diff --git a/src/app/store/queue/queue.reducer.ts b/src/app/store/queue/queue.reducer.ts index f4a348580..1c1c1aec7 100644 --- a/src/app/store/queue/queue.reducer.ts +++ b/src/app/store/queue/queue.reducer.ts @@ -183,24 +183,34 @@ export function queueReducer(state = initState, action: QueueActions): QueueStat case QueueActionType.ADD_JOBS: { const jobs = [...state.customJobs]; - const new_jobs = [...action.payload]; - let _duplicates = 0; - const jobsToQueue = new_jobs.filter(new_job => - !jobs.some(old_job => { - const result = old_job.job_type === new_job.job_type && - sameGranules(old_job.granules, new_job.granules); - if (result) { - _duplicates += 1; - } - return result; + const jobsToAdd = [...action.payload]; + let duplicates = 0; + + const makeJobId = (job: QueuedHyp3Job) => { + const granuleIds = job.granules.map(g => g?.id).join('-'); + return `${job.job_type.id}--${granuleIds}` + } + + const uniqueJobs = new Set(jobs.map(makeJobId)); + + const jobsToQueue = jobsToAdd.filter(jobToAdd => { + const jobId = makeJobId(jobToAdd); + + const isDuplicate = uniqueJobs.has(jobId); + + if (isDuplicate) { + duplicates += 1; + } else { + uniqueJobs.add(jobId); } - ) - ); + + return !isDuplicate; + }); return { ...state, customJobs: [...jobs, ...jobsToQueue], - duplicates: _duplicates + duplicates: duplicates }; }