diff --git a/src/app/components/map/map-controls/map-controls.component.ts b/src/app/components/map/map-controls/map-controls.component.ts index 42779fe8b..850e7a141 100644 --- a/src/app/components/map/map-controls/map-controls.component.ts +++ b/src/app/components/map/map-controls/map-controls.component.ts @@ -236,7 +236,7 @@ export class MapControlsComponent implements OnInit, OnDestroy { // url = this.selectedScene.downloadUrl; // } - this.mapService.setSelectedBrowse(url, wkt); + this.mapService.setSelectedBrowse(url, wkt, this.selectedScene); } private getBrowseCount() { 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 14aa51fd1..bbdc998f2 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 @@ -371,7 +371,7 @@ export class SceneDetailComponent implements OnInit, OnDestroy { // url = this.scene.downloadUrl; // } - this.mapService.setSelectedBrowse(url, wkt); + this.mapService.setSelectedBrowse(url, wkt, this.scene); } public onToggleSarviewsProductPin() { 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 e516ba7cc..31263457c 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 @@ -27,7 +27,7 @@ > { + let component: SceneFilesComponent; + let fixture: ComponentFixture; + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [SceneFilesModule, + ToastrModule.forRoot({ + positionClass: 'toast-bottom-right' + }) + ], + providers: [ + provideMockStore(), + provideHttpClient(), + provideHttpClientTesting(), + ], + }); + }); + + beforeEach(() => { fixture = TestBed.createComponent(SceneFilesComponent); component = fixture.componentInstance; fixture.detectChanges(); }); + + it('should create', () => { expect(component).toBeDefined(); }); + + it('should be able to generate params for L2 GSLC equivalent of L1 RSLC products', () => expect( + component.getNisarL2Params('NISAR_L1_PR_RSLC_088_039_D_114_2005_SHSH_A_20251114T222008_20251114T222017_T05000_N_P_J_001', 'RSLC')).toEqual( + ({ + granule_list: 'NISAR_L2_PR_GSLC_088_039_D_114_2005_SHSH_A_20251114T222008_20251114T222017_T05000_N_P_J_001' + }) + ) + ) + it('should be able to generate params for L2 GUNW equivalent of L1 RUNW products', () => expect( + component.getNisarL2Params('NISAR_L1_PR_RUNW_015_156_A_010_016_2000_SV_20230619T000803_20230619T000817_20230701T000803_20230701T000817_T00406_N_P_J_001', 'RUNW')).toEqual( + ({ + granule_list: 'NISAR_L2_PR_GUNW_015_156_A_010_016_2000_SV_20230619T000803_20230619T000817_20230701T000803_20230701T000817_T00406_N_P_J_001' + }) + ) + ) + it('should be able to generate params for L2 GOFF equivalent of L1 ROFF products', () => expect( + component.getNisarL2Params('NISAR_L1_PR_ROFF_039_002_D_123_040_4000_SH_20240403T084941_20240403T084954_20240415T084941_20240415T084954_T00408_N_P_J_001', 'ROFF')).toEqual( + ({ + granule_list: 'NISAR_L2_PR_GOFF_039_002_D_123_040_4000_SH_20240403T084941_20240403T084954_20240415T084941_20240415T084954_T00408_N_P_J_001' + }) + ) + ) + it('should be able to generate params for L2 UR equivalent of L1 UR products', () => expect( + component.getNisarL2Params('NISAR_L1_UR_ROFF_039_002_D_121_040_7700_SH_20240403T084849_20240403T084905_20240415T084849_20240415T084905_T00408_F_P_J_001', 'ROFF')).toEqual( + ({ + granule_list: 'NISAR_L2_UR_GOFF_039_002_D_121_040_7700_SH_20240403T084849_20240403T084905_20240415T084849_20240415T084905_T00408_F_P_J_001' + }) + ) + ) +}) \ No newline at end of file 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 e005fa48e..c588c1995 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 @@ -54,6 +54,7 @@ import { MatDialog } from '@angular/material/dialog'; import { ScreenSizeService } from '@services'; import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; import * as filterStore from '@store/filters'; +import { L1L2BrowseCollectionMapping } from '@models/datasets/nisar'; @Component({ selector: 'app-scene-files', @@ -488,7 +489,7 @@ export class SceneFilesComponent return toCMRProduct; } - public StaticLayerProduct$ = this.store$ + public dynamicallySearchedProduct$ = this.store$ .select(scenesStore.getSelectedScene) .pipe( debounceTime(100), @@ -528,12 +529,31 @@ export class SceneFilesComponent }), ), ); + }else if(!!scene && scene.id?.startsWith('NISAR_L1')) { + if (!Object.keys(L1L2BrowseCollectionMapping).includes(scene.metadata.productType)) { + return of([]) + } + + const queryParams = this.getNisarL2Params(scene.id, scene.metadata.productType) + + return this.asfApiService.query(queryParams).pipe( + map((products) => + products?.results?.length > 0 + ? this.productService.fromResponse(products).slice(0, 1) + : [], + ) + ); } else { return of([]); } }), ); + public getNisarL2Params(productID: string, productType: string) { + return { + granule_list: productID.replaceAll(productType, L1L2BrowseCollectionMapping[productType].productType).replaceAll('L1', 'L2') + }; + } public getProductSceneCount(products: SarviewsProduct[]) { const outputList = products.reduce((prev, product) => { const temp = product.granules.map((granule) => granule.granule_name); diff --git a/src/app/models/datasets/nisar.ts b/src/app/models/datasets/nisar.ts index 2125614c0..ad4378a29 100644 --- a/src/app/models/datasets/nisar.ts +++ b/src/app/models/datasets/nisar.ts @@ -276,3 +276,9 @@ export const nisar = { platformDesc: 'NISAR_DESC', platformIcon: '/assets/icons/satellite_alt_black_48dp.svg', }; + +export const L1L2BrowseCollectionMapping = { + RSLC: {collection: 'NISAR_L2_GSLC', productType: 'GSLC'}, + RUNW: {collection: 'NISAR_L2_GUNW', productType: 'GUNW'}, + ROFF: {collection: 'NISAR_L2_GOFF', productType: 'GOFF'} +}; diff --git a/src/app/services/browse-overlay.service.spec.ts b/src/app/services/browse-overlay.service.spec.ts new file mode 100644 index 000000000..92144d8f6 --- /dev/null +++ b/src/app/services/browse-overlay.service.spec.ts @@ -0,0 +1,43 @@ +import { TestBed } from '@angular/core/testing'; + +import { BrowseOverlayService } from './browse-overlay.service'; +import { provideMockStore } from '@ngrx/store/testing'; + + +describe('BrowseOverlayService', () => { + let service: BrowseOverlayService; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [provideMockStore({})], + }); + service = TestBed.inject(BrowseOverlayService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should replace NISAR L1 RSLC browse urls with L2 GSLC browse', () => { + const L1ProductUrl = 'https://nisar-test.asf.earthdatacloud.nasa.gov/BROWSE/NISAR_L1_RSLC_BETA_V1/NISAR_L1_PR_RSLC_088_039_D_114_2005_SHSH_A_20251114T222008_20251114T222017_T05000_N_P_J_001/NISAR_L1_PR_RSLC_088_039_D_114_2005_SHSH_A_20251114T222008_20251114T222017_T05000_N_P_J_001.png' + const L2ProductUrl = 'https://nisar-test.asf.earthdatacloud.nasa.gov/BROWSE/NISAR_L2_GSLC_BETA_V1/NISAR_L2_PR_GSLC_088_039_D_114_2005_SHSH_A_20251114T222008_20251114T222017_T05000_N_P_J_001/NISAR_L2_PR_GSLC_088_039_D_114_2005_SHSH_A_20251114T222008_20251114T222017_T05000_N_P_J_001.png' + expect(service.nisarL1ToL2BrowseImage(L1ProductUrl)).toBe(L2ProductUrl) + }); + + it('should replace NISAR L1 RUNW browse urls with L2 GUNW browse', () => { + const L1ProductUrl = 'https://nisar-test.asf.earthdatacloud.nasa.gov/BROWSE/NISAR_L1_RUNW_BETA_V1/NISAR_L1_PR_RUNW_039_002_D_121_040_7700_SH_20240403T084849_20240403T084905_20240415T084849_20240415T084905_T00408_N_P_J_001/NISAR_L1_PR_RUNW_039_002_D_121_040_7700_SH_20240403T084849_20240403T084905_20240415T084849_20240415T084905_T00408_N_P_J_001.png' + const L2ProductUrl = 'https://nisar-test.asf.earthdatacloud.nasa.gov/BROWSE/NISAR_L2_GUNW_BETA_V1/NISAR_L2_PR_GUNW_039_002_D_121_040_7700_SH_20240403T084849_20240403T084905_20240415T084849_20240415T084905_T00408_N_P_J_001/NISAR_L2_PR_GUNW_039_002_D_121_040_7700_SH_20240403T084849_20240403T084905_20240415T084849_20240415T084905_T00408_N_P_J_001.png' + expect(service.nisarL1ToL2BrowseImage(L1ProductUrl)).toBe(L2ProductUrl) + }); + + it('should replace NISAR L1 ROFF browse urls with L2 GOFF browse', () => { + const L1ProductUrl = 'https://nisar-test.asf.earthdatacloud.nasa.gov/BROWSE/NISAR_L1_ROFF_BETA_V1/NISAR_L1_PR_ROFF_039_002_D_122_040_7700_SH_20240403T084903_20240403T084938_20240415T084903_20240415T084938_T00408_N_P_J_001/NISAR_L1_PR_ROFF_039_002_D_122_040_7700_SH_20240403T084903_20240403T084938_20240415T084903_20240415T084938_T00408_N_P_J_001.png' + const L2ProductUrl = 'https://nisar-test.asf.earthdatacloud.nasa.gov/BROWSE/NISAR_L2_GOFF_BETA_V1/NISAR_L2_PR_GOFF_039_002_D_122_040_7700_SH_20240403T084903_20240403T084938_20240415T084903_20240415T084938_T00408_N_P_J_001/NISAR_L2_PR_GOFF_039_002_D_122_040_7700_SH_20240403T084903_20240403T084938_20240415T084903_20240415T084938_T00408_N_P_J_001.png' + expect(service.nisarL1ToL2BrowseImage(L1ProductUrl)).toBe(L2ProductUrl) + }); + + it('should not modify NISAR L1 browse images `/assets/no-browse.png`', () => { + const noBrowse = '/assets/no-browse.png' + expect(service.nisarL1ToL2BrowseImage(noBrowse)).toBe(noBrowse) + }); +}); diff --git a/src/app/services/browse-overlay.service.ts b/src/app/services/browse-overlay.service.ts index ddcc524e2..cd7b02382 100644 --- a/src/app/services/browse-overlay.service.ts +++ b/src/app/services/browse-overlay.service.ts @@ -35,6 +35,7 @@ import { get as getProjection, transform, } from 'ol/proj'; +import { L1L2BrowseCollectionMapping } from '@models/datasets/nisar'; // import { HttpClient } from '@angular/common/http'; // import { CustomProjection } from './map/views'; @@ -115,6 +116,11 @@ export class BrowseOverlayService { const feature = this.wktService.wktToFeature(wkt, 'EPSG:3857'); const polygon = this.getPolygonFromFeature(feature, wkt); + let isNisarL1Browse = url.split('/').pop().startsWith('NISAR_L1') + if (isNisarL1Browse) { + url = this.nisarL1ToL2BrowseImage(url) + } + const source = this.createImageSource(url, polygon.getExtent()); const output = new ImageLayer({ @@ -132,6 +138,21 @@ export class BrowseOverlayService { return output; } + public nisarL1ToL2BrowseImage(url: string) { + if (url === '/assets/no-browse.png') { + return url + } + + const fileName = url.split('/').pop(); + const metadata = fileName.split('_'); + const productType = metadata[3]; + const productionConfiguration = metadata[1] + const shortName = `NISAR_${productionConfiguration}_${productType}`; + + const outputUrl = url.replaceAll(shortName, L1L2BrowseCollectionMapping[productType].collection).replaceAll(productType, L1L2BrowseCollectionMapping[productType].productType).replaceAll('L1', 'L2'); + return outputUrl + } + public createGeotiffLayer( blob: Blob, _wkt: string, diff --git a/src/app/services/map/map.service.ts b/src/app/services/map/map.service.ts index 76259abe5..d9df38c28 100644 --- a/src/app/services/map/map.service.ts +++ b/src/app/services/map/map.service.ts @@ -962,8 +962,9 @@ export class MapService implements OnDestroy { public setSelectedBrowse( url: string, wkt: string, - _scene: models.CMRProduct = null, + scene: models.CMRProduct = null, ) { + this.setLayerText('Approximate Placement Only') if (this.browseImageLayer) { this.map.removeLayer(this.browseImageLayer); } @@ -984,11 +985,12 @@ export class MapService implements OnDestroy { this.map.addLayer(this.browseImageLayer); }); } - // else if(url.toLowerCase().includes('nisar')) { - // this.browseImageLayer = this.browseOverlayService.getKMLLayer(_scene, url, wkt, 'ol-layer', 'current-overlay'); - // this.map.addLayer(this.browseImageLayer); - // } else { + if (scene.dataset === 'NISAR') { + if (scene.id.startsWith('NISAR_L1') && url !== '/assets/no-browse.png') { + this.setLayerText('Level 2 Equivalent Browse Displayed') + } + } this.browseImageLayer = this.browseOverlayService.createNormalImageLayer( url, @@ -1305,6 +1307,12 @@ export class MapService implements OnDestroy { this.map.addControl(this.scaleLine); } + public setLayerText(text: string): void { + let style = this.selectedLayer.getStyle() as Style + let olText = style.getText() + olText.setText(text) + } + private getPointIntersection( aoi: Feature, polygon: Feature, diff --git a/src/app/store/map/map.effect.ts b/src/app/store/map/map.effect.ts index 3998e37bb..10f7f2d77 100644 --- a/src/app/store/map/map.effect.ts +++ b/src/app/store/map/map.effect.ts @@ -219,6 +219,7 @@ export class MapEffects { this.mapService.setSelectedBrowse( url, selectedProduct.metadata.polygon, + selectedProduct ); } }),