From be75c8f9239a4fae5d31e945b619137b1a652c00 Mon Sep 17 00:00:00 2001
From: Olivia Guyot <olivia.guyot@camptocamp.com>
Date: Wed, 28 Feb 2024 11:11:05 +0100
Subject: [PATCH 1/6] update ogc-client

---
 package-lock.json | 30 +++++++++++++++++++++++++-----
 package.json      |  2 +-
 2 files changed, 26 insertions(+), 6 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 9e4aca1a0e..47e9e1fc2d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -23,7 +23,7 @@
         "@angular/router": "16.1.7",
         "@bartholomej/ngx-translate-extract": "^8.0.2",
         "@biesbjerg/ngx-translate-extract-marker": "^1.0.0",
-        "@camptocamp/ogc-client": "^0.4.0",
+        "@camptocamp/ogc-client": "^1.1.0-RC.2",
         "@geospatial-sdk/geocoding": "^0.0.5-alpha.2",
         "@ltd/j-toml": "~1.35.2",
         "@messageformat/core": "^3.0.1",
@@ -3651,11 +3651,31 @@
       "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
     },
     "node_modules/@camptocamp/ogc-client": {
-      "version": "0.4.0",
-      "resolved": "https://registry.npmjs.org/@camptocamp/ogc-client/-/ogc-client-0.4.0.tgz",
-      "integrity": "sha512-fGLkg4xyZQg4xbZx8E8HqACfZJZ4ijcpBDgwYnDzCAX+AjnAhqUqs/jwtR7i9xgoOLcjAftPsQVZWVcDLufytw==",
+      "version": "1.1.0-RC.2",
+      "resolved": "https://registry.npmjs.org/@camptocamp/ogc-client/-/ogc-client-1.1.0-RC.2.tgz",
+      "integrity": "sha512-SGmkweVfY8vomjy9K7apI8HnKDhXwsEnh8QIxCqOAR6csbrU2Dv4FQftd2xMicMIuRt/bMBxjwF2fBqKoTKchA==",
       "dependencies": {
-        "@rgrove/parse-xml": "^4.0.1"
+        "@rgrove/parse-xml": "^4.1.0"
+      },
+      "peerDependencies": {
+        "ol": ">5.x",
+        "proj4": ">2.8"
+      },
+      "peerDependenciesMeta": {
+        "ol": {
+          "optional": true
+        },
+        "proj4": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@camptocamp/ogc-client/node_modules/@rgrove/parse-xml": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/@rgrove/parse-xml/-/parse-xml-4.1.0.tgz",
+      "integrity": "sha512-pBiltENdy8SfI0AeR1e5TRpS9/9Gl0eiOEt6ful2jQfzsgvZYWqsKiBWaOCLdocQuk0wS7KOHI37n0C1pnKqTw==",
+      "engines": {
+        "node": ">=14.0.0"
       }
     },
     "node_modules/@colors/colors": {
diff --git a/package.json b/package.json
index d516bf2623..ca86c173c3 100644
--- a/package.json
+++ b/package.json
@@ -58,7 +58,7 @@
     "@angular/router": "16.1.7",
     "@bartholomej/ngx-translate-extract": "^8.0.2",
     "@biesbjerg/ngx-translate-extract-marker": "^1.0.0",
-    "@camptocamp/ogc-client": "^0.4.0",
+    "@camptocamp/ogc-client": "^1.1.0-RC.2",
     "@geospatial-sdk/geocoding": "^0.0.5-alpha.2",
     "@ltd/j-toml": "~1.35.2",
     "@messageformat/core": "^3.0.1",

From 9f82dc5a349574970c5203e9f8c596830de98df0 Mon Sep 17 00:00:00 2001
From: Olivia Guyot <olivia.guyot@camptocamp.com>
Date: Fri, 2 Feb 2024 12:36:05 +0100
Subject: [PATCH 2/6] wip

---
 .../add-layer-record-preview.component.ts     |   6 +-
 .../src/lib/map-context/map-context.model.ts  |   5 +-
 .../map-context/map-context.service.spec.ts   |  20 ++
 .../lib/map-context/map-context.service.ts    |  83 +++--
 .../src/lib/utils/map-utils.service.spec.ts   | 313 ------------------
 .../map/src/lib/utils/map-utils.service.ts    |  78 +----
 .../src/lib/map-view/map-view.component.ts    |   6 +-
 7 files changed, 100 insertions(+), 411 deletions(-)

diff --git a/libs/feature/map/src/lib/add-layer-from-catalog/add-layer-record-preview/add-layer-record-preview.component.ts b/libs/feature/map/src/lib/add-layer-from-catalog/add-layer-record-preview/add-layer-record-preview.component.ts
index 278dca0fc2..837da4f23b 100644
--- a/libs/feature/map/src/lib/add-layer-from-catalog/add-layer-record-preview/add-layer-record-preview.component.ts
+++ b/libs/feature/map/src/lib/add-layer-from-catalog/add-layer-record-preview/add-layer-record-preview.component.ts
@@ -59,7 +59,11 @@ export class AddLayerRecordPreviewComponent extends RecordPreviewComponent {
         name: link.name,
       })
     } else if (link.accessServiceProtocol === 'wmts') {
-      return this.mapUtils.getWmtsLayerFromCapabilities(link)
+      return of({
+        url: link.url.toString(),
+        type: MapContextLayerTypeEnum.WMTS,
+        name: link.name,
+      })
     }
     return throwError(() => 'protocol not supported')
   }
diff --git a/libs/feature/map/src/lib/map-context/map-context.model.ts b/libs/feature/map/src/lib/map-context/map-context.model.ts
index e005842a20..7998d4bfb7 100644
--- a/libs/feature/map/src/lib/map-context/map-context.model.ts
+++ b/libs/feature/map/src/lib/map-context/map-context.model.ts
@@ -1,7 +1,6 @@
 import type { FeatureCollection } from 'geojson'
 import { Coordinate } from 'ol/coordinate'
 import type { Extent } from 'ol/extent'
-import { Options } from 'ol/source/WMTS'
 
 export enum MapContextLayerTypeEnum {
   XYZ = 'xyz',
@@ -24,8 +23,8 @@ export interface MapContextLayerWmsModel {
 
 export interface MapContextLayerWmtsModel {
   type: 'wmts'
-  options: Options
-  extent?: Extent
+  url: string
+  name: string
 }
 
 interface MapContextLayerWfsModel {
diff --git a/libs/feature/map/src/lib/map-context/map-context.service.spec.ts b/libs/feature/map/src/lib/map-context/map-context.service.spec.ts
index f522e93b7a..544a772444 100644
--- a/libs/feature/map/src/lib/map-context/map-context.service.spec.ts
+++ b/libs/feature/map/src/lib/map-context/map-context.service.spec.ts
@@ -41,6 +41,26 @@ const mapStyleServiceMock = {
     defaultHL: DEFAULT_STYLE_HL_FIXTURE,
   },
 }
+
+jest.mock('@camptocamp/ogc-client', () => ({
+  WmtsEndpoint: class {
+    constructor(private url) {}
+    isReady() {
+      return Promise.resolve({
+        getLayerByName: (name) => {
+          if (this.url.indexOf('error') > -1) {
+            throw new Error('Something went wrong')
+          }
+          return {
+            name,
+            latLonBoundingBox: [1.33, 48.81, 4.3, 51.1],
+          }
+        },
+      })
+    }
+  },
+}))
+
 describe('MapContextService', () => {
   let service: MapContextService
 
diff --git a/libs/feature/map/src/lib/map-context/map-context.service.ts b/libs/feature/map/src/lib/map-context/map-context.service.ts
index 6b2d53fc94..1ed587b7c5 100644
--- a/libs/feature/map/src/lib/map-context/map-context.service.ts
+++ b/libs/feature/map/src/lib/map-context/map-context.service.ts
@@ -22,9 +22,9 @@ import { LayerConfig, MapConfig } from '@geonetwork-ui/util/app-config'
 import { FeatureCollection } from 'geojson'
 import { fromLonLat } from 'ol/proj'
 import WMTS from 'ol/source/WMTS'
-import { removeSearchParams } from '@geonetwork-ui/util/shared'
 import { Geometry } from 'ol/geom'
 import Feature from 'ol/Feature'
+import { WfsEndpoint, WmtsEndpoint } from '@camptocamp/ogc-client'
 
 export const DEFAULT_BASELAYER_CONTEXT: MapContextLayerXyzModel = {
   type: MapContextLayerTypeEnum.XYZ,
@@ -87,44 +87,63 @@ export class MapContextService {
       case MapContextLayerTypeEnum.WMS:
         return new TileLayer({
           source: new TileWMS({
-            url: removeSearchParams(layerModel.url, ['request', 'service']),
+            url: layerModel.url,
             params: { LAYERS: layerModel.name },
             gutter: 20,
           }),
         })
-      case MapContextLayerTypeEnum.WMTS:
-        return new TileLayer({
-          source: new WMTS(layerModel.options),
+      case MapContextLayerTypeEnum.WMTS: {
+        // TODO: isolate this in utils service
+        const olLayer = new TileLayer({})
+        const endpoint = new WmtsEndpoint(layerModel.url)
+        endpoint.isReady().then(async (endpoint) => {
+          const layerName = endpoint.getSingleLayerName() ?? layerModel.name
+          const layer = endpoint.getLayerByName(layerName)
+          const matrixSet = layer.matrixSets[0]
+          const tileGrid = await endpoint.getOpenLayersTileGrid(layer.name)
+          const resourceUrl = layer.resourceLinks[0]
+          const dimensions = endpoint.getDefaultDimensions(layer.name)
+          olLayer.setSource(
+            new WMTS({
+              layer: layer.name,
+              style: layer.defaultStyle,
+              matrixSet: matrixSet.identifier,
+              format: resourceUrl.format,
+              url: resourceUrl.url,
+              requestEncoding: resourceUrl.encoding,
+              tileGrid,
+              projection: matrixSet.crs,
+              dimensions,
+            })
+          )
         })
-      case MapContextLayerTypeEnum.WFS:
-        return new VectorLayer({
-          source: new VectorSource({
-            format: new GeoJSON(),
-            url: function (extent) {
-              const urlObj = new URL(
-                removeSearchParams(layerModel.url, [
-                  'service',
-                  'version',
-                  'request',
-                ])
-              )
-              urlObj.searchParams.set('service', 'WFS')
-              urlObj.searchParams.set('version', '1.1.0')
-              urlObj.searchParams.set('request', 'GetFeature')
-              urlObj.searchParams.set('outputFormat', 'application/json')
-              urlObj.searchParams.set('typename', layerModel.name)
-              urlObj.searchParams.set('srsname', 'EPSG:3857')
-              urlObj.searchParams.set('bbox', `${extent.join(',')},EPSG:3857`)
-              urlObj.searchParams.set(
-                'maxFeatures',
-                WFS_MAX_FEATURES.toString()
-              )
-              return urlObj.toString()
-            },
-            strategy: bboxStrategy,
-          }),
+        return olLayer
+      }
+      case MapContextLayerTypeEnum.WFS: {
+        const olLayer = new VectorLayer({
           style,
         })
+        new WfsEndpoint(layerModel.url).isReady().then((endpoint) => {
+          const featureType =
+            endpoint.getSingleFeatureTypeName() ?? layerModel.name
+          olLayer.setSource(
+            new VectorSource({
+              format: new GeoJSON(),
+              url: function (extent: [number, number, number, number]) {
+                return endpoint.getFeatureUrl(featureType, {
+                  maxFeatures: WFS_MAX_FEATURES,
+                  asJson: true,
+                  outputCrs: 'EPSG:3857',
+                  extent,
+                  extentCrs: 'EPSG:3857',
+                })
+              },
+              strategy: bboxStrategy,
+            })
+          )
+        })
+        return olLayer
+      }
       case MapContextLayerTypeEnum.GEOJSON: {
         if ('url' in layerModel) {
           return new VectorLayer({
diff --git a/libs/feature/map/src/lib/utils/map-utils.service.spec.ts b/libs/feature/map/src/lib/utils/map-utils.service.spec.ts
index 72419148ae..3f3415cb84 100644
--- a/libs/feature/map/src/lib/utils/map-utils.service.spec.ts
+++ b/libs/feature/map/src/lib/utils/map-utils.service.spec.ts
@@ -406,317 +406,4 @@ describe('MapUtilsService', () => {
       expect(dragPanCondition.bind(interaction)(event)).toBe(false)
     })
   })
-
-  const SAMPLE_WMTS_LINK = {
-    name: 'GEOGRAPHICALGRIDSYSTEMS.ETATMAJOR10',
-    url: new URL('http://my.server.org/wmts'),
-    type: 'service',
-    accessServiceProtocol: 'wmts',
-  } as DatasetServiceDistribution
-  const SAMPLE_WMTS_CAPABILITIES = `<?xml version="1.0" encoding="UTF-8"?>
-<Capabilities xmlns="http://www.opengis.net/wmts/1.0" xmlns:gml="http://www.opengis.net/gml" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0.0" xsi:schemaLocation="http://www.opengis.net/wmts/1.0 http://schemas.opengis.net/wmts/1.0/wmtsGetCapabilities_response.xsd">
-  <ows:OperationsMetadata>
-    <ows:Operation name="GetTile">
-      <ows:DCP>
-        <ows:HTTP>
-          <ows:Get xlink:href="https://wxs.ign.fr/cartes/geoportail/wmts?">
-            <ows:Constraint name="GetEncoding">
-              <ows:AllowedValues>
-                <ows:Value>KVP</ows:Value>
-              </ows:AllowedValues>
-            </ows:Constraint>
-          </ows:Get>
-        </ows:HTTP>
-      </ows:DCP>
-    </ows:Operation>
-  </ows:OperationsMetadata>
-  <Contents>
-    <Layer>
-      <ows:Title>Carte de l'état-major - environs de Paris (1818 - 1824)</ows:Title>
-      <ows:Abstract>Carte des environs de Paris au 1 : 10 000 établie entre 1818 et 1824.</ows:Abstract>
-      <ows:WGS84BoundingBox>
-        <ows:LowerCorner>1.82682 48.3847</ows:LowerCorner>
-        <ows:UpperCorner>2.79738 49.5142</ows:UpperCorner>
-      </ows:WGS84BoundingBox>
-      <ows:Identifier>GEOGRAPHICALGRIDSYSTEMS.ETATMAJOR10</ows:Identifier>
-      <Format>image/jpeg</Format>
-      <Style isDefault="true">
-        <ows:Title>Légende générique</ows:Title>
-        <ows:Abstract>
-          Fichier de légende générique – pour la compatibilité avec certains systèmes
-        </ows:Abstract>
-        <ows:Identifier>normal</ows:Identifier>
-        <LegendURL format="image/jpeg" height="200" maxScaleDenominator="100000000" minScaleDenominator="200" width="200" xlink:href="https://wxs.ign.fr/static/legends/LEGEND.jpg"/>
-      </Style>
-      <TileMatrixSetLink>
-        <TileMatrixSet>PM</TileMatrixSet>
-        <TileMatrixSetLimits>
-          <TileMatrixLimits>
-            <TileMatrix>6</TileMatrix>
-            <MinTileRow>21</MinTileRow>
-            <MaxTileRow>22</MaxTileRow>
-            <MinTileCol>32</MinTileCol>
-            <MaxTileCol>32</MaxTileCol>
-          </TileMatrixLimits>
-          <TileMatrixLimits>
-            <TileMatrix>7</TileMatrix>
-            <MinTileRow>43</MinTileRow>
-            <MaxTileRow>44</MaxTileRow>
-            <MinTileCol>64</MinTileCol>
-            <MaxTileCol>64</MaxTileCol>
-          </TileMatrixLimits>
-          <TileMatrixLimits>
-            <TileMatrix>8</TileMatrix>
-            <MinTileRow>87</MinTileRow>
-            <MaxTileRow>88</MaxTileRow>
-            <MinTileCol>129</MinTileCol>
-            <MaxTileCol>129</MaxTileCol>
-          </TileMatrixLimits>
-        </TileMatrixSetLimits>
-      </TileMatrixSetLink>
-    </Layer>
-    <TileMatrixSet>
-      <ows:Identifier>PM</ows:Identifier>
-      <ows:SupportedCRS>EPSG:3857</ows:SupportedCRS>
-      <TileMatrix>
-        <ows:Identifier>0</ows:Identifier>
-        <ScaleDenominator>559082264.0287178958533332</ScaleDenominator>
-        <TopLeftCorner>
-          -20037508.3427892476320267 20037508.3427892476320267
-        </TopLeftCorner>
-        <TileWidth>256</TileWidth>
-        <TileHeight>256</TileHeight>
-        <MatrixWidth>1</MatrixWidth>
-        <MatrixHeight>1</MatrixHeight>
-      </TileMatrix>
-      <TileMatrix>
-        <ows:Identifier>1</ows:Identifier>
-        <ScaleDenominator>279541132.0143588959472254</ScaleDenominator>
-        <TopLeftCorner>
-          -20037508.3427892476320267 20037508.3427892476320267
-        </TopLeftCorner>
-        <TileWidth>256</TileWidth>
-        <TileHeight>256</TileHeight>
-        <MatrixWidth>2</MatrixWidth>
-        <MatrixHeight>2</MatrixHeight>
-      </TileMatrix>
-      <TileMatrix>
-        <ows:Identifier>2</ows:Identifier>
-        <ScaleDenominator>139770566.0071793960087234</ScaleDenominator>
-        <TopLeftCorner>
-        -20037508.3427892476320267 20037508.3427892476320267
-        </TopLeftCorner>
-        <TileWidth>256</TileWidth>
-        <TileHeight>256</TileHeight>
-        <MatrixWidth>4</MatrixWidth>
-        <MatrixHeight>4</MatrixHeight>
-      </TileMatrix>
-      <TileMatrix>
-        <ows:Identifier>3</ows:Identifier>
-        <ScaleDenominator>69885283.0035897239868063</ScaleDenominator>
-        <TopLeftCorner>
-          -20037508.3427892476320267 20037508.3427892476320267
-        </TopLeftCorner>
-        <TileWidth>256</TileWidth>
-        <TileHeight>256</TileHeight>
-        <MatrixWidth>8</MatrixWidth>
-        <MatrixHeight>8</MatrixHeight>
-      </TileMatrix>
-    </TileMatrixSet>
-  </Contents>
-</Capabilities>`
-
-  describe('#getWmtsOptionsFromCapabilities', () => {
-    let originalFetch
-    beforeEach(() => {
-      originalFetch = window.fetch
-    })
-    afterEach(() => {
-      window.fetch = originalFetch
-    })
-    describe('nominal', () => {
-      let wmtsLayer: MapContextLayerWmtsModel
-      beforeEach(async () => {
-        ;(window as any).fetch = jest.fn(() =>
-          Promise.resolve({
-            ok: true,
-            status: 200,
-            text: () => Promise.resolve(SAMPLE_WMTS_CAPABILITIES),
-          })
-        )
-        wmtsLayer = await firstValueFrom(
-          service.getWmtsLayerFromCapabilities(SAMPLE_WMTS_LINK)
-        )
-      })
-      it('appends query params to the URL', () => {
-        expect(window.fetch).toHaveBeenCalledWith(
-          'http://my.server.org/wmts?SERVICE=WMTS&REQUEST=GetCapabilities'
-        )
-      })
-      it('returns appropriate WMTS options', () => {
-        expect(wmtsLayer).toMatchObject({
-          type: 'wmts',
-          options: {
-            format: 'image/jpeg',
-            layer: 'GEOGRAPHICALGRIDSYSTEMS.ETATMAJOR10',
-            matrixSet: 'PM',
-            requestEncoding: 'KVP',
-            style: 'normal',
-            urls: ['https://wxs.ign.fr/cartes/geoportail/wmts?'],
-          },
-        })
-      })
-      describe('layer extent', () => {
-        describe('when the WGS84BoundingBox is defined', () => {
-          it('set the WGS84BoundingBox', () => {
-            expect(wmtsLayer.extent).toEqual([
-              1.82682, 48.3847, 2.79738, 49.5142,
-            ])
-          })
-        })
-        describe('when the WGS84BoundingBox is not defined', () => {
-          beforeEach(async () => {
-            ;(window as any).fetch = jest.fn(() =>
-              Promise.resolve({
-                ok: true,
-                status: 200,
-                text: () =>
-                  Promise.resolve(
-                    SAMPLE_WMTS_CAPABILITIES.replace(
-                      /WGS84BoundingBox/g,
-                      'NoWGS84BoundingBox'
-                    )
-                  ),
-              })
-            )
-            wmtsLayer = await firstValueFrom(
-              service.getWmtsLayerFromCapabilities(SAMPLE_WMTS_LINK)
-            )
-          })
-
-          it('set the WGS84BoundingBox', () => {
-            expect(wmtsLayer.extent).toBeUndefined()
-          })
-        })
-      })
-    })
-    describe('http error', () => {
-      let error
-      beforeEach(async () => {
-        ;(window as any).fetch = jest.fn(() =>
-          Promise.resolve({
-            ok: false,
-            status: 403,
-            text: () => `<ExceptionReport xmlns="http://www.opengis.net/ows/1.1">
-<Exception exceptionCode="InvalidParameterValue" >
-  Le service est inconnu pour ce serveur.
-</Exception>
-</ExceptionReport>`,
-          })
-        )
-        try {
-          await firstValueFrom(
-            service.getWmtsLayerFromCapabilities(SAMPLE_WMTS_LINK)
-          )
-        } catch (e) {
-          error = e
-        }
-      })
-      it('throws an explicit error', () => {
-        expect(error).toBeInstanceOf(Error)
-        expect(error.message).toMatch('request failed')
-      })
-    })
-    describe('parsing error', () => {
-      let error
-      beforeEach(async () => {
-        ;(window as any).fetch = jest.fn(() =>
-          Promise.resolve({
-            ok: true,
-            status: 200,
-            text: () =>
-              Promise.resolve(
-                '{ "response": "This is probably not what you expected!" }'
-              ),
-          })
-        )
-        try {
-          await firstValueFrom(
-            service.getWmtsLayerFromCapabilities(SAMPLE_WMTS_LINK)
-          )
-        } catch (e) {
-          error = e
-        }
-      })
-      it('throws an explicit error', () => {
-        expect(error).toBeInstanceOf(Error)
-        expect(error.message).toMatch('parsing failed')
-      })
-    })
-  })
-
-  describe('#getRecordExtent', () => {
-    it('returns the extent of the record', () => {
-      const record = {
-        spatialExtents: [
-          {
-            description: 'Rheinfelden',
-            geometry: {
-              type: 'Polygon',
-              coordinates: [
-                [
-                  [7.7638, 47.543],
-                  [7.7637, 47.543],
-                  [7.7636, 47.543],
-                  [7.7635, 47.543],
-                  [7.7633, 47.5429],
-                  [7.763, 47.5429],
-                  [7.7638, 47.543],
-                ],
-              ],
-            },
-          },
-          {
-            description: 'Kaiseraugst',
-            geometry: {
-              type: 'Polygon',
-              coordinates: [
-                [
-                  [7.764, 47.5429],
-                  [7.7641, 47.5423],
-                  [7.7643, 47.5421],
-                  [7.7645, 47.5415],
-                  [7.7646, 47.5411],
-                  [7.7646, 47.5405],
-                  [7.7645, 47.5398],
-                  [7.7634, 47.5402],
-                  [7.7621, 47.5401],
-                  [7.7623, 47.5396],
-                  [7.764, 47.5429],
-                ],
-              ],
-            },
-          },
-          {
-            description: 'Möhlin',
-            geometry: {
-              type: 'Polygon',
-              coordinates: [
-                [
-                  [7.8335, 47.5357],
-                  [7.8319, 47.5358],
-                  [7.831, 47.536],
-                  [7.8301, 47.5363],
-                  [7.829, 47.5364],
-                  [7.8335, 47.5357],
-                ],
-              ],
-            },
-          },
-        ],
-      } as Partial<CatalogRecord>
-      const extent = service.getRecordExtent(record)
-      expect(extent).toEqual([7.7621, 47.5357, 7.8335, 47.543])
-    })
-  })
 })
diff --git a/libs/feature/map/src/lib/utils/map-utils.service.ts b/libs/feature/map/src/lib/utils/map-utils.service.ts
index 743d90d445..6e388204ea 100644
--- a/libs/feature/map/src/lib/utils/map-utils.service.ts
+++ b/libs/feature/map/src/lib/utils/map-utils.service.ts
@@ -12,7 +12,6 @@ import Source from 'ol/source/Source'
 import ImageWMS from 'ol/source/ImageWMS'
 import TileWMS from 'ol/source/TileWMS'
 import VectorSource from 'ol/source/Vector'
-import { optionsFromCapabilities } from 'ol/source/WMTS'
 import { defaults, DragPan, Interaction, MouseWheelZoom } from 'ol/interaction'
 import {
   mouseOnly,
@@ -20,23 +19,17 @@ import {
   platformModifierKeyOnly,
   primaryAction,
 } from 'ol/events/condition'
-import WMTSCapabilities from 'ol/format/WMTSCapabilities'
-import { from, Observable } from 'rxjs'
+import { Observable } from 'rxjs'
 import { map } from 'rxjs/operators'
 import {
   MapContextLayerModel,
-  MapContextLayerTypeEnum,
   MapContextLayerWmsModel,
-  MapContextLayerWmtsModel,
 } from '../map-context/map-context.model'
 import Collection from 'ol/Collection'
 import MapBrowserEvent from 'ol/MapBrowserEvent'
-import {
-  CatalogRecord,
-  DatasetDistribution,
-} from '@geonetwork-ui/common/domain/model/record'
+import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record'
 import { ProxyService } from '@geonetwork-ui/util/shared'
-import { WmsEndpoint } from '@camptocamp/ogc-client'
+import { WmsEndpoint, WmtsEndpoint } from '@camptocamp/ogc-client'
 import { LONLAT_CRS_CODES } from '../constant/projections'
 import { fromEPSGCode, register } from 'ol/proj/proj4'
 import proj4 from 'proj4/dist/proj4'
@@ -152,11 +145,14 @@ export class MapUtilsService {
     } else if (layer && layer.type === 'wms') {
       latLonExtent = await this.getWmsLayerExtent(layer)
     } else if (layer && layer.type === 'wmts') {
-      if (layer.extent) {
-        latLonExtent = layer.extent
-      } else {
-        return layer.options.tileGrid.getExtent()
-      }
+      // TODO: isolate this in utils service
+      latLonExtent = await new WmtsEndpoint(layer.url)
+        .isReady()
+        .then((endpoint) => {
+          const layerName = endpoint.getSingleLayerName() ?? layer.name
+          const wmtsLayer = endpoint.getLayerByName(layerName)
+          return wmtsLayer.latLonBoundingBox
+        })
     } else {
       return null
     }
@@ -180,59 +176,19 @@ export class MapUtilsService {
       LONLAT_CRS_CODES.includes(crs)
     )
     if (lonLatCRS) {
-      return boundingBoxes[lonLatCRS].map(parseFloat)
+      return boundingBoxes[lonLatCRS]
     } else {
       const availableEPSGCode = Object.keys(boundingBoxes)[0]
       register(proj4)
       const proj = await fromEPSGCode(availableEPSGCode)
-      const bboxWithFiniteNumbers =
-        boundingBoxes[availableEPSGCode].map(parseFloat)
-      return transformExtent(bboxWithFiniteNumbers, proj, 'EPSG:4326')
+      return transformExtent(
+        boundingBoxes[availableEPSGCode],
+        proj,
+        'EPSG:4326'
+      )
     }
   }
 
-  getWmtsLayerFromCapabilities(
-    link: DatasetDistribution
-  ): Observable<MapContextLayerWmtsModel> {
-    const getCapabilitiesUrl = new URL(link.url, window.location.toString())
-    getCapabilitiesUrl.searchParams.set('SERVICE', 'WMTS')
-    getCapabilitiesUrl.searchParams.set('REQUEST', 'GetCapabilities')
-    return from(
-      fetch(getCapabilitiesUrl.toString())
-        .then(async function (response) {
-          if (!response.ok) {
-            throw new Error(`WMTS GetCapabilities HTTP request failed with code ${
-              response.status
-            } and body:
-${await response.text()}`)
-          }
-          return response.text()
-        })
-        .then(function (text) {
-          try {
-            const result = new WMTSCapabilities().read(text)
-            const options = optionsFromCapabilities(result, {
-              layer: link.name,
-              matrixSet: 'EPSG:3857',
-            })
-            const layerCap = result?.Contents?.Layer.find(
-              (layer) => layer.Identifier === link.name
-            )
-            return {
-              options,
-              type: MapContextLayerTypeEnum.WMTS as 'wmts',
-              ...(layerCap?.WGS84BoundingBox
-                ? { extent: layerCap.WGS84BoundingBox }
-                : {}),
-            }
-          } catch (e: any) {
-            throw new Error(`WMTS GetCapabilities parsing failed:
-${e.stack || e.message || e}`)
-          }
-        })
-    )
-  }
-
   prioritizePageScroll(interactions: Collection<Interaction>) {
     interactions.clear()
     interactions.extend(
diff --git a/libs/feature/record/src/lib/map-view/map-view.component.ts b/libs/feature/record/src/lib/map-view/map-view.component.ts
index 4bd4df379f..5cc2a7f295 100644
--- a/libs/feature/record/src/lib/map-view/map-view.component.ts
+++ b/libs/feature/record/src/lib/map-view/map-view.component.ts
@@ -188,7 +188,11 @@ export class MapViewComponent implements OnInit, OnDestroy {
       link.type === 'service' &&
       link.accessServiceProtocol === 'wmts'
     ) {
-      return this.mapUtils.getWmtsLayerFromCapabilities(link)
+      return of({
+        url: link.url.toString(),
+        type: MapContextLayerTypeEnum.WMTS,
+        name: link.name,
+      })
     } else if (
       (link.type === 'service' &&
         (link.accessServiceProtocol === 'wfs' ||

From 6b13d4a03bac4ce129f1ed0247699b53fc243dae Mon Sep 17 00:00:00 2001
From: ronitjadhav <ronit.jadhav@camptocamp.com>
Date: Tue, 9 Apr 2024 12:56:56 +0200
Subject: [PATCH 3/6] Added component to load layers from a OGC API service

---
 jest.preset.js                                |   2 +-
 .../add-layer-from-ogc-api.component.css      |   0
 .../add-layer-from-ogc-api.component.html     |  36 ++++++
 .../add-layer-from-ogc-api.component.spec.ts  | 112 ++++++++++++++++++
 .../add-layer-from-ogc-api.component.ts       |  65 ++++++++++
 .../feature/map/src/lib/feature-map.module.ts |   2 +
 .../layers-panel/layers-panel.component.html  |   5 +
 .../src/lib/map-context/map-context.model.ts  |   8 ++
 .../lib/map-context/map-context.service.ts    |   9 ++
 package-lock.json                             |   8 +-
 package.json                                  |   2 +-
 translations/de.json                          |   2 +
 translations/en.json                          |   2 +
 translations/es.json                          |   2 +
 translations/fr.json                          |   2 +
 translations/it.json                          |   2 +
 translations/nl.json                          |   2 +
 translations/pt.json                          |   2 +
 translations/sk.json                          |   2 +
 19 files changed, 259 insertions(+), 6 deletions(-)
 create mode 100644 libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.css
 create mode 100644 libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.html
 create mode 100644 libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.spec.ts
 create mode 100644 libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.ts

diff --git a/jest.preset.js b/jest.preset.js
index 6fa1ea73a1..e7f46ce016 100644
--- a/jest.preset.js
+++ b/jest.preset.js
@@ -5,7 +5,7 @@ module.exports = {
   coverageReporters: ['text'],
   setupFiles: ['jest-canvas-mock'],
   transformIgnorePatterns: [
-    'node_modules/(?!(color-*|ol|@mapbox|@geospatial-sdk|.*.mjs$))',
+    'node_modules/(?!(color-*|ol|@mapbox|@geospatial-sdk|@camptocamp/ogc-client|.*.mjs$))',
   ],
   transform: {
     '^.+\\.(ts|mjs|js|html)$': [
diff --git a/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.css b/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.css
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.html b/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.html
new file mode 100644
index 0000000000..47d5fed454
--- /dev/null
+++ b/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.html
@@ -0,0 +1,36 @@
+<div class="flex items-center mb-5">
+  <gn-ui-text-input
+    [(value)]="ogcUrl"
+    (valueChange)="urlChange.next($event)"
+    [hint]="'map.ogc.urlInput.hint' | translate"
+    class="w-96"
+  >
+  </gn-ui-text-input>
+</div>
+
+<div *ngIf="errorMessage" class="text-red-500 mt-2">
+  {{ errorMessage }}
+</div>
+
+<div *ngIf="loading">
+  <p class="loading-message" translate>map.loading.service</p>
+</div>
+
+<div *ngIf="!loading && layers.length > 0">
+  <h2 class="font-bold" translate>map.layers.available</h2>
+  <ng-container *ngFor="let layer of layers">
+    <div class="flex items-center justify-between my-2 layer-item-tree">
+      <p class="max-w-xs overflow-hidden overflow-ellipsis whitespace-nowrap">
+        {{ layer }}
+      </p>
+      <gn-ui-button
+        class="layer-add-btn"
+        type="primary"
+        (buttonClick)="addLayer(layer)"
+        extraClass="text-sm !px-2 !py-1"
+        translate
+        ><span translate> map.layer.add </span></gn-ui-button
+      >
+    </div>
+  </ng-container>
+</div>
diff --git a/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.spec.ts b/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.spec.ts
new file mode 100644
index 0000000000..5d506537db
--- /dev/null
+++ b/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.spec.ts
@@ -0,0 +1,112 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing'
+import { AddLayerFromOgcApiComponent } from './add-layer-from-ogc-api.component'
+import { MapFacade } from '../+state/map.facade'
+import { TranslateModule } from '@ngx-translate/core'
+import { NO_ERRORS_SCHEMA } from '@angular/core'
+import { MapContextLayerTypeEnum } from '../map-context/map-context.model'
+
+jest.mock('@camptocamp/ogc-client', () => ({
+  OgcApiEndpoint: class {
+    constructor(private url) {}
+    isReady() {
+      if (this.url.indexOf('error') > -1) {
+        return Promise.reject(new Error('Something went wrong'))
+      }
+      if (this.url.indexOf('wait') > -1) {
+        return new Promise(() => {
+          // do nothing
+        })
+      }
+      return Promise.resolve(this)
+    }
+    get featureCollections() {
+      if (this.url.includes('error')) {
+        return Promise.reject(new Error('Simulated loading error'))
+      }
+      return Promise.resolve(['layer1', 'layer2', 'layer3'])
+    }
+    getCollectionItemsUrl(collectionId) {
+      return Promise.resolve(
+        `http://example.com/collections/${collectionId}/items`
+      )
+    }
+  },
+}))
+
+class MapFacadeMock {
+  addLayer = jest.fn()
+}
+
+describe('AddLayerFromOgcApiComponent', () => {
+  let component: AddLayerFromOgcApiComponent
+  let fixture: ComponentFixture<AddLayerFromOgcApiComponent>
+  let mapFacade: MapFacade
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [TranslateModule.forRoot()],
+      declarations: [AddLayerFromOgcApiComponent],
+      providers: [
+        {
+          provide: MapFacade,
+          useClass: MapFacadeMock,
+        },
+      ],
+      schemas: [NO_ERRORS_SCHEMA],
+    }).compileComponents()
+
+    mapFacade = TestBed.inject(MapFacade)
+    fixture = TestBed.createComponent(AddLayerFromOgcApiComponent)
+    component = fixture.componentInstance
+    fixture.detectChanges()
+  })
+
+  it('should create', () => {
+    expect(component).toBeTruthy()
+    expect(component.errorMessage).toBeFalsy()
+    expect(component.loading).toBe(false)
+    expect(component.layers.length).toBe(0)
+  })
+
+  describe('loadLayers', () => {
+    it('should clear layers if OGC URL is empty', async () => {
+      component.ogcUrl = ''
+      await component.loadLayers()
+      expect(component.layers.length).toBe(0)
+    })
+
+    it('should load layers on valid OGC API service', async () => {
+      component.ogcUrl = 'http://example.com/ogc'
+      await component.loadLayers()
+      expect(component.errorMessage).toBeFalsy()
+      expect(component.loading).toBe(false)
+      expect(component.layers).toEqual(['layer1', 'layer2', 'layer3'])
+    })
+
+    it('should handle errors while loading layers', async () => {
+      component.ogcUrl = 'http://example.com/error'
+      await component.loadLayers()
+      expect(component.errorMessage).toContain('Error loading layers:')
+      expect(component.loading).toBe(false)
+      expect(component.layers.length).toBe(0)
+    })
+  })
+
+  describe('addLayer', () => {
+    beforeEach(async () => {
+      component.ogcUrl = 'http://example.com/ogc'
+      await component.loadLayers()
+      component.addLayer('layer1')
+    })
+
+    it('adds the selected layer to the map facade', () => {
+      expect(mapFacade.addLayer).toHaveBeenCalledWith(
+        expect.objectContaining({
+          name: 'layer1',
+          url: 'http://example.com/collections/layer1/items',
+          type: MapContextLayerTypeEnum.OGCAPI,
+        })
+      )
+    })
+  })
+})
diff --git a/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.ts b/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.ts
new file mode 100644
index 0000000000..b8af0fb4be
--- /dev/null
+++ b/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.ts
@@ -0,0 +1,65 @@
+import { ChangeDetectorRef, Component, OnInit } from '@angular/core'
+import { OgcApiEndpoint } from '@camptocamp/ogc-client'
+import { Subject, debounceTime } from 'rxjs'
+import { MapFacade } from '../+state/map.facade'
+import {
+  MapContextLayerModel,
+  MapContextLayerTypeEnum,
+} from '../map-context/map-context.model'
+
+@Component({
+  selector: 'gn-ui-add-layer-from-ogc-api',
+  templateUrl: './add-layer-from-ogc-api.component.html',
+  styleUrls: ['./add-layer-from-ogc-api.component.css'],
+})
+export class AddLayerFromOgcApiComponent implements OnInit {
+  urlChange = new Subject<string>()
+  ogcUrl = ''
+  layerUrl = ''
+  loading = false
+  layers: string[] = []
+  ogcEndpoint: OgcApiEndpoint = null
+  errorMessage: string | null = null
+
+  constructor(
+    private mapFacade: MapFacade,
+    private changeDetectorRef: ChangeDetectorRef
+  ) {}
+
+  ngOnInit() {
+    this.urlChange.pipe(debounceTime(700)).subscribe(() => this.loadLayers())
+  }
+
+  async loadLayers() {
+    this.errorMessage = null
+    try {
+      this.loading = true
+      if (this.ogcUrl.trim() === '') {
+        this.layers = []
+        return
+      }
+      this.ogcEndpoint = await new OgcApiEndpoint(this.ogcUrl)
+
+      // Currently only supports feature collections
+      this.layers = await this.ogcEndpoint.featureCollections
+    } catch (error) {
+      const err = error as Error
+      this.layers = []
+      this.errorMessage = 'Error loading layers: ' + err.message
+    } finally {
+      this.loading = false
+      this.changeDetectorRef.markForCheck()
+    }
+  }
+
+  async addLayer(layer) {
+    this.layerUrl = await this.ogcEndpoint.getCollectionItemsUrl(layer)
+
+    const layerToAdd: MapContextLayerModel = {
+      name: layer,
+      url: this.layerUrl,
+      type: MapContextLayerTypeEnum.OGCAPI,
+    }
+    this.mapFacade.addLayer({ ...layerToAdd, title: layer })
+  }
+}
diff --git a/libs/feature/map/src/lib/feature-map.module.ts b/libs/feature/map/src/lib/feature-map.module.ts
index 8365e02f8f..740b097ee3 100644
--- a/libs/feature/map/src/lib/feature-map.module.ts
+++ b/libs/feature/map/src/lib/feature-map.module.ts
@@ -25,6 +25,7 @@ import { AddLayerFromFileComponent } from './add-layer-from-file/add-layer-from-
 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'
+import { AddLayerFromOgcApiComponent } from './add-layer-from-ogc-api/add-layer-from-ogc-api.component'
 
 @NgModule({
   declarations: [
@@ -38,6 +39,7 @@ import { GEOCODING_PROVIDER, GeocodingProvider } from './geocoding.service'
     AddLayerFromFileComponent,
     AddLayerFromWfsComponent,
     GeocodingComponent,
+    AddLayerFromOgcApiComponent,
   ],
   exports: [
     MapContextComponent,
diff --git a/libs/feature/map/src/lib/layers-panel/layers-panel.component.html b/libs/feature/map/src/lib/layers-panel/layers-panel.component.html
index 199dcaa606..267be0cf82 100644
--- a/libs/feature/map/src/lib/layers-panel/layers-panel.component.html
+++ b/libs/feature/map/src/lib/layers-panel/layers-panel.component.html
@@ -40,6 +40,11 @@
           <gn-ui-add-layer-from-wfs></gn-ui-add-layer-from-wfs>
         </div>
       </mat-tab>
+      <mat-tab [label]="'map.add.layer.ogc.api' | translate" bodyClass="h-full">
+        <div class="p-3">
+          <gn-ui-add-layer-from-ogc-api></gn-ui-add-layer-from-ogc-api>
+        </div>
+      </mat-tab>
       <mat-tab [label]="'map.add.layer.file' | translate" bodyClass="h-full">
         <div class="p-3">
           <gn-ui-add-layer-from-file></gn-ui-add-layer-from-file>
diff --git a/libs/feature/map/src/lib/map-context/map-context.model.ts b/libs/feature/map/src/lib/map-context/map-context.model.ts
index 7998d4bfb7..12f07631ee 100644
--- a/libs/feature/map/src/lib/map-context/map-context.model.ts
+++ b/libs/feature/map/src/lib/map-context/map-context.model.ts
@@ -8,6 +8,7 @@ export enum MapContextLayerTypeEnum {
   WMTS = 'wmts',
   WFS = 'wfs',
   GEOJSON = 'geojson',
+  OGCAPI = 'ogcapi',
 }
 
 export interface MapContextModel {
@@ -33,6 +34,12 @@ interface MapContextLayerWfsModel {
   name: string
 }
 
+export interface MapContextLayerOgcapiModel {
+  type: 'ogcapi'
+  url: string
+  name: string
+}
+
 interface LayerXyzModel {
   type: 'xyz'
   name?: string
@@ -70,6 +77,7 @@ export type MapContextLayerModel =
   | MapContextLayerWfsModel
   | MapContextLayerXyzModel
   | MapContextLayerGeojsonModel
+  | MapContextLayerOgcapiModel
 
 export interface MapContextViewModel {
   center?: Coordinate // expressed in long/lat (EPSG:4326)
diff --git a/libs/feature/map/src/lib/map-context/map-context.service.ts b/libs/feature/map/src/lib/map-context/map-context.service.ts
index 1ed587b7c5..5e723badbe 100644
--- a/libs/feature/map/src/lib/map-context/map-context.service.ts
+++ b/libs/feature/map/src/lib/map-context/map-context.service.ts
@@ -77,6 +77,15 @@ export class MapContextService {
     const { type } = layerModel
     const style = this.styleService.styles.default
     switch (type) {
+      case MapContextLayerTypeEnum.OGCAPI:
+        return new VectorLayer({
+          source: new VectorSource({
+            format: new GeoJSON(),
+            url: layerModel.url,
+          }),
+          style,
+        })
+
       case MapContextLayerTypeEnum.XYZ:
         return new TileLayer({
           source: new XYZ({
diff --git a/package-lock.json b/package-lock.json
index 47e9e1fc2d..195668fdca 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -23,7 +23,7 @@
         "@angular/router": "16.1.7",
         "@bartholomej/ngx-translate-extract": "^8.0.2",
         "@biesbjerg/ngx-translate-extract-marker": "^1.0.0",
-        "@camptocamp/ogc-client": "^1.1.0-RC.2",
+        "@camptocamp/ogc-client": "^1.1.0-RC.3",
         "@geospatial-sdk/geocoding": "^0.0.5-alpha.2",
         "@ltd/j-toml": "~1.35.2",
         "@messageformat/core": "^3.0.1",
@@ -3651,9 +3651,9 @@
       "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
     },
     "node_modules/@camptocamp/ogc-client": {
-      "version": "1.1.0-RC.2",
-      "resolved": "https://registry.npmjs.org/@camptocamp/ogc-client/-/ogc-client-1.1.0-RC.2.tgz",
-      "integrity": "sha512-SGmkweVfY8vomjy9K7apI8HnKDhXwsEnh8QIxCqOAR6csbrU2Dv4FQftd2xMicMIuRt/bMBxjwF2fBqKoTKchA==",
+      "version": "1.1.0-RC.3",
+      "resolved": "https://registry.npmjs.org/@camptocamp/ogc-client/-/ogc-client-1.1.0-RC.3.tgz",
+      "integrity": "sha512-XZJwp0vxTQGtJD3t4GdTHJDLTidlPmv0sBvXskEt0A0cmrdaGUgBqr8KPeDfhjZfq99WFcXv/Gb3+hQXA0+LmQ==",
       "dependencies": {
         "@rgrove/parse-xml": "^4.1.0"
       },
diff --git a/package.json b/package.json
index ca86c173c3..bf37f4ae1b 100644
--- a/package.json
+++ b/package.json
@@ -58,7 +58,7 @@
     "@angular/router": "16.1.7",
     "@bartholomej/ngx-translate-extract": "^8.0.2",
     "@biesbjerg/ngx-translate-extract-marker": "^1.0.0",
-    "@camptocamp/ogc-client": "^1.1.0-RC.2",
+    "@camptocamp/ogc-client": "^1.1.0-RC.3",
     "@geospatial-sdk/geocoding": "^0.0.5-alpha.2",
     "@ltd/j-toml": "~1.35.2",
     "@messageformat/core": "^3.0.1",
diff --git a/translations/de.json b/translations/de.json
index f8324d3d2e..a1fbcf03bc 100644
--- a/translations/de.json
+++ b/translations/de.json
@@ -177,6 +177,7 @@
   "map.add.layer": "Eine Ebene hinzufügen",
   "map.add.layer.catalog": "Aus dem Katalog",
   "map.add.layer.file": "Aus einer Datei",
+  "map.add.layer.ogc.api": "",
   "map.add.layer.wfs": "Aus WFS",
   "map.add.layer.wms": "Aus WMS",
   "map.addFromFile.placeholder": "Klicke hier oder ziehe eine Datei herein",
@@ -188,6 +189,7 @@
   "map.loading.data": "Kartendaten werden geladen...",
   "map.loading.service": "Dienst wird geladen...",
   "map.navigation.message": "Bitte verwenden Sie STRG + Maus (oder zwei Finger auf einem Mobilgerät), um die Karte zu navigieren",
+  "map.ogc.urlInput.hint": "",
   "map.select.layer": "Datenquelle",
   "map.wfs.urlInput.hint": "Geben Sie die WFS URL ein",
   "map.wms.urlInput.hint": "Geben Sie die WMS URL ein",
diff --git a/translations/en.json b/translations/en.json
index dd45df888c..c43d66ee41 100644
--- a/translations/en.json
+++ b/translations/en.json
@@ -177,6 +177,7 @@
   "map.add.layer": "Add a layer",
   "map.add.layer.catalog": "From the catalog",
   "map.add.layer.file": "From a file",
+  "map.add.layer.ogc.api": "From OGC API",
   "map.add.layer.wfs": "From WFS",
   "map.add.layer.wms": "From WMS",
   "map.addFromFile.placeholder": "Click or drop a file here",
@@ -188,6 +189,7 @@
   "map.loading.data": "Loading map data...",
   "map.loading.service": "Loading service...",
   "map.navigation.message": "Please use CTRL + mouse (or two fingers on mobile) to navigate the map",
+  "map.ogc.urlInput.hint": "Enter OGC API service URL",
   "map.select.layer": "Data source",
   "map.wfs.urlInput.hint": "Enter WFS service URL",
   "map.wms.urlInput.hint": "Enter WMS service URL",
diff --git a/translations/es.json b/translations/es.json
index 9f82e0ab23..8552c95c54 100644
--- a/translations/es.json
+++ b/translations/es.json
@@ -177,6 +177,7 @@
   "map.add.layer": "",
   "map.add.layer.catalog": "",
   "map.add.layer.file": "",
+  "map.add.layer.ogc.api": "",
   "map.add.layer.wfs": "",
   "map.add.layer.wms": "",
   "map.addFromFile.placeholder": "",
@@ -188,6 +189,7 @@
   "map.loading.data": "",
   "map.loading.service": "",
   "map.navigation.message": "",
+  "map.ogc.urlInput.hint": "",
   "map.select.layer": "",
   "map.wfs.urlInput.hint": "",
   "map.wms.urlInput.hint": "",
diff --git a/translations/fr.json b/translations/fr.json
index 799605a027..a0e7906c03 100644
--- a/translations/fr.json
+++ b/translations/fr.json
@@ -177,6 +177,7 @@
   "map.add.layer": "Ajouter une couche",
   "map.add.layer.catalog": "Du catalogue",
   "map.add.layer.file": "À partir d'un fichier",
+  "map.add.layer.ogc.api": "",
   "map.add.layer.wfs": "Depuis un service WFS",
   "map.add.layer.wms": "Depuis un service WMS",
   "map.addFromFile.placeholder": "Cliquez ou déposez un fichier ici",
@@ -188,6 +189,7 @@
   "map.loading.data": "Chargement des données...",
   "map.loading.service": "Chargement du service...",
   "map.navigation.message": "Veuillez utiliser CTRL + souris (ou deux doigts sur mobile) pour naviguer sur la carte",
+  "map.ogc.urlInput.hint": "",
   "map.select.layer": "Source de données",
   "map.wfs.urlInput.hint": "Entrez l'URL du service WFS",
   "map.wms.urlInput.hint": "Entrez l'URL du service WMS",
diff --git a/translations/it.json b/translations/it.json
index 9b03fabaca..fd48ddb71a 100644
--- a/translations/it.json
+++ b/translations/it.json
@@ -177,6 +177,7 @@
   "map.add.layer": "Aggiungere un layer",
   "map.add.layer.catalog": "Dal catalogo",
   "map.add.layer.file": "Da un file",
+  "map.add.layer.ogc.api": "",
   "map.add.layer.wfs": "Da un WFS",
   "map.add.layer.wms": "Da un WMS",
   "map.addFromFile.placeholder": "Clicca o trascina un file qui",
@@ -188,6 +189,7 @@
   "map.loading.data": "Caricamento dati...",
   "map.loading.service": "Caricamento del servizio...",
   "map.navigation.message": "Si prega di utilizzare CTRL + mouse (o due dita su mobile) per navigare sulla mappa",
+  "map.ogc.urlInput.hint": "",
   "map.select.layer": "Sorgente dati",
   "map.wfs.urlInput.hint": "Inserisci URL del servizio WFS",
   "map.wms.urlInput.hint": "Inserisci URL del servizio WMS",
diff --git a/translations/nl.json b/translations/nl.json
index 64ee837518..449bb5d88e 100644
--- a/translations/nl.json
+++ b/translations/nl.json
@@ -177,6 +177,7 @@
   "map.add.layer": "",
   "map.add.layer.catalog": "",
   "map.add.layer.file": "",
+  "map.add.layer.ogc.api": "",
   "map.add.layer.wfs": "",
   "map.add.layer.wms": "",
   "map.addFromFile.placeholder": "",
@@ -188,6 +189,7 @@
   "map.loading.data": "",
   "map.loading.service": "",
   "map.navigation.message": "",
+  "map.ogc.urlInput.hint": "",
   "map.select.layer": "",
   "map.wfs.urlInput.hint": "",
   "map.wms.urlInput.hint": "",
diff --git a/translations/pt.json b/translations/pt.json
index 86fe64024b..f0c130712c 100644
--- a/translations/pt.json
+++ b/translations/pt.json
@@ -177,6 +177,7 @@
   "map.add.layer": "",
   "map.add.layer.catalog": "",
   "map.add.layer.file": "",
+  "map.add.layer.ogc.api": "",
   "map.add.layer.wfs": "",
   "map.add.layer.wms": "",
   "map.addFromFile.placeholder": "",
@@ -188,6 +189,7 @@
   "map.loading.data": "",
   "map.loading.service": "",
   "map.navigation.message": "",
+  "map.ogc.urlInput.hint": "",
   "map.select.layer": "",
   "map.wfs.urlInput.hint": "",
   "map.wms.urlInput.hint": "",
diff --git a/translations/sk.json b/translations/sk.json
index db63a71d01..a5c07120a2 100644
--- a/translations/sk.json
+++ b/translations/sk.json
@@ -177,6 +177,7 @@
   "map.add.layer": "Pridať vrstvu",
   "map.add.layer.catalog": "Z katalógu",
   "map.add.layer.file": "Zo súboru",
+  "map.add.layer.ogc.api": "",
   "map.add.layer.wfs": "Z WFS",
   "map.add.layer.wms": "Z WMS",
   "map.addFromFile.placeholder": "Kliknite na tlačidlo alebo sem vložte súbor",
@@ -188,6 +189,7 @@
   "map.loading.data": "Načítavanie dát mapy...",
   "map.loading.service": "Načítavanie služieb...",
   "map.navigation.message": "Použite prosím CTRL + myš (alebo dva prsty na mobilnom zariadení) na navigáciu po mape",
+  "map.ogc.urlInput.hint": "",
   "map.select.layer": "Zdroj dát",
   "map.wfs.urlInput.hint": "Zadajte URL adresu služby WFS",
   "map.wms.urlInput.hint": "Zadajte URL adresu služby WMS",

From 8baab122af9f05b02c058aba1c05243a3460df3b Mon Sep 17 00:00:00 2001
From: ronitjadhav <ronit.jadhav@camptocamp.com>
Date: Wed, 10 Apr 2024 15:29:12 +0200
Subject: [PATCH 4/6] fix unit tests

---
 .../map-context/map-context.service.spec.ts   | 26 +++++++++++++++++--
 .../src/lib/utils/map-utils.service.spec.ts   | 10 +++----
 .../lib/map-view/map-view.component.spec.ts   |  3 ++-
 .../data-fetcher/src/lib/readers/csv.spec.ts  |  8 ++++++
 .../src/lib/readers/excel.spec.ts             |  8 ++++++
 .../src/lib/readers/geojson.spec.ts           |  8 ++++++
 .../data-fetcher/src/lib/readers/gml.spec.ts  |  8 ++++++
 .../data-fetcher/src/lib/readers/json.spec.ts |  8 ++++++
 8 files changed, 71 insertions(+), 8 deletions(-)

diff --git a/libs/feature/map/src/lib/map-context/map-context.service.spec.ts b/libs/feature/map/src/lib/map-context/map-context.service.spec.ts
index 544a772444..9de1279924 100644
--- a/libs/feature/map/src/lib/map-context/map-context.service.spec.ts
+++ b/libs/feature/map/src/lib/map-context/map-context.service.spec.ts
@@ -59,6 +59,28 @@ jest.mock('@camptocamp/ogc-client', () => ({
       })
     }
   },
+  WfsEndpoint: class {
+    constructor(private url) {}
+    isReady() {
+      return Promise.resolve({
+        getLayerByName: (name) => {
+          if (this.url.indexOf('error') > -1) {
+            throw new Error('Something went wrong')
+          }
+          return {
+            name,
+            latLonBoundingBox: [1.33, 48.81, 4.3, 51.1],
+          }
+        },
+        getSingleFeatureTypeName: () => {
+          return 'ms:commune_actuelle_3857'
+        },
+        getFeatureUrl: () => {
+          return 'https://www.geograndest.fr/geoserver/region-grand-est/ows?service=WFS&version=1.1.0&request=GetFeature&outputFormat=application%2Fjson&typename=ms%3Acommune_actuelle_3857&srsname=EPSG%3A3857&bbox=10%2C20%2C30%2C40%2CEPSG%3A3857&maxFeatures=10000'
+        },
+      })
+    }
+  },
 }))
 
 describe('MapContextService', () => {
@@ -130,7 +152,7 @@ describe('MapContextService', () => {
         const urls = source.getUrls()
         expect(urls.length).toBe(1)
         expect(urls[0]).toBe(
-          'https://www.geograndest.fr/geoserver/region-grand-est/ows'
+          'https://www.geograndest.fr/geoserver/region-grand-est/ows?REQUEST=GetCapabilities&SERVICE=WMS'
         )
       })
       it('set WMS gutter of 20px', () => {
@@ -342,7 +364,7 @@ describe('MapContextService', () => {
         const layerWMSUrl = (map.getLayers().item(1) as TileLayer<TileWMS>)
           .getSource()
           .getUrls()[0]
-        expect(layerWMSUrl).toEqual('https://some-wms-server/')
+        expect(layerWMSUrl).toEqual('https://some-wms-server')
       })
       it('add one WFS layer from config on top of baselayer', () => {
         const layerWFSSource = (
diff --git a/libs/feature/map/src/lib/utils/map-utils.service.spec.ts b/libs/feature/map/src/lib/utils/map-utils.service.spec.ts
index 3f3415cb84..50afc5f787 100644
--- a/libs/feature/map/src/lib/utils/map-utils.service.spec.ts
+++ b/libs/feature/map/src/lib/utils/map-utils.service.spec.ts
@@ -46,17 +46,17 @@ jest.mock('@camptocamp/ogc-client', () => ({
             boundingBoxes = {}
           } else if (name.includes('4326')) {
             boundingBoxes = {
-              'EPSG:4326': ['1', '2.6', '3.3', '4.2'],
-              'CRS:84': ['2.3', '50.6', '2.8', '50.9'],
+              'EPSG:4326': [1, 2.6, 3.3, 4.2],
+              'CRS:84': [2.3, 50.6, 2.8, 50.9],
             }
           } else if (name.includes('2154')) {
             boundingBoxes = {
-              'EPSG:2154': ['650796.4', '7060330.6', '690891.3', '7090402.2'],
+              'EPSG:2154': [650796.4, 7060330.6, 690891.3, 7090402.2],
             }
           } else {
             boundingBoxes = {
-              'CRS:84': ['2.3', '50.6', '2.8', '50.9'],
-              'EPSG:2154': ['650796.4', '7060330.6', '690891.3', '7090402.2'],
+              'CRS:84': [2.3, 50.6, 2.8, 50.9],
+              'EPSG:2154': [650796.4, 7060330.6, 690891.3, 7090402.2],
             }
           }
           return {
diff --git a/libs/feature/record/src/lib/map-view/map-view.component.spec.ts b/libs/feature/record/src/lib/map-view/map-view.component.spec.ts
index 89288877dc..90167937f7 100644
--- a/libs/feature/record/src/lib/map-view/map-view.component.spec.ts
+++ b/libs/feature/record/src/lib/map-view/map-view.component.spec.ts
@@ -451,8 +451,9 @@ describe('MapViewComponent', () => {
         expect(mapComponent.context).toEqual({
           layers: [
             {
+              name: 'orthophoto',
               type: 'wmts',
-              options: expect.any(Object),
+              url: 'http://abcd.com/wmts',
             },
           ],
           view: expect.any(Object),
diff --git a/libs/util/data-fetcher/src/lib/readers/csv.spec.ts b/libs/util/data-fetcher/src/lib/readers/csv.spec.ts
index d182b0bb07..c8f74272f1 100644
--- a/libs/util/data-fetcher/src/lib/readers/csv.spec.ts
+++ b/libs/util/data-fetcher/src/lib/readers/csv.spec.ts
@@ -7,6 +7,14 @@ afterEach(() => {
   jest.clearAllMocks()
 })
 
+//todo: fix this test, to run without mocking useCache
+jest.mock('@camptocamp/ogc-client', () => ({
+  useCache: jest.fn(async (factory) =>
+    JSON.parse(JSON.stringify(await factory()))
+  ),
+  sharedFetch: jest.fn((url) => global.fetch(url)),
+}))
+
 describe('CSV parsing', () => {
   describe('parseCsv', () => {
     describe('valid CSV with id', () => {
diff --git a/libs/util/data-fetcher/src/lib/readers/excel.spec.ts b/libs/util/data-fetcher/src/lib/readers/excel.spec.ts
index f2649022ae..b2830bdd56 100644
--- a/libs/util/data-fetcher/src/lib/readers/excel.spec.ts
+++ b/libs/util/data-fetcher/src/lib/readers/excel.spec.ts
@@ -12,6 +12,14 @@ const sampleXls = fs.readFileSync(
   null
 )
 
+//todo: fix this test, to run without mocking useCache
+jest.mock('@camptocamp/ogc-client', () => ({
+  useCache: jest.fn(async (factory) =>
+    JSON.parse(JSON.stringify(await factory()))
+  ),
+  sharedFetch: jest.fn((url) => global.fetch(url)),
+}))
+
 describe('Excel parsing', () => {
   describe('parseExcel', () => {
     describe('.xslx file', () => {
diff --git a/libs/util/data-fetcher/src/lib/readers/geojson.spec.ts b/libs/util/data-fetcher/src/lib/readers/geojson.spec.ts
index 4122dd1b9b..63287e7be7 100644
--- a/libs/util/data-fetcher/src/lib/readers/geojson.spec.ts
+++ b/libs/util/data-fetcher/src/lib/readers/geojson.spec.ts
@@ -3,6 +3,14 @@ import fetchMock from 'fetch-mock-jest'
 import path from 'path'
 import fs from 'fs/promises'
 
+//todo: fix this test, to run without mocking useCache
+jest.mock('@camptocamp/ogc-client', () => ({
+  useCache: jest.fn(async (factory) =>
+    JSON.parse(JSON.stringify(await factory()))
+  ),
+  sharedFetch: jest.fn((url) => global.fetch(url)),
+}))
+
 describe('geojson parsing', () => {
   describe('parseGeojson', () => {
     describe('Valid Geojson (array of features)', () => {
diff --git a/libs/util/data-fetcher/src/lib/readers/gml.spec.ts b/libs/util/data-fetcher/src/lib/readers/gml.spec.ts
index e8d0f09948..ed5076c44e 100644
--- a/libs/util/data-fetcher/src/lib/readers/gml.spec.ts
+++ b/libs/util/data-fetcher/src/lib/readers/gml.spec.ts
@@ -6,6 +6,14 @@ import fetchMock from 'fetch-mock-jest'
 import path from 'path'
 import fs from 'fs/promises'
 
+//todo: fix this test, to run without mocking useCache
+jest.mock('@camptocamp/ogc-client', () => ({
+  useCache: jest.fn(async (factory) =>
+    JSON.parse(JSON.stringify(await factory()))
+  ),
+  sharedFetch: jest.fn((url) => global.fetch(url)),
+}))
+
 const singleFeatureValidGml = `<?xml version='1.0' encoding="UTF-8" ?>
 <wfs:FeatureCollection
   xmlns:ms="http://mapserver.gis.umn.edu/mapserver"
diff --git a/libs/util/data-fetcher/src/lib/readers/json.spec.ts b/libs/util/data-fetcher/src/lib/readers/json.spec.ts
index 6fd27f4393..8b2ccf6641 100644
--- a/libs/util/data-fetcher/src/lib/readers/json.spec.ts
+++ b/libs/util/data-fetcher/src/lib/readers/json.spec.ts
@@ -3,6 +3,14 @@ import fetchMock from 'fetch-mock-jest'
 import path from 'path'
 import fs from 'fs/promises'
 
+//todo: fix this test, to run without mocking useCache
+jest.mock('@camptocamp/ogc-client', () => ({
+  useCache: jest.fn(async (factory) =>
+    JSON.parse(JSON.stringify(await factory()))
+  ),
+  sharedFetch: jest.fn((url) => global.fetch(url)),
+}))
+
 describe('json parsing', () => {
   describe('parseJson', () => {
     describe('valid JSON with id', () => {

From 22f62e09380b9b6857ed966be8f88a2da95c1c95 Mon Sep 17 00:00:00 2001
From: ronitjadhav <ronit.jadhav@camptocamp.com>
Date: Mon, 15 Apr 2024 15:52:53 +0200
Subject: [PATCH 5/6] Made add-layer-from-ogc-api standalone component

---
 .../add-layer-from-ogc-api.component.spec.ts  | 34 ++----------------
 .../add-layer-from-ogc-api.component.ts       | 35 +++++++++++++------
 .../feature/map/src/lib/feature-map.module.ts |  2 +-
 .../layers-panel/layers-panel.component.html  |  5 ++-
 .../layers-panel/layers-panel.component.ts    |  5 +++
 5 files changed, 37 insertions(+), 44 deletions(-)

diff --git a/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.spec.ts b/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.spec.ts
index 5d506537db..ab58f79785 100644
--- a/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.spec.ts
+++ b/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.spec.ts
@@ -33,29 +33,17 @@ jest.mock('@camptocamp/ogc-client', () => ({
   },
 }))
 
-class MapFacadeMock {
-  addLayer = jest.fn()
-}
-
 describe('AddLayerFromOgcApiComponent', () => {
   let component: AddLayerFromOgcApiComponent
   let fixture: ComponentFixture<AddLayerFromOgcApiComponent>
-  let mapFacade: MapFacade
 
   beforeEach(async () => {
     await TestBed.configureTestingModule({
-      imports: [TranslateModule.forRoot()],
-      declarations: [AddLayerFromOgcApiComponent],
-      providers: [
-        {
-          provide: MapFacade,
-          useClass: MapFacadeMock,
-        },
-      ],
+      imports: [TranslateModule.forRoot(), AddLayerFromOgcApiComponent],
+      declarations: [],
       schemas: [NO_ERRORS_SCHEMA],
     }).compileComponents()
 
-    mapFacade = TestBed.inject(MapFacade)
     fixture = TestBed.createComponent(AddLayerFromOgcApiComponent)
     component = fixture.componentInstance
     fixture.detectChanges()
@@ -91,22 +79,4 @@ describe('AddLayerFromOgcApiComponent', () => {
       expect(component.layers.length).toBe(0)
     })
   })
-
-  describe('addLayer', () => {
-    beforeEach(async () => {
-      component.ogcUrl = 'http://example.com/ogc'
-      await component.loadLayers()
-      component.addLayer('layer1')
-    })
-
-    it('adds the selected layer to the map facade', () => {
-      expect(mapFacade.addLayer).toHaveBeenCalledWith(
-        expect.objectContaining({
-          name: 'layer1',
-          url: 'http://example.com/collections/layer1/items',
-          type: MapContextLayerTypeEnum.OGCAPI,
-        })
-      )
-    })
-  })
 })
diff --git a/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.ts b/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.ts
index b8af0fb4be..6010111cd3 100644
--- a/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.ts
+++ b/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.ts
@@ -1,33 +1,48 @@
-import { ChangeDetectorRef, Component, OnInit } from '@angular/core'
+import {
+  Component,
+  OnInit,
+  Output,
+  EventEmitter,
+  ChangeDetectionStrategy,
+  Input,
+  ChangeDetectorRef,
+} from '@angular/core'
 import { OgcApiEndpoint } from '@camptocamp/ogc-client'
 import { Subject, debounceTime } from 'rxjs'
-import { MapFacade } from '../+state/map.facade'
 import {
   MapContextLayerModel,
   MapContextLayerTypeEnum,
 } from '../map-context/map-context.model'
+import { TranslateModule } from '@ngx-translate/core'
+import { UiInputsModule } from '@geonetwork-ui/ui/inputs'
+import { CommonModule } from '@angular/common'
+import { MapLayer } from '../+state/map.models'
 
 @Component({
   selector: 'gn-ui-add-layer-from-ogc-api',
   templateUrl: './add-layer-from-ogc-api.component.html',
   styleUrls: ['./add-layer-from-ogc-api.component.css'],
+  standalone: true,
+  imports: [CommonModule, TranslateModule, UiInputsModule],
 })
 export class AddLayerFromOgcApiComponent implements OnInit {
+  @Input() ogcUrl: string
+  @Output() layerAdded = new EventEmitter<MapLayer>()
+
   urlChange = new Subject<string>()
-  ogcUrl = ''
   layerUrl = ''
   loading = false
   layers: string[] = []
   ogcEndpoint: OgcApiEndpoint = null
   errorMessage: string | null = null
 
-  constructor(
-    private mapFacade: MapFacade,
-    private changeDetectorRef: ChangeDetectorRef
-  ) {}
+  constructor(private changeDetectorRef: ChangeDetectorRef) {}
 
   ngOnInit() {
-    this.urlChange.pipe(debounceTime(700)).subscribe(() => this.loadLayers())
+    this.urlChange.pipe(debounceTime(700)).subscribe(() => {
+      this.loadLayers()
+      this.changeDetectorRef.detectChanges() // manually trigger change detection
+    })
   }
 
   async loadLayers() {
@@ -52,7 +67,7 @@ export class AddLayerFromOgcApiComponent implements OnInit {
     }
   }
 
-  async addLayer(layer) {
+  async addLayer(layer: string) {
     this.layerUrl = await this.ogcEndpoint.getCollectionItemsUrl(layer)
 
     const layerToAdd: MapContextLayerModel = {
@@ -60,6 +75,6 @@ export class AddLayerFromOgcApiComponent implements OnInit {
       url: this.layerUrl,
       type: MapContextLayerTypeEnum.OGCAPI,
     }
-    this.mapFacade.addLayer({ ...layerToAdd, title: layer })
+    this.layerAdded.emit({ ...layerToAdd, title: layer })
   }
 }
diff --git a/libs/feature/map/src/lib/feature-map.module.ts b/libs/feature/map/src/lib/feature-map.module.ts
index 740b097ee3..f2db017ee2 100644
--- a/libs/feature/map/src/lib/feature-map.module.ts
+++ b/libs/feature/map/src/lib/feature-map.module.ts
@@ -39,7 +39,6 @@ import { AddLayerFromOgcApiComponent } from './add-layer-from-ogc-api/add-layer-
     AddLayerFromFileComponent,
     AddLayerFromWfsComponent,
     GeocodingComponent,
-    AddLayerFromOgcApiComponent,
   ],
   exports: [
     MapContextComponent,
@@ -61,6 +60,7 @@ import { AddLayerFromOgcApiComponent } from './add-layer-from-ogc-api/add-layer-
     EffectsModule.forFeature([MapEffects]),
     UiElementsModule,
     UiInputsModule,
+    AddLayerFromOgcApiComponent,
   ],
   providers: [
     {
diff --git a/libs/feature/map/src/lib/layers-panel/layers-panel.component.html b/libs/feature/map/src/lib/layers-panel/layers-panel.component.html
index 267be0cf82..ce5d06cdfb 100644
--- a/libs/feature/map/src/lib/layers-panel/layers-panel.component.html
+++ b/libs/feature/map/src/lib/layers-panel/layers-panel.component.html
@@ -42,7 +42,10 @@
       </mat-tab>
       <mat-tab [label]="'map.add.layer.ogc.api' | translate" bodyClass="h-full">
         <div class="p-3">
-          <gn-ui-add-layer-from-ogc-api></gn-ui-add-layer-from-ogc-api>
+          <gn-ui-add-layer-from-ogc-api
+            [ogcUrl]="ogcUrl"
+            (layerAdded)="addLayer($event)"
+          ></gn-ui-add-layer-from-ogc-api>
         </div>
       </mat-tab>
       <mat-tab [label]="'map.add.layer.file' | translate" bodyClass="h-full">
diff --git a/libs/feature/map/src/lib/layers-panel/layers-panel.component.ts b/libs/feature/map/src/lib/layers-panel/layers-panel.component.ts
index 389f8561d0..f0cb32051d 100644
--- a/libs/feature/map/src/lib/layers-panel/layers-panel.component.ts
+++ b/libs/feature/map/src/lib/layers-panel/layers-panel.component.ts
@@ -9,9 +9,14 @@ import { MapFacade } from '../+state/map.facade'
 })
 export class LayersPanelComponent {
   layers$ = this.mapFacade.layers$
+  ogcUrl = ''
   constructor(private mapFacade: MapFacade) {}
 
   deleteLayer(index: number) {
     this.mapFacade.removeLayer(index)
   }
+
+  addLayer(layer) {
+    this.mapFacade.addLayer(layer)
+  }
 }

From a3e1040c5e23002a2d3fb28acf0190b532368924 Mon Sep 17 00:00:00 2001
From: Olivia Guyot <olivia.guyot@camptocamp.com>
Date: Tue, 16 Apr 2024 09:50:07 +0200
Subject: [PATCH 6/6] chore: upgrade ogc-client in npm package

---
 docs/guide/custom-app.md | 1 -
 package/package.json     | 2 +-
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/docs/guide/custom-app.md b/docs/guide/custom-app.md
index d2d3bb5bc4..ff5b4a02c6 100644
--- a/docs/guide/custom-app.md
+++ b/docs/guide/custom-app.md
@@ -118,7 +118,6 @@ the following settings to the `angular.json` file at the root of your project:
             "@rgrove/parse-xml",
             "@messageformat/core",
             "rbush",
-            "@camptocamp/ogc-client",
             "pbf",
             "alasql"
             // add dependencies here if other warnings show up and you want to hide them
diff --git a/package/package.json b/package/package.json
index cf48675b41..40675b1994 100644
--- a/package/package.json
+++ b/package/package.json
@@ -38,7 +38,7 @@
   },
   "dependencies": {
     "@biesbjerg/ngx-translate-extract-marker": "^1.0.0",
-    "@camptocamp/ogc-client": "^0.4.0",
+    "@camptocamp/ogc-client": "^1.1.0-RC.3",
     "@geospatial-sdk/geocoding": "^0.0.5-alpha.2",
     "@ltd/j-toml": "~1.35.2",
     "@messageformat/core": "^3.0.1",