From 23278329602553a85513247b5f988b936b164b9a Mon Sep 17 00:00:00 2001 From: Ronit Jadhav Date: Fri, 2 Feb 2024 17:32:58 +0100 Subject: [PATCH] Added a geocoding text field --- apps/map-viewer/src/app/app.component.html | 1 + jest.preset.js | 4 +- .../feature/map/src/lib/feature-map.module.ts | 3 + .../src/lib/geocoding/geocoding.component.css | 0 .../lib/geocoding/geocoding.component.html | 39 ++++++ .../lib/geocoding/geocoding.component.spec.ts | 115 ++++++++++++++++++ .../src/lib/geocoding/geocoding.component.ts | 83 +++++++++++++ package-lock.json | 6 + package.json | 1 + translations/de.json | 1 + translations/en.json | 1 + translations/es.json | 1 + translations/fr.json | 1 + translations/it.json | 1 + translations/nl.json | 1 + translations/pt.json | 1 + translations/sk.json | 1 + 17 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 libs/feature/map/src/lib/geocoding/geocoding.component.css create mode 100644 libs/feature/map/src/lib/geocoding/geocoding.component.html create mode 100644 libs/feature/map/src/lib/geocoding/geocoding.component.spec.ts create mode 100644 libs/feature/map/src/lib/geocoding/geocoding.component.ts diff --git a/apps/map-viewer/src/app/app.component.html b/apps/map-viewer/src/app/app.component.html index e46ab441a3..08409ca81a 100644 --- a/apps/map-viewer/src/app/app.component.html +++ b/apps/map-viewer/src/app/app.component.html @@ -4,4 +4,5 @@ class="absolute" style="top: 20px; left: 20px; bottom: 20px" > + diff --git a/jest.preset.js b/jest.preset.js index a7b8a3c70c..6fa1ea73a1 100644 --- a/jest.preset.js +++ b/jest.preset.js @@ -4,7 +4,9 @@ module.exports = { ...nxPreset, coverageReporters: ['text'], setupFiles: ['jest-canvas-mock'], - transformIgnorePatterns: ['node_modules/(?!(color-*|ol|@mapbox|.*.mjs$))'], + transformIgnorePatterns: [ + 'node_modules/(?!(color-*|ol|@mapbox|@geospatial-sdk|.*.mjs$))', + ], transform: { '^.+\\.(ts|mjs|js|html)$': [ 'jest-preset-angular', diff --git a/libs/feature/map/src/lib/feature-map.module.ts b/libs/feature/map/src/lib/feature-map.module.ts index c4d5f7c333..aad9e19541 100644 --- a/libs/feature/map/src/lib/feature-map.module.ts +++ b/libs/feature/map/src/lib/feature-map.module.ts @@ -23,6 +23,7 @@ import { UiInputsModule } from '@geonetwork-ui/ui/inputs' import { AddLayerFromWmsComponent } from './add-layer-from-wms/add-layer-from-wms.component' import { AddLayerFromFileComponent } from './add-layer-from-file/add-layer-from-file.component' import { AddLayerFromWfsComponent } from './add-layer-from-wfs/add-layer-from-wfs.component' +import { GeocodingComponent } from './geocoding/geocoding.component' @NgModule({ declarations: [ @@ -35,6 +36,7 @@ import { AddLayerFromWfsComponent } from './add-layer-from-wfs/add-layer-from-wf AddLayerFromWmsComponent, AddLayerFromFileComponent, AddLayerFromWfsComponent, + GeocodingComponent, ], exports: [ MapContextComponent, @@ -42,6 +44,7 @@ import { AddLayerFromWfsComponent } from './add-layer-from-wfs/add-layer-from-wf LayersPanelComponent, AddLayerFromCatalogComponent, MapContainerComponent, + GeocodingComponent, ], imports: [ CommonModule, diff --git a/libs/feature/map/src/lib/geocoding/geocoding.component.css b/libs/feature/map/src/lib/geocoding/geocoding.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libs/feature/map/src/lib/geocoding/geocoding.component.html b/libs/feature/map/src/lib/geocoding/geocoding.component.html new file mode 100644 index 0000000000..4e788277de --- /dev/null +++ b/libs/feature/map/src/lib/geocoding/geocoding.component.html @@ -0,0 +1,39 @@ + + + diff --git a/libs/feature/map/src/lib/geocoding/geocoding.component.spec.ts b/libs/feature/map/src/lib/geocoding/geocoding.component.spec.ts new file mode 100644 index 0000000000..de77aa93e0 --- /dev/null +++ b/libs/feature/map/src/lib/geocoding/geocoding.component.spec.ts @@ -0,0 +1,115 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' +import { GeocodingComponent } from './geocoding.component' +import { MapManagerService } from '../manager/map-manager.service' +import { NO_ERRORS_SCHEMA } from '@angular/core' +import Map from 'ol/Map' +import TileLayer from 'ol/layer/Tile' +import XYZ from 'ol/source/XYZ' +import VectorLayer from 'ol/layer/Vector' +import VectorSource from 'ol/source/Vector' +import GeoJSON from 'ol/format/GeoJSON' +import { FEATURE_COLLECTION_POINT_FIXTURE_4326 } from '@geonetwork-ui/common/fixtures' +import Feature from 'ol/Feature' +import { Geometry } from 'ol/geom' +import { TranslateModule } from '@ngx-translate/core' + +const vectorLayer = new VectorLayer({ + source: new VectorSource({ + features: new GeoJSON().readFeatures( + FEATURE_COLLECTION_POINT_FIXTURE_4326, + { + featureProjection: 'EPSG:3857', + dataProjection: 'EPSG:4326', + } + ), + }) as VectorSource>, +}) + +const mapMock = new Map({ + layers: [ + new TileLayer({ + source: new XYZ({ + url: 'http://test', + }), + }), + vectorLayer, + ], +}) + +const mapManagerMock = { + map: mapMock, +} + +describe('GeocodingComponent', () => { + let component: GeocodingComponent + let fixture: ComponentFixture + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot()], + declarations: [GeocodingComponent], + providers: [{ provide: MapManagerService, useValue: mapManagerMock }], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents() + + fixture = TestBed.createComponent(GeocodingComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + expect(component.searchText).toBe('') + expect(component.results).toEqual([]) + }) + + describe('On Search Change', () => { + describe('when search text is empty', () => { + beforeEach(() => { + component.onSearchChange('') + }) + it('should not show any results', () => { + expect(component.searchText).toEqual('') + expect(component.results).toEqual([]) + }) + }) + describe('when search text is not empty', () => { + beforeEach(() => { + component.searchText = 'test' + }) + it('should show results', () => { + expect(component.searchText).toEqual('test') + expect(component.results).toEqual([]) + }) + }) + }) + + describe('zoomToLocation', () => { + it('should zoom to the location of the result', () => { + const result = { + geom: { + coordinates: [[0, 0]], + }, + } + const viewMock = { + fit: jest.fn(), + } + mapMock.getView = jest.fn().mockReturnValue(viewMock) + component.zoomToLocation(result) + expect(viewMock.fit).toHaveBeenCalled() + }) + }) + describe('onEnterPress', () => { + it('should zoom to the location of the first result', () => { + const result = { + geom: { + coordinates: [[0, 0]], + }, + } + component.results = [result] + const zoomToLocationSpy = jest.spyOn(component, 'zoomToLocation') + component.onEnterPress() + expect(zoomToLocationSpy).toHaveBeenCalledWith(result) + }) + }) +}) diff --git a/libs/feature/map/src/lib/geocoding/geocoding.component.ts b/libs/feature/map/src/lib/geocoding/geocoding.component.ts new file mode 100644 index 0000000000..ec39013453 --- /dev/null +++ b/libs/feature/map/src/lib/geocoding/geocoding.component.ts @@ -0,0 +1,83 @@ +import { Component, OnDestroy } from '@angular/core' +import { queryGeoadmin, GeoadminOptions } from '@geospatial-sdk/geocoding' +import { catchError, from, Subject, takeUntil } from 'rxjs' +import { debounceTime, switchMap } from 'rxjs/operators' +import { MapManagerService } from '../manager/map-manager.service' +import { fromLonLat } from 'ol/proj' +import { Polygon } from 'ol/geom' + +@Component({ + selector: 'gn-ui-geocoding', + templateUrl: './geocoding.component.html', + styleUrls: ['./geocoding.component.css'], +}) +export class GeocodingComponent implements OnDestroy { + searchText = '' + results: any[] = [] + searchTextChanged = new Subject() + destroy$ = new Subject() + + constructor(private mapManager: MapManagerService) { + this.searchTextChanged + .pipe( + debounceTime(300), + switchMap((searchText) => { + const options: GeoadminOptions = { + origins: ['zipcode', 'gg25', 'address'], + limit: 6, + } + return from(queryGeoadmin(searchText, options)).pipe( + catchError((error) => { + console.error(error) + return [] + }) + ) + }), + takeUntil(this.destroy$) + ) + .subscribe((results) => { + this.results = results + }) + } + + ngOnDestroy() { + this.destroy$.next() + this.destroy$.complete() + } + + onSearchChange(searchText: string) { + if (!searchText) { + this.clearSearch() + return + } else { + this.searchTextChanged.next(searchText) + } + } + + clearSearch() { + this.searchText = '' + this.results = [] + } + + zoomToLocation(result) { + const map = this.mapManager.map + const view = map.getView() + const geometry = result.geom + + const polygonCoords = geometry.coordinates + const transformedCoords = polygonCoords[0].map((coord) => fromLonLat(coord)) + + const polygon = new Polygon([transformedCoords]) + + view.fit(polygon, { + duration: 100, + maxZoom: 12, + }) + } + + onEnterPress() { + if (this.results && this.results.length > 0) { + this.zoomToLocation(this.results[0]) + } + } +} diff --git a/package-lock.json b/package-lock.json index 9ad43926af..e22d2fa78c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "@bartholomej/ngx-translate-extract": "^8.0.2", "@biesbjerg/ngx-translate-extract-marker": "^1.0.0", "@camptocamp/ogc-client": "^0.4.0", + "@geospatial-sdk/geocoding": "^0.0.5-alpha.1", "@ltd/j-toml": "~1.35.2", "@messageformat/core": "^3.0.1", "@nestjs/common": "10.1.3", @@ -4310,6 +4311,11 @@ "integrity": "sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw==", "dev": true }, + "node_modules/@geospatial-sdk/geocoding": { + "version": "0.0.5-alpha.1", + "resolved": "https://registry.npmjs.org/@geospatial-sdk/geocoding/-/geocoding-0.0.5-alpha.1.tgz", + "integrity": "sha512-LM1aKG1hl2hnJFLouyjUpCwwT2ToQXeUlExHUvGi/cq1vy2z4AeygLL9etPkEnCPz70B6713gN4mKsmDWdaDiQ==" + }, "node_modules/@humanwhocodes/config-array": { "version": "0.9.5", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", diff --git a/package.json b/package.json index 7bf165852a..b0116a5b0f 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "@bartholomej/ngx-translate-extract": "^8.0.2", "@biesbjerg/ngx-translate-extract-marker": "^1.0.0", "@camptocamp/ogc-client": "^0.4.0", + "@geospatial-sdk/geocoding": "^0.0.5-alpha.1", "@ltd/j-toml": "~1.35.2", "@messageformat/core": "^3.0.1", "@nestjs/common": "10.1.3", diff --git a/translations/de.json b/translations/de.json index 3f3d708988..4326601202 100644 --- a/translations/de.json +++ b/translations/de.json @@ -177,6 +177,7 @@ "map.add.layer.wfs": "Aus WFS", "map.add.layer.wms": "Aus WMS", "map.addFromFile.placeholder": "Klicke hier oder ziehe eine Datei herein", + "map.geocoding.placeholder": "", "map.help.addFromFile": "Klicke oder ziehe eine Datei herein um eine Karte hinzuzufügen (momentan wird nur das GeoJSON-Format unterstützt).", "map.layer.add": "Hinzufügen", "map.layers.available": "Verfügbare Layer", diff --git a/translations/en.json b/translations/en.json index ce1f5bdf86..bf70f15d11 100644 --- a/translations/en.json +++ b/translations/en.json @@ -177,6 +177,7 @@ "map.add.layer.wfs": "From WFS", "map.add.layer.wms": "From WMS", "map.addFromFile.placeholder": "Click or drop a file here", + "map.geocoding.placeholder": "Search for a place", "map.help.addFromFile": "Click or drag and drop a file to add to the map (currently supports GeoJSON format only).", "map.layer.add": "Add", "map.layers.available": "Available Layers", diff --git a/translations/es.json b/translations/es.json index 8923c4b162..973148906c 100644 --- a/translations/es.json +++ b/translations/es.json @@ -177,6 +177,7 @@ "map.add.layer.wfs": "", "map.add.layer.wms": "", "map.addFromFile.placeholder": "", + "map.geocoding.placeholder": "", "map.help.addFromFile": "", "map.layer.add": "", "map.layers.available": "", diff --git a/translations/fr.json b/translations/fr.json index dfbbedfd0a..b50ca355d0 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -177,6 +177,7 @@ "map.add.layer.wfs": "", "map.add.layer.wms": "", "map.addFromFile.placeholder": "", + "map.geocoding.placeholder": "", "map.help.addFromFile": "", "map.layer.add": "", "map.layers.available": "", diff --git a/translations/it.json b/translations/it.json index 7116c0dbce..4939f68bf1 100644 --- a/translations/it.json +++ b/translations/it.json @@ -177,6 +177,7 @@ "map.add.layer.wfs": "Da un WFS", "map.add.layer.wms": "Da un WMS", "map.addFromFile.placeholder": "", + "map.geocoding.placeholder": "", "map.help.addFromFile": "", "map.layer.add": "", "map.layers.available": "", diff --git a/translations/nl.json b/translations/nl.json index 61e72a4db8..8ac625884a 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -177,6 +177,7 @@ "map.add.layer.wfs": "", "map.add.layer.wms": "", "map.addFromFile.placeholder": "", + "map.geocoding.placeholder": "", "map.help.addFromFile": "", "map.layer.add": "", "map.layers.available": "", diff --git a/translations/pt.json b/translations/pt.json index ae7c49754b..7cec1d606a 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -177,6 +177,7 @@ "map.add.layer.wfs": "", "map.add.layer.wms": "", "map.addFromFile.placeholder": "", + "map.geocoding.placeholder": "", "map.help.addFromFile": "", "map.layer.add": "", "map.layers.available": "", diff --git a/translations/sk.json b/translations/sk.json index 875937f552..1ee8dbcd9a 100644 --- a/translations/sk.json +++ b/translations/sk.json @@ -177,6 +177,7 @@ "map.add.layer.wfs": "Z WFS", "map.add.layer.wms": "Z WMS", "map.addFromFile.placeholder": "", + "map.geocoding.placeholder": "", "map.help.addFromFile": "", "map.layer.add": "", "map.layers.available": "",