@jupytergis/base 0.1.1

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 (108) hide show
  1. package/lib/commands.d.ts +11 -0
  2. package/lib/commands.js +809 -0
  3. package/lib/console/consoleview.d.ts +24 -0
  4. package/lib/console/consoleview.js +55 -0
  5. package/lib/console/index.d.ts +1 -0
  6. package/lib/console/index.js +1 -0
  7. package/lib/constants.d.ts +57 -0
  8. package/lib/constants.js +89 -0
  9. package/lib/dialogs/components/symbology/BandRendering.d.ts +4 -0
  10. package/lib/dialogs/components/symbology/BandRendering.js +29 -0
  11. package/lib/dialogs/components/symbology/BandRow.d.ts +10 -0
  12. package/lib/dialogs/components/symbology/BandRow.js +43 -0
  13. package/lib/dialogs/components/symbology/SingleBandPseudoColor.d.ts +20 -0
  14. package/lib/dialogs/components/symbology/SingleBandPseudoColor.js +281 -0
  15. package/lib/dialogs/components/symbology/StopRow.d.ts +11 -0
  16. package/lib/dialogs/components/symbology/StopRow.js +58 -0
  17. package/lib/dialogs/formdialog.d.ts +31 -0
  18. package/lib/dialogs/formdialog.js +68 -0
  19. package/lib/dialogs/layerBrowserDialog.d.ts +25 -0
  20. package/lib/dialogs/layerBrowserDialog.js +141 -0
  21. package/lib/dialogs/symbologyDialog.d.ts +23 -0
  22. package/lib/dialogs/symbologyDialog.js +68 -0
  23. package/lib/dialogs/terrainDialog.d.ts +21 -0
  24. package/lib/dialogs/terrainDialog.js +60 -0
  25. package/lib/formbuilder/creationform.d.ts +56 -0
  26. package/lib/formbuilder/creationform.js +117 -0
  27. package/lib/formbuilder/editform.d.ts +24 -0
  28. package/lib/formbuilder/editform.js +60 -0
  29. package/lib/formbuilder/formselectors.d.ts +5 -0
  30. package/lib/formbuilder/formselectors.js +38 -0
  31. package/lib/formbuilder/index.d.ts +6 -0
  32. package/lib/formbuilder/index.js +6 -0
  33. package/lib/formbuilder/objectform/baseform.d.ts +79 -0
  34. package/lib/formbuilder/objectform/baseform.js +167 -0
  35. package/lib/formbuilder/objectform/geojsonsource.d.ts +19 -0
  36. package/lib/formbuilder/objectform/geojsonsource.js +80 -0
  37. package/lib/formbuilder/objectform/hillshadeLayerForm.d.ts +8 -0
  38. package/lib/formbuilder/objectform/hillshadeLayerForm.js +12 -0
  39. package/lib/formbuilder/objectform/layerform.d.ts +19 -0
  40. package/lib/formbuilder/objectform/layerform.js +17 -0
  41. package/lib/formbuilder/objectform/tilesourceform.d.ts +7 -0
  42. package/lib/formbuilder/objectform/tilesourceform.js +60 -0
  43. package/lib/formbuilder/objectform/vectorlayerform.d.ts +15 -0
  44. package/lib/formbuilder/objectform/vectorlayerform.js +88 -0
  45. package/lib/formbuilder/objectform/webGlLayerForm.d.ts +8 -0
  46. package/lib/formbuilder/objectform/webGlLayerForm.js +10 -0
  47. package/lib/icons.d.ts +6 -0
  48. package/lib/icons.js +31 -0
  49. package/lib/index.d.ts +10 -0
  50. package/lib/index.js +10 -0
  51. package/lib/mainview/index.d.ts +3 -0
  52. package/lib/mainview/index.js +3 -0
  53. package/lib/mainview/mainView.d.ts +113 -0
  54. package/lib/mainview/mainView.js +743 -0
  55. package/lib/mainview/mainviewmodel.d.ts +24 -0
  56. package/lib/mainview/mainviewmodel.js +34 -0
  57. package/lib/mainview/mainviewwidget.d.ts +14 -0
  58. package/lib/mainview/mainviewwidget.js +18 -0
  59. package/lib/mainview/spinner.d.ts +6 -0
  60. package/lib/mainview/spinner.js +5 -0
  61. package/lib/panelview/components/filter-panel/Filter.d.ts +19 -0
  62. package/lib/panelview/components/filter-panel/Filter.js +223 -0
  63. package/lib/panelview/components/filter-panel/FilterRow.d.ts +9 -0
  64. package/lib/panelview/components/filter-panel/FilterRow.js +61 -0
  65. package/lib/panelview/components/layers.d.ts +13 -0
  66. package/lib/panelview/components/layers.js +275 -0
  67. package/lib/panelview/components/sources.d.ts +10 -0
  68. package/lib/panelview/components/sources.js +147 -0
  69. package/lib/panelview/header.d.ts +11 -0
  70. package/lib/panelview/header.js +20 -0
  71. package/lib/panelview/index.d.ts +5 -0
  72. package/lib/panelview/index.js +5 -0
  73. package/lib/panelview/leftpanel.d.ts +51 -0
  74. package/lib/panelview/leftpanel.js +125 -0
  75. package/lib/panelview/model.d.ts +19 -0
  76. package/lib/panelview/model.js +30 -0
  77. package/lib/panelview/objectproperties.d.ts +17 -0
  78. package/lib/panelview/objectproperties.js +92 -0
  79. package/lib/panelview/rightpanel.d.ts +19 -0
  80. package/lib/panelview/rightpanel.js +45 -0
  81. package/lib/toolbar/index.d.ts +2 -0
  82. package/lib/toolbar/index.js +2 -0
  83. package/lib/toolbar/usertoolbaritem.d.ts +19 -0
  84. package/lib/toolbar/usertoolbaritem.js +57 -0
  85. package/lib/toolbar/widget.d.ts +22 -0
  86. package/lib/toolbar/widget.js +104 -0
  87. package/lib/tools.d.ts +25 -0
  88. package/lib/tools.js +215 -0
  89. package/lib/types.d.ts +11 -0
  90. package/lib/types.js +1 -0
  91. package/lib/widget.d.ts +49 -0
  92. package/lib/widget.js +144 -0
  93. package/package.json +95 -0
  94. package/style/base.css +55 -0
  95. package/style/colorExpression.css +36 -0
  96. package/style/dialog.css +8 -0
  97. package/style/filterPanel.css +70 -0
  98. package/style/icons/geojson.svg +12 -0
  99. package/style/icons/mound.svg +9 -0
  100. package/style/icons/nonvisibility.svg +8 -0
  101. package/style/icons/raster.svg +5 -0
  102. package/style/icons/visibility.svg +7 -0
  103. package/style/index.css +6 -0
  104. package/style/index.js +6 -0
  105. package/style/layerBrowser.css +256 -0
  106. package/style/leftPanel.css +150 -0
  107. package/style/symbologyDialog.css +86 -0
  108. package/style/terrainDialog.css +14 -0
@@ -0,0 +1,743 @@
1
+ import { JupyterGISModel } from '@jupytergis/schema';
2
+ import { UUID } from '@lumino/coreutils';
3
+ import { Map as OlMap, View } from 'ol';
4
+ import { GeoJSON, MVT } from 'ol/format';
5
+ import DragAndDrop from 'ol/interaction/DragAndDrop';
6
+ import { Image as ImageLayer, Vector as VectorLayer, VectorTile as VectorTileLayer, WebGLTile as WebGlTileLayer } from 'ol/layer';
7
+ import TileLayer from 'ol/layer/Tile';
8
+ import { fromLonLat, toLonLat } from 'ol/proj';
9
+ import Feature from 'ol/render/Feature';
10
+ import { GeoTIFF as GeoTIFFSource, ImageTile as ImageTileSource, Vector as VectorSource, VectorTile as VectorTileSource, XYZ as XYZSource } from 'ol/source';
11
+ import Static from 'ol/source/ImageStatic';
12
+ import { Circle, Fill, Stroke, Style } from 'ol/style';
13
+ //@ts-expect-error no types for ol-pmtiles
14
+ import { PMTilesRasterSource, PMTilesVectorSource } from 'ol-pmtiles';
15
+ import * as React from 'react';
16
+ import { isLightTheme } from '../tools';
17
+ import { Spinner } from './spinner';
18
+ export class MainView extends React.Component {
19
+ constructor(props) {
20
+ super(props);
21
+ this.vectorLayerStyleFunc = (currentFeature, layer) => {
22
+ var _a;
23
+ const layerParameters = layer.parameters;
24
+ // const flatStyle = {
25
+ // 'fill-color': 'rgba(255,255,255,0.4)',
26
+ // 'stroke-color': '#3399CC',
27
+ // 'stroke-width': 1.25,
28
+ // 'circle-radius': 5,
29
+ // 'circle-fill-color': 'rgba(255,255,255,0.4)',
30
+ // 'circle-stroke-width': 1.25,
31
+ // 'circle-stroke-color': '#3399CC'
32
+ // };
33
+ // TODO: Need to make a version that works with strings as well
34
+ const operators = {
35
+ '>': (a, b) => a > b,
36
+ '<': (a, b) => a < b,
37
+ '>=': (a, b) => a >= b,
38
+ '<=': (a, b) => a <= b,
39
+ '==': (a, b) => a === b,
40
+ '!=': (a, b) => a !== b
41
+ };
42
+ // TODO: I don't think this will work with fancy color expressions
43
+ const fill = new Fill({
44
+ color: layerParameters.type === 'fill' || layerParameters.type === 'circle'
45
+ ? layerParameters.color
46
+ : '#F092DD'
47
+ });
48
+ const stroke = new Stroke({
49
+ color: layerParameters.type === 'line' || layerParameters.type === 'circle'
50
+ ? layerParameters.color
51
+ : '#392F5A',
52
+ width: 2
53
+ });
54
+ const style = new Style({
55
+ fill,
56
+ stroke,
57
+ image: new Circle({
58
+ radius: 5,
59
+ fill,
60
+ stroke
61
+ })
62
+ });
63
+ if (layer.filters && ((_a = layer.filters) === null || _a === void 0 ? void 0 : _a.appliedFilters.length) !== 0) {
64
+ const props = currentFeature.getProperties();
65
+ let shouldDisplayFeature = true;
66
+ switch (layer.filters.logicalOp) {
67
+ case 'any': {
68
+ // Display the feature if any filter conditions apply
69
+ shouldDisplayFeature = layer.filters.appliedFilters.some(({ feature, operator, value }) => operators[operator](props[feature], value));
70
+ break;
71
+ }
72
+ case 'all': {
73
+ // Display the feature only if all the filter conditions apply
74
+ shouldDisplayFeature = layer.filters.appliedFilters.every(({ feature, operator, value }) => operators[operator](props[feature], value));
75
+ break;
76
+ }
77
+ }
78
+ if (shouldDisplayFeature) {
79
+ return style;
80
+ }
81
+ else {
82
+ return undefined;
83
+ }
84
+ }
85
+ else {
86
+ return style;
87
+ }
88
+ };
89
+ /**
90
+ * Taken from https://openlayers.org/en/latest/examples/webgl-shaded-relief.html
91
+ * @returns
92
+ */
93
+ this.hillshadeMath = () => {
94
+ // The method used to extract elevations from the DEM.
95
+ // In this case the format used is Terrarium
96
+ // red * 256 + green + blue / 256 - 32768
97
+ //
98
+ // Other frequently used methods include the Mapbox format
99
+ // (red * 256 * 256 + green * 256 + blue) * 0.1 - 10000
100
+ //
101
+ function elevation(xOffset, yOffset) {
102
+ const red = ['band', 1, xOffset, yOffset];
103
+ const green = ['band', 2, xOffset, yOffset];
104
+ const blue = ['band', 3, xOffset, yOffset];
105
+ // band math operates on normalized values from 0-1
106
+ // so we scale by 255
107
+ return [
108
+ '+',
109
+ ['*', 255 * 256, red],
110
+ ['*', 255, green],
111
+ ['*', 255 / 256, blue],
112
+ -32768
113
+ ];
114
+ }
115
+ // Generates a shaded relief image given elevation data. Uses a 3x3
116
+ // neighborhood for determining slope and aspect.
117
+ const dp = ['*', 2, ['resolution']];
118
+ const z0x = ['*', 2, elevation(-1, 0)];
119
+ const z1x = ['*', 2, elevation(1, 0)];
120
+ const dzdx = ['/', ['-', z1x, z0x], dp];
121
+ const z0y = ['*', 2, elevation(0, -1)];
122
+ const z1y = ['*', 2, elevation(0, 1)];
123
+ const dzdy = ['/', ['-', z1y, z0y], dp];
124
+ const slope = ['atan', ['sqrt', ['+', ['^', dzdx, 2], ['^', dzdy, 2]]]];
125
+ const aspect = ['clamp', ['atan', ['-', 0, dzdx], dzdy], -Math.PI, Math.PI];
126
+ const sunEl = ['*', Math.PI / 180, 45];
127
+ const sunAz = ['*', Math.PI / 180, 46];
128
+ const cosIncidence = [
129
+ '+',
130
+ ['*', ['sin', sunEl], ['cos', slope]],
131
+ ['*', ['cos', sunEl], ['sin', slope], ['cos', ['-', sunAz, aspect]]]
132
+ ];
133
+ const scaled = ['*', 255, cosIncidence];
134
+ return scaled;
135
+ };
136
+ this._onClientSharedStateChanged = (sender, clients) => {
137
+ // TODO SOMETHING
138
+ };
139
+ this._handleThemeChange = () => {
140
+ const lightTheme = isLightTheme();
141
+ // TODO SOMETHING
142
+ this.setState(old => (Object.assign(Object.assign({}, old), { lightTheme })));
143
+ };
144
+ this._handleWindowResize = () => {
145
+ // TODO SOMETHING
146
+ };
147
+ this._initializedPosition = false;
148
+ this.divRef = React.createRef(); // Reference of render div
149
+ this._ready = false;
150
+ this._sourceToLayerMap = new Map();
151
+ this._mainViewModel = this.props.viewModel;
152
+ this._mainViewModel.viewSettingChanged.connect(this._onViewChanged, this);
153
+ this._model = this._mainViewModel.jGISModel;
154
+ this._model.themeChanged.connect(this._handleThemeChange, this);
155
+ this._model.sharedOptionsChanged.connect(this._onSharedOptionsChanged, this);
156
+ this._model.clientStateChanged.connect(this._onClientSharedStateChanged, this);
157
+ this._model.sharedLayersChanged.connect(this._onLayersChanged, this);
158
+ this._model.sharedLayerTreeChanged.connect(this._onLayerTreeChange, this);
159
+ this._model.sharedSourcesChanged.connect(this._onSourcesChange, this);
160
+ this.state = {
161
+ id: this._mainViewModel.id,
162
+ lightTheme: isLightTheme(),
163
+ loading: true,
164
+ firstLoad: true
165
+ };
166
+ this._sources = [];
167
+ }
168
+ async componentDidMount() {
169
+ window.addEventListener('resize', this._handleWindowResize);
170
+ await this.generateScene();
171
+ this._mainViewModel.initSignal();
172
+ }
173
+ componentWillUnmount() {
174
+ window.removeEventListener('resize', this._handleWindowResize);
175
+ this._mainViewModel.viewSettingChanged.disconnect(this._onViewChanged, this);
176
+ this._model.themeChanged.disconnect(this._handleThemeChange, this);
177
+ this._model.sharedOptionsChanged.disconnect(this._onSharedOptionsChanged, this);
178
+ this._model.clientStateChanged.disconnect(this._onClientSharedStateChanged, this);
179
+ this._mainViewModel.dispose();
180
+ }
181
+ async generateScene() {
182
+ if (this.divRef.current) {
183
+ this._Map = new OlMap({
184
+ target: this.divRef.current,
185
+ layers: [],
186
+ view: new View({
187
+ center: [0, 0],
188
+ zoom: 1
189
+ })
190
+ });
191
+ const dragAndDropInteraction = new DragAndDrop({
192
+ formatConstructors: [GeoJSON]
193
+ });
194
+ dragAndDropInteraction.on('addfeatures', event => {
195
+ const sourceId = UUID.uuid4();
196
+ const sourceModel = {
197
+ type: 'GeoJSONSource',
198
+ name: 'Drag and Drop source',
199
+ parameters: { path: event.file.name }
200
+ };
201
+ this.addSource(sourceId, sourceModel);
202
+ this._model.sharedModel.addSource(sourceId, sourceModel);
203
+ const layerModel = {
204
+ type: 'VectorLayer',
205
+ visible: true,
206
+ name: 'Drag and Drop layer',
207
+ parameters: {
208
+ color: '#FF0000',
209
+ opacity: 1.0,
210
+ type: 'line',
211
+ source: sourceId
212
+ }
213
+ };
214
+ const layerId = UUID.uuid4();
215
+ this.addLayer(layerId, layerModel, this.getLayers().length);
216
+ this._model.addLayer(layerId, layerModel);
217
+ });
218
+ this._Map.addInteraction(dragAndDropInteraction);
219
+ this._Map.on('moveend', () => {
220
+ if (!this._initializedPosition) {
221
+ return;
222
+ }
223
+ const currentOptions = this._model.getOptions();
224
+ const view = this._Map.getView();
225
+ const center = view.getCenter() || [0, 0];
226
+ const zoom = view.getZoom() || 0;
227
+ const projection = view.getProjection();
228
+ const latLng = toLonLat(center, projection);
229
+ const bearing = view.getRotation();
230
+ const updatedOptions = {
231
+ latitude: latLng[1],
232
+ longitude: latLng[0],
233
+ bearing,
234
+ projection: projection.getCode(),
235
+ zoom
236
+ };
237
+ // Update the extent only if has been initially provided.
238
+ if (currentOptions.extent) {
239
+ updatedOptions.extent = view.calculateExtent();
240
+ }
241
+ this._model.setOptions(Object.assign(Object.assign({}, currentOptions), updatedOptions));
242
+ });
243
+ if (JupyterGISModel.getOrderedLayerIds(this._model).length !== 0) {
244
+ await this._updateLayersImpl(JupyterGISModel.getOrderedLayerIds(this._model));
245
+ const options = this._model.getOptions();
246
+ this.updateOptions(options);
247
+ }
248
+ this.setState(old => (Object.assign(Object.assign({}, old), { loading: false })));
249
+ }
250
+ }
251
+ /**
252
+ * Add a source in the map.
253
+ *
254
+ * @param id - the source id.
255
+ * @param source - the source object.
256
+ */
257
+ async addSource(id, source) {
258
+ var _a, _b;
259
+ let newSource;
260
+ switch (source.type) {
261
+ case 'RasterSource': {
262
+ const sourceParameters = source.parameters;
263
+ const pmTiles = sourceParameters.url.endsWith('.pmtiles');
264
+ const url = this.computeSourceUrl(source);
265
+ if (!pmTiles) {
266
+ newSource = new XYZSource({
267
+ attributions: sourceParameters.attribution,
268
+ minZoom: sourceParameters.minZoom,
269
+ maxZoom: sourceParameters.maxZoom,
270
+ tileSize: 256,
271
+ url: url
272
+ });
273
+ }
274
+ else {
275
+ newSource = new PMTilesRasterSource({
276
+ attributions: sourceParameters.attribution,
277
+ tileSize: 256,
278
+ url: url
279
+ });
280
+ }
281
+ break;
282
+ }
283
+ case 'RasterDemSource': {
284
+ const sourceParameters = source.parameters;
285
+ newSource = new ImageTileSource({
286
+ url: this.computeSourceUrl(source),
287
+ attributions: sourceParameters.attribution
288
+ });
289
+ break;
290
+ }
291
+ case 'VectorTileSource': {
292
+ const sourceParameters = source.parameters;
293
+ const pmTiles = sourceParameters.url.endsWith('.pmtiles');
294
+ const url = this.computeSourceUrl(source);
295
+ if (!pmTiles) {
296
+ newSource = new VectorTileSource({
297
+ attributions: sourceParameters.attribution,
298
+ minZoom: sourceParameters.minZoom,
299
+ maxZoom: sourceParameters.maxZoom,
300
+ url: url,
301
+ format: new MVT({ featureClass: Feature })
302
+ });
303
+ }
304
+ else {
305
+ newSource = new PMTilesVectorSource({
306
+ attributions: sourceParameters.attribution,
307
+ url: url
308
+ });
309
+ }
310
+ break;
311
+ }
312
+ case 'GeoJSONSource': {
313
+ const data = ((_a = source.parameters) === null || _a === void 0 ? void 0 : _a.data) ||
314
+ (await this._model.readGeoJSON((_b = source.parameters) === null || _b === void 0 ? void 0 : _b.path));
315
+ const format = new GeoJSON();
316
+ // TODO: Don't hardcode projection
317
+ newSource = new VectorSource({
318
+ features: format.readFeatures(data, {
319
+ dataProjection: 'EPSG:4326',
320
+ featureProjection: this._Map.getView().getProjection()
321
+ })
322
+ });
323
+ break;
324
+ }
325
+ case 'ImageSource': {
326
+ const sourceParameters = source.parameters;
327
+ // Convert lon/lat array to extent
328
+ // Get lon/lat from source coordinates
329
+ const leftSide = Math.min(...sourceParameters.coordinates.map(corner => corner[0]));
330
+ const bottomSide = Math.min(...sourceParameters.coordinates.map(corner => corner[1]));
331
+ const rightSide = Math.max(...sourceParameters.coordinates.map(corner => corner[0]));
332
+ const topSide = Math.max(...sourceParameters.coordinates.map(corner => corner[1]));
333
+ // Convert lon/lat to OpenLayer coordinates
334
+ const topLeft = fromLonLat([leftSide, topSide]);
335
+ const bottomRight = fromLonLat([rightSide, bottomSide]);
336
+ // Get extent from coordinates
337
+ const minX = topLeft[0];
338
+ const maxY = topLeft[1];
339
+ const maxX = bottomRight[0];
340
+ const minY = bottomRight[1];
341
+ const extent = [minX, minY, maxX, maxY];
342
+ newSource = new Static({
343
+ imageExtent: extent,
344
+ url: sourceParameters.url,
345
+ interpolate: true,
346
+ crossOrigin: ''
347
+ });
348
+ break;
349
+ }
350
+ case 'VideoSource': {
351
+ console.warn('Video Tiles not supported with Open Layers');
352
+ break;
353
+ }
354
+ case 'GeoTiffSource': {
355
+ const sourceParameters = source.parameters;
356
+ newSource = new GeoTIFFSource({
357
+ sources: sourceParameters.urls,
358
+ normalize: sourceParameters.normalize,
359
+ wrapX: sourceParameters.wrapX
360
+ });
361
+ break;
362
+ }
363
+ }
364
+ newSource.set('id', id);
365
+ // _sources is a list of OpenLayers sources
366
+ this._sources[id] = newSource;
367
+ }
368
+ computeSourceUrl(source) {
369
+ const parameters = source.parameters;
370
+ const urlParameters = parameters.urlParameters || {};
371
+ let url = parameters.url;
372
+ for (const parameterName of Object.keys(urlParameters)) {
373
+ url = url.replace(`{${parameterName}}`, urlParameters[parameterName]);
374
+ }
375
+ // Special case for max_zoom and min_zoom
376
+ if (url.includes('{max_zoom}')) {
377
+ url = url.replace('{max_zoom}', parameters.maxZoom.toString());
378
+ }
379
+ if (url.includes('{min_zoom}')) {
380
+ url = url.replace('{min_zoom}', parameters.minZoom.toString());
381
+ }
382
+ return url;
383
+ }
384
+ /**
385
+ * Update a source in the map.
386
+ *
387
+ * @param id - the source id.
388
+ * @param source - the source object.
389
+ */
390
+ async updateSource(id, source) {
391
+ // get the layer id associated with this source
392
+ const layerId = this._sourceToLayerMap.get(id);
393
+ // get the OL layer
394
+ const mapLayer = this.getLayer(layerId);
395
+ if (!mapLayer) {
396
+ return;
397
+ }
398
+ // remove source being updated
399
+ this.removeSource(id);
400
+ // create updated source
401
+ this.addSource(id, source);
402
+ // change source of target layer
403
+ mapLayer.setSource(this._sources[id]);
404
+ }
405
+ /**
406
+ * Remove a source from the map.
407
+ *
408
+ * @param id - the source id.
409
+ */
410
+ removeSource(id) {
411
+ delete this._sources[id];
412
+ }
413
+ /**
414
+ * Add or move the layers of the map.
415
+ *
416
+ * @param layerIds - the list of layers in the depth order (beneath first).
417
+ */
418
+ updateLayers(layerIds) {
419
+ this._updateLayersImpl(layerIds);
420
+ }
421
+ async _updateLayersImpl(layerIds) {
422
+ const previousLayerIds = this.getLayers();
423
+ // We use the reverse order of the list to add the layer from the top to the
424
+ // bottom.
425
+ // This is to ensure that the beforeId (layer on top of the one we add/move)
426
+ // is already added/moved in the map.
427
+ const reversedLayerIds = layerIds.slice().reverse();
428
+ for (const layerId of reversedLayerIds) {
429
+ const layer = this._model.sharedModel.getLayer(layerId);
430
+ if (!layer) {
431
+ console.log(`Layer id ${layerId} does not exist`);
432
+ return;
433
+ }
434
+ // Get the expected index in the map.
435
+ const currentLayerIds = [...previousLayerIds];
436
+ let indexInMap = currentLayerIds.length;
437
+ const nextLayer = layerIds[layerIds.indexOf(layerId) + 1];
438
+ if (nextLayer !== undefined) {
439
+ indexInMap = currentLayerIds.indexOf(nextLayer);
440
+ if (indexInMap === -1) {
441
+ indexInMap = currentLayerIds.length;
442
+ }
443
+ }
444
+ if (this.getLayer(layerId)) {
445
+ this.moveLayer(layerId, indexInMap);
446
+ }
447
+ else {
448
+ await this.addLayer(layerId, layer, indexInMap);
449
+ }
450
+ // Remove the element of the previous list as treated.
451
+ const index = previousLayerIds.indexOf(layerId);
452
+ if (index > -1) {
453
+ previousLayerIds.splice(index, 1);
454
+ }
455
+ }
456
+ // Remove the layers not used anymore.
457
+ previousLayerIds.forEach(layerId => {
458
+ this._Map.removeLayer(layerId);
459
+ });
460
+ this._ready = true;
461
+ }
462
+ /**
463
+ * Add a layer to the map.
464
+ *
465
+ * @param id - id of the layer.
466
+ * @param layer - the layer object.
467
+ * @param index - expected index of the layer.
468
+ */
469
+ async addLayer(id, layer, index) {
470
+ var _a;
471
+ if (this.getLayer(id)) {
472
+ // Layer already exists
473
+ return;
474
+ }
475
+ const sourceId = (_a = layer.parameters) === null || _a === void 0 ? void 0 : _a.source;
476
+ const source = this._model.sharedModel.getSource(sourceId);
477
+ if (!source) {
478
+ return;
479
+ }
480
+ if (!this._sources[sourceId]) {
481
+ await this.addSource(sourceId, source);
482
+ }
483
+ let newLayer;
484
+ let layerParameters;
485
+ // TODO: OpenLayers provides a bunch of sources for specific tile
486
+ // providers, so maybe set up some way to use those
487
+ switch (layer.type) {
488
+ case 'RasterLayer': {
489
+ layerParameters = layer.parameters;
490
+ newLayer = new TileLayer({
491
+ opacity: layerParameters.opacity,
492
+ visible: layer.visible,
493
+ source: this._sources[layerParameters.source]
494
+ });
495
+ break;
496
+ }
497
+ case 'VectorLayer': {
498
+ layerParameters = layer.parameters;
499
+ newLayer = new VectorLayer({
500
+ opacity: layerParameters.opacity,
501
+ visible: layer.visible,
502
+ source: this._sources[layerParameters.source],
503
+ style: currentFeature => this.vectorLayerStyleFunc(currentFeature, layer)
504
+ });
505
+ break;
506
+ }
507
+ case 'VectorTileLayer': {
508
+ layerParameters = layer.parameters;
509
+ newLayer = new VectorTileLayer({
510
+ opacity: layerParameters.opacity,
511
+ source: this._sources[layerParameters.source],
512
+ style: currentFeature => this.vectorLayerStyleFunc(currentFeature, layer)
513
+ });
514
+ break;
515
+ }
516
+ case 'HillshadeLayer': {
517
+ layerParameters = layer.parameters;
518
+ newLayer = new WebGlTileLayer({
519
+ opacity: 0.3,
520
+ source: this._sources[layerParameters.source],
521
+ style: {
522
+ color: ['color', this.hillshadeMath()]
523
+ }
524
+ });
525
+ break;
526
+ }
527
+ case 'ImageLayer': {
528
+ layerParameters = layer.parameters;
529
+ newLayer = new ImageLayer({
530
+ opacity: layerParameters.opacity,
531
+ source: this._sources[layerParameters.source]
532
+ });
533
+ break;
534
+ }
535
+ case 'WebGlLayer': {
536
+ layerParameters = layer.parameters;
537
+ newLayer = new WebGlTileLayer({
538
+ opacity: layerParameters.opacity,
539
+ source: this._sources[layerParameters.source],
540
+ style: {
541
+ color: layerParameters.color
542
+ }
543
+ });
544
+ break;
545
+ }
546
+ }
547
+ // OpenLayers doesn't have name/id field so add it
548
+ newLayer.set('id', id);
549
+ // we need to keep track of which source has which layers
550
+ this._sourceToLayerMap.set(layerParameters.source, id);
551
+ this._Map.getLayers().insertAt(index, newLayer);
552
+ }
553
+ /**
554
+ * Move a layer in the stack.
555
+ *
556
+ * @param id - id of the layer.
557
+ * @param index - expected index of the layer.
558
+ */
559
+ moveLayer(id, index) {
560
+ // Get the beforeId value according to the expected index.
561
+ const currentLayerIds = this.getLayers();
562
+ let beforeId = undefined;
563
+ if (!(index === undefined) && index < currentLayerIds.length) {
564
+ beforeId = currentLayerIds[index];
565
+ }
566
+ const layerArray = this._Map.getLayers().getArray();
567
+ const movingLayer = this.getLayer(id);
568
+ if (!movingLayer || !index || !beforeId) {
569
+ return;
570
+ }
571
+ const indexOfMovingLayer = layerArray.indexOf(movingLayer);
572
+ layerArray.splice(indexOfMovingLayer, 1);
573
+ const beforeLayer = this.getLayer(beforeId);
574
+ if (!beforeLayer) {
575
+ return;
576
+ }
577
+ const indexOfBeforeLayer = layerArray.indexOf(beforeLayer);
578
+ layerArray.splice(indexOfBeforeLayer, 0, movingLayer);
579
+ this._Map.setLayers(layerArray);
580
+ }
581
+ /**
582
+ * Update a layer of the map.
583
+ *
584
+ * @param id - id of the layer.
585
+ * @param layer - the layer object.
586
+ */
587
+ async updateLayer(id, layer, mapLayer) {
588
+ var _a, _b, _c, _d;
589
+ const sourceId = (_a = layer.parameters) === null || _a === void 0 ? void 0 : _a.source;
590
+ const source = this._model.sharedModel.getSource(sourceId);
591
+ if (!source) {
592
+ return;
593
+ }
594
+ if (!this._sources[sourceId]) {
595
+ await this.addSource(sourceId, source);
596
+ }
597
+ mapLayer.setVisible(layer.visible);
598
+ switch (layer.type) {
599
+ case 'RasterLayer': {
600
+ mapLayer.setOpacity(((_b = layer.parameters) === null || _b === void 0 ? void 0 : _b.opacity) || 1);
601
+ break;
602
+ }
603
+ case 'VectorLayer': {
604
+ const layerParams = layer.parameters;
605
+ mapLayer.setOpacity(layerParams.opacity || 1);
606
+ mapLayer.setStyle(currentFeature => this.vectorLayerStyleFunc(currentFeature, layer));
607
+ break;
608
+ }
609
+ case 'VectorTileLayer': {
610
+ const layerParams = layer.parameters;
611
+ mapLayer.setOpacity(layerParams.opacity || 1);
612
+ mapLayer.setStyle(currentFeature => this.vectorLayerStyleFunc(currentFeature, layer));
613
+ break;
614
+ }
615
+ case 'HillshadeLayer': {
616
+ // TODO figure out color here
617
+ break;
618
+ }
619
+ case 'ImageLayer': {
620
+ break;
621
+ }
622
+ case 'WebGlLayer': {
623
+ mapLayer.setOpacity((_c = layer.parameters) === null || _c === void 0 ? void 0 : _c.opacity);
624
+ mapLayer.setStyle({
625
+ color: (_d = layer === null || layer === void 0 ? void 0 : layer.parameters) === null || _d === void 0 ? void 0 : _d.color
626
+ });
627
+ break;
628
+ }
629
+ }
630
+ }
631
+ /**
632
+ * Remove a layer from the map.
633
+ *
634
+ * @param id - the id of the layer.
635
+ */
636
+ removeLayer(id) {
637
+ const mapLayer = this.getLayer(id);
638
+ if (mapLayer) {
639
+ this._Map.removeLayer(mapLayer);
640
+ }
641
+ }
642
+ _onSharedOptionsChanged(sender, change) {
643
+ if (!this._initializedPosition) {
644
+ const options = this._model.getOptions();
645
+ this.updateOptions(options);
646
+ this._initializedPosition = true;
647
+ }
648
+ }
649
+ updateOptions(options) {
650
+ const view = this._Map.getView();
651
+ // use the extent if provided.
652
+ if (options.extent) {
653
+ view.fit(options.extent);
654
+ }
655
+ else {
656
+ const centerCoord = fromLonLat([options.longitude || 0, options.latitude || 0], this._Map.getView().getProjection());
657
+ this._Map.getView().setZoom(options.zoom || 0);
658
+ this._Map.getView().setCenter(centerCoord);
659
+ }
660
+ view.setRotation(options.bearing || 0);
661
+ }
662
+ _onViewChanged(sender, change) {
663
+ // TODO SOMETHING
664
+ }
665
+ /**
666
+ * Convenience method to get a specific layer from OpenLayers Map
667
+ * @param id Layer to retrieve
668
+ */
669
+ getLayer(id) {
670
+ return this._Map
671
+ .getLayers()
672
+ .getArray()
673
+ .find(layer => layer.get('id') === id);
674
+ }
675
+ /**
676
+ * Convenience method to get list layer IDs from the OpenLayers Map
677
+ */
678
+ getLayers() {
679
+ return this._Map
680
+ .getLayers()
681
+ .getArray()
682
+ .map(layer => layer.get('id'));
683
+ }
684
+ _onLayersChanged(_, change) {
685
+ var _a;
686
+ // Avoid concurrency update on layers on first load, if layersTreeChanged and
687
+ // LayersChanged are triggered simultaneously.
688
+ if (!this._ready) {
689
+ return;
690
+ }
691
+ (_a = change.layerChange) === null || _a === void 0 ? void 0 : _a.forEach(change => {
692
+ const layer = change.newValue;
693
+ if (!layer || Object.keys(layer).length === 0) {
694
+ this.removeLayer(change.id);
695
+ }
696
+ else {
697
+ const mapLayer = this.getLayer(change.id);
698
+ if (mapLayer &&
699
+ JupyterGISModel.getOrderedLayerIds(this._model).includes(change.id)) {
700
+ this.updateLayer(change.id, layer, mapLayer);
701
+ }
702
+ else {
703
+ this.updateLayers(JupyterGISModel.getOrderedLayerIds(this._model));
704
+ }
705
+ }
706
+ });
707
+ }
708
+ _onLayerTreeChange(sender, change) {
709
+ this._ready = false;
710
+ // We can't properly use the change, because of the nested groups in the the shared
711
+ // document which is flattened for the map tool.
712
+ this.updateLayers(JupyterGISModel.getOrderedLayerIds(this._model));
713
+ }
714
+ _onSourcesChange(_, change) {
715
+ var _a;
716
+ if (!this._ready) {
717
+ return;
718
+ }
719
+ (_a = change.sourceChange) === null || _a === void 0 ? void 0 : _a.forEach(change => {
720
+ if (!change.newValue || Object.keys(change.newValue).length === 0) {
721
+ this.removeSource(change.id);
722
+ }
723
+ else {
724
+ const source = this._model.getSource(change.id);
725
+ if (source) {
726
+ this.updateSource(change.id, source);
727
+ }
728
+ }
729
+ });
730
+ }
731
+ render() {
732
+ return (React.createElement("div", { className: "jGIS-Mainview", style: {
733
+ border: this.state.remoteUser
734
+ ? `solid 3px ${this.state.remoteUser.color}`
735
+ : 'unset'
736
+ } },
737
+ React.createElement(Spinner, { loading: this.state.loading }),
738
+ React.createElement("div", { ref: this.divRef, style: {
739
+ width: '100%',
740
+ height: 'calc(100%)'
741
+ } })));
742
+ }
743
+ }