@jupytergis/base 0.3.0 → 0.4.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 (76) hide show
  1. package/lib/annotations/components/Annotation.js +1 -1
  2. package/lib/annotations/model.d.ts +6 -7
  3. package/lib/annotations/model.js +15 -15
  4. package/lib/commands.d.ts +2 -3
  5. package/lib/commands.js +117 -62
  6. package/lib/constants.d.ts +2 -0
  7. package/lib/constants.js +4 -1
  8. package/lib/dialogs/formdialog.js +2 -2
  9. package/lib/dialogs/layerBrowserDialog.d.ts +4 -5
  10. package/lib/dialogs/layerBrowserDialog.js +9 -9
  11. package/lib/dialogs/symbology/hooks/useGetBandInfo.d.ts +1 -2
  12. package/lib/dialogs/symbology/hooks/useGetBandInfo.js +11 -6
  13. package/lib/dialogs/symbology/hooks/useGetProperties.d.ts +1 -1
  14. package/lib/dialogs/symbology/hooks/useGetProperties.js +6 -8
  15. package/lib/dialogs/symbology/symbologyDialog.d.ts +2 -3
  16. package/lib/dialogs/symbology/symbologyDialog.js +10 -9
  17. package/lib/dialogs/symbology/tiff_layer/TiffRendering.d.ts +1 -1
  18. package/lib/dialogs/symbology/tiff_layer/TiffRendering.js +6 -6
  19. package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.d.ts +1 -1
  20. package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.js +5 -4
  21. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.d.ts +1 -1
  22. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +8 -7
  23. package/lib/dialogs/symbology/vector_layer/VectorRendering.d.ts +1 -1
  24. package/lib/dialogs/symbology/vector_layer/VectorRendering.js +18 -13
  25. package/lib/dialogs/symbology/vector_layer/types/Categorized.d.ts +1 -1
  26. package/lib/dialogs/symbology/vector_layer/types/Categorized.js +30 -19
  27. package/lib/dialogs/symbology/vector_layer/types/Graduated.d.ts +1 -1
  28. package/lib/dialogs/symbology/vector_layer/types/Graduated.js +16 -13
  29. package/lib/dialogs/symbology/vector_layer/types/Heatmap.d.ts +4 -0
  30. package/lib/dialogs/symbology/vector_layer/types/Heatmap.js +77 -0
  31. package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.d.ts +1 -1
  32. package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.js +4 -3
  33. package/lib/formbuilder/creationform.d.ts +1 -2
  34. package/lib/formbuilder/creationform.js +4 -4
  35. package/lib/formbuilder/editform.d.ts +1 -2
  36. package/lib/formbuilder/editform.js +7 -7
  37. package/lib/formbuilder/formselectors.js +5 -2
  38. package/lib/formbuilder/objectform/baseform.d.ts +3 -4
  39. package/lib/formbuilder/objectform/baseform.js +2 -2
  40. package/lib/formbuilder/objectform/fileselectorwidget.js +13 -6
  41. package/lib/formbuilder/objectform/geotiffsource.d.ts +5 -1
  42. package/lib/formbuilder/objectform/geotiffsource.js +64 -18
  43. package/lib/formbuilder/objectform/heatmapLayerForm.d.ts +11 -0
  44. package/lib/formbuilder/objectform/heatmapLayerForm.js +60 -0
  45. package/lib/formbuilder/objectform/vectorlayerform.d.ts +0 -2
  46. package/lib/formbuilder/objectform/vectorlayerform.js +0 -59
  47. package/lib/mainview/TemporalSlider.d.ts +8 -0
  48. package/lib/mainview/TemporalSlider.js +303 -0
  49. package/lib/mainview/mainView.d.ts +25 -4
  50. package/lib/mainview/mainView.js +213 -75
  51. package/lib/mainview/mainviewmodel.d.ts +4 -0
  52. package/lib/mainview/mainviewmodel.js +4 -0
  53. package/lib/mainview/mainviewwidget.d.ts +0 -2
  54. package/lib/mainview/mainviewwidget.js +0 -2
  55. package/lib/panelview/annotationPanel.js +5 -5
  56. package/lib/panelview/components/filter-panel/Filter.js +4 -25
  57. package/lib/panelview/components/identify-panel/IdentifyPanel.js +1 -1
  58. package/lib/panelview/components/layers.js +2 -2
  59. package/lib/panelview/components/sources.js +1 -1
  60. package/lib/panelview/leftpanel.d.ts +3 -0
  61. package/lib/panelview/leftpanel.js +5 -1
  62. package/lib/panelview/model.js +8 -8
  63. package/lib/panelview/objectproperties.js +10 -10
  64. package/lib/panelview/rightpanel.d.ts +1 -1
  65. package/lib/panelview/rightpanel.js +10 -10
  66. package/lib/toolbar/widget.d.ts +1 -1
  67. package/lib/toolbar/widget.js +44 -32
  68. package/lib/tools.d.ts +10 -6
  69. package/lib/tools.js +89 -15
  70. package/lib/types.d.ts +2 -0
  71. package/lib/widget.d.ts +29 -5
  72. package/lib/widget.js +41 -7
  73. package/package.json +4 -3
  74. package/style/base.css +10 -0
  75. package/style/symbologyDialog.css +7 -1
  76. package/style/temporalSlider.css +47 -0
@@ -1,4 +1,5 @@
1
1
  import { JupyterGISModel } from '@jupytergis/schema';
2
+ import { showErrorMessage } from '@jupyterlab/apputils';
2
3
  import { CommandRegistry } from '@lumino/commands';
3
4
  import { UUID } from '@lumino/coreutils';
4
5
  import { ContextMenu } from '@lumino/widgets';
@@ -9,26 +10,26 @@ import { ScaleLine } from 'ol/control';
9
10
  import { singleClick } from 'ol/events/condition';
10
11
  import { GeoJSON, MVT } from 'ol/format';
11
12
  import { DragAndDrop, Select } from 'ol/interaction';
12
- import { Image as ImageLayer, Vector as VectorLayer, VectorTile as VectorTileLayer, WebGLTile as WebGlTileLayer } from 'ol/layer';
13
+ import { Heatmap as HeatmapLayer, Image as ImageLayer, Vector as VectorLayer, VectorTile as VectorTileLayer, WebGLTile as WebGlTileLayer } from 'ol/layer';
13
14
  import TileLayer from 'ol/layer/Tile';
14
15
  import { fromLonLat, get as getRegisteredProjection, toLonLat, transformExtent } from 'ol/proj';
15
16
  import { get as getProjection } from 'ol/proj.js';
16
17
  import { register } from 'ol/proj/proj4.js';
17
- import Feature from 'ol/render/Feature';
18
+ import RenderFeature from 'ol/render/Feature';
18
19
  import { GeoTIFF as GeoTIFFSource, ImageTile as ImageTileSource, Vector as VectorSource, VectorTile as VectorTileSource, XYZ as XYZSource } from 'ol/source';
19
20
  import Static from 'ol/source/ImageStatic';
20
21
  import TileSource from 'ol/source/Tile';
21
22
  import { Circle, Fill, Stroke, Style } from 'ol/style';
22
23
  import proj4 from 'proj4';
23
- //@ts-expect-error no types for proj4list
24
24
  import proj4list from 'proj4-list';
25
25
  import * as React from 'react';
26
26
  import AnnotationFloater from '../annotations/components/AnnotationFloater';
27
27
  import { CommandIDs } from '../constants';
28
28
  import StatusBar from '../statusbar/StatusBar';
29
- import { isLightTheme, loadFile, loadGeoTIFFWithCache, throttle } from '../tools';
29
+ import { isLightTheme, loadFile, throttle } from '../tools';
30
30
  import CollaboratorPointers from './CollaboratorPointers';
31
31
  import { FollowIndicator } from './FollowIndicator';
32
+ import TemporalSlider from './TemporalSlider';
32
33
  import { Spinner } from './spinner';
33
34
  export class MainView extends React.Component {
34
35
  constructor(props) {
@@ -128,6 +129,7 @@ export class MainView extends React.Component {
128
129
  });
129
130
  };
130
131
  this.vectorLayerStyleRuleBuilder = (layer) => {
132
+ var _a, _b;
131
133
  const layerParams = layer.parameters;
132
134
  if (!layerParams) {
133
135
  return;
@@ -145,27 +147,25 @@ export class MainView extends React.Component {
145
147
  style: defaultStyle
146
148
  };
147
149
  const layerStyle = Object.assign({}, defaultRules);
148
- if (layer.filters &&
149
- layer.filters.logicalOp &&
150
- layer.filters.appliedFilters.length !== 0) {
151
- const filterExpr = [];
150
+ if (((_a = layer.filters) === null || _a === void 0 ? void 0 : _a.logicalOp) && ((_b = layer.filters.appliedFilters) === null || _b === void 0 ? void 0 : _b.length) > 0) {
151
+ const buildCondition = (filter) => {
152
+ const base = [filter.operator, ['get', filter.feature]];
153
+ return filter.operator === 'between'
154
+ ? [...base, filter.betweenMin, filter.betweenMax]
155
+ : [...base, filter.value];
156
+ };
157
+ let filterExpr;
152
158
  // 'Any' and 'All' operators require more than one argument
153
159
  // So if there's only one filter, skip that part to avoid error
154
160
  if (layer.filters.appliedFilters.length === 1) {
155
- layer.filters.appliedFilters.forEach(filter => {
156
- filterExpr.push(filter.operator, ['get', filter.feature], filter.value);
157
- });
161
+ filterExpr = buildCondition(layer.filters.appliedFilters[0]);
158
162
  }
159
163
  else {
160
- filterExpr.push(layer.filters.logicalOp);
161
164
  // Arguments for "Any" and 'All' need to be wrapped in brackets
162
- layer.filters.appliedFilters.forEach(filter => {
163
- filterExpr.push([
164
- filter.operator,
165
- ['get', filter.feature],
166
- filter.value
167
- ]);
168
- });
165
+ filterExpr = [
166
+ layer.filters.logicalOp,
167
+ ...layer.filters.appliedFilters.map(buildCondition)
168
+ ];
169
169
  }
170
170
  layerStyle.filter = filterExpr;
171
171
  }
@@ -223,16 +223,64 @@ export class MainView extends React.Component {
223
223
  const scaled = ['*', 255, cosIncidence];
224
224
  return scaled;
225
225
  };
226
+ /**
227
+ * Heatmap layers don't work with style based filtering.
228
+ * This modifies the features in the underlying source
229
+ * to work with the temporal controller
230
+ */
231
+ this.handleTemporalController = (id, layer) => {
232
+ var _a, _b, _c, _d, _e, _f;
233
+ const selectedLayer = (_c = (_b = (_a = this._model) === null || _a === void 0 ? void 0 : _a.localState) === null || _b === void 0 ? void 0 : _b.selected) === null || _c === void 0 ? void 0 : _c.value;
234
+ // Temporal Controller shouldn't be active if more than one layer is selected
235
+ if (!selectedLayer || Object.keys(selectedLayer).length !== 1) {
236
+ return;
237
+ }
238
+ const selectedLayerId = Object.keys(selectedLayer)[0];
239
+ // Don't do anything to unselected layers
240
+ if (selectedLayerId !== id) {
241
+ return;
242
+ }
243
+ const layerParams = layer.parameters;
244
+ const source = this._sources[layerParams.source];
245
+ if ((_d = layer.filters) === null || _d === void 0 ? void 0 : _d.appliedFilters.length) {
246
+ // Heatmaps don't work with existing filter system so this should be fine
247
+ const activeFilter = layer.filters.appliedFilters[0];
248
+ // Save original features on first filter application
249
+ if (!Object.keys(this._originalFeatures).includes(id)) {
250
+ this._originalFeatures[id] = source.getFeatures();
251
+ }
252
+ // clear current features
253
+ source.clear();
254
+ const startTime = (_e = activeFilter.betweenMin) !== null && _e !== void 0 ? _e : 0;
255
+ const endTime = (_f = activeFilter.betweenMax) !== null && _f !== void 0 ? _f : 1000;
256
+ const filteredFeatures = this._originalFeatures[id].filter(feature => {
257
+ const featureTime = feature.get(activeFilter.feature);
258
+ return featureTime >= startTime && featureTime <= endTime;
259
+ });
260
+ // set state for restoration
261
+ this.setState(old => (Object.assign(Object.assign({}, old), { filterStates: Object.assign(Object.assign({}, this.state.filterStates), { [selectedLayerId]: activeFilter }) })));
262
+ source.addFeatures(filteredFeatures);
263
+ }
264
+ else {
265
+ // Restore original features when no filters are applied
266
+ source.addFeatures(this._originalFeatures[id]);
267
+ delete this._originalFeatures[id];
268
+ }
269
+ };
226
270
  this._onClientSharedStateChanged = (sender, clients) => {
227
- var _a, _b, _c, _d, _e;
228
- const remoteUser = (_a = this._model.localState) === null || _a === void 0 ? void 0 : _a.remoteUser;
271
+ var _a, _b, _c;
272
+ const localState = this._model.localState;
273
+ if (!localState) {
274
+ return;
275
+ }
276
+ const remoteUser = localState.remoteUser;
229
277
  // If we are in following mode, we update our position and selection
230
278
  if (remoteUser) {
231
279
  const remoteState = clients.get(remoteUser);
232
280
  if (!remoteState) {
233
281
  return;
234
282
  }
235
- if (((_b = remoteState.user) === null || _b === void 0 ? void 0 : _b.username) !== ((_c = this.state.remoteUser) === null || _c === void 0 ? void 0 : _c.username)) {
283
+ if (((_a = remoteState.user) === null || _a === void 0 ? void 0 : _a.username) !== ((_b = this.state.remoteUser) === null || _b === void 0 ? void 0 : _b.username)) {
236
284
  this.setState(old => (Object.assign(Object.assign({}, old), { remoteUser: remoteState.user })));
237
285
  }
238
286
  const remoteViewport = remoteState.viewportState;
@@ -246,7 +294,7 @@ export class MainView extends React.Component {
246
294
  // If we are unfollowing a remote user, we reset our center and zoom to their previous values
247
295
  if (this.state.remoteUser !== null) {
248
296
  this.setState(old => (Object.assign(Object.assign({}, old), { remoteUser: null })));
249
- const viewportState = (_e = (_d = this._model.localState) === null || _d === void 0 ? void 0 : _d.viewportState) === null || _e === void 0 ? void 0 : _e.value;
297
+ const viewportState = (_c = localState.viewportState) === null || _c === void 0 ? void 0 : _c.value;
250
298
  if (viewportState) {
251
299
  this._moveToPosition(viewportState.coordinates, viewportState.zoom);
252
300
  }
@@ -289,6 +337,13 @@ export class MainView extends React.Component {
289
337
  }
290
338
  this.setState(old => (Object.assign(Object.assign({}, old), { clientPointers: clientPointers })));
291
339
  });
340
+ // Temporal controller bit
341
+ // ? There's probably a better way to get changes in the model to trigger react rerenders
342
+ const isTemporalControllerActive = localState.isTemporalControllerActive;
343
+ if (isTemporalControllerActive !== this.state.displayTemporalController) {
344
+ this.setState(old => (Object.assign(Object.assign({}, old), { displayTemporalController: isTemporalControllerActive })));
345
+ this._mainViewModel.commands.notifyCommandChanged(CommandIDs.temporalController);
346
+ }
292
347
  };
293
348
  this._onSharedModelStateChange = (_, change) => {
294
349
  var _a;
@@ -343,10 +398,11 @@ export class MainView extends React.Component {
343
398
  this._handleWindowResize = () => {
344
399
  // TODO SOMETHING
345
400
  };
346
- this._initializedPosition = false;
401
+ this._isPositionInitialized = false;
347
402
  this.divRef = React.createRef(); // Reference of render div
348
403
  this._ready = false;
349
404
  this._sourceToLayerMap = new Map();
405
+ this._originalFeatures = {};
350
406
  this._mainViewModel = this.props.viewModel;
351
407
  this._mainViewModel.viewSettingChanged.connect(this._onViewChanged, this);
352
408
  this._model = this._mainViewModel.jGISModel;
@@ -357,8 +413,10 @@ export class MainView extends React.Component {
357
413
  this._model.sharedLayerTreeChanged.connect(this._onLayerTreeChange, this);
358
414
  this._model.sharedSourcesChanged.connect(this._onSourcesChange, this);
359
415
  this._model.sharedModel.changed.connect(this._onSharedModelStateChange);
360
- this._mainViewModel.jGISModel.sharedMetadataChanged.connect(this._onSharedMetadataChanged, this);
416
+ this._model.sharedMetadataChanged.connect(this._onSharedMetadataChanged, this);
361
417
  this._model.zoomToPositionSignal.connect(this._onZoomToPosition, this);
418
+ this._model.updateLayerSignal.connect(this._triggerLayerUpdate, this);
419
+ this._model.addFeatureAsMsSignal.connect(this._convertFeatureToMs, this);
362
420
  this.state = {
363
421
  id: this._mainViewModel.id,
364
422
  lightTheme: isLightTheme(),
@@ -368,7 +426,10 @@ export class MainView extends React.Component {
368
426
  clientPointers: {},
369
427
  viewProjection: { code: '', units: '' },
370
428
  loadingLayer: false,
371
- scale: 0
429
+ scale: 0,
430
+ loadingErrors: [],
431
+ displayTemporalController: false,
432
+ filterStates: {}
372
433
  };
373
434
  this._sources = [];
374
435
  this._loadingLayers = new Set();
@@ -458,9 +519,6 @@ export class MainView extends React.Component {
458
519
  }
459
520
  });
460
521
  this._Map.on('moveend', () => {
461
- if (!this._initializedPosition) {
462
- return;
463
- }
464
522
  const currentOptions = this._model.getOptions();
465
523
  const view = this._Map.getView();
466
524
  const center = view.getCenter() || [0, 0];
@@ -495,7 +553,6 @@ export class MainView extends React.Component {
495
553
  await this._updateLayersImpl(JupyterGISModel.getOrderedLayerIds(this._model));
496
554
  const options = this._model.getOptions();
497
555
  this.updateOptions(options);
498
- this._initializedPosition = true;
499
556
  }
500
557
  this._Map.getViewport().addEventListener('contextmenu', event => {
501
558
  event.preventDefault();
@@ -510,10 +567,6 @@ export class MainView extends React.Component {
510
567
  } })));
511
568
  }
512
569
  }
513
- async _loadGeoTIFFWithCache(sourceInfo) {
514
- const result = await loadGeoTIFFWithCache(sourceInfo);
515
- return result === null || result === void 0 ? void 0 : result.file;
516
- }
517
570
  /**
518
571
  * Add a source in the map.
519
572
  *
@@ -564,7 +617,7 @@ export class MainView extends React.Component {
564
617
  minZoom: sourceParameters.minZoom,
565
618
  maxZoom: sourceParameters.maxZoom,
566
619
  url: url,
567
- format: new MVT({ featureClass: Feature })
620
+ format: new MVT({ featureClass: RenderFeature })
568
621
  });
569
622
  }
570
623
  else {
@@ -656,8 +709,13 @@ export class MainView extends React.Component {
656
709
  return Object.assign(Object.assign({}, url), { nodata: 0 });
657
710
  };
658
711
  const sourcesWithBlobs = await Promise.all(sourceParameters.urls.map(async (sourceInfo) => {
659
- const blob = await this._loadGeoTIFFWithCache(sourceInfo);
660
- return Object.assign(Object.assign({}, addNoData(sourceInfo)), { blob });
712
+ var _a;
713
+ const geotiff = await loadFile({
714
+ filepath: (_a = sourceInfo.url) !== null && _a !== void 0 ? _a : '',
715
+ type: 'GeoTiffSource',
716
+ model: this._model
717
+ });
718
+ return Object.assign(Object.assign({}, addNoData(sourceInfo)), { geotiff, url: URL.createObjectURL(geotiff.file) });
661
719
  }));
662
720
  newSource = new GeoTIFFSource({
663
721
  sources: sourcesWithBlobs,
@@ -773,7 +831,7 @@ export class MainView extends React.Component {
773
831
  * @returns - the map layer.
774
832
  */
775
833
  async _buildMapLayer(id, layer) {
776
- var _a;
834
+ var _a, _b, _c;
777
835
  const sourceId = (_a = layer.parameters) === null || _a === void 0 ? void 0 : _a.source;
778
836
  const source = this._model.sharedModel.getLayerSource(sourceId);
779
837
  if (!source) {
@@ -815,7 +873,6 @@ export class MainView extends React.Component {
815
873
  opacity: layerParameters.opacity,
816
874
  source: this._sources[layerParameters.source]
817
875
  });
818
- this.updateLayer(id, layer, newMapLayer);
819
876
  break;
820
877
  }
821
878
  case 'HillshadeLayer': {
@@ -850,6 +907,17 @@ export class MainView extends React.Component {
850
907
  newMapLayer = new WebGlTileLayer(layerOptions);
851
908
  break;
852
909
  }
910
+ case 'HeatmapLayer': {
911
+ layerParameters = layer.parameters;
912
+ newMapLayer = new HeatmapLayer({
913
+ opacity: layerParameters.opacity,
914
+ source: this._sources[layerParameters.source],
915
+ blur: (_b = layerParameters.blur) !== null && _b !== void 0 ? _b : 15,
916
+ radius: (_c = layerParameters.radius) !== null && _c !== void 0 ? _c : 8,
917
+ gradient: layerParameters.color
918
+ });
919
+ break;
920
+ }
853
921
  }
854
922
  await this._waitForSourceReady(newMapLayer);
855
923
  // OpenLayers doesn't have name/id field so add it
@@ -897,10 +965,29 @@ export class MainView extends React.Component {
897
965
  // Layer already exists
898
966
  return;
899
967
  }
900
- const newMapLayer = await this._buildMapLayer(id, layer);
901
- if (newMapLayer !== undefined) {
902
- await this._waitForReady();
903
- this._Map.getLayers().insertAt(index, newMapLayer);
968
+ try {
969
+ const newMapLayer = await this._buildMapLayer(id, layer);
970
+ if (newMapLayer !== undefined) {
971
+ await this._waitForReady();
972
+ // Adjust index to ensure it's within bounds
973
+ const numLayers = this._Map.getLayers().getLength();
974
+ const safeIndex = Math.min(index, numLayers);
975
+ this._Map.getLayers().insertAt(safeIndex, newMapLayer);
976
+ }
977
+ }
978
+ catch (error) {
979
+ if (this.state.loadingErrors.find(item => item.id === id && item.error === error.message)) {
980
+ this._loadingLayers.delete(id);
981
+ return;
982
+ }
983
+ await showErrorMessage(`Error Adding ${layer.name}`, `Failed to add ${layer.name}: ${error.message || 'invalid file path'}`);
984
+ this.setState(old => (Object.assign(Object.assign({}, old), { loadingLayer: false })));
985
+ this.state.loadingErrors.push({
986
+ id,
987
+ error: error.message || 'invalid file path',
988
+ index
989
+ });
990
+ this._loadingLayers.delete(id);
904
991
  }
905
992
  }
906
993
  /**
@@ -909,8 +996,8 @@ export class MainView extends React.Component {
909
996
  * @param id - id of the layer.
910
997
  * @param layer - the layer object.
911
998
  */
912
- async updateLayer(id, layer, mapLayer) {
913
- var _a, _b, _c, _d;
999
+ async updateLayer(id, layer, mapLayer, oldLayer) {
1000
+ var _a, _b, _c, _d, _e, _f, _g, _h;
914
1001
  const sourceId = (_a = layer.parameters) === null || _a === void 0 ? void 0 : _a.source;
915
1002
  const source = this._model.sharedModel.getLayerSource(sourceId);
916
1003
  if (!source) {
@@ -953,6 +1040,16 @@ export class MainView extends React.Component {
953
1040
  }
954
1041
  break;
955
1042
  }
1043
+ case 'HeatmapLayer': {
1044
+ const layerParams = layer.parameters;
1045
+ const heatmap = mapLayer;
1046
+ heatmap.setOpacity((_e = layerParams.opacity) !== null && _e !== void 0 ? _e : 1);
1047
+ heatmap.setBlur((_f = layerParams.blur) !== null && _f !== void 0 ? _f : 15);
1048
+ heatmap.setRadius((_g = layerParams.radius) !== null && _g !== void 0 ? _g : 8);
1049
+ heatmap.setGradient((_h = layerParams.color) !== null && _h !== void 0 ? _h : ['#00f', '#0ff', '#0f0', '#ff0', '#f00']);
1050
+ this.handleTemporalController(id, layer);
1051
+ break;
1052
+ }
956
1053
  }
957
1054
  }
958
1055
  /**
@@ -1006,11 +1103,11 @@ export class MainView extends React.Component {
1006
1103
  this._Map.removeLayer(mapLayer);
1007
1104
  }
1008
1105
  }
1009
- _onSharedOptionsChanged(sender, change) {
1010
- if (!this._initializedPosition) {
1106
+ _onSharedOptionsChanged() {
1107
+ if (!this._isPositionInitialized) {
1011
1108
  const options = this._model.getOptions();
1012
1109
  this.updateOptions(options);
1013
- this._initializedPosition = true;
1110
+ this._isPositionInitialized = true;
1014
1111
  }
1015
1112
  }
1016
1113
  async updateOptions(options) {
@@ -1097,7 +1194,20 @@ export class MainView extends React.Component {
1097
1194
  if (currentIndex < index) {
1098
1195
  nextIndex -= 1;
1099
1196
  }
1100
- this._Map.getLayers().insertAt(nextIndex, layer);
1197
+ // Adjust index to ensure it's within bounds
1198
+ const numLayers = this._Map.getLayers().getLength();
1199
+ const safeIndex = Math.min(index, numLayers);
1200
+ this._Map.getLayers().insertAt(safeIndex, layer);
1201
+ }
1202
+ /**
1203
+ * Remove and recreate layer
1204
+ * @param id ID of layer being replaced
1205
+ * @param layer New layer to replace with
1206
+ */
1207
+ replaceLayer(id, layer) {
1208
+ const layerIndex = this.getLayerIndex(id);
1209
+ this.removeLayer(id);
1210
+ this.addLayer(id, layer, layerIndex);
1101
1211
  }
1102
1212
  _onLayersChanged(_, change) {
1103
1213
  var _a;
@@ -1107,21 +1217,25 @@ export class MainView extends React.Component {
1107
1217
  return;
1108
1218
  }
1109
1219
  (_a = change.layerChange) === null || _a === void 0 ? void 0 : _a.forEach(change => {
1110
- const layer = change.newValue;
1111
- if (!layer || Object.keys(layer).length === 0) {
1112
- this.removeLayer(change.id);
1220
+ const { id, oldValue: oldLayer, newValue: newLayer } = change;
1221
+ if (!newLayer || Object.keys(newLayer).length === 0) {
1222
+ this.removeLayer(id);
1223
+ return;
1224
+ }
1225
+ if (oldLayer && oldLayer.type !== newLayer.type) {
1226
+ this.replaceLayer(id, newLayer);
1227
+ return;
1228
+ }
1229
+ const mapLayer = this.getLayer(id);
1230
+ const layerTree = JupyterGISModel.getOrderedLayerIds(this._model);
1231
+ if (!mapLayer) {
1232
+ return;
1233
+ }
1234
+ if (layerTree.includes(id)) {
1235
+ this.updateLayer(id, newLayer, mapLayer, oldLayer);
1113
1236
  }
1114
1237
  else {
1115
- const mapLayer = this.getLayer(change.id);
1116
- const layerTree = JupyterGISModel.getOrderedLayerIds(this._model);
1117
- if (mapLayer) {
1118
- if (layerTree.includes(change.id)) {
1119
- this.updateLayer(change.id, layer, mapLayer);
1120
- }
1121
- else {
1122
- this.updateLayers(layerTree);
1123
- }
1124
- }
1238
+ this.updateLayers(layerTree);
1125
1239
  }
1126
1240
  });
1127
1241
  }
@@ -1256,6 +1370,28 @@ export class MainView extends React.Component {
1256
1370
  }
1257
1371
  }
1258
1372
  }
1373
+ _triggerLayerUpdate(_, args) {
1374
+ // ? could send just the filters object and modify that instead of emitting whole layer
1375
+ const json = JSON.parse(args);
1376
+ const { layerId, layer: jgisLayer } = json;
1377
+ const olLayer = this.getLayer(layerId);
1378
+ if (!jgisLayer || !olLayer) {
1379
+ console.log('Layer not found');
1380
+ return;
1381
+ }
1382
+ this.updateLayer(layerId, jgisLayer, olLayer);
1383
+ }
1384
+ _convertFeatureToMs(_, args) {
1385
+ const json = JSON.parse(args);
1386
+ const { id: layerId, selectedFeature } = json;
1387
+ const olLayer = this.getLayer(layerId);
1388
+ const source = olLayer.getSource();
1389
+ source.forEachFeature(feature => {
1390
+ const time = feature.get(selectedFeature);
1391
+ const parsedTime = typeof time === 'string' ? Date.parse(time) : time;
1392
+ feature.set(`${selectedFeature}ms`, parsedTime);
1393
+ });
1394
+ }
1259
1395
  render() {
1260
1396
  return (React.createElement(React.Fragment, null,
1261
1397
  Object.entries(this.state.annotations).map(([key, annotation]) => {
@@ -1269,18 +1405,20 @@ export class MainView extends React.Component {
1269
1405
  }, className: 'jGIS-Popup-Wrapper' },
1270
1406
  React.createElement(AnnotationFloater, { itemId: key, annotationModel: this._model.annotationModel, open: false }))));
1271
1407
  }),
1272
- React.createElement("div", { className: "jGIS-Mainview", style: {
1273
- border: this.state.remoteUser
1274
- ? `solid 3px ${this.state.remoteUser.color}`
1275
- : 'unset'
1276
- } },
1277
- React.createElement(Spinner, { loading: this.state.loading }),
1278
- React.createElement(FollowIndicator, { remoteUser: this.state.remoteUser }),
1279
- React.createElement(CollaboratorPointers, { clients: this.state.clientPointers }),
1280
- React.createElement("div", { ref: this.divRef, style: {
1281
- width: '100%',
1282
- height: 'calc(100%)'
1283
- } })),
1284
- React.createElement(StatusBar, { jgisModel: this._model, loading: this.state.loadingLayer, projection: this.state.viewProjection, scale: this.state.scale })));
1408
+ React.createElement("div", { className: "jGIS-Mainview-Container" },
1409
+ this.state.displayTemporalController && (React.createElement(TemporalSlider, { model: this._model, filterStates: this.state.filterStates })),
1410
+ React.createElement("div", { className: "jGIS-Mainview", style: {
1411
+ border: this.state.remoteUser
1412
+ ? `solid 3px ${this.state.remoteUser.color}`
1413
+ : 'unset'
1414
+ } },
1415
+ React.createElement(Spinner, { loading: this.state.loading }),
1416
+ React.createElement(FollowIndicator, { remoteUser: this.state.remoteUser }),
1417
+ React.createElement(CollaboratorPointers, { clients: this.state.clientPointers }),
1418
+ React.createElement("div", { ref: this.divRef, style: {
1419
+ width: '100%',
1420
+ height: '100%'
1421
+ } })),
1422
+ React.createElement(StatusBar, { jgisModel: this._model, loading: this.state.loadingLayer, projection: this.state.viewProjection, scale: this.state.scale }))));
1285
1423
  }
1286
1424
  }
@@ -1,5 +1,6 @@
1
1
  import { IAnnotation, IJupyterGISModel } from '@jupytergis/schema';
2
2
  import { ObservableMap } from '@jupyterlab/observables';
3
+ import { CommandRegistry } from '@lumino/commands';
3
4
  import { JSONValue } from '@lumino/coreutils';
4
5
  import { IDisposable } from '@lumino/disposable';
5
6
  export declare class MainViewModel implements IDisposable {
@@ -8,12 +9,14 @@ export declare class MainViewModel implements IDisposable {
8
9
  get id(): string;
9
10
  get jGISModel(): IJupyterGISModel;
10
11
  get viewSettingChanged(): import("@lumino/signaling").ISignal<ObservableMap<JSONValue>, import("@jupyterlab/observables").IObservableMap.IChangedArgs<JSONValue>>;
12
+ get commands(): CommandRegistry;
11
13
  dispose(): void;
12
14
  initSignal(): void;
13
15
  addAnnotation(value: IAnnotation): void;
14
16
  private _onsharedLayersChanged;
15
17
  private _jGISModel;
16
18
  private _viewSetting;
19
+ private _commands;
17
20
  private _id;
18
21
  private _isDisposed;
19
22
  }
@@ -21,5 +24,6 @@ export declare namespace MainViewModel {
21
24
  interface IOptions {
22
25
  jGISModel: IJupyterGISModel;
23
26
  viewSetting: ObservableMap<JSONValue>;
27
+ commands: CommandRegistry;
24
28
  }
25
29
  }
@@ -4,6 +4,7 @@ export class MainViewModel {
4
4
  this._isDisposed = false;
5
5
  this._jGISModel = options.jGISModel;
6
6
  this._viewSetting = options.viewSetting;
7
+ this._commands = options.commands;
7
8
  }
8
9
  get isDisposed() {
9
10
  return this._isDisposed;
@@ -17,6 +18,9 @@ export class MainViewModel {
17
18
  get viewSettingChanged() {
18
19
  return this._viewSetting.changed;
19
20
  }
21
+ get commands() {
22
+ return this._commands;
23
+ }
20
24
  dispose() {
21
25
  if (this._isDisposed) {
22
26
  return;
@@ -3,8 +3,6 @@ import { MainViewModel } from './mainviewmodel';
3
3
  export declare class JupyterGISMainViewPanel extends ReactWidget {
4
4
  /**
5
5
  * Construct a `JupyterGISPanel`.
6
- *
7
- * @param context - The documents context.
8
6
  */
9
7
  constructor(options: {
10
8
  mainViewModel: MainViewModel;
@@ -4,8 +4,6 @@ import { MainView } from './mainView';
4
4
  export class JupyterGISMainViewPanel extends ReactWidget {
5
5
  /**
6
6
  * Construct a `JupyterGISPanel`.
7
- *
8
- * @param context - The documents context.
9
7
  */
10
8
  constructor(options) {
11
9
  super();
@@ -9,12 +9,12 @@ export class AnnotationsPanel extends Component {
9
9
  };
10
10
  this._annotationModel = props.annotationModel;
11
11
  this._rightPanelModel = props.rightPanelModel;
12
- this._annotationModel.contextChanged.connect(async () => {
13
- var _a, _b, _c, _d, _e, _f, _g, _h;
14
- await ((_b = (_a = this._annotationModel) === null || _a === void 0 ? void 0 : _a.context) === null || _b === void 0 ? void 0 : _b.ready);
15
- (_e = (_d = (_c = this._annotationModel) === null || _c === void 0 ? void 0 : _c.context) === null || _d === void 0 ? void 0 : _d.model) === null || _e === void 0 ? void 0 : _e.sharedMetadataChanged.disconnect(updateCallback);
12
+ this._annotationModel.modelChanged.connect(async () => {
13
+ // await this._annotationModel?.context?.ready;
14
+ var _a, _b, _c, _d;
15
+ (_b = (_a = this._annotationModel) === null || _a === void 0 ? void 0 : _a.model) === null || _b === void 0 ? void 0 : _b.sharedMetadataChanged.disconnect(updateCallback);
16
16
  this._annotationModel = props.annotationModel;
17
- (_h = (_g = (_f = this._annotationModel) === null || _f === void 0 ? void 0 : _f.context) === null || _g === void 0 ? void 0 : _g.model) === null || _h === void 0 ? void 0 : _h.sharedMetadataChanged.connect(updateCallback);
17
+ (_d = (_c = this._annotationModel) === null || _c === void 0 ? void 0 : _c.model) === null || _d === void 0 ? void 0 : _d.sharedMetadataChanged.connect(updateCallback);
18
18
  this.forceUpdate();
19
19
  });
20
20
  }
@@ -2,9 +2,8 @@ import { Button, ReactWidget } from '@jupyterlab/ui-components';
2
2
  import { Panel } from '@lumino/widgets';
3
3
  import { cloneDeep } from 'lodash';
4
4
  import React, { useEffect, useRef, useState } from 'react';
5
- import { debounce, getLayerTileInfo } from '../../../tools';
5
+ import { debounce, loadFile } from '../../../tools';
6
6
  import FilterRow from './FilterRow';
7
- import { loadFile } from '../../../tools';
8
7
  /**
9
8
  * The filters panel widget.
10
9
  */
@@ -29,7 +28,7 @@ const FilterComponent = (props) => {
29
28
  const [featuresInLayer, setFeaturesInLayer] = useState({});
30
29
  const [model, setModel] = useState(props.model.jGISModel);
31
30
  (_a = props.model) === null || _a === void 0 ? void 0 : _a.documentChanged.connect((_, widget) => {
32
- setModel(widget === null || widget === void 0 ? void 0 : widget.context.model);
31
+ setModel(widget === null || widget === void 0 ? void 0 : widget.model);
33
32
  });
34
33
  // Reset state values when current widget changes
35
34
  useEffect(() => {
@@ -108,13 +107,12 @@ const FilterComponent = (props) => {
108
107
  featuresInLayerRef.current = featuresInLayer;
109
108
  }, [featuresInLayer]);
110
109
  const buildFilterObject = async (currentLayer) => {
111
- var _a, _b, _c, _d;
110
+ var _a, _b;
112
111
  if (!model) {
113
112
  return;
114
113
  }
115
114
  const layer = model.getLayer(currentLayer !== null && currentLayer !== void 0 ? currentLayer : selectedLayer);
116
115
  const source = model.getSource((_a = layer === null || layer === void 0 ? void 0 : layer.parameters) === null || _a === void 0 ? void 0 : _a.source);
117
- const { latitude, longitude, extent, zoom } = model.getOptions();
118
116
  if (!source || !layer) {
119
117
  return;
120
118
  }
@@ -131,28 +129,9 @@ const FilterComponent = (props) => {
131
129
  });
132
130
  }
133
131
  switch (source.type) {
134
- case 'VectorTileSource': {
135
- try {
136
- const tile = await getLayerTileInfo((_b = source === null || source === void 0 ? void 0 : source.parameters) === null || _b === void 0 ? void 0 : _b.url, {
137
- latitude,
138
- longitude,
139
- extent,
140
- zoom
141
- });
142
- const layerValue = tile.layers[(_c = layer.parameters) === null || _c === void 0 ? void 0 : _c.sourceLayer];
143
- for (let i = 0; i < layerValue.length; i++) {
144
- const feature = layerValue.feature(i);
145
- addFeatureValue(feature.properties, aggregatedProperties);
146
- }
147
- }
148
- catch (error) {
149
- console.warn(`Error fetching tile info: ${error}`);
150
- }
151
- break;
152
- }
153
132
  case 'GeoJSONSource': {
154
133
  const data = await loadFile({
155
- filepath: (_d = source.parameters) === null || _d === void 0 ? void 0 : _d.path,
134
+ filepath: (_b = source.parameters) === null || _b === void 0 ? void 0 : _b.path,
156
135
  type: 'GeoJSONSource',
157
136
  model: model
158
137
  });
@@ -26,7 +26,7 @@ const IdentifyPanelComponent = ({ controlPanelModel, tracker }) => {
26
26
  * Update the model when it changes.
27
27
  */
28
28
  controlPanelModel === null || controlPanelModel === void 0 ? void 0 : controlPanelModel.documentChanged.connect((_, widget) => {
29
- setJgisModel(widget === null || widget === void 0 ? void 0 : widget.context.model);
29
+ setJgisModel(widget === null || widget === void 0 ? void 0 : widget.model);
30
30
  });
31
31
  // Reset state values when current widget changes
32
32
  useEffect(() => {