@jupytergis/base 0.2.1 → 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 (95) hide show
  1. package/lib/annotations/components/Annotation.js +2 -2
  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 +146 -62
  6. package/lib/constants.d.ts +3 -0
  7. package/lib/constants.js +5 -1
  8. package/lib/dialogs/formdialog.d.ts +5 -0
  9. package/lib/dialogs/formdialog.js +2 -2
  10. package/lib/dialogs/layerBrowserDialog.d.ts +4 -5
  11. package/lib/dialogs/layerBrowserDialog.js +9 -9
  12. package/lib/dialogs/symbology/components/color_ramp/ModeSelectRow.js +2 -1
  13. package/lib/dialogs/symbology/hooks/useGetBandInfo.d.ts +26 -0
  14. package/lib/dialogs/symbology/hooks/useGetBandInfo.js +64 -0
  15. package/lib/dialogs/symbology/hooks/useGetProperties.d.ts +1 -1
  16. package/lib/dialogs/symbology/hooks/useGetProperties.js +12 -9
  17. package/lib/dialogs/symbology/symbologyDialog.d.ts +2 -3
  18. package/lib/dialogs/symbology/symbologyDialog.js +10 -9
  19. package/lib/dialogs/symbology/tiff_layer/TiffRendering.d.ts +1 -1
  20. package/lib/dialogs/symbology/tiff_layer/TiffRendering.js +16 -3
  21. package/lib/dialogs/symbology/tiff_layer/components/BandRow.d.ts +16 -3
  22. package/lib/dialogs/symbology/tiff_layer/components/BandRow.js +21 -7
  23. package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.d.ts +4 -0
  24. package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.js +85 -0
  25. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.d.ts +1 -20
  26. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +25 -65
  27. package/lib/dialogs/symbology/vector_layer/VectorRendering.d.ts +1 -1
  28. package/lib/dialogs/symbology/vector_layer/VectorRendering.js +18 -13
  29. package/lib/dialogs/symbology/vector_layer/types/Categorized.d.ts +1 -1
  30. package/lib/dialogs/symbology/vector_layer/types/Categorized.js +30 -19
  31. package/lib/dialogs/symbology/vector_layer/types/Graduated.d.ts +1 -1
  32. package/lib/dialogs/symbology/vector_layer/types/Graduated.js +16 -13
  33. package/lib/dialogs/symbology/vector_layer/types/Heatmap.d.ts +4 -0
  34. package/lib/dialogs/symbology/vector_layer/types/Heatmap.js +77 -0
  35. package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.d.ts +1 -1
  36. package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.js +4 -3
  37. package/lib/formbuilder/creationform.d.ts +6 -2
  38. package/lib/formbuilder/creationform.js +6 -6
  39. package/lib/formbuilder/editform.d.ts +2 -2
  40. package/lib/formbuilder/editform.js +14 -9
  41. package/lib/formbuilder/formselectors.js +11 -1
  42. package/lib/formbuilder/objectform/baseform.d.ts +12 -3
  43. package/lib/formbuilder/objectform/baseform.js +39 -0
  44. package/lib/formbuilder/objectform/fileselectorwidget.d.ts +2 -0
  45. package/lib/formbuilder/objectform/fileselectorwidget.js +88 -0
  46. package/lib/formbuilder/objectform/geojsonsource.d.ts +5 -7
  47. package/lib/formbuilder/objectform/geojsonsource.js +8 -24
  48. package/lib/formbuilder/objectform/geotiffsource.d.ts +5 -1
  49. package/lib/formbuilder/objectform/geotiffsource.js +64 -18
  50. package/lib/formbuilder/objectform/heatmapLayerForm.d.ts +11 -0
  51. package/lib/formbuilder/objectform/heatmapLayerForm.js +60 -0
  52. package/lib/formbuilder/objectform/layerform.d.ts +2 -0
  53. package/lib/formbuilder/objectform/layerform.js +6 -0
  54. package/lib/formbuilder/objectform/pathbasedsource.d.ts +19 -0
  55. package/lib/formbuilder/objectform/pathbasedsource.js +98 -0
  56. package/lib/formbuilder/objectform/vectorlayerform.d.ts +0 -2
  57. package/lib/formbuilder/objectform/vectorlayerform.js +0 -59
  58. package/lib/icons.d.ts +1 -0
  59. package/lib/icons.js +5 -0
  60. package/lib/keybindings.json +62 -0
  61. package/lib/mainview/TemporalSlider.d.ts +8 -0
  62. package/lib/mainview/TemporalSlider.js +303 -0
  63. package/lib/mainview/mainView.d.ts +46 -8
  64. package/lib/mainview/mainView.js +431 -144
  65. package/lib/mainview/mainviewmodel.d.ts +4 -0
  66. package/lib/mainview/mainviewmodel.js +4 -0
  67. package/lib/mainview/mainviewwidget.d.ts +0 -2
  68. package/lib/mainview/mainviewwidget.js +0 -2
  69. package/lib/panelview/annotationPanel.js +5 -5
  70. package/lib/panelview/components/filter-panel/Filter.js +8 -24
  71. package/lib/panelview/components/identify-panel/IdentifyPanel.js +1 -1
  72. package/lib/panelview/components/layers.js +2 -2
  73. package/lib/panelview/components/sources.js +1 -1
  74. package/lib/panelview/leftpanel.d.ts +3 -0
  75. package/lib/panelview/leftpanel.js +5 -1
  76. package/lib/panelview/model.js +8 -8
  77. package/lib/panelview/objectproperties.js +10 -10
  78. package/lib/panelview/rightpanel.d.ts +1 -1
  79. package/lib/panelview/rightpanel.js +10 -10
  80. package/lib/statusbar/StatusBar.d.ts +13 -0
  81. package/lib/statusbar/StatusBar.js +52 -0
  82. package/lib/toolbar/widget.d.ts +1 -1
  83. package/lib/toolbar/widget.js +44 -37
  84. package/lib/tools.d.ts +50 -7
  85. package/lib/tools.js +394 -12
  86. package/lib/types.d.ts +2 -0
  87. package/lib/widget.d.ts +29 -5
  88. package/lib/widget.js +41 -7
  89. package/package.json +17 -5
  90. package/style/base.css +11 -0
  91. package/style/icons/logo_mini_qgz.svg +31 -0
  92. package/style/leftPanel.css +8 -0
  93. package/style/statusBar.css +16 -0
  94. package/style/symbologyDialog.css +7 -1
  95. package/style/temporalSlider.css +47 -0
@@ -1,19 +1,18 @@
1
1
  import { IJGISFormSchemaRegistry, IJupyterGISModel, IRasterLayerGalleryEntry } from '@jupytergis/schema';
2
2
  import { Dialog } from '@jupyterlab/apputils';
3
3
  import { PromiseDelegate } from '@lumino/coreutils';
4
- import React from 'react';
5
- import { DocumentRegistry } from '@jupyterlab/docregistry';
6
4
  import { Signal } from '@lumino/signaling';
5
+ import React from 'react';
7
6
  interface ILayerBrowserDialogProps {
8
- context: DocumentRegistry.IContext<IJupyterGISModel>;
7
+ model: IJupyterGISModel;
9
8
  registry: IRasterLayerGalleryEntry[];
10
9
  formSchemaRegistry: IJGISFormSchemaRegistry;
11
10
  okSignalPromise: PromiseDelegate<Signal<Dialog<any>, number>>;
12
11
  cancel: () => void;
13
12
  }
14
- export declare const LayerBrowserComponent: ({ context, registry, formSchemaRegistry, okSignalPromise, cancel }: ILayerBrowserDialogProps) => React.JSX.Element;
13
+ export declare const LayerBrowserComponent: ({ model, registry, formSchemaRegistry, okSignalPromise, cancel }: ILayerBrowserDialogProps) => React.JSX.Element;
15
14
  export interface ILayerBrowserOptions {
16
- context: DocumentRegistry.IContext<IJupyterGISModel>;
15
+ model: IJupyterGISModel;
17
16
  registry: IRasterLayerGalleryEntry[];
18
17
  formSchemaRegistry: IJGISFormSchemaRegistry;
19
18
  }
@@ -2,11 +2,11 @@ import { faCheck, faPlus } from '@fortawesome/free-solid-svg-icons';
2
2
  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3
3
  import { Dialog } from '@jupyterlab/apputils';
4
4
  import { PromiseDelegate, UUID } from '@lumino/coreutils';
5
+ import { Signal } from '@lumino/signaling';
5
6
  import React, { useEffect, useState } from 'react';
6
7
  import CUSTOM_RASTER_IMAGE from '../../rasterlayer_gallery/custom_raster.png';
7
8
  import { CreationFormWrapper } from './formdialog';
8
- import { Signal } from '@lumino/signaling';
9
- export const LayerBrowserComponent = ({ context, registry, formSchemaRegistry, okSignalPromise, cancel }) => {
9
+ export const LayerBrowserComponent = ({ model, registry, formSchemaRegistry, okSignalPromise, cancel }) => {
10
10
  const [searchTerm, setSearchTerm] = useState('');
11
11
  const [activeLayers, setActiveLayers] = useState([]);
12
12
  const [selectedCategory, setSelectedCategory] = useState();
@@ -15,9 +15,9 @@ export const LayerBrowserComponent = ({ context, registry, formSchemaRegistry, o
15
15
  const providers = [...new Set(registry.map(item => item.source.provider))];
16
16
  const filteredGallery = galleryWithCategory.filter(item => item.name.toLowerCase().includes(searchTerm));
17
17
  useEffect(() => {
18
- context.model.sharedModel.layersChanged.connect(handleLayerChange);
18
+ model.sharedModel.layersChanged.connect(handleLayerChange);
19
19
  return () => {
20
- context.model.sharedModel.layersChanged.disconnect(handleLayerChange);
20
+ model.sharedModel.layersChanged.disconnect(handleLayerChange);
21
21
  };
22
22
  }, []);
23
23
  /**
@@ -25,7 +25,7 @@ export const LayerBrowserComponent = ({ context, registry, formSchemaRegistry, o
25
25
  */
26
26
  const handleLayerChange = (_, change) => {
27
27
  // The split is to get rid of the 'Layer' part of the name to match the names in the gallery
28
- setActiveLayers(Object.values(context.model.sharedModel.layers).map(layer => layer.name.split(' ')[0]));
28
+ setActiveLayers(Object.values(model.sharedModel.layers).map(layer => layer.name.split(' ')[0]));
29
29
  };
30
30
  const handleSearchInput = (event) => {
31
31
  setSearchTerm(event.target.value.toLowerCase());
@@ -64,8 +64,8 @@ export const LayerBrowserComponent = ({ context, registry, formSchemaRegistry, o
64
64
  visible: true,
65
65
  name: tile.name + ' Layer'
66
66
  };
67
- context.model.sharedModel.addSource(sourceId, sourceModel);
68
- context.model.addLayer(UUID.uuid4(), layerModel);
67
+ model.sharedModel.addSource(sourceId, sourceModel);
68
+ model.addLayer(UUID.uuid4(), layerModel);
69
69
  };
70
70
  if (creatingCustomRaster) {
71
71
  // Disconnect any previous handler
@@ -73,7 +73,7 @@ export const LayerBrowserComponent = ({ context, registry, formSchemaRegistry, o
73
73
  value.disconnect(cancel, this);
74
74
  });
75
75
  return (React.createElement("div", { className: "jGIS-customlayer-form" },
76
- React.createElement(CreationFormWrapper, { context: context, formSchemaRegistry: formSchemaRegistry, createLayer: true, createSource: true, layerType: 'RasterLayer', sourceType: 'RasterSource', layerData: {
76
+ React.createElement(CreationFormWrapper, { model: model, formSchemaRegistry: formSchemaRegistry, createLayer: true, createSource: true, layerType: 'RasterLayer', sourceType: 'RasterSource', layerData: {
77
77
  name: 'Custom Raster'
78
78
  }, sourceData: {
79
79
  url: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
@@ -122,7 +122,7 @@ export class LayerBrowserWidget extends Dialog {
122
122
  this.resolve(0);
123
123
  };
124
124
  const okSignalPromise = new PromiseDelegate();
125
- const body = (React.createElement(LayerBrowserComponent, { context: options.context, registry: options.registry, formSchemaRegistry: options.formSchemaRegistry, okSignalPromise: okSignalPromise, cancel: cancelCallback }));
125
+ const body = (React.createElement(LayerBrowserComponent, { model: options.model, registry: options.registry, formSchemaRegistry: options.formSchemaRegistry, okSignalPromise: okSignalPromise, cancel: cancelCallback }));
126
126
  super({ body, buttons: [Dialog.cancelButton(), Dialog.okButton()] });
127
127
  this.id = 'jupytergis::layerBrowser';
128
128
  this.okSignal = new Signal(this);
@@ -6,6 +6,7 @@ const ModeSelectRow = ({ numberOfShades, setNumberOfShades, selectedMode, setSel
6
6
  React.createElement("input", { className: "jp-mod-styled", name: "class-number-input", type: "number", value: selectedMode === 'continuous' ? 52 : numberOfShades, onChange: event => setNumberOfShades(event.target.value), disabled: selectedMode === 'continuous' })),
7
7
  React.createElement("div", { className: "jp-gis-color-ramp-div" },
8
8
  React.createElement("label", { htmlFor: "mode-select" }, "Mode:"),
9
- React.createElement("select", { name: "mode-select", onChange: event => setSelectedMode(event.target.value) }, modeOptions.map(mode => (React.createElement("option", { className: "jp-mod-styled", value: mode, selected: selectedMode === mode }, mode)))))));
9
+ React.createElement("div", { className: "jp-select-wrapper" },
10
+ React.createElement("select", { name: "mode-select", id: "mode-select", className: "jp-mod-styled", value: selectedMode, onChange: event => setSelectedMode(event.target.value) }, modeOptions.map(mode => (React.createElement("option", { key: mode, value: mode, selected: selectedMode === mode }, mode))))))));
10
11
  };
11
12
  export default ModeSelectRow;
@@ -0,0 +1,26 @@
1
+ import { IDict, IJGISLayer, IJupyterGISModel } from '@jupytergis/schema';
2
+ export interface IBandHistogram {
3
+ buckets: number[];
4
+ count: number;
5
+ max: number;
6
+ min: number;
7
+ }
8
+ export interface IBandRow {
9
+ band: number;
10
+ colorInterpretation: string;
11
+ stats: {
12
+ minimum: number;
13
+ maximum: number;
14
+ mean: number;
15
+ stdDev: number;
16
+ };
17
+ metadata: IDict;
18
+ histogram: IBandHistogram;
19
+ }
20
+ declare const useGetBandInfo: (model: IJupyterGISModel, layer: IJGISLayer) => {
21
+ bandRows: IBandRow[];
22
+ setBandRows: import("react").Dispatch<import("react").SetStateAction<IBandRow[]>>;
23
+ loading: boolean;
24
+ error: string | null;
25
+ };
26
+ export default useGetBandInfo;
@@ -0,0 +1,64 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { loadFile } from '../../../tools';
3
+ const preloadGeoTiffFile = async (sourceInfo, model) => {
4
+ var _a;
5
+ return await loadFile({
6
+ filepath: (_a = sourceInfo.url) !== null && _a !== void 0 ? _a : '',
7
+ type: 'GeoTiffSource',
8
+ model: model
9
+ });
10
+ };
11
+ const useGetBandInfo = (model, layer) => {
12
+ const [bandRows, setBandRows] = useState([]);
13
+ const [loading, setLoading] = useState(false);
14
+ const [error, setError] = useState(null);
15
+ const fetchBandInfo = async () => {
16
+ var _a, _b;
17
+ setLoading(true);
18
+ setError(null);
19
+ try {
20
+ const bandsArr = [];
21
+ const source = model.getSource((_a = layer === null || layer === void 0 ? void 0 : layer.parameters) === null || _a === void 0 ? void 0 : _a.source);
22
+ const sourceInfo = (_b = source === null || source === void 0 ? void 0 : source.parameters) === null || _b === void 0 ? void 0 : _b.urls[0];
23
+ if (!(sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.url)) {
24
+ setError('No source URL found.');
25
+ setLoading(false);
26
+ return;
27
+ }
28
+ const preloadedFile = await preloadGeoTiffFile(sourceInfo, model);
29
+ const { file, metadata, sourceUrl } = Object.assign({}, preloadedFile);
30
+ if (file && metadata && sourceUrl === sourceInfo.url) {
31
+ metadata['bands'].forEach((bandData) => {
32
+ var _a, _b;
33
+ bandsArr.push({
34
+ band: bandData.band,
35
+ colorInterpretation: bandData.colorInterpretation,
36
+ stats: {
37
+ minimum: (_a = sourceInfo.min) !== null && _a !== void 0 ? _a : bandData.minimum,
38
+ maximum: (_b = sourceInfo.max) !== null && _b !== void 0 ? _b : bandData.maximum,
39
+ mean: bandData.mean,
40
+ stdDev: bandData.stdDev
41
+ },
42
+ metadata: bandData.metadata,
43
+ histogram: bandData.histogram
44
+ });
45
+ });
46
+ setBandRows(bandsArr);
47
+ }
48
+ else {
49
+ setError('Failed to preload the file or metadata mismatch.');
50
+ }
51
+ }
52
+ catch (err) {
53
+ setError(`Error fetching band info: ${err.message}`);
54
+ }
55
+ finally {
56
+ setLoading(false);
57
+ }
58
+ };
59
+ useEffect(() => {
60
+ fetchBandInfo();
61
+ }, []);
62
+ return { bandRows, setBandRows, loading, error };
63
+ };
64
+ export default useGetBandInfo;
@@ -4,7 +4,7 @@ interface IUseGetPropertiesProps {
4
4
  model: IJupyterGISModel;
5
5
  }
6
6
  interface IUseGetPropertiesResult {
7
- featureProps: Record<string, Set<any>>;
7
+ featureProperties: Record<string, Set<any>>;
8
8
  isLoading: boolean;
9
9
  error?: Error;
10
10
  }
@@ -1,7 +1,8 @@
1
1
  // import { GeoJSONFeature } from 'geojson';
2
2
  import { useEffect, useState } from 'react';
3
+ import { loadFile } from '../../../tools';
3
4
  export const useGetProperties = ({ layerId, model }) => {
4
- const [featureProps, setFeatureProps] = useState({});
5
+ const [featureProperties, setFeatureProperties] = useState({});
5
6
  const [isLoading, setIsLoading] = useState(true);
6
7
  const [error, setError] = useState(undefined);
7
8
  const getProperties = async () => {
@@ -15,7 +16,11 @@ export const useGetProperties = ({ layerId, model }) => {
15
16
  if (!source) {
16
17
  throw new Error('Source not found');
17
18
  }
18
- const data = await model.readGeoJSON((_b = source.parameters) === null || _b === void 0 ? void 0 : _b.path);
19
+ const data = await loadFile({
20
+ filepath: (_b = source.parameters) === null || _b === void 0 ? void 0 : _b.path,
21
+ type: 'GeoJSONSource',
22
+ model: model
23
+ });
19
24
  if (!data) {
20
25
  throw new Error('Failed to read GeoJSON data');
21
26
  }
@@ -23,16 +28,14 @@ export const useGetProperties = ({ layerId, model }) => {
23
28
  data.features.forEach((feature) => {
24
29
  if (feature.properties) {
25
30
  Object.entries(feature.properties).forEach(([key, value]) => {
26
- if (typeof value !== 'string') {
27
- if (!(key in result)) {
28
- result[key] = new Set();
29
- }
30
- result[key].add(value);
31
+ if (!(key in result)) {
32
+ result[key] = new Set();
31
33
  }
34
+ result[key].add(value);
32
35
  });
33
36
  }
34
37
  });
35
- setFeatureProps(result);
38
+ setFeatureProperties(result);
36
39
  setIsLoading(false);
37
40
  }
38
41
  catch (err) {
@@ -43,5 +46,5 @@ export const useGetProperties = ({ layerId, model }) => {
43
46
  useEffect(() => {
44
47
  getProperties();
45
48
  }, [model, layerId]);
46
- return { featureProps, isLoading, error };
49
+ return { featureProperties, isLoading, error };
47
50
  };
@@ -1,18 +1,17 @@
1
1
  import { IJupyterGISModel } from '@jupytergis/schema';
2
2
  import { Dialog } from '@jupyterlab/apputils';
3
- import { DocumentRegistry } from '@jupyterlab/docregistry';
4
3
  import { IStateDB } from '@jupyterlab/statedb';
5
4
  import { PromiseDelegate } from '@lumino/coreutils';
6
5
  import { Signal } from '@lumino/signaling';
7
6
  export interface ISymbologyDialogProps {
8
- context: DocumentRegistry.IContext<IJupyterGISModel>;
7
+ model: IJupyterGISModel;
9
8
  state: IStateDB;
10
9
  okSignalPromise: PromiseDelegate<Signal<SymbologyWidget, null>>;
11
10
  cancel: () => void;
12
11
  layerId?: string;
13
12
  }
14
13
  export interface ISymbologyWidgetOptions {
15
- context: DocumentRegistry.IContext<IJupyterGISModel>;
14
+ model: IJupyterGISModel;
16
15
  state: IStateDB;
17
16
  }
18
17
  export interface IStopRow {
@@ -4,31 +4,31 @@ import { Signal } from '@lumino/signaling';
4
4
  import React, { useEffect, useState } from 'react';
5
5
  import TiffRendering from './tiff_layer/TiffRendering';
6
6
  import VectorRendering from './vector_layer/VectorRendering';
7
- const SymbologyDialog = ({ context, state, okSignalPromise, cancel }) => {
7
+ const SymbologyDialog = ({ model, state, okSignalPromise, cancel }) => {
8
8
  const [selectedLayer, setSelectedLayer] = useState(null);
9
9
  const [componentToRender, setComponentToRender] = useState(null);
10
10
  let LayerSymbology;
11
11
  useEffect(() => {
12
12
  const handleClientStateChanged = () => {
13
13
  var _a, _b;
14
- if (!((_b = (_a = context.model.localState) === null || _a === void 0 ? void 0 : _a.selected) === null || _b === void 0 ? void 0 : _b.value)) {
14
+ if (!((_b = (_a = model.localState) === null || _a === void 0 ? void 0 : _a.selected) === null || _b === void 0 ? void 0 : _b.value)) {
15
15
  return;
16
16
  }
17
- const currentLayer = Object.keys(context.model.localState.selected.value)[0];
17
+ const currentLayer = Object.keys(model.localState.selected.value)[0];
18
18
  setSelectedLayer(currentLayer);
19
19
  };
20
20
  // Initial state
21
21
  handleClientStateChanged();
22
- context.model.clientStateChanged.connect(handleClientStateChanged);
22
+ model.clientStateChanged.connect(handleClientStateChanged);
23
23
  return () => {
24
- context.model.clientStateChanged.disconnect(handleClientStateChanged);
24
+ model.clientStateChanged.disconnect(handleClientStateChanged);
25
25
  };
26
26
  }, []);
27
27
  useEffect(() => {
28
28
  if (!selectedLayer) {
29
29
  return;
30
30
  }
31
- const layer = context.model.getLayer(selectedLayer);
31
+ const layer = model.getLayer(selectedLayer);
32
32
  if (!layer) {
33
33
  return;
34
34
  }
@@ -36,10 +36,11 @@ const SymbologyDialog = ({ context, state, okSignalPromise, cancel }) => {
36
36
  switch (layer.type) {
37
37
  case 'VectorLayer':
38
38
  case 'VectorTileLayer':
39
- LayerSymbology = (React.createElement(VectorRendering, { context: context, state: state, okSignalPromise: okSignalPromise, cancel: cancel, layerId: selectedLayer }));
39
+ case 'HeatmapLayer':
40
+ LayerSymbology = (React.createElement(VectorRendering, { model: model, state: state, okSignalPromise: okSignalPromise, cancel: cancel, layerId: selectedLayer }));
40
41
  break;
41
42
  case 'WebGlLayer':
42
- LayerSymbology = (React.createElement(TiffRendering, { context: context, state: state, okSignalPromise: okSignalPromise, cancel: cancel, layerId: selectedLayer }));
43
+ LayerSymbology = (React.createElement(TiffRendering, { model: model, state: state, okSignalPromise: okSignalPromise, cancel: cancel, layerId: selectedLayer }));
43
44
  break;
44
45
  default:
45
46
  LayerSymbology = React.createElement("div", null, "Layer Type Not Supported");
@@ -54,7 +55,7 @@ export class SymbologyWidget extends Dialog {
54
55
  this.resolve(0);
55
56
  };
56
57
  const okSignalPromise = new PromiseDelegate();
57
- const body = (React.createElement(SymbologyDialog, { context: options.context, okSignalPromise: okSignalPromise, cancel: cancelCallback, state: options.state }));
58
+ const body = (React.createElement(SymbologyDialog, { model: options.model, okSignalPromise: okSignalPromise, cancel: cancelCallback, state: options.state }));
58
59
  super({ title: 'Symbology', body });
59
60
  this.id = 'jupytergis::symbologyWidget';
60
61
  this.okSignal = new Signal(this);
@@ -1,4 +1,4 @@
1
1
  import React from 'react';
2
2
  import { ISymbologyDialogProps } from '../symbologyDialog';
3
- declare const TiffRendering: ({ context, state, okSignalPromise, cancel, layerId }: ISymbologyDialogProps) => React.JSX.Element;
3
+ declare const TiffRendering: ({ model, state, okSignalPromise, cancel, layerId }: ISymbologyDialogProps) => React.JSX.Element | undefined;
4
4
  export default TiffRendering;
@@ -1,17 +1,30 @@
1
1
  import React, { useEffect, useState } from 'react';
2
2
  import SingleBandPseudoColor from './types/SingleBandPseudoColor';
3
- const TiffRendering = ({ context, state, okSignalPromise, cancel, layerId }) => {
3
+ import MultibandColor from './types/MultibandColor';
4
+ const TiffRendering = ({ model, state, okSignalPromise, cancel, layerId }) => {
4
5
  const renderTypes = ['Singleband Pseudocolor', 'Multiband Color'];
5
- const [selectedRenderType, setSelectedRenderType] = useState('Singleband Pseudocolor');
6
+ const [selectedRenderType, setSelectedRenderType] = useState();
6
7
  const [componentToRender, setComponentToRender] = useState(null);
7
8
  let RenderComponent;
9
+ if (!layerId) {
10
+ return;
11
+ }
12
+ useEffect(() => {
13
+ var _a, _b;
14
+ const layer = model.getLayer(layerId);
15
+ const renderType = (_b = (_a = layer === null || layer === void 0 ? void 0 : layer.parameters) === null || _a === void 0 ? void 0 : _a.symbologyState) === null || _b === void 0 ? void 0 : _b.renderType;
16
+ setSelectedRenderType(renderType !== null && renderType !== void 0 ? renderType : 'Singleband Pseudocolor');
17
+ }, []);
8
18
  useEffect(() => {
9
19
  if (!selectedRenderType) {
10
20
  return;
11
21
  }
12
22
  switch (selectedRenderType) {
13
23
  case 'Singleband Pseudocolor':
14
- RenderComponent = (React.createElement(SingleBandPseudoColor, { context: context, state: state, okSignalPromise: okSignalPromise, cancel: cancel, layerId: layerId }));
24
+ RenderComponent = (React.createElement(SingleBandPseudoColor, { model: model, state: state, okSignalPromise: okSignalPromise, cancel: cancel, layerId: layerId }));
25
+ break;
26
+ case 'Multiband Color':
27
+ RenderComponent = (React.createElement(MultibandColor, { model: model, state: state, okSignalPromise: okSignalPromise, cancel: cancel, layerId: layerId }));
15
28
  break;
16
29
  default:
17
30
  RenderComponent = React.createElement("div", null, "Render Type Not Implemented (yet)");
@@ -1,10 +1,23 @@
1
1
  import React from 'react';
2
- import { IBandRow } from '../types/SingleBandPseudoColor';
3
- declare const BandRow: ({ index, bandRow, bandRows, setSelectedBand, setBandRows }: {
2
+ import { IBandRow } from '../../hooks/useGetBandInfo';
3
+ interface IBandRowProps {
4
+ label: string;
4
5
  index: number;
5
6
  bandRow: IBandRow;
6
7
  bandRows: IBandRow[];
7
8
  setSelectedBand: (band: number) => void;
8
9
  setBandRows: (bandRows: IBandRow[]) => void;
9
- }) => React.JSX.Element;
10
+ isMultibandColor?: boolean;
11
+ }
12
+ /**
13
+ *
14
+ * @param label Label displayed in symbology dialog
15
+ * @param index Index of current row in band row data
16
+ * @param bandRow Band from bands array, will be undefined when band is 'unset' in Multiband color
17
+ * @param bandRows Bands array from tiff data
18
+ * @param setSelectedBand Function to set selected band parent
19
+ * @param setBandRows Function to update band rows in parent
20
+ * @param isMultibandColor Used to hide min/max input and add 'Unset' option to drop down menu for MultiBand symbology
21
+ */
22
+ declare const BandRow: ({ label, index, bandRow, bandRows, setSelectedBand, setBandRows, isMultibandColor }: IBandRowProps) => React.JSX.Element;
10
23
  export default BandRow;
@@ -1,7 +1,17 @@
1
1
  import React, { useState } from 'react';
2
- const BandRow = ({ index, bandRow, bandRows, setSelectedBand, setBandRows }) => {
3
- const [minValue, setMinValue] = useState(bandRow.stats.minimum);
4
- const [maxValue, setMaxValue] = useState(bandRow.stats.maximum);
2
+ /**
3
+ *
4
+ * @param label Label displayed in symbology dialog
5
+ * @param index Index of current row in band row data
6
+ * @param bandRow Band from bands array, will be undefined when band is 'unset' in Multiband color
7
+ * @param bandRows Bands array from tiff data
8
+ * @param setSelectedBand Function to set selected band parent
9
+ * @param setBandRows Function to update band rows in parent
10
+ * @param isMultibandColor Used to hide min/max input and add 'Unset' option to drop down menu for MultiBand symbology
11
+ */
12
+ const BandRow = ({ label, index, bandRow, bandRows, setSelectedBand, setBandRows, isMultibandColor }) => {
13
+ const [minValue, setMinValue] = useState(bandRow === null || bandRow === void 0 ? void 0 : bandRow.stats.minimum);
14
+ const [maxValue, setMaxValue] = useState(bandRow === null || bandRow === void 0 ? void 0 : bandRow.stats.maximum);
5
15
  const handleMinValueChange = (event) => {
6
16
  setMinValue(+event.target.value);
7
17
  setNewBands();
@@ -18,10 +28,14 @@ const BandRow = ({ index, bandRow, bandRows, setSelectedBand, setBandRows }) =>
18
28
  };
19
29
  return (React.createElement(React.Fragment, null,
20
30
  React.createElement("div", { className: "jp-gis-symbology-row" },
21
- React.createElement("label", { htmlFor: `band-select-${index}` }, "Band:"),
31
+ React.createElement("label", { htmlFor: `band-select-${index}` },
32
+ label,
33
+ ":"),
22
34
  React.createElement("div", { className: "jp-select-wrapper" },
23
- React.createElement("select", { name: `band-select-${index}`, onChange: event => setSelectedBand(+event.target.value), className: "jp-mod-styled" }, bandRows.map((band, bandIndex) => (React.createElement("option", { key: bandIndex, value: band.band, selected: band.band === bandRow.band, className: "jp-mod-styled" }, `Band ${band.band} (${band.colorInterpretation})`)))))),
24
- React.createElement("div", { className: "jp-gis-symbology-row", style: { gap: '0.5rem' } },
35
+ React.createElement("select", { name: `band-select-${index}`, onChange: event => setSelectedBand(+event.target.value), className: "jp-mod-styled" },
36
+ bandRows.map((band, bandIndex) => (React.createElement("option", { key: bandIndex, value: band.band, selected: band.band === (bandRow === null || bandRow === void 0 ? void 0 : bandRow.band), className: "jp-mod-styled" }, `Band ${band.band} (${band.colorInterpretation})`))),
37
+ isMultibandColor ? (React.createElement("option", { key: 'unset', value: 0, selected: !bandRow, className: "jp-mod-styled" }, "Unset")) : null))),
38
+ isMultibandColor ? null : (React.createElement("div", { className: "jp-gis-symbology-row", style: { gap: '0.5rem' } },
25
39
  React.createElement("div", { style: {
26
40
  display: 'flex',
27
41
  justifyContent: 'space-between',
@@ -38,6 +52,6 @@ const BandRow = ({ index, bandRow, bandRows, setSelectedBand, setBandRows }) =>
38
52
  React.createElement("label", { htmlFor: "band-max", style: { alignSelf: 'center' } }, "Max"),
39
53
  React.createElement("input", { type: "number", className: "jp-mod-styled",
40
54
  // defaultValue={bandRow.stats.maximum}
41
- value: maxValue, onChange: handleMaxValueChange, onBlur: setNewBands })))));
55
+ value: maxValue, onChange: handleMaxValueChange, onBlur: setNewBands }))))));
42
56
  };
43
57
  export default BandRow;
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ import { ISymbologyDialogProps } from '../../symbologyDialog';
3
+ declare const MultibandColor: ({ model, okSignalPromise, cancel, layerId }: ISymbologyDialogProps) => React.JSX.Element | undefined;
4
+ export default MultibandColor;
@@ -0,0 +1,85 @@
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import { Spinner } from '../../../../mainview/spinner';
3
+ import useGetBandInfo from '../../hooks/useGetBandInfo';
4
+ import BandRow from '../components/BandRow';
5
+ const MultibandColor = ({ model, okSignalPromise, cancel, layerId }) => {
6
+ if (!layerId) {
7
+ return;
8
+ }
9
+ const layer = model.getLayer(layerId);
10
+ if (!(layer === null || layer === void 0 ? void 0 : layer.parameters)) {
11
+ return;
12
+ }
13
+ const { bandRows, setBandRows, loading } = useGetBandInfo(model, layer);
14
+ const [selectedBands, setSelectedBands] = useState({
15
+ red: 1,
16
+ green: 2,
17
+ blue: 3
18
+ });
19
+ const numOfBandsRef = useRef(0);
20
+ const selectedBandsRef = useRef({
21
+ red: selectedBands.red,
22
+ green: selectedBands.green,
23
+ blue: selectedBands.blue
24
+ });
25
+ useEffect(() => {
26
+ populateOptions();
27
+ okSignalPromise.promise.then(okSignal => {
28
+ okSignal.connect(handleOk);
29
+ });
30
+ return () => {
31
+ okSignalPromise.promise.then(okSignal => {
32
+ okSignal.disconnect(handleOk, this);
33
+ });
34
+ };
35
+ }, []);
36
+ useEffect(() => {
37
+ numOfBandsRef.current = bandRows.length;
38
+ }, [bandRows]);
39
+ useEffect(() => {
40
+ selectedBandsRef.current = selectedBands;
41
+ }, [selectedBands]);
42
+ const populateOptions = async () => {
43
+ var _a, _b, _c, _d, _e, _f;
44
+ const layerParams = layer.parameters;
45
+ const red = (_b = (_a = layerParams.symbologyState) === null || _a === void 0 ? void 0 : _a.redBand) !== null && _b !== void 0 ? _b : 1;
46
+ const green = (_d = (_c = layerParams.symbologyState) === null || _c === void 0 ? void 0 : _c.greenBand) !== null && _d !== void 0 ? _d : 2;
47
+ const blue = (_f = (_e = layerParams.symbologyState) === null || _e === void 0 ? void 0 : _e.blueBand) !== null && _f !== void 0 ? _f : 3;
48
+ setSelectedBands({ red, green, blue });
49
+ };
50
+ const updateBand = (color, value) => {
51
+ setSelectedBands(prevBands => (Object.assign(Object.assign({}, prevBands), { [color]: value })));
52
+ };
53
+ const handleOk = () => {
54
+ // Update layer
55
+ if (!layer.parameters) {
56
+ return;
57
+ }
58
+ const colorExpr = ['array'];
59
+ const rgb = ['red', 'green', 'blue'];
60
+ rgb.forEach(color => {
61
+ const bandValue = selectedBandsRef.current[color];
62
+ colorExpr.push(bandValue !== 0 ? ['band', bandValue] : 0);
63
+ });
64
+ // Array expression expects 4 values
65
+ // Last band should be alpha band added by OpenLayers
66
+ colorExpr.push(['band', numOfBandsRef.current + 1]);
67
+ const symbologyState = {
68
+ renderType: 'Multiband Color',
69
+ redBand: selectedBandsRef.current['red'],
70
+ greenBand: selectedBandsRef.current['green'],
71
+ blueBand: selectedBandsRef.current['blue']
72
+ };
73
+ layer.parameters.symbologyState = symbologyState;
74
+ layer.parameters.color = colorExpr;
75
+ layer.type = 'WebGlLayer';
76
+ model.sharedModel.updateLayer(layerId, layer);
77
+ cancel();
78
+ };
79
+ return (React.createElement("div", { className: "jp-gis-layer-symbology-container" },
80
+ React.createElement("div", { className: "jp-gis-band-container" }, loading ? (React.createElement(Spinner, { loading: loading })) : (React.createElement(React.Fragment, null,
81
+ React.createElement(BandRow, { label: "Red Band", index: selectedBands['red'] - 1, bandRow: bandRows[selectedBands['red'] - 1], bandRows: bandRows, setSelectedBand: val => updateBand('red', val), setBandRows: setBandRows, isMultibandColor: true }),
82
+ React.createElement(BandRow, { label: "Green Band", index: selectedBands['green'] - 1, bandRow: bandRows[selectedBands['green'] - 1], bandRows: bandRows, setSelectedBand: val => updateBand('green', val), setBandRows: setBandRows, isMultibandColor: true }),
83
+ React.createElement(BandRow, { label: "Blue Band", index: selectedBands['blue'] - 1, bandRow: bandRows[selectedBands['blue'] - 1], bandRows: bandRows, setSelectedBand: val => updateBand('blue', val), setBandRows: setBandRows, isMultibandColor: true }))))));
84
+ };
85
+ export default MultibandColor;
@@ -1,24 +1,5 @@
1
- import { IDict } from '@jupytergis/schema';
2
1
  import React from 'react';
3
2
  import { ISymbologyDialogProps } from '../../symbologyDialog';
4
- export interface IBandRow {
5
- band: number;
6
- colorInterpretation: string;
7
- stats: {
8
- minimum: number;
9
- maximum: number;
10
- mean: number;
11
- stdDev: number;
12
- };
13
- metadata: IDict;
14
- histogram: IBandHistogram;
15
- }
16
- export interface IBandHistogram {
17
- buckets: number[];
18
- count: number;
19
- max: number;
20
- min: number;
21
- }
22
3
  export type InterpolationType = 'discrete' | 'linear' | 'exact';
23
- declare const SingleBandPseudoColor: ({ context, okSignalPromise, cancel, layerId }: ISymbologyDialogProps) => React.JSX.Element | undefined;
4
+ declare const SingleBandPseudoColor: ({ model, okSignalPromise, cancel, layerId }: ISymbologyDialogProps) => React.JSX.Element | undefined;
24
5
  export default SingleBandPseudoColor;