Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Map-Viewer] Added Geocoding service for Multiple Providers #795

Merged
merged 2 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions libs/feature/map/src/lib/feature-map.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { AddLayerFromWmsComponent } from './add-layer-from-wms/add-layer-from-wm
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'
import { GEOCODING_PROVIDER, GeocodingProvider } from './geocoding.service'

@NgModule({
declarations: [
Expand Down Expand Up @@ -65,6 +66,10 @@ import { GeocodingComponent } from './geocoding/geocoding.component'
useValue: defaultMapOptions,
},
MapFacade,
{
provide: GEOCODING_PROVIDER,
useValue: ['geonames', { maxRows: 5 }] as GeocodingProvider,
},
],
})
export class FeatureMapModule {}
59 changes: 59 additions & 0 deletions libs/feature/map/src/lib/geocoding.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Injectable, Inject, InjectionToken } from '@angular/core'
import {
queryGeoadmin,
GeoadminOptions,
GeocodingResult,
queryGeonames,
GeonamesOptions,
DataGouvFrOptions,
queryDataGouvFr,
} from '@geospatial-sdk/geocoding'
import { from, Observable, throwError } from 'rxjs'
import { catchError } from 'rxjs/operators'

type GeoadminGeocodingProvider = ['geoadmin', GeoadminOptions]
type GeonamesGeocodingProvider = ['geonames', GeonamesOptions]
type DataGouvFrGeocodingProvider = ['data-gouv-fr', DataGouvFrOptions]
export type GeocodingProvider =
| GeoadminGeocodingProvider
| GeonamesGeocodingProvider
| DataGouvFrGeocodingProvider

export const GEOCODING_PROVIDER = new InjectionToken<GeocodingProvider>(
'geocoding-provider'
)

@Injectable({
providedIn: 'root',
})
export class GeocodingService {
constructor(
@Inject(GEOCODING_PROVIDER) private provider: GeocodingProvider
) {}

query(text: string): Observable<GeocodingResult[]> {
let queryObservable: Observable<GeocodingResult[]>
switch (this.provider[0]) {
case 'geoadmin':
queryObservable = from(
queryGeoadmin(text, this.provider[1] as GeoadminOptions)
)
break
case 'geonames':
queryObservable = from(
queryGeonames(text, this.provider[1] as GeonamesOptions)
)
break
case 'data-gouv-fr':
queryObservable = from(
queryDataGouvFr(text, this.provider[1] as DataGouvFrOptions)
)
break
default:
return throwError(
() => new Error(`Unsupported geocoding provider: ${this.provider[0]}`)
)
}
return queryObservable.pipe(catchError((error) => throwError(error)))
}
}
73 changes: 66 additions & 7 deletions libs/feature/map/src/lib/geocoding/geocoding.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { FEATURE_COLLECTION_POINT_FIXTURE_4326 } from '@geonetwork-ui/common/fix
import Feature from 'ol/Feature'
import { Geometry } from 'ol/geom'
import { TranslateModule } from '@ngx-translate/core'
import { GeocodingService } from '../geocoding.service'
import { of } from 'rxjs'

const vectorLayer = new VectorLayer({
source: new VectorSource({
Expand Down Expand Up @@ -40,6 +42,10 @@ const mapManagerMock = {
map: mapMock,
}

const geocodingServiceMock = {
query: jest.fn().mockReturnValue(of([])),
}

describe('GeocodingComponent', () => {
let component: GeocodingComponent
let fixture: ComponentFixture<GeocodingComponent>
Expand All @@ -48,7 +54,10 @@ describe('GeocodingComponent', () => {
await TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()],
declarations: [GeocodingComponent],
providers: [{ provide: MapManagerService, useValue: mapManagerMock }],
providers: [
{ provide: MapManagerService, useValue: mapManagerMock },
{ provide: GeocodingService, useValue: geocodingServiceMock },
],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents()

Expand Down Expand Up @@ -85,18 +94,68 @@ describe('GeocodingComponent', () => {
})

describe('zoomToLocation', () => {
it('should zoom to the location of the result', () => {
let viewMock: any
let zoomToPointSpy: jest.SpyInstance
let zoomToPolygonSpy: jest.SpyInstance

beforeEach(() => {
viewMock = {
setCenter: jest.fn(),
setZoom: jest.fn(),
fit: jest.fn(),
}
mapMock.getView = jest.fn().mockReturnValue(viewMock)
zoomToPointSpy = jest.spyOn(component, 'zoomToPoint')
zoomToPolygonSpy = jest.spyOn(component, 'zoomToPolygon')
})

it('should zoom to the location of the result if geometry type is Point', () => {
const result = {
geom: {
coordinates: [[0, 0]],
type: 'Point',
coordinates: [0, 0],
},
}
const viewMock = {
fit: jest.fn(),
component.zoomToLocation(result)
expect(zoomToPointSpy).toHaveBeenCalledWith(
result.geom.coordinates,
viewMock
)
})

it('should zoom to the location of the result if geometry type is Polygon', () => {
const result = {
geom: {
type: 'Polygon',
coordinates: [
[
[0, 0],
[1, 1],
[2, 2],
[0, 0],
],
],
},
}
component.zoomToLocation(result)
expect(zoomToPolygonSpy).toHaveBeenCalledWith(
result.geom.coordinates,
viewMock
)
})

it('should log an error if geometry type is unsupported', () => {
const consoleSpy = jest.spyOn(console, 'error').mockImplementation()
const result = {
geom: {
type: 'Unsupported',
coordinates: [0, 0],
},
}
mapMock.getView = jest.fn().mockReturnValue(viewMock)
component.zoomToLocation(result)
expect(viewMock.fit).toHaveBeenCalled()
expect(consoleSpy).toHaveBeenCalledWith(
`Unsupported geometry type: ${result.geom.type}`
)
})
})
describe('onEnterPress', () => {
Expand Down
38 changes: 27 additions & 11 deletions libs/feature/map/src/lib/geocoding/geocoding.component.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
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'
import { GeocodingService } from '../geocoding.service'

@Component({
selector: 'gn-ui-geocoding',
Expand All @@ -16,18 +16,20 @@ export class GeocodingComponent implements OnDestroy {
results: any[] = []
searchTextChanged = new Subject<string>()
destroy$ = new Subject<void>()
errorMessage: string | null = null

constructor(private mapManager: MapManagerService) {
constructor(
private mapManager: MapManagerService,
private geocodingService: GeocodingService
) {
this.searchTextChanged
.pipe(
debounceTime(300),
switchMap((searchText) => {
const options: GeoadminOptions = {
origins: ['zipcode', 'gg25', 'address'],
limit: 6,
}
return from(queryGeoadmin(searchText, options)).pipe(
return from(this.geocodingService.query(searchText)).pipe(
catchError((error) => {
this.errorMessage =
'An error occurred while searching. Please try again.'
console.error(error)
return []
})
Expand Down Expand Up @@ -57,18 +59,32 @@ export class GeocodingComponent implements OnDestroy {
clearSearch() {
this.searchText = ''
this.results = []
this.errorMessage = null
}

zoomToLocation(result) {
zoomToLocation(result: any) {
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))
if (geometry.type === 'Point') {
this.zoomToPoint(geometry.coordinates, view)
} else if (geometry.type === 'Polygon') {
this.zoomToPolygon(geometry.coordinates, view)
} else {
console.error(`Unsupported geometry type: ${geometry.type}`)
}
}

const polygon = new Polygon([transformedCoords])
zoomToPoint(pointCoords: [number, number], view: any) {
const transformedCoords = fromLonLat(pointCoords)
view.setCenter(transformedCoords)
view.setZoom(12)
}

zoomToPolygon(polygonCoords: [[number, number][]], view: any) {
const transformedCoords = polygonCoords[0].map((coord) => fromLonLat(coord))
const polygon = new Polygon([transformedCoords])
view.fit(polygon, {
duration: 100,
maxZoom: 12,
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +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",
"@geospatial-sdk/geocoding": "^0.0.5-alpha.2",
"@ltd/j-toml": "~1.35.2",
"@messageformat/core": "^3.0.1",
"@nestjs/common": "10.1.3",
Expand Down
Loading