@jupytergis/base 0.3.0 → 0.4.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 (77) 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 +3 -8
  12. package/lib/dialogs/symbology/hooks/useGetBandInfo.js +16 -28
  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/components/BandRow.js +3 -1
  20. package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.d.ts +1 -1
  21. package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.js +5 -4
  22. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.d.ts +1 -1
  23. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +8 -7
  24. package/lib/dialogs/symbology/vector_layer/VectorRendering.d.ts +1 -1
  25. package/lib/dialogs/symbology/vector_layer/VectorRendering.js +18 -13
  26. package/lib/dialogs/symbology/vector_layer/types/Categorized.d.ts +1 -1
  27. package/lib/dialogs/symbology/vector_layer/types/Categorized.js +30 -19
  28. package/lib/dialogs/symbology/vector_layer/types/Graduated.d.ts +1 -1
  29. package/lib/dialogs/symbology/vector_layer/types/Graduated.js +16 -13
  30. package/lib/dialogs/symbology/vector_layer/types/Heatmap.d.ts +4 -0
  31. package/lib/dialogs/symbology/vector_layer/types/Heatmap.js +77 -0
  32. package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.d.ts +1 -1
  33. package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.js +4 -3
  34. package/lib/formbuilder/creationform.d.ts +1 -2
  35. package/lib/formbuilder/creationform.js +4 -4
  36. package/lib/formbuilder/editform.d.ts +1 -2
  37. package/lib/formbuilder/editform.js +7 -7
  38. package/lib/formbuilder/formselectors.js +5 -2
  39. package/lib/formbuilder/objectform/baseform.d.ts +3 -4
  40. package/lib/formbuilder/objectform/baseform.js +2 -2
  41. package/lib/formbuilder/objectform/fileselectorwidget.js +13 -6
  42. package/lib/formbuilder/objectform/geotiffsource.d.ts +5 -1
  43. package/lib/formbuilder/objectform/geotiffsource.js +51 -18
  44. package/lib/formbuilder/objectform/heatmapLayerForm.d.ts +11 -0
  45. package/lib/formbuilder/objectform/heatmapLayerForm.js +60 -0
  46. package/lib/formbuilder/objectform/vectorlayerform.d.ts +0 -2
  47. package/lib/formbuilder/objectform/vectorlayerform.js +0 -59
  48. package/lib/mainview/TemporalSlider.d.ts +8 -0
  49. package/lib/mainview/TemporalSlider.js +303 -0
  50. package/lib/mainview/mainView.d.ts +26 -5
  51. package/lib/mainview/mainView.js +221 -108
  52. package/lib/mainview/mainviewmodel.d.ts +4 -0
  53. package/lib/mainview/mainviewmodel.js +4 -0
  54. package/lib/mainview/mainviewwidget.d.ts +0 -2
  55. package/lib/mainview/mainviewwidget.js +0 -2
  56. package/lib/panelview/annotationPanel.js +5 -5
  57. package/lib/panelview/components/filter-panel/Filter.js +4 -25
  58. package/lib/panelview/components/identify-panel/IdentifyPanel.js +1 -1
  59. package/lib/panelview/components/layers.js +2 -2
  60. package/lib/panelview/components/sources.js +1 -1
  61. package/lib/panelview/leftpanel.d.ts +3 -0
  62. package/lib/panelview/leftpanel.js +5 -1
  63. package/lib/panelview/model.js +8 -8
  64. package/lib/panelview/objectproperties.js +10 -10
  65. package/lib/panelview/rightpanel.d.ts +1 -1
  66. package/lib/panelview/rightpanel.js +10 -10
  67. package/lib/toolbar/widget.d.ts +1 -1
  68. package/lib/toolbar/widget.js +44 -32
  69. package/lib/tools.d.ts +6 -16
  70. package/lib/tools.js +54 -56
  71. package/lib/types.d.ts +2 -0
  72. package/lib/widget.d.ts +30 -6
  73. package/lib/widget.js +43 -9
  74. package/package.json +4 -3
  75. package/style/base.css +10 -0
  76. package/style/symbologyDialog.css +7 -1
  77. package/style/temporalSlider.css +47 -0
@@ -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(() => {
@@ -104,8 +104,8 @@ function LayersBodyComponent(props) {
104
104
  */
105
105
  (_b = props.model) === null || _b === void 0 ? void 0 : _b.documentChanged.connect((_, widget) => {
106
106
  var _a;
107
- setModel(widget === null || widget === void 0 ? void 0 : widget.context.model);
108
- setLayerTree(((_a = widget === null || widget === void 0 ? void 0 : widget.context.model) === null || _a === void 0 ? void 0 : _a.getLayerTree()) || []);
107
+ setModel(widget === null || widget === void 0 ? void 0 : widget.model);
108
+ setLayerTree(((_a = widget === null || widget === void 0 ? void 0 : widget.model) === null || _a === void 0 ? void 0 : _a.getLayerTree()) || []);
109
109
  });
110
110
  return (React.createElement("div", { id: "jp-gis-layer-tree" }, layerTree
111
111
  .slice()
@@ -55,7 +55,7 @@ function SourcesBodyComponent(props) {
55
55
  * Update the model when it changes.
56
56
  */
57
57
  (_b = props.model) === null || _b === void 0 ? void 0 : _b.documentChanged.connect((_, widget) => {
58
- setModel(widget === null || widget === void 0 ? void 0 : widget.context.model);
58
+ setModel(widget === null || widget === void 0 ? void 0 : widget.model);
59
59
  });
60
60
  return (React.createElement("div", { id: "jp-gis-sources" }, sourceIds.map(sourceId => {
61
61
  return (React.createElement(SourceComponent, { key: `source-${sourceId}`, gisModel: model, sourceId: sourceId, onClick: onItemClick }));
@@ -4,6 +4,7 @@ import { SidePanel } from '@jupyterlab/ui-components';
4
4
  import { Message } from '@lumino/messaging';
5
5
  import { MouseEvent as ReactMouseEvent } from 'react';
6
6
  import { IControlPanelModel } from '../types';
7
+ import { CommandRegistry } from '@lumino/commands';
7
8
  /**
8
9
  * Options of the left panel widget.
9
10
  */
@@ -37,12 +38,14 @@ export declare class LeftPanelWidget extends SidePanel {
37
38
  private _lastSelectedNodeId;
38
39
  private _model;
39
40
  private _state;
41
+ private _commands;
40
42
  }
41
43
  export declare namespace LeftPanelWidget {
42
44
  interface IOptions {
43
45
  model: IControlPanelModel;
44
46
  tracker: IJupyterGISTracker;
45
47
  state: IStateDB;
48
+ commands: CommandRegistry;
46
49
  }
47
50
  interface IProps {
48
51
  filePath?: string;
@@ -3,6 +3,7 @@ import { LayersPanel } from './components/layers';
3
3
  import { SourcesPanel } from './components/sources';
4
4
  import { ControlPanelHeader } from './header';
5
5
  import { FilterPanel } from './components/filter-panel/Filter';
6
+ import { CommandIDs } from '../constants';
6
7
  export class LeftPanelWidget extends SidePanel {
7
8
  constructor(options) {
8
9
  super();
@@ -50,11 +51,13 @@ export class LeftPanelWidget extends SidePanel {
50
51
  const updatedSelectedValue = Object.assign(Object.assign({}, selectedValue), { [item]: { type, selectedNodeId: nodeId } });
51
52
  this._lastSelectedNodeId = nodeId;
52
53
  jGISModel.syncSelected(updatedSelectedValue, this.id);
54
+ this._commands.notifyCommandChanged(CommandIDs.temporalController);
53
55
  }
54
56
  };
55
57
  this.addClass('jGIS-sidepanel-widget');
56
58
  this._model = options.model;
57
59
  this._state = options.state;
60
+ this._commands = options.commands;
58
61
  const header = new ControlPanelHeader();
59
62
  this.header.addWidget(header);
60
63
  const sourcesPanel = new SourcesPanel({
@@ -81,7 +84,7 @@ export class LeftPanelWidget extends SidePanel {
81
84
  this.addWidget(filterPanel);
82
85
  options.tracker.currentChanged.connect((_, changed) => {
83
86
  if (changed) {
84
- header.title.label = changed.context.localPath;
87
+ header.title.label = changed.model.filePath;
85
88
  }
86
89
  else {
87
90
  header.title.label = '-';
@@ -129,5 +132,6 @@ export class LeftPanelWidget extends SidePanel {
129
132
  this._lastSelectedNodeId = nodeId;
130
133
  }
131
134
  (_b = (_a = this._model) === null || _a === void 0 ? void 0 : _a.jGISModel) === null || _b === void 0 ? void 0 : _b.syncSelected(selection, this.id);
135
+ this._commands.notifyCommandChanged(CommandIDs.temporalController);
132
136
  }
133
137
  }
@@ -8,23 +8,23 @@ export class ControlPanelModel {
8
8
  }
9
9
  get filePath() {
10
10
  var _a;
11
- return (_a = this._tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.context.localPath;
11
+ return (_a = this._tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.model.filePath;
12
12
  }
13
13
  get jGISModel() {
14
14
  var _a;
15
- return (_a = this._tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.context.model;
15
+ return (_a = this._tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.model;
16
16
  }
17
17
  get sharedModel() {
18
18
  var _a;
19
- return (_a = this._tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.context.model.sharedModel;
19
+ return (_a = this._tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.model.sharedModel;
20
20
  }
21
21
  disconnect(f) {
22
22
  this._tracker.forEach(w => {
23
- w.context.model.sharedLayersChanged.disconnect(f);
24
- w.context.model.sharedSourcesChanged.disconnect(f);
25
- w.context.model.sharedOptionsChanged.disconnect(f);
23
+ w.model.sharedLayersChanged.disconnect(f);
24
+ w.model.sharedSourcesChanged.disconnect(f);
25
+ w.model.sharedOptionsChanged.disconnect(f);
26
26
  });
27
- this._tracker.forEach(w => w.context.model.themeChanged.disconnect(f));
28
- this._tracker.forEach(w => w.context.model.clientStateChanged.disconnect(f));
27
+ this._tracker.forEach(w => w.model.themeChanged.disconnect(f));
28
+ this._tracker.forEach(w => w.model.clientStateChanged.disconnect(f));
29
29
  }
30
30
  }
@@ -45,7 +45,7 @@ class ObjectPropertiesReact extends React.Component {
45
45
  }
46
46
  };
47
47
  this.state = {
48
- context: (_a = props.tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.context,
48
+ model: (_a = props.tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.model,
49
49
  clientId: null,
50
50
  id: uuid()
51
51
  };
@@ -55,14 +55,14 @@ class ObjectPropertiesReact extends React.Component {
55
55
  if (changed) {
56
56
  this.props.cpModel.disconnect(this._sharedJGISModelChanged);
57
57
  this.props.cpModel.disconnect(this._onClientSharedStateChanged);
58
- changed.context.model.sharedLayersChanged.connect(this._sharedJGISModelChanged);
59
- changed.context.model.sharedSourcesChanged.connect(this._sharedJGISModelChanged);
60
- changed.context.model.clientStateChanged.connect(this._onClientSharedStateChanged);
61
- this.setState(old => (Object.assign(Object.assign({}, old), { context: changed.context, filePath: changed.context.localPath, clientId: changed.context.model.getClientId() })));
58
+ changed.model.sharedLayersChanged.connect(this._sharedJGISModelChanged);
59
+ changed.model.sharedSourcesChanged.connect(this._sharedJGISModelChanged);
60
+ changed.model.clientStateChanged.connect(this._onClientSharedStateChanged);
61
+ this.setState(old => (Object.assign(Object.assign({}, old), { model: changed.model, filePath: changed.model.filePath, clientId: changed.model.getClientId() })));
62
62
  }
63
63
  else {
64
64
  this.setState({
65
- context: undefined,
65
+ model: undefined,
66
66
  selectedObject: undefined
67
67
  });
68
68
  }
@@ -71,22 +71,22 @@ class ObjectPropertiesReact extends React.Component {
71
71
  render() {
72
72
  var _a;
73
73
  const selectedObject = this.state.selectedObject;
74
- if (!selectedObject || !this.state.context) {
74
+ if (!selectedObject || !this.state.model) {
75
75
  return React.createElement("div", null);
76
76
  }
77
77
  let layerId = undefined;
78
78
  let sourceId = undefined;
79
- const layer = this.state.context.model.getLayer(selectedObject);
79
+ const layer = this.state.model.getLayer(selectedObject);
80
80
  if (layer) {
81
81
  layerId = selectedObject;
82
82
  sourceId = (_a = layer.parameters) === null || _a === void 0 ? void 0 : _a.source;
83
83
  }
84
84
  else {
85
- const source = this.state.context.model.getSource(selectedObject);
85
+ const source = this.state.model.getSource(selectedObject);
86
86
  if (source) {
87
87
  sourceId = selectedObject;
88
88
  }
89
89
  }
90
- return (React.createElement(EditForm, { layer: layerId, source: sourceId, formSchemaRegistry: this.props.formSchemaRegistry, context: this.state.context }));
90
+ return (React.createElement(EditForm, { layer: layerId, source: sourceId, formSchemaRegistry: this.props.formSchemaRegistry, model: this.state.model }));
91
91
  }
92
92
  }
@@ -4,7 +4,7 @@ import { IControlPanelModel } from '../types';
4
4
  export declare class RightPanelWidget extends SidePanel {
5
5
  constructor(options: RightPanelWidget.IOptions);
6
6
  dispose(): void;
7
- private _currentContext;
7
+ private _currentModel;
8
8
  private _model;
9
9
  private _annotationModel;
10
10
  }
@@ -31,12 +31,12 @@ export class RightPanelWidget extends SidePanel {
31
31
  this.addWidget(identifyPanel);
32
32
  this._model.documentChanged.connect((_, changed) => {
33
33
  if (changed) {
34
- if (changed.context.model.sharedModel.editable) {
35
- header.title.label = changed.context.localPath;
34
+ if (changed.model.sharedModel.editable) {
35
+ header.title.label = changed.model.filePath;
36
36
  properties.show();
37
37
  }
38
38
  else {
39
- header.title.label = `${changed.context.localPath} - Read Only`;
39
+ header.title.label = `${changed.model.filePath} - Read Only`;
40
40
  properties.hide();
41
41
  }
42
42
  }
@@ -47,16 +47,16 @@ export class RightPanelWidget extends SidePanel {
47
47
  options.tracker.currentChanged.connect(async (_, changed) => {
48
48
  var _a;
49
49
  if (changed) {
50
- this._currentContext = changed.context;
51
- header.title.label = this._currentContext.localPath;
52
- this._annotationModel.context =
53
- ((_a = options.tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.context) || undefined;
54
- await changed.context.ready;
50
+ this._currentModel = changed.model;
51
+ header.title.label = this._currentModel.filePath;
52
+ this._annotationModel.model =
53
+ ((_a = options.tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.model) || undefined;
54
+ // await changed.context.ready;
55
55
  }
56
56
  else {
57
57
  header.title.label = '-';
58
- this._currentContext = null;
59
- this._annotationModel.context = undefined;
58
+ this._currentModel = null;
59
+ this._annotationModel.model = undefined;
60
60
  }
61
61
  });
62
62
  }
@@ -1,5 +1,5 @@
1
1
  import { IJGISExternalCommand, JupyterGISModel } from '@jupytergis/schema';
2
- import { Toolbar, ReactiveToolbar } from '@jupyterlab/ui-components';
2
+ import { ReactiveToolbar, Toolbar } from '@jupyterlab/ui-components';
3
3
  import { CommandRegistry } from '@lumino/commands';
4
4
  import { Widget } from '@lumino/widgets';
5
5
  export declare const TOOLBAR_SEPARATOR_CLASS = "jGIS-Toolbar-Separator";
@@ -1,8 +1,9 @@
1
1
  import { CommandToolbarButton } from '@jupyterlab/apputils';
2
- import { ReactWidget, ReactiveToolbar, ToolbarButton, addIcon, redoIcon, undoIcon, terminalIcon } from '@jupyterlab/ui-components';
2
+ import { MenuSvg, ReactWidget, ReactiveToolbar, ToolbarButton, addIcon, redoIcon, terminalIcon, undoIcon } from '@jupyterlab/ui-components';
3
3
  import { Menu, Widget } from '@lumino/widgets';
4
4
  import * as React from 'react';
5
5
  import { CommandIDs } from '../constants';
6
+ import { rasterIcon } from '../icons';
6
7
  import { UsersItem } from './usertoolbaritem';
7
8
  export const TOOLBAR_SEPARATOR_CLASS = 'jGIS-Toolbar-Separator';
8
9
  export const TOOLBAR_GROUPNAME_CLASS = 'jGIS-Toolbar-GroupName';
@@ -55,52 +56,63 @@ export class ToolbarWidget extends ReactiveToolbar {
55
56
  label: '',
56
57
  commands: options.commands
57
58
  }));
58
- const NewButton = new ToolbarButton({
59
+ // vector sub menu
60
+ const vectorSubMenu = new Menu({ commands: options.commands });
61
+ vectorSubMenu.title.label = 'Add Vector Layer';
62
+ vectorSubMenu.title.iconClass = 'fa fa-vector-square';
63
+ vectorSubMenu.id = 'jp-gis-toolbar-vector-menu';
64
+ vectorSubMenu.addItem({
65
+ type: 'command',
66
+ command: CommandIDs.newGeoJSONEntry
67
+ });
68
+ vectorSubMenu.addItem({
69
+ type: 'command',
70
+ command: CommandIDs.newShapefileLayer
71
+ });
72
+ //raster submenu
73
+ const rasterSubMenu = new Menu({ commands: options.commands });
74
+ rasterSubMenu.title.label = 'Add Raster Layer';
75
+ rasterSubMenu.title.icon = rasterIcon;
76
+ rasterSubMenu.id = 'jp-gis-toolbar-raster-menu';
77
+ rasterSubMenu.addItem({
78
+ type: 'command',
79
+ command: CommandIDs.newHillshadeEntry
80
+ });
81
+ rasterSubMenu.addItem({
82
+ type: 'command',
83
+ command: CommandIDs.newImageEntry
84
+ });
85
+ rasterSubMenu.addItem({
86
+ type: 'command',
87
+ command: CommandIDs.newGeoTiffEntry
88
+ });
89
+ const NewSubMenu = new MenuSvg({ commands: options.commands });
90
+ NewSubMenu.title.label = 'Add Layer';
91
+ NewSubMenu.addItem({ type: 'submenu', submenu: rasterSubMenu });
92
+ NewSubMenu.addItem({ type: 'submenu', submenu: vectorSubMenu });
93
+ const NewEntryButton = new ToolbarButton({
59
94
  icon: addIcon,
60
95
  noFocusOnClick: false,
61
96
  onClick: () => {
62
97
  if (!options.commands) {
63
98
  return;
64
99
  }
65
- const bbox = NewButton.node.getBoundingClientRect();
66
- const NewSubMenu = new Menu({ commands: options.commands });
67
- NewSubMenu.title.label = 'New Layer';
68
- NewSubMenu.addItem({
69
- type: 'command',
70
- command: CommandIDs.newHillshadeEntry
71
- });
72
- NewSubMenu.addItem({
73
- type: 'separator'
74
- });
75
- NewSubMenu.addItem({
76
- type: 'command',
77
- command: CommandIDs.newImageEntry
78
- });
79
- NewSubMenu.addItem({
80
- type: 'separator'
81
- });
82
- NewSubMenu.addItem({
83
- type: 'command',
84
- command: CommandIDs.newShapefileLayer
85
- });
86
- NewSubMenu.addItem({
87
- type: 'command',
88
- command: CommandIDs.newGeoTiffEntry
89
- });
90
- NewSubMenu.addItem({
91
- type: 'command',
92
- command: CommandIDs.newGeoJSONEntry
93
- });
100
+ const bbox = NewEntryButton.node.getBoundingClientRect();
94
101
  NewSubMenu.open(bbox.x, bbox.bottom);
95
102
  }
96
103
  });
97
- this.addItem('New', NewButton);
104
+ this.addItem('New', NewEntryButton);
98
105
  this.addItem('separator2', new Separator());
99
106
  this.addItem('identify', new CommandToolbarButton({
100
107
  id: CommandIDs.identify,
101
108
  label: '',
102
109
  commands: options.commands
103
110
  }));
111
+ this.addItem('temporalController', new CommandToolbarButton({
112
+ id: CommandIDs.temporalController,
113
+ label: '',
114
+ commands: options.commands
115
+ }));
104
116
  this.addItem('spacer', ReactiveToolbar.createSpacerItem());
105
117
  // Users
106
118
  this.addItem('users', ReactWidget.create(React.createElement(UsersItem, { model: options.model })));
package/lib/tools.d.ts CHANGED
@@ -22,7 +22,6 @@ export declare function deepCopy<T = IDict<any>>(value: T): T;
22
22
  */
23
23
  export declare function createDefaultLayerRegistry(layerBrowserRegistry: IJGISLayerBrowserRegistry): void;
24
24
  export declare function getLayerTileInfo(tileUrl: string, mapOptions: Pick<IJGISOptions, 'latitude' | 'longitude' | 'extent' | 'zoom'>, urlParameters?: IDict<string>): Promise<VectorTile>;
25
- export declare function getSourceLayerNames(tileUrl: string, urlParameters?: IDict<string>): Promise<string[]>;
26
25
  export interface IParsedStyle {
27
26
  fillColor: string;
28
27
  strokeColor: string;
@@ -46,27 +45,17 @@ export declare const openDatabase: () => Promise<IDBDatabase>;
46
45
  * @param metadata metadata of file.
47
46
  * @returns A promise that resolves once the data is successfully saved.
48
47
  */
49
- export declare const saveToIndexedDB: (key: string, file: Blob, metadata: any) => Promise<void>;
48
+ export declare const saveToIndexedDB: (key: string, file: any, metadata?: any | undefined) => Promise<void>;
50
49
  /**
51
50
  * Retrieve a file and its metadata from the IndexedDB database.
52
51
  *
53
52
  * @param key fileID (sourceUrl).
54
53
  * @returns A promise that resolves to the stored data object or undefined.
55
54
  */
56
- export declare const getFromIndexedDB: (key: string) => Promise<any>;
57
- /**
58
- * Load a GeoTIFF file from IndexedDB database cache or fetch it .
59
- *
60
- * @param sourceInfo object containing the URL of the GeoTIFF file.
61
- * @returns A promise that resolves to the file as a Blob, or undefined .
62
- */
63
- export declare const loadGeoTIFFWithCache: (sourceInfo: {
64
- url?: string | undefined;
65
- }) => Promise<{
66
- file: Blob;
67
- metadata: any;
68
- sourceUrl: string;
69
- } | null>;
55
+ export declare const getFromIndexedDB: (key: string) => Promise<{
56
+ file: any;
57
+ metadata?: any | undefined;
58
+ } | undefined>;
70
59
  /**
71
60
  * Generalized file reader for different source types.
72
61
  *
@@ -106,3 +95,4 @@ export declare const getMimeType: (filename: string) => string;
106
95
  * @returns An ArrayBuffer.
107
96
  */
108
97
  export declare const stringToArrayBuffer: (content: string) => Promise<ArrayBuffer>;
98
+ export declare const getNumericFeatureAttributes: (featureProperties: Record<string, Set<any>>) => Record<string, Set<number>>;
package/lib/tools.js CHANGED
@@ -1,12 +1,11 @@
1
1
  import Protobuf from 'pbf';
2
2
  import { VectorTile } from '@mapbox/vector-tile';
3
- import { URLExt } from '@jupyterlab/coreutils';
3
+ import { PathExt, URLExt } from '@jupyterlab/coreutils';
4
4
  import { ServerConnection } from '@jupyterlab/services';
5
+ import { showErrorMessage } from '@jupyterlab/apputils';
5
6
  import * as d3Color from 'd3-color';
6
- import { PathExt } from '@jupyterlab/coreutils';
7
7
  import shp from 'shpjs';
8
8
  import RASTER_LAYER_GALLERY from '../rasterlayer_gallery/raster_layer_gallery.json';
9
- import { getGdal } from './gdal';
10
9
  export const debounce = (func, timeout = 100) => {
11
10
  let timeoutId;
12
11
  return (...args) => {
@@ -215,11 +214,6 @@ export async function getLayerTileInfo(tileUrl, mapOptions, urlParameters) {
215
214
  const tile = new VectorTile(new Protobuf(arrayBuffer));
216
215
  return tile;
217
216
  }
218
- export async function getSourceLayerNames(tileUrl, urlParameters) {
219
- const tile = await getLayerTileInfo(tileUrl, { latitude: 0, longitude: 0, zoom: 0, extent: [] }, urlParameters);
220
- const layerNames = Object.keys(tile.layers);
221
- return layerNames;
222
- }
223
217
  export function parseColor(type, style) {
224
218
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
225
219
  if (!type || !style) {
@@ -306,38 +300,26 @@ export const getFromIndexedDB = async (key) => {
306
300
  request.onerror = () => reject(request.error);
307
301
  });
308
302
  };
309
- /**
310
- * Load a GeoTIFF file from IndexedDB database cache or fetch it .
311
- *
312
- * @param sourceInfo object containing the URL of the GeoTIFF file.
313
- * @returns A promise that resolves to the file as a Blob, or undefined .
314
- */
315
- export const loadGeoTIFFWithCache = async (sourceInfo) => {
316
- if (!(sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.url)) {
317
- return null;
318
- }
319
- const cachedData = await getFromIndexedDB(sourceInfo.url);
320
- if (cachedData) {
321
- return {
322
- file: new Blob([cachedData.file]),
323
- metadata: cachedData.metadata,
324
- sourceUrl: sourceInfo.url
325
- };
303
+ const fetchWithProxies = async (url, parseResponse) => {
304
+ const proxyUrls = [
305
+ url, // Direct fetch
306
+ `/jupytergis_core/proxy?url=${encodeURIComponent(url)}`, // Internal proxy
307
+ `https://corsproxy.io/?url=${encodeURIComponent(url)}` // External proxy
308
+ ];
309
+ for (const proxyUrl of proxyUrls) {
310
+ try {
311
+ const response = await fetch(proxyUrl);
312
+ if (!response.ok) {
313
+ console.warn(`Failed to fetch from ${proxyUrl}: ${response.statusText}`);
314
+ continue;
315
+ }
316
+ return await parseResponse(response);
317
+ }
318
+ catch (error) {
319
+ console.warn(`Error fetching from ${proxyUrl}:`, error);
320
+ }
326
321
  }
327
- const response = await fetch(sourceInfo.url);
328
- const fileBlob = await response.blob();
329
- const file = new File([fileBlob], 'loaded.tif');
330
- const Gdal = await getGdal();
331
- const result = await Gdal.open(file);
332
- const tifDataset = result.datasets[0];
333
- const metadata = await Gdal.gdalinfo(tifDataset, ['-stats']);
334
- Gdal.close(tifDataset);
335
- await saveToIndexedDB(sourceInfo.url, fileBlob, metadata);
336
- return {
337
- file: fileBlob,
338
- metadata,
339
- sourceUrl: sourceInfo.url
340
- };
322
+ return null;
341
323
  };
342
324
  /**
343
325
  * Generalized file reader for different source types.
@@ -369,30 +351,33 @@ export const loadFile = async (fileInfo) => {
369
351
  }
370
352
  }
371
353
  case 'ShapefileSource': {
372
- try {
373
- const response = await fetch(`/jupytergis_core/proxy?url=${filepath}`);
354
+ const cached = await getFromIndexedDB(filepath);
355
+ if (cached) {
356
+ return cached.file;
357
+ }
358
+ const geojson = await fetchWithProxies(filepath, async (response) => {
374
359
  const arrayBuffer = await response.arrayBuffer();
375
- const geojson = await shp(arrayBuffer);
360
+ return shp(arrayBuffer);
361
+ });
362
+ if (geojson) {
363
+ await saveToIndexedDB(filepath, geojson);
376
364
  return geojson;
377
365
  }
378
- catch (error) {
379
- console.error('Error loading remote shapefile:', error);
380
- throw error;
381
- }
366
+ showErrorMessage('Network error', `Failed to fetch ${filepath}`);
367
+ throw new Error(`Failed to fetch ${filepath}`);
382
368
  }
383
369
  case 'GeoJSONSource': {
384
- try {
385
- const response = await fetch(`/jupytergis_core/proxy?url=${filepath}`);
386
- if (!response.ok) {
387
- throw new Error(`Failed to fetch GeoJSON from URL: ${filepath}`);
388
- }
389
- const geojson = await response.json();
390
- return geojson;
370
+ const cached = await getFromIndexedDB(filepath);
371
+ if (cached) {
372
+ return cached.file;
391
373
  }
392
- catch (error) {
393
- console.error('Error loading remote GeoJSON:', error);
394
- throw error;
374
+ const geojson = await fetchWithProxies(filepath, async (response) => response.json());
375
+ if (geojson) {
376
+ await saveToIndexedDB(filepath, geojson);
377
+ return geojson;
395
378
  }
379
+ showErrorMessage('Network error', `Failed to fetch ${filepath}`);
380
+ throw new Error(`Failed to fetch ${filepath}`);
396
381
  }
397
382
  default: {
398
383
  throw new Error(`Unsupported URL handling for source type: ${type}`);
@@ -645,3 +630,16 @@ export const stringToArrayBuffer = async (content) => {
645
630
  const base64Response = await fetch(`data:application/octet-stream;base64,${content}`);
646
631
  return await base64Response.arrayBuffer();
647
632
  };
633
+ export const getNumericFeatureAttributes = (featureProperties) => {
634
+ // We only want number values here
635
+ const filteredRecord = {};
636
+ for (const [key, set] of Object.entries(featureProperties)) {
637
+ const firstValue = set.values().next().value;
638
+ // Check if the first value is a string that cannot be parsed as a number
639
+ const isInvalidString = typeof firstValue === 'string' && isNaN(Number(firstValue));
640
+ if (!isInvalidString) {
641
+ filteredRecord[key] = set;
642
+ }
643
+ }
644
+ return filteredRecord;
645
+ };
package/lib/types.d.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import { IDict, IJupyterGISDoc, IJupyterGISModel, IJupyterGISTracker, IJupyterGISWidget } from '@jupytergis/schema';
2
+ import { WidgetTracker } from '@jupyterlab/apputils';
2
3
  import { ISignal } from '@lumino/signaling';
3
4
  import { Map } from 'ol';
4
5
  export { IDict };
5
6
  export type ValueOf<T> = T[keyof T];
7
+ export type JupyterGISTracker = WidgetTracker<IJupyterGISWidget>;
6
8
  export interface IControlPanelModel {
7
9
  disconnect(f: any): void;
8
10
  documentChanged: ISignal<IJupyterGISTracker, IJupyterGISWidget | null>;