Skip to content

Commit

Permalink
[Map-Viewer] Add layer from WMS service (#768)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
ronitjadhav committed Jan 17, 2024
1 parent 8173700 commit f866474
Show file tree
Hide file tree
Showing 14 changed files with 320 additions and 1 deletion.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<div class="flex items-center mb-5">
<gn-ui-text-input
[(value)]="wmsUrl"
(valueChange)="urlChange.next($event)"
[hint]="'map.wms.urlInput.hint' | translate"
class="w-96"
>
</gn-ui-text-input>
</div>

<div *ngIf="errorMessage" class="text-red-500 mt-2">
{{ errorMessage }}
</div>

<div *ngIf="loading">
<p class="loading-message" translate>map.loading.service</p>
</div>

<div *ngIf="!loading && layers.length > 0">
<h2 class="font-bold" translate>map.layers.available</h2>
<ng-container
*ngFor="let layer of layers"
[ngTemplateOutlet]="layerTreeItem"
[ngTemplateOutletContext]="{
layer: layer
}"
></ng-container>
</div>

<ng-template #layerTreeItem let-layer="layer">
<div class="flex items-center justify-between layer-tree-item my-2">
<p class="max-w-xs overflow-hidden overflow-ellipsis whitespace-nowrap">
{{ layer.title }}
</p>
<gn-ui-button
*ngIf="layer.name"
class="layer-add-btn"
type="primary"
(buttonClick)="addLayer(layer)"
extraClass="text-sm !px-2 !py-1"
translate
><span translate> map.layer.add </span></gn-ui-button
>
</div>
<div *ngIf="layer.children?.length > 0" class="ml-4">
<ng-container
*ngFor="let child of layer.children"
[ngTemplateOutlet]="layerTreeItem"
[ngTemplateOutletContext]="{
layer: child
}"
>
</ng-container>
</div>
</ng-template>
Original file line number Diff line number Diff line change
@@ -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<AddLayerFromWmsComponent>
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',
})
})
})
})
Original file line number Diff line number Diff line change
@@ -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<string>()
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 })
}
}
2 changes: 2 additions & 0 deletions libs/feature/map/src/lib/feature-map.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand All @@ -29,6 +30,7 @@ import { UiInputsModule } from '@geonetwork-ui/ui/inputs'
AddLayerFromCatalogComponent,
MapContainerComponent,
AddLayerRecordPreviewComponent,
AddLayerFromWmsComponent,
],
exports: [
MapContextComponent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
</div>
</mat-tab>
<mat-tab [label]="'map.add.layer.wms' | translate" bodyClass="h-full">
<div class="p-3 h-full">Add from WMS</div>
<div class="p-3">
<gn-ui-add-layer-from-wms></gn-ui-add-layer-from-wms>
</div>
</mat-tab>
<mat-tab [label]="'map.add.layer.wfs' | translate" bodyClass="h-full">
<div class="p-3 h-full">Add from WFS</div>
Expand Down
4 changes: 4 additions & 0 deletions translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions translations/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "",
Expand Down
4 changes: 4 additions & 0 deletions translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions translations/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading

0 comments on commit f866474

Please sign in to comment.