@jupytergis/base 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/lib/annotations/components/Annotation.d.ts +11 -0
  2. package/lib/annotations/components/Annotation.js +61 -0
  3. package/lib/annotations/components/AnnotationFloater.d.ts +7 -0
  4. package/lib/annotations/components/AnnotationFloater.js +30 -0
  5. package/lib/annotations/components/Message.d.ts +8 -0
  6. package/lib/annotations/components/Message.js +17 -0
  7. package/lib/annotations/index.d.ts +3 -0
  8. package/lib/annotations/index.js +3 -0
  9. package/lib/annotations/model.d.ts +28 -0
  10. package/lib/annotations/model.js +67 -0
  11. package/lib/classificationModes.d.ts +13 -0
  12. package/lib/classificationModes.js +326 -0
  13. package/lib/commands.js +52 -7
  14. package/lib/constants.d.ts +2 -0
  15. package/lib/constants.js +5 -1
  16. package/lib/dialogs/symbology/classificationModes.d.ts +13 -0
  17. package/lib/dialogs/symbology/classificationModes.js +326 -0
  18. package/lib/dialogs/symbology/components/color_ramp/CanvasSelectComponent.d.ts +11 -0
  19. package/lib/dialogs/symbology/components/color_ramp/CanvasSelectComponent.js +119 -0
  20. package/lib/dialogs/symbology/components/color_ramp/ColorRamp.d.ts +15 -0
  21. package/lib/dialogs/symbology/components/color_ramp/ColorRamp.js +33 -0
  22. package/lib/dialogs/symbology/components/color_ramp/ColorRampEntry.d.ts +9 -0
  23. package/lib/dialogs/symbology/components/color_ramp/ColorRampEntry.js +24 -0
  24. package/lib/dialogs/symbology/components/color_ramp/ModeSelectRow.d.ts +10 -0
  25. package/lib/dialogs/symbology/components/color_ramp/ModeSelectRow.js +11 -0
  26. package/lib/dialogs/symbology/components/color_stops/StopContainer.d.ts +9 -0
  27. package/lib/dialogs/symbology/components/color_stops/StopContainer.js +28 -0
  28. package/lib/dialogs/{components/symbology → symbology/components/color_stops}/StopRow.js +9 -2
  29. package/lib/dialogs/symbology/hooks/useGetProperties.d.ts +12 -0
  30. package/lib/dialogs/symbology/hooks/useGetProperties.js +47 -0
  31. package/lib/dialogs/{symbologyDialog.js → symbology/symbologyDialog.js} +3 -3
  32. package/lib/dialogs/symbology/symbologyUtils.d.ts +9 -0
  33. package/lib/dialogs/symbology/symbologyUtils.js +94 -0
  34. package/lib/dialogs/symbology/tiff_layer/TiffRendering.d.ts +4 -0
  35. package/lib/dialogs/{components/symbology/BandRendering.js → symbology/tiff_layer/TiffRendering.js} +3 -3
  36. package/lib/dialogs/{components/symbology → symbology/tiff_layer/components}/BandRow.d.ts +1 -1
  37. package/lib/dialogs/{components/symbology → symbology/tiff_layer/types}/SingleBandPseudoColor.d.ts +9 -1
  38. package/lib/dialogs/{components/symbology → symbology/tiff_layer/types}/SingleBandPseudoColor.js +131 -83
  39. package/lib/dialogs/{components/symbology → symbology/vector_layer}/VectorRendering.d.ts +1 -1
  40. package/lib/dialogs/{components/symbology → symbology/vector_layer}/VectorRendering.js +10 -13
  41. package/lib/dialogs/symbology/vector_layer/components/ValueSelect.d.ts +8 -0
  42. package/lib/dialogs/symbology/vector_layer/components/ValueSelect.js +7 -0
  43. package/lib/dialogs/symbology/vector_layer/types/Categorized.d.ts +4 -0
  44. package/lib/dialogs/symbology/vector_layer/types/Categorized.js +94 -0
  45. package/lib/dialogs/symbology/vector_layer/types/Graduated.js +169 -0
  46. package/lib/dialogs/{components/symbology → symbology/vector_layer/types}/SimpleSymbol.js +8 -13
  47. package/lib/formbuilder/formselectors.js +4 -0
  48. package/lib/formbuilder/objectform/baseform.d.ts +1 -1
  49. package/lib/formbuilder/objectform/baseform.js +31 -42
  50. package/lib/formbuilder/objectform/geojsonsource.js +33 -30
  51. package/lib/formbuilder/objectform/geotiffsource.d.ts +16 -0
  52. package/lib/formbuilder/objectform/geotiffsource.js +71 -0
  53. package/lib/formbuilder/objectform/vectorlayerform.js +1 -0
  54. package/lib/formbuilder/objectform/webGlLayerForm.js +1 -0
  55. package/lib/index.d.ts +7 -4
  56. package/lib/index.js +7 -4
  57. package/lib/mainview/CollaboratorPointers.d.ts +17 -0
  58. package/lib/mainview/CollaboratorPointers.js +37 -0
  59. package/lib/mainview/FollowIndicator.d.ts +7 -0
  60. package/lib/mainview/FollowIndicator.js +9 -0
  61. package/lib/mainview/mainView.d.ts +39 -3
  62. package/lib/mainview/mainView.js +451 -41
  63. package/lib/mainview/mainviewmodel.d.ts +2 -1
  64. package/lib/mainview/mainviewmodel.js +5 -0
  65. package/lib/panelview/annotationPanel.d.ts +27 -0
  66. package/lib/panelview/annotationPanel.js +45 -0
  67. package/lib/panelview/components/filter-panel/Filter.d.ts +7 -2
  68. package/lib/panelview/components/filter-panel/Filter.js +1 -1
  69. package/lib/panelview/components/filter-panel/FilterRow.js +3 -3
  70. package/lib/panelview/components/identify-panel/IdentifyPanel.d.ts +15 -0
  71. package/lib/panelview/components/identify-panel/IdentifyPanel.js +108 -0
  72. package/lib/panelview/components/layers.js +4 -4
  73. package/lib/panelview/leftpanel.js +8 -0
  74. package/lib/panelview/rightpanel.d.ts +4 -1
  75. package/lib/panelview/rightpanel.js +28 -7
  76. package/lib/store.d.ts +9 -0
  77. package/lib/store.js +25 -0
  78. package/lib/toolbar/widget.js +12 -2
  79. package/lib/tools.d.ts +35 -0
  80. package/lib/tools.js +86 -0
  81. package/lib/types.d.ts +14 -0
  82. package/package.json +18 -20
  83. package/style/base.css +4 -8
  84. package/style/dialog.css +1 -1
  85. package/style/icons/logo_mini.svg +70 -148
  86. package/style/icons/nonvisibility.svg +2 -7
  87. package/style/icons/visibility.svg +2 -6
  88. package/style/leftPanel.css +5 -0
  89. package/style/symbologyDialog.css +104 -3
  90. package/lib/dialogs/components/symbology/BandRendering.d.ts +0 -4
  91. package/lib/dialogs/components/symbology/Graduated.js +0 -188
  92. /package/lib/dialogs/{components/symbology → symbology/components/color_stops}/StopRow.d.ts +0 -0
  93. /package/lib/dialogs/{symbologyDialog.d.ts → symbology/symbologyDialog.d.ts} +0 -0
  94. /package/lib/dialogs/{components/symbology → symbology/tiff_layer/components}/BandRow.js +0 -0
  95. /package/lib/dialogs/{components/symbology → symbology/vector_layer/types}/Graduated.d.ts +0 -0
  96. /package/lib/dialogs/{components/symbology → symbology/vector_layer/types}/SimpleSymbol.d.ts +0 -0
@@ -1,101 +1,109 @@
1
- import { faSpinner } from '@fortawesome/free-solid-svg-icons';
2
- import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3
1
  import { Button } from '@jupyterlab/ui-components';
4
2
  import React, { useEffect, useRef, useState } from 'react';
5
- import { getGdal } from '../../../gdal';
6
- import BandRow from './BandRow';
7
- import StopRow from './StopRow';
8
- const SingleBandPseudoColor = ({ context, state, okSignalPromise, cancel, layerId }) => {
3
+ import { GeoTiffClassifications } from '../../classificationModes';
4
+ import { GlobalStateDbManager } from '../../../../store';
5
+ import BandRow from '../components/BandRow';
6
+ import ColorRamp from '../../components/color_ramp/ColorRamp';
7
+ import StopRow from '../../components/color_stops/StopRow';
8
+ import { Utils } from '../../symbologyUtils';
9
+ import { Spinner } from '../../../../mainview/spinner';
10
+ import { loadGeoTIFFWithCache } from '../../../../tools';
11
+ const SingleBandPseudoColor = ({ context, okSignalPromise, cancel, layerId }) => {
9
12
  const functions = ['discrete', 'linear', 'exact'];
13
+ const modeOptions = ['continuous', 'equal interval', 'quantile'];
10
14
  const stopRowsRef = useRef();
11
15
  const bandRowsRef = useRef([]);
12
16
  const selectedFunctionRef = useRef();
13
- const [selectedFunction, setSelectedFunction] = useState();
17
+ const colorRampOptionsRef = useRef();
18
+ const layerStateRef = useRef();
19
+ const selectedBandRef = useRef();
20
+ const [layerState, setLayerState] = useState();
14
21
  const [selectedBand, setSelectedBand] = useState(1);
15
22
  const [stopRows, setStopRows] = useState([]);
16
23
  const [bandRows, setBandRows] = useState([]);
24
+ const [selectedFunction, setSelectedFunction] = useState('linear');
25
+ const [colorRampOptions, setColorRampOptions] = useState();
17
26
  if (!layerId) {
18
27
  return;
19
28
  }
20
29
  const layer = context.model.getLayer(layerId);
21
- if (!layer) {
30
+ if (!(layer === null || layer === void 0 ? void 0 : layer.parameters)) {
22
31
  return;
23
32
  }
33
+ const stateDb = GlobalStateDbManager.getInstance().getStateDb();
24
34
  useEffect(() => {
25
- getBandInfo();
26
- setInitialFunction();
35
+ populateOptions();
36
+ okSignalPromise.promise.then(okSignal => {
37
+ okSignal.connect(handleOk);
38
+ });
39
+ return () => {
40
+ okSignalPromise.promise.then(okSignal => {
41
+ okSignal.disconnect(handleOk, this);
42
+ });
43
+ };
27
44
  }, []);
45
+ useEffect(() => {
46
+ layerStateRef.current = layerState;
47
+ getBandInfo();
48
+ }, [layerState]);
28
49
  useEffect(() => {
29
50
  bandRowsRef.current = bandRows;
30
51
  buildColorInfo();
31
52
  }, [bandRows]);
32
53
  useEffect(() => {
33
54
  stopRowsRef.current = stopRows;
34
- }, [stopRows]);
35
- useEffect(() => {
36
55
  selectedFunctionRef.current = selectedFunction;
37
- }, [selectedFunction]);
38
- const setInitialFunction = () => {
39
- var _a;
40
- if (!((_a = layer.parameters) === null || _a === void 0 ? void 0 : _a.color)) {
41
- setSelectedFunction('linear');
42
- return;
43
- }
44
- const color = layer.parameters.color;
45
- if (color[0] === 'interpolate') {
46
- setSelectedFunction('linear');
47
- return;
48
- }
49
- // If expression is using 'case' we look at the comparison operator to set selected function
50
- // Looking at fourth element because second is for nodata
51
- const operator = color[3][0];
52
- operator === '<='
53
- ? setSelectedFunction('discrete')
54
- : setSelectedFunction('exact');
56
+ colorRampOptionsRef.current = colorRampOptions;
57
+ selectedBandRef.current = selectedBand;
58
+ layerStateRef.current = layerState;
59
+ }, [stopRows, selectedFunction, colorRampOptions, selectedBand, layerState]);
60
+ const populateOptions = async () => {
61
+ var _a, _b, _c, _d;
62
+ const layerState = (await (stateDb === null || stateDb === void 0 ? void 0 : stateDb.fetch(`jupytergis:${layerId}`)));
63
+ setLayerState(layerState);
64
+ const layerParams = layer.parameters;
65
+ const band = (_b = (_a = layerParams.symbologyState) === null || _a === void 0 ? void 0 : _a.band) !== null && _b !== void 0 ? _b : 1;
66
+ const interpolation = (_d = (_c = layerParams.symbologyState) === null || _c === void 0 ? void 0 : _c.interpolation) !== null && _d !== void 0 ? _d : 'linear';
67
+ setSelectedBand(band);
68
+ setSelectedFunction(interpolation);
69
+ };
70
+ const preloadGeoTiffFile = async (sourceInfo) => {
71
+ return await loadGeoTIFFWithCache(sourceInfo);
55
72
  };
56
73
  const getBandInfo = async () => {
57
74
  var _a, _b;
58
75
  const bandsArr = [];
59
76
  const source = context.model.getSource((_a = layer === null || layer === void 0 ? void 0 : layer.parameters) === null || _a === void 0 ? void 0 : _a.source);
60
77
  const sourceInfo = (_b = source === null || source === void 0 ? void 0 : source.parameters) === null || _b === void 0 ? void 0 : _b.urls[0];
61
- if (!sourceInfo.url) {
78
+ if (!(sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.url)) {
62
79
  return;
63
80
  }
64
- let tifData;
65
- const layerState = await state.fetch(`jupytergis:${layerId}`);
66
- if (layerState) {
67
- tifData = JSON.parse(layerState.tifData);
68
- }
69
- else {
70
- const Gdal = await getGdal();
71
- const fileData = await fetch(sourceInfo.url);
72
- const file = new File([await fileData.blob()], 'loaded.tif');
73
- const result = await Gdal.open(file);
74
- const tifDataset = result.datasets[0];
75
- tifData = await Gdal.gdalinfo(tifDataset, ['-stats']);
76
- Gdal.close(tifDataset);
77
- state.save(`jupytergis:${layerId}`, { tifData: JSON.stringify(tifData) });
78
- }
79
- tifData['bands'].forEach((bandData) => {
80
- var _a, _b;
81
- bandsArr.push({
82
- band: bandData.band,
83
- colorInterpretation: bandData.colorInterpretation,
84
- stats: {
85
- minimum: (_a = sourceInfo.min) !== null && _a !== void 0 ? _a : bandData.minimum,
86
- maximum: (_b = sourceInfo.max) !== null && _b !== void 0 ? _b : bandData.maximum,
87
- mean: bandData.mean,
88
- stdDev: bandData.stdDev
89
- },
90
- metadata: bandData.metadata
81
+ // Preload the file only once
82
+ const preloadedFile = await preloadGeoTiffFile(sourceInfo);
83
+ const { file, metadata, sourceUrl } = Object.assign({}, preloadedFile);
84
+ if (file && metadata && sourceUrl === sourceInfo.url) {
85
+ metadata['bands'].forEach((bandData) => {
86
+ var _a, _b;
87
+ bandsArr.push({
88
+ band: bandData.band,
89
+ colorInterpretation: bandData.colorInterpretation,
90
+ stats: {
91
+ minimum: (_a = sourceInfo.min) !== null && _a !== void 0 ? _a : bandData.minimum,
92
+ maximum: (_b = sourceInfo.max) !== null && _b !== void 0 ? _b : bandData.maximum,
93
+ mean: bandData.mean,
94
+ stdDev: bandData.stdDev
95
+ },
96
+ metadata: bandData.metadata,
97
+ histogram: bandData.histogram
98
+ });
91
99
  });
92
- });
93
- setBandRows(bandsArr);
100
+ setBandRows(bandsArr);
101
+ }
94
102
  };
95
103
  const buildColorInfo = () => {
96
104
  var _a;
97
105
  // This it to parse a color object on the layer
98
- if (!((_a = layer.parameters) === null || _a === void 0 ? void 0 : _a.color)) {
106
+ if (!((_a = layer.parameters) === null || _a === void 0 ? void 0 : _a.color) || !layerState) {
99
107
  return;
100
108
  }
101
109
  const color = layer.parameters.color;
@@ -103,6 +111,7 @@ const SingleBandPseudoColor = ({ context, state, okSignalPromise, cancel, layerI
103
111
  if (typeof color === 'string') {
104
112
  return;
105
113
  }
114
+ const isQuantile = layerState.selectedMode === 'quantile';
106
115
  const valueColorPairs = [];
107
116
  // So if it's not a string then it's an array and we parse
108
117
  // Color[0] is the operator used for the color expression
@@ -115,7 +124,7 @@ const SingleBandPseudoColor = ({ context, state, okSignalPromise, cancel, layerI
115
124
  // Sixth and on is value:color pairs
116
125
  for (let i = 5; i < color.length; i += 2) {
117
126
  const obj = {
118
- stop: scaleValue(color[i]),
127
+ stop: scaleValue(color[i], isQuantile),
119
128
  output: color[i + 1]
120
129
  };
121
130
  valueColorPairs.push(obj);
@@ -132,7 +141,7 @@ const SingleBandPseudoColor = ({ context, state, okSignalPromise, cancel, layerI
132
141
  // Last element is fallback value
133
142
  for (let i = 3; i < color.length - 1; i += 2) {
134
143
  const obj = {
135
- stop: scaleValue(color[i][2]),
144
+ stop: scaleValue(color[i][2], isQuantile),
136
145
  output: color[i + 1]
137
146
  };
138
147
  valueColorPairs.push(obj);
@@ -143,7 +152,7 @@ const SingleBandPseudoColor = ({ context, state, okSignalPromise, cancel, layerI
143
152
  setStopRows(valueColorPairs);
144
153
  };
145
154
  const handleOk = () => {
146
- var _a, _b, _c, _d;
155
+ var _a, _b, _c, _d, _e, _f, _g, _h;
147
156
  // Update source
148
157
  const bandRow = bandRowsRef.current[selectedBand - 1];
149
158
  if (!bandRow) {
@@ -154,6 +163,7 @@ const SingleBandPseudoColor = ({ context, state, okSignalPromise, cancel, layerI
154
163
  if (!source || !source.parameters) {
155
164
  return;
156
165
  }
166
+ const isQuantile = ((_b = colorRampOptionsRef.current) === null || _b === void 0 ? void 0 : _b.selectedMode) === 'quantile';
157
167
  const sourceInfo = source.parameters.urls[0];
158
168
  sourceInfo.min = bandRow.stats.minimum;
159
169
  sourceInfo.max = bandRow.stats.maximum;
@@ -171,8 +181,8 @@ const SingleBandPseudoColor = ({ context, state, okSignalPromise, cancel, layerI
171
181
  colorExpr.push(['band', selectedBand]);
172
182
  // Set NoData values to transparent
173
183
  colorExpr.push(0.0, [0.0, 0.0, 0.0, 0.0]);
174
- (_b = stopRowsRef.current) === null || _b === void 0 ? void 0 : _b.map(stop => {
175
- colorExpr.push(unscaleValue(stop.stop));
184
+ (_c = stopRowsRef.current) === null || _c === void 0 ? void 0 : _c.map(stop => {
185
+ colorExpr.push(unscaleValue(stop.stop, isQuantile));
176
186
  colorExpr.push(stop.output);
177
187
  });
178
188
  break;
@@ -182,11 +192,11 @@ const SingleBandPseudoColor = ({ context, state, okSignalPromise, cancel, layerI
182
192
  // Set NoData values to transparent
183
193
  colorExpr.push(['==', ['band', selectedBand], 0]);
184
194
  colorExpr.push([0.0, 0.0, 0.0, 0.0]);
185
- (_c = stopRowsRef.current) === null || _c === void 0 ? void 0 : _c.map(stop => {
195
+ (_d = stopRowsRef.current) === null || _d === void 0 ? void 0 : _d.map(stop => {
186
196
  colorExpr.push([
187
197
  '<=',
188
198
  ['band', selectedBand],
189
- unscaleValue(stop.stop)
199
+ unscaleValue(stop.stop, isQuantile)
190
200
  ]);
191
201
  colorExpr.push(stop.output);
192
202
  });
@@ -199,11 +209,11 @@ const SingleBandPseudoColor = ({ context, state, okSignalPromise, cancel, layerI
199
209
  // Set NoData values to transparent
200
210
  colorExpr.push(['==', ['band', selectedBand], 0]);
201
211
  colorExpr.push([0.0, 0.0, 0.0, 0.0]);
202
- (_d = stopRowsRef.current) === null || _d === void 0 ? void 0 : _d.map(stop => {
212
+ (_e = stopRowsRef.current) === null || _e === void 0 ? void 0 : _e.map(stop => {
203
213
  colorExpr.push([
204
214
  '==',
205
215
  ['band', selectedBand],
206
- unscaleValue(stop.stop)
216
+ unscaleValue(stop.stop, isQuantile)
207
217
  ]);
208
218
  colorExpr.push(stop.output);
209
219
  });
@@ -212,13 +222,19 @@ const SingleBandPseudoColor = ({ context, state, okSignalPromise, cancel, layerI
212
222
  break;
213
223
  }
214
224
  }
225
+ const symbologyState = {
226
+ renderType: 'Singleband Pseudocolor',
227
+ band: selectedBandRef.current,
228
+ interpolation: selectedFunctionRef.current,
229
+ colorRamp: (_f = colorRampOptionsRef.current) === null || _f === void 0 ? void 0 : _f.selectedRamp,
230
+ nClasses: (_g = colorRampOptionsRef.current) === null || _g === void 0 ? void 0 : _g.numberOfShades,
231
+ mode: (_h = colorRampOptionsRef.current) === null || _h === void 0 ? void 0 : _h.selectedMode
232
+ };
233
+ layer.parameters.symbologyState = symbologyState;
215
234
  layer.parameters.color = colorExpr;
216
235
  context.model.sharedModel.updateLayer(layerId, layer);
217
236
  cancel();
218
237
  };
219
- okSignalPromise.promise.then(okSignal => {
220
- okSignal.connect(handleOk);
221
- });
222
238
  const addStopRow = () => {
223
239
  setStopRows([
224
240
  {
@@ -233,24 +249,55 @@ const SingleBandPseudoColor = ({ context, state, okSignalPromise, cancel, layerI
233
249
  newFilters.splice(index, 1);
234
250
  setStopRows(newFilters);
235
251
  };
236
- const scaleValue = (bandValue) => {
252
+ const buildColorInfoFromClassification = async (selectedMode, numberOfShades, selectedRamp, setIsLoading) => {
253
+ var _a, _b;
254
+ // Update layer state with selected options
255
+ setColorRampOptions({
256
+ selectedRamp,
257
+ numberOfShades,
258
+ selectedMode
259
+ });
260
+ let stops = [];
261
+ const currentBand = bandRows[selectedBand - 1];
262
+ const source = context.model.getSource((_a = layer === null || layer === void 0 ? void 0 : layer.parameters) === null || _a === void 0 ? void 0 : _a.source);
263
+ const sourceInfo = (_b = source === null || source === void 0 ? void 0 : source.parameters) === null || _b === void 0 ? void 0 : _b.urls[0];
264
+ const nClasses = selectedMode === 'continuous' ? 52 : +numberOfShades;
265
+ setIsLoading(true);
266
+ switch (selectedMode) {
267
+ case 'quantile':
268
+ stops = await GeoTiffClassifications.classifyQuantileBreaks(nClasses, selectedBand, sourceInfo.url, selectedFunction);
269
+ break;
270
+ case 'continuous':
271
+ stops = GeoTiffClassifications.classifyContinuousBreaks(nClasses, currentBand.stats.minimum, currentBand.stats.maximum, selectedFunction);
272
+ break;
273
+ case 'equal interval':
274
+ stops = GeoTiffClassifications.classifyEqualIntervalBreaks(nClasses, currentBand.stats.minimum, currentBand.stats.maximum, selectedFunction);
275
+ break;
276
+ default:
277
+ console.warn('No mode selected');
278
+ return;
279
+ }
280
+ setIsLoading(false);
281
+ const valueColorPairs = Utils.getValueColorPairs(stops, selectedRamp, nClasses);
282
+ setStopRows(valueColorPairs);
283
+ };
284
+ const scaleValue = (bandValue, isQuantile) => {
237
285
  const currentBand = bandRows[selectedBand - 1];
238
286
  if (!currentBand) {
239
287
  return bandValue;
240
288
  }
241
- return ((bandValue * (currentBand.stats.maximum - currentBand.stats.minimum)) /
242
- (1 - 0) +
243
- currentBand.stats.minimum);
289
+ const min = isQuantile ? 1 : currentBand.stats.minimum;
290
+ const max = isQuantile ? 65535 : currentBand.stats.maximum;
291
+ return (bandValue * (max - min)) / (1 - 0) + min;
244
292
  };
245
- const unscaleValue = (value) => {
293
+ const unscaleValue = (value, isQuantile) => {
246
294
  const currentBand = bandRowsRef.current[selectedBand - 1];
247
- return ((value * (1 - 0) - currentBand.stats.minimum * (1 - 0)) /
248
- (currentBand.stats.maximum - currentBand.stats.minimum));
295
+ const min = isQuantile ? 1 : currentBand.stats.minimum;
296
+ const max = isQuantile ? 65535 : currentBand.stats.maximum;
297
+ return (value * (1 - 0) - min * (1 - 0)) / (max - min);
249
298
  };
250
299
  return (React.createElement("div", { className: "jp-gis-layer-symbology-container" },
251
- React.createElement("div", { className: "jp-gis-band-container" }, bandRows.length === 0 ? (React.createElement("div", { className: "jp-gis-band-info-loading-container" },
252
- React.createElement("span", null, "Fetching band info..."),
253
- React.createElement(FontAwesomeIcon, { icon: faSpinner, className: "jp-gis-loading-spinner" }))) : (React.createElement(BandRow
300
+ React.createElement("div", { className: "jp-gis-band-container" }, bandRows.length === 0 ? (React.createElement(Spinner, { loading: bandRows.length === 0 })) : (React.createElement(BandRow
254
301
  // Band numbers are 1 indexed
255
302
  , {
256
303
  // Band numbers are 1 indexed
@@ -261,6 +308,7 @@ const SingleBandPseudoColor = ({ context, state, okSignalPromise, cancel, layerI
261
308
  React.createElement("select", { name: "function-select", id: "function-select", className: "jp-mod-styled", value: selectedFunction, style: { textTransform: 'capitalize' }, onChange: event => {
262
309
  setSelectedFunction(event.target.value);
263
310
  } }, functions.map((func, funcIndex) => (React.createElement("option", { key: func, value: func, style: { textTransform: 'capitalize' } }, func)))))),
311
+ bandRows.length > 0 && (React.createElement(ColorRamp, { layerParams: layer.parameters, modeOptions: modeOptions, classifyFunc: buildColorInfoFromClassification, showModeRow: true })),
264
312
  React.createElement("div", { className: "jp-gis-stop-container" },
265
313
  React.createElement("div", { className: "jp-gis-stop-labels", style: { display: 'flex', gap: 6 } },
266
314
  React.createElement("span", { style: { flex: '0 0 18%' } },
@@ -1,4 +1,4 @@
1
1
  import React from 'react';
2
- import { ISymbologyDialogProps } from '../../symbologyDialog';
2
+ import { ISymbologyDialogProps } from '../symbologyDialog';
3
3
  declare const VectorRendering: ({ context, state, okSignalPromise, cancel, layerId }: ISymbologyDialogProps) => React.JSX.Element | undefined;
4
4
  export default VectorRendering;
@@ -1,6 +1,7 @@
1
1
  import React, { useEffect, useState } from 'react';
2
- import Graduated from './Graduated';
3
- import SimpleSymbol from './SimpleSymbol';
2
+ import Graduated from './types/Graduated';
3
+ import SimpleSymbol from './types/SimpleSymbol';
4
+ import Categorized from './types/Categorized';
4
5
  const VectorRendering = ({ context, state, okSignalPromise, cancel, layerId }) => {
5
6
  const [selectedRenderType, setSelectedRenderType] = useState('Single Symbol');
6
7
  const [componentToRender, setComponentToRender] = useState(null);
@@ -16,20 +17,13 @@ const VectorRendering = ({ context, state, okSignalPromise, cancel, layerId }) =
16
17
  return;
17
18
  }
18
19
  useEffect(() => {
19
- const getSelectedRenderType = async () => {
20
- const layerState = await state.fetch(`jupytergis:${layerId}`);
21
- if (!layerState) {
22
- return;
23
- }
24
- const renderType = layerState
25
- .renderType;
26
- setSelectedRenderType(renderType !== null && renderType !== void 0 ? renderType : 'Single Symbol');
27
- };
20
+ var _a, _b;
21
+ const renderType = (_b = (_a = layer.parameters) === null || _a === void 0 ? void 0 : _a.symbologyState) === null || _b === void 0 ? void 0 : _b.renderType;
22
+ setSelectedRenderType(renderType !== null && renderType !== void 0 ? renderType : 'Single Symbol');
28
23
  if (layer.type === 'VectorLayer') {
29
- const options = ['Single Symbol', 'Graduated'];
24
+ const options = ['Single Symbol', 'Graduated', 'Categorized'];
30
25
  setRenderTypeOptions(options);
31
26
  }
32
- getSelectedRenderType();
33
27
  }, []);
34
28
  useEffect(() => {
35
29
  switch (selectedRenderType) {
@@ -39,6 +33,9 @@ const VectorRendering = ({ context, state, okSignalPromise, cancel, layerId }) =
39
33
  case 'Graduated':
40
34
  RenderComponent = (React.createElement(Graduated, { context: context, state: state, okSignalPromise: okSignalPromise, cancel: cancel, layerId: layerId }));
41
35
  break;
36
+ case 'Categorized':
37
+ RenderComponent = (React.createElement(Categorized, { context: context, state: state, okSignalPromise: okSignalPromise, cancel: cancel, layerId: layerId }));
38
+ break;
42
39
  default:
43
40
  RenderComponent = React.createElement("div", null, "Render Type Not Implemented (yet)");
44
41
  }
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ interface IValueSelectProps {
3
+ featureProperties: any;
4
+ selectedValue: string;
5
+ setSelectedValue: (value: string) => void;
6
+ }
7
+ declare const ValueSelect: ({ featureProperties, selectedValue, setSelectedValue }: IValueSelectProps) => React.JSX.Element;
8
+ export default ValueSelect;
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ const ValueSelect = ({ featureProperties, selectedValue, setSelectedValue }) => {
3
+ return (React.createElement("div", { className: "jp-gis-symbology-row" },
4
+ React.createElement("label", { htmlFor: 'vector-value-select' }, "Value:"),
5
+ React.createElement("select", { name: 'vector-value-select', onChange: event => setSelectedValue(event.target.value), className: "jp-mod-styled" }, Object.keys(featureProperties).map((feature, index) => (React.createElement("option", { key: index, value: feature, selected: feature === selectedValue, className: "jp-mod-styled" }, feature))))));
6
+ };
7
+ export default ValueSelect;
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ import { ISymbologyDialogProps } from '../../symbologyDialog';
3
+ declare const Categorized: ({ context, state, okSignalPromise, cancel, layerId }: ISymbologyDialogProps) => React.JSX.Element | undefined;
4
+ export default Categorized;
@@ -0,0 +1,94 @@
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import ValueSelect from '../components/ValueSelect';
3
+ import { useGetProperties } from '../../hooks/useGetProperties';
4
+ import StopContainer from '../../components/color_stops/StopContainer';
5
+ import { Utils, VectorUtils } from '../../symbologyUtils';
6
+ import ColorRamp from '../../components/color_ramp/ColorRamp';
7
+ const Categorized = ({ context, state, okSignalPromise, cancel, layerId }) => {
8
+ const selectedValueRef = useRef();
9
+ const stopRowsRef = useRef();
10
+ const colorRampOptionsRef = useRef();
11
+ const [selectedValue, setSelectedValue] = useState('');
12
+ const [stopRows, setStopRows] = useState([]);
13
+ const [colorRampOptions, setColorRampOptions] = useState();
14
+ if (!layerId) {
15
+ return;
16
+ }
17
+ const layer = context.model.getLayer(layerId);
18
+ if (!(layer === null || layer === void 0 ? void 0 : layer.parameters)) {
19
+ return;
20
+ }
21
+ const { featureProps } = useGetProperties({
22
+ layerId,
23
+ model: context.model
24
+ });
25
+ useEffect(() => {
26
+ const valueColorPairs = VectorUtils.buildColorInfo(layer);
27
+ setStopRows(valueColorPairs);
28
+ okSignalPromise.promise.then(okSignal => {
29
+ okSignal.connect(handleOk, this);
30
+ });
31
+ return () => {
32
+ okSignalPromise.promise.then(okSignal => {
33
+ okSignal.disconnect(handleOk, this);
34
+ });
35
+ };
36
+ }, []);
37
+ useEffect(() => {
38
+ populateOptions();
39
+ }, [featureProps]);
40
+ useEffect(() => {
41
+ selectedValueRef.current = selectedValue;
42
+ stopRowsRef.current = stopRows;
43
+ colorRampOptionsRef.current = colorRampOptions;
44
+ }, [selectedValue, stopRows, colorRampOptions]);
45
+ const populateOptions = async () => {
46
+ var _a, _b;
47
+ const layerParams = layer.parameters;
48
+ const value = (_b = (_a = layerParams.symbologyState) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : Object.keys(featureProps)[0];
49
+ setSelectedValue(value);
50
+ };
51
+ const buildColorInfoFromClassification = (selectedMode, numberOfShades, selectedRamp, setIsLoading) => {
52
+ setColorRampOptions({
53
+ selectedFunction: '',
54
+ selectedRamp,
55
+ numberOfShades: '',
56
+ selectedMode: ''
57
+ });
58
+ const stops = Array.from(featureProps[selectedValue]).sort((a, b) => a - b);
59
+ const valueColorPairs = Utils.getValueColorPairs(stops, selectedRamp, stops.length);
60
+ setStopRows(valueColorPairs);
61
+ };
62
+ const handleOk = () => {
63
+ var _a, _b, _c, _d;
64
+ if (!layer.parameters) {
65
+ return;
66
+ }
67
+ const colorExpr = [];
68
+ colorExpr.push('case');
69
+ (_a = stopRowsRef.current) === null || _a === void 0 ? void 0 : _a.map(stop => {
70
+ colorExpr.push(['==', ['get', selectedValueRef.current], stop.stop]);
71
+ colorExpr.push(stop.output);
72
+ });
73
+ // fallback value
74
+ colorExpr.push([0, 0, 0, 0.0]);
75
+ const newStyle = Object.assign({}, layer.parameters.color);
76
+ newStyle['circle-fill-color'] = colorExpr;
77
+ const symbologyState = {
78
+ renderType: 'Categorized',
79
+ value: selectedValueRef.current,
80
+ colorRamp: (_b = colorRampOptionsRef.current) === null || _b === void 0 ? void 0 : _b.selectedRamp,
81
+ nClasses: (_c = colorRampOptionsRef.current) === null || _c === void 0 ? void 0 : _c.numberOfShades,
82
+ mode: (_d = colorRampOptionsRef.current) === null || _d === void 0 ? void 0 : _d.selectedMode
83
+ };
84
+ layer.parameters.symbologyState = symbologyState;
85
+ layer.parameters.color = newStyle;
86
+ context.model.sharedModel.updateLayer(layerId, layer);
87
+ cancel();
88
+ };
89
+ return (React.createElement("div", { className: "jp-gis-layer-symbology-container" },
90
+ React.createElement(ValueSelect, { featureProperties: featureProps, selectedValue: selectedValue, setSelectedValue: setSelectedValue }),
91
+ React.createElement(ColorRamp, { layerParams: layer.parameters, modeOptions: [], classifyFunc: buildColorInfoFromClassification, showModeRow: false }),
92
+ React.createElement(StopContainer, { selectedMethod: '', stopRows: stopRows, setStopRows: setStopRows })));
93
+ };
94
+ export default Categorized;