diff --git a/src/app/components/baseline-chart/baseline-chart.component.ts b/src/app/components/baseline-chart/baseline-chart.component.ts index 3ca4236fe..59748098d 100644 --- a/src/app/components/baseline-chart/baseline-chart.component.ts +++ b/src/app/components/baseline-chart/baseline-chart.component.ts @@ -79,7 +79,7 @@ export class BaselineChartComponent implements OnInit, OnDestroy { ngOnInit(): void { this.createSVG(); - const products$ = this.scenesService.scenes$().pipe( + const products$ = this.scenesService.scenes$.pipe( tap(products => products.map( product => this.criticalBaseline = criticalBaselineFor(product) )), diff --git a/src/app/components/filters-dropdown/dataset-filters/dataset-filters.component.html b/src/app/components/filters-dropdown/dataset-filters/dataset-filters.component.html index 073354819..e9c6d76d7 100644 --- a/src/app/components/filters-dropdown/dataset-filters/dataset-filters.component.html +++ b/src/app/components/filters-dropdown/dataset-filters/dataset-filters.component.html @@ -130,4 +130,20 @@ + + + + + + OPERA S1 Filters + + +
+ +
+
diff --git a/src/app/components/filters-dropdown/dataset-filters/dataset-filters.module.ts b/src/app/components/filters-dropdown/dataset-filters/dataset-filters.module.ts index 407d93157..5c8c537c0 100644 --- a/src/app/components/filters-dropdown/dataset-filters/dataset-filters.module.ts +++ b/src/app/components/filters-dropdown/dataset-filters/dataset-filters.module.ts @@ -18,6 +18,7 @@ import { DatasetSelectorModule } from '@components/shared/selectors/dataset-sele import { AoiOptionsModule } from '@components/shared/aoi-options'; import { DocsModalModule } from '@components/shared/docs-modal'; import { BurstSelectorModule } from '@components/shared/selectors/burst-selector'; +import { OperaS1SelectorModule } from '@components/shared/selectors/opera-s1-selector'; // import { TranslateModule } from "@ngx-translate/core"; import { SharedModule } from "@shared"; @@ -42,6 +43,7 @@ import { SharedModule } from "@shared"; AoiOptionsModule, SearchTypeSelectorModule, BurstSelectorModule, + OperaS1SelectorModule, SharedModule, // TranslateModule ], diff --git a/src/app/components/header/create-subscription/create-subscription.component.html b/src/app/components/header/create-subscription/create-subscription.component.html deleted file mode 100644 index c8653d050..000000000 --- a/src/app/components/header/create-subscription/create-subscription.component.html +++ /dev/null @@ -1,331 +0,0 @@ -
-
-
-
- - -
- - -
-
- -
-
- {{ 'CREATE_SUBSCRIPTION' | translate }} -
-
- {{ 'POWERED_BY_HY_P3' | translate }} -
-
- -
- -
-
- -
-
-
-
-
- close -
-
-
- - -
-
- - - - {{ 'SEARCH_OPTIONS' | translate }} - - -
-
- {{ 'SUBSCRIPTION_DATE' | translate }} -
*{{ errors.dateError }}
-
- - -
- -
-
- {{ 'AOI_OPTIONS' | translate }} -
*{{ errors.polygonError }}
-
- -
- -
- - {{ 'PATH' | translate }} - - {{ path !== null ? '' : ('NO_PATH_INPUT' | translate)}} - - - - {{ 'FRAME' | translate }} - - {{ frame !== null ? '' : ('NO_FRAME_INPUT' | translate) }} - -
-
- - - {{ 'SUBSCRIPTION_OPTIONS' | translate }} - -
-
- {{ 'JOB_TYPE' | translate }} -
- - - - - {{jobType.name}} - - - -
- -
-
- {{ 'DATASET_OPTIONS' | translate }} -
- - - - - {{types[type].displayName}} - - - - - {{ !productType ? ( 'SELECT_FILE_TYPE' | translate) : productType + ' ' + ('SELECTED' | translate | lowercase ) }} - - - - - - - {{type.displayName}} - - - - - {{ !productType ? ('SELECT_TYPE' | translate ) : subtype + ' ' + ('SELECTED' | translate | lowercase ) }} - - - - - - - {{direction.toUpperCase() | translate }} - - - - - {{ !flightDirection ? ('NO_FLIGHT_DIRECTION_SELECTED' | translate ) : ('FLIGHT_DIRECTION_SELECTED' | translate ) }} - - - - - - - {{pol}} - - - - - {{ polarization.length === 0 ? ('NO_POLARIZATION_SELECTED' | translate ) : ('POLARIZATION_SELECTED' | translate ) }} - - -
- - - -
- - - {{ 'REVIEW' | translate }} - -
- {{ 'BASED_ON_YOUR_SEARCH_PARAMETERS_THIS_SUBSCRIPTION_WILL_RUN' | translate }} - {{ currentProducts }} {{'JOBS_NOW' | translate }} {{ 'AND' | translate }} - {{ 'APPROXIMATELY' | translate }} {{ subEstimate }} {{ 'JOBS_PER_MONTH' | translate }}. -
- -
- {{ 'NAME' | translate }}: -
*{{ errors.projectNameError }}
- -
- -
- {{ 'JOB_TYPE' | translate }}: {{ jobTypeId }} -
-

{{ 'SEARCH_OPTIONS' | translate }}:

- -
-
- {{ 'DATASET' | translate }}: {{ subtype }} -
-
- {{ 'SEARCH_AREA' | translate }}: {{ showSearchAreaType(polygon) }} -
- -
- {{ 'STAR' | translate }}t: {{ dateRange.start | shortDate }} -
-
- {{ 'END' | translate }}: {{ dateRange.end | shortDate }} -
- -
- {{ 'FILE_TYPE' | translate }}: {{ productType }} -
- -
- {{ 'POLARIZATION' | translate }}: {{ polarization }} -
- -
- {{ 'FLIGHT_DIR' | translate }}: {{ flightDirection }} -
-
- -

{{ 'PROCESSING_OPTIONS' | translate }}:

- -
-
- {{ param.name }}: {{ param.val }} -
-
- -
-
-
- - -
-
- - - - diff --git a/src/app/components/header/create-subscription/create-subscription.component.scss b/src/app/components/header/create-subscription/create-subscription.component.scss deleted file mode 100644 index 8283d70e5..000000000 --- a/src/app/components/header/create-subscription/create-subscription.component.scss +++ /dev/null @@ -1,284 +0,0 @@ -@import "asf-theme"; - -$header-height: 95px; -$footer-height: 60px; -$bottom-offset: 10px; - -[mat-dialog-title] { - background: #369; // TODO: check this - color: $light-primary-text; - cursor: move; - text-align: center; -} - -.queue { - @include themify($themes) { - color: themed('dark-primary-text'); - background: themed('primary-light'); - } - - min-width: 350px; - height: 100%; - min-height: 500px; - margin: 0; - padding: 0; - position: relative; - display: flex; - align-items: flex-start; - justify-content: flex-start; - font-size: 25px; - font-weight: bold; -} - -.mat-dialog-content { - @include themify($themes) { - background-color: themed('background-white'); - } - - position: absolute; - display: block; - top: $header-height; - height: calc(100% - #{$header-height} - #{$footer-height} - #{$bottom-offset}); - max-height: calc(100% - #{$header-height} - #{$footer-height} - #{$bottom-offset}); - width: 100%; - overflow-y: hidden; - overflow-x: hidden; - padding: 0; - margin: 0; - - .mat-dialog-content-2 { - top: initial; - height: calc(100% - #{$footer-height}); - overflow: initial; - } -} - -.content-top-area { - display: flex; - flex-flow: row wrap; - padding: 0 0 8px 15px; - border-bottom: solid 2px grey; - height: 100%; -} - -.content-area { - padding: 0 0 0 15px; - height: 100%; -} - -.content-bottom-area { - display: flex; - flex-direction: row; - height: 100%; - overflow-y: auto; -} - -.pq-card-title-group { - @include themify($themes) { - background: themed('primary-light'); - color: themed('dark-primary-text'); - border-bottom-color: themed('primary-dark'); - } - - padding: 10px 10px 0 0; - border-bottom-style: solid; - border-bottom-width: 1px; - width: 100%; - height: calc(#{$header-height} - 16px); - position: absolute; - top: 5px; - flex: 1 1 auto; - text-align: left; -} - -.header-link { - font-size: 18px; - - a { - @include themify($themes) { - color: themed('blue-link'); - } - - text-decoration: underline; - } -} - -.doc-feedback-div-wrapper { - flex: 1 1 auto; -} - -.doc-feedback-div { - display: flex; - justify-content: flex-end; - margin-top: 29px; - margin-right: 20px; -} - -.docs-div { - cursor: pointer; - flex: 0 1 auto; - margin-right: 20px; - margin-top: 2px; -} - -.feedback-div { - cursor: pointer; - flex: 0 1 auto; -} - -.content-block { - display: flex; - flex-direction: column; - width: 100%; - text-align: left; -} - -.footer { - @include themify($themes) { - background: themed('primary-light'); - border-top-color: themed('primary-dark'); - } - - display: flex; - flex-direction: row; - justify-content: space-between; - height: 100%; - border-top: 1px; - border-top-style: solid; -} - -.mat-dialog-actions { - @include themify($themes) { - background-color: themed('primary-light'); - } - - position: absolute; - display: flex; - width: 100%; - height: $footer-height; - bottom: $bottom-offset; - margin-bottom: 0; - padding: 0; -} - -.subscription-footer { - @include themify($themes) { - background-color: themed('primary-light'); - border-top-color: themed('primary-dark'); - } - - display: flex; - flex-direction: row; - justify-content: flex-end; - height: 100%; - border-top: 1px; - border-top-style: solid; -} - -.subscription-footer-item { - margin: 15px 25px 15px 15px; -} - -.validate-toggle { - @include themify($themes) { - color: themed('dark-secondary-text'); - } - - margin-top: 22px; -} - -.validate-only { - @include themify($themes) { - color: themed('dark-primary-text'); - } -} - -.on-demand-branding { - display: inline-block; - vertical-align: middle; - margin-left: 10px; - margin-bottom: -5px; -} - -.on-demand-icon { - @include themify($themes) { - color: themed('dark-primary-text'); - } - @include md-icon-size(72px); -} - -.on-demand-text { - vertical-align: middle; - margin-top: -16px; -} - -.on-demand-subtitle { - @include themify($themes) { - color: themed('dark-secondary-text'); - } - - font-style: italic; - font-size: 14px; - margin-top: -15px; -} - -.sub-option { - margin-right: 8px; -} - -.step-error { - @include themify($themes) { - color: themed('err'); - } -} - -.close-x { - @include themify($themes) { - color: themed('dark-primary-text'); - } - - position: absolute; - top: 10px; - right: 24px; - text-align: right; - float: right; - cursor: pointer; - z-index: 100; -} - -.navigation-row { - display: flex; - flex-direction: row; - justify-content: center; - margin-top: 25px; - margin-bottom: 25px; -} - -.navigation-item { - flex: none; -} - -.mobile-navigation-item { - display: flex; - justify-content: space-between; - width: 100%; -} - -.hyp3-url { - @include themify($themes) { - color: themed('dark-secondary-text'); - } - - height: 32px; - font-size: 12px; - margin: -4px 0 6px 0; -} - -.hyp3-url-mobile { - margin: 17px 0 0 0; -} - -.branding-block { - display: flex; - flex-direction: column; -} diff --git a/src/app/components/header/create-subscription/create-subscription.component.ts b/src/app/components/header/create-subscription/create-subscription.component.ts deleted file mode 100644 index 7afb3e89a..000000000 --- a/src/app/components/header/create-subscription/create-subscription.component.ts +++ /dev/null @@ -1,500 +0,0 @@ -import { Component, OnInit, OnDestroy, ViewChild, Inject } from '@angular/core'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { MatStepper } from '@angular/material/stepper'; -import { SubSink } from 'subsink'; -import * as moment from 'moment'; - -import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; -import { STEPPER_GLOBAL_OPTIONS } from '@angular/cdk/stepper'; - -import { Store } from '@ngrx/store'; -import { AppState } from '@store'; -import * as hyp3Store from '@store/hyp3'; - -import { - ScreenSizeService, MapService, Hyp3Service, EnvironmentService, - AsfApiService, NotificationService, WktService -} from '@services'; -import * as models from '@models'; - -enum CreateSubscriptionSteps { - SEARCH_OPTIONS = 0, - PROCESSING_OPTIONS = 1, - REVIEW = 2 -} - -@Component({ - selector: 'app-create-subscription', - templateUrl: './create-subscription.component.html', - styleUrls: ['./create-subscription.component.scss'], - providers: [{ - provide: STEPPER_GLOBAL_OPTIONS, useValue: {showError: true} - }] -}) -export class CreateSubscriptionComponent implements OnInit, OnDestroy { - @ViewChild('stepper', { static: false }) public stepper: MatStepper; - - public steps = CreateSubscriptionSteps; - - public jobTypeId = models.hyp3JobTypes.RTC_GAMMA.id; - public errors = { - dateError: null, - polygonError: null, - projectNameError: null, - }; - - public hyp3JobTypes = models.hyp3JobTypes; - public hyp3JobTypesList = models.hyp3JobTypesList; - public selectedJobTypeId: string | null = models.hyp3JobTypes.RTC_GAMMA.id; - - public types = { - 'GRD_HD': { - apiValue: 'GRD_HD', - displayName: 'L1 Detected High-Res Dual-Pol (GRD-HD)' - }, - 'SLC': { - apiValue: 'SLC', - displayName: 'L1 Single Look Complex (SLC)' - } - }; - public productTypes = ['SLC', 'GRD_HD']; - public productType = 'SLC'; - - public flightDirectionTypes = [ - 'All', - ...models.flightDirections - ]; - public flightDirection = 'All'; - - public polarizations = [ - 'VV+VH', - 'HH+HV', - 'VV', - 'HH', - ]; - public polarization = []; - public path: number = null; - public frame: number = null; - - public s1Subtypes = [{ - displayName: 'Sentinel-1', - apiValue: 'S1', - }, - ...models.sentinel_1.subtypes - ]; - public subtype = 'S1'; - - public processingOptionsList = []; - public processingOptions; - - public current: Date; - public dateRange: models.Range; - public projectName: string; - public searchFilters; - public polygon: string; - public subEstimate: number | null = null; - public currentProducts: number | null = null; - - public breakpoint: models.Breakpoints; - public breakpoints = models.Breakpoints; - public validateOnly = false; - public searchOptionErrorsFound = true; - public subscriptionOptionErrorsFound = true; - public reviewErrorsFound = false; - public errorsFound = true; - - private subs = new SubSink(); - - public searchOptionsFormGroup: UntypedFormGroup; - public reviewFormGroup: UntypedFormGroup; - - constructor( - @Inject(MAT_DIALOG_DATA) public data: any, - public dialogRef: MatDialogRef, - private screenSize: ScreenSizeService, - private mapService: MapService, - private wktService: WktService, - private hyp3: Hyp3Service, - public env: EnvironmentService, - private asfApi: AsfApiService, - private store$: Store, - private _formBuilder: UntypedFormBuilder, - private notificationService: NotificationService, - ) { } - - ngOnInit(): void { - if (this.data !== null && this.data.referenceScene) { - const reference = this.data.referenceScene; - this.loadOptionsFromReferenceScene(reference); - this.selectedJobTypeId = models.hyp3JobTypes.INSAR_GAMMA.id; - this.setJobType(models.hyp3JobTypes.INSAR_GAMMA.id); - } - - // @ts-ignore - this.dialogRef.afterClosed().subscribe(x => { - this.store$.dispatch(new hyp3Store.ClearProcessingOptions()); - this.store$.dispatch(new hyp3Store.SetProcessingProjectName('')); - this.selectedJobTypeId = null; - this.subs.unsubscribe(); - this.dialogRef = null; - }); - this.current = new Date(); - - const end = new Date(); - this.dateRange = { - start: new Date(this.current.getTime()), - end: new Date(end.setDate(end.getDate() + 179)), - }; - - this.searchOptionsFormGroup = this._formBuilder.group({ - dateRange: [1, Validators.max(1)], - aoi: [1, Validators.max(1)] - }); - - this.reviewFormGroup = this._formBuilder.group({ - projectName: [1, Validators.max(1)] - }); - - this.subs.add( - this.screenSize.breakpoint$.subscribe( - breakpoint => this.breakpoint = breakpoint - ) - ); - - this.subs.add( - this.mapService.searchPolygon$.subscribe( - polygon => this.polygon = polygon - ) - ); - - this.subs.add( - this.store$.select(hyp3Store.getProcessingOptions).subscribe( - options => { - this.processingOptionsList = Object.entries(options).filter(([_, v]) => v); - this.processingOptions = options; - }) - ); - - this.subs.add( - this.store$.select(hyp3Store.getProcessingProjectName).subscribe( - name => this.projectName = name - ) - ); - } - - private loadOptionsFromReferenceScene(reference: models.CMRProduct): void { - const features = this.wktService.wktToFeature( - reference.metadata.polygon, - this.mapService.epsg() - ); - - this.mapService.setDrawFeature(features); - this.polarization = [reference.metadata.polarization]; - this.path = reference.metadata.path; - - this.flightDirectionTypes.forEach(flightDir => { - if (flightDir.toLowerCase() === reference.metadata.flightDirection.toLowerCase()) { - this.flightDirection = flightDir; - } - }); - } - - public onSelectionChange(): void { - this.checkErrors(); - } - - public onNewStartDate(d: Date): void { - this.dateRange.start = d; - - if (this.dateRange.end < this.dateRange.start && !!this.dateRange.end) { - this.dateRange.end = d; - } - } - - public onNewEndDate(d: Date): void { - this.dateRange.end = d; - - if (this.dateRange.start > this.dateRange.end && !!this.dateRange.start) { - this.dateRange.start = d; - } - } - - public onNext(): void { - const hasErrors = this.checkErrors(); - if (hasErrors) { - return; - } - - if (this.isLastStep() && !hasErrors) { - if (this.checkAllErrors()) { - return; - } - - this.submitSubscription(); - } else { - this.stepper.next(); - } - } - - public checkErrors(): boolean { - - if (this.stepper.selectedIndex === CreateSubscriptionSteps.SEARCH_OPTIONS) { - this.errors.dateError = null; - this.errors.polygonError = null; - this.checkSearchOptions(); - - return this.searchOptionErrorsFound; - } else if (this.stepper.selectedIndex === CreateSubscriptionSteps.PROCESSING_OPTIONS) { - this.checkSubscriptionOptions(); - return this.subscriptionOptionErrorsFound; - - } else if (this.stepper.selectedIndex === CreateSubscriptionSteps.REVIEW) { - this.checkReviewOptions(); - return this.reviewErrorsFound; - } - } - - public checkAllErrors(): boolean { - - this.searchOptionErrorsFound = true; - this.subscriptionOptionErrorsFound = true; - this.reviewErrorsFound = false; - this.errorsFound = true; - - this.errors.dateError = null; - this.errors.polygonError = null; - - this.checkSearchOptions(); - this.checkSubscriptionOptions(); - this.checkReviewOptions(); - - this.errorsFound = this.searchOptionErrorsFound || this.subscriptionOptionErrorsFound || this.reviewErrorsFound; - - return this.errorsFound; - } - - public checkSearchOptions() { - this.searchOptionErrorsFound = true; - - if (!this.dateRange.start && !this.dateRange.end) { - this.errors.dateError = 'Start and end date required'; - } else if (!this.dateRange.start) { - this.errors.dateError = 'Start date required'; - } else if (!this.dateRange.end) { - this.errors.dateError = 'End date required'; - } else if (this.dateRange.end < this.dateRange.start) { - this.errors.dateError = 'End date is before start date'; - } - - if (!this.polygon && this.frame === null && this.path === null) { - this.errors.polygonError = 'Area of interest, path or frame required (will use polygon from current search)'; - } - - if (this.errors.dateError) { - this.searchOptionsFormGroup.controls['dateRange'].setValue(2); - } else if (this.errors.polygonError) { - this.searchOptionsFormGroup.controls['aoi'].setValue(2); - } else { - this.searchOptionErrorsFound = false; - this.searchOptionsFormGroup.controls['aoi'].setValue(1); - this.searchOptionsFormGroup.controls['dateRange'].setValue(1); - } - } - - public checkSubscriptionOptions() { - this.onEstimateSubscription(); - this.subscriptionOptionErrorsFound = false; - } - - public checkReviewOptions() { - if (!this.projectName) { - this.errors.projectNameError = 'Project Name is required'; - this.reviewFormGroup.controls['projectName'].setValue(2); - this.reviewErrorsFound = true; - } else { - this.reviewFormGroup.controls['projectName'].setValue(1); - this.reviewErrorsFound = false; - } - } - - public isLastStep(): boolean { - if (!this.stepper) { - return false; - } - - return this.stepper.selectedIndex === this.stepper.steps.length - 1; - } - - public submitSubscription(): void { - const searchParams = this.getSearchParams(); - const searchOptions = this.filterOptions(this.processingOptionsList, this.jobTypeId).reduce( - (ps, p) => { - ps[p.apiName] = p.val; - return ps; - }, {} - ); - - const sub = { - job_specification: { - job_parameters: { - ...searchOptions - }, - job_type: this.jobTypeId, - name: this.projectName - }, - search_parameters: { - ...searchParams, - } - }; - - this.hyp3.submitSubscription$({ - subscription: sub, validate_only: this.validateOnly - }).subscribe(_ => { - this.notificationService.info(this.projectName + ' subscription submitted'); - this.dialogRef.close(); - }); - - } - - public onNewProductType(e): void { - this.productType = e.value; - } - - public onNewFlightDirection(e): void { - this.flightDirection = e.value; - } - - public onNewPolarization(e): void { - this.polarization = e.value; - } - - public onNewSubType(e): void { - this.subtype = e.value; - } - - public onNewJobType(e): void { - this.setJobType(e.value); - } - - private setJobType(jobTypeId: string): void { - this.jobTypeId = jobTypeId; - const dataset = models.hyp3JobTypes[jobTypeId].productTypes[0]; - this.productTypes = dataset.productTypes; - this.productType = this.productTypes[0]; - - this.polarizations = dataset.polarizations; - this.polarization = []; - - } - - public onBack(): void { - this.stepper.previous(); - } - - public showSearchAreaType(polygon: string): string { - return polygon.split('(')[0]; - } - - public onValidateOnlyToggle(val: boolean): void { - this.validateOnly = val; - } - - public filterOptions(optionList, jobTypeId) { - - const jobType = models.hyp3JobTypes[jobTypeId]; - const allOptions = jobType ? jobType.options : models.hyp3JobOptionsOrdered; - - const options = optionList.reduce((total, op) => { - total[op[0]] = op[1]; - return total; - }, {}); - - const filtered = allOptions - .filter(option => options[option.apiName]) - .map(option => { - return {name: option.name, val: options[option.apiName], apiName: option.apiName}; - }); - - return filtered; - } - - public onEstimateSubscription(): void { - const start = new Date(this.current.getTime()); - const dateRange = { - start: new Date(start.setDate(start.getDate() - 179)), - end: new Date(this.current.getTime()), - }; - - const flightDir = this.flightDirection === 'All' ? null : this.flightDirection; - const pol = this.polarization.join(','); - - const params = this.filterNullKeys({ - start: moment.utc(dateRange.start).format(), - end: moment.utc(dateRange.end).format(), - intersectsWith: this.polygon, - relativeOrbit: this.path, - frame: this.frame, - platform: this.subtype, - flightDirection: !!flightDir ? flightDir.toUpperCase() : null, - polarization: pol, - processinglevel: this.productType, - output: 'COUNT', - }); - - if (this.dateRange.start < this.current) { - const curr = { - ...params, - start: moment.utc(this.dateRange.start).format(), - }; - - this.subs.add( - this.asfApi.query(curr).subscribe( - estimate => this.currentProducts = estimate - ) - ); - } - - this.subs.add( - this.asfApi.query(params).subscribe( - estimate => this.subEstimate = Math.round(estimate / 6) - ) - ); - } - - private getSearchParams() { - const flightDir = this.flightDirection === 'All' ? null : this.flightDirection; - const pol = this.polarization.length > 0 ? this.polarization : null; - - const params = { - start: moment.utc(this.dateRange.start).format(), - end: moment.utc(this.dateRange.end).format(), - intersectsWith: this.polygon, - relativeOrbit: !!this.path ? [this.path] : null, - frame: !!this.frame ? [ this.frame ] : null, - platform: this.subtype, - flightDirection: !!flightDir ? flightDir.toUpperCase() : null, - polarization: pol, - processingLevel: this.productType, - }; - - return this.filterNullKeys(params); - } - - public isDevMode(): boolean { - return !this.env.isProd; - } - - private filterNullKeys(params) { - return Object.keys(params) - .filter((k) => params[k] != null) - .reduce((a, k) => ({ ...a, [k]: params[k] }), {}); - } - - public onCloseDialog() { - this.dialogRef.close(); - } - - ngOnDestroy(): void { - this.subs.unsubscribe(); - } -} diff --git a/src/app/components/header/create-subscription/create-subscription.module.ts b/src/app/components/header/create-subscription/create-subscription.module.ts deleted file mode 100644 index 395d90bc8..000000000 --- a/src/app/components/header/create-subscription/create-subscription.module.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FormsModule } from '@angular/forms'; - -import { MatSelectModule } from '@angular/material/select'; -import { MatStepperModule } from '@angular/material/stepper'; -import { MatSlideToggleModule } from '@angular/material/slide-toggle'; -import { MatInputModule } from '@angular/material/input'; -import { MatSharedModule } from '@shared'; - -import { SearchFiltersModule } from '@components/sidebar/saved-searches/saved-search/search-filters'; -import { DateSelectorModule } from '@components/shared/selectors/date-selector'; -import { ProjectNameSelectorModule } from '@components/shared/selectors/project-name-selector'; -import { ProductTypeSelectorModule } from '@components/shared/selectors/product-type-selector'; -import { AoiOptionsModule } from '@components/shared/aoi-options'; - -import { CreateSubscriptionComponent } from './create-subscription.component'; -import { ProcessingOptionsModule } from '../processing-queue/processing-options'; -import { DateRangeModule } from '@components/shared/selectors/date-range/date-range.module'; - -import { MatDialogModule } from '@angular/material/dialog'; -import { PipesModule } from '@pipes'; -import { SubscriptionDateRangeComponent } from './subscription-date-range/subscription-date-range.component'; -import {DocsModalModule} from '@components/shared/docs-modal'; -import {Hyp3UrlModule} from '@components/shared/hyp3-url/hyp3-url.module'; -import { SharedModule } from '@shared'; - -@NgModule({ - declarations: [ - CreateSubscriptionComponent, - SubscriptionDateRangeComponent - ], - imports: [ - CommonModule, - FormsModule, - MatDialogModule, - MatSelectModule, - MatStepperModule, - MatSlideToggleModule, - MatInputModule, - MatSharedModule, - ProcessingOptionsModule, - SearchFiltersModule, - DateSelectorModule, - ProjectNameSelectorModule, - ProductTypeSelectorModule, - AoiOptionsModule, - DateRangeModule, - PipesModule, - DocsModalModule, - Hyp3UrlModule, - SharedModule - ], - exports: [ - CreateSubscriptionComponent - ] -}) -export class CreateSubscriptionModule { } diff --git a/src/app/components/header/create-subscription/index.ts b/src/app/components/header/create-subscription/index.ts deleted file mode 100644 index f3ed18e25..000000000 --- a/src/app/components/header/create-subscription/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './create-subscription.component'; -export * from './create-subscription.module'; diff --git a/src/app/components/header/create-subscription/subscription-date-range/subscription-date-range.component.html b/src/app/components/header/create-subscription/subscription-date-range/subscription-date-range.component.html deleted file mode 100644 index de2358034..000000000 --- a/src/app/components/header/create-subscription/subscription-date-range/subscription-date-range.component.html +++ /dev/null @@ -1,10 +0,0 @@ - - diff --git a/src/app/components/header/create-subscription/subscription-date-range/subscription-date-range.component.ts b/src/app/components/header/create-subscription/subscription-date-range/subscription-date-range.component.ts deleted file mode 100644 index c70acf465..000000000 --- a/src/app/components/header/create-subscription/subscription-date-range/subscription-date-range.component.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; -import { NotificationService } from '@services'; - -import * as models from '@models'; -import * as moment from 'moment'; - -@Component({ - selector: 'app-subscription-date-range', - templateUrl: './subscription-date-range.component.html', - styleUrls: ['./subscription-date-range.component.scss'] -}) -export class SubscriptionDateRangeComponent implements OnInit { - public minDate: Date; - public maxDate: Date; - - @Input() public startDate: Date; - @Input() public endDate: Date; - - @Output() public newEnd = new EventEmitter(); - @Output() public newStart = new EventEmitter(); - - constructor(private notificationService: NotificationService) { } - - ngOnInit(): void { - this.minDate = models.sentinel_1.date.start; - this.maxDate = this.addDays(new Date(), 179); - } - - public onStartDateChange(date): void { - this.newStart.emit(date); - } - - public onEndDateChange(date): void { - this.newEnd.emit(date); - } - - public onStartDateError(): void { - const min = new Date(this.minDate); - const dayBefore = moment(min.setDate(min.getDate() - 1)).format('MMMM Do YYYY'); - - this.notificationService.error( - `subscription start date must be after ${dayBefore}`, - 'Invalid Start Date'); - } - - public onEndDateError(): void { - this.notificationService.error( - 'subscription end date must be within 6 months of current date', - 'Invalid End Date'); - } - - private addDays(date: Date, numDays: number): Date { - const d = new Date(date.valueOf()); - return new Date(d.setDate(d.getDate() + numDays)); - } -} diff --git a/src/app/components/header/header-buttons/header-buttons.component.html b/src/app/components/header/header-buttons/header-buttons.component.html index 6dec72a51..6c8ab35c3 100644 --- a/src/app/components/header/header-buttons/header-buttons.component.html +++ b/src/app/components/header/header-buttons/header-buttons.component.html @@ -22,11 +22,6 @@ (click)="onOpenProcessingQueue()" mat-menu-item>{{ 'ON_DEMAND_QUEUE' | translate }} - - - - @@ -364,5 +354,3 @@ - - diff --git a/src/app/components/header/header-buttons/header-buttons.component.ts b/src/app/components/header/header-buttons/header-buttons.component.ts index 1e3cc8019..8fa34bd03 100644 --- a/src/app/components/header/header-buttons/header-buttons.component.ts +++ b/src/app/components/header/header-buttons/header-buttons.component.ts @@ -262,10 +262,6 @@ export class HeaderButtonsComponent implements OnInit, OnDestroy { this.store$.dispatch(new uiStore.SetIsOnDemandQueueOpen(true)); } - public onOpenSubscriptions() { - this.store$.dispatch(new uiStore.OpenSidebar(SidebarType.ON_DEMAND_SUBSCRIPTIONS)); - } - public listWebsiteLinks() { const links = new Set(); diff --git a/src/app/components/header/header.module.ts b/src/app/components/header/header.module.ts index a3558ff52..a256fd182 100644 --- a/src/app/components/header/header.module.ts +++ b/src/app/components/header/header.module.ts @@ -44,7 +44,6 @@ import { AoiFilterComponent } from './dataset-header/aoi-filter/aoi-filter.compo import { MatButtonToggleModule } from '@angular/material/button-toggle'; import { MasterSceneSelectorModule } from '@components/shared/selectors/master-scene-selector'; -import { CreateSubscriptionModule } from './create-subscription/create-subscription.module'; import { CiSearchModule } from './info-bar/ci-search/ci-search.module'; import { SarviewsEventTypeSelectorModule } from '@components/shared/selectors/sarviews-event-type-selector'; import { Hyp3UrlModule } from '@components/shared/hyp3-url/hyp3-url.module'; @@ -92,7 +91,6 @@ import { LanguageSelectorModule } from "@components/shared/selectors/language-se ProjectNameSelectorModule, JobStatusSelectorModule, JobProductNameSelectorModule, - CreateSubscriptionModule, CiSearchModule, SarviewsEventSearchSelectorModule, SarviewsEventTypeSelectorModule, diff --git a/src/app/components/header/info-bar/info-bar.component.html b/src/app/components/header/info-bar/info-bar.component.html index 2b88e3c50..ed7d9842b 100644 --- a/src/app/components/header/info-bar/info-bar.component.html +++ b/src/app/components/header/info-bar/info-bar.component.html @@ -55,6 +55,16 @@ {{'FULL_BURST_ID' | translate}}: {{fullBurstIDs.length <= 4 ? fullBurstIDs.join(', ') : fullBurstIDs.length + ' IDs'}} + + + {{'OPERA_BURST_ID' | translate}}: {{operaBurstIDs.length <= 4 ? operaBurstIDs.join(', ') : operaBurstIDs.length + ' IDs'}} + + + + + Group ID: {{groupID}} + +
diff --git a/src/app/components/header/info-bar/info-bar.component.ts b/src/app/components/header/info-bar/info-bar.component.ts index 34e69317b..4b73ba3b4 100644 --- a/src/app/components/header/info-bar/info-bar.component.ts +++ b/src/app/components/header/info-bar/info-bar.component.ts @@ -46,6 +46,8 @@ export class InfoBarComponent implements OnInit, OnDestroy { public perpRange: models.Range; public tempRange: models.Range; public fullBurstIDs: string[] = []; + public operaBurstIDs: string[] = []; + public groupID: string; private subs = new SubSink(); @@ -126,6 +128,14 @@ export class InfoBarComponent implements OnInit, OnDestroy { burstIDs => this.fullBurstIDs = burstIDs ); + const operaBurstIDSub = this.store$.select(filtersStore.getOperaBurstIDs).subscribe( + burstIDs => this.operaBurstIDs = burstIDs + ); + + const groupIDSub = this.store$.select(filtersStore.getGroupID).subscribe( + groupID => this.groupID = groupID + ); + [ startSub, endSub, pathSub, frameSub, @@ -140,7 +150,9 @@ export class InfoBarComponent implements OnInit, OnDestroy { missionSub, tempSub, perpSub, eventProductType, - fullBurstIDSub + fullBurstIDSub, + operaBurstIDSub, + groupIDSub ].forEach(sub => this.subs.add(sub)); this.subs.add( diff --git a/src/app/components/header/processing-queue/processing-queue.component.html b/src/app/components/header/processing-queue/processing-queue.component.html index 76120ae4b..55cddf480 100644 --- a/src/app/components/header/processing-queue/processing-queue.component.html +++ b/src/app/components/header/processing-queue/processing-queue.component.html @@ -199,7 +199,7 @@ [value]="progress"> - @@ -89,7 +89,9 @@

*ngIf="!product.isUnzippedFile && !product.metadata.job && product.groupId !== 'SARViews'" style="margin-right: 15px;" prompt="{{ 'COPY_FILE_ID' | translate}}" - [value]="product.metadata.productType === 'BURST_XML' ? product.id?.split('-XML')[0] : product.id" + [value]="product.metadata.productType === 'BURST_XML' ? product.id?.split('-XML')[0] : ( + product.metadata.parentID || product.id + )" > diff --git a/src/app/components/header/queue/queue.component.ts b/src/app/components/header/queue/queue.component.ts index b6f68de57..c94574af7 100644 --- a/src/app/components/header/queue/queue.component.ts +++ b/src/app/components/header/queue/queue.component.ts @@ -169,7 +169,7 @@ export class QueueComponent implements OnInit, OnDestroy { if (product.metadata.productType === 'BURST_XML') { return product.id?.split('-XML')[0] } - return product.id; + return product.metadata.parentID || product.id; }) .join('\n'); this.clipboardService.copyFromContent(productListStr); diff --git a/src/app/components/map/map.component.ts b/src/app/components/map/map.component.ts index 118c75dc8..7e272732b 100644 --- a/src/app/components/map/map.component.ts +++ b/src/app/components/map/map.component.ts @@ -430,7 +430,7 @@ export class MapComponent implements OnInit, OnDestroy { } private scenesToFeatures(projection: string): Observable[]> { - return this.scenesService.scenes$().pipe( + return this.scenesService.scenes$.pipe( map(scenes => scenes.filter(scene => scene.id !== this.selectedScene?.id)), map(scenes => this.scenesToFeature(scenes, projection))); } 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 ef0c03927..98d2ec9e3 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 @@ -96,7 +96,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 6605455d2..0b293d402 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 @@ -30,7 +30,7 @@ enum CardViews { export class BaselineResultsMenuComponent implements OnInit, OnDestroy { @Input() resize$: Observable; - public numBaselineScenes$ = this.scenesService.scenes$().pipe( + public numBaselineScenes$ = this.scenesService.scenes$.pipe( map(scenes => scenes.length), ); diff --git a/src/app/components/results-menu/desktop-results-menu/desktop-results-menu.component.ts b/src/app/components/results-menu/desktop-results-menu/desktop-results-menu.component.ts index 968be2fa0..e70176f98 100644 --- a/src/app/components/results-menu/desktop-results-menu/desktop-results-menu.component.ts +++ b/src/app/components/results-menu/desktop-results-menu/desktop-results-menu.component.ts @@ -40,7 +40,7 @@ export class DesktopResultsMenuComponent implements OnInit, OnDestroy { ) ); this.subs.add( - this.scenesService.scenes$().subscribe( + this.scenesService.scenes$.subscribe( scenes => this.scenesLength = scenes.length ) ); diff --git a/src/app/components/results-menu/scene-detail/image-dialog/browse-list/browse-list.component.ts b/src/app/components/results-menu/scene-detail/image-dialog/browse-list/browse-list.component.ts index dda3d6a46..1dc13e710 100644 --- a/src/app/components/results-menu/scene-detail/image-dialog/browse-list/browse-list.component.ts +++ b/src/app/components/results-menu/scene-detail/image-dialog/browse-list/browse-list.component.ts @@ -25,7 +25,7 @@ export class BrowseListComponent implements OnInit, AfterViewInit, OnDestroy { @ViewChild(CdkVirtualScrollViewport) scroll: CdkVirtualScrollViewport; public scenesSorted$ = this.scenesService.sortScenes$( - this.scenesService.scenes$() + this.scenesService.scenes$ ); public scenes$: Observable; private scene: models.CMRProduct; 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 d6be24038..0bd106d5e 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 @@ -323,12 +323,14 @@
+ ) ? ('GEOGRAPHIC_SEARCH_BASED_ON_THIS_SCENE_S_PATH_FRAME' | translate) : ( 'NOT_ABLE_TO_SELECT_SIMILAR_SCENES_FROM_THIS_SOURCE' | 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 b87e001c7..47c3779e8 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 @@ -360,7 +360,7 @@ export class SceneDetailComponent implements OnInit, OnDestroy { this.makeSarviewsEventGeoSearch(); } else { const scene = this.scene; - const shouldClear = this.searchType !== models.SearchType.DATASET; + const shouldClear = this.searchType !== models.SearchType.DATASET || this.dataset.id === 'OPERA-S1'; const dateRange = this.dateRange; this.store$.dispatch(new searchStore.SetSearchType(models.SearchType.DATASET)); @@ -382,6 +382,17 @@ export class SceneDetailComponent implements OnInit, OnDestroy { } } + public staticLayer(){ + const operaBurstID = this.scene.metadata.opera.operaBurstID; + const sensorDate = new Date(this.scene.metadata.date.toDate()); + const staticType = this.scene.metadata.productType + '-STATIC' + this.store$.dispatch(new searchStore.ClearSearch()); + this.store$.dispatch(new filtersStore.SetSelectedDataset('OPERA-S1')) + this.store$.dispatch(new filtersStore.setOperaBurstID([operaBurstID])); + this.store$.dispatch(new filtersStore.SetProductTypes([models.opera_s1.productTypes.find(t => t.apiValue === staticType)])); + this.store$.dispatch(new filtersStore.SetEndDate(sensorDate)); + this.store$.dispatch(new searchStore.MakeSearch()); + } public makeBaselineSearch(): void { const sceneName = this.baselineSceneName(); const dateRange = this.dateRange; diff --git a/src/app/components/results-menu/scene-files/scene-file/scene-file.component.html b/src/app/components/results-menu/scene-files/scene-file/scene-file.component.html index c01a8822a..14baebb3d 100644 --- a/src/app/components/results-menu/scene-files/scene-file/scene-file.component.html +++ b/src/app/components/results-menu/scene-files/scene-file/scene-file.component.html @@ -60,7 +60,9 @@ @@ -85,7 +87,7 @@
- {{ 'VIRTUAL' | translate }} + {{ 'VIRTUAL' | translate }} {{product.bytes.toString() | readableSizeFromBytes }}
diff --git a/src/app/components/results-menu/scene-files/scene-files.component.html b/src/app/components/results-menu/scene-files/scene-files.component.html index 7ccaf3899..24e690415 100644 --- a/src/app/components/results-menu/scene-files/scene-files.component.html +++ b/src/app/components/results-menu/scene-files/scene-files.component.html @@ -10,8 +10,8 @@
- - + + +
diff --git a/src/app/components/results-menu/scene-files/scene-files.component.ts b/src/app/components/results-menu/scene-files/scene-files.component.ts index 4ddd6abaf..6579e860c 100644 --- a/src/app/components/results-menu/scene-files/scene-files.component.ts +++ b/src/app/components/results-menu/scene-files/scene-files.component.ts @@ -1,8 +1,8 @@ import {Component, OnInit, OnDestroy, AfterContentInit, Input, ViewChild} from '@angular/core'; import { SubSink } from 'subsink'; -import { combineLatest } from 'rxjs'; -import { debounceTime, filter, map, take, withLatestFrom } from 'rxjs/operators'; +import { combineLatest, of } from 'rxjs'; +import { debounceTime, distinctUntilChanged, filter, map, switchMap, take, withLatestFrom } from 'rxjs/operators'; import { Store } from '@ngrx/store'; import { AppState } from '@store'; @@ -12,7 +12,7 @@ import * as userStore from '@store/user'; import * as hyp3Store from '@store/hyp3'; import * as uiStore from '@store/ui'; -import { Hyp3Service, NotificationService, SarviewsEventsService } from '@services'; +import { AsfApiService, Hyp3Service, NotificationService, ProductService, SarviewsEventsService } from '@services'; import * as models from '@models'; import { CMRProductMetadata, hyp3JobTypes, SarviewProductGranule, SarviewsProduct } from '@models'; import { ClipboardService } from 'ngx-clipboard'; @@ -111,6 +111,8 @@ export class SceneFilesComponent implements OnInit, OnDestroy, AfterContentInit private eventMonitoringService: SarviewsEventsService, public dialog: MatDialog, private screenSize: ScreenSizeService, + private asfApiService: AsfApiService, + private productService: ProductService ) { } ngOnInit() { @@ -118,9 +120,10 @@ export class SceneFilesComponent implements OnInit, OnDestroy, AfterContentInit combineLatest([ this.store$.select(scenesStore.getSelectedSceneProducts), this.store$.select(scenesStore.getOpenUnzippedProduct), - this.store$.select(scenesStore.getUnzippedProducts)] - ).pipe(debounceTime(0)) - .subscribe( + this.store$.select(scenesStore.getUnzippedProducts), + ] + ).pipe(debounceTime(0) + ).subscribe( ([products, unzipped, unzippedFiles]) => { this.unzippedProducts = unzippedFiles; this.products = products; @@ -417,6 +420,40 @@ export class SceneFilesComponent implements OnInit, OnDestroy, AfterContentInit }; return toCMRProduct; } + + public StaticLayerProduct$ = this.store$.select(scenesStore.getSelectedScene).pipe( + debounceTime(100), + distinctUntilChanged((prev, curr) => prev?.id === curr?.id), + switchMap(scene => { + if(!!scene && ['RTC', 'CSLC'].includes(scene?.metadata?.productType) && scene?.id.startsWith('OPERA')) { + const queryParams = { + processinglevel : scene.metadata.productType + '-STATIC', + start: scene.metadata.stopDate === null ? '' : moment.utc( scene.metadata.stopDate ).format(), + operaburstid: scene.metadata?.opera?.operaBurstID, + collections: models.opera_s1.apiValue.collections, + }; + return this.asfApiService.query(queryParams).pipe( + map(products => products.length > 0 ? this.productService.fromResponse(products).slice(0, 1) : []) + ); + } else { + return of([]); + + } + } + + ) + ); + + // const queryParams = { + // processinglevel : selectedScene.metadata.productType + '-STATIC', + // start: selectedScene.metadata.stopDate === null ? '' : moment.utc( selectedScene.metadata.stopDate ).format(), + // operaburstid: selectedScene.metadata?.opera?.operaBurstID + // } + + // const staticLayer = this.asfApiService.query(queryParams).pipe( + // map(products => this.productService.fromResponse(products).slice(0,1)) + // ); + // } public getProductSceneCount(products: SarviewsProduct[]) { const outputList = products.reduce((prev, product) => { const temp = product.granules.map(granule => granule.granule_name); diff --git a/src/app/components/results-menu/scenes-list-header/scenes-list-header.component.html b/src/app/components/results-menu/scenes-list-header/scenes-list-header.component.html index 68749b45d..5d322e831 100644 --- a/src/app/components/results-menu/scenes-list-header/scenes-list-header.component.html +++ b/src/app/components/results-menu/scenes-list-header/scenes-list-header.component.html @@ -158,7 +158,7 @@ - + @@ -283,7 +283,7 @@ - - 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 03bd38bd8..752bbca9c 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 @@ -1,11 +1,8 @@ import { Component, OnInit, Input, ViewChild } from '@angular/core'; import { MatMenu } from '@angular/material/menu'; -import { MatDialog } from '@angular/material/dialog'; - import { Store } from '@ngrx/store'; import { AppState } from '@store'; import * as queueStore from '@store/queue'; -import * as hyp3Store from '@store/hyp3'; import * as userStore from '@store/user'; import * as models from '@models'; @@ -14,7 +11,6 @@ import { getMasterName, getScenes } from '@store/scenes'; import { getSearchType } from '@store/search'; import { CMRProduct, Hyp3ableByProductType, SearchType } from '@models'; import { withLatestFrom } from 'rxjs/operators'; -import { CreateSubscriptionComponent } from '../../header/create-subscription'; import { EnvironmentService } from '@services'; @Component({ @@ -26,7 +22,6 @@ export class OnDemandAddMenuComponent implements OnInit { @Input() hyp3ableProducts: models.Hyp3ableProductByJobType; @Input() isExpired = false; @Input() expiredJobs: models.Hyp3Job; - @Input() showSubscriptions = false; @ViewChild('addMenu', {static: true}) addMenu: MatMenu; @@ -44,7 +39,6 @@ export class OnDemandAddMenuComponent implements OnInit { constructor( private store$: Store, - private dialog: MatDialog, public env: EnvironmentService, ) { } @@ -128,23 +122,6 @@ export class OnDemandAddMenuComponent implements OnInit { this.store$.dispatch(new queueStore.AddJobs(jobs)); } - public onOpenCreateSubscription() { - const ref = this.dialog.open(CreateSubscriptionComponent, { - id: 'subscriptionQueueDialog', - maxWidth: '100vw', - maxHeight: '100vh', - data: { - referenceScene: this.referenceScene - } - }); - - ref.afterClosed().subscribe( - _ => { - this.store$.dispatch(new hyp3Store.LoadSubscriptions()); - } - ); - } - public onOpenHelp(infoUrl) { window.open(infoUrl); } diff --git a/src/app/components/shared/on-demand-add-menu/on-demand-add-menu.module.ts b/src/app/components/shared/on-demand-add-menu/on-demand-add-menu.module.ts index 221703f78..c09a01ba1 100644 --- a/src/app/components/shared/on-demand-add-menu/on-demand-add-menu.module.ts +++ b/src/app/components/shared/on-demand-add-menu/on-demand-add-menu.module.ts @@ -9,7 +9,6 @@ import { MatSharedModule } from '@shared'; import { OnDemandAddMenuComponent } from './on-demand-add-menu.component'; import { ClosestPairComponent } from './closest-pair/closest-pair.component'; -import { CreateSubscriptionModule } from '@components/header/create-subscription'; import { SharedModule } from '@shared'; @NgModule({ @@ -20,7 +19,6 @@ import { SharedModule } from '@shared'; MatSharedModule, MatInputModule, FormsModule, - CreateSubscriptionModule, SharedModule ], exports: [ OnDemandAddMenuComponent ] diff --git a/src/app/components/shared/scene-metadata/scene-metadata.component.html b/src/app/components/shared/scene-metadata/scene-metadata.component.html index 5aefc4652..4cd5e36b8 100644 --- a/src/app/components/shared/scene-metadata/scene-metadata.component.html +++ b/src/app/components/shared/scene-metadata/scene-metadata.component.html @@ -37,7 +37,7 @@
  • {{ 'BEAM_MODE' |translate }} • {{ scene.metadata.beamMode }} - settings @@ -157,6 +157,17 @@ {{ 'PGE_VERSION' | translate }} • {{ scene.metadata.pgeVersion }}
  • +
  • + {{'OPERA_BURST_ID' | translate }} • {{scene.metadata.opera.operaBurstID}} + + settings + + + + + +
  • +
  • {{'BURST_ID_FULL' | translate }} • {{scene.metadata.burst.fullBurstID}} diff --git a/src/app/components/shared/scene-metadata/scene-metadata.component.ts b/src/app/components/shared/scene-metadata/scene-metadata.component.ts index 2b95c1937..c1f4ae8b9 100644 --- a/src/app/components/shared/scene-metadata/scene-metadata.component.ts +++ b/src/app/components/shared/scene-metadata/scene-metadata.component.ts @@ -136,6 +136,10 @@ export class SceneMetadataComponent implements OnInit, OnDestroy { this.store$.dispatch(new filtersStore.setFullBurst([this.scene.metadata.burst.fullBurstID])) } + public setOperaBurst(): void { + this.store$.dispatch(new filtersStore.setOperaBurstID([this.scene.metadata.opera.operaBurstID])) + } + private capitalizeFirstLetter(str) { return str.charAt(0).toUpperCase() + str.slice(1); } 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 28e7e4711..df137f13c 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 @@ -57,7 +57,7 @@ export class JobProductNameSelectorComponent implements OnInit, OnDestroy { ) ); - const fileNames = this.scenesService.scenes$().pipe( + const fileNames = this.scenesService.scenes$.pipe( map(scenes => scenes.map(scene => scene.metadata.fileName.toLowerCase().split('.')[0]) ) diff --git a/src/app/components/shared/selectors/opera-s1-selector/index.ts b/src/app/components/shared/selectors/opera-s1-selector/index.ts new file mode 100644 index 000000000..00f03d8e9 --- /dev/null +++ b/src/app/components/shared/selectors/opera-s1-selector/index.ts @@ -0,0 +1,2 @@ +export * from "./opera-s1-selector.module"; +export * from "./opera-s1-selector.component"; \ No newline at end of file diff --git a/src/app/components/shared/selectors/opera-s1-selector/opera-burst-id-selector/opera-burst-id-selector.component.html b/src/app/components/shared/selectors/opera-s1-selector/opera-burst-id-selector/opera-burst-id-selector.component.html new file mode 100644 index 000000000..487af654b --- /dev/null +++ b/src/app/components/shared/selectors/opera-s1-selector/opera-burst-id-selector/opera-burst-id-selector.component.html @@ -0,0 +1,5 @@ + + {{'OPERA_BURST_ID' | translate}} + + + \ No newline at end of file diff --git a/src/app/components/header/create-subscription/subscription-date-range/subscription-date-range.component.scss b/src/app/components/shared/selectors/opera-s1-selector/opera-burst-id-selector/opera-burst-id-selector.component.scss similarity index 100% rename from src/app/components/header/create-subscription/subscription-date-range/subscription-date-range.component.scss rename to src/app/components/shared/selectors/opera-s1-selector/opera-burst-id-selector/opera-burst-id-selector.component.scss diff --git a/src/app/components/shared/selectors/opera-s1-selector/opera-burst-id-selector/opera-burst-id-selector.component.ts b/src/app/components/shared/selectors/opera-s1-selector/opera-burst-id-selector/opera-burst-id-selector.component.ts new file mode 100644 index 000000000..1911f8a18 --- /dev/null +++ b/src/app/components/shared/selectors/opera-s1-selector/opera-burst-id-selector/opera-burst-id-selector.component.ts @@ -0,0 +1,53 @@ +import { Component, OnInit, EventEmitter, OnDestroy } from '@angular/core'; +import { debounceTime, filter, map } from 'rxjs'; +import { SubSink } from 'subsink'; + +import * as filtersStore from '@store/filters'; +import { Store } from '@ngrx/store'; +import { AppState } from '@store'; +@Component({ + selector: 'app-opera-burst-id-selector', + templateUrl: './opera-burst-id-selector.component.html', + styleUrls: ['./opera-burst-id-selector.component.scss'] +}) +export class OperaBurstIdSelectorComponent implements OnInit, OnDestroy { + public operaBurstIDs: string[] = [] + private IDsInputUpdated: EventEmitter = new EventEmitter(); + private subs: SubSink = new SubSink(); + + constructor(private store$: Store) { } + + ngOnInit(): void { + this.subs.add( + this.IDsInputUpdated.pipe( + debounceTime(3.0), + filter(ids => ids !== null), + map(ids => { + const idsArray = ids.split(',').map(id => id.trim()); + return idsArray.filter(entry => entry.length > 0); + }), + filter(ids => ids !== this.operaBurstIDs) + ).subscribe(ids => this.updateIDs(ids)) + ); + + this.subs.add( + this.store$.select(filtersStore.getOperaBurstIDs) + .subscribe( + ids => this.operaBurstIDs = ids + ) + ); + } + + ngOnDestroy(): void { + this.subs.unsubscribe() + } + + public onChange(event: Event) { + const text = (event.target as HTMLInputElement).value + this.IDsInputUpdated.emit(text) + } + + private updateIDs(ids: string[]) { + this.store$.dispatch(new filtersStore.setOperaBurstID(ids)) + } +} diff --git a/src/app/components/shared/selectors/opera-s1-selector/opera-s1-selector.component.html b/src/app/components/shared/selectors/opera-s1-selector/opera-s1-selector.component.html new file mode 100644 index 000000000..b3a118d78 --- /dev/null +++ b/src/app/components/shared/selectors/opera-s1-selector/opera-s1-selector.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscription/subscription-job-options/subscription-job-options.component.scss b/src/app/components/shared/selectors/opera-s1-selector/opera-s1-selector.component.scss similarity index 100% rename from src/app/components/sidebar/on-demand-subscriptions/on-demand-subscription/subscription-job-options/subscription-job-options.component.scss rename to src/app/components/shared/selectors/opera-s1-selector/opera-s1-selector.component.scss diff --git a/src/app/components/shared/selectors/opera-s1-selector/opera-s1-selector.component.ts b/src/app/components/shared/selectors/opera-s1-selector/opera-s1-selector.component.ts new file mode 100644 index 000000000..b9fe34391 --- /dev/null +++ b/src/app/components/shared/selectors/opera-s1-selector/opera-s1-selector.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-opera-s1-selector', + templateUrl: './opera-s1-selector.component.html', + styleUrls: ['./opera-s1-selector.component.scss'] +}) +export class OperaS1SelectorComponent { + +} diff --git a/src/app/components/shared/selectors/opera-s1-selector/opera-s1-selector.module.ts b/src/app/components/shared/selectors/opera-s1-selector/opera-s1-selector.module.ts new file mode 100644 index 000000000..e47d51402 --- /dev/null +++ b/src/app/components/shared/selectors/opera-s1-selector/opera-s1-selector.module.ts @@ -0,0 +1,32 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { OperaBurstIdSelectorComponent } from './opera-burst-id-selector/opera-burst-id-selector.component'; +import { OperaS1SelectorComponent } from './opera-s1-selector.component'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { FormsModule } from '@angular/forms'; +import { MatExpansionModule } from '@angular/material/expansion'; +import { MatInputModule } from '@angular/material/input'; +import { MatSharedModule, SharedModule } from '@shared'; + + + +@NgModule({ + declarations: [ + OperaS1SelectorComponent, + OperaBurstIdSelectorComponent, + ], + imports: [ + CommonModule, + MatFormFieldModule, + MatExpansionModule, + MatInputModule, + FormsModule, + MatSharedModule, + SharedModule + ], + exports: [ + OperaS1SelectorComponent, + OperaBurstIdSelectorComponent + ] +}) +export class OperaS1SelectorModule { } diff --git a/src/app/components/shared/selectors/other-selector/other-selector.component.html b/src/app/components/shared/selectors/other-selector/other-selector.component.html index 748f45555..8373608bd 100644 --- a/src/app/components/shared/selectors/other-selector/other-selector.component.html +++ b/src/app/components/shared/selectors/other-selector/other-selector.component.html @@ -85,4 +85,22 @@ {{ 'NO_SUBTYPES_TO_SELECT' | translate }} + + + + + + diff --git a/src/app/components/shared/selectors/other-selector/other-selector.component.ts b/src/app/components/shared/selectors/other-selector/other-selector.component.ts index f5ca15004..561c33bcd 100644 --- a/src/app/components/shared/selectors/other-selector/other-selector.component.ts +++ b/src/app/components/shared/selectors/other-selector/other-selector.component.ts @@ -21,6 +21,7 @@ export class OtherSelectorComponent implements OnInit, OnDestroy { beamModes: models.DatasetBeamModes; polarizations: models.DatasetPolarizations; subtypes: models.DatasetSubtypes; + groupID: string; public datasetProductTypes$ = this.store$.select(filtersStore.getProductTypes); public flightDirections$ = this.store$.select(filtersStore.getFlightDirections); @@ -28,6 +29,7 @@ export class OtherSelectorComponent implements OnInit, OnDestroy { public polarizations$ = this.store$.select(filtersStore.getPolarizations); public selectedDataset$ = this.store$.select(filtersStore.getSelectedDataset); public subtypes$ = this.store$.select(filtersStore.getSubtypes); + public groupID$ = this.store$.select(filtersStore.getGroupID); public flightDirectionTypes = models.flightDirections; public p = models.Props; @@ -62,6 +64,9 @@ export class OtherSelectorComponent implements OnInit, OnDestroy { this.subs.add( this.subtypes$.subscribe(subtypes => this.subtypes = subtypes) ); + this.subs.add( + this.groupID$.subscribe(groupID => this.groupID = groupID) + ); } public onNewDatasetBeamModes(beamModes: string[]): void { @@ -88,6 +93,13 @@ export class OtherSelectorComponent implements OnInit, OnDestroy { this.store$.dispatch(new filtersStore.SetSubtypes(subtypes)); } + public onNewGroupID(): void { + if(this.groupID.length > 29) { + this.groupID = this.groupID.slice(0, 29); + } + this.store$.dispatch(new filtersStore.setGroupID(this.groupID)); + } + ngOnDestroy() { this.subs.unsubscribe(); } diff --git a/src/app/components/shared/selectors/other-selector/other-selector.module.ts b/src/app/components/shared/selectors/other-selector/other-selector.module.ts index 4c2b6cb68..c756f1a62 100644 --- a/src/app/components/shared/selectors/other-selector/other-selector.module.ts +++ b/src/app/components/shared/selectors/other-selector/other-selector.module.ts @@ -10,6 +10,7 @@ import { ProductTypeSelectorModule } from '@components/shared/selectors/product- import { OtherSelectorComponent } from './other-selector.component'; import { BurstSelectorModule } from '../burst-selector'; import { SharedModule } from "@shared"; +import { MatInputModule } from '@angular/material/input'; @NgModule({ declarations: [ OtherSelectorComponent ], @@ -21,7 +22,8 @@ import { SharedModule } from "@shared"; MatSharedModule, ProductTypeSelectorModule, BurstSelectorModule, - SharedModule + SharedModule, + MatInputModule ], exports: [ OtherSelectorComponent ], }) diff --git a/src/app/components/sidebar/on-demand-subscriptions/index.ts b/src/app/components/sidebar/on-demand-subscriptions/index.ts deleted file mode 100644 index 3296d25ca..000000000 --- a/src/app/components/sidebar/on-demand-subscriptions/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './on-demand-subscriptions.module'; -export * from './on-demand-subscriptions.component'; diff --git a/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscription/on-demand-subscription.component.html b/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscription/on-demand-subscription.component.html deleted file mode 100644 index 7f7d8954c..000000000 --- a/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscription/on-demand-subscription.component.html +++ /dev/null @@ -1,103 +0,0 @@ -
    -
    -
    -
    - keyboard_arrow_right - keyboard_arrow_down -
    - -
    -
    - {{ subscription.name }} · - {{ subscription.jobType.name }} -
    -
    - {{ subscription.filters.start | shortDate }} to - - - {{ subscription.filters.end | shortDate }} - edit - -
    -
    -
    - -
    - - - - - - - -
    - - {{ 'LOADING' | translate }}... - - - {{ subscription.enabled ? 'Disable' : 'Enable' }} - -
    -
    -
    -
    - -
    -
    - -
    - - - {{ 'NEW_END_DATE' | translate }} - - - - - - - -
    -
    -
    - - -
    - -
    - - -
    -
    -
    diff --git a/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscription/on-demand-subscription.component.scss b/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscription/on-demand-subscription.component.scss deleted file mode 100644 index ee1e27501..000000000 --- a/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscription/on-demand-subscription.component.scss +++ /dev/null @@ -1,52 +0,0 @@ -.subscription { - padding: 10px; -} - -.subscription-disabled { - color: grey !important; -} - -.edit-icon { - font-size: 18px; -} - -.subscription-header { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; -} - -.subscription-header-info { - display: flex; -} - -.subscription-preview { - display: flex; - flex-direction: column; -} - -.preview-top { - font-weight: bold; -} - -.subscription-detail { - margin: 10px 10px 10px 45px; -} - -.subscription-action { - background-color: unset !important; -} - -.expand-arrow { - margin-right: 20px; - margin-top: -2px; -} - -.expand-arrow-icon { - font-size: 38px; -} - -.t-label { - width: 70px; -} diff --git a/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscription/on-demand-subscription.component.ts b/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscription/on-demand-subscription.component.ts deleted file mode 100644 index 42cf4be1c..000000000 --- a/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscription/on-demand-subscription.component.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { Component, OnInit, Input, Output, EventEmitter, ViewChild, OnDestroy } from '@angular/core'; -import { NgForm } from '@angular/forms'; - -import { MatDatepickerInputEvent } from '@angular/material/datepicker'; -import { Subject } from 'rxjs'; -import { tap, delay } from 'rxjs/operators'; -import { SubSink } from 'subsink'; -import * as moment from 'moment'; - -import * as models from '@models'; - -@Component({ - selector: 'app-on-demand-subscription', - templateUrl: './on-demand-subscription.component.html', - styleUrls: ['./on-demand-subscription.component.scss'] -}) -export class OnDemandSubscriptionComponent implements OnInit, OnDestroy { - @Input() subscription: models.OnDemandSubscription; - @Input() isExpanded: boolean; - @Input() isToggling: boolean; - - @Output() toggleEnabled = new EventEmitter(); - @Output() toggleExpand = new EventEmitter(); - @Output() loadSearch = new EventEmitter(); - @Output() viewProducts = new EventEmitter(); - @Output() newEnd = new EventEmitter(); - @Output() renew = new EventEmitter(); - - @ViewChild('endDateForm', { static: false }) public endDateForm: NgForm; - - public endDateErrors$ = new Subject(); - - public isEditingEndDate = false; - public newEndDate: Date; - public isEndError = false; - public isSubOutOfDate: boolean; - public expiresThisMonth: boolean; - - public subs = new SubSink(); - - constructor() { } - - ngOnInit(): void { - this.subs.add( - this.endDateErrors$.pipe( - tap(_ => { - this.isEndError = true; - this.subEndControl.reset(); - this.subEndControl.setErrors({'incorrect': true}); - }), - delay(820), - ).subscribe(_ => { - this.isEndError = false; - this.subEndControl.setErrors(null); - }) - ); - - const today = new Date(); - const subEnd = new Date(this.subscription.filters.end); - this.isSubOutOfDate = today > subEnd; - const daysLeft = Math.max(moment(subEnd).diff(moment(today), 'days'), 0); - this.expiresThisMonth = daysLeft < 29; - } - - public getMinDate(): Date { - return this.subscription.filters.start; - } - - public getMaxDate(): Date { - const current = new Date(); - current.setDate(current.getDate() + 179); - - return current; - } - - public onEndDateChange(e: MatDatepickerInputEvent): void { - let date: null | Date; - - if (!this.subEndControl.valid || !e.value) { - date = null; - this.endDateErrors$.next(); - } else { - const momentDate = e.value.set({h: 0}); - date = momentDate.toDate(); - } - - this.newEndDate = date; - } - - public onEditDate(): void { - if (this.isExpanded === false) { - this.toggleExpand.emit(this.subscription.id); - } - - this.newEndDate = this.subscription.filters.end; - this.isEditingEndDate = true; - } - - public onDoneEditing(): void { - this.isEditingEndDate = false; - - if (this.newEndDate) { - this.newEnd.emit(this.newEndDate); - } - } - - public onToggleEnabled(): void { - this.toggleEnabled.emit(this.subscription); - } - - public onToggleExpand(): void { - this.toggleExpand.emit(this.subscription.id); - } - - public loadOnDemandSearch(): void { - this.viewProducts.emit(this.subscription.name); - } - - public onRenewSubscription(): void { - this.renew.emit(this.subscription); - } - - public onLoadSearch(): void { - this.loadSearch.emit(this.subscription); - } - - private get subEndControl() { - return this.endDateForm.form - .controls['endInput']; - } - - ngOnDestroy() { - this.subs.unsubscribe(); - } -} diff --git a/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscription/subscription-filters/subscription-filters.component.html b/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscription/subscription-filters/subscription-filters.component.html deleted file mode 100644 index a519fde1f..000000000 --- a/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscription/subscription-filters/subscription-filters.component.html +++ /dev/null @@ -1,54 +0,0 @@ -
    -
    -
    {{ 'FILTERS' | translate }}
    - - -
    - -
    -
    -
    - {{ 'DATASET' | translate }}: {{ filters.platform }} -
    - -
    - Search Area: {{ showSearchAreaType(filters.intersectsWith) }} - - -
    - -
    - {{ 'PATH' | translate }}: {{ filters.relativeOrbit }} -
    - -
    - {{ 'FRAME' | translate}}: {{ filters.frame }} -
    - -
    - {{ 'PRODUCT_TYPE' | translate }}: {{ filters.processingLevel }} -
    - -
    - {{ 'POLARIZATIONS' | translate }}: {{ filters.polarization }} -
    - -
    - {{ 'FLIGHT_DIRECTION' | translate }}: {{ filters.flightDirection }} -
    - -
    - {{ 'BEAM_MODE' | translate }}: {{ filters.beamMode }} -
    -
    -
    diff --git a/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscription/subscription-filters/subscription-filters.component.scss b/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscription/subscription-filters/subscription-filters.component.scss deleted file mode 100644 index 480e13dcc..000000000 --- a/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscription/subscription-filters/subscription-filters.component.scss +++ /dev/null @@ -1,6 +0,0 @@ -.header { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; -} diff --git a/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscription/subscription-filters/subscription-filters.component.ts b/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscription/subscription-filters/subscription-filters.component.ts deleted file mode 100644 index d650dd6c4..000000000 --- a/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscription/subscription-filters/subscription-filters.component.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; - -@Component({ - selector: 'app-subscription-filters', - templateUrl: './subscription-filters.component.html', - styleUrls: ['./subscription-filters.component.scss'] -}) -export class SubscriptionFiltersComponent implements OnInit { - @Input() filters; - @Output() loadSearch = new EventEmitter(); - - constructor() { } - - ngOnInit(): void { - } - - public showSearchAreaType(polygon: string): string { - return polygon.split('(')[0]; - } - - public onLoadSearch() { - this.loadSearch.emit(); - } -} diff --git a/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscription/subscription-job-options/subscription-job-options.component.html b/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscription/subscription-job-options/subscription-job-options.component.html deleted file mode 100644 index 12be5d553..000000000 --- a/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscription/subscription-job-options/subscription-job-options.component.html +++ /dev/null @@ -1,9 +0,0 @@ -
    -
    {{ 'PROCESSING_OPTIONS' | translate }}
    -
    -
    -
    - {{ param.name }}: {{ param.val }} -
    -
    -
    diff --git a/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscription/subscription-job-options/subscription-job-options.component.ts b/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscription/subscription-job-options/subscription-job-options.component.ts deleted file mode 100644 index 545ea9a4f..000000000 --- a/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscription/subscription-job-options/subscription-job-options.component.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Component, OnInit, Input } from '@angular/core'; - -@Component({ - selector: 'app-subscription-job-options', - templateUrl: './subscription-job-options.component.html', - styleUrls: ['./subscription-job-options.component.scss'] -}) -export class SubscriptionJobOptionsComponent implements OnInit { - @Input() options = {}; - - constructor() { } - - ngOnInit(): void { - } - - public listFrom(options): any[] { - return Object.entries(options).map( - ([name, val]) => ({ name, val }) - ).filter(param => !!param.val); - } -} diff --git a/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscriptions.component.html b/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscriptions.component.html deleted file mode 100644 index 59901e1e8..000000000 --- a/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscriptions.component.html +++ /dev/null @@ -1,60 +0,0 @@ -
    -
    -
    - {{ 'ON_DEMAND_SUBSCRIPTIONS' | translate }} -
    -
    - -
    -
    - -
    - -
    - -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -

    {{ 'YOU_HAVE_NO_SUBSCRIPTIONS' | translate }}

    -
    -
    -
    - -
    -
    - -
    -
    -
    diff --git a/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscriptions.component.scss b/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscriptions.component.scss deleted file mode 100644 index 9aa2edc96..000000000 --- a/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscriptions.component.scss +++ /dev/null @@ -1,42 +0,0 @@ -@import "asf-theme"; - - -.subscriptions-content-wrapper { - display: flex; - flex-direction: row; - width: 100%; - max-height: calc(100vh - 200px); -} - -.no-saved-filters-presets h2 { - color: #c9cac4; - padding-left: 15px; -} - -.add-button-container { - display: flex; - justify-content: center; - width: 100px; - float: right; - margin-top: -32px; -} - -.fab-button { - border-radius: 40px !important; -} - -.subscription-disabled { - color: grey !important; -} - -.header-overrides { - flex-direction: column !important; - align-items: flex-start !important; - height: fit-content !important; - padding: 15px !important; -} - -.api-url { - display: flex; - font-size: 12px; -} diff --git a/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscriptions.component.ts b/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscriptions.component.ts deleted file mode 100644 index 5e36bc901..000000000 --- a/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscriptions.component.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; -import { MatDialog } from '@angular/material/dialog'; -import { SubSink } from 'subsink'; -import * as moment from 'moment'; - -import { Store } from '@ngrx/store'; -import { AppState } from '@store'; -import * as filtersStore from '@store/filters'; -import * as uiStore from '@store/ui'; -import * as hyp3Store from '@store/hyp3'; -import * as searchStore from '@store/search'; - -import { CreateSubscriptionComponent } from '@components/header/create-subscription'; -import { ScreenSizeService, Hyp3Service, WktService, MapService } from '@services'; -import * as models from '@models'; - -@Component({ - selector: 'app-on-demand-subscriptions', - templateUrl: './on-demand-subscriptions.component.html', - styleUrls: [ - './on-demand-subscriptions.component.scss', - '../save-user-filters/save-user-filters.component.scss', - '../saved-searches/saved-searches.component.scss', - ] -}) -export class OnDemandSubscriptionsComponent implements OnInit, OnDestroy { - public subscriptions = []; - public selectedSubId: string = null; - - public breakpoint: models.Breakpoints; - public breakpoints = models.Breakpoints; - public loadingSubs = new Set(); - - public searchType$ = this.store$.select(searchStore.getSearchType); - public searchType: models.SearchType; - public SearchType = models.SearchType; - - private subs = new SubSink(); - - constructor( - private store$: Store, - private dialog: MatDialog, - private screenSize: ScreenSizeService, - private wktService: WktService, - private mapService: MapService, - private hyp3: Hyp3Service, - ) { } - - ngOnInit(): void { - this.subs.add( - this.screenSize.breakpoint$.subscribe( - breakpoint => this.breakpoint = breakpoint - ) - ); - - this.subs.add( - this.store$.select(hyp3Store.getOnDemandSubscriptions).subscribe( - subs => this.subscriptions = subs - ) - ); - - this.subs.add( - this.searchType$.subscribe(searchType => { - this.searchType = searchType; - }) - ); - } - - public onCreateSubscription() { - const ref = this.dialog.open(CreateSubscriptionComponent, { - id: 'subscriptionQueueDialog', - maxWidth: '100vw', - maxHeight: '100vh', - }); - - ref.afterClosed().subscribe( - _ => { - this.store$.dispatch(new hyp3Store.LoadSubscriptions()); - } - ); - } - - public onRenewSubscription(sub: models.OnDemandSubscription): void { - let end; - - if (this.expiresThisMonth(sub)) { - const subEnd = new Date(sub.filters.end); - const newEnd = new Date(subEnd.setDate(subEnd.getDate() + 30)); - end = moment.utc(newEnd).format(); - } else { - const today = new Date(); - const endDate = new Date(today.setDate(today.getDate() + 30)); - end = moment.utc(endDate).format(); - } - - this.hyp3.editSubscription(sub.id, {end}).subscribe( - _ => { - this.store$.dispatch(new hyp3Store.LoadSubscriptions()); - } - ); - } - - private expiresThisMonth(sub: models.OnDemandSubscription): boolean { - const today = new Date(); - const subEnd = new Date(sub.filters.end); - const daysLeft = Math.max(moment(subEnd).diff(moment(today), 'days'), 0); - - return daysLeft < 29; - } - - public onLoadSearch(sub: models.OnDemandSubscription): void { - this.store$.dispatch(new searchStore.SetSearchType(models.SearchType.DATASET)); - this.store$.dispatch(new searchStore.ClearSearch()); - - const filters = sub.filters; - const features = this.wktService.wktToFeature( - filters.intersectsWith, - this.mapService.epsg() - ); - - this.mapService.setDrawFeature(features); - - if (filters.processingLevel !== undefined) { - models.sentinel_1.productTypes.forEach(productType => { - if (productType.apiValue === filters.processingLevel) { - this.store$.dispatch(new filtersStore.SetProductTypes([productType])); - } - }); - } - - if (filters.flightDirection !== undefined) { - this.store$.dispatch(new filtersStore.SetFlightDirections([filters.flightDirection])); - } - - if (filters.polarization !== undefined) { - this.store$.dispatch(new filtersStore.SetPolarizations(filters.polarization)); - } - - if (filters.beamMode !== undefined) { - this.store$.dispatch(new filtersStore.SetBeamModes(filters.beamMode)); - } - - if (filters.relativeOrbit !== undefined) { - this.store$.dispatch(new filtersStore.SetPathStart(filters.relativeOrbit)); - this.store$.dispatch(new filtersStore.SetPathEnd(filters.relativeOrbit)); - } - - if (filters.frame !== undefined) { - this.store$.dispatch(new filtersStore.SetFrameStart(filters.frame)); - this.store$.dispatch(new filtersStore.SetFrameEnd(filters.frame)); - } - - this.store$.dispatch(new uiStore.CloseSidebar()); - this.store$.dispatch(new searchStore.MakeSearch()); - } - - public onClose() { - this.store$.dispatch(new uiStore.CloseSidebar()); - } - - public onNewEndDate(subId, date: Date): void { - const end = moment.utc(date).format(); - - this.hyp3.editSubscription(subId, {end}).subscribe( - _ => { - this.store$.dispatch(new hyp3Store.LoadSubscriptions()); - } - ); - } - - public onToggleSelected(subId: string): void { - if (this.selectedSubId === subId) { - this.selectedSubId = null; - } else { - this.selectedSubId = subId; - } - } - - public onToggleEnabled(sub: models.OnDemandSubscription): void { - this.loadingSubs.add(sub.id); - - this.hyp3.editSubscription(sub.id, {enabled: !sub.enabled}).subscribe( - _ => { - this.store$.dispatch(new hyp3Store.LoadSubscriptions()); - this.loadingSubs.delete(sub.id); - } - ); - } - - public onLoadProductsFor(subName: string): void { - this.store$.dispatch(new uiStore.CloseSidebar()); - this.store$.dispatch(new searchStore.SetSearchType(models.SearchType.CUSTOM_PRODUCTS)); - this.store$.dispatch(new filtersStore.SetProjectName(subName)); - } - - ngOnDestroy() { - this.subs.unsubscribe(); - } -} - diff --git a/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscriptions.module.ts b/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscriptions.module.ts deleted file mode 100644 index fb7a53673..000000000 --- a/src/app/components/sidebar/on-demand-subscriptions/on-demand-subscriptions.module.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FormsModule } from '@angular/forms'; - -import { CreateSubscriptionModule } from '@components/header/create-subscription'; -import { CopyToClipboardModule } from '@components/shared/copy-to-clipboard'; -import { MatDatepickerModule } from '@angular/material/datepicker'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatInputModule } from '@angular/material/input'; -import { MAT_MOMENT_DATE_ADAPTER_OPTIONS, MatMomentDateModule } from '@angular/material-moment-adapter'; - -import { PipesModule } from '@pipes'; -import { MatSharedModule } from '@shared'; - -import { OnDemandSubscriptionsComponent } from './on-demand-subscriptions.component'; -import { OnDemandSubscriptionComponent } from './on-demand-subscription/on-demand-subscription.component'; -import { SubscriptionFiltersComponent } from './on-demand-subscription/subscription-filters/subscription-filters.component'; -import { SubscriptionJobOptionsComponent } from './on-demand-subscription/subscription-job-options/subscription-job-options.component'; -import {MatSlideToggleModule} from '@angular/material/slide-toggle'; -import {Hyp3UrlModule} from '@components/shared/hyp3-url/hyp3-url.module'; -import { SharedModule } from '@shared'; - - -@NgModule({ - declarations: [ - OnDemandSubscriptionsComponent, - OnDemandSubscriptionComponent, - SubscriptionFiltersComponent, - SubscriptionJobOptionsComponent, - ], - imports: [ - CommonModule, - FormsModule, - PipesModule, - MatSharedModule, - MatDatepickerModule, - MatFormFieldModule, - MatInputModule, - MatSharedModule, - MatMomentDateModule, - CreateSubscriptionModule, - MatSlideToggleModule, - CopyToClipboardModule, - Hyp3UrlModule, - SharedModule - ], - exports: [ - OnDemandSubscriptionsComponent, - OnDemandSubscriptionComponent - ], - providers: [ - { provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: { useUtc: true } } - ], -}) -export class OnDemandSubscriptionsModule { } diff --git a/src/app/components/sidebar/saved-searches/saved-search/search-filters/geographic-search-filters/geographic-search-filters.component.html b/src/app/components/sidebar/saved-searches/saved-search/search-filters/geographic-search-filters/geographic-search-filters.component.html index b9b217618..7da14d833 100644 --- a/src/app/components/sidebar/saved-searches/saved-search/search-filters/geographic-search-filters/geographic-search-filters.component.html +++ b/src/app/components/sidebar/saved-searches/saved-search/search-filters/geographic-search-filters/geographic-search-filters.component.html @@ -44,6 +44,9 @@
    {{ 'FULL_BURST_ID' | translate}}: {{ filters.fullBurstIDs }}
    +
    + {{ 'OPERA_BURST_ID' | translate}}: {{ filters.operaBurstIDs }} +
    diff --git a/src/app/components/sidebar/sidebar.component.html b/src/app/components/sidebar/sidebar.component.html index 45e8a4a68..260a3f243 100644 --- a/src/app/components/sidebar/sidebar.component.html +++ b/src/app/components/sidebar/sidebar.component.html @@ -6,7 +6,4 @@ - -
    diff --git a/src/app/components/sidebar/sidebar.module.ts b/src/app/components/sidebar/sidebar.module.ts index d6f14c0f0..a72e7e396 100644 --- a/src/app/components/sidebar/sidebar.module.ts +++ b/src/app/components/sidebar/sidebar.module.ts @@ -3,7 +3,6 @@ import { CommonModule } from '@angular/common'; import { SavedSearchesModule } from './saved-searches'; import { SaveUserFiltersModule } from './save-user-filters'; -import { OnDemandSubscriptionsModule } from './on-demand-subscriptions'; import { SidebarComponent } from './sidebar.component'; @@ -15,7 +14,6 @@ import { SidebarComponent } from './sidebar.component'; CommonModule, SavedSearchesModule, SaveUserFiltersModule, - OnDemandSubscriptionsModule, ], exports: [ SidebarComponent diff --git a/src/app/models/cmr-product.model.ts b/src/app/models/cmr-product.model.ts index 220e5e425..46bb2aad7 100644 --- a/src/app/models/cmr-product.model.ts +++ b/src/app/models/cmr-product.model.ts @@ -57,11 +57,18 @@ export interface CMRProductMetadata { // SLC BURST burst: SLCBurstMetadata | null; + // OPERA-S1 + opera: OperaS1Metadata | null; + fileName: string | null; job: Hyp3Job | null; // versioning pgeVersion: number | null; + + // BURST XML, OPERA-S1 + subproducts: any[]; + parentID: string; } export interface SLCBurstMetadata { @@ -75,6 +82,11 @@ export interface SLCBurstMetadata { subswath: string; } +export interface OperaS1Metadata { + operaBurstID: string; + additionalUrls: string[]; +} + export enum FlightDirection { ASCENDING = 'ASCENDING', DESCENDING = 'DESCENDING', diff --git a/src/app/models/dataset.model.ts b/src/app/models/dataset.model.ts index c2262c304..33f4b0888 100644 --- a/src/app/models/dataset.model.ts +++ b/src/app/models/dataset.model.ts @@ -52,6 +52,7 @@ export type DatasetSubtypes = DatasetSubtype[]; export const sentinel_1 = fromDatasets.sentinel_1; export const sentinel_1_bursts = fromDatasets.sentinel_1_bursts; +export const opera_s1 = fromDatasets.opera_s1; export const alos = fromDatasets.alos; export const avnir = fromDatasets.avnir; export const sirc = fromDatasets.sirc; @@ -67,6 +68,7 @@ export const seasat = fromDatasets.seasat; export const datasetList: Dataset[] = [ fromDatasets.sentinel_1, fromDatasets.sentinel_1_bursts, + fromDatasets.opera_s1, fromDatasets.alos, fromDatasets.avnir, fromDatasets.sirc, diff --git a/src/app/models/datasets/index.ts b/src/app/models/datasets/index.ts index 5424ffb33..2fca65ae8 100644 --- a/src/app/models/datasets/index.ts +++ b/src/app/models/datasets/index.ts @@ -11,3 +11,4 @@ export * from './beta'; export * from './sirc'; export * from './avnir'; export * from './sentinel-1-burst'; +export * from './opera_s1'; diff --git a/src/app/models/datasets/opera_s1.ts b/src/app/models/datasets/opera_s1.ts new file mode 100644 index 000000000..5601dc0ef --- /dev/null +++ b/src/app/models/datasets/opera_s1.ts @@ -0,0 +1,61 @@ +import { Props } from '../filters.model'; + +export const opera_s1 = { + id: 'OPERA-S1', + name: 'OPERA-S1', + subName: '', + beta: true, + properties: [ + Props.DATE, + Props.BEAM_MODE, + Props.FLIGHT_DIRECTION, + Props.POLARIZATION, + Props.ABSOLUTE_ORBIT, + Props.BASELINE_TOOL, + Props.SUBTYPE, + ], + apiValue: { + collections: + "C1259974840-ASF,C1259976861-ASF,C1259981910-ASF,C1259982010-ASF,C1259975087-ASFDEV,C1259976862-ASFDEV,C1259983643-ASFDEV,C1259983645-ASFDEV,C2777443834-ASF,C2777436413-ASF" }, + date: { start: new Date('2014/06/15 03:44:43 UTC') }, + infoUrl: 'https://asf.alaska.edu/datasets/daac/opera/', + citationUrl: 'https://asf.alaska.edu/datasets/daac/opera/', + frequency: 'C-Band', + source: { + name: 'OPERA-JPL', + url: 'https://www.jpl.nasa.gov/go/opera' + }, + productTypes: [ + { + apiValue: 'RTC', + displayName: 'L2 Radiometric Terrain Corrected (RTC)' + }, { + apiValue: 'CSLC', + displayName: 'L2 Co-registered Single Look Complex (CSLC)' + }, { + apiValue: 'RTC-STATIC', + displayName: 'L2 Radiometric Terrain Corrected Static Layer (RTC-STATIC)' + }, { + apiValue: 'CSLC-STATIC', + displayName: 'L2 Co-registered Single Look Complex Static Layer (CSLC-STATIC)' + } + ], + beamModes: [ + 'IW', 'EW' + ], + polarizations: [ + 'VV', + 'HH', + 'HV', + 'VH' + ], + subtypes: [{ + displayName: 'Sentinel-1A', + apiValue: 'SA', + }, { + displayName: 'Sentinel-1B', + apiValue: 'SB', + }], + platformDesc: 'OPERA_S1_DESC', + platformIcon: '/assets/icons/satellite_alt_black_48dp.svg', +}; diff --git a/src/app/models/search.model.ts b/src/app/models/search.model.ts index 6cf0cc9f2..a4c33811e 100644 --- a/src/app/models/search.model.ts +++ b/src/app/models/search.model.ts @@ -80,6 +80,7 @@ export interface GeographicFiltersType { selectedMission: null | string; fullBurstIDs: string[]; + operaBurstIDs: string[]; } export interface SarviewsFiltersType { diff --git a/src/app/models/sidebar.model.ts b/src/app/models/sidebar.model.ts index b803bd681..14f0307ea 100644 --- a/src/app/models/sidebar.model.ts +++ b/src/app/models/sidebar.model.ts @@ -2,6 +2,5 @@ export enum SidebarType { SAVED_SEARCHES = 'Saved Searches', SEARCH_HISTORY = 'Search History', USER_FILTERS = 'User Filters', - ON_DEMAND_SUBSCRIPTIONS = 'On Demand Subscriptions', NONE = 'None', } diff --git a/src/app/services/dataset-for-product.service.ts b/src/app/services/dataset-for-product.service.ts index d13333471..e85e0c8b8 100644 --- a/src/app/services/dataset-for-product.service.ts +++ b/src/app/services/dataset-for-product.service.ts @@ -17,6 +17,9 @@ export class DatasetForProductService { if(scene.metadata.productType === 'BURST') { return models.sentinel_1_bursts; } + if(scene.id.startsWith('OPERA')) { + return models.opera_s1; + } const exact = (datasetID, sceneDataset) => ( datasetID === sceneDataset diff --git a/src/app/services/envs/env-devel.ts b/src/app/services/envs/env-devel.ts index 7d45f9d88..bd07e4b83 100644 --- a/src/app/services/envs/env-devel.ts +++ b/src/app/services/envs/env-devel.ts @@ -11,7 +11,7 @@ export const env = { }, test: { api: 'https://api-test.asf.alaska.edu', - api_maturity: 'prod', + api_maturity: 'test', auth: 'https://auth.asf.alaska.edu', urs: 'https://urs.earthdata.nasa.gov', urs_client_id: 'BO_n7nTIlMljdvU6kRRB3g', diff --git a/src/app/services/envs/env-test.ts b/src/app/services/envs/env-test.ts index e38d373fe..bd07e4b83 100644 --- a/src/app/services/envs/env-test.ts +++ b/src/app/services/envs/env-test.ts @@ -11,7 +11,7 @@ export const env = { }, test: { api: 'https://api-test.asf.alaska.edu', - api_maturity: 'prod', + api_maturity: 'test', auth: 'https://auth.asf.alaska.edu', urs: 'https://urs.earthdata.nasa.gov', urs_client_id: 'BO_n7nTIlMljdvU6kRRB3g', @@ -22,4 +22,3 @@ export const env = { }, defaultEnv: 'test' }; - diff --git a/src/app/services/keyboard.service.ts b/src/app/services/keyboard.service.ts index 84956a008..b90e8d64a 100644 --- a/src/app/services/keyboard.service.ts +++ b/src/app/services/keyboard.service.ts @@ -23,7 +23,7 @@ export class KeyboardService { init() { const scenesSorted$ = this.scenesService.sortScenes$( - this.scenesService.scenes$() + this.scenesService.scenes$ ); fromEvent(document, 'keydown').pipe( diff --git a/src/app/services/product.service.ts b/src/app/services/product.service.ts index 4344bb36a..cefcba482 100644 --- a/src/app/services/product.service.ts +++ b/src/app/services/product.service.ts @@ -35,7 +35,7 @@ export class ProductService { if ( !filename.includes(g.gn)) { filename = `${g.gn}-${filename}`; } - return ({ + let product = { name: g.gn, productTypeDisplay: g.ptd || g.gn, file: filename, @@ -48,7 +48,10 @@ export class ProductService { groupId: g.gid.replace('{gn}', g.gn), isUnzippedFile: false, metadata: this.getMetadataFrom(g) - }); + }; + + product.metadata.subproducts = this.getSubproducts(product) + return product; } ); @@ -86,7 +89,10 @@ export class ProductService { job: null, fileName: null, burst: g.s1b ? g.s1b : null, - pgeVersion: g.pge !== null ? parseFloat(g.pge) : null + opera: g.s1o ? g.s1o : null, + pgeVersion: g.pge !== null ? parseFloat(g.pge) : null, + subproducts: [], + parentID: null }) private isNumber = n => !isNaN(n) && isFinite(n); @@ -94,4 +100,95 @@ export class ProductService { (dateString: string): moment.Moment => { return moment.utc(dateString); } + + private getSubproducts(product: models.CMRProduct): models.CMRProduct[] { + if (product.metadata.productType === 'BURST') { + return [this.burstXMLFromScene(product)] + } + if (!!product.metadata.opera) { + return this.operaSubproductsFromScene(product) + } + return [] + } + + + private burstXMLFromScene(product: models.CMRProduct) { + let p = { + ...product, + downloadUrl: product.downloadUrl.replace('tiff', 'xml'), + productTypeDisplay: 'XML Metadata (BURST)', + file: product.file.replace('tiff', 'xml'), + id: product.id + '-XML', + bytes: 0, + metadata: { + ...product.metadata, + productType: product.metadata.productType + '_XML', + subproducts: [], + parentID: product.id, + }, + } as models.CMRProduct; + + return p; + } + + private operaProductTypeDisplays = { + hh: 'HH GeoTIFF', + hv: 'HV GeoTIFF', + vv: 'VV GeoTIFF', + vh: 'VH GeoTIFF', + mask: 'Mask GeoTIFF', + h5: 'HDF5', + xml: 'Metadata XML', + rtc_anf_gamma0_to_sigma0: 'RTC Gamma to Sigma GeoTIFF', + number_of_looks: '# of Looks GeoTIFF', + incidence_angle: 'Incidence Angle GeoTIFF', + rtc_anf_gamma0_to_beta0: 'RTC Gamm to Beta GeoTIFF', + local_incidence_angle: 'Local Incidence Angle GeoTIFF' + } + + private operaSubproductsFromScene(product: models.CMRProduct) { + let products = [] + + let reg = product.downloadUrl.split(/(_v[0-9]\.[0-9]){1}(\.(\w*)|(_(\w*(_*))*.))*/); + let file_suffix = !!reg[3] ? reg[3] : reg[5] + product.productTypeDisplay = this.operaProductTypeDisplays[file_suffix.toLowerCase()] + + for (const p of product.metadata.opera.additionalUrls.filter(url => url !== product.downloadUrl)) { + reg = p.split(/(_v[0-9]\.[0-9]){1}(\.(\w*)|(_(\w*(_*))*.))*/); + file_suffix = !!reg[3] ? reg[3] : reg[5] + const productTypeDisplay = this.operaProductTypeDisplays[file_suffix.toLowerCase()]; + + const fileID = p.split('/').slice(-1)[0] + + let subproduct = { + ...product, + downloadUrl: p, + productTypeDisplay: productTypeDisplay || p, + file: fileID, + id: product.id + '-' + file_suffix, + bytes: 0, + browses: [], + thumbnail: null, + metadata: { + ...product.metadata, + productType: product.metadata.productType, + parentID: product.id, + subproducts: [] + }, + } as models.CMRProduct; + + products.push(subproduct) + } + + return products.sort((a, b) => { + if(['hh', 'vv', 'vh', 'hv'].includes(a.productTypeDisplay.slice(0, 2).toLowerCase())) { + return -1; + } else if(['hh', 'vv', 'vh', 'hv'].includes(b.productTypeDisplay.slice(0, 2).toLowerCase())) + return 1; + + return a.productTypeDisplay < b.productTypeDisplay ? -1 : 1 + } + ) + } + } diff --git a/src/app/services/scenes.service.ts b/src/app/services/scenes.service.ts index f817048b3..32f86c65e 100644 --- a/src/app/services/scenes.service.ts +++ b/src/app/services/scenes.service.ts @@ -46,9 +46,7 @@ export class ScenesService { ); } - public scenes$(): Observable { - return ( - this.filterByProductName$( + public scenes$: Observable = this.filterByProductName$( this.projectNameFilter$( this.hideExpired$( this.jobStatusFilter$( @@ -56,8 +54,7 @@ export class ScenesService { this.filterBaselineValues$( this.filterByDate$( this.store$.select(getScenes) - )))))))); - } + ))))))); public withBrowses$(scenes$: Observable): Observable { @@ -72,7 +69,7 @@ export class ScenesService { this.store$.select(getTemporalSortDirection), this.store$.select(getPerpendicularSortDirection)] ).pipe( - debounceTime(0), + debounceTime(50), map( ([scenes, tempSort, perpSort]) => { if (tempSort === ColumnSortDirection.NONE && perpSort === ColumnSortDirection.NONE) { diff --git a/src/app/services/search-params.service.ts b/src/app/services/search-params.service.ts index fb89acf3f..406a2043a 100644 --- a/src/app/services/search-params.service.ts +++ b/src/app/services/search-params.service.ts @@ -112,7 +112,9 @@ export class SearchParamsService { this.polarizations$(), this.maxResults$(), this.missionParam$(), - this.burstParams$(),] + this.burstParams$(), + this.operaBurstParams$(), + this.groupID$()] ).pipe( map((params: any[]) => params .reduce( @@ -150,6 +152,22 @@ export class SearchParamsService { ) ); } + + private operaBurstParams$() { + return this.store$.select(filterStore.getOperaBurstIDs).pipe( + map(operaIDs => ({ + operaburstid: operaIDs?.join(',')}) + ) + ); + } + + private groupID$() { + return this.store$.select(filterStore.getGroupID).pipe( + map(groupid => ({ + groupid + })) + ) + } private searchPolygon$() { return combineLatest([ diff --git a/src/app/services/url-state.service.ts b/src/app/services/url-state.service.ts index 711f72ddb..bd1a81ee2 100644 --- a/src/app/services/url-state.service.ts +++ b/src/app/services/url-state.service.ts @@ -486,6 +486,20 @@ export class UrlStateService { map(list => ({ fullBurstIDs: list?.map(num => num.toString()).join(',') })) ), loader: this.loadFullBurstIDs + }, + { + name: 'operaBurstIDs', + source: this.store$.select(filterStore.getOperaBurstIDs).pipe( + map(list => ({ operaBurstID: list?.map(num => num.toString()).join(',') })) + ), + loader: this.loadOperaBurstIDs + }, + { + name: 'groupID', + source: this.store$.select(filterStore.getGroupID).pipe( + map(groupId => ({ groupId }) + )), + loader: this.loadGroupId }]; } @@ -804,4 +818,13 @@ export class UrlStateService { const list = ids.split(','); return new filterStore.setFullBurst(list); }; + + private loadOperaBurstIDs = (ids: string): Action => { + const list = ids.split(','); + return new filterStore.setOperaBurstID(list); + }; + + private loadGroupId = (id: string): Action => { + return new filterStore.setGroupID(id); + }; } diff --git a/src/app/store/filters/filters.action.ts b/src/app/store/filters/filters.action.ts index 5ad1d8ad2..a889d0f87 100644 --- a/src/app/store/filters/filters.action.ts +++ b/src/app/store/filters/filters.action.ts @@ -91,6 +91,10 @@ export enum FiltersActionType { SET_GEOCODE = '[Filters] Set geocode area name', SET_FULL_BURST = '[Filters] Set Full Burst IDs', + + SET_OPERA_BURST_ID = '[Filters] Set Full OPERA S1 Burst IDs', + + SET_GROUP_ID = '[Filters] Set Sentinel-1 Group ID' } export class SetSelectedDataset implements Action { @@ -432,6 +436,19 @@ export class setFullBurst implements Action { constructor(public payload: string[]) {} } +export class setOperaBurstID implements Action { + public readonly type = FiltersActionType.SET_OPERA_BURST_ID; + + constructor(public payload: string[]) {} +} + +export class setGroupID implements Action { + public readonly type = FiltersActionType.SET_GROUP_ID; + + constructor(public payload: string) {} +} + + export type FiltersActions = | SetSelectedDataset @@ -493,4 +510,6 @@ export type FiltersActions = | SetEventProductSorting | ClearEventFilters | ClearHyp3ProductTypes - | setFullBurst; + | setFullBurst + | setOperaBurstID + | setGroupID; diff --git a/src/app/store/filters/filters.reducer.ts b/src/app/store/filters/filters.reducer.ts index 82ab59784..464d1541f 100644 --- a/src/app/store/filters/filters.reducer.ts +++ b/src/app/store/filters/filters.reducer.ts @@ -50,6 +50,10 @@ export interface FiltersState { geocode: null | string; fullBurstIDs: null | string[]; + + operaBurstIDs: null | string[]; + + groupID: null | string; } @@ -120,7 +124,11 @@ export const initState: FiltersState = { geocode: null, - fullBurstIDs: [] + fullBurstIDs: [], + + operaBurstIDs: [], + + groupID: null }; @@ -142,6 +150,7 @@ export function filtersReducer(state = initState, action: FiltersActions): Filte polarizations: [], subtypes: [], fullBurstIDs: [], + operaBurstIDs: [], selectedMission: null, }; } @@ -350,6 +359,12 @@ export function filtersReducer(state = initState, action: FiltersActions): Filte fullBurstIDs: [metadata.burst.fullBurstID], } } + if(action.payload.dataset.id === models.opera_s1.id) { + filters = { + groupID: action.payload.product.groupId, + selectedDatasetId: 'SENTINEL-1' + } + } return { ...state, ...filters @@ -394,7 +409,9 @@ export function filtersReducer(state = initState, action: FiltersActions): Filte flightDirections: new Set([]), selectedMission: null, geocode: null, - fullBurstIDs: [] + fullBurstIDs: [], + operaBurstIDs: [], + groupID: null }; } @@ -610,7 +627,8 @@ export function filtersReducer(state = initState, action: FiltersActions): Filte flightDirections: new Set(filters.flightDirections), subtypes, selectedMission: filters.selectedMission, - fullBurstIDs: filters.fullBurstIDs + fullBurstIDs: filters.fullBurstIDs, + operaBurstIDs: filters.operaBurstIDs }; } } @@ -726,6 +744,18 @@ export function filtersReducer(state = initState, action: FiltersActions): Filte fullBurstIDs: action.payload } } + case FiltersActionType.SET_OPERA_BURST_ID: { + return { + ...state, + operaBurstIDs: action.payload + } + } + case FiltersActionType.SET_GROUP_ID: { + return { + ...state, + groupID: action.payload + } + } default: { return state; } @@ -897,7 +927,8 @@ export const getGeographicSearch = createSelector( flightDirections: state.flightDirections, subtypes: state.subtypes, selectedMission: state.selectedMission, - fullBurstIDs: state.fullBurstIDs + fullBurstIDs: state.fullBurstIDs, + operaBurstIDs: state.operaBurstIDs }) ); @@ -1008,3 +1039,13 @@ export const getFullBurstIDs = createSelector( getFiltersState, (state: FiltersState) => state.fullBurstIDs ) + +export const getOperaBurstIDs = createSelector( + getFiltersState, + (state: FiltersState) => state.operaBurstIDs +) + +export const getGroupID = createSelector( + getFiltersState, + (state: FiltersState) => state.groupID +) \ No newline at end of file diff --git a/src/app/store/scenes/scenes.effect.ts b/src/app/store/scenes/scenes.effect.ts index 272c355ff..c20aab1ef 100644 --- a/src/app/store/scenes/scenes.effect.ts +++ b/src/app/store/scenes/scenes.effect.ts @@ -71,8 +71,7 @@ export class ScenesEffects { ofType(ScenesActionType.SET_SCENES), filter(scenes => !!scenes.payload.products), filter(scenes => scenes.payload.products.length > 0), - debounceTime(1000), - withLatestFrom(this.sceneService.scenes$()), + withLatestFrom(this.sceneService.scenes$), map(([action, filtered]) => ({ products: filtered, searchType: action.payload.searchType })), distinctUntilChanged(), withLatestFrom(this.store$.select(getSelectedScene)), @@ -126,7 +125,7 @@ export class ScenesEffects { public onHideRawData = createEffect(() => this.actions$.pipe( ofType(UIActionType.HIDE_S1_RAW_DATA), debounceTime(1000), - withLatestFrom(this.sceneService.scenes$()), + withLatestFrom(this.sceneService.scenes$), withLatestFrom(this.store$.select(getSelectedScene)), map(([[_, scenes], selected]) => { if (!scenes.map(scene => scene.id).includes(selected.id)) { diff --git a/src/app/store/scenes/scenes.reducer.ts b/src/app/store/scenes/scenes.reducer.ts index 9d4f405db..8bb8d4bb9 100644 --- a/src/app/store/scenes/scenes.reducer.ts +++ b/src/app/store/scenes/scenes.reducer.ts @@ -2,7 +2,7 @@ import { createFeatureSelector, createSelector } from '@ngrx/store'; import { ScenesActionType, ScenesActions } from './scenes.action'; -import { CMRProduct, UnzippedFolder, ColumnSortDirection, SarviewsEvent, SarviewsProduct } from '@models'; +import { CMRProduct, UnzippedFolder, ColumnSortDirection, SarviewsEvent, SarviewsProduct, opera_s1 } from '@models'; import { PinnedProduct } from '@services/browse-map.service'; import { createSelectorFactory, defaultMemoize } from '@ngrx/store'; @@ -66,19 +66,22 @@ export const initState: ScenesState = { export function scenesReducer(state = initState, action: ScenesActions): ScenesState { switch (action.type) { case ScenesActionType.SET_SCENES: { - let bursts: CMRProduct[] = [] + let subproducts: CMRProduct[] = [] let searchResults = action.payload.products.map(p => p.metadata.productType === 'BURST' ? ({...p, productTypeDisplay: 'Single Look Complex (BURST)'}) as CMRProduct : p) + + const ungrouped_product_types = [...opera_s1.productTypes, {apiValue: 'BURST'}, {apiValue: 'BURST_XML'}].map(m => m.apiValue) for (let product of searchResults) { - if(product.metadata.productType === 'BURST') { - const p = burstXMLFromScene(product) - bursts.push(p) + if(product.metadata.subproducts.length > 0) { + for (let subproduct of product.metadata.subproducts) { + subproducts.push(subproduct) + } } } - searchResults = searchResults.concat(bursts) + searchResults = searchResults.concat(subproducts) const products = searchResults .reduce((total, product) => { @@ -86,32 +89,39 @@ export function scenesReducer(state = initState, action: ScenesActions): ScenesS return total; }, {}); - - const productIDs = searchResults.reduce((total, product) => { - total[product.metadata.productType] = product; - - return total; - }, {}); let productGroups: {[id: string]: string[]} = {} let scenes: {[id: string]: string[]} = {} - - if (Object.keys(productIDs).length <= 2 && Object.keys(productIDs)[0].toUpperCase() === 'BURST') { + // const productIDs = searchResults.reduce((total, product) => { + // total[product.metadata.productType] = product; + + // return total; + // }, {}); + // if (Object.keys(productIDs).length <= 2 && Object.keys(productIDs)[0].toUpperCase() === 'BURST') { + // productGroups = searchResults.reduce((total, product) => { + // const scene = total[product.name] || []; + + // total[product.name] = [...scene, product.id]; + // return total; + // }, {}) + // } else { productGroups = searchResults.reduce((total, product) => { - const scene = total[product.name] || []; - - total[product.name] = [...scene, product.id]; - return total; - }, {}) - } else { - productGroups = searchResults.reduce((total, product) => { - const scene = total[product.groupId] || []; - - total[product.groupId] = [...scene, product.id]; + let groupCriteria = product.groupId; + if (product.metadata.subproducts.length > 0) { + groupCriteria = product.id; + } else if(ungrouped_product_types.includes(product.metadata.productType)) { + if(isSubProduct(product)) { + groupCriteria = product.metadata.parentID; + } else { + groupCriteria = product.id; + } + } + const scene = total[groupCriteria] || []; + + total[groupCriteria] = [...scene, product.id]; return total; }, {}); - } - + // } for (const [groupId, productNames] of Object.entries(productGroups)) { (productNames).sort( @@ -362,11 +372,19 @@ export const allScenesWithBrowse = (scenes: {[id: string]: string[]}, products) }; function arrayEquals(a, b) { + return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.toString() === b.toString() && - a.every((value, index) => value.toString() === b[index].toString()) + a.every((value, index) => { + if(Array.isArray(value) && Array.isArray(b[index])) { + return arrayEquals(value, b[index]) + } else { + value.id === b[index].id + } + } + ) } export const createArraySelector = createSelectorFactory( @@ -472,17 +490,21 @@ const productsForScene = (selected, state) => { return; } - const productTypes = Object.values(state.products).reduce((total, product: CMRProduct) => { - total[product.metadata.productType] = product; + // const productTypes = Object.values(state.products).reduce((total, product: CMRProduct) => { + // total[product.metadata.productType] = product; - return total; - }, {}); + // return total; + // }, {}); let products = [] - if (Object.keys(productTypes).length <= 2 && Object.keys(productTypes)[0] === 'BURST') { - products = state.scenes[selected.name] || []; - } else { + const ungrouped_product_types = [...opera_s1.productTypes, {apiValue: 'BURST'}, {apiValue: 'BURST_XML'}].map(m => m.apiValue) + // if (Object.keys(productTypes).length <= 2 && Object.keys(productTypes)[0] === 'BURST') { + // products = state.scenes[selected.name] || []; + if(ungrouped_product_types.includes(selected.metadata.productType)) { + products = state.scenes[selected.id] || []; + } + else { products = state.scenes[selected.groupId] || [] } @@ -739,19 +761,6 @@ function eqSet(aSet, bSet): boolean { return true; } -function burstXMLFromScene(product: CMRProduct) { - let p = { - ...product, - downloadUrl: product.downloadUrl.replace('tiff', 'xml'), - productTypeDisplay: 'XML Metadata (BURST)', - file: product.file.replace('tiff', 'xml'), - id: product.id + '-XML', - bytes: 0, - metadata: { - ...product.metadata, - productType: product.metadata.productType + '_XML' - } - } as CMRProduct; - - return p; +function isSubProduct(product): boolean { + return !!product.metadata.parentID; } \ No newline at end of file diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 0376cd37a..192300054 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -508,6 +508,9 @@ "OPEN_LINK_TO_DOCUMENTATION": "Open link to documentation.", "OPEN_MANUAL": "Open Manual", "OPEN_STREET_MAP_CONTRIBUTORS": "© OpenStreetMap contributors", + "OPERA_BURST_ID": "Opera Burst IDs", + "OPERA_S1_DESC": "Sentinel-1 RTC backscatter products providing near-global coverage, as well as Sentinel-1 CSLC products covering North America.", + "OPERA_S1_SOURCE_DATA": "Source Sentinel-1 scene for selected OPERA-S1 product", "OPTION_OR": "option or", "OPTION_OR ": "option or", "OPTIONS": "Options", @@ -726,6 +729,7 @@ "SORT_BY": "Sort By", "SORT_CRITERIA": "Sort Criteria", "SORT_ORDER": "Sort Order", + "SOURCE_DATA": "Source Data", "START": "Start", "START DATE": "Start Date", "START_ADDING_CUSTOM_PAIR": "Start adding custom pair", @@ -734,6 +738,7 @@ "START_DATE_TIME_BEAM_MODE_PATH_FRAME_FLIGHT_DIRECTION_POLARIZATION_ABSOLUTE_ORBIT_AND_A": "Start Date/Time, Beam Mode, Path, Frame, Flight Direction, Polarization, Absolute Orbit, and a", "START_TIME": "Start Time", "STARTING_CORNER_MOVE_THE_MOUSE_THEN_CLICK_AGAIN_TO_FINISH_THE_BOX": "starting corner, move the mouse, then click again to finish the box.", + "STATIC_LAYER": "Static Layer", "STATISTICS_AND_GITHUB_REPOSITORY": "Statistics and Github Repository", "STOP_ADDING_CUSTOM_PAIR": "Stop adding custom pair", "STOP_DOWNLOAD": "Stop download",