Skip to content

Commit

Permalink
Merge pull request #949 from geonetwork/use-geoespatial-sdk-for-maps
Browse files Browse the repository at this point in the history
Use geospatial-sdk for maps
  • Loading branch information
jahow authored Sep 18, 2024
2 parents 86d11dd + dd44d80 commit 4992149
Show file tree
Hide file tree
Showing 113 changed files with 2,038 additions and 3,593 deletions.
4 changes: 3 additions & 1 deletion apps/datahub-e2e/src/e2e/datasetDetailPage.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,9 @@ describe('dataset pages', () => {
.should('have.length.gt', 1)
})
it('should display the map', () => {
cy.get('@previewSection').find('gn-ui-map').should('be.visible')
cy.get('@previewSection')
.find('gn-ui-map-container')
.should('be.visible')
})
it('should display the table', () => {
cy.get('@previewSection')
Expand Down
33 changes: 33 additions & 0 deletions apps/datahub/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
ORGANIZATION_URL_TOKEN,
} from '@geonetwork-ui/feature/catalog'
import {
EXTERNAL_VIEWER_OPEN_NEW_TAB,
EXTERNAL_VIEWER_URL_TEMPLATE,
FeatureRecordModule,
GN_UI_VERSION,
WEB_COMPONENT_EMBEDDER_URL,
Expand Down Expand Up @@ -43,6 +45,8 @@ import {
import { UiSearchModule } from '@geonetwork-ui/ui/search'
import {
getGlobalConfig,
getMapContextLayerFromConfig,
getOptionalMapConfig,
getOptionalSearchConfig,
getThemeConfig,
TRANSLATE_WITH_OVERRIDES_CONFIG,
Expand Down Expand Up @@ -95,6 +99,11 @@ import { UiWidgetsModule } from '@geonetwork-ui/ui/widgets'
import { RecordUserFeedbacksComponent } from './record/record-user-feedbacks/record-user-feedbacks.component'
import { LetDirective } from '@ngrx/component'
import { OrganizationPageComponent } from './organization/organization-page/organization-page.component'
import {
BASEMAP_LAYERS,
DO_NOT_USE_DEFAULT_BASEMAP,
MAP_VIEW_CONSTRAINTS,
} from '@geonetwork-ui/ui/map'

export const metaReducers: MetaReducer[] = !environment.production ? [] : []

Expand Down Expand Up @@ -229,6 +238,30 @@ export const metaReducers: MetaReducer[] = !environment.production ? [] : []
provide: ORGANIZATION_URL_TOKEN,
useValue: `${ROUTER_ROUTE_SEARCH}?${ROUTE_PARAMS.PUBLISHER}=\${name}`,
},
{
provide: DO_NOT_USE_DEFAULT_BASEMAP,
useFactory: () => getOptionalMapConfig()?.DO_NOT_USE_DEFAULT_BASEMAP,
},
{
provide: BASEMAP_LAYERS,
useFactory: () =>
getOptionalMapConfig()?.MAP_LAYERS.map(getMapContextLayerFromConfig),
},
{
provide: MAP_VIEW_CONSTRAINTS,
useFactory: () => ({
maxExtent: getOptionalMapConfig()?.MAX_EXTENT,
maxZoom: getOptionalMapConfig()?.MAX_ZOOM,
}),
},
{
provide: EXTERNAL_VIEWER_URL_TEMPLATE,
useFactory: () => getOptionalMapConfig()?.EXTERNAL_VIEWER_URL_TEMPLATE,
},
{
provide: EXTERNAL_VIEWER_OPEN_NEW_TAB,
useFactory: () => getOptionalMapConfig()?.EXTERNAL_VIEWER_OPEN_NEW_TAB,
},
],
bootstrap: [AppComponent],
})
Expand Down
1 change: 1 addition & 0 deletions apps/demo/.storybook/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import 'ol/ol.css';
2 changes: 1 addition & 1 deletion apps/map-viewer/src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div class="h-full w-full relative">
<gn-ui-map-container></gn-ui-map-container>
<gn-ui-map-state-container></gn-ui-map-state-container>
<gn-ui-layers-panel
class="absolute"
style="top: 20px; left: 20px; bottom: 20px"
Expand Down
9 changes: 3 additions & 6 deletions apps/map-viewer/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
import { Component } from '@angular/core'
import {
MapContextLayerTypeEnum,
MapContextModel,
} from '@geonetwork-ui/feature/map'
import { MapContext } from '@geospatial-sdk/core'

@Component({
selector: 'map-viewer-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
context: MapContextModel = {
context: MapContext = {
view: {
center: [4, 42],
zoom: 6,
},
layers: [
{
type: MapContextLayerTypeEnum.XYZ,
type: 'xyz',
url: 'https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png',
},
],
Expand Down
4 changes: 4 additions & 0 deletions apps/map-viewer/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { BrowserModule } from '@angular/platform-browser'
import { AppComponent } from './app.component'
import {
FeatureMapModule,
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 @@ -39,6 +41,8 @@ export const metaReducers: MetaReducer<any>[] = !environment.production
EffectsModule.forRoot(),
FeatureCatalogModule,
LayersPanelComponent,
MapStateContainerComponent,
GeocodingComponent,
],
providers: [
importProvidersFrom(FeatureAuthModule),
Expand Down
2 changes: 1 addition & 1 deletion apps/metadata-editor-e2e/src/e2e/edit.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ describe('editor form', () => {
describe('geographical coverage', () => {
it('should show a map', () => {
cy.get('gn-ui-form-field-spatial-extent')
.find('gn-ui-map')
.find('gn-ui-map-container')
.should('be.visible')
})
describe('spatial extents', () => {
Expand Down
2 changes: 0 additions & 2 deletions apps/search/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { FeatureCatalogModule } from '@geonetwork-ui/feature/catalog'
import { FeatureDatavizModule } from '@geonetwork-ui/feature/dataviz'
import { FeatureMapModule } from '@geonetwork-ui/feature/map'
import { UiLayoutModule } from '@geonetwork-ui/ui/layout'
import { UiMapModule } from '@geonetwork-ui/ui/map'
import {
TRANSLATE_DEFAULT_CONFIG,
UtilI18nModule,
Expand Down Expand Up @@ -39,7 +38,6 @@ export const metaReducers: MetaReducer<any>[] = !environment.production
FeatureCatalogModule,
UiLayoutModule,
FeatureMapModule,
UiMapModule,
FeatureDatavizModule,
StoreModule.forRoot({}, { metaReducers }),
!environment.production ? StoreDevtoolsModule.instrument() : [],
Expand Down
2 changes: 0 additions & 2 deletions apps/search/src/app/main-search/main-search.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Component, OnInit } from '@angular/core'
import { FeatureInfoService } from '@geonetwork-ui/feature/map'
import { SearchFacade } from '@geonetwork-ui/feature/search'
import { UiApiService } from '@geonetwork-ui/data-access/gn4'
import { firstValueFrom, map } from 'rxjs'
Expand All @@ -12,7 +11,6 @@ import { firstValueFrom, map } from 'rxjs'
export class MainSearchComponent implements OnInit {
constructor(
private uiService: UiApiService,
private featureInfo: FeatureInfoService,
private searchFacade: SearchFacade
) {}

Expand Down
7 changes: 6 additions & 1 deletion apps/webcomponents/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@
"polyfills": "apps/webcomponents/src/polyfills.ts",
"tsConfig": "apps/webcomponents/tsconfig.app.json",
"assets": [],
"styles": [],
"styles": [
"tailwind.base.css",
"apps/webcomponents/src/styles.css",
"node_modules/tippy.js/dist/tippy.css",
"node_modules/ol/ol.css"
],
"scripts": [],
"allowedCommonJsDependencies": [
"duration-relativetimeformat",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div class="h-full w-full relative">
<gn-ui-map-container></gn-ui-map-container>
<gn-ui-map-state-container></gn-ui-map-state-container>
<gn-ui-layers-panel
class="absolute"
style="top: 20px; left: 20px; bottom: 20px"
Expand Down
8 changes: 7 additions & 1 deletion apps/webcomponents/src/app/webcomponents.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ import { GnResultsListComponent } from './components/gn-results-list/gn-results-
import { GnSearchInputComponent } from './components/gn-search-input/gn-search-input.component'
import { GnDatasetViewTableComponent } from './components/gn-dataset-view-table/gn-dataset-view-table.component'
import { GnMapViewerComponent } from './components/gn-map-viewer/gn-map-viewer.component'
import { FeatureMapModule } from '@geonetwork-ui/feature/map'
import {
FeatureMapModule,
LayersPanelComponent,
MapStateContainerComponent,
} from '@geonetwork-ui/feature/map'
import { GnDatasetViewChartComponent } from './components/gn-dataset-view-chart/gn-dataset-view-chart.component'
import { FeatureDatavizModule } from '@geonetwork-ui/feature/dataviz'
import { FeatureAuthModule } from '@geonetwork-ui/feature/auth'
Expand Down Expand Up @@ -90,6 +94,8 @@ const CUSTOM_ELEMENTS: [new (...args) => BaseComponent, string][] = [
FeatureDatavizModule,
FeatureAuthModule,
BrowserAnimationsModule,
MapStateContainerComponent,
LayersPanelComponent,
],
providers: [
provideGn4(),
Expand Down
4 changes: 4 additions & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ function sidebarReference() {
text: 'Pivot Format',
link: '/reference/pivot-format',
},
{
text: 'Interactive maps',
link: '/reference/maps',
},
],
},
{
Expand Down
50 changes: 50 additions & 0 deletions docs/reference/maps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
outline: deep
---

# Interactive maps

GeoNetwork-UI relies on the [geospatial-sdk](https://github.com/camptocamp/geospatial-sdk) library to render maps. This library works by taking in a Map Context ([see the model here](https://github.com/camptocamp/geospatial-sdk/blob/main/packages/core/lib/model/map-context.ts)) describing the layers and the view of the map to be shown.

Two components are present in GeoNetwork-UI to render a map using a context.

## `MapContainerComponent`

This component simply takes a map context as input and will render it. Everytime the map context changes, the map is updated accordingly.

This component also offers the following events: `mapClick`, `featuresClicked`, `featuresHovered`.

```ts
import { MapContainerComponent } from '@geonetwork-ui/ui/map'
```

```html
<gn-ui-map-container
[context]="mapContext"
(featuresClick)="handleFeaturesClicked($event)"
></gn-ui-map-container>
```

There are a couple of injection tokens that can be used to specify some map options:

- `BASEMAP_LAYERS`: this allows specifying layers that will be added in the background of the map, regardless of the layers in the context; note that there is always a default background tile layer so that the map shown is never empty; this default background layer can be disabled by setting the `DO_NOT_USE_DEFAULT_BASEMAP` token to `true`
- `MAP_VIEW_CONSTRAINTS`: this allows specifying `maxZoom` and `maxExtent` options that will be applied regardless of the map context

## `MapStateContainerComponent`

This component is connected to a map state accessible through the `MapFacade` class. This allows changing the context used in the map from anywhere in the application, as well as showing the currently selected feature in the map (if any).

The `LayersPanel` component is an example of how another component can interact with the map through the `MapFacade` class.

```ts
import {
MapStateContainerComponent,
MapFacade,
LayersPanel,
} from '@geonetwork-ui/feature/map'
```

```html
<gn-ui-map-state-container></gn-ui-map-state-container>
<gn-ui-layers-panel class="absolute inset-y-0 left-0"></gn-ui-layers-panel>
```
15 changes: 14 additions & 1 deletion jest.preset.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
const nxPreset = require('@nx/jest/preset').default

const npmDependenciesOnlyInEsm = [
'color-*',
'ol',
'@mapbox',
'@geospatial-sdk',
'@camptocamp/ogc-client',
'node-fetch',
'data-uri-to-buffer',
'fetch-blob',
'formdata-polyfill',
'.*.mjs',
]

module.exports = {
...nxPreset,
coverageReporters: ['text'],
setupFiles: ['jest-canvas-mock'],
transformIgnorePatterns: [
'node_modules/(?!(color-*|ol|@mapbox|@geospatial-sdk|@camptocamp/ogc-client|.*.mjs$))',
`node_modules/(?!(${npmDependenciesOnlyInEsm.join('|')}))`,
],
transform: {
'^.+\\.(ts|mjs|js|html)$': [
Expand Down
2 changes: 2 additions & 0 deletions libs/common/fixtures/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ export * from './lib/user.fixtures'
export * from './lib/user-feedbacks.fixtures'

export * from './lib/editor'

export * from './lib/map'
1 change: 1 addition & 0 deletions libs/common/fixtures/src/lib/map/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './map-context.fixtures'
53 changes: 53 additions & 0 deletions libs/common/fixtures/src/lib/map/map-context.fixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { polygonFeatureCollectionFixture } from '../geojson.fixtures'
import { Extent } from 'ol/extent'
import type {
MapContext,
MapContextLayer,
MapContextView,
} from '@geospatial-sdk/core'

export const mapCtxLayerXyzFixture = (): MapContextLayer => ({
type: 'xyz',
url: 'https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png',
attributions: '<a href="https://www.openstreetmap.org/copyright">',
})

export const mapCtxLayerWmsFixture = (): MapContextLayer => ({
type: 'wms',
url: 'https://www.geograndest.fr/geoserver/region-grand-est/ows?REQUEST=GetCapabilities&SERVICE=WMS',
name: 'commune_actuelle_3857',
})

export const mapCtxLayerWfsFixture = (): MapContextLayer => ({
type: 'wfs',
url: 'https://www.geograndest.fr/geoserver/region-grand-est/ows?REQUEST=GetCapabilities&SERVICE=WFS&VERSION=1.1.0',
featureType: 'ms:commune_actuelle_3857',
})

export const mapCtxLayerGeojsonFixture = (): MapContextLayer => ({
type: 'geojson',
data: polygonFeatureCollectionFixture(),
})

export const mapCtxLayerGeojsonRemoteFixture = (): MapContextLayer => ({
type: 'geojson',
url: 'https://my.host.com/data/regions.json',
})

export const mapCtxViewFixture = (): MapContextView => ({
center: [7.75, 48.6],
zoom: 9,
})

export const mapCtxFixture = (): MapContext => ({
layers: [
mapCtxLayerXyzFixture(),
mapCtxLayerWmsFixture(),
mapCtxLayerGeojsonFixture(),
],
view: mapCtxViewFixture(),
})

export const mapCtxExtentFixture = (): Extent => [
171083.69713494915, 6246047.945419401, 476970.39956295764, 6631079.362882684,
]
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
Loading

0 comments on commit 4992149

Please sign in to comment.