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

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.
@@ -1,15 +1,30 @@
1
1
  import GirafeHTMLElement from '../base/GirafeHTMLElement.js';
2
2
  import IGirafeContext from '../tools/context/icontext.js';
3
3
  export default class GeoGirafeApi extends GirafeHTMLElement {
4
+ protected templateUrl: string;
5
+ protected styleUrls: string[];
6
+ private isInitialized;
4
7
  constructor();
8
+ private get config();
5
9
  protected connectedCallback(): void;
10
+ static get observedAttributes(): string[];
11
+ protected attributeChangedCallback(name: string, oldValue: string, newValue: string): void;
6
12
  protected getInheritedContext(): IGirafeContext;
7
13
  private defineApiComponents;
8
14
  private manageAttributes;
15
+ private manageUserInteraction;
16
+ private getAttributeFromConfig;
17
+ private defineAndAddComponent;
9
18
  private manageCenterAttribute;
10
19
  private manageZoomAttribute;
11
20
  private manageBasemapAttribute;
21
+ private manageLayersAttribute;
12
22
  private manageBasemapSelectorAttribute;
23
+ private manageSearchbarAttribute;
24
+ private manageSelectionboxAttribute;
25
+ private manageCrosshairAttribute;
26
+ private manageTooltipAttribute;
27
+ private manageMarkersAttribute;
13
28
  private initialize;
14
29
  private injectConfigMetaTags;
15
30
  }
@@ -6,12 +6,20 @@ import { register } from 'ol/proj/proj4.js';
6
6
  import GirafeApiContext from './apicontext.js';
7
7
  import BasemapComponent from '../components/basemap/component.js';
8
8
  import MenuButtonComponent from '../components/menubutton/component.js';
9
- import { applyOpacityToLayers } from '../tools/utils/utils.js';
9
+ import MapCustomContextMenuComponent from '../components/context-menu/custom-context-menu/component.js';
10
+ import SearchComponent from '../components/search/component.js';
11
+ import SelectionWindowComponent from '../components/selectionwindow/component.js';
10
12
  export default class GeoGirafeApi extends GirafeHTMLElement {
13
+ templateUrl = './template.html';
14
+ styleUrls = ['../styles/common.css', './style.css'];
15
+ isInitialized = false;
11
16
  constructor() {
12
17
  super('geogirafe-api');
13
18
  this.injectConfigMetaTags();
14
19
  }
20
+ get config() {
21
+ return this.context.configManager.Config.api.demo;
22
+ }
15
23
  connectedCallback() {
16
24
  super.connectedCallback();
17
25
  this.initialize().then(() => {
@@ -20,10 +28,46 @@ export default class GeoGirafeApi extends GirafeHTMLElement {
20
28
  this.subscribe('application.isReady', (_, isLoaded) => {
21
29
  if (isLoaded) {
22
30
  this.manageAttributes();
31
+ this.isInitialized = true;
32
+ this.dispatchEvent(new CustomEvent('geogirafe-api-ready'));
23
33
  }
24
34
  });
35
+ this.render();
25
36
  });
26
37
  }
38
+ static get observedAttributes() {
39
+ return ['center', 'zoom', 'basemap', 'basemapselector', 'crosshair', 'tooltip', 'markers', 'layers'];
40
+ }
41
+ attributeChangedCallback(name, oldValue, newValue) {
42
+ if (this.isInitialized) {
43
+ // We listen to attribute changes only if the API is already initialized
44
+ console.log(`Attribute ${name} changed : ${oldValue} → ${newValue}`);
45
+ if (name === 'center') {
46
+ this.manageCenterAttribute();
47
+ }
48
+ else if (name === 'zoom') {
49
+ this.manageZoomAttribute();
50
+ }
51
+ else if (name === 'basemap') {
52
+ this.manageBasemapAttribute();
53
+ }
54
+ else if (name === 'basemapselector') {
55
+ this.manageBasemapSelectorAttribute();
56
+ }
57
+ else if (name === 'crosshair') {
58
+ this.manageCrosshairAttribute();
59
+ }
60
+ else if (name === 'tooltip') {
61
+ this.manageTooltipAttribute();
62
+ }
63
+ else if (name === 'markers') {
64
+ this.manageMarkersAttribute();
65
+ }
66
+ else if (name === 'layers') {
67
+ this.manageLayersAttribute();
68
+ }
69
+ }
70
+ }
27
71
  getInheritedContext() {
28
72
  return new GirafeApiContext();
29
73
  }
@@ -31,16 +75,56 @@ export default class GeoGirafeApi extends GirafeHTMLElement {
31
75
  if (!customElements.get('girafe-map')) {
32
76
  customElements.define('girafe-map', MapComponent);
33
77
  }
34
- this.shadow.innerHTML = `<girafe-map></girafe-map>`;
35
78
  }
36
79
  manageAttributes() {
37
80
  this.manageCenterAttribute();
38
81
  this.manageZoomAttribute();
39
82
  this.manageBasemapAttribute();
40
83
  this.manageBasemapSelectorAttribute();
84
+ this.manageSearchbarAttribute();
85
+ this.manageCrosshairAttribute();
86
+ this.manageTooltipAttribute();
87
+ this.manageMarkersAttribute();
88
+ this.manageLayersAttribute();
89
+ this.manageSelectionboxAttribute();
90
+ this.manageUserInteraction();
91
+ }
92
+ manageUserInteraction() {
93
+ // Deacivate the preview of search results
94
+ this.context.configManager.Config.search.objectPreview = false;
95
+ this.context.configManager.Config.search.layerPreview = false;
96
+ // Force window as selection component
97
+ this.state.interface.selectionComponent = 'window';
98
+ // Deactivate selection if the selectionbox is not active
99
+ const selectionbox = this.getAttribute('selectionbox');
100
+ if (selectionbox === null) {
101
+ this.context.userInteractionManager.registerListener('map.select', true, 'api');
102
+ }
103
+ }
104
+ getAttributeFromConfig(attributeName) {
105
+ let attributeValue = this.getAttribute(attributeName);
106
+ if (!attributeValue) {
107
+ return null;
108
+ }
109
+ if (attributeValue.startsWith('api.demo.')) {
110
+ const configName = attributeValue.replace('api.demo.', '');
111
+ attributeValue = this.config[configName];
112
+ this.setAttribute(attributeName, attributeValue);
113
+ }
114
+ return attributeValue;
115
+ }
116
+ defineAndAddComponent(customElementName, customElementType) {
117
+ if (!customElements.get(customElementName)) {
118
+ customElements.define(customElementName, customElementType);
119
+ }
120
+ const existingElement = this.shadowRoot?.querySelector(customElementName);
121
+ if (!existingElement) {
122
+ const component = new customElementType();
123
+ this.shadow.appendChild(component);
124
+ }
41
125
  }
42
126
  manageCenterAttribute() {
43
- const center = this.getAttribute('center');
127
+ const center = this.getAttributeFromConfig('center');
44
128
  if (center) {
45
129
  const coords = center.split(',');
46
130
  const x = Number(coords[0].trim());
@@ -54,7 +138,7 @@ export default class GeoGirafeApi extends GirafeHTMLElement {
54
138
  }
55
139
  }
56
140
  manageZoomAttribute() {
57
- const zoom = this.getAttribute('zoom');
141
+ const zoom = this.getAttributeFromConfig('zoom');
58
142
  if (zoom) {
59
143
  const zoomLevel = Number(zoom.trim());
60
144
  if (Number.isNaN(zoomLevel)) {
@@ -66,14 +150,15 @@ export default class GeoGirafeApi extends GirafeHTMLElement {
66
150
  }
67
151
  }
68
152
  manageBasemapAttribute() {
69
- const basemap = this.getAttribute('basemap');
153
+ const basemap = this.getAttributeFromConfig('basemap');
70
154
  if (basemap) {
71
155
  const basemapName = basemap.trim();
72
156
  if (basemapName) {
73
157
  // Find the basemap in the available basemaps
74
158
  const availableBasemap = Object.values(this.context.stateManager.state.basemaps).find((b) => b.name === basemapName);
75
159
  if (availableBasemap) {
76
- applyOpacityToLayers(1, availableBasemap.layersList);
160
+ // Force opacity to 1 for the API
161
+ availableBasemap.opacity = 1;
77
162
  this.context.stateManager.state.activeBasemaps = [availableBasemap];
78
163
  }
79
164
  else {
@@ -82,16 +167,90 @@ export default class GeoGirafeApi extends GirafeHTMLElement {
82
167
  }
83
168
  }
84
169
  }
170
+ manageLayersAttribute() {
171
+ const layers = this.getAttributeFromConfig('layers');
172
+ if (layers) {
173
+ const layerNames = layers.split(',');
174
+ for (const layerName of layerNames) {
175
+ if (!this.context.themesHelper.addLayerFromName(layerName.trim())) {
176
+ console.warn(`Cannot add layer ${layerName.trim()}`);
177
+ }
178
+ }
179
+ }
180
+ }
85
181
  manageBasemapSelectorAttribute() {
86
182
  const basemapselector = this.getAttribute('basemapselector');
87
183
  if (basemapselector != null) {
88
- if (!customElements.get('girafe-menu-button')) {
89
- customElements.define('girafe-menu-button', MenuButtonComponent);
184
+ this.defineAndAddComponent('girafe-menu-button', MenuButtonComponent);
185
+ this.defineAndAddComponent('girafe-basemap', BasemapComponent);
186
+ }
187
+ }
188
+ manageSearchbarAttribute() {
189
+ const searchbar = this.getAttribute('searchbar');
190
+ if (searchbar != null) {
191
+ this.defineAndAddComponent('girafe-search', SearchComponent);
192
+ }
193
+ }
194
+ manageSelectionboxAttribute() {
195
+ const selectionbox = this.getAttribute('selectionbox');
196
+ if (selectionbox != null) {
197
+ this.defineAndAddComponent('girafe-selection-window', SelectionWindowComponent);
198
+ }
199
+ }
200
+ manageCrosshairAttribute() {
201
+ const crosshair = this.getAttributeFromConfig('crosshair');
202
+ if (crosshair) {
203
+ const coords = crosshair.split(',');
204
+ const x = Number(coords[0].trim());
205
+ const y = Number(coords[1].trim());
206
+ if (!Number.isNaN(x) && !Number.isNaN(y)) {
207
+ this.context.stateManager.state.position.crosshair = [x, y];
208
+ }
209
+ else {
210
+ console.warn('Invalid crosshair coordinates');
211
+ }
212
+ }
213
+ }
214
+ manageTooltipAttribute() {
215
+ const tooltip = this.getAttributeFromConfig('tooltip');
216
+ if (tooltip) {
217
+ this.defineAndAddComponent('girafe-custom-context-menu', MapCustomContextMenuComponent);
218
+ const content = tooltip.split('|');
219
+ const coords = content[0].split(',');
220
+ const x = Number(coords[0].trim());
221
+ const y = Number(coords[1].trim());
222
+ if (!Number.isNaN(x) && !Number.isNaN(y)) {
223
+ const text = content[1];
224
+ this.context.stateManager.state.position.tooltip = {
225
+ position: [x, y],
226
+ content: text
227
+ };
228
+ }
229
+ else {
230
+ console.warn('Invalid tooltip coordinates');
90
231
  }
91
- if (!customElements.get('girafe-basemap')) {
92
- customElements.define('girafe-basemap', BasemapComponent);
232
+ }
233
+ }
234
+ manageMarkersAttribute() {
235
+ const markers = this.getAttributeFromConfig('markers');
236
+ if (markers) {
237
+ const markerValues = markers.split(';');
238
+ for (const makerValue of markerValues) {
239
+ const content = makerValue.split('|');
240
+ const coords = content[0].split(',');
241
+ const x = Number(coords[0].trim());
242
+ const y = Number(coords[1].trim());
243
+ if (!Number.isNaN(x) && !Number.isNaN(y)) {
244
+ const imageUrl = content[1].trim();
245
+ this.context.stateManager.state.position.markers.push({
246
+ position: [x, y],
247
+ imageUrl: imageUrl
248
+ });
249
+ }
250
+ else {
251
+ console.warn('Invalid marker coordinates');
252
+ }
93
253
  }
94
- this.shadow.innerHTML += '<girafe-basemap></girafe-basemap>';
95
254
  }
96
255
  }
97
256
  async initialize() {
@@ -101,7 +260,9 @@ export default class GeoGirafeApi extends GirafeHTMLElement {
101
260
  proj4.defs(crs.code, crs.definition);
102
261
  }
103
262
  register(proj4);
104
- // Tell the application it is initialized (no auth at the moment for the api)
263
+ // No custom serializer for the API
264
+ this.context.stateManager.state.application.isCustomSerializerInitialized = true;
265
+ // No auth for the api
105
266
  this.context.stateManager.state.application.isAuthInitialized = true;
106
267
  // Automatically toggle dark/light mode when changed in the system
107
268
  globalThis.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
@@ -116,9 +277,7 @@ export default class GeoGirafeApi extends GirafeHTMLElement {
116
277
  }
117
278
  injectConfigMetaTags() {
118
279
  const location = new URL(import.meta.url);
119
- if (import.meta?.env?.DEV) {
120
- location.pathname = location.pathname.replace('/src/api', '');
121
- }
280
+ location.pathname = location.pathname.replace('/src/api', '');
122
281
  const origin = `${location.origin}${location.pathname.substring(0, location.pathname.lastIndexOf('/'))}`;
123
282
  const baseConfigUrl = `${origin}/config.json`;
124
283
  const apiConfigUrl = `${origin}/config.api.json`;
@@ -17,7 +17,6 @@ import DrawingManager from './tools/drawingmanager.js';
17
17
  import GirafeHTMLElement from '../../base/GirafeHTMLElement.js';
18
18
  import Basemap from '../../models/basemaps/basemap.js';
19
19
  import Layer from '../../models/layers/layer.js';
20
- import MapPosition from '../../tools/state/mapposition.js';
21
20
  import BaseLayer from '../../models/layers/baselayer.js';
22
21
  import { FocusFeature } from './tools/focusfeature.js';
23
22
  import XyzManager from './tools/xyzmanager.js';
@@ -52,6 +51,8 @@ export default class MapComponent extends GirafeHTMLElement {
52
51
  crosshairFeature: Feature;
53
52
  crosshairLayer: VectorLayer<VectorSource>;
54
53
  geolocationSource: VectorSource;
54
+ private readonly markerSource;
55
+ private readonly markerLayer;
55
56
  get projection(): import("ol/proj.js").Projection;
56
57
  get config(): import("../../tools/main.js").GirafeConfig;
57
58
  /**
@@ -155,5 +156,7 @@ export default class MapComponent extends GirafeHTMLElement {
155
156
  * Moves the map to the position defined in the permalink, making sure the map is initialized and ready to be moved.
156
157
  */
157
158
  private applyMapPositionFromPermalink;
158
- showCrosshair(position: MapPosition): void;
159
+ private clearAllMarkers;
160
+ private addMarker;
161
+ private showCrosshair;
159
162
  }
@@ -1,7 +1,7 @@
1
1
  import { html as uHtml } from 'uhtml';
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
  import { Collection, Feature } from 'ol';
4
- import { Circle, Fill, RegularShape, Stroke, Style } from 'ol/style.js';
4
+ import { Circle, Fill, Icon, RegularShape, Stroke, Style } from 'ol/style.js';
5
5
  import VectorSource from 'ol/source/Vector.js';
6
6
  import VectorLayer from 'ol/layer/Vector.js';
7
7
  import { DragBox } from 'ol/interaction.js';
@@ -74,6 +74,10 @@ export default class MapComponent extends GirafeHTMLElement {
74
74
  crosshairFeature;
75
75
  crosshairLayer;
76
76
  geolocationSource;
77
+ markerSource = new VectorSource();
78
+ markerLayer = new VectorLayer({
79
+ source: this.markerSource
80
+ });
77
81
  get projection() {
78
82
  return this.olMap.getView().getProjection();
79
83
  }
@@ -124,6 +128,12 @@ export default class MapComponent extends GirafeHTMLElement {
124
128
  this.subscribe('selection.selectedFeatures', (_oldFeatures, newFeatures) => this.onFeaturesSelected(newFeatures));
125
129
  this.subscribe('selection.highlightedFeatures', (_oldFeatures, newFeatures) => this.onFeatureHighlighted(newFeatures));
126
130
  this.subscribe('selection.focusedFeatures', (_oldFeature, newFeature) => this.focusFeature.setFocusedFeatures(newFeature));
131
+ this.subscribe('position.markers', () => {
132
+ this.clearAllMarkers();
133
+ for (const marker of this.state.position.markers) {
134
+ this.addMarker(marker.position, marker.imageUrl);
135
+ }
136
+ });
127
137
  this.subscribe('globe.display', async () => {
128
138
  await this.onGlobeToggled();
129
139
  this.onCameraChanged(this.state.globe.camera);
@@ -162,6 +172,9 @@ export default class MapComponent extends GirafeHTMLElement {
162
172
  this.applyFeatureSelectionFromSharedState();
163
173
  }
164
174
  this.showCrosshair(this.state.position);
175
+ for (const marker of this.state.position.markers) {
176
+ this.addMarker(marker.position, marker.imageUrl);
177
+ }
165
178
  }
166
179
  });
167
180
  }
@@ -272,6 +285,7 @@ export default class MapComponent extends GirafeHTMLElement {
272
285
  this.setHighlightLayerStyle();
273
286
  this.olMap.addLayer(this.selectionLayer);
274
287
  this.olMap.addLayer(this.highlightLayer);
288
+ this.olMap.addLayer(this.markerLayer);
275
289
  this.selectionLayer.setZIndex(1002);
276
290
  this.highlightLayer.setZIndex(1003);
277
291
  this.selectionLayer.set('altitudeMode', 'clampToGround');
@@ -999,8 +1013,29 @@ export default class MapComponent extends GirafeHTMLElement {
999
1013
  if (position?.isValid) {
1000
1014
  // We need the following to recalculate resolution and scale to properly update the position state
1001
1015
  this.state.position = position;
1016
+ if (position.markers.length > 0) {
1017
+ // Add marker to the map
1018
+ this.addMarker(position.markers[0].position, position.markers[0].imageUrl);
1019
+ }
1002
1020
  }
1003
1021
  }
1022
+ clearAllMarkers() {
1023
+ this.markerSource.clear();
1024
+ }
1025
+ addMarker(position, imageUrl) {
1026
+ const iconStyle = new Style({
1027
+ image: new Icon({
1028
+ //anchor: [0.5, 1], // Point d'ancrage (centre en bas)
1029
+ src: imageUrl
1030
+ //scale: 0.5, // Ajustez la taille si nécessaire
1031
+ })
1032
+ });
1033
+ const marker = new Feature({
1034
+ geometry: new Point(position)
1035
+ });
1036
+ marker.setStyle(iconStyle);
1037
+ this.markerSource.addFeature(marker);
1038
+ }
1004
1039
  showCrosshair(position) {
1005
1040
  if (!position.crosshair) {
1006
1041
  return;
@@ -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.2407606313",
9
9
  "type": "module",
10
10
  "engines": {
11
11
  "node": ">=20.19.0"
@@ -53,7 +53,6 @@
53
53
  },
54
54
  "peerDependencies": {
55
55
  "@geoblocks/mapfishprint": "0.3",
56
- "@types/geojson": "7946",
57
56
  "cesium": ">1.113",
58
57
  "ol": "8 || 9 || 10",
59
58
  "ol-ext": "^4.0.37",
@@ -94,10 +93,10 @@
94
93
  "proj4": "2.19.10",
95
94
  "svgo": "4.0.0",
96
95
  "typescript": "5.8.3",
97
- "vite": "7.0.6",
98
- "vite-bundle-analyzer": "1.3.2",
96
+ "vite": "7.3.1",
97
+ "vite-bundle-analyzer": "1.3.6",
99
98
  "vite-plugin-html": "3.2.2",
100
- "vite-plugin-static-copy": "3.1.1",
99
+ "vite-plugin-static-copy": "3.2.0",
101
100
  "vitest": "3.2.4"
102
101
  },
103
102
  "types": "./main.d.ts",
package/styles/api.css ADDED
@@ -0,0 +1,2 @@
1
+ @import './variables.css';
2
+ @import '../../node_modules/tippy.js/dist/tippy.css';