Skip to content

Commit

Permalink
Merge pull request #795 from geonetwork/mv-geocoding-providers
Browse files Browse the repository at this point in the history
[Map-Viewer] Added Geocoding service for Multiple Providers
  • Loading branch information
ronitjadhav committed Feb 14, 2024
2 parents 3ee8e2c + 1c8006b commit e394770
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 23 deletions.
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

0 comments on commit e394770

Please sign in to comment.