-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[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
- Loading branch information
1 parent
8173700
commit f866474
Showing
14 changed files
with
320 additions
and
1 deletion.
There are no files selected for viewing
Empty file.
55 changes: 55 additions & 0 deletions
55
libs/feature/map/src/lib/add-layer-from-wms/add-layer-from-wms.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
165 changes: 165 additions & 0 deletions
165
libs/feature/map/src/lib/add-layer-from-wms/add-layer-from-wms.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', | ||
}) | ||
}) | ||
}) | ||
}) |
63 changes: 63 additions & 0 deletions
63
libs/feature/map/src/lib/add-layer-from-wms/add-layer-from-wms.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.