@kispace-io/gs-lib 1.1.8 → 1.2.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.
Files changed (61) hide show
  1. package/dist/base-map-builder.d.ts.map +1 -1
  2. package/dist/gs-model.d.ts +6 -0
  3. package/dist/gs-model.d.ts.map +1 -1
  4. package/dist/index.d.ts +3 -5
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +890 -288
  7. package/dist/index.js.map +1 -1
  8. package/dist/map-renderer.d.ts +94 -0
  9. package/dist/map-renderer.d.ts.map +1 -0
  10. package/dist/ml/gs-gs2ml.d.ts +96 -0
  11. package/dist/ml/gs-gs2ml.d.ts.map +1 -0
  12. package/dist/ml/gs-ml-adapters.d.ts +41 -0
  13. package/dist/ml/gs-ml-adapters.d.ts.map +1 -0
  14. package/dist/ml/gs-ml-lib.d.ts +17 -0
  15. package/dist/ml/gs-ml-lib.d.ts.map +1 -0
  16. package/dist/ml/gs-ml2gs.d.ts +10 -0
  17. package/dist/ml/gs-ml2gs.d.ts.map +1 -0
  18. package/dist/ml/gs-mlns.d.ts +10 -0
  19. package/dist/ml/gs-mlns.d.ts.map +1 -0
  20. package/dist/ml/index.d.ts +9 -0
  21. package/dist/ml/index.d.ts.map +1 -0
  22. package/dist/ml/maplibre-map-renderer.d.ts +66 -0
  23. package/dist/ml/maplibre-map-renderer.d.ts.map +1 -0
  24. package/dist/{gs-gs2ol.d.ts → ol/gs-gs2ol.d.ts} +2 -2
  25. package/dist/ol/gs-gs2ol.d.ts.map +1 -0
  26. package/dist/ol/gs-ol-adapters.d.ts.map +1 -0
  27. package/dist/{gs-lib.d.ts → ol/gs-ol-lib.d.ts} +4 -4
  28. package/dist/ol/gs-ol-lib.d.ts.map +1 -0
  29. package/dist/{gs-ol2gs.d.ts → ol/gs-ol2gs.d.ts} +1 -1
  30. package/dist/ol/gs-ol2gs.d.ts.map +1 -0
  31. package/dist/ol/gs-olns.d.ts.map +1 -0
  32. package/dist/ol/index.d.ts +9 -0
  33. package/dist/ol/index.d.ts.map +1 -0
  34. package/dist/ol/openlayers-map-renderer.d.ts +68 -0
  35. package/dist/ol/openlayers-map-renderer.d.ts.map +1 -0
  36. package/package.json +6 -2
  37. package/src/base-map-builder.ts +8 -9
  38. package/src/gs-model.ts +7 -1
  39. package/src/index.ts +12 -7
  40. package/src/map-renderer.ts +115 -0
  41. package/src/ml/gs-gs2ml.ts +717 -0
  42. package/src/ml/gs-ml-adapters.ts +134 -0
  43. package/src/ml/gs-ml-lib.ts +124 -0
  44. package/src/ml/gs-ml2gs.ts +66 -0
  45. package/src/ml/gs-mlns.ts +50 -0
  46. package/src/ml/index.ts +41 -0
  47. package/src/ml/maplibre-map-renderer.ts +428 -0
  48. package/src/{gs-gs2ol.ts → ol/gs-gs2ol.ts} +10 -4
  49. package/src/{gs-lib.ts → ol/gs-ol-lib.ts} +7 -6
  50. package/src/{gs-ol2gs.ts → ol/gs-ol2gs.ts} +1 -1
  51. package/src/ol/index.ts +21 -0
  52. package/src/ol/openlayers-map-renderer.ts +719 -0
  53. package/dist/gs-gs2ol.d.ts.map +0 -1
  54. package/dist/gs-lib.d.ts.map +0 -1
  55. package/dist/gs-ol-adapters.d.ts.map +0 -1
  56. package/dist/gs-ol2gs.d.ts.map +0 -1
  57. package/dist/gs-olns.d.ts.map +0 -1
  58. /package/dist/{gs-ol-adapters.d.ts → ol/gs-ol-adapters.d.ts} +0 -0
  59. /package/dist/{gs-olns.d.ts → ol/gs-olns.d.ts} +0 -0
  60. /package/src/{gs-ol-adapters.ts → ol/gs-ol-adapters.ts} +0 -0
  61. /package/src/{gs-olns.ts → ol/gs-olns.ts} +0 -0
@@ -0,0 +1,428 @@
1
+ /**
2
+ * MapLibre GL map renderer implementation
3
+ */
4
+
5
+ import { MapOperations, MapRenderer, MapSyncEvent, ScreenshotResult } from '../map-renderer';
6
+ import {
7
+ GsMap,
8
+ GsSourceType,
9
+ GsLayerType,
10
+ GsFeature,
11
+ GsGeometry,
12
+ KEY_UUID,
13
+ ensureUuid
14
+ } from '../gs-model';
15
+ import { mlLib } from './gs-ml-lib';
16
+ import {
17
+ toGeoJsonFeatureCollection,
18
+ toMlFillPaint,
19
+ toMlLinePaint,
20
+ toMlCirclePaint,
21
+ toWgs84,
22
+ toWebMercator,
23
+ ML_KEY_GS_LAYER_UUID
24
+ } from './gs-gs2ml';
25
+ import { toGsFeature } from './gs-ml2gs';
26
+ import { v4 as uuidv4 } from 'uuid';
27
+ import { Map, MapMouseEvent, GeoJSONSource, LngLatLike } from 'maplibre-gl';
28
+
29
+ /**
30
+ * MapLibre map renderer that implements the MapRenderer interface
31
+ */
32
+ export class MapLibreMapRenderer implements MapRenderer {
33
+ private map?: Map;
34
+ private gsMap: GsMap;
35
+ private env?: any;
36
+ private onDirtyCallback?: () => void;
37
+ private onSyncCallback?: (event: MapSyncEvent) => void;
38
+ private isDestroyed: boolean = false;
39
+ private operations?: MapLibreMapOperations;
40
+
41
+ constructor(gsMap: GsMap, env?: any) {
42
+ this.gsMap = gsMap;
43
+ this.env = env;
44
+ }
45
+
46
+ async reattached(): Promise<void> {}
47
+
48
+ async render(container: string | HTMLElement): Promise<void> {
49
+ try {
50
+ this.map = await mlLib({
51
+ containerSelector: container,
52
+ gsMap: this.gsMap,
53
+ env: this.env
54
+ });
55
+
56
+ this.operations = new MapLibreMapOperations(this.map, this);
57
+ this.setupEventListeners();
58
+
59
+ } catch (error) {
60
+ console.error('Failed to render map:', error);
61
+ throw error;
62
+ }
63
+ }
64
+
65
+ async modelToUI(updatedGsMap?: GsMap): Promise<void> {
66
+ if (!this.map) throw new Error('Map not initialized');
67
+ if (updatedGsMap) this.gsMap = updatedGsMap;
68
+
69
+ const container = this.map.getContainer();
70
+ if (!container) throw new Error('Map container not found');
71
+
72
+ this.destroy();
73
+ container.innerHTML = '';
74
+ this.isDestroyed = false;
75
+ await this.render(container);
76
+ }
77
+
78
+ getOperations(): MapOperations {
79
+ if (!this.operations) throw new Error('Operations not available - map not rendered yet');
80
+ return this.operations;
81
+ }
82
+
83
+ async getViewExtent(): Promise<number[]> {
84
+ if (!this.map) throw new Error('Map not available for extent calculation');
85
+ const bounds = this.map.getBounds();
86
+ return [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()];
87
+ }
88
+
89
+ async captureScreenshot(): Promise<ScreenshotResult> {
90
+ if (!this.map) return { success: false, error: 'Map not available' };
91
+
92
+ try {
93
+ this.map.triggerRepaint();
94
+ await new Promise(resolve => requestAnimationFrame(resolve));
95
+
96
+ const canvas = this.map.getCanvas();
97
+ if (!canvas) return { success: false, error: 'Map canvas not found' };
98
+
99
+ const dataUrl = canvas.toDataURL('image/png');
100
+ return { success: true, dataUrl, width: canvas.width, height: canvas.height };
101
+ } catch (error: any) {
102
+ return { success: false, error: `Failed to capture canvas: ${error.message}` };
103
+ }
104
+ }
105
+
106
+ setOnDirty(callback: () => void): void { this.onDirtyCallback = callback; }
107
+ setOnSync(callback: (event: MapSyncEvent) => void): void { this.onSyncCallback = callback; }
108
+
109
+ triggerDirty(): void {
110
+ if (this.isDestroyed || !this.onDirtyCallback) return;
111
+ this.onDirtyCallback();
112
+ }
113
+
114
+ triggerSync(event: MapSyncEvent): void {
115
+ if (this.isDestroyed || !this.onSyncCallback) return;
116
+ this.onSyncCallback(event);
117
+ }
118
+
119
+ private syncViewToModel(): void {
120
+ if (!this.map) return;
121
+ const center = this.map.getCenter();
122
+ const zoom = this.map.getZoom();
123
+ const pitch = this.map.getPitch();
124
+ const bearing = this.map.getBearing();
125
+ const centerMercator = toWebMercator([center.lng, center.lat]);
126
+
127
+ this.triggerSync({
128
+ type: 'viewChanged',
129
+ view: { center: centerMercator, zoom, pitch, bearing }
130
+ });
131
+ }
132
+
133
+ syncLayerFeaturesToModel(layerUuid: string): void {
134
+ if (!this.map) return;
135
+
136
+ const source = this.map.getSource(layerUuid) as GeoJSONSource;
137
+ if (!source) return;
138
+
139
+ const features = this.map.querySourceFeatures(layerUuid);
140
+ const gsFeatures = features.map(f => toGsFeature(f as unknown as GeoJSON.Feature));
141
+
142
+ this.triggerSync({ type: 'featuresChanged', layerUuid, features: gsFeatures });
143
+ }
144
+
145
+ private setupEventListeners(): void {
146
+ if (!this.map) return;
147
+
148
+ this.map.on('moveend', () => {
149
+ this.syncViewToModel();
150
+ this.triggerDirty();
151
+ });
152
+
153
+ this.map.on('zoomend', () => {
154
+ this.syncViewToModel();
155
+ this.triggerDirty();
156
+ });
157
+
158
+ this.map.on('pitchend', () => {
159
+ this.syncViewToModel();
160
+ this.triggerDirty();
161
+ });
162
+
163
+ this.map.on('rotateend', () => {
164
+ this.syncViewToModel();
165
+ this.triggerDirty();
166
+ });
167
+ }
168
+
169
+ getMap(): Map | undefined { return this.map; }
170
+ getGsMap(): GsMap { return this.gsMap; }
171
+
172
+ destroy(): void {
173
+ this.isDestroyed = true;
174
+ if (this.operations) this.operations.cleanup();
175
+ this.map?.remove();
176
+ this.map = undefined;
177
+ }
178
+ }
179
+
180
+ /**
181
+ * MapLibre-specific map operations implementation
182
+ */
183
+ export class MapLibreMapOperations implements MapOperations {
184
+ private selectedFeatureId?: string | number;
185
+ private selectedLayerId?: string;
186
+ private drawMode?: 'Point' | 'LineString' | 'Polygon';
187
+ private clickHandler?: (e: MapMouseEvent) => void;
188
+ private keyHandler?: (e: KeyboardEvent) => void;
189
+
190
+ constructor(private map: Map, private renderer: MapLibreMapRenderer) {
191
+ this.keyHandler = (e: KeyboardEvent) => {
192
+ if (e.key === 'Escape') {
193
+ if (this.drawMode) {
194
+ this.disableDrawing();
195
+ this.renderer.triggerSync({ type: 'drawingDisabled' } as any);
196
+ }
197
+ if (this.selectedFeatureId !== undefined) {
198
+ this.disableSelection();
199
+ }
200
+ }
201
+ };
202
+ document.addEventListener('keydown', this.keyHandler);
203
+ }
204
+
205
+ async setZoom(zoom: number): Promise<void> { this.map.setZoom(zoom); }
206
+
207
+ async setCenter(center: [number, number]): Promise<void> {
208
+ const centerWgs84 = toWgs84(center);
209
+ this.map.setCenter(centerWgs84 as LngLatLike);
210
+ }
211
+
212
+ async switchColorMode(mode?: 'dark' | 'light'): Promise<void> {
213
+ let darkMode: boolean = (this.map as any)._darkMode ?? false;
214
+
215
+ if (mode === 'dark') darkMode = true;
216
+ else if (mode === 'light') darkMode = false;
217
+ else darkMode = !darkMode;
218
+
219
+ (this.map as any)._darkMode = darkMode;
220
+ const canvas = this.map.getCanvas();
221
+ if (canvas) canvas.style.filter = darkMode ? 'invert(100%)' : '';
222
+ }
223
+
224
+ async addLayer(layer: any, isBasemap?: boolean): Promise<void> {
225
+ const sourceId = layer.uuid;
226
+
227
+ let sourceSpec: any;
228
+ if (layer.source.type === GsSourceType.Features) {
229
+ sourceSpec = { type: 'geojson', data: toGeoJsonFeatureCollection(layer.source.features || []) };
230
+ } else if (layer.source.type === GsSourceType.GeoJSON) {
231
+ sourceSpec = { type: 'geojson', data: layer.source.url };
232
+ } else if (layer.source.type === GsSourceType.OSM) {
233
+ sourceSpec = { type: 'raster', tiles: ['https://tile.openstreetmap.org/{z}/{x}/{y}.png'], tileSize: 256 };
234
+ } else if (layer.source.type === GsSourceType.XYZ) {
235
+ sourceSpec = { type: 'raster', tiles: [layer.source.url], tileSize: 256 };
236
+ }
237
+
238
+ if (sourceSpec) {
239
+ this.map.addSource(sourceId, sourceSpec);
240
+
241
+ if (layer.type === GsLayerType.TILE) {
242
+ this.map.addLayer({
243
+ id: sourceId,
244
+ type: 'raster',
245
+ source: sourceId
246
+ }, isBasemap ? this.getFirstLayerId() : undefined);
247
+ } else if (layer.type === GsLayerType.VECTOR) {
248
+ this.map.addLayer({ id: `${sourceId}-fill`, type: 'fill', source: sourceId,
249
+ filter: ['any', ['==', ['geometry-type'], 'Polygon'], ['==', ['geometry-type'], 'MultiPolygon']],
250
+ paint: toMlFillPaint() });
251
+ this.map.addLayer({ id: `${sourceId}-line`, type: 'line', source: sourceId,
252
+ filter: ['any', ['==', ['geometry-type'], 'LineString'], ['==', ['geometry-type'], 'MultiLineString']],
253
+ paint: toMlLinePaint() });
254
+ this.map.addLayer({ id: `${sourceId}-circle`, type: 'circle', source: sourceId,
255
+ filter: ['any', ['==', ['geometry-type'], 'Point'], ['==', ['geometry-type'], 'MultiPoint']],
256
+ paint: toMlCirclePaint() });
257
+ }
258
+ }
259
+ }
260
+
261
+ private getFirstLayerId(): string | undefined {
262
+ return this.map.getStyle()?.layers?.[0]?.id;
263
+ }
264
+
265
+ async deleteLayer(uuid: string): Promise<void> {
266
+ const suffixes = ['', '-fill', '-line', '-circle', '-extrusion'];
267
+ for (const suffix of suffixes) {
268
+ const layerId = `${uuid}${suffix}`;
269
+ if (this.map.getLayer(layerId)) this.map.removeLayer(layerId);
270
+ }
271
+ if (this.map.getSource(uuid)) this.map.removeSource(uuid);
272
+ }
273
+
274
+ async renameLayer(_uuid: string, _newName: string): Promise<void> {}
275
+
276
+ async moveLayer(uuid: string, targetUuid?: string): Promise<void> {
277
+ const suffixes = ['', '-fill', '-line', '-circle', '-extrusion'];
278
+ for (const suffix of suffixes) {
279
+ const layerId = `${uuid}${suffix}`;
280
+ const beforeId = targetUuid ? `${targetUuid}${suffix}` : undefined;
281
+ if (this.map.getLayer(layerId)) this.map.moveLayer(layerId, beforeId);
282
+ }
283
+ }
284
+
285
+ async setLayerVisible(uuid: string, visible: boolean): Promise<void> {
286
+ const visibility = visible ? 'visible' : 'none';
287
+ const suffixes = ['', '-fill', '-line', '-circle', '-extrusion'];
288
+ for (const suffix of suffixes) {
289
+ const layerId = `${uuid}${suffix}`;
290
+ if (this.map.getLayer(layerId)) this.map.setLayoutProperty(layerId, 'visibility', visibility);
291
+ }
292
+ }
293
+
294
+ async addControlFromModule(_src: string): Promise<void> { console.warn('Dynamic control adding not yet implemented for MapLibre'); }
295
+ async removeControl(_uuid: string): Promise<void> { console.warn('Control removal not yet implemented for MapLibre'); }
296
+ async addOverlayFromModule(_src: string, _position?: string): Promise<void> { console.warn('Dynamic overlay adding not yet implemented for MapLibre'); }
297
+ async removeOverlay(_uuid: string): Promise<void> { console.warn('Overlay removal not yet implemented for MapLibre'); }
298
+
299
+ async enableDrawing(geometryType: 'Point' | 'LineString' | 'Polygon', layerUuid: string): Promise<void> {
300
+ this.disableSelection();
301
+ this.disableDrawing();
302
+
303
+ this.drawMode = geometryType;
304
+ this.map.getCanvas().style.cursor = 'crosshair';
305
+
306
+ if (geometryType === 'Point') {
307
+ this.clickHandler = (e: MapMouseEvent) => {
308
+ const coords = [e.lngLat.lng, e.lngLat.lat];
309
+ this.addFeatureToLayer(layerUuid, { type: 'Point', coordinates: coords });
310
+ };
311
+ this.map.on('click', this.clickHandler);
312
+ } else {
313
+ console.warn(`${geometryType} drawing requires maplibre-gl-draw or custom implementation`);
314
+ }
315
+ }
316
+
317
+ private addFeatureToLayer(layerUuid: string, geometry: any): void {
318
+ const source = this.map.getSource(layerUuid) as GeoJSONSource;
319
+ if (!source) return;
320
+
321
+ const uuid = uuidv4();
322
+ const gsMap = this.renderer.getGsMap();
323
+ const layer = gsMap.layers.find(l => l.uuid === layerUuid);
324
+ if (layer?.source?.type === GsSourceType.Features) {
325
+ const features = layer.source.features || [];
326
+ const coordsMercator = toWebMercator(geometry.coordinates as [number, number]);
327
+
328
+ const gsFeature = ensureUuid({
329
+ geometry: ensureUuid({ type: geometry.type, coordinates: coordsMercator } as GsGeometry),
330
+ uuid
331
+ } as GsFeature);
332
+
333
+ features.push(gsFeature);
334
+ source.setData(toGeoJsonFeatureCollection(features));
335
+
336
+ this.renderer.syncLayerFeaturesToModel(layerUuid);
337
+ this.renderer.triggerDirty();
338
+ }
339
+ }
340
+
341
+ async disableDrawing(): Promise<void> {
342
+ if (this.clickHandler) {
343
+ this.map.off('click', this.clickHandler);
344
+ this.clickHandler = undefined;
345
+ }
346
+ this.drawMode = undefined;
347
+ this.map.getCanvas().style.cursor = '';
348
+ }
349
+
350
+ async enableFeatureSelection(): Promise<void> {
351
+ this.disableDrawing();
352
+ this.disableSelection();
353
+
354
+ this.map.getCanvas().style.cursor = 'pointer';
355
+
356
+ this.clickHandler = (e: MapMouseEvent) => {
357
+ const features = this.map.queryRenderedFeatures(e.point);
358
+
359
+ if (features.length > 0) {
360
+ const feature = features[0];
361
+ this.selectedFeatureId = feature.id;
362
+ this.selectedLayerId = feature.layer.id;
363
+
364
+ const gsFeature = toGsFeature(feature as unknown as GeoJSON.Feature);
365
+ const layerUuid = (feature.layer as any).metadata?.[ML_KEY_GS_LAYER_UUID] ||
366
+ feature.layer.id.replace(/-(?:fill|line|circle|extrusion)$/, '');
367
+
368
+ this.renderer.triggerSync({ type: 'featureSelected', layerUuid, feature: gsFeature });
369
+ }
370
+ };
371
+
372
+ this.map.on('click', this.clickHandler);
373
+ }
374
+
375
+ async disableSelection(): Promise<void> {
376
+ if (this.clickHandler) {
377
+ this.map.off('click', this.clickHandler);
378
+ this.clickHandler = undefined;
379
+ }
380
+
381
+ if (this.selectedFeatureId !== undefined) {
382
+ this.renderer.triggerSync({ type: 'featureDeselected' });
383
+ }
384
+
385
+ this.selectedFeatureId = undefined;
386
+ this.selectedLayerId = undefined;
387
+ this.map.getCanvas().style.cursor = '';
388
+ }
389
+
390
+ async deleteSelectedFeatures(): Promise<void> {
391
+ if (this.selectedFeatureId === undefined || !this.selectedLayerId) {
392
+ throw new Error('No features selected');
393
+ }
394
+
395
+ const layerUuid = this.selectedLayerId.replace(/-(?:fill|line|circle|extrusion)$/, '');
396
+ const source = this.map.getSource(layerUuid) as GeoJSONSource;
397
+
398
+ if (!source) throw new Error('Source not found');
399
+
400
+ const gsMap = this.renderer.getGsMap();
401
+ const layer = gsMap.layers.find(l => l.uuid === layerUuid);
402
+
403
+ if (layer?.source?.type === GsSourceType.Features && layer.source.features) {
404
+ const featureId = this.selectedFeatureId.toString();
405
+ layer.source.features = layer.source.features.filter(f => f.uuid !== featureId);
406
+
407
+ source.setData(toGeoJsonFeatureCollection(layer.source.features));
408
+
409
+ this.renderer.syncLayerFeaturesToModel(layerUuid);
410
+ this.renderer.triggerDirty();
411
+ }
412
+
413
+ this.selectedFeatureId = undefined;
414
+ this.selectedLayerId = undefined;
415
+ }
416
+
417
+ cleanup(): void {
418
+ if (this.keyHandler) {
419
+ document.removeEventListener('keydown', this.keyHandler);
420
+ this.keyHandler = undefined;
421
+ }
422
+ if (this.clickHandler) {
423
+ this.map.off('click', this.clickHandler);
424
+ this.clickHandler = undefined;
425
+ }
426
+ }
427
+ }
428
+
@@ -27,7 +27,7 @@ import {
27
27
  KEY_STATE,
28
28
  KEY_UUID,
29
29
  KEY_URL
30
- } from "./gs-model";
30
+ } from "../gs-model";
31
31
  import {Feature, Map, Overlay, View} from "ol";
32
32
  import {MapOptions} from "ol/Map";
33
33
  import BaseObject from "ol/Object";
@@ -47,11 +47,11 @@ import {apply as applyMapboxStyle} from "ol-mapbox-style";
47
47
  import LayerGroup from "ol/layer/Group";
48
48
  import {Control} from "ol/control";
49
49
  import * as ol from "./gs-olns"
50
- import {lit} from "./gs-litns";
50
+ import {lit} from "../gs-litns";
51
51
  import {v4 as uuidv4} from '@kispace-io/appspace/externals/third-party'
52
52
  import {subscribe, publish, unsubscribe} from '@kispace-io/appspace/core/events'
53
53
  import {GsControlAdapter, GsOverlayAdapter} from "./gs-ol-adapters";
54
- import {rtUtils} from "./index";
54
+ import {rtUtils} from "../index";
55
55
  import Layer from "ol/layer/Layer";
56
56
 
57
57
  const withState = <T extends BaseObject>(state: GsState, olObject: T): T => {
@@ -597,7 +597,7 @@ export const cleanupEventSubscriptions = (olMap: Map): void => {
597
597
  }
598
598
  }
599
599
 
600
- export const toOlMap = async (gsMap: GsMap, options?: MapOptions, env?: any, importer?: Importer) => {
600
+ export const toOlMap = async (gsMap: GsMap, options?: MapOptions, env?: any, importer?: Importer, target?: HTMLElement) => {
601
601
  const olMap = withState(gsMap, new Map(options));
602
602
  olMap.set(KEY_ENV, env)
603
603
  olMap.setView(new View({
@@ -610,6 +610,12 @@ export const toOlMap = async (gsMap: GsMap, options?: MapOptions, env?: any, imp
610
610
  olMap.addLayer(olLayer)
611
611
  }
612
612
 
613
+ // Attach map to DOM early so it renders immediately
614
+ // This prevents blank screens when user modules request permissions (e.g., geolocation)
615
+ if (target) {
616
+ olMap.setTarget(target)
617
+ }
618
+
613
619
  for (const overlay of gsMap.overlays || []) {
614
620
  const olOverlay = toOlOverlay(overlay)
615
621
  olMap.addOverlay(olOverlay)
@@ -1,10 +1,10 @@
1
1
  import {toOlMap, Importer} from "./gs-gs2ol";
2
- import {GsMap} from "./gs-model";
2
+ import {GsMap} from "../gs-model";
3
3
  import "ol/ol.css";
4
4
  import {defaultControls, defaultInteractions} from "./gs-olns";
5
5
  // Note: WebAwesome is imported via gs-litns (which is imported by gs-gs2ol), so it's available to user modules
6
6
 
7
- export * from "./gs-model";
7
+ export * from "../gs-model";
8
8
  export * from "./gs-gs2ol";
9
9
 
10
10
  export interface GsAppOptions {
@@ -18,7 +18,7 @@ export interface GsAppOptions {
18
18
  }
19
19
  }
20
20
 
21
- export const gsLib = async (options: GsAppOptions) => {
21
+ export const olLib = async (options: GsAppOptions) => {
22
22
  const mapOptions = {
23
23
  interactions: defaultInteractions({keyboard: false}),
24
24
  controls: defaultControls(options.mapOptions?.controls)
@@ -41,14 +41,15 @@ export const gsLib = async (options: GsAppOptions) => {
41
41
  };
42
42
  }
43
43
 
44
- const olMap = await toOlMap(options.gsMap, mapOptions, options.env, importer)
45
-
46
44
  // Handle both string selector and DOM element
47
45
  const target = typeof options.containerSelector === 'string'
48
46
  ? document.querySelector(options.containerSelector)! as HTMLElement
49
47
  : options.containerSelector;
50
48
 
51
- olMap.setTarget(target)
49
+ // Create map and attach to DOM first to ensure immediate rendering
50
+ // Controls and overlays are loaded after the map is visible
51
+ const olMap = await toOlMap(options.gsMap, mapOptions, options.env, importer, target)
52
+
52
53
  return olMap
53
54
  }
54
55
 
@@ -8,7 +8,7 @@ import {
8
8
  GsState,
9
9
  KEY_STATE,
10
10
  KEY_UUID
11
- } from "./gs-model";
11
+ } from "../gs-model";
12
12
  import BaseObject from "ol/Object";
13
13
 
14
14
  export const toGsLayerType = (tag: string) => {
@@ -0,0 +1,21 @@
1
+ // OpenLayers renderer exports
2
+ // Import from '@kispace-io/gs-lib/ol' for OpenLayers-specific functionality
3
+
4
+ // Core model (needed for types)
5
+ export * from "../gs-model"
6
+
7
+ // Renderer interface
8
+ export * from "../map-renderer"
9
+
10
+ // OpenLayers-specific exports
11
+ export * from "./gs-gs2ol"
12
+ export * from "./gs-ol2gs"
13
+ export * from "./gs-ol-adapters"
14
+ export * from "./gs-ol-lib"
15
+
16
+ // OpenLayers namespace
17
+ export * from "./gs-olns"
18
+
19
+ // OpenLayers renderer implementation
20
+ export { OpenLayersMapRenderer } from "./openlayers-map-renderer"
21
+