diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 4f8eaf418..ed90c208e 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -150,6 +150,7 @@ export const routes = [ services.PairService, services.SceneSelectService, services.OnDemandService, + services.PossibleHyp3JobsService, {provide: SAVER, useFactory: getSaver}, { provide: DateAdapter, diff --git a/src/app/components/results-menu/baseline-results-menu/baseline-results-menu.component.html b/src/app/components/results-menu/baseline-results-menu/baseline-results-menu.component.html index 98d2ec9e3..9155699ce 100644 --- a/src/app/components/results-menu/baseline-results-menu/baseline-results-menu.component.html +++ b/src/app/components/results-menu/baseline-results-menu/baseline-results-menu.component.html @@ -76,7 +76,7 @@ -
diff --git a/src/app/components/results-menu/baseline-results-menu/baseline-results-menu.component.ts b/src/app/components/results-menu/baseline-results-menu/baseline-results-menu.component.ts index 0b293d402..2239933a0 100644 --- a/src/app/components/results-menu/baseline-results-menu/baseline-results-menu.component.ts +++ b/src/app/components/results-menu/baseline-results-menu/baseline-results-menu.component.ts @@ -10,7 +10,7 @@ import * as queueStore from '@store/queue'; import { ScreenSizeService, MapService, ScenesService, PairService, - Hyp3Service + Hyp3Service, PossibleHyp3JobsService, } from '@services'; import { SubSink } from 'subsink'; @@ -64,6 +64,7 @@ export class BaselineResultsMenuComponent implements OnInit, OnDestroy { private scenesService: ScenesService, private pairService: PairService, private hyp3: Hyp3Service, + private possibleHyp3JobsService: PossibleHyp3JobsService, ) { } ngOnInit(): void { @@ -76,20 +77,19 @@ export class BaselineResultsMenuComponent implements OnInit, OnDestroy { this.products = products; this.downloadableProds = this.hyp3.downloadable(products); this.pairs = [ ...pairs, ...custom ]; - - this.hyp3able = this.hyp3.getHyp3ableProducts([ - ...this.products.map(prod => [prod]), - ...this.pairs?.sort((a, b) => { - if (a.metadata.date < b.metadata.date) { - return -1; - } - return 1; - }) - ]); } ) ); + this.subs.add( + this.possibleHyp3JobsService.possibleJobs$ + .subscribe( + possibleJobs => { + this.hyp3able = this.hyp3.getHyp3ableProducts(possibleJobs); + } + ) + ); + this.subs.add( this.screenSize.breakpoint$.subscribe( point => this.breakpoint = point diff --git a/src/app/components/results-menu/results-menu.component.html b/src/app/components/results-menu/results-menu.component.html index 59e3d4813..78289611f 100644 --- a/src/app/components/results-menu/results-menu.component.html +++ b/src/app/components/results-menu/results-menu.component.html @@ -19,7 +19,6 @@
-
diff --git a/src/app/components/results-menu/scene-detail/scene-detail.component.html b/src/app/components/results-menu/scene-detail/scene-detail.component.html index 5e074af16..46d44cbf4 100644 --- a/src/app/components/results-menu/scene-detail/scene-detail.component.html +++ b/src/app/components/results-menu/scene-detail/scene-detail.component.html @@ -274,12 +274,13 @@
{{ 'SEARCH' | translate | uppercase }}:
@@ -322,7 +323,7 @@
{{searchType !== searchTypes.SARVIEWS_EVENTS ? dataset?.id === 'OPERA-S1' ? ('SOURCE_DATA' | translate) : ('MORE_LIKE_THIS' | translate ) : ('GEOGRAPHIC' | translate )}} -
diff --git a/src/app/components/results-menu/scene-detail/scene-detail.component.ts b/src/app/components/results-menu/scene-detail/scene-detail.component.ts index 49dfea59f..640fe5b16 100644 --- a/src/app/components/results-menu/scene-detail/scene-detail.component.ts +++ b/src/app/components/results-menu/scene-detail/scene-detail.component.ts @@ -211,9 +211,11 @@ export class SceneDetailComponent implements OnInit, OnDestroy { } public sceneCanInSAR(): boolean { - return this.dataset.id === models.sentinel_1.id ? true : this.selectedProducts - .map(product => product.metadata.canInSAR) - .some(canInSAR => !!canInSAR); + return this.dataset.id === models.sentinel_1.id || + this.selectedProducts + .map(product => product.metadata.canInSAR) + .some(canInSAR => !!canInSAR); + ; } public baselineSceneName(): string { @@ -223,7 +225,7 @@ export class SceneDetailComponent implements OnInit, OnDestroy { if (this.dataset.id === models.sentinel_1.id) { return this.selectedProducts.filter( - product => product.metadata.productType === 'SLC' || product.metadata.productType === 'BURST' + product => product.metadata.productType === 'SLC' || product.metadata.productType === 'BURST' )[0].name; } else { return this.scene.name; @@ -233,8 +235,8 @@ export class SceneDetailComponent implements OnInit, OnDestroy { public sceneHasBrowse() { return ( !!this.scene.browses && - this.scene.browses.length > 0 && - !this.scene?.browses[0].includes('no-browse') + this.scene.browses.length > 0 && + !this.scene?.browses[0].includes('no-browse') ); } @@ -313,14 +315,14 @@ export class SceneDetailComponent implements OnInit, OnDestroy { this.browseIndex = newIndex; let [url, wkt] = this.searchType === this.searchTypes.SARVIEWS_EVENTS - ? [this.selectedEventProducts[this.browseIndex].files.browse_url, this.selectedEventProducts[this.browseIndex]?.granules[0].wkt] - : [this.scene.browses[this.browseIndex], this.scene.metadata.polygon]; + ? [this.selectedEventProducts[this.browseIndex].files.browse_url, this.selectedEventProducts[this.browseIndex]?.granules[0].wkt] + : [this.scene.browses[this.browseIndex], this.scene.metadata.polygon]; // for OPERA-S1 geotiffs // if(this.scene?.id.startsWith('OPERA')) { // url = this.scene.downloadUrl; // } - + this.mapService.setSelectedBrowse(url, wkt); } @@ -496,7 +498,7 @@ export class SceneDetailComponent implements OnInit, OnDestroy { private getBrowseCount() { return this.searchType === this.searchTypes.SARVIEWS_EVENTS - ? this.selectedEventProducts.length : this.scene.browses.length; + ? this.selectedEventProducts.length : this.scene.browses.length; } ngOnDestroy() { diff --git a/src/app/components/results-menu/scenes-list-header/scenes-list-header.component.ts b/src/app/components/results-menu/scenes-list-header/scenes-list-header.component.ts index 072d137a3..42b494f1b 100644 --- a/src/app/components/results-menu/scenes-list-header/scenes-list-header.component.ts +++ b/src/app/components/results-menu/scenes-list-header/scenes-list-header.component.ts @@ -1,7 +1,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { saveAs } from 'file-saver'; -import { combineLatest, } from 'rxjs'; +import { combineLatest } from 'rxjs'; import { debounceTime, filter, map, tap } from 'rxjs/operators'; import { Store } from '@ngrx/store'; @@ -14,7 +14,7 @@ import * as filtersStore from '@store/filters'; import { faCopy } from '@fortawesome/free-solid-svg-icons'; import { - MapService, ScenesService, ScreenSizeService, + MapService, ScenesService, ScreenSizeService, PossibleHyp3JobsService, PairService, Hyp3Service, SarviewsEventsService, NotificationService } from '@services'; @@ -35,7 +35,6 @@ export class ScenesListHeaderComponent implements OnInit, OnDestroy { public pairs$ = this.pairService.pairs$; private pairProducts$ = this.pairService.productsFromPairs$; - public totalResultCount$ = combineLatest([ this.store$.select(searchStore.getSearchAmount), this.scenesService.scenes$] @@ -56,7 +55,7 @@ export class ScenesListHeaderComponent implements OnInit, OnDestroy { public downloadableProds = []; public sarviewsEventProducts: SarviewsProduct[] = []; public pinnedEventIDs: string[]; - + public productsByType$: Observable<{[key:string]: models.CMRProduct[]}> = this.store$.select(scenesStore.getAllProducts).pipe( map((scenes: []) => scenes.reduce((prev, curr: models.CMRProduct) => { @@ -85,8 +84,7 @@ export class ScenesListHeaderComponent implements OnInit, OnDestroy { private products$ = this.scenesService.products$(); private operaProductsByType: {[key:string]: models.CMRProduct[]} = {} - public isBurstStack$ = - combineLatest([ + public isBurstStack$ = combineLatest([ this.products$, this.pairService.pairs$, this.store$.select(searchStore.getSearchType), @@ -97,11 +95,12 @@ export class ScenesListHeaderComponent implements OnInit, OnDestroy { || (currentSearchType === this.SearchTypes.SBAS && ( (pairs.pairs?.length > 0 ? pairs.pairs[0][0].metadata.productType === 'BURST' : false) - || (pairs.custom?.length > 0 ? pairs.custom[0][0].metadata.productType === 'BURST' : false) + || (pairs.custom?.length > 0 ? pairs.custom[0][0].metadata.productType === 'BURST' : false) ) + ) ) ) - ) + public numPairs$ = this.pairService.pairs$.pipe( filter(pairs => !!pairs), map(pairs => pairs.pairs.length + pairs.custom.length) @@ -195,6 +194,7 @@ export class ScenesListHeaderComponent implements OnInit, OnDestroy { private hyp3: Hyp3Service, private clipboard: ClipboardService, private notificationService: NotificationService, + private possibleHyp3JobsService: PossibleHyp3JobsService, ) { } ngOnInit() { @@ -205,22 +205,23 @@ export class ScenesListHeaderComponent implements OnInit, OnDestroy { ); this.subs.add( - combineLatest([ - this.products$, - this.pairs$ - ] - ).subscribe( - ([products, { pairs, custom }]) => { - this.products = products; - this.downloadableProds = this.hyp3.downloadable(products); - this.pairs = [...pairs, ...custom]; - - this.hyp3able = this.hyp3.getHyp3ableProducts([ - ...this.products.map(prod => [prod]), - ...this.pairs - ]); - } - ) + this.possibleHyp3JobsService.possibleJobs$ + .subscribe( + possibleJobs => { + this.hyp3able = this.hyp3.getHyp3ableProducts(possibleJobs); + } + ) + ); + + this.subs.add( + this.products$.subscribe(products => { + this.products = products; + this.downloadableProds = this.hyp3.downloadable(products); + }) + ); + + this.subs.add( + this.pairs$.subscribe(({pairs, custom}) => this.pairs = [...pairs, ...custom]) ); this.subs.add( @@ -228,16 +229,16 @@ export class ScenesListHeaderComponent implements OnInit, OnDestroy { this.scenesService.scenes$, this.store$.select(filtersStore.getProductTypes), this.store$.select(searchStore.getSearchType), - this.store$.select(filtersStore.getSelectedDataset)] + this.store$.select(filtersStore.getSelectedDataset)] ).pipe( - debounceTime(250) - ).subscribe(([scenes, productTypes, searchType, selectedDataset]) => { - this.canHideRawData = - searchType === models.SearchType.DATASET && - scenes.every(scene => scene.dataset === 'Sentinel-1B' || scene.dataset === 'Sentinel-1A') && - productTypes.length <= 0 - && selectedDataset.id !== models.opera_s1.id; - }) + debounceTime(250) + ).subscribe(([scenes, productTypes, searchType, selectedDataset]) => { + this.canHideRawData = + searchType === models.SearchType.DATASET && + scenes.every(scene => scene.dataset === 'Sentinel-1B' || scene.dataset === 'Sentinel-1A') && + productTypes.length <= 0 + && selectedDataset.id !== models.opera_s1.id; + }) ); this.subs.add( @@ -374,22 +375,22 @@ export class ScenesListHeaderComponent implements OnInit, OnDestroy { public onDownloadPairCSV() { const pairRows = this.pairs - .map(([reference, secondary]) => { + .map(([reference, secondary]) => { - const temporalBaseline = Math.abs(reference.metadata.temporal - secondary.metadata.temporal); - const perpendicularBaseline = Math.abs(reference.metadata.perpendicular - secondary.metadata.perpendicular); + const temporalBaseline = Math.abs(reference.metadata.temporal - secondary.metadata.temporal); + const perpendicularBaseline = Math.abs(reference.metadata.perpendicular - secondary.metadata.perpendicular); - return ( - `${reference.name},${reference.downloadUrl},` + + return ( + `${reference.name},${reference.downloadUrl},` + `${secondary.name},${secondary.downloadUrl},` + `${perpendicularBaseline},${temporalBaseline}` - ); - }) - .join('\n'); + ); + }) + .join('\n'); const pairsHeader = `Reference, Reference URL, Secondary, Secondary URL, ` + - `Pair Perpendicular Baseline (meters), Pair Temporal Baseline (days)`; + `Pair Perpendicular Baseline (meters), Pair Temporal Baseline (days)`; const pairsCSV = `${pairsHeader}\n${pairRows}`; diff --git a/src/app/components/results-menu/scenes-list/scenes-list.component.html b/src/app/components/results-menu/scenes-list/scenes-list.component.html index 46a12e8b5..b498a4f22 100644 --- a/src/app/components/results-menu/scenes-list/scenes-list.component.html +++ b/src/app/components/results-menu/scenes-list/scenes-list.component.html @@ -25,7 +25,7 @@ [offsets]="offsets" [searchType]="searchType" [isQueued]="allQueued[getGroupCriteria(scene)]" - [hyp3ableByJobType]="hyp3ableByScene[scene.groupId]" + [hyp3ableByJobType]="hyp3ableByScene[scene.groupId] || hyp3ableByScene[scene.name]" [jobQueued]="allJobNames.includes(scene.name)" [numQueued]="numberOfQueue[scene.groupId]" [isSelected]="scene.id === selected"> diff --git a/src/app/components/results-menu/scenes-list/scenes-list.component.ts b/src/app/components/results-menu/scenes-list/scenes-list.component.ts index 29e971504..9264a6bb4 100644 --- a/src/app/components/results-menu/scenes-list/scenes-list.component.ts +++ b/src/app/components/results-menu/scenes-list/scenes-list.component.ts @@ -165,6 +165,7 @@ export class ScenesListComponent implements OnInit, OnDestroy, AfterContentInit ) ); + this.subs.add( this.pairs$.subscribe( pairs => { @@ -188,20 +189,52 @@ export class ScenesListComponent implements OnInit, OnDestroy, AfterContentInit ) ); + const baselineReference$ = combineLatest( + this.store$.select(scenesStore.getScenes), + this.store$.select(scenesStore.getMasterName), + this.store$.select(searchStore.getSearchType) + ).pipe( + map( + ([scenes, referenceName, searchType]) => { + if (searchType === models.SearchType.BASELINE && !!referenceName) { + const referenceSceneIdx = scenes.findIndex(scene => scene.name === referenceName); - this.store$.select(scenesStore.getAllSceneProducts).subscribe( - searchScenes => { + if (referenceSceneIdx !== -1) { + return scenes[referenceSceneIdx]; + } + } else { + return null; + } + } + ), + ); + + + this.store$.select(scenesStore.getAllSceneProducts).pipe( + withLatestFrom(baselineReference$) + ) + + .subscribe( + ([searchScenes, baselineReference]) => { this.hyp3ableByScene = {}; + Object.entries(searchScenes).forEach(([groupId, products]) => { - const hyp3able = this.hyp3.getHyp3ableProducts( - (products).map(product => [product]) - ); + const possibleJobs = []; + (products).forEach(product => { + possibleJobs.push([product]); - this.hyp3ableByScene[groupId] = hyp3able; - }); - } - ); + if (!!baselineReference) { + possibleJobs.push([baselineReference, product]); + } + }); + + const hyp3able = this.hyp3.getHyp3ableProducts(possibleJobs); + + this.hyp3ableByScene[groupId] = hyp3able; + }); + } + ); const queueScenes$ = combineLatest([ this.store$.select(queueStore.getQueuedProducts), @@ -263,8 +296,8 @@ export class ScenesListComponent implements OnInit, OnDestroy, AfterContentInit this.store$.select(searchStore.getIsLoading).pipe( filter(_ => !!this.scroll) ).subscribe( - _ => this.scroll.scrollToOffset(0) - ) + _ => this.scroll.scrollToOffset(0) + ) ); this.subs.add( @@ -278,14 +311,14 @@ export class ScenesListComponent implements OnInit, OnDestroy, AfterContentInit return Math.max(0, sceneIdx - 1); }) ).subscribe( - idx => { - if (!this.selectedFromList) { - this.scrollTo(idx); - } + idx => { + if (!this.selectedFromList) { + this.scrollTo(idx); + } - this.selectedFromList = false; - } - ) + this.selectedFromList = false; + } + ) ); } @@ -308,12 +341,12 @@ export class ScenesListComponent implements OnInit, OnDestroy, AfterContentInit return Math.max(0, sceneIdx - 1); }) ).subscribe( - idx => { - if (!this.selectedFromList) { - this.scrollTo(idx); + idx => { + if (!this.selectedFromList) { + this.scrollTo(idx); + } } - } - )); + )); this.subs.add(this.pairs$.pipe( filter(loaded => !!loaded), @@ -328,12 +361,12 @@ export class ScenesListComponent implements OnInit, OnDestroy, AfterContentInit return Math.max(0, sceneIdx - 1); }) ).subscribe( - idx => { - if (!this.selectedFromList) { - this.scrollTo(idx); + idx => { + if (!this.selectedFromList) { + this.scrollTo(idx); + } } - } - ) + ) ); } @@ -362,7 +395,7 @@ export class ScenesListComponent implements OnInit, OnDestroy, AfterContentInit public getGroupCriteria(scene: CMRProduct): string { const ungrouped_product_types = [...models.opera_s1.productTypes, {apiValue: 'BURST'}, {apiValue: 'BURST_XML'}].map(m => m.apiValue) if(ungrouped_product_types.includes(scene.metadata.productType)) { - return scene.metadata.parentID || scene.id; + return scene.metadata.parentID || scene.id; } return scene.groupId; } diff --git a/src/app/components/shared/on-demand-add-menu/on-demand-add-menu.component.html b/src/app/components/shared/on-demand-add-menu/on-demand-add-menu.component.html index 4d50e1e8f..ca9b2be11 100644 --- a/src/app/components/shared/on-demand-add-menu/on-demand-add-menu.component.html +++ b/src/app/components/shared/on-demand-add-menu/on-demand-add-menu.component.html @@ -13,26 +13,6 @@ info - -
- - - -
@@ -50,19 +30,3 @@
- - - - -
-
- -
-
-
-
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 752bbca9c..82ff5df2d 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 @@ -92,37 +92,39 @@ export class OnDemandAddMenuComponent implements OnInit { } const slcProducts = this.findSLCs(byProductType).products; - return slcProducts.length >= 1 && - this.isNotReferenceScene(slcProducts); + return ( + slcProducts.length >= 1 && + this.isNotReferenceScene(slcProducts) + ); } private findSLCs(byProductType: Hyp3ableByProductType[]): Hyp3ableByProductType { - return byProductType.find(prod => prod.productType === 'SLC'); + return byProductType.find(prod => prod.productType === 'SLC' || prod.productType === 'BURST'); } private isNotReferenceScene(products: CMRProduct[][]): boolean { return products[0][0].id !== this.referenceScene.id || - products[products.length - 1][0].id !== this.referenceScene.id; + products[products.length - 1][0].id !== this.referenceScene.id; } public queueBaselinePairOnDemand(products: models.CMRProduct[][], job_type: models.Hyp3JobType) { products = products.filter(prod => prod[0].id !== this.referenceScene.id); const jobs: models.QueuedHyp3Job[] = products.map(product => { return { - granules: [this.referenceScene, product[0]]?.sort((a, b) => { - if (a.metadata.date < b.metadata.date) { - return -1; - } - return 1; - }), - job_type - } as models.QueuedHyp3Job; - }); + granules: [this.referenceScene, product[0]]?.sort((a, b) => { + if (a.metadata.date < b.metadata.date) { + return -1; + } + return 1; + }), + job_type + } as models.QueuedHyp3Job; + }); this.store$.dispatch(new queueStore.AddJobs(jobs)); } - public onOpenHelp(infoUrl) { + public onOpenHelp(infoUrl: string) { window.open(infoUrl); } } diff --git a/src/app/components/shared/selectors/dataset-selector/dataset-selector.component.html b/src/app/components/shared/selectors/dataset-selector/dataset-selector.component.html index fd5a790ce..0f538bbb3 100644 --- a/src/app/components/shared/selectors/dataset-selector/dataset-selector.component.html +++ b/src/app/components/shared/selectors/dataset-selector/dataset-selector.component.html @@ -26,7 +26,8 @@
{{ dataset.name }} - + (beta) diff --git a/src/app/components/shared/selectors/job-product-name-selector/job-product-name-selector.component.ts b/src/app/components/shared/selectors/job-product-name-selector/job-product-name-selector.component.ts index df137f13c..9a5d0e90f 100644 --- a/src/app/components/shared/selectors/job-product-name-selector/job-product-name-selector.component.ts +++ b/src/app/components/shared/selectors/job-product-name-selector/job-product-name-selector.component.ts @@ -58,14 +58,26 @@ export class JobProductNameSelectorComponent implements OnInit, OnDestroy { ); const fileNames = this.scenesService.scenes$.pipe( - map(scenes => - scenes.map(scene => scene.metadata.fileName.toLowerCase().split('.')[0]) + map(scenes => scenes + .map( + scene => { + const filename = scene.metadata.fileName || ''; + return filename.toLowerCase().split('.')[0]; + }) ) ); this.subs.add( this.store$.select(getScenes).subscribe( - res => this.unfilteredScenes = Array.from(new Set(res.map(scene => scene.metadata.fileName.toLowerCase().split('.')[0]))) + res => this.unfilteredScenes = Array.from( + new Set( + res + .map(scene => { + const filename = scene.metadata.fileName || ''; + return filename.toLowerCase().split('.')[0]; + }) + ) + ) ) ); diff --git a/src/app/models/hyp3-jobs.model.ts b/src/app/models/hyp3-jobs.model.ts index db6ef1227..d2fff318c 100644 --- a/src/app/models/hyp3-jobs.model.ts +++ b/src/app/models/hyp3-jobs.model.ts @@ -1,4 +1,4 @@ -import { sentinel_1 } from './dataset.model'; +import { sentinel_1, sentinel_1_bursts } from './dataset.model'; import { Hyp3JobType, JobOptionType } from './hyp3-job-type.model'; export const RtcGammaJobType: Hyp3JobType = { @@ -230,6 +230,50 @@ export const InsarGammaJobType: Hyp3JobType = { }] }; +export const InsarIsceBurstJobType: Hyp3JobType = { + id: 'INSAR_ISCE_BURST', + name: 'InSAR ISCE Burst', + infoUrl: 'https://hyp3-docs.asf.alaska.edu/guides/burst_insar_product_guide/', + description: `INSAR_DESC`, + numProducts: 2, + productTypes: [{ + dataset: sentinel_1_bursts, + productTypes: [ + 'BURST', + ], + beamModes: ['IW'], + polarizations: [ + 'VV', 'HH' + ] + }], + options: [{ + name: 'Looks', + apiName: 'looks', + type: JobOptionType.DROPDOWN, + options: [{ + name: '20x4', + apiValue: '20x4' + }, { + name: '10x2', + apiValue: '10x2' + }, { + name: '5x1', + apiValue: '5x1' + }], + default: '20x4', + info: `Number of looks to take in range and azimuth.` + }, { + name: 'Water Mask', + apiName: 'apply_water_mask', + type: JobOptionType.TOGGLE, + default: false, + info: ` + Sets pixels over coastal and large inland waterbodies as invalid + for phase unwrapping. + ` + }] +}; + export const AutoRift: Hyp3JobType = { id: 'AUTORIFT', name: 'autoRIFT', @@ -252,6 +296,7 @@ export const AutoRift: Hyp3JobType = { export const hyp3JobTypes = { RTC_GAMMA: RtcGammaJobType, INSAR_GAMMA: InsarGammaJobType, + INSAR_ISCE_BURST: InsarIsceBurstJobType, AUTORIFT: AutoRift }; diff --git a/src/app/services/hyp3.service.ts b/src/app/services/hyp3.service.ts index 633f00d63..1aae1ff7d 100644 --- a/src/app/services/hyp3.service.ts +++ b/src/app/services/hyp3.service.ts @@ -179,6 +179,7 @@ export class Hyp3Service { if (a.metadata.date < b.metadata.date) { return -1; } + return 1; })); }); diff --git a/src/app/services/index.ts b/src/app/services/index.ts index 905448f6e..07ed8c0b1 100644 --- a/src/app/services/index.ts +++ b/src/app/services/index.ts @@ -33,3 +33,4 @@ export { SarviewsEventsService } from './sarviews-events.service'; export { BrowseOverlayService } from './browse-overlay.service'; export { ThemingService } from './theming.service'; export { ExportService } from './export.service'; +export { PossibleHyp3JobsService } from './possible-hyp3-jobs.service'; diff --git a/src/app/services/possible-hyp3-jobs.service.ts b/src/app/services/possible-hyp3-jobs.service.ts new file mode 100644 index 000000000..07b592cd3 --- /dev/null +++ b/src/app/services/possible-hyp3-jobs.service.ts @@ -0,0 +1,101 @@ +import { Injectable } from '@angular/core'; + +import { combineLatest, of } from 'rxjs'; +import { map, switchMap, withLatestFrom } from 'rxjs/operators'; +import { Store } from '@ngrx/store'; + +import { AppState } from '@store'; +import * as scenesStore from '@store/scenes'; +import * as searchStore from '@store/search'; + +import * as models from '@models'; + +import { + ScenesService, PairService, +} from '@services'; + +@Injectable({ + providedIn: 'root' +}) +export class PossibleHyp3JobsService { + private products$ = this.scenesService.products$(); + public pairs$ = this.pairService.pairs$; + + private possibleProductJobs$ = this.products$.pipe(map( + products => products.map(p => [p]) + )); + + private referenceScene$ = this.store$.select(scenesStore.getScenes).pipe( + withLatestFrom(this.store$.select(scenesStore.getMasterName)), + map( + ([scenes, referenceName]) => { + if (!!referenceName) { + const referenceSceneIdx = scenes.findIndex(scene => scene.name === referenceName); + + if (referenceSceneIdx !== -1) { + return scenes[referenceSceneIdx]; + } + } + } + )); + + private hyp3ableJobsSBAS$ = combineLatest([ + this.possibleProductJobs$, + this.pairs$, + ]).pipe( + map(([possibleJobs, {pairs, custom}]) => { + const allPossiblePairJobs = [...pairs, ...custom]; + + return allPossiblePairJobs.concat(possibleJobs); + })); + + private hyp3ableJobsBaseline$ = combineLatest([ + this.products$, + this.referenceScene$ + ]).pipe( + map(([products, referenceScene]) => { + if (!referenceScene) { + return products.map(p => [p]); + } + + const baselinePairs = this.makeBaselinePairs(products, referenceScene); + return baselinePairs.concat(products.map(p => [p])); + }) + ); + + private makeBaselinePairs(products: models.CMRProduct[], referenceScene: models.CMRProduct) { + products = products.filter(prod => prod.id !== referenceScene.id); + + const pairedJobs: models.CMRProduct[][] = products.map(product => { + return [referenceScene, product]?.sort((a, b) => { + if (a.metadata.date < b.metadata.date) { + return -1; + } + return 1; + }) + }); + + return pairedJobs; + } + + public possibleJobs$ = this.store$.select(searchStore.getSearchType).pipe( + switchMap((searchType: models.SearchType) => { + if (searchType === models.SearchType.DATASET || searchType === models.SearchType.LIST) { + return this.possibleProductJobs$; + } + else if (searchType === models.SearchType.SBAS) { + return this.hyp3ableJobsSBAS$; + } else if (searchType === models.SearchType.BASELINE) { + return this.hyp3ableJobsBaseline$; + } else { + return of([]); + } + }) + ); + + constructor( + private store$: Store, + private scenesService: ScenesService, + private pairService: PairService, + ) { } +} diff --git a/src/app/services/scenes.service.ts b/src/app/services/scenes.service.ts index 32f86c65e..b62dfb1de 100644 --- a/src/app/services/scenes.service.ts +++ b/src/app/services/scenes.service.ts @@ -41,8 +41,9 @@ export class ScenesService { this.jobStatusFilter$( this.filterBaselineValues$( this.filterByDate$( + this.filterBurstSubproducts$( this.store$.select(getAllProducts) - )))))) + ))))))) ); } @@ -53,8 +54,9 @@ export class ScenesService { this.hideS1Raw$( this.filterBaselineValues$( this.filterByDate$( + this.filterBurstSubproducts$( this.store$.select(getScenes) - ))))))); + )))))))); public withBrowses$(scenes$: Observable): Observable { @@ -170,16 +172,30 @@ export class ScenesService { return scenes .filter(scene => { - const statusCode = scene.metadata.job.status_code; - - return ( - statuses.has(statusCode) - ); + const job = scene.metadata.job; + if(!!job) { + const statusCode = job.status_code; + return statuses.has(statusCode) + } + return false; }); }) ); } + private filterBurstSubproducts$(scenes$: Observable) { + return combineLatest([ + scenes$, + this.store$.select(getSearchType) + ]).pipe( + map(([scenes, searchtype]) => { + if (searchtype === SearchType.CUSTOM_PRODUCTS) { + return scenes.filter(scene => scene.productTypeDisplay !== 'XML Metadata (BURST)'); + } + return scenes; + }) + ) + } private hideExpired$(scenes$: Observable) { return combineLatest([ scenes$, diff --git a/src/app/store/scenes/scenes.reducer.ts b/src/app/store/scenes/scenes.reducer.ts index 8bb8d4bb9..ae7f8be16 100644 --- a/src/app/store/scenes/scenes.reducer.ts +++ b/src/app/store/scenes/scenes.reducer.ts @@ -60,7 +60,7 @@ export const initState: ScenesState = { perpendicularSort: ColumnSortDirection.NONE, temporalSort: ColumnSortDirection.NONE, pinnedProductBrowses: {} -}; +} export function scenesReducer(state = initState, action: ScenesActions): ScenesState { @@ -763,4 +763,4 @@ function eqSet(aSet, bSet): boolean { function isSubProduct(product): boolean { return !!product.metadata.parentID; -} \ No newline at end of file +} diff --git a/src/app/store/search/search.effect.ts b/src/app/store/search/search.effect.ts index 90ad4e2f7..d7e847e9a 100644 --- a/src/app/store/search/search.effect.ts +++ b/src/app/store/search/search.effect.ts @@ -339,11 +339,12 @@ export class SearchEffects { } ).join(','); - const collections = this.asfApiService.collections(); - - return this.asfApiService.query({ 'granule_list': granules, 'collections': collections.join(',') }).pipe( + return this.asfApiService.query({ 'granule_list': granules }).pipe( map(results => this.productService.fromResponse(results) - .filter(product => !product.metadata.productType.includes('METADATA')) + .filter(product => { + return !product.metadata.productType.includes('METADATA') || + !product.metadata.productType.includes('BURST_XML'); + }) .reduce((products, product) => { products[product.name] = product; return products; @@ -363,7 +364,6 @@ export class SearchEffects { ), catchError( _ => { - console.log(_); return of(new SearchError(`Error loading search results`)); } ), @@ -417,7 +417,6 @@ export class SearchEffects { ), catchError( _ => { - console.log(_); return of(new SearchError(`Error loading next batch of On Demand results`)); } ), @@ -438,6 +437,7 @@ export class SearchEffects { const virtualProducts = jobs .filter(job => products[job.job_parameters.granules[0]]) .map(job => { + const product = products[job.job_parameters.granules[0]]; const jobFile = !!job.files ? job.files[0] : @@ -449,7 +449,7 @@ export class SearchEffects { job.job_parameters.scenes.push(products[scene_key]); } - return { + const jobProduct = { ...product, browses: job.browse_images ? job.browse_images : ['assets/no-browse.png'], thumbnail: job.thumbnail_images ? job.thumbnail_images[0] : 'assets/no-thumb.png', @@ -460,11 +460,13 @@ export class SearchEffects { id: job.job_id, metadata: { ...product.metadata, - fileName: jobFile.filename, + fileName: jobFile.filename || '', productType: job.job_type, job }, }; + + return jobProduct }); return virtualProducts;