@kispace-io/gs-lib 0.0.0
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/README.md +58 -0
- package/bin/map-builder.js +132 -0
- package/dist/base-map-builder.d.ts +102 -0
- package/dist/base-map-builder.d.ts.map +1 -0
- package/dist/gs-gs2ol.d.ts +41 -0
- package/dist/gs-gs2ol.d.ts.map +1 -0
- package/dist/gs-lib.css +3724 -0
- package/dist/gs-lib.d.ts +16 -0
- package/dist/gs-lib.d.ts.map +1 -0
- package/dist/gs-litns.d.ts +32 -0
- package/dist/gs-litns.d.ts.map +1 -0
- package/dist/gs-model.d.ts +186 -0
- package/dist/gs-model.d.ts.map +1 -0
- package/dist/gs-ol-adapters.d.ts +23 -0
- package/dist/gs-ol-adapters.d.ts.map +1 -0
- package/dist/gs-ol2gs.d.ts +9 -0
- package/dist/gs-ol2gs.d.ts.map +1 -0
- package/dist/gs-olns.d.ts +22 -0
- package/dist/gs-olns.d.ts.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.html +69 -0
- package/dist/index.js +104888 -0
- package/dist/index.js.map +1 -0
- package/dist/pwa/assets/icons/192x192.png +0 -0
- package/dist/pwa/assets/icons/24x24.png +0 -0
- package/dist/pwa/assets/icons/48x48.png +0 -0
- package/dist/pwa/assets/icons/512x512.png +0 -0
- package/dist/pwa/assets/icons/icon_192.png +0 -0
- package/dist/pwa/assets/icons/icon_24.png +0 -0
- package/dist/pwa/assets/icons/icon_48.png +0 -0
- package/dist/pwa/assets/icons/icon_512.png +0 -0
- package/dist/pwa/manifest.json +54 -0
- package/dist/pwa/staticwebapp.config.json +6 -0
- package/dist/pwa/sw.js +109 -0
- package/lib/node-map-builder.ts +200 -0
- package/package.json +51 -0
- package/public/index.html +69 -0
- package/public/pwa/assets/icons/192x192.png +0 -0
- package/public/pwa/assets/icons/24x24.png +0 -0
- package/public/pwa/assets/icons/48x48.png +0 -0
- package/public/pwa/assets/icons/512x512.png +0 -0
- package/public/pwa/assets/icons/icon_192.png +0 -0
- package/public/pwa/assets/icons/icon_24.png +0 -0
- package/public/pwa/assets/icons/icon_48.png +0 -0
- package/public/pwa/assets/icons/icon_512.png +0 -0
- package/public/pwa/manifest.json +54 -0
- package/public/pwa/staticwebapp.config.json +6 -0
- package/public/pwa/sw.js +109 -0
- package/src/base-map-builder.ts +414 -0
- package/src/gs-gs2ol.ts +626 -0
- package/src/gs-lib.ts +54 -0
- package/src/gs-litns.ts +213 -0
- package/src/gs-model.ts +393 -0
- package/src/gs-ol-adapters.ts +89 -0
- package/src/gs-ol2gs.ts +86 -0
- package/src/gs-olns.ts +30 -0
- package/src/index.ts +15 -0
- package/tsconfig.json +23 -0
package/src/gs-gs2ol.ts
ADDED
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_GSMAP,
|
|
3
|
+
GsControl,
|
|
4
|
+
GsFeature,
|
|
5
|
+
GsFillStyle,
|
|
6
|
+
GsGeometry,
|
|
7
|
+
GsIcon,
|
|
8
|
+
GsImageStyle,
|
|
9
|
+
GsLayer,
|
|
10
|
+
GsLayerType,
|
|
11
|
+
GsMap,
|
|
12
|
+
GsOverlay,
|
|
13
|
+
GsSource,
|
|
14
|
+
GsSourceType,
|
|
15
|
+
GsState,
|
|
16
|
+
GsStrokeStyle,
|
|
17
|
+
GsStyle,
|
|
18
|
+
GsTextStyle,
|
|
19
|
+
KEY_ENV,
|
|
20
|
+
KEY_EVENT_SUBSCRIPTIONS,
|
|
21
|
+
KEY_FORMAT,
|
|
22
|
+
KEY_GS_MANAGED,
|
|
23
|
+
KEY_LABEL,
|
|
24
|
+
KEY_NAME,
|
|
25
|
+
KEY_SOURCETYPE,
|
|
26
|
+
KEY_SRC,
|
|
27
|
+
KEY_STATE,
|
|
28
|
+
KEY_UUID,
|
|
29
|
+
KEY_URL
|
|
30
|
+
} from "./gs-model";
|
|
31
|
+
import {Feature, Map, Overlay, View} from "ol";
|
|
32
|
+
import {MapOptions} from "ol/Map";
|
|
33
|
+
import BaseObject from "ol/Object";
|
|
34
|
+
import * as olGeom from "ol/geom";
|
|
35
|
+
import {Circle as CircleStyle, Fill, Icon, RegularShape, Stroke, Style, Text} from "ol/style";
|
|
36
|
+
import {GeoTIFF, OSM, Source, TileWMS, WMTS, XYZ} from "ol/source";
|
|
37
|
+
import {optionsFromCapabilities} from "ol/source/WMTS";
|
|
38
|
+
import FeatureFormat from "ol/format/Feature";
|
|
39
|
+
import WMTSCapabilities from "ol/format/WMTSCapabilities";
|
|
40
|
+
import VectorSource from "ol/source/Vector";
|
|
41
|
+
import {GeoJSON, GPX} from "ol/format";
|
|
42
|
+
import TileLayer from "ol/layer/Tile";
|
|
43
|
+
import TileSource from "ol/source/Tile";
|
|
44
|
+
import VectorLayer from "ol/layer/Vector";
|
|
45
|
+
import BaseLayer from "ol/layer/Base";
|
|
46
|
+
import {apply as applyMapboxStyle} from "ol-mapbox-style";
|
|
47
|
+
import LayerGroup from "ol/layer/Group";
|
|
48
|
+
import {Control} from "ol/control";
|
|
49
|
+
import * as ol from "./gs-olns"
|
|
50
|
+
import {lit} from "./gs-litns";
|
|
51
|
+
import {v4 as uuidv4} from '@kispace-io/appspace/externals/third-party'
|
|
52
|
+
import PubSub from '@kispace-io/appspace/externals/third-party'
|
|
53
|
+
import {GsControlAdapter, GsOverlayAdapter} from "./gs-ol-adapters";
|
|
54
|
+
import {rtUtils} from "./index";
|
|
55
|
+
import Layer from "ol/layer/Layer";
|
|
56
|
+
|
|
57
|
+
const withState = <T extends BaseObject>(state: GsState, olObject: T): T => {
|
|
58
|
+
if (state.uuid) {
|
|
59
|
+
olObject.set(KEY_UUID, state.uuid)
|
|
60
|
+
}
|
|
61
|
+
if (state.state) {
|
|
62
|
+
// add as properties for easy access via get(...)
|
|
63
|
+
for (let stateKey in state.state) {
|
|
64
|
+
olObject.set(stateKey, state.state[stateKey])
|
|
65
|
+
}
|
|
66
|
+
// add states as object as it is not possible to differentiate own state keys from OL keys
|
|
67
|
+
olObject.set(KEY_STATE, state.state)
|
|
68
|
+
}
|
|
69
|
+
return olObject
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const toOLGeometry = (geometry: GsGeometry) => {
|
|
73
|
+
return withState(geometry, new olGeom[geometry.type](geometry.coordinates));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const toOlResource = (resource: GsIcon) => {
|
|
77
|
+
return new Icon({
|
|
78
|
+
src: resource.src
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const toOlStroke = (gsStroke: GsStrokeStyle): Stroke => {
|
|
83
|
+
return new Stroke({
|
|
84
|
+
color: gsStroke.color,
|
|
85
|
+
width: gsStroke.width,
|
|
86
|
+
lineDash: gsStroke.lineDash,
|
|
87
|
+
lineCap: gsStroke.lineCap,
|
|
88
|
+
lineJoin: gsStroke.lineJoin,
|
|
89
|
+
miterLimit: gsStroke.miterLimit
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const toOlFill = (gsFill: GsFillStyle): Fill => {
|
|
94
|
+
return new Fill({
|
|
95
|
+
color: gsFill.color
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const toOlCircleImage = (gsImage: GsImageStyle): CircleStyle => {
|
|
100
|
+
return new CircleStyle({
|
|
101
|
+
radius: gsImage.radius || 5,
|
|
102
|
+
fill: gsImage.fill ? toOlFill(gsImage.fill) : undefined,
|
|
103
|
+
stroke: gsImage.stroke ? toOlStroke(gsImage.stroke) : undefined,
|
|
104
|
+
displacement: gsImage.displacement,
|
|
105
|
+
scale: gsImage.scale,
|
|
106
|
+
rotation: gsImage.rotation,
|
|
107
|
+
rotateWithView: gsImage.rotateWithView,
|
|
108
|
+
declutterMode: undefined
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const toOlIconImage = (gsImage: GsImageStyle): Icon => {
|
|
113
|
+
if (!gsImage.src) {
|
|
114
|
+
throw new Error('Icon image requires src property')
|
|
115
|
+
}
|
|
116
|
+
return new Icon({
|
|
117
|
+
src: gsImage.src,
|
|
118
|
+
anchor: gsImage.anchor || [0.5, 1],
|
|
119
|
+
anchorXUnits: gsImage.anchorXUnits || 'fraction',
|
|
120
|
+
anchorYUnits: gsImage.anchorYUnits || 'fraction',
|
|
121
|
+
anchorOrigin: gsImage.anchorOrigin,
|
|
122
|
+
scale: gsImage.scale || 1,
|
|
123
|
+
opacity: gsImage.opacity,
|
|
124
|
+
rotation: gsImage.rotation,
|
|
125
|
+
rotateWithView: gsImage.rotateWithView,
|
|
126
|
+
displacement: gsImage.displacement,
|
|
127
|
+
offset: gsImage.offset,
|
|
128
|
+
offsetOrigin: gsImage.offsetOrigin,
|
|
129
|
+
size: gsImage.size,
|
|
130
|
+
color: gsImage.color,
|
|
131
|
+
crossOrigin: gsImage.crossOrigin,
|
|
132
|
+
declutterMode: undefined
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export const toOlRegularShapeImage = (gsImage: GsImageStyle): RegularShape => {
|
|
137
|
+
return new RegularShape({
|
|
138
|
+
points: gsImage.points || 3,
|
|
139
|
+
radius: gsImage.radius || gsImage.radius1 || 5,
|
|
140
|
+
radius2: gsImage.radius2,
|
|
141
|
+
angle: gsImage.angle || 0,
|
|
142
|
+
displacement: gsImage.displacement,
|
|
143
|
+
fill: gsImage.fill ? toOlFill(gsImage.fill) : undefined,
|
|
144
|
+
stroke: gsImage.stroke ? toOlStroke(gsImage.stroke) : undefined,
|
|
145
|
+
rotation: gsImage.rotation,
|
|
146
|
+
rotateWithView: gsImage.rotateWithView,
|
|
147
|
+
scale: gsImage.scale,
|
|
148
|
+
declutterMode: undefined
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export const toOlImage = (gsImage: GsImageStyle): CircleStyle | Icon | RegularShape => {
|
|
153
|
+
switch (gsImage.type) {
|
|
154
|
+
case 'circle':
|
|
155
|
+
return toOlCircleImage(gsImage)
|
|
156
|
+
case 'icon':
|
|
157
|
+
return toOlIconImage(gsImage)
|
|
158
|
+
case 'regular-shape':
|
|
159
|
+
return toOlRegularShapeImage(gsImage)
|
|
160
|
+
default:
|
|
161
|
+
throw new Error(`Unknown image type: ${gsImage.type}`)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export const toOlText = (gsText: GsTextStyle): Text => {
|
|
166
|
+
return new Text({
|
|
167
|
+
text: gsText.text,
|
|
168
|
+
font: gsText.font,
|
|
169
|
+
maxAngle: gsText.maxAngle,
|
|
170
|
+
offsetX: gsText.offsetX,
|
|
171
|
+
offsetY: gsText.offsetY,
|
|
172
|
+
overflow: gsText.overflow,
|
|
173
|
+
placement: gsText.placement,
|
|
174
|
+
repeat: gsText.repeat,
|
|
175
|
+
scale: gsText.scale,
|
|
176
|
+
rotateWithView: gsText.rotateWithView,
|
|
177
|
+
rotation: gsText.rotation,
|
|
178
|
+
textAlign: gsText.textAlign,
|
|
179
|
+
justify: gsText.justify,
|
|
180
|
+
textBaseline: gsText.textBaseline,
|
|
181
|
+
fill: gsText.fill ? toOlFill(gsText.fill) : undefined,
|
|
182
|
+
stroke: gsText.stroke ? toOlStroke(gsText.stroke) : undefined,
|
|
183
|
+
backgroundFill: gsText.backgroundFill ? toOlFill(gsText.backgroundFill) : undefined,
|
|
184
|
+
backgroundStroke: gsText.backgroundStroke ? toOlStroke(gsText.backgroundStroke) : undefined,
|
|
185
|
+
padding: gsText.padding,
|
|
186
|
+
declutterMode: undefined
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export const toOlStyle = (gsStyle: GsStyle): Style => {
|
|
191
|
+
const styleOptions: any = {}
|
|
192
|
+
|
|
193
|
+
if (gsStyle.stroke) {
|
|
194
|
+
styleOptions.stroke = toOlStroke(gsStyle.stroke)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (gsStyle.fill) {
|
|
198
|
+
styleOptions.fill = toOlFill(gsStyle.fill)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (gsStyle.image) {
|
|
202
|
+
styleOptions.image = toOlImage(gsStyle.image)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (gsStyle.text) {
|
|
206
|
+
styleOptions.text = toOlText(gsStyle.text)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (gsStyle.zIndex !== undefined) {
|
|
210
|
+
styleOptions.zIndex = gsStyle.zIndex
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return new Style(styleOptions)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export const toOlFeature = (feature: GsFeature): Feature => {
|
|
217
|
+
return withState(feature, new Feature({
|
|
218
|
+
geometry: toOLGeometry(feature.geometry),
|
|
219
|
+
}))
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export const OL_SOURCES: any = {}
|
|
223
|
+
OL_SOURCES[GsSourceType.OSM] = (source: GsSource) => {
|
|
224
|
+
const olSource = new OSM();
|
|
225
|
+
olSource.set(KEY_LABEL, source.type)
|
|
226
|
+
return olSource
|
|
227
|
+
}
|
|
228
|
+
OL_SOURCES[GsSourceType.XYZ] = (source: GsSource) => {
|
|
229
|
+
const olSource = new XYZ({
|
|
230
|
+
url: source.url!,
|
|
231
|
+
crossOrigin: 'anonymous'
|
|
232
|
+
});
|
|
233
|
+
olSource.set(KEY_URL, source.url!)
|
|
234
|
+
olSource.set(KEY_LABEL, `${source.type}@${source.url}`)
|
|
235
|
+
return olSource
|
|
236
|
+
}
|
|
237
|
+
OL_SOURCES[GsSourceType.WMS] = (source: GsSource) => {
|
|
238
|
+
const olSource = new TileWMS({
|
|
239
|
+
url: source.url!,
|
|
240
|
+
params: {
|
|
241
|
+
'LAYERS': '', // Will be overridden by source.state
|
|
242
|
+
...source.state || {}
|
|
243
|
+
},
|
|
244
|
+
crossOrigin: 'anonymous' // Required for COEP, same as OSM default
|
|
245
|
+
})
|
|
246
|
+
olSource.set(KEY_URL, source.url!)
|
|
247
|
+
olSource.set(KEY_LABEL, `${source.type}@${source.url}`)
|
|
248
|
+
return olSource
|
|
249
|
+
}
|
|
250
|
+
OL_SOURCES[GsSourceType.WMTS] = (source: GsSource, olLayer?: Layer) => {
|
|
251
|
+
const state = source.state || {}
|
|
252
|
+
const parser = new WMTSCapabilities()
|
|
253
|
+
|
|
254
|
+
// Create a minimal placeholder source
|
|
255
|
+
const olSource = new WMTS({
|
|
256
|
+
url: source.url!,
|
|
257
|
+
layer: state['LAYER'] || '',
|
|
258
|
+
matrixSet: state['MATRIXSET'] || 'GLOBAL_WEBMERCATOR',
|
|
259
|
+
style: state['STYLE'] || 'default',
|
|
260
|
+
crossOrigin: 'anonymous'
|
|
261
|
+
} as any)
|
|
262
|
+
|
|
263
|
+
// Fetch and parse capabilities, then replace the source on the layer
|
|
264
|
+
fetch(source.url!)
|
|
265
|
+
.then(response => response.text())
|
|
266
|
+
.then(text => {
|
|
267
|
+
const result = parser.read(text)
|
|
268
|
+
const options = optionsFromCapabilities(result, {
|
|
269
|
+
layer: state['LAYER'] || '',
|
|
270
|
+
matrixSet: state['MATRIXSET'] || 'GLOBAL_WEBMERCATOR',
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
if (options) {
|
|
274
|
+
// Create a new WMTS source with proper options
|
|
275
|
+
const newSource = new WMTS({
|
|
276
|
+
...options,
|
|
277
|
+
crossOrigin: 'anonymous'
|
|
278
|
+
})
|
|
279
|
+
newSource.set(KEY_URL, source.url!)
|
|
280
|
+
newSource.set(KEY_LABEL, `${source.type}@${source.url}`)
|
|
281
|
+
|
|
282
|
+
// Replace the source on the layer
|
|
283
|
+
if (olLayer && olLayer.setSource) {
|
|
284
|
+
olLayer.setSource(newSource)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
})
|
|
288
|
+
.catch(error => {
|
|
289
|
+
console.error('Failed to fetch WMTS capabilities:', error)
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
olSource.set(KEY_URL, source.url!)
|
|
293
|
+
olSource.set(KEY_LABEL, `${source.type}@${source.url}`)
|
|
294
|
+
return olSource
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const formatSource = (source: GsSource, format: FeatureFormat): Source => {
|
|
298
|
+
const olSource = new VectorSource({
|
|
299
|
+
format: format,
|
|
300
|
+
url: source.url!
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
olSource.set(KEY_FORMAT, source.type)
|
|
304
|
+
olSource.set(KEY_URL, source.url!)
|
|
305
|
+
olSource.set(KEY_LABEL, `${source.type}@${source.url}`)
|
|
306
|
+
return olSource
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
OL_SOURCES[GsSourceType.GeoJSON] = (source: GsSource) => {
|
|
310
|
+
return formatSource(source, new GeoJSON())
|
|
311
|
+
}
|
|
312
|
+
OL_SOURCES[GsSourceType.GPX] = (source: GsSource) => {
|
|
313
|
+
return formatSource(source, new GPX())
|
|
314
|
+
}
|
|
315
|
+
OL_SOURCES[GsSourceType.GeoTIFF] = (source: GsSource) => {
|
|
316
|
+
const olSource = new GeoTIFF({
|
|
317
|
+
sources: [
|
|
318
|
+
{
|
|
319
|
+
url: source.url!
|
|
320
|
+
}
|
|
321
|
+
]
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
olSource.set(KEY_URL, source.url!)
|
|
325
|
+
olSource.set(KEY_LABEL, `${source.type}@${source.url}`)
|
|
326
|
+
return olSource
|
|
327
|
+
}
|
|
328
|
+
OL_SOURCES[GsSourceType.Features] = (source: GsSource) => {
|
|
329
|
+
const features = (source.features || [])
|
|
330
|
+
.map(feature => toOlFeature(feature))!
|
|
331
|
+
const olSource = new VectorSource({
|
|
332
|
+
features: features
|
|
333
|
+
})
|
|
334
|
+
olSource.set(KEY_LABEL, source.type)
|
|
335
|
+
return olSource
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export const toOlSource = (source: GsSource, olLayer?: TileLayer<TileSource>) => {
|
|
339
|
+
return withState(source, OL_SOURCES[source.type](source, olLayer))
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export const OL_LAYERS: any = {}
|
|
343
|
+
OL_LAYERS[GsLayerType.TILE] = (layer: GsLayer) => {
|
|
344
|
+
const tileLayer = new TileLayer()
|
|
345
|
+
const source = toOlSource(layer.source, tileLayer) as TileSource
|
|
346
|
+
tileLayer.setSource(source)
|
|
347
|
+
return tileLayer
|
|
348
|
+
}
|
|
349
|
+
OL_LAYERS[GsLayerType.VECTOR] = (layer: GsLayer) => {
|
|
350
|
+
return new VectorLayer({
|
|
351
|
+
source: toOlSource(layer.source) as VectorSource,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
OL_LAYERS[GsLayerType.GROUP] = (layer: GsLayer) => {
|
|
355
|
+
const group = new LayerGroup();
|
|
356
|
+
group.set(KEY_URL, layer.source.url)
|
|
357
|
+
group.set(KEY_SOURCETYPE, layer.source.type)
|
|
358
|
+
applyMapboxStyle(group, layer.source.url).then()
|
|
359
|
+
return group
|
|
360
|
+
}
|
|
361
|
+
export const toOlLayer = (layer: GsLayer) => {
|
|
362
|
+
const olLayer: BaseLayer = withState(layer, OL_LAYERS[layer.type](layer))
|
|
363
|
+
olLayer.set(KEY_LABEL, layer.type)
|
|
364
|
+
olLayer.set(KEY_NAME, layer.name)
|
|
365
|
+
olLayer.set(KEY_GS_MANAGED, true)
|
|
366
|
+
olLayer.setVisible(layer.visible ?? true)
|
|
367
|
+
return olLayer
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export class GsOlControl extends Control {
|
|
371
|
+
constructor(options?: any) {
|
|
372
|
+
const element = document.createElement('div');
|
|
373
|
+
element.style.margin = "0"
|
|
374
|
+
element.style.padding = "0"
|
|
375
|
+
super({
|
|
376
|
+
...options,
|
|
377
|
+
element: element,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
public getElement() {
|
|
382
|
+
return this.element;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export class GsOlOverlay extends Overlay {
|
|
387
|
+
constructor(options?: any) {
|
|
388
|
+
const element = document.createElement('div');
|
|
389
|
+
element.style.margin = "0"
|
|
390
|
+
element.style.padding = "0"
|
|
391
|
+
super({
|
|
392
|
+
...options
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
public getElement() {
|
|
397
|
+
return this.element;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export const toOlOverlay = (overlay: GsOverlay) => {
|
|
402
|
+
const olOverlay = withState(overlay, new GsOlOverlay({
|
|
403
|
+
positioning: overlay.position,
|
|
404
|
+
stopEvent: true
|
|
405
|
+
}))
|
|
406
|
+
olOverlay.set(KEY_SRC, overlay.src)
|
|
407
|
+
return olOverlay
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export const toOlControl = (control: GsControl) => {
|
|
411
|
+
const olControl = withState(control, new GsOlControl())
|
|
412
|
+
olControl.set(KEY_SRC, control.src)
|
|
413
|
+
return olControl
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const importSrc = async (adapter: GsControlAdapter | GsOverlayAdapter, src: string, importer?: Importer) => {
|
|
417
|
+
const olMap = adapter.getMap()!;
|
|
418
|
+
return (importer || DefaultImporter)(src).then((mod) => {
|
|
419
|
+
const init = () => {
|
|
420
|
+
olMap.removeEventListener("rendercomplete", init)
|
|
421
|
+
const vars: any = {
|
|
422
|
+
// backward compatibility
|
|
423
|
+
...lit,
|
|
424
|
+
// forward compatibility
|
|
425
|
+
lit: lit,
|
|
426
|
+
style: adapter.style.bind(adapter),
|
|
427
|
+
render: adapter.render.bind(adapter),
|
|
428
|
+
map: olMap,
|
|
429
|
+
element: adapter.getElement(),
|
|
430
|
+
querySelector: adapter.getElement().querySelector.bind(adapter.getElement()),
|
|
431
|
+
querySelectorAll: adapter.getElement().querySelector.bind(adapter.getElement()),
|
|
432
|
+
ol: ol,
|
|
433
|
+
env: olMap.get(KEY_ENV) || {},
|
|
434
|
+
utils: {
|
|
435
|
+
uuid: uuidv4
|
|
436
|
+
},
|
|
437
|
+
asset: (path: string) => {
|
|
438
|
+
return rtUtils.resolveUrl(`assets/${path}`)
|
|
439
|
+
},
|
|
440
|
+
signal: (_name: string) => {
|
|
441
|
+
},
|
|
442
|
+
events: (topic: string, callback: Function | any) => {
|
|
443
|
+
if (callback instanceof Function) {
|
|
444
|
+
const token = PubSub.subscribe(topic, (_message: string, data: any) => callback(data));
|
|
445
|
+
let subscriptions = olMap.get(KEY_EVENT_SUBSCRIPTIONS) as string[] | undefined;
|
|
446
|
+
if (!subscriptions) {
|
|
447
|
+
subscriptions = [];
|
|
448
|
+
olMap.set(KEY_EVENT_SUBSCRIPTIONS, subscriptions);
|
|
449
|
+
}
|
|
450
|
+
subscriptions.push(token);
|
|
451
|
+
return token;
|
|
452
|
+
} else {
|
|
453
|
+
return PubSub.publish(topic, callback);
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
settings: (key: string, callback?: Function | any) => {
|
|
457
|
+
const mapUuid = olMap.get(KEY_UUID) as string | undefined;
|
|
458
|
+
const storageKey = mapUuid ? `gs-settings-${mapUuid}` : 'gs-settings';
|
|
459
|
+
|
|
460
|
+
const loadSettings = (): any => {
|
|
461
|
+
try {
|
|
462
|
+
const stored = localStorage.getItem(storageKey);
|
|
463
|
+
return stored ? JSON.parse(stored) : {};
|
|
464
|
+
} catch {
|
|
465
|
+
return {};
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
const saveSettings = (settings: any): void => {
|
|
470
|
+
try {
|
|
471
|
+
localStorage.setItem(storageKey, JSON.stringify(settings));
|
|
472
|
+
} catch (error) {
|
|
473
|
+
console.error('Failed to save settings to localStorage:', error);
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
const settings = loadSettings();
|
|
478
|
+
|
|
479
|
+
// if no callback, assume caller wants the key value
|
|
480
|
+
if (callback === undefined) {
|
|
481
|
+
return settings[key];
|
|
482
|
+
}
|
|
483
|
+
// if a function, register as event topic, call it with current value, and return value
|
|
484
|
+
if (callback instanceof Function) {
|
|
485
|
+
vars.events(key, callback);
|
|
486
|
+
callback(settings[key]);
|
|
487
|
+
return settings[key];
|
|
488
|
+
}
|
|
489
|
+
settings[key] = callback;
|
|
490
|
+
saveSettings(settings);
|
|
491
|
+
// publish as event to inform settings listeners
|
|
492
|
+
return PubSub.publish(key, callback);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
const objectType = adapter instanceof GsControlAdapter ? "control" : "overlay"
|
|
496
|
+
vars[objectType] = adapter
|
|
497
|
+
|
|
498
|
+
const templateFunction = mod instanceof Function ? mod : mod.default
|
|
499
|
+
if (templateFunction) {
|
|
500
|
+
vars.state = <T>(initialValue: T) => {
|
|
501
|
+
const createReactiveProperty = (target: any, key: string, getValue: () => any, setValue: (v: any) => void) => {
|
|
502
|
+
Object.defineProperty(target, key, {
|
|
503
|
+
get() {
|
|
504
|
+
return getValue();
|
|
505
|
+
},
|
|
506
|
+
set(newValue: any) {
|
|
507
|
+
const oldValue = getValue();
|
|
508
|
+
if (oldValue !== newValue) {
|
|
509
|
+
setValue(newValue);
|
|
510
|
+
adapter.render();
|
|
511
|
+
}
|
|
512
|
+
},
|
|
513
|
+
enumerable: true,
|
|
514
|
+
configurable: true
|
|
515
|
+
});
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
if (typeof initialValue === 'object' && initialValue !== null && !Array.isArray(initialValue)) {
|
|
519
|
+
const values: any = { ...initialValue };
|
|
520
|
+
|
|
521
|
+
return new Proxy({} as any, {
|
|
522
|
+
get(_target, prop: string) {
|
|
523
|
+
return values[prop];
|
|
524
|
+
},
|
|
525
|
+
set(_target, prop: string, newValue: any) {
|
|
526
|
+
if (values[prop] !== newValue) {
|
|
527
|
+
values[prop] = newValue;
|
|
528
|
+
adapter.render();
|
|
529
|
+
}
|
|
530
|
+
return true;
|
|
531
|
+
},
|
|
532
|
+
has(_target, prop: string) {
|
|
533
|
+
return prop in values;
|
|
534
|
+
},
|
|
535
|
+
ownKeys(_target) {
|
|
536
|
+
return Object.keys(values);
|
|
537
|
+
},
|
|
538
|
+
getOwnPropertyDescriptor(_target, prop: string) {
|
|
539
|
+
if (prop in values) {
|
|
540
|
+
return {
|
|
541
|
+
enumerable: true,
|
|
542
|
+
configurable: true,
|
|
543
|
+
value: values[prop]
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
return undefined;
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
} else {
|
|
550
|
+
const stateObj: any = {};
|
|
551
|
+
let value = initialValue;
|
|
552
|
+
createReactiveProperty(
|
|
553
|
+
stateObj,
|
|
554
|
+
'value',
|
|
555
|
+
() => value,
|
|
556
|
+
(v) => { value = v; }
|
|
557
|
+
);
|
|
558
|
+
return stateObj;
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
const component = templateFunction(vars)
|
|
563
|
+
|
|
564
|
+
if (component instanceof Function) {
|
|
565
|
+
adapter.render(component);
|
|
566
|
+
} else {
|
|
567
|
+
adapter.render(component)
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
if (adapter instanceof GsControlAdapter) {
|
|
571
|
+
adapter.rendered()
|
|
572
|
+
}
|
|
573
|
+
olMap.render()
|
|
574
|
+
}
|
|
575
|
+
olMap.on("rendercomplete", init)
|
|
576
|
+
})
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
export const importOverlaySource = async (olOverlay: GsOlOverlay, src: string, importer?: Importer) => {
|
|
580
|
+
const overlayAdapter = new GsOverlayAdapter(olOverlay)
|
|
581
|
+
return importSrc(overlayAdapter, src, importer)
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
export const importControlSource = async (olControl: GsOlControl, src: string, importer?: Importer) => {
|
|
585
|
+
const controlAdapter = new GsControlAdapter(olControl)
|
|
586
|
+
return importSrc(controlAdapter, src, importer)
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
export type Importer = (src: string) => Promise<any>
|
|
590
|
+
export const DefaultImporter: Importer = (src: string) => import(src)
|
|
591
|
+
|
|
592
|
+
export const cleanupEventSubscriptions = (olMap: Map): void => {
|
|
593
|
+
const subscriptions = olMap.get(KEY_EVENT_SUBSCRIPTIONS) as string[] | undefined;
|
|
594
|
+
if (subscriptions) {
|
|
595
|
+
subscriptions.forEach(token => PubSub.unsubscribe(token));
|
|
596
|
+
olMap.set(KEY_EVENT_SUBSCRIPTIONS, []);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
export const toOlMap = async (gsMap: GsMap, options?: MapOptions, env?: any, importer?: Importer) => {
|
|
601
|
+
const olMap = withState(gsMap, new Map(options));
|
|
602
|
+
olMap.set(KEY_ENV, env)
|
|
603
|
+
olMap.setView(new View({
|
|
604
|
+
center: gsMap.view.center && gsMap.view.center.length == 2 ? gsMap.view.center : DEFAULT_GSMAP.view.center,
|
|
605
|
+
zoom: gsMap.view.zoom || DEFAULT_GSMAP.view.zoom,
|
|
606
|
+
projection: gsMap.view.projection || DEFAULT_GSMAP.view.projection
|
|
607
|
+
}))
|
|
608
|
+
for (const layer of gsMap.layers || []) {
|
|
609
|
+
const olLayer = toOlLayer(layer)
|
|
610
|
+
olMap.addLayer(olLayer)
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
for (const overlay of gsMap.overlays || []) {
|
|
614
|
+
const olOverlay = toOlOverlay(overlay)
|
|
615
|
+
olMap.addOverlay(olOverlay)
|
|
616
|
+
await importOverlaySource(olOverlay, overlay.src, importer)
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
for (const control of gsMap.controls || []) {
|
|
620
|
+
const olControl = toOlControl(control)
|
|
621
|
+
olMap.addControl(olControl)
|
|
622
|
+
await importControlSource(olControl, control.src, importer)
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
return olMap
|
|
626
|
+
}
|
package/src/gs-lib.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {toOlMap, Importer} from "./gs-gs2ol";
|
|
2
|
+
import {GsMap} from "./gs-model";
|
|
3
|
+
import "ol/ol.css";
|
|
4
|
+
import {defaultControls, defaultInteractions} from "./gs-olns";
|
|
5
|
+
// Note: WebAwesome is imported via gs-litns (which is imported by gs-gs2ol), so it's available to user modules
|
|
6
|
+
|
|
7
|
+
export * from "./gs-model";
|
|
8
|
+
export * from "./gs-gs2ol";
|
|
9
|
+
|
|
10
|
+
export interface GsAppOptions {
|
|
11
|
+
containerSelector: string | HTMLElement,
|
|
12
|
+
gsMap: GsMap,
|
|
13
|
+
env?: any,
|
|
14
|
+
modules?: any,
|
|
15
|
+
importer?: Importer,
|
|
16
|
+
mapOptions?: {
|
|
17
|
+
controls: any
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const gsLib = async (options: GsAppOptions) => {
|
|
22
|
+
const mapOptions = {
|
|
23
|
+
interactions: defaultInteractions({keyboard: false}),
|
|
24
|
+
controls: defaultControls(options.mapOptions?.controls)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let importer: Importer | undefined = options.importer;
|
|
28
|
+
|
|
29
|
+
if (!importer && options.modules) {
|
|
30
|
+
importer = async (src: string) => {
|
|
31
|
+
const module = options.modules[src];
|
|
32
|
+
if (module) {
|
|
33
|
+
// If module is a string, it's a URL/identifier - import it
|
|
34
|
+
if (typeof module === 'string') {
|
|
35
|
+
return import(module);
|
|
36
|
+
}
|
|
37
|
+
// If module is already an object, it's the imported module - return it directly
|
|
38
|
+
return module;
|
|
39
|
+
}
|
|
40
|
+
throw new Error(`Module not found: ${src}`);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const olMap = await toOlMap(options.gsMap, mapOptions, options.env, importer)
|
|
45
|
+
|
|
46
|
+
// Handle both string selector and DOM element
|
|
47
|
+
const target = typeof options.containerSelector === 'string'
|
|
48
|
+
? document.querySelector(options.containerSelector)! as HTMLElement
|
|
49
|
+
: options.containerSelector;
|
|
50
|
+
|
|
51
|
+
olMap.setTarget(target)
|
|
52
|
+
return olMap
|
|
53
|
+
}
|
|
54
|
+
|