@geogirafe/lib-geoportal 1.1.0-dev.2407038460 → 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.
- package/api/apigeogirafeapp.d.ts +15 -0
- package/api/apigeogirafeapp.js +174 -15
- package/components/map/component.d.ts +5 -2
- package/components/map/component.js +36 -1
- package/package.json +4 -5
- package/styles/api.css +2 -0
- package/templates/api.html +205 -15
- package/templates/public/about.json +1 -1
- package/templates/vite.config.js +4 -0
- package/tools/configuration/girafeconfig.d.ts +14 -0
- package/tools/configuration/girafeconfig.js +5 -0
- package/tools/main.d.ts +1 -0
- package/tools/share/serializers/mappositionserializer.js +3 -1
- package/tools/share/serializers/mappositionserializer.spec.js +24 -2
- package/tools/state/mapposition.d.ts +5 -0
- package/tools/state/mapposition.js +7 -0
- package/tools/state/mapposition.spec.js +1 -0
- package/tools/themes/themeshelper.d.ts +2 -1
- package/tools/themes/themeshelper.js +34 -30
- package/tools/url/permalinkmanager.d.ts +2 -1
- package/tools/url/permalinkmanager.js +11 -0
- package/tools/url/permalinkmanager.spec.js +1 -0
package/api/apigeogirafeapp.d.ts
CHANGED
|
@@ -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
|
}
|
package/api/apigeogirafeapp.js
CHANGED
|
@@ -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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
89
|
-
|
|
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
|
-
|
|
92
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
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.
|
|
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.
|
|
98
|
-
"vite-bundle-analyzer": "1.3.
|
|
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.
|
|
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
package/templates/api.html
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1, shrink-to-fit=no" />
|
|
9
9
|
|
|
10
10
|
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
|
11
|
+
|
|
11
12
|
<title>GeoGirafe API</title>
|
|
12
13
|
<style>
|
|
13
14
|
body {
|
|
@@ -66,6 +67,7 @@
|
|
|
66
67
|
.left {
|
|
67
68
|
width: 50%;
|
|
68
69
|
height: 100%;
|
|
70
|
+
position: relative;
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
.right {
|
|
@@ -73,7 +75,8 @@
|
|
|
73
75
|
height: 100%;
|
|
74
76
|
}
|
|
75
77
|
|
|
76
|
-
|
|
78
|
+
geogirafe-map {
|
|
79
|
+
position: absolute;
|
|
77
80
|
display: block;
|
|
78
81
|
width: 100%;
|
|
79
82
|
height: 100%;
|
|
@@ -102,29 +105,54 @@
|
|
|
102
105
|
h2 {
|
|
103
106
|
border-bottom: solid 1px #aaa;
|
|
104
107
|
padding-bottom: 0.5rem;
|
|
108
|
+
margin-bottom: 0;
|
|
109
|
+
width: 100%;
|
|
110
|
+
}
|
|
111
|
+
.descr {
|
|
105
112
|
width: 100%;
|
|
113
|
+
text-align: left;
|
|
114
|
+
margin-bottom: 1rem;
|
|
106
115
|
}
|
|
107
116
|
</style>
|
|
117
|
+
|
|
108
118
|
<script type="module" src="./src/api/api.ts"></script>
|
|
119
|
+
<link rel="stylesheet" href="./src/styles/api.css" />
|
|
120
|
+
|
|
109
121
|
<script>
|
|
110
122
|
document.addEventListener('DOMContentLoaded', () => {
|
|
111
123
|
const apiOrigin = `${window.location.origin}${window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/'))}`;
|
|
112
124
|
const elem = document.getElementById('include-code');
|
|
113
125
|
|
|
114
126
|
elem.textContent = elem.textContent.replace('src="/geogirafe-api.js"', `src="${apiOrigin}/geogirafe-api.js"`);
|
|
127
|
+
elem.textContent = elem.textContent.replace(
|
|
128
|
+
'href="/geogirafe-api.css"',
|
|
129
|
+
`src="${apiOrigin}/geogirafe-api.css"`
|
|
130
|
+
);
|
|
115
131
|
|
|
116
132
|
const sections = document.querySelectorAll('section');
|
|
117
|
-
|
|
133
|
+
for (const section of sections) {
|
|
118
134
|
const leftDiv = section.querySelector('.left');
|
|
119
135
|
const rightDiv = section.querySelector('.right');
|
|
120
136
|
if (leftDiv && rightDiv) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
137
|
+
const ggMap = leftDiv.querySelector('geogirafe-map');
|
|
138
|
+
if (ggMap) {
|
|
139
|
+
ggMap.addEventListener('geogirafe-api-ready', () => {
|
|
140
|
+
let content = leftDiv.innerHTML;
|
|
141
|
+
content = content
|
|
142
|
+
.replaceAll(/\s+/g, ' ')
|
|
143
|
+
.replaceAll(/>\s+/g, '>')
|
|
144
|
+
.replaceAll(/\s+</g, '<')
|
|
145
|
+
.replaceAll('=""', '')
|
|
146
|
+
.replaceAll(' style="display: block;"', '')
|
|
147
|
+
.replaceAll('><', '>\n<')
|
|
148
|
+
.trim();
|
|
149
|
+
const pre = document.createElement('pre');
|
|
150
|
+
pre.textContent = content;
|
|
151
|
+
rightDiv.appendChild(pre);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
126
154
|
}
|
|
127
|
-
}
|
|
155
|
+
}
|
|
128
156
|
});
|
|
129
157
|
</script>
|
|
130
158
|
</head>
|
|
@@ -150,12 +178,13 @@
|
|
|
150
178
|
<pre id="include-code">
|
|
151
179
|
<head>
|
|
152
180
|
<script type="module" crossorigin src="/geogirafe-api.js"></script>
|
|
181
|
+
<link rel="stylesheet" href="/geogirafe-api.css" />
|
|
153
182
|
</head></pre
|
|
154
183
|
>
|
|
155
184
|
</section>
|
|
156
185
|
|
|
157
|
-
<!-- Simple map -->
|
|
158
186
|
<h2>Add a simple map view</h2>
|
|
187
|
+
<p class="descr">Add a simple map to the page, with its default configuration.</p>
|
|
159
188
|
<section>
|
|
160
189
|
<div class="row">
|
|
161
190
|
<div class="left">
|
|
@@ -167,10 +196,11 @@
|
|
|
167
196
|
|
|
168
197
|
<!-- With center -->
|
|
169
198
|
<h2>Add a map with center coordinates</h2>
|
|
199
|
+
<p class="descr">Set the default center point.</p>
|
|
170
200
|
<section>
|
|
171
201
|
<div class="row">
|
|
172
202
|
<div class="left">
|
|
173
|
-
<geogirafe-map center="
|
|
203
|
+
<geogirafe-map center="api.demo.center" />
|
|
174
204
|
</div>
|
|
175
205
|
<div class="right"></div>
|
|
176
206
|
</div>
|
|
@@ -178,10 +208,11 @@
|
|
|
178
208
|
|
|
179
209
|
<!-- Custom zoom level -->
|
|
180
210
|
<h2>Add a map with a custom zoomlevel</h2>
|
|
211
|
+
<p class="descr">Set the default zoom level.</p>
|
|
181
212
|
<section>
|
|
182
213
|
<div class="row">
|
|
183
214
|
<div class="left">
|
|
184
|
-
<geogirafe-map zoom="
|
|
215
|
+
<geogirafe-map zoom="api.demo.zoom" />
|
|
185
216
|
</div>
|
|
186
217
|
<div class="right"></div>
|
|
187
218
|
</div>
|
|
@@ -189,17 +220,19 @@
|
|
|
189
220
|
|
|
190
221
|
<!-- Custom basemap -->
|
|
191
222
|
<h2>Add a map with a custom basemap</h2>
|
|
223
|
+
<p class="descr">Set the default basemap.</p>
|
|
192
224
|
<section>
|
|
193
225
|
<div class="row">
|
|
194
226
|
<div class="left">
|
|
195
|
-
<geogirafe-map basemap="
|
|
227
|
+
<geogirafe-map basemap="api.demo.basemap" />
|
|
196
228
|
</div>
|
|
197
229
|
<div class="right"></div>
|
|
198
230
|
</div>
|
|
199
231
|
</section>
|
|
200
232
|
|
|
201
|
-
<!-- Basemap selector
|
|
202
|
-
|
|
233
|
+
<!-- Basemap selector -->
|
|
234
|
+
<h2>Add a map with a basemap selector</h2>
|
|
235
|
+
<p class="descr">Activate the basemap selector, allowing the user to select a different basemap.</p>
|
|
203
236
|
<section>
|
|
204
237
|
<div class="row">
|
|
205
238
|
<div class="left">
|
|
@@ -207,7 +240,164 @@
|
|
|
207
240
|
</div>
|
|
208
241
|
<div class="right"></div>
|
|
209
242
|
</div>
|
|
210
|
-
</section>
|
|
243
|
+
</section>
|
|
244
|
+
|
|
245
|
+
<!-- Crosshair -->
|
|
246
|
+
<h2>Add a crosshair somewhere on the map</h2>
|
|
247
|
+
<p class="descr">Add a cross at the defined coordinates.</p>
|
|
248
|
+
<section>
|
|
249
|
+
<div class="row">
|
|
250
|
+
<div class="left">
|
|
251
|
+
<geogirafe-map crosshair="api.demo.crosshair" />
|
|
252
|
+
</div>
|
|
253
|
+
<div class="right"></div>
|
|
254
|
+
</div>
|
|
255
|
+
</section>
|
|
256
|
+
|
|
257
|
+
<!-- Tooltip -->
|
|
258
|
+
<h2>Add a toolip somewhere on the map</h2>
|
|
259
|
+
<p class="descr">Add a tooltip with cutom text at the defined coordinates.</p>
|
|
260
|
+
<section>
|
|
261
|
+
<div class="row">
|
|
262
|
+
<div class="left">
|
|
263
|
+
<geogirafe-map tooltip="api.demo.tooltip" />
|
|
264
|
+
</div>
|
|
265
|
+
<div class="right"></div>
|
|
266
|
+
</div>
|
|
267
|
+
</section>
|
|
268
|
+
|
|
269
|
+
<!-- Markers -->
|
|
270
|
+
<h2>Add a marker somewhere on the map</h2>
|
|
271
|
+
<p class="descr">Add a marker at the defined coordinates.</p>
|
|
272
|
+
<section>
|
|
273
|
+
<div class="row">
|
|
274
|
+
<div class="left">
|
|
275
|
+
<geogirafe-map markers="api.demo.marker" />
|
|
276
|
+
</div>
|
|
277
|
+
<div class="right"></div>
|
|
278
|
+
</div>
|
|
279
|
+
</section>
|
|
280
|
+
|
|
281
|
+
<!-- Multiple markers -->
|
|
282
|
+
<h2>Add multiple markers on the map</h2>
|
|
283
|
+
<p class="descr">Add multiple markers at the defined coordinates.</p>
|
|
284
|
+
<section>
|
|
285
|
+
<div class="row">
|
|
286
|
+
<div class="left">
|
|
287
|
+
<geogirafe-map markers="api.demo.markers" />
|
|
288
|
+
</div>
|
|
289
|
+
<div class="right"></div>
|
|
290
|
+
</div>
|
|
291
|
+
</section>
|
|
292
|
+
|
|
293
|
+
<!-- Layers -->
|
|
294
|
+
<h2>Add a layer to the map</h2>
|
|
295
|
+
<p class="descr">Add a layer to the map. The layer name must be defined in the themes.json file.</p>
|
|
296
|
+
<section>
|
|
297
|
+
<div class="row">
|
|
298
|
+
<div class="left">
|
|
299
|
+
<geogirafe-map layers="api.demo.layers" />
|
|
300
|
+
</div>
|
|
301
|
+
<div class="right"></div>
|
|
302
|
+
</div>
|
|
303
|
+
</section>
|
|
304
|
+
|
|
305
|
+
<!-- Mulaiple layers -->
|
|
306
|
+
<h2>Add multiple layers to the map</h2>
|
|
307
|
+
<p class="descr">Add multiple layers to the map. The layer names must be defined in the themes.json file.</p>
|
|
308
|
+
<section>
|
|
309
|
+
<div class="row">
|
|
310
|
+
<div class="left">
|
|
311
|
+
<geogirafe-map layers="api.demo.multiLayers" />
|
|
312
|
+
</div>
|
|
313
|
+
<div class="right"></div>
|
|
314
|
+
</div>
|
|
315
|
+
</section>
|
|
316
|
+
|
|
317
|
+
<!-- Layers with config -->
|
|
318
|
+
<h2>Add a layer to the map, including default opacity and filter</h2>
|
|
319
|
+
<p class="descr">
|
|
320
|
+
Add a layer to the map. The layer name must be defined in the themes.json file. The layer options follow the
|
|
321
|
+
format defined in the
|
|
322
|
+
<a target="_blank" href="https://doc.geogirafe.org/docs/core-concepts/permalink#layers-configuration"
|
|
323
|
+
>documentation</a
|
|
324
|
+
>.
|
|
325
|
+
</p>
|
|
326
|
+
<section>
|
|
327
|
+
<div class="row">
|
|
328
|
+
<div class="left">
|
|
329
|
+
<geogirafe-map center="api.demo.layersWithConfigCenter" layers="api.demo.layersWithConfig" />
|
|
330
|
+
</div>
|
|
331
|
+
<div class="right"></div>
|
|
332
|
+
</div>
|
|
333
|
+
</section>
|
|
334
|
+
|
|
335
|
+
<!-- Search bar -->
|
|
336
|
+
<h2>Add a map with a search bar</h2>
|
|
337
|
+
<p class="descr">Activate the searchbar, allowing the user to search for objects and layers.</p>
|
|
338
|
+
<section>
|
|
339
|
+
<div class="row">
|
|
340
|
+
<div class="left">
|
|
341
|
+
<geogirafe-map searchbar />
|
|
342
|
+
</div>
|
|
343
|
+
<div class="right"></div>
|
|
344
|
+
</div>
|
|
345
|
+
</section>
|
|
346
|
+
|
|
347
|
+
<!-- Selection box -->
|
|
348
|
+
<h2>Add a map and allow selection</h2>
|
|
349
|
+
<p class="descr">
|
|
350
|
+
Activate the selection window, allowing the user to select objects and display their properties.
|
|
351
|
+
</p>
|
|
352
|
+
<section>
|
|
353
|
+
<div class="row">
|
|
354
|
+
<div class="left">
|
|
355
|
+
<geogirafe-map layers="api.demo.layers" selectionbox />
|
|
356
|
+
</div>
|
|
357
|
+
<div class="right"></div>
|
|
358
|
+
</div>
|
|
359
|
+
</section>
|
|
360
|
+
|
|
361
|
+
<!-- Set values with javascript -->
|
|
362
|
+
<h2>Set map values with javascript</h2>
|
|
363
|
+
<p class="descr">This example shows how to use javascript to set attributes after the map creation.</p>
|
|
364
|
+
<section>
|
|
365
|
+
<div class="row">
|
|
366
|
+
<div class="left">
|
|
367
|
+
<button onclick="document.getElementById('my-map').setAttribute('center', 'api.demo.center');">
|
|
368
|
+
Recenter
|
|
369
|
+
</button>
|
|
370
|
+
<button onclick="document.getElementById('my-map').setAttribute('zoom', 'api.demo.zoom');">Zoom</button>
|
|
371
|
+
<button onclick="document.getElementById('my-map').setAttribute('basemap', 'api.demo.basemap');">
|
|
372
|
+
Set Basemap
|
|
373
|
+
</button>
|
|
374
|
+
<button onclick="document.getElementById('my-map').setAttribute('basemapselector', '');">
|
|
375
|
+
Enable basemap selector
|
|
376
|
+
</button>
|
|
377
|
+
<button onclick="document.getElementById('my-map').setAttribute('markers', 'api.demo.marker');">
|
|
378
|
+
Add Marker
|
|
379
|
+
</button>
|
|
380
|
+
<button onclick="document.getElementById('my-map').setAttribute('layers', 'api.demo.layers');">
|
|
381
|
+
Add Layer
|
|
382
|
+
</button>
|
|
383
|
+
<geogirafe-map id="my-map" />
|
|
384
|
+
</div>
|
|
385
|
+
<div class="right"></div>
|
|
386
|
+
</div>
|
|
387
|
+
</section>
|
|
211
388
|
</div>
|
|
389
|
+
|
|
390
|
+
<!--
|
|
391
|
+
Still missing:
|
|
392
|
+
- searchfor: centerto search result
|
|
393
|
+
- center to bounding box
|
|
394
|
+
- projection: configure map projection
|
|
395
|
+
- legend:
|
|
396
|
+
- embeded: deactivate mouse scroll
|
|
397
|
+
- marker: offset, size, ...
|
|
398
|
+
- load markers from file
|
|
399
|
+
- load data from file
|
|
400
|
+
|
|
401
|
+
-->
|
|
212
402
|
</body>
|
|
213
403
|
</html>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"1.1.0-dev.
|
|
1
|
+
{"version":"1.1.0-dev.2407606313", "build":"2407606313", "date":"25/03/2026"}
|
package/templates/vite.config.js
CHANGED
|
@@ -96,6 +96,10 @@ export default defineConfig(({ command, mode }) => {
|
|
|
96
96
|
return 'geogirafe-api.js';
|
|
97
97
|
}
|
|
98
98
|
return 'assets/[name].[hash].js';
|
|
99
|
+
},
|
|
100
|
+
assetFileNames: (assetInfo) => {
|
|
101
|
+
if (assetInfo.name == 'api.css') return 'geogirafe-api.css';
|
|
102
|
+
return 'assets/[name].[hash].[ext]';
|
|
99
103
|
}
|
|
100
104
|
}
|
|
101
105
|
}
|
|
@@ -223,6 +223,19 @@ declare class GirafeConfig {
|
|
|
223
223
|
description: string;
|
|
224
224
|
}[];
|
|
225
225
|
};
|
|
226
|
+
api?: {
|
|
227
|
+
demo: {
|
|
228
|
+
center: string;
|
|
229
|
+
zoom: string;
|
|
230
|
+
basemap: string;
|
|
231
|
+
crosshair: string;
|
|
232
|
+
tooltip: string;
|
|
233
|
+
layers: string;
|
|
234
|
+
multiLayers: string;
|
|
235
|
+
layersWithConfig: string;
|
|
236
|
+
layersWithConfigCenter: string;
|
|
237
|
+
};
|
|
238
|
+
};
|
|
226
239
|
extendedConfig?: Record<string, object>;
|
|
227
240
|
static readonly DEFAULT_LOCALE = "en-US";
|
|
228
241
|
/**
|
|
@@ -261,5 +274,6 @@ declare class GirafeConfig {
|
|
|
261
274
|
private initExternalLayers;
|
|
262
275
|
private initExtendedConfig;
|
|
263
276
|
private initOnboarding;
|
|
277
|
+
private initApiConfig;
|
|
264
278
|
}
|
|
265
279
|
export default GirafeConfig;
|
|
@@ -28,6 +28,7 @@ class GirafeConfig {
|
|
|
28
28
|
userdata;
|
|
29
29
|
contact;
|
|
30
30
|
onboarding;
|
|
31
|
+
api;
|
|
31
32
|
// The extended configuration can be used by third-party components or extensions
|
|
32
33
|
// to add custom attributes to the GirafeConfig.
|
|
33
34
|
extendedConfig;
|
|
@@ -66,6 +67,7 @@ class GirafeConfig {
|
|
|
66
67
|
this.contact = this.initConfigContact(config);
|
|
67
68
|
this.extendedConfig = this.initExtendedConfig(config);
|
|
68
69
|
this.onboarding = this.initOnboarding(config);
|
|
70
|
+
this.api = this.initApiConfig(config);
|
|
69
71
|
try {
|
|
70
72
|
this.search = this.initConfigSearch(config);
|
|
71
73
|
}
|
|
@@ -403,5 +405,8 @@ class GirafeConfig {
|
|
|
403
405
|
initOnboarding(config) {
|
|
404
406
|
return config.onboarding ?? undefined;
|
|
405
407
|
}
|
|
408
|
+
initApiConfig(config) {
|
|
409
|
+
return config.api ?? undefined;
|
|
410
|
+
}
|
|
406
411
|
}
|
|
407
412
|
export default GirafeConfig;
|
package/tools/main.d.ts
CHANGED
|
@@ -76,6 +76,7 @@ export type { default as IGirafePanel } from './state/igirafepanel.js';
|
|
|
76
76
|
export { isGirafePanel } from './state/igirafepanel.js';
|
|
77
77
|
export { default as LayersConfig } from './state/layersConfig.js';
|
|
78
78
|
export { default as MapManager } from './state/mapManager.js';
|
|
79
|
+
export type { MapMarker } from './state/mapposition.js';
|
|
79
80
|
export { default as MapPosition } from './state/mapposition.js';
|
|
80
81
|
export type { InitialSelectionQuery } from './state/objectselection.js';
|
|
81
82
|
export { default as ObjectSelection } from './state/objectselection.js';
|
|
@@ -12,7 +12,8 @@ export default class MapPositionSerializer {
|
|
|
12
12
|
center: mapPosition.center,
|
|
13
13
|
resolution: mapPosition.resolution,
|
|
14
14
|
crosshair: mapPosition.crosshair,
|
|
15
|
-
tooltip: mapPosition.tooltip
|
|
15
|
+
tooltip: mapPosition.tooltip,
|
|
16
|
+
markers: mapPosition.markers
|
|
16
17
|
};
|
|
17
18
|
return JSON.stringify(pos);
|
|
18
19
|
}
|
|
@@ -23,6 +24,7 @@ export default class MapPositionSerializer {
|
|
|
23
24
|
mapPosition.resolution = pos.resolution;
|
|
24
25
|
mapPosition.crosshair = pos.crosshair;
|
|
25
26
|
mapPosition.tooltip = pos.tooltip;
|
|
27
|
+
mapPosition.markers = pos.markers;
|
|
26
28
|
this.state.position = mapPosition;
|
|
27
29
|
}
|
|
28
30
|
}
|
|
@@ -26,6 +26,10 @@ describe('MapPositionSerializer.serialize', () => {
|
|
|
26
26
|
content: 'Test tooltip',
|
|
27
27
|
position: [10, 20]
|
|
28
28
|
};
|
|
29
|
+
mapPosition.markers.push({
|
|
30
|
+
imageUrl: 'http://url.to.marker',
|
|
31
|
+
position: [11, 22]
|
|
32
|
+
});
|
|
29
33
|
const serialized = serializer.brainSerialize(mapPosition);
|
|
30
34
|
expect(serialized).toBe(JSON.stringify({
|
|
31
35
|
center: [10, 20],
|
|
@@ -34,7 +38,13 @@ describe('MapPositionSerializer.serialize', () => {
|
|
|
34
38
|
tooltip: {
|
|
35
39
|
content: 'Test tooltip',
|
|
36
40
|
position: [10, 20]
|
|
37
|
-
}
|
|
41
|
+
},
|
|
42
|
+
markers: [
|
|
43
|
+
{
|
|
44
|
+
imageUrl: 'http://url.to.marker',
|
|
45
|
+
position: [11, 22]
|
|
46
|
+
}
|
|
47
|
+
]
|
|
38
48
|
}));
|
|
39
49
|
});
|
|
40
50
|
});
|
|
@@ -47,7 +57,13 @@ describe('MapPositionSerializer.deserialize', () => {
|
|
|
47
57
|
tooltip: {
|
|
48
58
|
content: 'Test tooltip',
|
|
49
59
|
position: [10, 20]
|
|
50
|
-
}
|
|
60
|
+
},
|
|
61
|
+
markers: [
|
|
62
|
+
{
|
|
63
|
+
imageUrl: 'http://url.to.marker',
|
|
64
|
+
position: [11, 22]
|
|
65
|
+
}
|
|
66
|
+
]
|
|
51
67
|
});
|
|
52
68
|
serializer.brainDeserialize(json);
|
|
53
69
|
const state = context.stateManager.state;
|
|
@@ -59,6 +75,12 @@ describe('MapPositionSerializer.deserialize', () => {
|
|
|
59
75
|
content: 'Test tooltip',
|
|
60
76
|
position: [10, 20]
|
|
61
77
|
});
|
|
78
|
+
expect(state.position.markers).toEqual([
|
|
79
|
+
{
|
|
80
|
+
imageUrl: 'http://url.to.marker',
|
|
81
|
+
position: [11, 22]
|
|
82
|
+
}
|
|
83
|
+
]);
|
|
62
84
|
});
|
|
63
85
|
it('should override previous map position in state', () => {
|
|
64
86
|
const state = context.stateManager.state;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { Coordinate } from 'ol/coordinate.js';
|
|
2
|
+
export type MapMarker = {
|
|
3
|
+
imageUrl: string;
|
|
4
|
+
position: Coordinate;
|
|
5
|
+
};
|
|
2
6
|
declare class MapPosition {
|
|
3
7
|
center: Coordinate;
|
|
4
8
|
zoom?: number;
|
|
@@ -9,6 +13,7 @@ declare class MapPosition {
|
|
|
9
13
|
content: string;
|
|
10
14
|
position?: Coordinate;
|
|
11
15
|
};
|
|
16
|
+
markers: MapMarker[];
|
|
12
17
|
get isValid(): boolean;
|
|
13
18
|
clone(): MapPosition;
|
|
14
19
|
}
|
|
@@ -5,6 +5,7 @@ class MapPosition {
|
|
|
5
5
|
scale;
|
|
6
6
|
crosshair;
|
|
7
7
|
tooltip;
|
|
8
|
+
markers = [];
|
|
8
9
|
get isValid() {
|
|
9
10
|
if (Number.isNaN(this.resolution)) {
|
|
10
11
|
return false;
|
|
@@ -27,6 +28,12 @@ class MapPosition {
|
|
|
27
28
|
position: this.tooltip.position ? [...this.tooltip.position] : undefined
|
|
28
29
|
}
|
|
29
30
|
: undefined;
|
|
31
|
+
for (const marker of this.markers) {
|
|
32
|
+
position.markers.push({
|
|
33
|
+
imageUrl: marker.imageUrl,
|
|
34
|
+
position: marker.position
|
|
35
|
+
});
|
|
36
|
+
}
|
|
30
37
|
return position;
|
|
31
38
|
}
|
|
32
39
|
}
|
|
@@ -13,6 +13,7 @@ describe('MapPosition', () => {
|
|
|
13
13
|
expect(mapPosition.scale).toBeUndefined();
|
|
14
14
|
expect(mapPosition.crosshair).toBeUndefined();
|
|
15
15
|
expect(mapPosition.tooltip).toBeUndefined();
|
|
16
|
+
expect(mapPosition.markers.length).toBe(0);
|
|
16
17
|
});
|
|
17
18
|
describe('isValid', () => {
|
|
18
19
|
it('should return false if resolution is NaN', () => {
|
|
@@ -25,7 +25,8 @@ export default class ThemesHelper extends GirafeSingleton {
|
|
|
25
25
|
addThemesFromUrl(): boolean;
|
|
26
26
|
addGroupsFromUrl(): boolean;
|
|
27
27
|
addLayersFromUrl(): boolean;
|
|
28
|
-
|
|
28
|
+
addLayerFromName(layername: string): boolean;
|
|
29
|
+
private addLayerBaseFromName;
|
|
29
30
|
private extractLayerOptions;
|
|
30
31
|
private extractLayerOptionOpacity;
|
|
31
32
|
private extractLayerOptionFilter;
|
|
@@ -206,7 +206,7 @@ export default class ThemesHelper extends GirafeSingleton {
|
|
|
206
206
|
let added = false;
|
|
207
207
|
if (this.context.permalinkManager.hasGroups()) {
|
|
208
208
|
for (const groupname of this.context.permalinkManager.getGroups()) {
|
|
209
|
-
added = this.
|
|
209
|
+
added = this.addLayerBaseFromName(groupname, 'group') || added;
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
212
|
return added;
|
|
@@ -215,38 +215,40 @@ export default class ThemesHelper extends GirafeSingleton {
|
|
|
215
215
|
let added = false;
|
|
216
216
|
if (this.context.permalinkManager.hasLayers()) {
|
|
217
217
|
for (const layername of this.context.permalinkManager.getLayers()) {
|
|
218
|
-
added = this.
|
|
218
|
+
added = this.addLayerBaseFromName(layername, 'layer') || added;
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
return added;
|
|
222
222
|
}
|
|
223
|
-
|
|
224
|
-
|
|
223
|
+
addLayerFromName(layername) {
|
|
224
|
+
return this.addLayerBaseFromName(layername, 'layer');
|
|
225
|
+
}
|
|
226
|
+
addLayerBaseFromName(layer, type) {
|
|
225
227
|
const layerOptions = this.extractLayerOptions(layer, type);
|
|
226
|
-
if (layerOptions) {
|
|
227
|
-
const clonedTheme = this.getMinimalClonedThemeForLayer(layerOptions.originalLayer);
|
|
228
|
-
this.mergeLayerWithExistingLayerTree(clonedTheme, this.state.layers.layersList);
|
|
229
|
-
added = true;
|
|
230
|
-
if (layerOptions.active) {
|
|
231
|
-
const clonedLayer = this.findLayerRecursive(clonedTheme.children, layerOptions.originalLayer.name);
|
|
232
|
-
if (clonedLayer) {
|
|
233
|
-
this.context.layerManager.toggle(clonedLayer, 'on');
|
|
234
|
-
if (layerOptions.opacity) {
|
|
235
|
-
clonedLayer.opacity = layerOptions.opacity;
|
|
236
|
-
}
|
|
237
|
-
if (layerOptions.filter) {
|
|
238
|
-
clonedLayer.filter = layerOptions.filter;
|
|
239
|
-
}
|
|
240
|
-
if (layerOptions.timeRestriction) {
|
|
241
|
-
clonedLayer.timeRestriction = layerOptions.timeRestriction;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
else {
|
|
228
|
+
if (!layerOptions) {
|
|
247
229
|
console.warn(`Layer ${layer} cannot be found`);
|
|
230
|
+
return false;
|
|
248
231
|
}
|
|
249
|
-
|
|
232
|
+
const clonedTheme = this.getMinimalClonedThemeForLayer(layerOptions.originalLayer);
|
|
233
|
+
this.mergeLayerWithExistingLayerTree(clonedTheme, this.state.layers.layersList);
|
|
234
|
+
if (!layerOptions.active) {
|
|
235
|
+
// Layer added, but inactive. No further config needed
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
const clonedLayer = this.findLayerRecursive(this.state.layers.layersList, layerOptions.originalLayer.name);
|
|
239
|
+
if (clonedLayer) {
|
|
240
|
+
this.context.layerManager.toggle(clonedLayer, 'on');
|
|
241
|
+
if (layerOptions.opacity) {
|
|
242
|
+
clonedLayer.opacity = layerOptions.opacity;
|
|
243
|
+
}
|
|
244
|
+
if (layerOptions.filter) {
|
|
245
|
+
clonedLayer.filter = layerOptions.filter;
|
|
246
|
+
}
|
|
247
|
+
if (layerOptions.timeRestriction) {
|
|
248
|
+
clonedLayer.timeRestriction = layerOptions.timeRestriction;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return true;
|
|
250
252
|
}
|
|
251
253
|
extractLayerOptions(urlParam, type) {
|
|
252
254
|
let active = true;
|
|
@@ -349,12 +351,14 @@ export default class ThemesHelper extends GirafeSingleton {
|
|
|
349
351
|
this.addLayerToLayerTree(newLayer, existingList, activate, layerTreeChanges, parent);
|
|
350
352
|
return;
|
|
351
353
|
}
|
|
352
|
-
|
|
354
|
+
if (newLayer.isHighlighted) {
|
|
355
|
+
// The layer is already present
|
|
353
356
|
this.highlightLayerInLayerTree(existingLayer, activate, layerTreeChanges);
|
|
357
|
+
return;
|
|
354
358
|
}
|
|
355
|
-
// Otherwise, we have to merge the themes
|
|
356
|
-
if ((newLayer instanceof ThemeLayer
|
|
357
|
-
(
|
|
359
|
+
// Otherwise, we have to merge the themes/groups
|
|
360
|
+
if ((newLayer instanceof ThemeLayer && existingLayer instanceof ThemeLayer) ||
|
|
361
|
+
(newLayer instanceof GroupLayer && existingLayer instanceof GroupLayer)) {
|
|
358
362
|
for (const child of newLayer.children) {
|
|
359
363
|
this.mergeLayerWithExistingLayerTree(child, existingLayer.children, activate, layerTreeChanges, existingLayer);
|
|
360
364
|
}
|
|
@@ -19,7 +19,8 @@ export default class PermalinkManager extends GirafeSingleton {
|
|
|
19
19
|
}[];
|
|
20
20
|
} | null;
|
|
21
21
|
hasMapPosition(): boolean | "" | null;
|
|
22
|
-
hasToolTip
|
|
22
|
+
private hasToolTip;
|
|
23
|
+
private hasMarker;
|
|
23
24
|
getMapPosition(targetProjection: Projection): MapPosition | undefined;
|
|
24
25
|
hasSearch(): boolean;
|
|
25
26
|
getSearchTerm(): string;
|
|
@@ -12,6 +12,7 @@ export default class PermalinkManager extends GirafeSingleton {
|
|
|
12
12
|
'map_zoom',
|
|
13
13
|
'map_crosshair',
|
|
14
14
|
'map_tooltip',
|
|
15
|
+
'map_marker',
|
|
15
16
|
'search',
|
|
16
17
|
'basemap',
|
|
17
18
|
'themes',
|
|
@@ -79,6 +80,9 @@ export default class PermalinkManager extends GirafeSingleton {
|
|
|
79
80
|
hasToolTip() {
|
|
80
81
|
return this.params['map_tooltip'] !== null;
|
|
81
82
|
}
|
|
83
|
+
hasMarker() {
|
|
84
|
+
return this.params['map_marker'] !== null;
|
|
85
|
+
}
|
|
82
86
|
getMapPosition(targetProjection) {
|
|
83
87
|
if (this.hasMapPosition()) {
|
|
84
88
|
const position = new MapPosition();
|
|
@@ -106,6 +110,13 @@ export default class PermalinkManager extends GirafeSingleton {
|
|
|
106
110
|
position: center
|
|
107
111
|
};
|
|
108
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
|
+
});
|
|
119
|
+
}
|
|
109
120
|
if (position.isValid) {
|
|
110
121
|
return position;
|
|
111
122
|
}
|