@geogirafe/lib-geoportal 1.2.0-dev.2714239322 → 1.2.0-dev.2736532702

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.
@@ -319,17 +319,19 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
319
319
  }
320
320
  }
321
321
  onProjectionChanged(oldProj, newProj) {
322
- if (oldProj != null && oldProj != newProj) {
323
- const geoJson = new GeoJSON();
324
- const features = [...this.drawingState.features];
325
- features.forEach((f) => {
326
- // TODO Handle the case of disks
327
- f.geojson = geoJson.writeFeatureObject(geoJson.readFeature(f.geojson, { dataProjection: oldProj, featureProjection: newProj }));
328
- });
329
- // Refresh all the listeners
330
- this.drawingState.features = [];
331
- this.drawingState.features = features;
322
+ if (oldProj === null || oldProj === newProj) {
323
+ return;
332
324
  }
325
+ const features = [...this.drawingState.features];
326
+ features.forEach((f) => {
327
+ const transformedGeoJson = DrawingFeature.transformGeoJson(f.geojson, oldProj, newProj);
328
+ if (transformedGeoJson) {
329
+ f.geojson = transformedGeoJson;
330
+ }
331
+ });
332
+ // Refresh all the listeners
333
+ this.drawingState.features = [];
334
+ this.drawingState.features = features;
333
335
  }
334
336
  onToggleBatchMode() {
335
337
  this.batchCreateMode = !this.batchCreateMode;
@@ -1,6 +1,6 @@
1
1
  import type { BaseCustomizer, MFPLayer, MFPMap, MFPWmtsLayer, MFPWmsLayer } from '@geoblocks/mapfishprint';
2
- import GroupLayer from '../../../models/layers/grouplayer.js';
3
2
  import type BaseLayer from '../../../models/layers/baselayer.js';
3
+ import GroupLayer from '../../../models/layers/grouplayer.js';
4
4
  import MapManager from '../../../tools/state/mapManager.js';
5
5
  import State from '../../../tools/state/state.js';
6
6
  import LayerLocalFile from '../../../models/layers/layerlocalfile.js';
@@ -1,5 +1,5 @@
1
- import GroupLayer from '../../../models/layers/grouplayer.js';
2
1
  import { getAbsoluteUrl, getWmtsMatrices, getWmtsUrl, MFPVectorEncoder } from '@geoblocks/mapfishprint';
2
+ import GroupLayer from '../../../models/layers/grouplayer.js';
3
3
  import { toDegrees } from 'ol/math.js';
4
4
  import LayerLocalFile from '../../../models/layers/layerlocalfile.js';
5
5
  import LayerWms from '../../../models/layers/layerwms.js';
@@ -9,6 +9,7 @@ import { isLayerVisible } from './printUtils.js';
9
9
  import VectorSource from 'ol/source/Vector.js';
10
10
  import LayerWmtsExternal from '../../../models/layers/layerwmtsexternal.js';
11
11
  import LayerWmsExternal from '../../../models/layers/layerwmsexternal.js';
12
+ import WmsClient from '../../../tools/wms/wmsclient.js';
12
13
  /** Supported MapFishPRint servers
13
14
  * see https://github.com/mapfish/mapfish-print/blob/master/core/src/main/java/org/mapfish/print/map/image/wms/WmsLayerParam.java#L123
14
15
  */
@@ -190,6 +191,9 @@ export default class MFPEncoder {
190
191
  customParams[key] = value;
191
192
  });
192
193
  }
194
+ if (layerWms.filter) {
195
+ customParams.FILTER = WmsClient.buildFilterQuery(layerWms);
196
+ }
193
197
  let serverType = layerWms.ogcServer.type;
194
198
  // Remove unsupported serverType (otherwise, you'll get an error from the print server).
195
199
  if (!MFPSupportedServerType.has(serverType)) {
@@ -65,8 +65,8 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
65
65
  }
66
66
  else if (featureLike.getGeometry()?.getType() == 'Circle') {
67
67
  const circle = featureLike.getGeometry();
68
- const radiusDataForCircle = getRadiusDataForCircle(circle, this.defaultStyle, new Stroke({ color: 'rgba(0, 0, 0, 0.4)', width: this.drawing.defaultStrokeWidth }));
69
- styles.push(radiusDataForCircle.style, getLabelStyle(getHalfPoint(radiusDataForCircle.radiusLine), getLengthAsMetricText(radiusDataForCircle.radius), this.labelStyle), getLabelStyle(new Point(circle.getCenter()), getAreaAsMetricText(getAreaOfCircle(circle, this.state.projection)), this.labelStyle));
68
+ const radiusDataForCircle = getRadiusDataForCircle(circle, this.state.projection, this.defaultStyle, new Stroke({ color: 'rgba(0, 0, 0, 0.4)', width: this.drawing.defaultStrokeWidth }));
69
+ styles.push(radiusDataForCircle.style, getLabelStyle(getHalfPoint(radiusDataForCircle.radiusLine), getLengthAsMetricText(radiusDataForCircle.radiusMeter), this.labelStyle), getLabelStyle(new Point(circle.getCenter()), getAreaAsMetricText(getAreaOfCircle(circle, this.state.projection)), this.labelStyle));
70
70
  }
71
71
  return styles.filter((style) => !!style);
72
72
  };
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "GeoGirafe PSC",
6
6
  "url": "https://doc.geogirafe.org"
7
7
  },
8
- "version": "1.2.0-dev.2714239322",
8
+ "version": "1.2.0-dev.2736532702",
9
9
  "type": "module",
10
10
  "engines": {
11
11
  "node": ">=20.19.0"
@@ -1 +1 @@
1
- {"version":"1.2.0-dev.2714239322", "build":"2714239322", "date":"29/07/2026"}
1
+ {"version":"1.2.0-dev.2736532702", "build":"2736532702", "date":"06/08/2026"}
@@ -1,6 +1,7 @@
1
1
  import { Style } from 'ol/style.js';
2
2
  import { Circle as CircleGeom, Geometry } from 'ol/geom.js';
3
3
  import Feature from 'ol/Feature.js';
4
+ import GeoJSON from 'ol/format/GeoJSON.js';
4
5
  import IGirafeContext from '../context/icontext.js';
5
6
  import DrawingState from './drawingState.js';
6
7
  export declare enum DrawingShape {
@@ -37,6 +38,9 @@ export type SerializedFeature = {
37
38
  t: DrawingShape;
38
39
  };
39
40
  export default class DrawingFeature {
41
+ static readonly geoJSONFormat: GeoJSON<Feature<Geometry, {
42
+ [x: string]: any;
43
+ }>>;
40
44
  private readonly _tool;
41
45
  private _name;
42
46
  private _nameColor;
@@ -106,6 +110,11 @@ export default class DrawingFeature {
106
110
  * with a limited number of edges to improve support across formats and services.
107
111
  */
108
112
  static olFeatureFromGeoJson(geoJson: any, circleToPolygonNbEdges?: number): Feature<Geometry>;
113
+ /**
114
+ * Transform a geojson (valid object, or disk with its properties) to a new projection.
115
+ * @returns The transformed geojson object or null.
116
+ */
117
+ static transformGeoJson(geoJson: object, oldProj: string, newProj: string): object | null;
109
118
  /**
110
119
  * Finds a name composed of the shape type and a number.
111
120
  * The number starts from how many shapes of the same type are already in the state +1.
@@ -7,6 +7,7 @@ import Feature from 'ol/Feature.js';
7
7
  import GeoJSON from 'ol/format/GeoJSON.js';
8
8
  import { getAreaAsMetricText, getAzimuthAsText, getLengthAsMetricText } from '../utils/olutils.js';
9
9
  import { fromCircle } from 'ol/geom/Polygon.js';
10
+ import LineString from 'ol/geom/LineString.js';
10
11
  export var DrawingShape;
11
12
  (function (DrawingShape) {
12
13
  DrawingShape[DrawingShape["Point"] = 0] = "Point";
@@ -19,6 +20,7 @@ export var DrawingShape;
19
20
  DrawingShape[DrawingShape["FreehandPolygon"] = 7] = "FreehandPolygon";
20
21
  })(DrawingShape || (DrawingShape = {}));
21
22
  export default class DrawingFeature {
23
+ static geoJSONFormat = new GeoJSON();
22
24
  _tool;
23
25
  _name;
24
26
  _nameColor;
@@ -263,17 +265,18 @@ export default class DrawingFeature {
263
265
  const radius = disk.getRadius();
264
266
  const pointer = disk.getProperties()['pointer'] ?? [center[0] + radius, center[1]];
265
267
  return {
268
+ id: olFeature.getId(),
266
269
  type: 'Feature',
267
270
  geometry: {
268
271
  type: 'Disk',
269
272
  center: center,
270
273
  radius: radius,
271
- azimuth: disk.getProperties()['azimuth'] ?? 0,
274
+ azimuth: disk.get('azimuth') ?? 0,
272
275
  pointer: pointer
273
276
  }
274
277
  };
275
278
  }
276
- return JSON.parse(new GeoJSON().writeFeature(olFeature));
279
+ return JSON.parse(DrawingFeature.geoJSONFormat.writeFeature(olFeature));
277
280
  }
278
281
  /**
279
282
  * Create an ol feature instance from a geojson object, optionally simplifying circle geometries to polygons
@@ -294,17 +297,54 @@ export default class DrawingFeature {
294
297
  azimuth: geometry.azimuth,
295
298
  pointer: geometry.pointer
296
299
  });
297
- return new Feature(olGeometry);
300
+ const feature = new Feature(olGeometry);
301
+ if (geoJson.id) {
302
+ feature.setId(geoJson.id);
303
+ }
304
+ return feature;
298
305
  }
299
306
  else {
300
307
  // Standard case: parse with GeoJSON format reader
301
- const feature = new Feature(new GeoJSON().readFeatures(geoJson)[0].getGeometry());
308
+ const feature = new Feature(DrawingFeature.geoJSONFormat.readFeatures(geoJson)[0].getGeometry());
302
309
  if (Array.isArray(feature)) {
303
310
  throw new TypeError('GeoJSON feature is an array, expected single feature');
304
311
  }
305
312
  return feature;
306
313
  }
307
314
  }
315
+ /**
316
+ * Transform a geojson (valid object, or disk with its properties) to a new projection.
317
+ * @returns The transformed geojson object or null.
318
+ */
319
+ static transformGeoJson(geoJson, oldProj, newProj) {
320
+ if (oldProj === null || oldProj === newProj) {
321
+ return geoJson;
322
+ }
323
+ try {
324
+ if (geoJson.geometry?.type === 'Disk') {
325
+ // Case of disks that are not a valid geojson object, handle them manually.
326
+ const olFeature = DrawingFeature.olFeatureFromGeoJson(geoJson);
327
+ const geom = olFeature.getGeometry();
328
+ geom?.transform(oldProj, newProj);
329
+ if (geom) {
330
+ const center = geom.getCenter();
331
+ const line = new LineString([center, [center[0], center[1] + geom.getRadius()]]);
332
+ line.rotate(toRadians((geom.get('azimuth') ?? 0) * -1), center);
333
+ geom.set('pointer', line.getLastCoordinate());
334
+ }
335
+ return DrawingFeature.geojsonFromOlFeature(olFeature, DrawingShape.Disk);
336
+ }
337
+ // Case of valid geojson objects.
338
+ return DrawingFeature.geoJSONFormat.writeFeatureObject(DrawingFeature.geoJSONFormat.readFeature(geoJson, {
339
+ dataProjection: oldProj,
340
+ featureProjection: newProj
341
+ }));
342
+ }
343
+ catch (e) {
344
+ console.error('Error transforming this draw object to the new projection.', e);
345
+ }
346
+ return null;
347
+ }
308
348
  /**
309
349
  * Finds a name composed of the shape type and a number.
310
350
  * The number starts from how many shapes of the same type are already in the state +1.
@@ -528,8 +528,9 @@ export default class OlDrawing {
528
528
  createDiskFixedLength(coordinates, geom) {
529
529
  const coord = coordinates;
530
530
  this.fixLastLength(this.fixedLineLength, coord);
531
- geom = geom ?? new CircleGeom(coord[0], getDistance(coord, this.state.projection));
532
- geom.setCenterAndRadius(coord[0], getDistance(coord, this.state.projection));
531
+ const radius = new LineString(coord).getLength();
532
+ geom ??= new CircleGeom(coord[0], radius);
533
+ geom.setCenterAndRadius(coord[0], radius);
533
534
  geom.setProperties({
534
535
  azimuth: (90 - Math.atan2(coord[1][1] - coord[0][1], coord[1][0] - coord[0][0]) * (180 / Math.PI) + 360) % 360,
535
536
  pointer: coord[1]
@@ -793,9 +794,9 @@ export default class OlDrawing {
793
794
  }
794
795
  else if (dFeature.type == DrawingShape.Disk) {
795
796
  if (dFeature.measureInformation !== 'none') {
796
- const radiusDataForCircle = getRadiusDataForCircle(geometry, defaultStyle, new Stroke({ color: measureColor, width: 2 }));
797
+ const radiusDataForCircle = getRadiusDataForCircle(geometry, this.state.projection, defaultStyle, new Stroke({ color: measureColor, width: 2 }));
797
798
  styles.push(radiusDataForCircle.style);
798
- const lengthText = dFeature.getLengthText(radiusDataForCircle.radius);
799
+ const lengthText = dFeature.getLengthText(radiusDataForCircle.radiusMeter);
799
800
  const azimuthText = dFeature.getAzimuthText(geometry);
800
801
  const textTemplate = dFeature.measureInformation === 'simple' ? lengthText : lengthText + ', ' + azimuthText;
801
802
  addLabel(getHalfPoint(radiusDataForCircle.radiusLine), textTemplate);
@@ -43,9 +43,9 @@ export declare const reprojectGeometry: (geometry: Geometry, sourceProjection: P
43
43
  export declare const ensurePolygonIsProperlyClosed: (polygon: Polygon) => Coordinate[];
44
44
  export declare const getHalfPoint: (coordinates: Coordinate[]) => Point;
45
45
  export declare const getLabelStyle: (position: Point, text: string, labelStyle: Style) => Style | undefined;
46
- export declare const getRadiusDataForCircle: (circle: Circle, defaultStyle: Style, stroke: Stroke) => {
46
+ export declare const getRadiusDataForCircle: (circle: Circle, projection: ProjectionLike, defaultStyle: Style, stroke: Stroke) => {
47
+ radiusMeter: number;
47
48
  style: Style;
48
- radius: number;
49
49
  radiusLine: Coordinate[];
50
50
  };
51
51
  export declare const getLengthAsMetricText: (length?: number) => string;
@@ -147,9 +147,10 @@ export const getLabelStyle = (position, text, labelStyle) => {
147
147
  }
148
148
  return undefined;
149
149
  };
150
- export const getRadiusDataForCircle = (circle, defaultStyle, stroke) => {
150
+ export const getRadiusDataForCircle = (circle, projection, defaultStyle, stroke) => {
151
151
  const radius = circle.getRadius();
152
152
  const center = circle.getCenter();
153
+ const radiusMeter = getDistance([center, [center[0], center[1] + radius]], projection);
153
154
  const pointer = circle.getProperties()['pointer'] ?? [center[0] + radius, center[1]];
154
155
  const radiusLine = [center, pointer];
155
156
  const radiusLineStyle = defaultStyle.clone();
@@ -157,8 +158,8 @@ export const getRadiusDataForCircle = (circle, defaultStyle, stroke) => {
157
158
  radiusLineStyle.getText().setText('');
158
159
  radiusLineStyle.setGeometry(new LineString(radiusLine));
159
160
  return {
161
+ radiusMeter,
160
162
  style: radiusLineStyle,
161
- radius: radius,
162
163
  radiusLine: radiusLine
163
164
  };
164
165
  };
@@ -42,7 +42,14 @@ export default abstract class WmsClient {
42
42
  private manageLayerOptions;
43
43
  private makeLayerIndependent;
44
44
  private updateLayerFilter;
45
- abstract buildFilterQuery(layerWms: LayerWms): string;
45
+ /**
46
+ * MapServer wants 1 filter per underlying WFS queryLayer, all included in parentheses
47
+ * QGIS-server wants 1 filter per underlying WFS queryLayer, each in its own in parentheses
48
+ *
49
+ * MapServer: (<filter>...</filter><filter>...</filter>)
50
+ * QGIS-server: (<filter>...</filter>)(<filter>...</filter>)
51
+ */
52
+ static buildFilterQuery(layerWms: LayerWms): string;
46
53
  private updateTimeRestriction;
47
54
  selectFeatures(extent: number[]): void;
48
55
  /**
@@ -62,17 +69,11 @@ export declare class WmsClientQgis extends WmsClient {
62
69
  * Solution: directly query the queryLayers
63
70
  */
64
71
  getOpenLayerLayerNames(layerList: LayerWms[]): string[];
65
- /** MapServer wants 1 filter per underlying WFS queryLayer, all included in parentheses
66
- * QGIS-server wants 1 filter per underlying WFS queryLayer, each in its own in parentheses
67
- *
68
- * MapServer: (<filter>...</filter><filter>...</filter>)
69
- * QGIS-server: (<filter>...</filter>)(<filter>...</filter>)
70
- */
71
- buildFilterQuery(layerWms: LayerWms): string;
72
+ static buildFilterQuery(layerWms: LayerWms): string;
72
73
  }
73
74
  export declare class WmsClientMapServer extends WmsClient {
74
75
  getOpenLayerLayerNames(layerList: LayerWms[]): string[];
75
- buildFilterQuery(layerWms: LayerWms): string;
76
+ static buildFilterQuery(layerWms: LayerWms): string;
76
77
  }
77
78
  export declare const WmsClientDefault: typeof WmsClientQgis;
78
79
  export declare const WmsClientGeoServer: typeof WmsClientMapServer;
@@ -231,7 +231,7 @@ export default class WmsClient {
231
231
  updateLayerFilter(layerWms, olayer) {
232
232
  const source = olayer.getSource();
233
233
  if (layerWms.hasFilter) {
234
- const filterStr = this.buildFilterQuery(layerWms);
234
+ const filterStr = WmsClient.buildFilterQuery(layerWms);
235
235
  source.updateParams({ FILTER: filterStr });
236
236
  }
237
237
  else {
@@ -243,6 +243,19 @@ export default class WmsClient {
243
243
  }
244
244
  }
245
245
  }
246
+ /**
247
+ * MapServer wants 1 filter per underlying WFS queryLayer, all included in parentheses
248
+ * QGIS-server wants 1 filter per underlying WFS queryLayer, each in its own in parentheses
249
+ *
250
+ * MapServer: (<filter>...</filter><filter>...</filter>)
251
+ * QGIS-server: (<filter>...</filter>)(<filter>...</filter>)
252
+ */
253
+ static buildFilterQuery(layerWms) {
254
+ if (layerWms.ogcServer.type === 'qgisserver') {
255
+ return WmsClientQgis.buildFilterQuery(layerWms);
256
+ }
257
+ return WmsClientMapServer.buildFilterQuery(layerWms);
258
+ }
246
259
  updateTimeRestriction(layerWms, olayer) {
247
260
  const source = olayer.getSource();
248
261
  if (layerWms.hasTimeRestriction) {
@@ -414,34 +427,26 @@ export class WmsClientQgis extends WmsClient {
414
427
  return layerNames;
415
428
  }
416
429
  }
417
- /** MapServer wants 1 filter per underlying WFS queryLayer, all included in parentheses
418
- * QGIS-server wants 1 filter per underlying WFS queryLayer, each in its own in parentheses
419
- *
420
- * MapServer: (<filter>...</filter><filter>...</filter>)
421
- * QGIS-server: (<filter>...</filter>)(<filter>...</filter>)
422
- */
423
- buildFilterQuery(layerWms) {
424
- let filterStr = '';
425
- if (layerWms.hasFilter) {
426
- const filter = layerWms.filter;
427
- const nbQuerylayers = layerWms.queryLayers.split(',').length;
428
- filterStr = ('(' + filter.toWmsGetMapFilter() + ')').repeat(nbQuerylayers);
430
+ static buildFilterQuery(layerWms) {
431
+ if (!layerWms.hasFilter) {
432
+ return '';
429
433
  }
430
- return filterStr;
434
+ const filter = layerWms.filter;
435
+ const nbQuerylayers = layerWms.queryLayers.split(',').length;
436
+ return ('(' + filter.toWmsGetMapFilter() + ')').repeat(nbQuerylayers);
431
437
  }
432
438
  }
433
439
  export class WmsClientMapServer extends WmsClient {
434
440
  getOpenLayerLayerNames(layerList) {
435
441
  return layerList.map((l) => l.layers);
436
442
  }
437
- buildFilterQuery(layerWms) {
438
- let filterStr = '';
439
- if (layerWms.hasFilter) {
440
- const filter = layerWms.filter;
441
- const nbQuerylayers = layerWms.queryLayers.split(',').length;
442
- filterStr = '(' + filter.toWmsGetMapFilter().repeat(nbQuerylayers) + ')';
443
+ static buildFilterQuery(layerWms) {
444
+ if (!layerWms.hasFilter) {
445
+ return '';
443
446
  }
444
- return filterStr;
447
+ const filter = layerWms.filter;
448
+ const nbQuerylayers = layerWms.queryLayers.split(',').length;
449
+ return '(' + filter.toWmsGetMapFilter().repeat(nbQuerylayers) + ')';
445
450
  }
446
451
  }
447
452
  export const WmsClientDefault = WmsClientQgis;