From f866474c417d57d7ddcc6c978b13ef1c015b1cd8 Mon Sep 17 00:00:00 2001 From: Ronit Jadhav Date: Wed, 17 Jan 2024 17:13:29 +0100 Subject: [PATCH] [Map-Viewer] Add layer from WMS service (#768) * Added component to load layers from WMS URL * Updated the logic to get WMS layers and its children * resolved the format issues * resolved test errors * resolved format issue * Implementing Tailwind CSS and translation * Worked on the unit tests * fixed formating issue --- .../add-layer-from-wms.component.css | 0 .../add-layer-from-wms.component.html | 55 ++++++ .../add-layer-from-wms.component.spec.ts | 165 ++++++++++++++++++ .../add-layer-from-wms.component.ts | 63 +++++++ .../feature/map/src/lib/feature-map.module.ts | 2 + .../layers-panel/layers-panel.component.html | 4 +- translations/de.json | 4 + translations/en.json | 4 + translations/es.json | 4 + translations/fr.json | 4 + translations/it.json | 4 + translations/nl.json | 4 + translations/pt.json | 4 + translations/sk.json | 4 + 14 files changed, 320 insertions(+), 1 deletion(-) create mode 100644 libs/feature/map/src/lib/add-layer-from-wms/add-layer-from-wms.component.css create mode 100644 libs/feature/map/src/lib/add-layer-from-wms/add-layer-from-wms.component.html create mode 100644 libs/feature/map/src/lib/add-layer-from-wms/add-layer-from-wms.component.spec.ts create mode 100644 libs/feature/map/src/lib/add-layer-from-wms/add-layer-from-wms.component.ts diff --git a/libs/feature/map/src/lib/add-layer-from-wms/add-layer-from-wms.component.css b/libs/feature/map/src/lib/add-layer-from-wms/add-layer-from-wms.component.css new file mode 100644 index 000000000..e69de29bb diff --git a/libs/feature/map/src/lib/add-layer-from-wms/add-layer-from-wms.component.html b/libs/feature/map/src/lib/add-layer-from-wms/add-layer-from-wms.component.html new file mode 100644 index 000000000..daaff461d --- /dev/null +++ b/libs/feature/map/src/lib/add-layer-from-wms/add-layer-from-wms.component.html @@ -0,0 +1,55 @@ +
+ + +
+ +
+ {{ errorMessage }} +
+ +
+

map.loading.service

+
+ +
+

map.layers.available

+ +
+ + +
+

+ {{ layer.title }} +

+ map.layer.add +
+
+ + +
+
diff --git a/libs/feature/map/src/lib/add-layer-from-wms/add-layer-from-wms.component.spec.ts b/libs/feature/map/src/lib/add-layer-from-wms/add-layer-from-wms.component.spec.ts new file mode 100644 index 000000000..6fcb950c7 --- /dev/null +++ b/libs/feature/map/src/lib/add-layer-from-wms/add-layer-from-wms.component.spec.ts @@ -0,0 +1,165 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' +import { AddLayerFromWmsComponent } from './add-layer-from-wms.component' +import { MapFacade } from '../+state/map.facade' +import { NO_ERRORS_SCHEMA } from '@angular/core' +import { TranslateModule } from '@ngx-translate/core' +import { By } from '@angular/platform-browser' + +jest.mock('@camptocamp/ogc-client', () => ({ + WmsEndpoint: class { + constructor(private url) {} + isReady() { + if (this.url.indexOf('error') > -1) { + return Promise.reject(new Error('Something went wrong')) + } + if (this.url.indexOf('wait') > -1) { + return new Promise(() => { + // do nothing + }) + } + return Promise.resolve(this) + } + getLayers() { + return [ + { + name: 'layer1', + title: 'Layer 1', + children: [ + { + title: 'Layer 2', + }, + { + name: 'layer3', + title: 'Layer 3', + }, + ], + }, + ] + } + }, +})) + +class MapFacadeMock { + addLayer = jest.fn() +} + +describe('AddLayerFromWmsComponent', () => { + let component: AddLayerFromWmsComponent + let fixture: ComponentFixture + let mapFacade: MapFacade + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot()], + declarations: [AddLayerFromWmsComponent], + providers: [ + { + provide: MapFacade, + useClass: MapFacadeMock, + }, + ], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents() + + mapFacade = TestBed.inject(MapFacade) + fixture = TestBed.createComponent(AddLayerFromWmsComponent) + component = fixture.componentInstance + }) + + it('should create', () => { + fixture.detectChanges() + expect(component).toBeTruthy() + expect(component.errorMessage).toBeFalsy() + expect(component.loading).toBe(false) + expect(component.layers).toEqual([]) + }) + + describe('loadLayers', () => { + describe('while layers are loading', () => { + beforeEach(() => { + component.wmsUrl = 'http://my.service.org/wait' + component.loadLayers() + }) + it('shows only a "loading" message', () => { + expect(component.errorMessage).toBeFalsy() + expect(component.loading).toBe(true) + expect(component.layers).toEqual([]) + }) + }) + describe('valid WMS service', () => { + beforeEach(() => { + component.wmsUrl = 'http://my.service.org/wms' + component.loadLayers() + }) + it('shows a list of layers', () => { + expect(component.errorMessage).toBeFalsy() + expect(component.loading).toBe(false) + expect(component.layers).toEqual([ + { + name: 'layer1', + title: 'Layer 1', + children: expect.any(Array), + }, + ]) + }) + it('should show an Add layer button for each layer with a name', () => { + fixture.detectChanges() + const layerElts = fixture.debugElement.queryAll( + By.css('.layer-tree-item') + ) + expect(layerElts.length).toBe(3) + const hasButtons = layerElts.map( + (layerElt) => !!layerElt.query(By.css('.layer-add-btn')) + ) + expect(hasButtons).toEqual([true, false, true]) + }) + }) + describe('an error is received', () => { + beforeEach(() => { + component.wmsUrl = 'http://my.service.org/error' + component.loadLayers().catch(() => { + // do nothing + }) + }) + it('shows the error', () => { + expect(component.errorMessage).toContain('Something went wrong') + expect(component.loading).toBe(false) + expect(component.layers).toEqual([]) + }) + }) + describe('error and then valid service', () => { + beforeEach(async () => { + component.wmsUrl = 'http://my.service.org/error' + await component.loadLayers().catch(() => { + // do nothing + }) + component.wmsUrl = 'http://my.service.org/wms' + await component.loadLayers() + }) + it('shows no error', () => { + expect(component.errorMessage).toBeFalsy() + expect(component.loading).toBe(false) + expect(component.layers).not.toEqual([]) + }) + }) + }) + + describe('addLayer', () => { + beforeEach(() => { + component.wmsUrl = 'http://my.service.org/wms' + component.addLayer({ + name: 'myLayer', + title: 'My Layer', + abstract: 'This is my layer', + }) + }) + it('adds the selected layer in the current map context', () => { + expect(mapFacade.addLayer).toHaveBeenCalledWith({ + name: 'myLayer', + title: 'My Layer', + type: 'wms', + url: 'http://my.service.org/wms', + }) + }) + }) +}) diff --git a/libs/feature/map/src/lib/add-layer-from-wms/add-layer-from-wms.component.ts b/libs/feature/map/src/lib/add-layer-from-wms/add-layer-from-wms.component.ts new file mode 100644 index 000000000..720eae2dd --- /dev/null +++ b/libs/feature/map/src/lib/add-layer-from-wms/add-layer-from-wms.component.ts @@ -0,0 +1,63 @@ +import { Component, ChangeDetectorRef, OnInit } from '@angular/core' +import { WmsEndpoint, WmsLayerSummary } from '@camptocamp/ogc-client' +import { MapFacade } from '../+state/map.facade' +import { + MapContextLayerModel, + MapContextLayerTypeEnum, +} from '../map-context/map-context.model' +import { Subject } from 'rxjs' +import { debounceTime } from 'rxjs/operators' + +@Component({ + selector: 'gn-ui-add-layer-from-wms', + templateUrl: './add-layer-from-wms.component.html', + styleUrls: ['./add-layer-from-wms.component.css'], +}) +export class AddLayerFromWmsComponent implements OnInit { + wmsUrl = '' + loading = false + layers: WmsLayerSummary[] = [] + wmsEndpoint: WmsEndpoint | null = null + urlChange = new Subject() + errorMessage: string | null = null + + constructor( + private mapFacade: MapFacade, + private changeDetectorRef: ChangeDetectorRef + ) {} + + ngOnInit() { + this.urlChange.pipe(debounceTime(700)).subscribe(() => this.loadLayers()) + } + + async loadLayers() { + this.errorMessage = null + try { + this.loading = true + + if (this.wmsUrl.trim() === '') { + this.layers = [] + return + } + + this.wmsEndpoint = await new WmsEndpoint(this.wmsUrl).isReady() + this.layers = this.wmsEndpoint.getLayers() + } catch (error) { + const err = error as Error + this.layers = [] + this.errorMessage = 'Error loading layers: ' + err.message + } finally { + this.loading = false + this.changeDetectorRef.markForCheck() + } + } + + addLayer(layer: WmsLayerSummary) { + const layerToAdd: MapContextLayerModel = { + name: layer.name, + url: this.wmsUrl.toString(), + type: MapContextLayerTypeEnum.WMS, + } + this.mapFacade.addLayer({ ...layerToAdd, title: layer.title }) + } +} diff --git a/libs/feature/map/src/lib/feature-map.module.ts b/libs/feature/map/src/lib/feature-map.module.ts index c77251b80..facd91d16 100644 --- a/libs/feature/map/src/lib/feature-map.module.ts +++ b/libs/feature/map/src/lib/feature-map.module.ts @@ -20,6 +20,7 @@ import { MapContainerComponent } from './map-container/map-container.component' import { AddLayerRecordPreviewComponent } from './add-layer-from-catalog/add-layer-record-preview/add-layer-record-preview.component' import { UiElementsModule } from '@geonetwork-ui/ui/elements' import { UiInputsModule } from '@geonetwork-ui/ui/inputs' +import { AddLayerFromWmsComponent } from './add-layer-from-wms/add-layer-from-wms.component' @NgModule({ declarations: [ @@ -29,6 +30,7 @@ import { UiInputsModule } from '@geonetwork-ui/ui/inputs' AddLayerFromCatalogComponent, MapContainerComponent, AddLayerRecordPreviewComponent, + AddLayerFromWmsComponent, ], exports: [ MapContextComponent, diff --git a/libs/feature/map/src/lib/layers-panel/layers-panel.component.html b/libs/feature/map/src/lib/layers-panel/layers-panel.component.html index d2141e1b3..61454e4e2 100644 --- a/libs/feature/map/src/lib/layers-panel/layers-panel.component.html +++ b/libs/feature/map/src/lib/layers-panel/layers-panel.component.html @@ -31,7 +31,9 @@ -
Add from WMS
+
+ +
Add from WFS
diff --git a/translations/de.json b/translations/de.json index 20e6e1a39..00048b25b 100644 --- a/translations/de.json +++ b/translations/de.json @@ -175,10 +175,14 @@ "map.add.layer.file": "Aus einer Datei", "map.add.layer.wfs": "Aus WFS", "map.add.layer.wms": "Aus WMS", + "map.layer.add": "", + "map.layers.available": "", "map.layers.list": "Ebenen", "map.loading.data": "Kartendaten werden geladen...", + "map.loading.service": "", "map.navigation.message": "Bitte verwenden Sie STRG + Maus (oder zwei Finger auf einem Mobilgerät), um die Karte zu navigieren", "map.select.layer": "Datenquelle", + "map.wms.urlInput.hint": "", "multiselect.filter.placeholder": "Suche", "nav.back": "Zurück", "next": "weiter", diff --git a/translations/en.json b/translations/en.json index de0d52101..3fb97bc19 100644 --- a/translations/en.json +++ b/translations/en.json @@ -175,10 +175,14 @@ "map.add.layer.file": "From a file", "map.add.layer.wfs": "From WFS", "map.add.layer.wms": "From WMS", + "map.layer.add": "Add", + "map.layers.available": "Available Layers", "map.layers.list": "Layers", "map.loading.data": "Loading map data...", + "map.loading.service": "Loading service...", "map.navigation.message": "Please use CTRL + mouse (or two fingers on mobile) to navigate the map", "map.select.layer": "Data source", + "map.wms.urlInput.hint": "Enter WMS service URL", "multiselect.filter.placeholder": "Search", "nav.back": "Back", "next": "next", diff --git a/translations/es.json b/translations/es.json index f645ff47e..dea04b3d1 100644 --- a/translations/es.json +++ b/translations/es.json @@ -175,10 +175,14 @@ "map.add.layer.file": "", "map.add.layer.wfs": "", "map.add.layer.wms": "", + "map.layer.add": "", + "map.layers.available": "", "map.layers.list": "", "map.loading.data": "", + "map.loading.service": "", "map.navigation.message": "", "map.select.layer": "", + "map.wms.urlInput.hint": "", "multiselect.filter.placeholder": "", "nav.back": "", "next": "", diff --git a/translations/fr.json b/translations/fr.json index b4dd3237c..edea41d94 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -175,10 +175,14 @@ "map.add.layer.file": "", "map.add.layer.wfs": "", "map.add.layer.wms": "", + "map.layer.add": "", + "map.layers.available": "", "map.layers.list": "", "map.loading.data": "Chargement des données...", + "map.loading.service": "", "map.navigation.message": "Veuillez utiliser CTRL + souris (ou deux doigts sur mobile) pour naviguer sur la carte", "map.select.layer": "Source de données", + "map.wms.urlInput.hint": "", "multiselect.filter.placeholder": "Rechercher", "nav.back": "Retour", "next": "suivant", diff --git a/translations/it.json b/translations/it.json index 95d9a9a7b..d4bf47de5 100644 --- a/translations/it.json +++ b/translations/it.json @@ -175,10 +175,14 @@ "map.add.layer.file": "Da un file", "map.add.layer.wfs": "Da un WFS", "map.add.layer.wms": "Da un WMS", + "map.layer.add": "", + "map.layers.available": "", "map.layers.list": "Layers", "map.loading.data": "Caricamento dati...", + "map.loading.service": "", "map.navigation.message": "Si prega di utilizzare CTRL + mouse (o due dita su mobile) per navigare sulla mappa", "map.select.layer": "Sorgente dati", + "map.wms.urlInput.hint": "", "multiselect.filter.placeholder": "Cerca", "nav.back": "Indietro", "next": "successivo", diff --git a/translations/nl.json b/translations/nl.json index a7a0178cd..44445b67b 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -175,10 +175,14 @@ "map.add.layer.file": "", "map.add.layer.wfs": "", "map.add.layer.wms": "", + "map.layer.add": "", + "map.layers.available": "", "map.layers.list": "", "map.loading.data": "", + "map.loading.service": "", "map.navigation.message": "", "map.select.layer": "", + "map.wms.urlInput.hint": "", "multiselect.filter.placeholder": "", "nav.back": "", "next": "", diff --git a/translations/pt.json b/translations/pt.json index 63b3833b7..3f1d70375 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -175,10 +175,14 @@ "map.add.layer.file": "", "map.add.layer.wfs": "", "map.add.layer.wms": "", + "map.layer.add": "", + "map.layers.available": "", "map.layers.list": "", "map.loading.data": "", + "map.loading.service": "", "map.navigation.message": "", "map.select.layer": "", + "map.wms.urlInput.hint": "", "multiselect.filter.placeholder": "", "nav.back": "", "next": "", diff --git a/translations/sk.json b/translations/sk.json index 40e0c45af..bc379dd3c 100644 --- a/translations/sk.json +++ b/translations/sk.json @@ -175,10 +175,14 @@ "map.add.layer.file": "Zo súboru", "map.add.layer.wfs": "Z WFS", "map.add.layer.wms": "Z WMS", + "map.layer.add": "", + "map.layers.available": "", "map.layers.list": "Vrstvy", "map.loading.data": "Načítavanie dát mapy...", + "map.loading.service": "", "map.navigation.message": "Použite prosím CTRL + myš (alebo dva prsty na mobilnom zariadení) na navigáciu po mape", "map.select.layer": "Zdroj dát", + "map.wms.urlInput.hint": "", "multiselect.filter.placeholder": "Hľadať", "nav.back": "Späť", "next": "Ďalej",