Skip to content

Commit

Permalink
feat(map): make geocoding standalone & use geospatial-sdk properly
Browse files Browse the repository at this point in the history
Also converted a few more components to standalone on the way
  • Loading branch information
jahow committed Sep 15, 2024
1 parent 4abf21b commit 23fef10
Show file tree
Hide file tree
Showing 11 changed files with 97 additions and 112 deletions.
4 changes: 3 additions & 1 deletion apps/map-viewer/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { BrowserModule } from '@angular/platform-browser'
import { AppComponent } from './app.component'
import {
FeatureMapModule,
MapStateContainerComponent,
GeocodingComponent,
LayersPanelComponent,
MapStateContainerComponent,
} from '@geonetwork-ui/feature/map'
import { ThemeService } from '@geonetwork-ui/util/shared'
import { TranslateModule } from '@ngx-translate/core'
Expand Down Expand Up @@ -41,6 +42,7 @@ export const metaReducers: MetaReducer<any>[] = !environment.production
FeatureCatalogModule,
LayersPanelComponent,
MapStateContainerComponent,
GeocodingComponent,
],
providers: [
importProvidersFrom(FeatureAuthModule),
Expand Down
6 changes: 5 additions & 1 deletion libs/feature/catalog/src/lib/feature-catalog.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { InjectionToken, NgModule } from '@angular/core'
import { SiteTitleComponent } from './site-title/site-title.component'
import { UiCatalogModule } from '@geonetwork-ui/ui/catalog'
import {
OrganisationsFilterComponent,
UiCatalogModule,
} from '@geonetwork-ui/ui/catalog'
import {
GroupsApiService,
SearchApiService,
Expand Down Expand Up @@ -64,6 +67,7 @@ const organizationsServiceFactory = (
UtilI18nModule,
TranslateModule.forChild(),
UiElementsModule,
OrganisationsFilterComponent,
],
exports: [SiteTitleComponent, SourceLabelComponent, OrganisationsComponent],
providers: [
Expand Down
3 changes: 0 additions & 3 deletions libs/feature/map/src/lib/feature-map.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,10 @@ import * as fromMap from './+state/map.reducer'
import { MapFacade } from './+state/map.facade'
import { UiElementsModule } from '@geonetwork-ui/ui/elements'
import { TextInputComponent, UiInputsModule } from '@geonetwork-ui/ui/inputs'
import { GeocodingComponent } from './geocoding/geocoding.component'
import { GEOCODING_PROVIDER, GeocodingProvider } from './geocoding.service'
import { AddLayerFromOgcApiComponent } from './add-layer-from-ogc-api/add-layer-from-ogc-api.component'

@NgModule({
declarations: [GeocodingComponent],
exports: [GeocodingComponent],
imports: [
CommonModule,
UiLayoutModule,
Expand Down
118 changes: 44 additions & 74 deletions libs/feature/map/src/lib/geocoding/geocoding.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,36 @@
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 { pointFeatureCollectionFixture } from '@geonetwork-ui/common/fixtures'
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({
features: new GeoJSON().readFeatures(pointFeatureCollectionFixture(), {
featureProjection: 'EPSG:3857',
dataProjection: 'EPSG:4326',
}),
}) as VectorSource<Feature<Geometry>>,
})

const mapMock = new Map({
layers: [
new TileLayer({
source: new XYZ({
url: 'http://test',
}),
}),
vectorLayer,
],
})

const mapManagerMock = {
map: mapMock,
}

const geocodingServiceMock = {
query: jest.fn().mockReturnValue(of([])),
}
import { MockBuilder, MockProvider } from 'ng-mocks'
import { MapFacade } from '../+state/map.facade'

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

beforeEach(() => {
return MockBuilder(GeocodingComponent)
})

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()],
declarations: [GeocodingComponent],
providers: [
{ provide: MapManagerService, useValue: mapManagerMock },
{ provide: GeocodingService, useValue: geocodingServiceMock },
MockProvider(GeocodingService, {
query: jest.fn().mockReturnValue(of([])),
}),
MockProvider(MapFacade, {
context$: of({
layers: [],
view: null,
}),
applyContext: jest.fn(),
}),
],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents()

mapFacade = TestBed.inject(MapFacade)
fixture = TestBed.createComponent(GeocodingComponent)
component = fixture.componentInstance
fixture.detectChanges()
Expand Down Expand Up @@ -91,36 +64,21 @@ describe('GeocodingComponent', () => {
})

describe('zoomToLocation', () => {
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', () => {
it('should zoom to the location of the result if geometry type is Point', async () => {
const result = {
geom: {
type: 'Point',
coordinates: [0, 0],
},
}
component.zoomToLocation(result)
expect(zoomToPointSpy).toHaveBeenCalledWith(
result.geom.coordinates,
viewMock
)
await component.zoomToLocation(result)
expect(mapFacade.applyContext).toHaveBeenCalledWith({
layers: [],
view: { center: [0, 0], zoom: 10 },
})
})

it('should zoom to the location of the result if geometry type is Polygon', () => {
it('should zoom to the location of the result if geometry type is Polygon', async () => {
const result = {
geom: {
type: 'Polygon',
Expand All @@ -134,22 +92,34 @@ describe('GeocodingComponent', () => {
],
},
}
component.zoomToLocation(result)
expect(zoomToPolygonSpy).toHaveBeenCalledWith(
result.geom.coordinates,
viewMock
)
await component.zoomToLocation(result)
expect(mapFacade.applyContext).toHaveBeenCalledWith({
layers: [],
view: {
geometry: {
coordinates: [
[
[0, 0],
[1, 1],
[2, 2],
[0, 0],
],
],
type: 'Polygon',
},
},
})
})

it('should log an error if geometry type is unsupported', () => {
it('should log an error if geometry type is unsupported', async () => {
const consoleSpy = jest.spyOn(console, 'error').mockImplementation()
const result = {
geom: {
type: 'Unsupported',
coordinates: [0, 0],
},
}
component.zoomToLocation(result)
await component.zoomToLocation(result)
expect(consoleSpy).toHaveBeenCalledWith(
`Unsupported geometry type: ${result.geom.type}`
)
Expand Down
31 changes: 26 additions & 5 deletions libs/feature/map/src/lib/geocoding/geocoding.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,22 @@ import { catchError, firstValueFrom, from, Subject, takeUntil } from 'rxjs'
import { debounceTime, switchMap } from 'rxjs/operators'
import { GeocodingService } from '../geocoding.service'
import { MapFacade } from '../+state/map.facade'
import { CommonModule } from '@angular/common'
import { SearchInputComponent, UiInputsModule } from '@geonetwork-ui/ui/inputs'
import { TranslateModule } from '@ngx-translate/core'
import { MapContextView } from '@geospatial-sdk/core'

@Component({
selector: 'gn-ui-geocoding',
templateUrl: './geocoding.component.html',
styleUrls: ['./geocoding.component.css'],
standalone: true,
imports: [
CommonModule,
UiInputsModule,
TranslateModule,
SearchInputComponent,
],
})
export class GeocodingComponent implements OnDestroy {
searchText = ''
Expand Down Expand Up @@ -60,31 +71,41 @@ export class GeocodingComponent implements OnDestroy {
this.errorMessage = null
}

zoomToLocation(result: any) {
async zoomToLocation(result: any) {
const geometry = result.geom

if (geometry.type === 'Point') {
this.zoomToPoint(geometry.coordinates)
await this.zoomToPoint(geometry.coordinates)
} else if (geometry.type === 'Polygon') {
this.zoomToPolygon(geometry.coordinates)
await this.zoomToPolygon(geometry.coordinates)
} else {
console.error(`Unsupported geometry type: ${geometry.type}`)
}
}

async zoomToPoint(pointCoords: [number, number]) {
const context = await firstValueFrom(this.mapFacade.context$)
const view: MapContextView = {
center: pointCoords,
zoom: 10,
}
this.mapFacade.applyContext({
...context,
// TODO: change context to fit point
view,
})
}

async zoomToPolygon(polygonCoords: [[number, number][]]) {
const context = await firstValueFrom(this.mapFacade.context$)
const view: MapContextView = {
geometry: {
type: 'Polygon',
coordinates: polygonCoords,
},
}
this.mapFacade.applyContext({
...context,
// TODO: change context to fit polygon
view,
})
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,18 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { OrganisationsFilterComponent } from './organisations-filter.component'
import { Component, EventEmitter, Input, Output } from '@angular/core'
import { TranslateModule } from '@ngx-translate/core'

@Component({
selector: 'gn-ui-dropdown-selector',
template: '',
})
class DropdownSelectorMockComponent {
@Input() showTitle: unknown
@Input() choices: {
value: unknown
label: string
}[]
@Input() selected: unknown
@Output() selectValue = new EventEmitter<unknown>()
}
import { MockBuilder } from 'ng-mocks'

describe('OrganisationsOrderComponent', () => {
let component: OrganisationsFilterComponent
let fixture: ComponentFixture<OrganisationsFilterComponent>

beforeEach(() => {
return MockBuilder(OrganisationsFilterComponent)
})

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
OrganisationsFilterComponent,
DropdownSelectorMockComponent,
],
imports: [TranslateModule.forRoot()],
}).compileComponents()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@ import {
} from '@angular/core'
import { marker } from '@biesbjerg/ngx-translate-extract-marker'
import { SortByField } from '@geonetwork-ui/common/domain/model/search'
import { Subject, debounceTime } from 'rxjs'
import { debounceTime, Subject } from 'rxjs'
import {
DropdownSelectorComponent,
SearchInputComponent,
} from '@geonetwork-ui/ui/inputs'
import { TranslateModule } from '@ngx-translate/core'

@Component({
selector: 'gn-ui-organisations-filter',
templateUrl: './organisations-filter.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [SearchInputComponent, DropdownSelectorComponent, TranslateModule],
})
export class OrganisationsFilterComponent {
choices: { value: string; label: string }[] = [
Expand Down
3 changes: 0 additions & 3 deletions libs/ui/catalog/src/lib/ui-catalog.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { OrganisationPreviewComponent } from './organisation-preview/organisatio
import { TranslateModule } from '@ngx-translate/core'
import { MatIconModule } from '@angular/material/icon'
import { UiElementsModule } from '@geonetwork-ui/ui/elements'
import { OrganisationsFilterComponent } from './organisations-filter/organisations-filter.component'
import { UiInputsModule } from '@geonetwork-ui/ui/inputs'
import { LanguageSwitcherComponent } from './language-switcher/language-switcher.component'
import { OrganisationsResultComponent } from './organisations-result/organisations-result.component'
Expand All @@ -15,7 +14,6 @@ import { RouterLink } from '@angular/router'
declarations: [
CatalogTitleComponent,
OrganisationPreviewComponent,
OrganisationsFilterComponent,
LanguageSwitcherComponent,
OrganisationsResultComponent,
],
Expand All @@ -30,7 +28,6 @@ import { RouterLink } from '@angular/router'
exports: [
CatalogTitleComponent,
OrganisationPreviewComponent,
OrganisationsFilterComponent,
LanguageSwitcherComponent,
OrganisationsResultComponent,
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ describe('SearchInputComponent', () => {

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [SearchInputComponent],
imports: [SearchInputComponent],
})
fixture = TestBed.createComponent(SearchInputComponent)
component = fixture.componentInstance
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ import {
Input,
Output,
} from '@angular/core'
import { Subject, distinctUntilChanged } from 'rxjs'
import { distinctUntilChanged, Subject } from 'rxjs'
import { MatIconModule } from '@angular/material/icon'
import { CommonModule } from '@angular/common'

@Component({
selector: 'gn-ui-search-input',
templateUrl: './search-input.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [CommonModule, MatIconModule],
})
export class SearchInputComponent {
@Input() value = ''
Expand Down
Loading

0 comments on commit 23fef10

Please sign in to comment.