Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(me): migrate spatial-extents map to use geo-sdk
Browse files Browse the repository at this point in the history
jahow committed Sep 15, 2024
1 parent 095f39e commit 2f3fe49
Showing 3 changed files with 147 additions and 208 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
<gn-ui-map-context
[context]="mapContext$ | async"
[mapConfig]="mapConfig"
></gn-ui-map-context>
<gn-ui-map-container [context]="mapContext$ | async"></gn-ui-map-container>
Original file line number Diff line number Diff line change
@@ -1,118 +1,33 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'

import { FormFieldMapContainerComponent } from './form-field-map-container.component'
import {
defaultMapStyleFixture,
defaultMapStyleHlFixture,
MapContextModel,
MapFacade,
MapStyleService,
MapUtilsService,
} from '@geonetwork-ui/feature/map'
import { of } from 'rxjs'
import { Style } from 'ol/style'
import { StoreModule } from '@ngrx/store'
import { EffectsModule } from '@ngrx/effects'
import { Gn4Converter } from '@geonetwork-ui/api/metadata-converter'
import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform.service.interface'
import { TranslateModule } from '@ngx-translate/core'
import { Component, Input } from '@angular/core'
import { MapConfig } from '@geonetwork-ui/util/app-config'
import { CommonModule } from '@angular/common'
import Map from 'ol/Map'
import { MockBuilder } from 'ng-mocks'
import { firstValueFrom } from 'rxjs'
import { createViewFromLayer } from '@geospatial-sdk/core'

class ResizeObserverMock {
observe = jest.fn()
unobserve = jest.fn()
disconnect = jest.fn()
}
;(window as any).ResizeObserver = ResizeObserverMock
class Gn4MetadataMapperMock {
readRecords = jest.fn((records) =>
Promise.all(records.map((r) => this.readRecord(r)))
)
readRecord = jest.fn((record) => Promise.resolve(record))
}
class mapStyleServiceMock {
createDefaultStyle = jest.fn(() => new Style())
styles = {
default: defaultMapStyleFixture(),
defaultHL: defaultMapStyleHlFixture(),
}
createGeometryStyles = jest.fn(() => new Style())
createStyleFunction = jest.fn()
}
class MapFacadeMock {
addLayer = jest.fn()
removeLayer = jest.fn()
jest.mock('@geospatial-sdk/core', () => ({
createViewFromLayer: jest.fn(() => {
return Promise.resolve({
zoom: 3,
center: [3, 4],
})
}),
}))

layers$ = of([])
}
class MapUtilsServiceMock {
getLayerExtent = jest.fn(() => null)
createEmptyMap = jest.fn(() => new Map())
}
class PlatformServiceInterfaceMock {
searchKeywords = jest.fn(() =>
of([{ label: 'Africa', thesaurus: { id: '1' } }])
)
getMe = jest.fn(() => of({}))
}
@Component({
selector: 'gn-ui-map-context',
template: '<div></div>',
})
export class MockMapContextComponent {
@Input() context: MapContextModel
@Input() mapConfig: MapConfig
}
@Component({
selector: 'gn-ui-map',
template: '<div></div>',
})
export class MockMapComponent {
@Input() map: Map
}
describe('FormFieldMapContainerComponent', () => {
let component: FormFieldMapContainerComponent
let fixture: ComponentFixture<FormFieldMapContainerComponent>
let mapFacade: MapFacade

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [MockMapContextComponent, MockMapComponent],
imports: [
FormFieldMapContainerComponent,
EffectsModule.forRoot(),
StoreModule.forRoot({}),
TranslateModule.forRoot(),
CommonModule,
],
providers: [
{
provide: MapUtilsService,
useClass: MapUtilsServiceMock,
},
{
provide: MapFacade,
useClass: MapFacadeMock,
},
{
provide: MapStyleService,
useClass: mapStyleServiceMock,
},
{
provide: Gn4Converter,
useClass: Gn4MetadataMapperMock,
},
{
provide: PlatformServiceInterface,
useClass: PlatformServiceInterfaceMock,
},
],
}).compileComponents()
jest.clearAllMocks()
})

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

beforeEach(() => {
TestBed.configureTestingModule({}).compileComponents()

mapFacade = TestBed.inject(MapFacade)
fixture = TestBed.createComponent(FormFieldMapContainerComponent)
component = fixture.componentInstance
fixture.detectChanges()
@@ -122,7 +37,12 @@ describe('FormFieldMapContainerComponent', () => {
expect(component).toBeTruthy()
})

it('should remove previous layer and add a new layer with geometries', () => {
it('initial context is null (default map)', async () => {
const context = await firstValueFrom(component.mapContext$)
expect(context).toEqual(null)
})

it('should remove previous layer and add a new layer with geometries', async () => {
component.spatialExtents = [
{
geometry: {
@@ -139,28 +59,92 @@ describe('FormFieldMapContainerComponent', () => {
},
},
]
component.ngOnChanges()

expect(
mapFacade.layers$.subscribe((layers) => {
expect(layers.length).toEqual(1)
})
)
const context = await firstValueFrom(component.mapContext$)
expect(context).toEqual({
layers: [
{
data: {
features: [
{
geometry: {
coordinates: [
[
[0, 0],
[0, 1],
[1, 1],
[1, 0],
[0, 0],
],
],
type: 'Polygon',
},
properties: {},
type: 'Feature',
},
],
type: 'FeatureCollection',
},
label: 'Spatial extents',
style: {
'stroke-color': 'black',
'stroke-width': 2,
},
type: 'geojson',
},
],
view: {
center: [3, 4],
zoom: 3,
},
})
expect(createViewFromLayer).toHaveBeenCalledWith(context.layers[0])
})

it('should remove previous layer and add a new layer with bbox', () => {
it('should remove previous layer and add a new layer with bbox', async () => {
component.spatialExtents = [
{
bbox: [0, 0, 1, 1],
},
]
component.ngOnChanges()

expect(
mapFacade.layers$.subscribe((layers) => {
expect(layers.length).toEqual(1)
})
)
const context = await firstValueFrom(component.mapContext$)
expect(context).toEqual({
layers: [
{
data: {
features: [
{
geometry: {
coordinates: [
[
[0, 0],
[0, 1],
[1, 1],
[1, 0],
[0, 0],
],
],
type: 'Polygon',
},
properties: {},
type: 'Feature',
},
],
type: 'FeatureCollection',
},
label: 'Spatial extents',
style: {
'stroke-color': 'black',
'stroke-width': 2,
},
type: 'geojson',
},
],
view: {
center: [3, 4],
zoom: 3,
},
})
expect(createViewFromLayer).toHaveBeenCalledWith(context.layers[0])
})

it('should transform bbox to geometry', () => {
Original file line number Diff line number Diff line change
@@ -1,95 +1,41 @@
import {
ChangeDetectionStrategy,
Component,
Input,
OnChanges,
} from '@angular/core'
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { CommonModule } from '@angular/common'
import { catchError, from, map, Observable, of, switchMap } from 'rxjs'
import {
DEFAULT_BASELAYER_CONTEXT,
FeatureMapModule,
MapContextLayerTypeEnum,
MapContextModel,
MapFacade,
MapStyleService,
MapUtilsService,
} from '@geonetwork-ui/feature/map'
import { Fill, Stroke, Style } from 'ol/style'
import { getOptionalMapConfig, MapConfig } from '@geonetwork-ui/util/app-config'
import { Geometry } from 'geojson'
import { GeoJSONFeatureCollection } from 'ol/format/GeoJSON'
import GeoJSON, { GeoJSONFeatureCollection } from 'ol/format/GeoJSON'
import { DatasetSpatialExtent } from '@geonetwork-ui/common/domain/model/record'
import { Polygon } from 'ol/geom'
import GeoJSON from 'ol/format/GeoJSON'
import {
createViewFromLayer,
MapContext,
MapContextLayer,
} from '@geospatial-sdk/core'
import { MapContainerComponent } from '@geonetwork-ui/ui/map'
import { BehaviorSubject, Observable } from 'rxjs'
import { switchMap } from 'rxjs/operators'

@Component({
selector: 'gn-ui-form-field-map-container',
standalone: true,
imports: [CommonModule, FeatureMapModule],
imports: [CommonModule, MapContainerComponent],
templateUrl: './form-field-map-container.component.html',
styleUrls: ['./form-field-map-container.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FormFieldMapContainerComponent implements OnChanges {
@Input() spatialExtents: DatasetSpatialExtent[]

error = ''
mapContext$: Observable<MapContextModel> = this.mapFacade.layers$.pipe(
switchMap((layers) =>
from(this.mapUtils.getLayerExtent(layers[0])).pipe(
catchError(() => {
this.error = 'The layer has no extent'
return of(undefined)
}),
map((extent) => {
return {
layers: [DEFAULT_BASELAYER_CONTEXT, ...layers],
view: {
extent: extent,
},
} as MapContextModel
})
)
)
)

mapConfig: MapConfig = getOptionalMapConfig()

constructor(
private mapFacade: MapFacade,
private mapUtils: MapUtilsService,
private styleService: MapStyleService
) {
const fill = new Fill({
color: 'transparent',
})
const stroke = new Stroke({
color: 'black',
width: 2,
})
const styles = [
new Style({
fill: fill,
stroke: stroke,
}),
]
this.styleService.styles.default = this.styleService.createStyleFunction({
...this.styleService.createGeometryStyles({ color: 'black' }),
polygon: styles,
})
export class FormFieldMapContainerComponent {
@Input() set spatialExtents(value: DatasetSpatialExtent[]) {
this.spatialExtents$.next(value)
}

ngOnChanges(): void {
this.mapFacade.removeLayer(0)

if (this.spatialExtents) {
spatialExtents$ = new BehaviorSubject<DatasetSpatialExtent[]>([])
mapContext$: Observable<MapContext> = this.spatialExtents$.pipe(
switchMap(async (extents) => {
if (extents.length === 0) {
return null // null extent means default view
}
const featureCollection: GeoJSONFeatureCollection = {
type: 'FeatureCollection',
features: [],
}

this.spatialExtents.forEach((extent) => {
extents.forEach((extent) => {
if (extent.geometry) {
featureCollection.features.push({
type: 'Feature',
@@ -104,13 +50,26 @@ export class FormFieldMapContainerComponent implements OnChanges {
})
}
})
this.mapFacade.addLayer({
type: MapContextLayerTypeEnum.GEOJSON,

const layer: MapContextLayer = {
type: 'geojson',
data: featureCollection,
title: 'Spatial extents',
})
}
}
label: 'Spatial extents',
style: {
'stroke-color': 'black',
'stroke-width': 2,
},
}
const view = await createViewFromLayer(layer)
return {
view,
layers: [layer],
}
})
)

error = ''

bboxCoordsToGeometry(bbox: [number, number, number, number]): Geometry {
const geometry = new Polygon([
[
@@ -122,7 +81,6 @@ export class FormFieldMapContainerComponent implements OnChanges {
],
])

const geoJSONGeom = new GeoJSON().writeGeometryObject(geometry)
return geoJSONGeom
return new GeoJSON().writeGeometryObject(geometry)
}
}

0 comments on commit 2f3fe49

Please sign in to comment.