@geogirafe/lib-geoportal 1.1.0-dev.2536443365 → 1.1.0-dev.2557861681

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.
@@ -26,6 +26,10 @@ export default class GeoGirafeApi extends GirafeHTMLElement {
26
26
  private manageCrosshairAttribute;
27
27
  private manageTooltipAttribute;
28
28
  private manageMarkersAttribute;
29
+ private markerStringToMapMarker;
30
+ private manageMarkersFileAttribute;
31
+ private readMarkersFromFile;
32
+ private lineToMarker;
29
33
  private initialize;
30
34
  private injectConfigMetaTags;
31
35
  }
@@ -10,6 +10,22 @@ import MenuButtonComponent from '../components/menubutton/component.js';
10
10
  import MapCustomContextMenuComponent from '../components/context-menu/custom-context-menu/component.js';
11
11
  import SearchComponent from '../components/search/component.js';
12
12
  import SelectionWindowComponent from '../components/selectionwindow/component.js';
13
+ import { splitTrimAndConvertToNumber } from '../tools/utils/utils.js';
14
+ const getImageSize = (url) => {
15
+ return new Promise((resolve, reject) => {
16
+ const img = new Image();
17
+ img.onload = () => {
18
+ resolve({
19
+ width: img.naturalWidth,
20
+ height: img.naturalHeight
21
+ });
22
+ };
23
+ img.onerror = () => {
24
+ reject(new Error(`Failed to load image: ${url}`));
25
+ };
26
+ img.src = url;
27
+ });
28
+ };
13
29
  export default class GeoGirafeApi extends GirafeHTMLElement {
14
30
  templateUrl = null;
15
31
  styleUrls = null;
@@ -46,7 +62,7 @@ export default class GeoGirafeApi extends GirafeHTMLElement {
46
62
  });
47
63
  }
48
64
  static get observedAttributes() {
49
- return ['center', 'zoom', 'basemap', 'basemapselector', 'crosshair', 'tooltip', 'markers', 'layers'];
65
+ return ['center', 'zoom', 'basemap', 'basemapselector', 'crosshair', 'tooltip', 'markers', 'markersfile', 'layers'];
50
66
  }
51
67
  attributeChangedCallback(name, oldValue, newValue) {
52
68
  if (this.isInitialized) {
@@ -73,6 +89,9 @@ export default class GeoGirafeApi extends GirafeHTMLElement {
73
89
  else if (name === 'markers') {
74
90
  this.manageMarkersAttribute();
75
91
  }
92
+ else if (name === 'markersfile') {
93
+ this.manageMarkersFileAttribute();
94
+ }
76
95
  else if (name === 'layers') {
77
96
  this.manageLayersAttribute();
78
97
  }
@@ -95,6 +114,7 @@ export default class GeoGirafeApi extends GirafeHTMLElement {
95
114
  this.manageCrosshairAttribute();
96
115
  this.manageTooltipAttribute();
97
116
  this.manageMarkersAttribute();
117
+ this.manageMarkersFileAttribute();
98
118
  this.manageLayersAttribute();
99
119
  this.manageSelectionboxAttribute();
100
120
  this.manageUserInteraction();
@@ -245,17 +265,10 @@ export default class GeoGirafeApi extends GirafeHTMLElement {
245
265
  const markers = this.getAttributeFromConfig('markers');
246
266
  if (markers) {
247
267
  const markerValues = markers.split(';');
248
- for (const makerValue of markerValues) {
249
- const content = makerValue.split('|');
250
- const coords = content[0].split(',');
251
- const x = Number(coords[0].trim());
252
- const y = Number(coords[1].trim());
253
- if (!Number.isNaN(x) && !Number.isNaN(y)) {
254
- const imageUrl = content[1].trim();
255
- this.context.stateManager.state.position.markers.push({
256
- position: [x, y],
257
- imageUrl: imageUrl
258
- });
268
+ for (const markerValue of markerValues) {
269
+ const marker = this.markerStringToMapMarker(markerValue);
270
+ if (marker) {
271
+ this.context.stateManager.state.position.markers.push(marker);
259
272
  }
260
273
  else {
261
274
  console.warn('Invalid marker coordinates');
@@ -263,6 +276,119 @@ export default class GeoGirafeApi extends GirafeHTMLElement {
263
276
  }
264
277
  }
265
278
  }
279
+ markerStringToMapMarker(markerValue) {
280
+ console.log('markerStringToMapMarker', markerValue);
281
+ const contentParts = markerValue.split('|');
282
+ const needToGuessThirdParameter = contentParts.length == 3;
283
+ if (needToGuessThirdParameter) {
284
+ console.warn(`Marker has only 3 Parameters! Guessing if third Parameter '${contentParts[2]}' is Size or Offset`);
285
+ }
286
+ const coords = contentParts[0].split(',');
287
+ const x = Number(coords[0].trim());
288
+ const y = Number(coords[1].trim());
289
+ if (!Number.isNaN(x) && !Number.isNaN(y)) {
290
+ const imageUrl = contentParts[1].trim();
291
+ let size;
292
+ let offset;
293
+ if (needToGuessThirdParameter) {
294
+ if (contentParts[2].match(/[+-]+/gm)) {
295
+ offset = splitTrimAndConvertToNumber(contentParts[2]);
296
+ }
297
+ else {
298
+ size = splitTrimAndConvertToNumber(contentParts[2]);
299
+ }
300
+ }
301
+ else if (contentParts.length > 2) {
302
+ size = splitTrimAndConvertToNumber(contentParts[2]);
303
+ offset = splitTrimAndConvertToNumber(contentParts[3]);
304
+ }
305
+ return {
306
+ position: [x, y],
307
+ imageUrl: imageUrl,
308
+ size: size,
309
+ offset: offset
310
+ };
311
+ }
312
+ else {
313
+ return undefined;
314
+ }
315
+ }
316
+ manageMarkersFileAttribute() {
317
+ const markersFile = this.getAttributeFromConfig('markersfile');
318
+ if (markersFile) {
319
+ void this.readMarkersFromFile(markersFile).then((markers) => {
320
+ this.context.stateManager.state.position.markers.push(...markers);
321
+ });
322
+ }
323
+ }
324
+ async readMarkersFromFile(fileUrl) {
325
+ const response = await fetch(fileUrl);
326
+ if (!response.ok) {
327
+ console.warn(`Cannot read markers file '${fileUrl}': ${response.status} ${response.statusText}`);
328
+ return [];
329
+ }
330
+ const fileContent = await response.text();
331
+ const lines = fileContent
332
+ .split(/\r?\n/)
333
+ .map((line) => line.trim())
334
+ .filter((line) => line.length > 0);
335
+ if (lines.length < 2) {
336
+ return [];
337
+ }
338
+ const headers = lines[0].split('\t').map((header) => header.trim());
339
+ const pointIndex = headers.indexOf('point');
340
+ const iconIndex = headers.indexOf('icon');
341
+ const iconSizeIndex = headers.indexOf('iconSize');
342
+ const iconOffsetIndex = headers.indexOf('iconOffset');
343
+ if (pointIndex === -1 || iconIndex === -1) {
344
+ console.warn(`Invalid markers file '${fileUrl}': missing required 'point' or 'icon' column`);
345
+ return [];
346
+ }
347
+ const markers = [];
348
+ const legacy = fileUrl.includes('legacy');
349
+ for (const line of lines.slice(1)) {
350
+ await this.lineToMarker(line, pointIndex, iconIndex, iconSizeIndex, iconOffsetIndex, legacy).then((marker) => {
351
+ if (marker) {
352
+ markers.push(marker);
353
+ }
354
+ });
355
+ }
356
+ return markers;
357
+ }
358
+ async lineToMarker(line, pointIndex, iconIndex, iconSizeIndex, iconOffsetIndex, legacy) {
359
+ const columns = line.split('\t');
360
+ const coords = splitTrimAndConvertToNumber(columns[pointIndex]);
361
+ const imageUrl = columns[iconIndex]?.trim();
362
+ if (coords.length < 2 || !imageUrl || Number.isNaN(coords[0]) || Number.isNaN(coords[1])) {
363
+ console.warn(`Invalid marker line ': ${line}`);
364
+ return undefined;
365
+ }
366
+ // In the old WebGIS the coordinates are in the wrong order. This is to ensure compatibility with older data.
367
+ if (coords[0] < coords[1] && this.context.stateManager.state.projection === 'EPSG:2056') {
368
+ coords.reverse();
369
+ }
370
+ const size = iconSizeIndex >= 0 && columns[iconSizeIndex]?.trim()
371
+ ? splitTrimAndConvertToNumber(columns[iconSizeIndex])
372
+ : undefined;
373
+ let offset = iconOffsetIndex >= 0 && columns[iconOffsetIndex]?.trim()
374
+ ? splitTrimAndConvertToNumber(columns[iconOffsetIndex])
375
+ : undefined;
376
+ // In the old WebGIS the Offset referred to the Size of the original Image, while now it refers to the Size of the
377
+ // resized Image. This is to ensure compatibility with older data.
378
+ if (offset && size && legacy) {
379
+ await getImageSize(imageUrl).then((imageSize) => {
380
+ const scaleX = size[0] / imageSize.width;
381
+ const scaleY = size[1] / imageSize.height;
382
+ offset = [offset[0] * scaleX, offset[1] * scaleY];
383
+ });
384
+ }
385
+ return {
386
+ position: [coords[0], coords[1]],
387
+ imageUrl,
388
+ size,
389
+ offset
390
+ };
391
+ }
266
392
  async initialize() {
267
393
  await this.context.initialize();
268
394
  // Register Coordinate Reference Systems (CRS) definitions in PROJ4
@@ -135,7 +135,7 @@ export default class MapComponent extends GirafeHTMLElement {
135
135
  this.subscribe('position.markers', () => {
136
136
  this.clearAllMarkers();
137
137
  for (const marker of this.state.position.markers) {
138
- this.addMarker(marker.position, marker.imageUrl);
138
+ this.addMarker(marker);
139
139
  }
140
140
  });
141
141
  this.subscribe('globe.display', async () => {
@@ -177,7 +177,7 @@ export default class MapComponent extends GirafeHTMLElement {
177
177
  }
178
178
  this.showCrosshair(this.state.position);
179
179
  for (const marker of this.state.position.markers) {
180
- this.addMarker(marker.position, marker.imageUrl);
180
+ this.addMarker(marker);
181
181
  }
182
182
  }
183
183
  });
@@ -1017,23 +1017,24 @@ export default class MapComponent extends GirafeHTMLElement {
1017
1017
  this.state.position = position;
1018
1018
  if (position.markers.length > 0) {
1019
1019
  // Add marker to the map
1020
- this.addMarker(position.markers[0].position, position.markers[0].imageUrl);
1020
+ this.addMarker(position.markers[0]);
1021
1021
  }
1022
1022
  }
1023
1023
  }
1024
1024
  clearAllMarkers() {
1025
1025
  this.markerSource.clear();
1026
1026
  }
1027
- addMarker(position, imageUrl) {
1027
+ addMarker(mapMarker) {
1028
1028
  const iconStyle = new Style({
1029
1029
  image: new Icon({
1030
- //anchor: [0.5, 1], // Point d'ancrage (centre en bas)
1031
- src: imageUrl
1032
- //scale: 0.5, // Ajustez la taille si nécessaire
1030
+ displacement: mapMarker.offset,
1031
+ src: mapMarker.imageUrl,
1032
+ width: mapMarker.size ? mapMarker.size[0] : undefined,
1033
+ height: mapMarker.size ? mapMarker.size[1] : undefined
1033
1034
  })
1034
1035
  });
1035
1036
  const marker = new Feature({
1036
- geometry: new Point(position)
1037
+ geometry: new Point(mapMarker.position)
1037
1038
  });
1038
1039
  marker.setStyle(iconStyle);
1039
1040
  this.markerSource.addFeature(marker);
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.2536443365",
8
+ "version": "1.1.0-dev.2557861681",
9
9
  "type": "module",
10
10
  "engines": {
11
11
  "node": ">=20.19.0"
@@ -113,6 +113,10 @@
113
113
  text-align: left;
114
114
  margin-bottom: 1rem;
115
115
  }
116
+ ul {
117
+ width: 100%;
118
+ margin: 0;
119
+ }
116
120
  </style>
117
121
 
118
122
  <script type="module" src="src/main.api.ts"></script>
@@ -290,6 +294,53 @@
290
294
  </div>
291
295
  </section>
292
296
 
297
+ <!-- Multiple markers with size and/or offset -->
298
+ <h2>Add multiple markers with size and/or offset on the map</h2>
299
+ <p class="descr">
300
+ Add multiple markers at the defined coordinates. <br />
301
+ You can specify size and offset.<br />
302
+ If you only specify three parameters, the third parameter can be the size if you <strong>don't</strong> include
303
+ a sign (e.g. <code>24,24</code>) or the offset if you <strong>do</strong> include a sign (e.g.
304
+ <code>+2,-6</code>).
305
+ </p>
306
+ <section>
307
+ <div class="row">
308
+ <div class="left">
309
+ <geogirafe-map markers="api.demo.markersWithSizeAndOrOffset" />
310
+ </div>
311
+ <div class="right"></div>
312
+ </div>
313
+ </section>
314
+
315
+ <!-- Multiple markers with size and/or offset -->
316
+ <h2>Add multiple markers on the map by providing a File/URL</h2>
317
+ <p class="descr">
318
+ You can provide a URL to a TXT file containing markers.<br />
319
+ Each line represents a marker with TAB-separated values.<br />
320
+ The file must contain the column names as header.
321
+ </p>
322
+ <p class="descr">Allowed column names:</p>
323
+ <ul>
324
+ <li><strong>point</strong>: coordinates where the amrker should be added.</li>
325
+ <li><strong>icon</strong>: url to the marker image</li>
326
+ <li><strong>iconSize</strong> (optional): size if the marker should be resized</li>
327
+ <li><strong>iconOffset</strong> (optional): offset if the marker should be shifted</li>
328
+ </ul>
329
+ <p class="descr">
330
+ Other columns will be ignored.<br />
331
+ Compatibility with the format used in the old GeoMapFish API will be managed if the URL to fo file contains the
332
+ keyword <code>legacy</code>.<br />
333
+ Coordinates can be given in both way (North,East) or (East,North) only if the SRID <code>CH:2056</code> is used.
334
+ </p>
335
+ <section>
336
+ <div class="row">
337
+ <div class="left">
338
+ <geogirafe-map markersfile="api.demo.markersFile" />
339
+ </div>
340
+ <div class="right"></div>
341
+ </div>
342
+ </section>
343
+
293
344
  <!-- Layers -->
294
345
  <h2>Add a layer to the map</h2>
295
346
  <p class="descr">Add a layer to the map. The layer name must be defined in the themes.json file.</p>
@@ -302,7 +353,7 @@
302
353
  </div>
303
354
  </section>
304
355
 
305
- <!-- Mulaiple layers -->
356
+ <!-- Multiple layers -->
306
357
  <h2>Add multiple layers to the map</h2>
307
358
  <p class="descr">Add multiple layers to the map. The layer names must be defined in the themes.json file.</p>
308
359
  <section>
@@ -394,10 +445,6 @@
394
445
  - projection: configure map projection
395
446
  - legend:
396
447
  - embeded: deactivate mouse scroll
397
- - marker: offset, size, ...
398
- - load markers from file
399
- - load data from file
400
-
401
448
  -->
402
449
  </body>
403
450
  </html>
@@ -1 +1 @@
1
- {"version":"1.1.0-dev.2536443365", "build":"2536443365", "date":"19/05/2026"}
1
+ {"version":"1.1.0-dev.2557861681", "build":"2557861681", "date":"28/05/2026"}
@@ -0,0 +1,8 @@
1
+ id point title description icon iconSize iconOffset
2
+ 1 1146337,2554168 Information Office de l'information<br />Tél: 032 000 00 00<br>Email: <a href="mailto:info@example.com">info@example.com</a><br />Internet: <a href="http://fr.wikipedia.org/wiki/La_Chaux-de-Fonds" target=new>Cliquer ici</a> api/marker-plus.png 21,25 -51,-90
3
+ 2 1146205,2554168 Ma première station Diesel pas cher api/marker2-plus.png 21,25 -51,-90
4
+ 3 1145605,2554168 Mon parking C'est celui-là le meilleur. api/marker2-plus.png 21,25 -51,-90
5
+ 4 1145542,2554168 Mon parking Ce parking est<br/>le meillleur. api/marker2-plus.png 21,25 -51,-90
6
+ 5 1145977,2554168 Ma deuxième station Sans-plomb pas cher. api/marker-plus.png 21,25 -51,-90
7
+ 6 1145631,2554175 Test marker 1. api/marker-plus.png 21,25 -51,-90
8
+ 7 1145472,2554507 Test marker 2. api/marker2-plus.png 21,25 -51,-90
@@ -0,0 +1,6 @@
1
+ id point title description icon iconSize iconOffset
2
+ 1 2611778,1266865 Popups konfigurieren <br>Popups können via der Text-Datei beliebig mit Inhalt gefüllt werden!<br/>Die anderen zwei Beispiel-Popups zeigen weitere Beispiele.<br><img src="/static/api/apihelp/img/geoportal-bs.jpg" width="300px"> api/marker-blue.png 21,25 -10.5,-25
3
+ 2 2611542,1267266 Klänge der Basler Fasnacht <br/><br/>Audio-Dateien in ein Popup einbinden:<br/><br/><audio controls><source src="/static/api/apihelp/BS_N19_Fasnacht_v1.mp3" type="audio/mp3">Your browser does not support the audio element.</audio> api/marker-green.png 21,25 -10.5,-25
4
+ 3 2613089,1267566 FILM AB! Für den GEO-Beruf <br>IFrames in ein Popup einbinden: <br/><br/><iframe width="312" height="175" src="https://www.youtube.com/embed/-Mw277Rgcyc" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> api/parking.png 21,25 -10.5,-25
5
+ 4 2611738,1267123 Test for Offset/Size Test for Offset/Size api/marker-huge.png 24,38 -162,-512
6
+ 5 2611738,1267123 Test for Offset/Size Test for Offset/Size api/marker-huge.png 24,38 -12,-38
package/tools/main.d.ts CHANGED
@@ -120,7 +120,7 @@ export { unByKeyAll, getOlayerByName, removeUnwantedOlParams, polygonFromCircle,
120
120
  export { getPropertyByPath, setPropertyByPath, createObjectFromPath, deletePropertyByPath, mergeObjects } from './utils/pathUtils.js';
121
121
  export { generateQrCode } from './utils/qrcode.js';
122
122
  export { default as ServiceWorkerHelper } from './utils/swhelper.js';
123
- export { systemIsInDarkMode, isSafari, isFirefox, getValidIndex, minMax, hexToRgbaArray, rgbStrToRgbaArray, colorToRgbaArray, isValidEmail, applyOpacityToLayers, applyFeaturesToSelection, linkify, applyDefaultPrefixToUrl } from './utils/utils.js';
123
+ export { systemIsInDarkMode, isSafari, isFirefox, getValidIndex, minMax, hexToRgbaArray, rgbStrToRgbaArray, colorToRgbaArray, isValidEmail, applyOpacityToLayers, applyFeaturesToSelection, linkify, applyDefaultPrefixToUrl, splitTrimAndConvertToNumber } from './utils/utils.js';
124
124
  export { default as VendorSpecificOgcServerManager } from './vendorspecificogcservermanager.js';
125
125
  export type { WfsClientOptions, WfsClientOptionalOptions, QueryableLayerWms, GetFeatureOptionsPartial } from './wfs/wfsclient.js';
126
126
  export { default as WfsClient, WfsClientMapServer, WfsClientQgis, WfsClientGeorama, WfsClientDefault, WfsClientGeoServer } from './wfs/wfsclient.js';
package/tools/main.js CHANGED
@@ -94,7 +94,7 @@ export { unByKeyAll, getOlayerByName, removeUnwantedOlParams, polygonFromCircle,
94
94
  export { getPropertyByPath, setPropertyByPath, createObjectFromPath, deletePropertyByPath, mergeObjects } from './utils/pathUtils.js';
95
95
  export { generateQrCode } from './utils/qrcode.js';
96
96
  export { default as ServiceWorkerHelper } from './utils/swhelper.js';
97
- export { systemIsInDarkMode, isSafari, isFirefox, getValidIndex, minMax, hexToRgbaArray, rgbStrToRgbaArray, colorToRgbaArray, isValidEmail, applyOpacityToLayers, applyFeaturesToSelection, linkify, applyDefaultPrefixToUrl } from './utils/utils.js';
97
+ export { systemIsInDarkMode, isSafari, isFirefox, getValidIndex, minMax, hexToRgbaArray, rgbStrToRgbaArray, colorToRgbaArray, isValidEmail, applyOpacityToLayers, applyFeaturesToSelection, linkify, applyDefaultPrefixToUrl, splitTrimAndConvertToNumber } from './utils/utils.js';
98
98
  export { default as VendorSpecificOgcServerManager } from './vendorspecificogcservermanager.js';
99
99
  export { default as WfsClient, WfsClientMapServer, WfsClientQgis, WfsClientGeorama, WfsClientDefault, WfsClientGeoServer } from './wfs/wfsclient.js';
100
100
  export { default as WfsFilter } from './wfs/wfsfilter.js';
@@ -2,6 +2,8 @@ import { Coordinate } from 'ol/coordinate.js';
2
2
  export type MapMarker = {
3
3
  imageUrl: string;
4
4
  position: Coordinate;
5
+ size?: number[];
6
+ offset?: number[];
5
7
  };
6
8
  declare class MapPosition {
7
9
  center: Coordinate;
@@ -31,7 +31,9 @@ class MapPosition {
31
31
  for (const marker of this.markers) {
32
32
  position.markers.push({
33
33
  imageUrl: marker.imageUrl,
34
- position: marker.position
34
+ position: marker.position,
35
+ size: marker.size,
36
+ offset: marker.offset
35
37
  });
36
38
  }
37
39
  return position;
@@ -21,7 +21,12 @@ export default class PermalinkManager extends GirafeSingleton {
21
21
  hasMapPosition(): boolean | "" | null;
22
22
  private hasToolTip;
23
23
  private hasMarker;
24
+ private hasMarkerSize;
25
+ private hasMarkerOffset;
24
26
  getMapPosition(targetProjection: Projection): MapPosition | undefined;
27
+ private addCenter;
28
+ private addTooltip;
29
+ private addMarker;
25
30
  hasSearch(): boolean;
26
31
  getSearchTerm(): string;
27
32
  hasThemes(): boolean;
@@ -5,6 +5,7 @@ import MapPosition from '../state/mapposition.js';
5
5
  import { get as getProjection, transform } from 'ol/proj.js';
6
6
  import { isCoordinateInDegrees } from '../utils/olutils.js';
7
7
  import { BASEMAP_VISIBLE_PARAMETER, SEARCH_VISIBLE_PARAMETER } from './permalinkmanager-constants.js';
8
+ import { splitTrimAndConvertToNumber } from '../utils/utils.js';
8
9
  export default class PermalinkManager extends GirafeSingleton {
9
10
  urlParamKeys = [
10
11
  'map_x',
@@ -13,6 +14,8 @@ export default class PermalinkManager extends GirafeSingleton {
13
14
  'map_crosshair',
14
15
  'map_tooltip',
15
16
  'map_marker',
17
+ 'map_marker_size',
18
+ 'map_marker_offset',
16
19
  'search',
17
20
  'basemap',
18
21
  'themes',
@@ -83,46 +86,71 @@ export default class PermalinkManager extends GirafeSingleton {
83
86
  hasMarker() {
84
87
  return this.params['map_marker'] !== null;
85
88
  }
89
+ hasMarkerSize() {
90
+ return this.params['map_marker_size'] !== null;
91
+ }
92
+ hasMarkerOffset() {
93
+ return this.params['map_marker_offset'] !== null;
94
+ }
86
95
  getMapPosition(targetProjection) {
87
96
  if (this.hasMapPosition()) {
88
97
  const position = new MapPosition();
89
- let center = [Number.parseFloat(this.params['map_x']), Number.parseFloat(this.params['map_y'])];
90
- // Transform position to the target projection by making an educated guess about the current CRS
91
- // of the permalink map position
92
- const defaultProjection = this.context.configManager.getDefaultConfigValue('map.srid');
93
- const projectionInUrl = getProjection(isCoordinateInDegrees(position.center) ? 'EPSG:4326' : defaultProjection);
94
- if (projectionInUrl.getCode() !== this.state.projection) {
95
- center = transform(center, projectionInUrl, targetProjection);
96
- }
97
- position.center = center;
98
+ this.addCenter(position, targetProjection);
98
99
  const defaultZoom = this.context.configManager.getDefaultConfigValue('map.startZoom');
99
100
  position.zoom = Number.parseInt(this.params['map_zoom'] ?? defaultZoom);
100
101
  if (this.params['map_crosshair'] === 'true') {
101
- position.crosshair = center;
102
- }
103
- if (this.hasToolTip()) {
104
- const content = DOMPurify.sanitize(this.params['map_tooltip'], {
105
- ALLOWED_TAGS: ['br', 'b', 'div', 'em', 'i', 'p', 'strong'],
106
- ALLOWED_ATTR: []
107
- });
108
- position.tooltip = {
109
- content: content,
110
- position: center
111
- };
112
- }
113
- if (this.hasMarker()) {
114
- const imageUrl = DOMPurify.sanitize(this.params['map_marker']);
115
- position.markers.push({
116
- imageUrl: imageUrl,
117
- position: center
118
- });
102
+ position.crosshair = position.center;
119
103
  }
104
+ this.addTooltip(position);
105
+ this.addMarker(position);
120
106
  if (position.isValid) {
121
107
  return position;
122
108
  }
123
109
  }
124
110
  return undefined;
125
111
  }
112
+ addCenter(position, targetProjection) {
113
+ let center = [Number.parseFloat(this.params['map_x']), Number.parseFloat(this.params['map_y'])];
114
+ // Transform position to the target projection by making an educated guess about the current CRS
115
+ // of the permalink map position
116
+ const defaultProjection = this.context.configManager.getDefaultConfigValue('map.srid');
117
+ const projectionInUrl = getProjection(isCoordinateInDegrees(position.center) ? 'EPSG:4326' : defaultProjection);
118
+ if (projectionInUrl.getCode() !== this.state.projection) {
119
+ center = transform(center, projectionInUrl, targetProjection);
120
+ }
121
+ position.center = center;
122
+ }
123
+ addTooltip(position) {
124
+ if (this.hasToolTip()) {
125
+ const content = DOMPurify.sanitize(this.params['map_tooltip'], {
126
+ ALLOWED_TAGS: ['br', 'b', 'div', 'em', 'i', 'p', 'strong'],
127
+ ALLOWED_ATTR: []
128
+ });
129
+ position.tooltip = {
130
+ content: content,
131
+ position: position.center
132
+ };
133
+ }
134
+ }
135
+ addMarker(position) {
136
+ if (this.hasMarker()) {
137
+ const imageUrl = DOMPurify.sanitize(this.params['map_marker']);
138
+ let size;
139
+ let offset;
140
+ if (this.hasMarkerSize()) {
141
+ size = splitTrimAndConvertToNumber(DOMPurify.sanitize(this.params['map_marker_size']));
142
+ }
143
+ if (this.hasMarkerOffset()) {
144
+ offset = splitTrimAndConvertToNumber(DOMPurify.sanitize(this.params['map_marker_offset']));
145
+ }
146
+ position.markers.push({
147
+ imageUrl: imageUrl,
148
+ position: position.center,
149
+ size: size,
150
+ offset: offset
151
+ });
152
+ }
153
+ }
126
154
  hasSearch() {
127
155
  return this.params['search'] !== null;
128
156
  }
@@ -77,3 +77,8 @@ export declare const linkify: (str: string) => string;
77
77
  * @returns
78
78
  */
79
79
  export declare function applyDefaultPrefixToUrl(context: IGirafeContext, metadataUrl?: string): string | undefined;
80
+ /**
81
+ * Splits a String containing Numbers into an Array of Numbers. The Separator is a comma.
82
+ * @param numbersAsString The String containing Numbers.
83
+ */
84
+ export declare const splitTrimAndConvertToNumber: (numbersAsString: string) => number[];
@@ -199,3 +199,13 @@ export function applyDefaultPrefixToUrl(context, metadataUrl) {
199
199
  }
200
200
  return metadataUrl;
201
201
  }
202
+ /**
203
+ * Splits a String containing Numbers into an Array of Numbers. The Separator is a comma.
204
+ * @param numbersAsString The String containing Numbers.
205
+ */
206
+ export const splitTrimAndConvertToNumber = (numbersAsString) => {
207
+ return numbersAsString
208
+ .split(',')
209
+ .map((e) => e.trim())
210
+ .map(Number);
211
+ };