Skip to content

Commit

Permalink
Merge pull request #776 from geonetwork/mv-add-from-wfs
Browse files Browse the repository at this point in the history
[Map-Viewer] Add layer from WFS service
  • Loading branch information
ronitjadhav committed Jan 23, 2024
2 parents 725d837 + 78bfee8 commit 05d3ea3
Show file tree
Hide file tree
Showing 16 changed files with 286 additions and 2 deletions.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<div class="flex items-center mb-5">
<gn-ui-text-input
[(value)]="wfsUrl"
(valueChange)="urlChange.next($event)"
[hint]="'map.wfs.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">
<div class="flex items-center justify-between my-2 layer-item-tree">
<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>
</ng-container>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { AddLayerFromWfsComponent } from './add-layer-from-wfs.component'
import { MapFacade } from '../+state/map.facade'
import { TranslateModule } from '@ngx-translate/core'
import { By } from '@angular/platform-browser'

jest.mock('@camptocamp/ogc-client', () => ({
WfsEndpoint: 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)
}
getFeatureTypes() {
return [
{
name: 'ft1',
title: 'Feature Type 1',
},
{
name: 'ft2',
title: 'Feature Type 2',
},
{
name: 'ft3',
title: 'Feature Type 3',
},
]
}
},
}))

class MapFacadeMock {
addLayer = jest.fn()
}

describe('AddLayerFromWfsComponent', () => {
let component: AddLayerFromWfsComponent
let fixture: ComponentFixture<AddLayerFromWfsComponent>
let mapFacade: MapFacade

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()],
declarations: [AddLayerFromWfsComponent],
providers: [
{
provide: MapFacade,
useClass: MapFacadeMock,
},
],
}).compileComponents()

mapFacade = TestBed.inject(MapFacade)
fixture = TestBed.createComponent(AddLayerFromWfsComponent)
component = fixture.componentInstance
})

it('should create', () => {
fixture.detectChanges()
expect(component).toBeTruthy()
expect(component.errorMessage).toBeFalsy()
expect(component.loading).toBe(false)
expect(component.layers.length).toBe(0)
})

describe('loadLayers', () => {
describe('while layers are loading', () => {
beforeEach(() => {
component.wfsUrl = '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 WFS service', () => {
beforeEach(() => {
component.wfsUrl = 'http://my.service.org/wfs'
component.loadLayers()
})
it('shows all layers', () => {
expect(component.errorMessage).toBeFalsy()
expect(component.loading).toBe(false)
expect(component.layers).toEqual([
{
name: 'ft1',
title: 'Feature Type 1',
},
{
name: 'ft2',
title: 'Feature Type 2',
},
{
name: 'ft3',
title: 'Feature Type 3',
},
])
})
it('should show a Add button for each layer', () => {
fixture.detectChanges()
const layerElts = fixture.debugElement.queryAll(
By.css('.layer-item-tree')
)
expect(layerElts.length).toBe(3)
const hasButtons = layerElts.map(
(layerElt) => !!layerElt.query(By.css('.layer-add-btn'))
)
expect(hasButtons).toEqual([true, true, true])
})
})
describe('error loading layers', () => {
beforeEach(() => {
component.wfsUrl = 'http://my.service.org/error'
component.loadLayers()
})
it('shows an error message', () => {
expect(component.errorMessage).toBeTruthy()
expect(component.loading).toBe(false)
expect(component.layers.length).toBe(0)
})
})
describe('error and then valid service', () => {
beforeEach(async () => {
component.wfsUrl = 'http://my.service.org/error'
await component.loadLayers().catch(() => {
// do nothing
})
component.wfsUrl = 'http://my.service.org/wfs'
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.wfsUrl = 'http://my.service.org/wfs'
component.addLayer({
name: 'ft1',
title: 'Feature Type 1',
})
})
it('should add the selected layer in the current map context', () => {
expect(mapFacade.addLayer).toHaveBeenCalledWith({
name: 'ft1',
title: 'Feature Type 1',
url: 'http://my.service.org/wfs',
type: 'wfs',
})
})
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { ChangeDetectorRef, Component, OnInit } from '@angular/core'
import { WfsEndpoint, WfsFeatureTypeBrief } from '@camptocamp/ogc-client'
import { Subject } from 'rxjs'
import {
MapContextLayerModel,
MapContextLayerTypeEnum,
} from '../map-context/map-context.model'
import { MapFacade } from '../+state/map.facade'
import { debounceTime } from 'rxjs/operators'

@Component({
selector: 'gn-ui-add-layer-from-wfs',
templateUrl: './add-layer-from-wfs.component.html',
styleUrls: ['./add-layer-from-wfs.component.css'],
})
export class AddLayerFromWfsComponent implements OnInit {
wfsUrl = ''
loading = false
layers: WfsFeatureTypeBrief[] = []
wfsEndpoint: WfsEndpoint | 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.wfsUrl.trim() === '') {
this.layers = []
return
}

this.wfsEndpoint = await new WfsEndpoint(this.wfsUrl).isReady()
this.layers = this.wfsEndpoint.getFeatureTypes()
console.log(this.layers)
} 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: WfsFeatureTypeBrief) {
const layerToAdd: MapContextLayerModel = {
name: layer.name,
url: this.wfsUrl.toString(),
type: MapContextLayerTypeEnum.WFS,
}
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 @@ -21,6 +21,7 @@ import { AddLayerRecordPreviewComponent } from './add-layer-from-catalog/add-lay
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'
import { AddLayerFromWfsComponent } from './add-layer-from-wfs/add-layer-from-wfs.component'

@NgModule({
declarations: [
Expand All @@ -31,6 +32,7 @@ import { AddLayerFromWmsComponent } from './add-layer-from-wms/add-layer-from-wm
MapContainerComponent,
AddLayerRecordPreviewComponent,
AddLayerFromWmsComponent,
AddLayerFromWfsComponent,
],
exports: [
MapContextComponent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@
</div>
</mat-tab>
<mat-tab [label]="'map.add.layer.wfs' | translate" bodyClass="h-full">
<div class="p-3 h-full">Add from WFS</div>
<div class="p-3">
<gn-ui-add-layer-from-wfs></gn-ui-add-layer-from-wfs>
</div>
</mat-tab>
<mat-tab [label]="'map.add.layer.file' | translate" bodyClass="h-full">
<div class="p-3 h-full">Add from file</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ describe('MapContextService', () => {
const source = layer.getSource()
const urlLoader = source.getUrl()
expect(urlLoader([10, 20, 30, 40])).toBe(
'https://www.geograndest.fr/geoserver/region-grand-est/ows?service=WFS&version=1.1.0&request=GetFeature&outputFormat=application%2Fjson&typename=ms%3Acommune_actuelle_3857&srsname=EPSG%3A3857&bbox=10%2C20%2C30%2C40%2CEPSG%3A3857'
'https://www.geograndest.fr/geoserver/region-grand-est/ows?service=WFS&version=1.1.0&request=GetFeature&outputFormat=application%2Fjson&typename=ms%3Acommune_actuelle_3857&srsname=EPSG%3A3857&bbox=10%2C20%2C30%2C40%2CEPSG%3A3857&maxFeatures=10000'
)
})
})
Expand Down
6 changes: 6 additions & 0 deletions libs/feature/map/src/lib/map-context/map-context.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export const DEFAULT_VIEW: MapContextViewModel = {
zoom: 2,
}

export const WFS_MAX_FEATURES = 10000

@Injectable({
providedIn: 'root',
})
Expand Down Expand Up @@ -111,6 +113,10 @@ export class MapContextService {
urlObj.searchParams.set('typename', layerModel.name)
urlObj.searchParams.set('srsname', 'EPSG:3857')
urlObj.searchParams.set('bbox', `${extent.join(',')},EPSG:3857`)
urlObj.searchParams.set(
'maxFeatures',
WFS_MAX_FEATURES.toString()
)
return urlObj.toString()
},
strategy: bboxStrategy,
Expand Down
1 change: 1 addition & 0 deletions translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@
"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.wfs.urlInput.hint": "",
"map.wms.urlInput.hint": "",
"multiselect.filter.placeholder": "Suche",
"nav.back": "Zurück",
Expand Down
1 change: 1 addition & 0 deletions translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@
"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.wfs.urlInput.hint": "Enter WFS service URL",
"map.wms.urlInput.hint": "Enter WMS service URL",
"multiselect.filter.placeholder": "Search",
"nav.back": "Back",
Expand Down
1 change: 1 addition & 0 deletions translations/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@
"map.loading.service": "",
"map.navigation.message": "",
"map.select.layer": "",
"map.wfs.urlInput.hint": "",
"map.wms.urlInput.hint": "",
"multiselect.filter.placeholder": "",
"nav.back": "",
Expand Down
1 change: 1 addition & 0 deletions translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@
"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.wfs.urlInput.hint": "",
"map.wms.urlInput.hint": "",
"multiselect.filter.placeholder": "Rechercher",
"nav.back": "Retour",
Expand Down
1 change: 1 addition & 0 deletions translations/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@
"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.wfs.urlInput.hint": "",
"map.wms.urlInput.hint": "",
"multiselect.filter.placeholder": "Cerca",
"nav.back": "Indietro",
Expand Down
1 change: 1 addition & 0 deletions translations/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@
"map.loading.service": "",
"map.navigation.message": "",
"map.select.layer": "",
"map.wfs.urlInput.hint": "",
"map.wms.urlInput.hint": "",
"multiselect.filter.placeholder": "",
"nav.back": "",
Expand Down
1 change: 1 addition & 0 deletions translations/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@
"map.loading.service": "",
"map.navigation.message": "",
"map.select.layer": "",
"map.wfs.urlInput.hint": "",
"map.wms.urlInput.hint": "",
"multiselect.filter.placeholder": "",
"nav.back": "",
Expand Down
1 change: 1 addition & 0 deletions translations/sk.json
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@
"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.wfs.urlInput.hint": "",
"map.wms.urlInput.hint": "",
"multiselect.filter.placeholder": "Hľadať",
"nav.back": "Späť",
Expand Down

0 comments on commit 05d3ea3

Please sign in to comment.