@geogirafe/lib-geoportal 1.1.0-dev.2407028282 → 1.1.0-dev.2407038460

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,6 +12,8 @@ declare class ServerWfs<WfsXmlTypes = XmlTypes> {
12
12
  };
13
13
  initialized: boolean;
14
14
  constructor(name: string, url: string);
15
+ addLayer(layer: string): void;
16
+ removeLayer(layer: string): void;
15
17
  addLayerAttribute(layer: string, name: string, type: string): void;
16
18
  getGeometryColumnNameToFeatureTypes(featureTypes: string[]): Record<string, string[]>;
17
19
  }
@@ -9,11 +9,17 @@ class ServerWfs {
9
9
  this.url = url;
10
10
  this.initialized = false;
11
11
  }
12
- addLayerAttribute(layer, name, type) {
13
- if (!(layer in this.layers)) {
14
- // Layer does not exists yet
12
+ addLayer(layer) {
13
+ if (!this.layers[layer]) {
15
14
  this.layers[layer] = [];
16
15
  }
16
+ }
17
+ removeLayer(layer) {
18
+ delete this.layers[layer];
19
+ delete this.featureTypeToGeometryColumnName[layer];
20
+ }
21
+ addLayerAttribute(layer, name, type) {
22
+ this.addLayer(layer);
17
23
  this.layers[layer].push({
18
24
  name: name,
19
25
  type: type
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "GeoGirafe PSC",
6
6
  "url": "https://doc.geomapfish.dev"
7
7
  },
8
- "version": "1.1.0-dev.2407028282",
8
+ "version": "1.1.0-dev.2407038460",
9
9
  "type": "module",
10
10
  "engines": {
11
11
  "node": ">=20.19.0"
@@ -1 +1 @@
1
- {"version":"1.1.0-dev.2407028282", "build":"2407028282", "date":"25/03/2026"}
1
+ {"version":"1.1.0-dev.2407038460", "build":"2407038460", "date":"25/03/2026"}
@@ -32,9 +32,10 @@ export default class WfsClient<WfsXmlTypes = XmlTypes> {
32
32
  private configMaxFeatures;
33
33
  getServerWfs(): Promise<ServerWfs<WfsXmlTypes>>;
34
34
  protected describeFeatureType(): Promise<ServerWfs<WfsXmlTypes>>;
35
- private describeFeatureTypeInternal;
35
+ private requestDescribeFeatureType;
36
+ private parseDescribeFeatureTypeResponse;
36
37
  private initializeAttribute;
37
- protected manageLayerAttribute(serverWfs: ServerWfs<WfsXmlTypes>, element: Element, featureType: string): boolean;
38
+ protected manageLayerAttribute(serverWfs: ServerWfs<WfsXmlTypes>, element: Element, featureType: string): void;
38
39
  protected validateLayerAttributeType(type: string): boolean;
39
40
  protected getElementToTypeName(xml: Document): Record<string, string>;
40
41
  protected getDescribeFeatureTypeUrl(): string;
@@ -48,7 +48,7 @@ export default class WfsClient {
48
48
  }
49
49
  describeFeatureType() {
50
50
  if (!this.serverWfs) {
51
- this.serverWfs = this.describeFeatureTypeInternal();
51
+ this.serverWfs = this.parseDescribeFeatureTypeResponse();
52
52
  this.serverWfs.catch((error) => {
53
53
  const msg = `WFS server with URL ${this.wfsUrl} could not be initialized. Error: `;
54
54
  console.error(msg, error);
@@ -57,12 +57,15 @@ export default class WfsClient {
57
57
  }
58
58
  return this.serverWfs;
59
59
  }
60
- async describeFeatureTypeInternal() {
61
- const serverWfs = new ServerWfs('', this.wfsUrl);
60
+ async requestDescribeFeatureType() {
62
61
  const url = this.getDescribeFeatureTypeUrl();
63
62
  const response = await fetch(url);
64
63
  const content = await response.text();
65
- const xml = new DOMParser().parseFromString(content, 'text/xml');
64
+ return new DOMParser().parseFromString(content, 'text/xml');
65
+ }
66
+ async parseDescribeFeatureTypeResponse() {
67
+ const serverWfs = new ServerWfs('', this.wfsUrl);
68
+ const xml = await this.requestDescribeFeatureType();
66
69
  // First, find all direct "element" children
67
70
  const elementTypeToName = this.getElementToTypeName(xml);
68
71
  // Then, find all "complexType" elements
@@ -83,48 +86,39 @@ export default class WfsClient {
83
86
  }
84
87
  const featureType = elementTypeToName[typeName];
85
88
  const elements = tag.getElementsByTagName('sequence')[0].getElementsByTagName('element');
86
- let geometryAttributeFound = false;
87
- for (const element of elements) {
88
- if (this.manageLayerAttribute(serverWfs, element, featureType)) {
89
- geometryAttributeFound = true;
89
+ try {
90
+ serverWfs.addLayer(featureType);
91
+ for (const element of elements) {
92
+ this.manageLayerAttribute(serverWfs, element, featureType);
93
+ }
94
+ // If we didn't find any geometry attribute for this featureType, wfs querying won't be possible
95
+ if (!serverWfs.featureTypeToGeometryColumnName[featureType]) {
96
+ throw new Error(`No Geometry column for the type ${featureType}`);
90
97
  }
91
98
  }
92
- // If we didn't find any geometry attribute for this featureType, then we have a problem
93
- // Because the wfs query won't be possible
94
- if (!geometryAttributeFound) {
95
- throw new Error('No Geometry column for the type ' + featureType);
99
+ catch (error) {
100
+ serverWfs.removeLayer(featureType);
101
+ console.error(`Error while parsing feature type ${typeName}: ${error}`);
96
102
  }
97
103
  }
98
104
  manageLayerAttribute(serverWfs, element, featureType) {
99
- let geometryAttributeFound = false;
100
- const type = element.getAttribute('type');
101
- if (type?.startsWith('gml:')) {
102
- // We are on the geometry attribute
103
- const geometryAttributeName = element.getAttribute('name');
104
- if (geometryAttributeName) {
105
- serverWfs.featureTypeToGeometryColumnName[featureType] = geometryAttributeName;
106
- geometryAttributeFound = true;
107
- }
108
- else {
109
- throw new Error('Why is geometryAttributeName null here ?');
110
- }
105
+ const attributeType = element.getAttribute('type');
106
+ const attributeName = element.getAttribute('name');
107
+ if (!attributeName || !attributeType) {
108
+ console.warn(`Error while loading attributes for layer ${featureType}. Attribute name and/or type is missing. Element ignored.`);
109
+ return;
111
110
  }
112
- else {
113
- // We are not on a geometry attribute, but on a normal attribute
114
- // We update the WMS Layer with its attribute information
115
- const attrName = element.getAttribute('name');
116
- const attrType = element.getAttribute('type');
117
- if (!attrName || !attrType) {
118
- console.warn(`Error while loading attribute for layer ${featureType}. Querying or filtering this layer won't work correctly.`);
119
- }
120
- else if (this.validateLayerAttributeType(attrType)) {
121
- serverWfs.addLayerAttribute(featureType, attrName, attrType);
122
- }
123
- else {
124
- console.warn(`Unmanaged layer attribute type: ${attrType} for attribute ${attrName} of featureType ${featureType}. ${attrName} ignored.`);
125
- }
111
+ // Handle the geometry attribute
112
+ if (attributeType.startsWith('gml:')) {
113
+ serverWfs.featureTypeToGeometryColumnName[featureType] = attributeName;
114
+ return;
115
+ }
116
+ // Handle all remaining regular attributes
117
+ if (!this.validateLayerAttributeType(attributeType)) {
118
+ console.warn(`Unmanaged layer attribute type: ${attributeType} for attribute ${attributeName} of featureType ${featureType}. ${attributeName} ignored.`);
119
+ return;
126
120
  }
127
- return geometryAttributeFound;
121
+ serverWfs.addLayerAttribute(featureType, attributeName, attributeType);
128
122
  }
129
123
  validateLayerAttributeType(type) {
130
124
  return xmlTypesStrList.includes(type);
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
- import { vi, afterAll, beforeAll, describe, expect, it } from 'vitest';
2
+ import { vi, afterAll, describe, expect, it, beforeEach } from 'vitest';
3
3
  import { WfsClientMapServer } from './wfsclient';
4
4
  import ServerOgc from '../../models/serverogc';
5
5
  import MockHelper from '../tests/mockhelper';
@@ -9,8 +9,9 @@ describe('WfsClient', () => {
9
9
  let context;
10
10
  let server;
11
11
  let client;
12
- beforeAll(() => {
12
+ beforeEach(() => {
13
13
  context = MockHelper.startMocking();
14
+ vi.resetAllMocks();
14
15
  server = new ServerOgc('testOgcServer', {
15
16
  url: 'https://wms-1.test.url',
16
17
  wfsSupport: true,
@@ -116,4 +117,82 @@ describe('WfsClient', () => {
116
117
  expect(completeOptions[1].geometryName).toBe('geom');
117
118
  });
118
119
  });
120
+ describe('describeFeatureType', () => {
121
+ const xmlResponse = `
122
+ <schema
123
+ targetNamespace="http://mapserver.gis.umn.edu/mapserver"
124
+ xmlns:ms="http://mapserver.gis.umn.edu/mapserver"
125
+ xmlns:ogc="http://www.opengis.net/ogc"
126
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
127
+ xmlns="http://www.w3.org/2001/XMLSchema"
128
+ xmlns:gml="http://www.opengis.net/gml"
129
+ elementFormDefault="qualified" version="0.1" >
130
+ <import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/3.1.1/base/gml.xsd" />
131
+
132
+ <element name="testlayer1" type="ms:testlayer1Type" substitutionGroup="gml:_Feature" />
133
+ <complexType name="testlayer1Type">
134
+ <complexContent>
135
+ <extension base="gml:AbstractFeatureType">
136
+ <sequence>
137
+ <element name="geom" type="gml:SurfacePropertyType" minOccurs="0" maxOccurs="1"/>
138
+ <element name="text_de" minOccurs="0" type="string"/>
139
+ </sequence>
140
+ </extension>
141
+ </complexContent>
142
+ </complexType>
143
+ </schema>
144
+ `;
145
+ const mockDescribeFeatureRequest = async (xmlResponse) => {
146
+ const describeFeatureTypeResponse = new DOMParser().parseFromString(xmlResponse, 'application/xml');
147
+ // @ts-ignore
148
+ vi.spyOn(client, 'requestDescribeFeatureType').mockReturnValue(
149
+ // @ts-ignore
150
+ new Promise((resolve) => resolve(describeFeatureTypeResponse)));
151
+ };
152
+ it('registers the features type described in the response', async () => {
153
+ await mockDescribeFeatureRequest(xmlResponse);
154
+ const server = await client.getServerWfs();
155
+ expect(server.layers).toBeDefined();
156
+ expect(Object.keys(server.layers)).toContain('testlayer1');
157
+ expect(server.layers['testlayer1']).toEqual([{ name: 'text_de', type: 'string' }]);
158
+ });
159
+ it('registers the feature type even if it does not have any attributes except a geometry', async () => {
160
+ const xmlNoAttributes = xmlResponse.replace('<element name="text_de" minOccurs="0" type="string"/>', '');
161
+ await mockDescribeFeatureRequest(xmlNoAttributes);
162
+ const server = await client.getServerWfs();
163
+ expect(server.layers).toBeDefined();
164
+ expect(Object.keys(server.layers)).toContain('testlayer1');
165
+ expect(server.layers['testlayer1']).toEqual([]);
166
+ });
167
+ it('ignores an attribute if its type is missing', async () => {
168
+ const xmlNoType = xmlResponse.replace('<element name="text_de" minOccurs="0" type="string"/>', '<element name="text_de" minOccurs="0"/>');
169
+ await mockDescribeFeatureRequest(xmlNoType);
170
+ const server = await client.getServerWfs();
171
+ expect(server.layers).toBeDefined();
172
+ expect(Object.keys(server.layers)).toContain('testlayer1');
173
+ expect(server.layers['testlayer1']).toEqual([]);
174
+ });
175
+ it('ignores an attribute if its name is missing', async () => {
176
+ const xmlNoAttributeName = xmlResponse.replace('<element name="text_de" minOccurs="0" type="string"/>', '<element minOccurs="0" type="string"/>');
177
+ await mockDescribeFeatureRequest(xmlNoAttributeName);
178
+ const server = await client.getServerWfs();
179
+ expect(server.layers).toBeDefined();
180
+ expect(Object.keys(server.layers)).toContain('testlayer1');
181
+ expect(server.layers['testlayer1']).toEqual([]);
182
+ });
183
+ it('ignores the feature type if it does not contain a geometry attribute', async () => {
184
+ const xmlNoGeometry = xmlResponse.replace('<element name="geom" type="gml:SurfacePropertyType" minOccurs="0" maxOccurs="1"/>', '');
185
+ await mockDescribeFeatureRequest(xmlNoGeometry);
186
+ const server = await client.getServerWfs();
187
+ expect(server.layers).toBeDefined();
188
+ expect(Object.keys(server.layers)).toEqual([]);
189
+ });
190
+ it('ignores the feature type if the geometry attribute has no geometry name', async () => {
191
+ const xmlNoGeometryName = xmlResponse.replace('<element name="geom" type="gml:SurfacePropertyType" minOccurs="0" maxOccurs="1"/>', '<element type="gml:SurfacePropertyType" minOccurs="0" maxOccurs="1"/>');
192
+ await mockDescribeFeatureRequest(xmlNoGeometryName);
193
+ const server = await client.getServerWfs();
194
+ expect(server.layers).toBeDefined();
195
+ expect(Object.keys(server.layers)).toEqual([]);
196
+ });
197
+ });
119
198
  });