@jupytergis/base 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/annotations/components/Annotation.js +1 -1
- package/lib/annotations/model.d.ts +6 -7
- package/lib/annotations/model.js +15 -15
- package/lib/commands.d.ts +2 -3
- package/lib/commands.js +117 -62
- package/lib/constants.d.ts +2 -0
- package/lib/constants.js +4 -1
- package/lib/dialogs/formdialog.js +2 -2
- package/lib/dialogs/layerBrowserDialog.d.ts +4 -5
- package/lib/dialogs/layerBrowserDialog.js +9 -9
- package/lib/dialogs/symbology/hooks/useGetBandInfo.d.ts +1 -2
- package/lib/dialogs/symbology/hooks/useGetBandInfo.js +11 -6
- package/lib/dialogs/symbology/hooks/useGetProperties.d.ts +1 -1
- package/lib/dialogs/symbology/hooks/useGetProperties.js +6 -8
- package/lib/dialogs/symbology/symbologyDialog.d.ts +2 -3
- package/lib/dialogs/symbology/symbologyDialog.js +10 -9
- package/lib/dialogs/symbology/tiff_layer/TiffRendering.d.ts +1 -1
- package/lib/dialogs/symbology/tiff_layer/TiffRendering.js +6 -6
- package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.d.ts +1 -1
- package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.js +5 -4
- package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.d.ts +1 -1
- package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +8 -7
- package/lib/dialogs/symbology/vector_layer/VectorRendering.d.ts +1 -1
- package/lib/dialogs/symbology/vector_layer/VectorRendering.js +18 -13
- package/lib/dialogs/symbology/vector_layer/types/Categorized.d.ts +1 -1
- package/lib/dialogs/symbology/vector_layer/types/Categorized.js +30 -19
- package/lib/dialogs/symbology/vector_layer/types/Graduated.d.ts +1 -1
- package/lib/dialogs/symbology/vector_layer/types/Graduated.js +16 -13
- package/lib/dialogs/symbology/vector_layer/types/Heatmap.d.ts +4 -0
- package/lib/dialogs/symbology/vector_layer/types/Heatmap.js +77 -0
- package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.d.ts +1 -1
- package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.js +4 -3
- package/lib/formbuilder/creationform.d.ts +1 -2
- package/lib/formbuilder/creationform.js +4 -4
- package/lib/formbuilder/editform.d.ts +1 -2
- package/lib/formbuilder/editform.js +7 -7
- package/lib/formbuilder/formselectors.js +5 -2
- package/lib/formbuilder/objectform/baseform.d.ts +3 -4
- package/lib/formbuilder/objectform/baseform.js +2 -2
- package/lib/formbuilder/objectform/fileselectorwidget.js +13 -6
- package/lib/formbuilder/objectform/geotiffsource.d.ts +5 -1
- package/lib/formbuilder/objectform/geotiffsource.js +64 -18
- package/lib/formbuilder/objectform/heatmapLayerForm.d.ts +11 -0
- package/lib/formbuilder/objectform/heatmapLayerForm.js +60 -0
- package/lib/formbuilder/objectform/vectorlayerform.d.ts +0 -2
- package/lib/formbuilder/objectform/vectorlayerform.js +0 -59
- package/lib/mainview/TemporalSlider.d.ts +8 -0
- package/lib/mainview/TemporalSlider.js +303 -0
- package/lib/mainview/mainView.d.ts +25 -4
- package/lib/mainview/mainView.js +213 -75
- package/lib/mainview/mainviewmodel.d.ts +4 -0
- package/lib/mainview/mainviewmodel.js +4 -0
- package/lib/mainview/mainviewwidget.d.ts +0 -2
- package/lib/mainview/mainviewwidget.js +0 -2
- package/lib/panelview/annotationPanel.js +5 -5
- package/lib/panelview/components/filter-panel/Filter.js +4 -25
- package/lib/panelview/components/identify-panel/IdentifyPanel.js +1 -1
- package/lib/panelview/components/layers.js +2 -2
- package/lib/panelview/components/sources.js +1 -1
- package/lib/panelview/leftpanel.d.ts +3 -0
- package/lib/panelview/leftpanel.js +5 -1
- package/lib/panelview/model.js +8 -8
- package/lib/panelview/objectproperties.js +10 -10
- package/lib/panelview/rightpanel.d.ts +1 -1
- package/lib/panelview/rightpanel.js +10 -10
- package/lib/toolbar/widget.d.ts +1 -1
- package/lib/toolbar/widget.js +44 -32
- package/lib/tools.d.ts +10 -6
- package/lib/tools.js +89 -15
- package/lib/types.d.ts +2 -0
- package/lib/widget.d.ts +29 -5
- package/lib/widget.js +41 -7
- package/package.json +4 -3
- package/style/base.css +10 -0
- package/style/symbologyDialog.css +7 -1
- package/style/temporalSlider.css +47 -0
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { getSourceLayerNames } from '../../tools';
|
|
2
1
|
import { LayerPropertiesForm } from './layerform';
|
|
3
2
|
/**
|
|
4
3
|
* The form to modify a vector layer.
|
|
@@ -6,29 +5,6 @@ import { LayerPropertiesForm } from './layerform';
|
|
|
6
5
|
export class VectorLayerPropertiesForm extends LayerPropertiesForm {
|
|
7
6
|
constructor(props) {
|
|
8
7
|
super(props);
|
|
9
|
-
this.sourceLayers = [];
|
|
10
|
-
this.fetchSourceLayers(this.props.sourceData);
|
|
11
|
-
// If there is a source form attached, we listen to its changes
|
|
12
|
-
if (this.sourceFormChangedSignal) {
|
|
13
|
-
this.sourceFormChangedSignal.connect((sender, sourceData) => {
|
|
14
|
-
if (this.props.sourceType === 'VectorTileSource') {
|
|
15
|
-
this.fetchSourceLayers(this.currentFormData, sourceData);
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
props.model.clientStateChanged.connect(() => {
|
|
20
|
-
var _a;
|
|
21
|
-
if (!((_a = props.model.localState) === null || _a === void 0 ? void 0 : _a.selected.value)) {
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
const l = this.props.model.getLayer(Object.keys(props.model.localState.selected.value)[0]);
|
|
25
|
-
const source = this.props.model.getSource(l === null || l === void 0 ? void 0 : l.parameters.source);
|
|
26
|
-
if (!source || source.type !== 'VectorTileSource') {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
const sourceData = source.parameters;
|
|
30
|
-
this.fetchSourceLayers(this.currentFormData, sourceData);
|
|
31
|
-
});
|
|
32
8
|
}
|
|
33
9
|
onFormChange(e) {
|
|
34
10
|
super.onFormChange(e);
|
|
@@ -40,7 +16,6 @@ export class VectorLayerPropertiesForm extends LayerPropertiesForm {
|
|
|
40
16
|
if (!source || source.type !== 'VectorTileSource') {
|
|
41
17
|
return;
|
|
42
18
|
}
|
|
43
|
-
this.fetchSourceLayers(this.currentFormData, source.parameters);
|
|
44
19
|
}
|
|
45
20
|
processSchema(data, schema, uiSchema) {
|
|
46
21
|
this.removeFormEntry('color', data, schema, uiSchema);
|
|
@@ -49,39 +24,5 @@ export class VectorLayerPropertiesForm extends LayerPropertiesForm {
|
|
|
49
24
|
if (!data) {
|
|
50
25
|
return;
|
|
51
26
|
}
|
|
52
|
-
// Show a dropdown for available sourceLayers if available
|
|
53
|
-
// And automatically select one
|
|
54
|
-
if (this.sourceLayers.length !== 0) {
|
|
55
|
-
if (!data.sourceLayer || !this.sourceLayers.includes(data.sourceLayer)) {
|
|
56
|
-
data.sourceLayer = this.sourceLayers[0];
|
|
57
|
-
}
|
|
58
|
-
schema.properties.sourceLayer.enum = this.sourceLayers;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
async fetchSourceLayers(data, sourceData) {
|
|
62
|
-
if (data && data.source) {
|
|
63
|
-
this.currentSourceId = data.source;
|
|
64
|
-
if (!sourceData) {
|
|
65
|
-
const currentSource = this.props.model.getSource(data.source);
|
|
66
|
-
if (!currentSource || currentSource.type !== 'VectorTileSource') {
|
|
67
|
-
this.sourceLayers = [];
|
|
68
|
-
this.forceUpdate();
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
sourceData = currentSource.parameters;
|
|
72
|
-
}
|
|
73
|
-
try {
|
|
74
|
-
this.sourceLayers = await getSourceLayerNames(sourceData.url, sourceData.urlParameters);
|
|
75
|
-
this.forceUpdate();
|
|
76
|
-
}
|
|
77
|
-
catch (e) {
|
|
78
|
-
console.error(e);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
this.currentSourceId = '';
|
|
83
|
-
this.sourceLayers = [];
|
|
84
|
-
this.forceUpdate();
|
|
85
|
-
}
|
|
86
27
|
}
|
|
87
28
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { IDict, IJGISFilterItem, IJupyterGISModel } from '@jupytergis/schema';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
interface ITemporalSliderProps {
|
|
4
|
+
model: IJupyterGISModel;
|
|
5
|
+
filterStates: IDict<IJGISFilterItem | undefined>;
|
|
6
|
+
}
|
|
7
|
+
declare const TemporalSlider: ({ model, filterStates }: ITemporalSliderProps) => React.JSX.Element;
|
|
8
|
+
export default TemporalSlider;
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { faPause, faPlay } from '@fortawesome/free-solid-svg-icons';
|
|
2
|
+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
3
|
+
import { Button, Slider } from '@jupyter/react-components';
|
|
4
|
+
import { format, isValid, parse } from 'date-fns';
|
|
5
|
+
import { daysInYear, millisecondsInDay, millisecondsInHour, millisecondsInMinute, millisecondsInSecond, millisecondsInWeek, minutesInMonth } from 'date-fns/constants';
|
|
6
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
7
|
+
import { useGetProperties } from '../dialogs/symbology/hooks/useGetProperties';
|
|
8
|
+
// List of common date formats to try
|
|
9
|
+
// TODO: Not even close to every valid format
|
|
10
|
+
const commonDateFormats = [
|
|
11
|
+
'yyyy-MM-dd', // ISO format (e.g., 2023-10-05)
|
|
12
|
+
'dd/MM/yyyy', // European format (e.g., 05/10/2023)
|
|
13
|
+
'MM/dd/yyyy', // US format (e.g., 10/05/2023)
|
|
14
|
+
'yyyyMMdd', // Compact format (e.g., 20231005)
|
|
15
|
+
'dd-MM-yyyy', // European format with hyphens (e.g., 05-10-2023)
|
|
16
|
+
'MM-dd-yyyy', // US format with hyphens (e.g., 10-05-2023)
|
|
17
|
+
'yyyy/MM/dd', // ISO format with slashes (e.g., 2023/10/05)
|
|
18
|
+
'dd.MM.yyyy', // European format with dots (e.g., 05.10.2023)
|
|
19
|
+
'MM.dd.yyyy' // US format with dots (e.g., 10.05.2023)
|
|
20
|
+
];
|
|
21
|
+
// Time steps in milliseconds
|
|
22
|
+
const stepMap = {
|
|
23
|
+
millisecond: 1,
|
|
24
|
+
second: millisecondsInSecond,
|
|
25
|
+
minute: millisecondsInMinute,
|
|
26
|
+
hour: millisecondsInHour,
|
|
27
|
+
day: millisecondsInDay,
|
|
28
|
+
week: millisecondsInWeek,
|
|
29
|
+
month: minutesInMonth * millisecondsInMinute,
|
|
30
|
+
year: millisecondsInDay * daysInYear
|
|
31
|
+
};
|
|
32
|
+
const TemporalSlider = ({ model, filterStates }) => {
|
|
33
|
+
const [layerId, setLayerId] = useState('');
|
|
34
|
+
const [selectedFeature, setSelectedFeature] = useState('');
|
|
35
|
+
const [range, setRange] = useState({ start: 0, end: 1 }); // min/max of current range being displayed
|
|
36
|
+
const [minMax, setMinMax] = useState({ min: 0, max: 1 }); // min/max of data values
|
|
37
|
+
const [validFeatures, setValidFeatures] = useState([]);
|
|
38
|
+
const [dateFormat, setDateFormat] = useState('yyyy-MM-dd');
|
|
39
|
+
const [step, setStep] = useState(stepMap.year);
|
|
40
|
+
const [currentValue, setCurrentValue] = useState(0);
|
|
41
|
+
const [fps, setFps] = useState(1);
|
|
42
|
+
const [validSteps, setValidSteps] = useState({});
|
|
43
|
+
const layerIdRef = useRef('');
|
|
44
|
+
const intervalRef = useRef(null);
|
|
45
|
+
const { featureProperties } = useGetProperties({ layerId, model });
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
// This is for when the selected layer changes
|
|
48
|
+
const handleClientStateChanged = () => {
|
|
49
|
+
var _a, _b;
|
|
50
|
+
if (!((_b = (_a = model.localState) === null || _a === void 0 ? void 0 : _a.selected) === null || _b === void 0 ? void 0 : _b.value)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const selectedLayerId = Object.keys(model.localState.selected.value)[0];
|
|
54
|
+
// reset
|
|
55
|
+
if (selectedLayerId !== layerIdRef.current) {
|
|
56
|
+
setLayerId(selectedLayerId);
|
|
57
|
+
setDateFormat('yyyy-MM-dd');
|
|
58
|
+
setFps(1);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
// this is for when the layer itself changes
|
|
62
|
+
const handleLayerChange = (_, change) => {
|
|
63
|
+
var _a;
|
|
64
|
+
// Get the changes for the selected layer
|
|
65
|
+
const selectedLayer = (_a = change.layerChange) === null || _a === void 0 ? void 0 : _a.find(layer => layer.id === layerIdRef.current);
|
|
66
|
+
// Bail if there's no relevant change
|
|
67
|
+
if (!(selectedLayer === null || selectedLayer === void 0 ? void 0 : selectedLayer.newValue)) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const { newValue, oldValue } = selectedLayer;
|
|
71
|
+
// If layer was deleted (empty object) or the layer type changed, close the temporal controller
|
|
72
|
+
if (Object.keys(newValue).length === 0 ||
|
|
73
|
+
newValue.type !== oldValue.type) {
|
|
74
|
+
model.toggleTemporalController();
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
// Initial state
|
|
78
|
+
handleClientStateChanged();
|
|
79
|
+
model.clientStateChanged.connect(handleClientStateChanged);
|
|
80
|
+
model.sharedLayersChanged.connect(handleLayerChange);
|
|
81
|
+
return () => {
|
|
82
|
+
model.clientStateChanged.disconnect(handleClientStateChanged);
|
|
83
|
+
model.sharedLayersChanged.disconnect(handleLayerChange);
|
|
84
|
+
removeFilter();
|
|
85
|
+
if (intervalRef.current) {
|
|
86
|
+
clearInterval(intervalRef.current);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}, []);
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
layerIdRef.current = layerId;
|
|
92
|
+
}, [layerId]);
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
const results = [];
|
|
95
|
+
for (const [key, set] of Object.entries(featureProperties)) {
|
|
96
|
+
if (set.size === 0) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const sampleValue = set.values().next().value;
|
|
100
|
+
// Validate value type
|
|
101
|
+
const isString = typeof sampleValue === 'string';
|
|
102
|
+
const isInteger = Number.isInteger(sampleValue);
|
|
103
|
+
if (!isString && !isInteger) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
// Date validation
|
|
107
|
+
if (isString) {
|
|
108
|
+
const dateFormatFromString = determineDateFormat(sampleValue);
|
|
109
|
+
if (!dateFormatFromString) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
setDateFormat(dateFormatFromString);
|
|
113
|
+
}
|
|
114
|
+
results.push(key);
|
|
115
|
+
}
|
|
116
|
+
// if we have state then remove the ms from the converted feature name
|
|
117
|
+
const currentState = filterStates[layerId];
|
|
118
|
+
const currentFeature = currentState === null || currentState === void 0 ? void 0 : currentState.feature.slice(0, -2);
|
|
119
|
+
setValidFeatures(results);
|
|
120
|
+
setSelectedFeature(currentFeature !== null && currentFeature !== void 0 ? currentFeature : results[0]);
|
|
121
|
+
}, [featureProperties]);
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
var _a, _b, _c;
|
|
124
|
+
if (!selectedFeature || !featureProperties[selectedFeature]) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
// Get and validate values
|
|
128
|
+
const valueSet = featureProperties[selectedFeature];
|
|
129
|
+
if (valueSet.size === 0) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const values = Array.from(valueSet);
|
|
133
|
+
const [firstValue] = values;
|
|
134
|
+
// Check the type of the first element
|
|
135
|
+
const isString = typeof firstValue === 'string';
|
|
136
|
+
let convertedValues;
|
|
137
|
+
if (isString) {
|
|
138
|
+
convertedValues = values.map(value => Date.parse(value)); // Convert all strings to milliseconds
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
convertedValues = values; // Keep numbers as they are
|
|
142
|
+
}
|
|
143
|
+
// Calculate min and max
|
|
144
|
+
const min = Math.min(...convertedValues);
|
|
145
|
+
const max = Math.max(...convertedValues);
|
|
146
|
+
// Get valid step options
|
|
147
|
+
const filteredSteps = Object.fromEntries(Object.entries(stepMap).filter(([_, val]) => val < max - min));
|
|
148
|
+
//using filter item as a state object to restore prev values
|
|
149
|
+
const currentState = filterStates[layerId];
|
|
150
|
+
const step = (_a = Object.values(filteredSteps).slice(-1)[0]) !== null && _a !== void 0 ? _a : stepMap.millisecond;
|
|
151
|
+
setValidSteps(filteredSteps);
|
|
152
|
+
setStep(step);
|
|
153
|
+
setMinMax({ min, max });
|
|
154
|
+
setRange({
|
|
155
|
+
start: (_b = currentState === null || currentState === void 0 ? void 0 : currentState.betweenMin) !== null && _b !== void 0 ? _b : min,
|
|
156
|
+
end: (_c = currentState === null || currentState === void 0 ? void 0 : currentState.betweenMax) !== null && _c !== void 0 ? _c : min + step
|
|
157
|
+
});
|
|
158
|
+
model.addFeatureAsMs(layerId, selectedFeature);
|
|
159
|
+
}, [selectedFeature]);
|
|
160
|
+
// minMax needs to be set before current value so the slider displays correctly
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
const currentState = filterStates[layerId];
|
|
163
|
+
setCurrentValue(typeof (currentState === null || currentState === void 0 ? void 0 : currentState.value) === 'number' ? currentState.value : minMax.min);
|
|
164
|
+
}, [minMax]);
|
|
165
|
+
// Guess the date format from a date string
|
|
166
|
+
const determineDateFormat = (dateString) => {
|
|
167
|
+
for (const format of commonDateFormats) {
|
|
168
|
+
const parsedDate = parse(dateString, format, new Date());
|
|
169
|
+
if (isValid(parsedDate)) {
|
|
170
|
+
return format; // Return the format if the date is valid
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return null; // Return null if no format matches
|
|
174
|
+
};
|
|
175
|
+
// Convert milliseconds back to the original date string format
|
|
176
|
+
const millisecondsToDateString = (milliseconds, dateFormat) => {
|
|
177
|
+
const date = new Date(milliseconds); // Create a Date object from milliseconds
|
|
178
|
+
return format(date, dateFormat); // Format back to the original string format
|
|
179
|
+
};
|
|
180
|
+
const handleChange = (e) => {
|
|
181
|
+
setCurrentValue(+e.target.value);
|
|
182
|
+
setRange({ start: +e.target.value, end: +e.target.value + step });
|
|
183
|
+
applyFilter(+e.target.value);
|
|
184
|
+
};
|
|
185
|
+
const applyFilter = (value) => {
|
|
186
|
+
var _a, _b;
|
|
187
|
+
const newFilter = {
|
|
188
|
+
feature: `${selectedFeature}ms`,
|
|
189
|
+
operator: 'between',
|
|
190
|
+
value: value,
|
|
191
|
+
betweenMin: value,
|
|
192
|
+
betweenMax: value + step
|
|
193
|
+
};
|
|
194
|
+
const layer = model.getLayer(layerId);
|
|
195
|
+
if (!layer) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const appliedFilters = ((_a = layer.filters) === null || _a === void 0 ? void 0 : _a.appliedFilters) || [];
|
|
199
|
+
const logicalOp = ((_b = layer.filters) === null || _b === void 0 ? void 0 : _b.logicalOp) || 'all';
|
|
200
|
+
// This is the only way to add a 'between' filter so
|
|
201
|
+
// find the index of the existing 'between' filter
|
|
202
|
+
const betweenFilterIndex = appliedFilters.findIndex(filter => filter.operator === 'between');
|
|
203
|
+
if (betweenFilterIndex !== -1) {
|
|
204
|
+
// If found, replace the existing filter
|
|
205
|
+
appliedFilters[betweenFilterIndex] = Object.assign({}, newFilter);
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
// If not found, add the new filter
|
|
209
|
+
appliedFilters.push(newFilter);
|
|
210
|
+
}
|
|
211
|
+
// Apply the updated filters to the layer
|
|
212
|
+
layer.filters = { logicalOp, appliedFilters };
|
|
213
|
+
model.triggerLayerUpdate(layerId, layer);
|
|
214
|
+
};
|
|
215
|
+
const removeFilter = () => {
|
|
216
|
+
var _a, _b;
|
|
217
|
+
const layer = model.getLayer(layerIdRef.current);
|
|
218
|
+
if (!layer) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const appliedFilters = ((_a = layer.filters) === null || _a === void 0 ? void 0 : _a.appliedFilters) || [];
|
|
222
|
+
const logicalOp = ((_b = layer.filters) === null || _b === void 0 ? void 0 : _b.logicalOp) || 'all';
|
|
223
|
+
// This is the only way to add a 'between' filter so
|
|
224
|
+
// find the index of the existing 'between' filter
|
|
225
|
+
const betweenFilterIndex = appliedFilters.findIndex(filter => filter.operator === 'between');
|
|
226
|
+
if (betweenFilterIndex !== -1) {
|
|
227
|
+
// If found, replace the existing filter
|
|
228
|
+
appliedFilters.splice(betweenFilterIndex, 1);
|
|
229
|
+
}
|
|
230
|
+
// Apply the updated filters to the layer
|
|
231
|
+
layer.filters = { logicalOp, appliedFilters };
|
|
232
|
+
model.triggerLayerUpdate(layerIdRef.current, layer);
|
|
233
|
+
};
|
|
234
|
+
const playAnimation = () => {
|
|
235
|
+
// Clear any existing interval first
|
|
236
|
+
if (intervalRef.current) {
|
|
237
|
+
clearInterval(intervalRef.current);
|
|
238
|
+
}
|
|
239
|
+
const incrementValue = () => {
|
|
240
|
+
setCurrentValue(prev => {
|
|
241
|
+
// Calculate next value with safety bounds
|
|
242
|
+
const nextValue = prev + step;
|
|
243
|
+
// Clear interval if we've reached the max
|
|
244
|
+
// step is subtracted to keep range values correct
|
|
245
|
+
if (nextValue >= minMax.max - step && intervalRef.current) {
|
|
246
|
+
clearInterval(intervalRef.current);
|
|
247
|
+
return minMax.max - step;
|
|
248
|
+
}
|
|
249
|
+
return nextValue;
|
|
250
|
+
});
|
|
251
|
+
};
|
|
252
|
+
// Start animation
|
|
253
|
+
intervalRef.current = setInterval(incrementValue, 1000 / fps);
|
|
254
|
+
};
|
|
255
|
+
const pauseAnimation = () => {
|
|
256
|
+
if (intervalRef.current) {
|
|
257
|
+
clearInterval(intervalRef.current);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
return (React.createElement("div", { className: "jp-gis-temporal-slider-container" },
|
|
261
|
+
React.createElement("div", { className: "jp-gis-temporal-slider-row" },
|
|
262
|
+
React.createElement("div", null,
|
|
263
|
+
React.createElement("label", { htmlFor: "time-feature-select" }, "Feature: "),
|
|
264
|
+
React.createElement("select", { id: "time-feature-select", onChange: e => {
|
|
265
|
+
setSelectedFeature(e.target.value);
|
|
266
|
+
} }, validFeatures.map(feature => {
|
|
267
|
+
return (React.createElement("option", { value: feature, selected: selectedFeature === feature }, feature));
|
|
268
|
+
}))),
|
|
269
|
+
React.createElement("div", null,
|
|
270
|
+
React.createElement("span", null, "Current Frame:"),
|
|
271
|
+
' ',
|
|
272
|
+
millisecondsToDateString(range.start, dateFormat),
|
|
273
|
+
" \u2264 ",
|
|
274
|
+
React.createElement("span", null, "t"),
|
|
275
|
+
" \u2264",
|
|
276
|
+
' ',
|
|
277
|
+
millisecondsToDateString(range.end, dateFormat))),
|
|
278
|
+
React.createElement("div", { className: "jp-gis-temporal-slider-row" },
|
|
279
|
+
React.createElement("div", { className: "jp-gis-temporal-slider-controls" },
|
|
280
|
+
React.createElement("div", { className: "jp-gis-temporal-slider-sub-controls" },
|
|
281
|
+
React.createElement(Button, { appearance: "neutral", scale: "medium", onClick: pauseAnimation },
|
|
282
|
+
React.createElement(FontAwesomeIcon, { icon: faPause })),
|
|
283
|
+
React.createElement(Button, { appearance: "neutral", scale: "medium", onClick: playAnimation },
|
|
284
|
+
React.createElement(FontAwesomeIcon, { icon: faPlay }))),
|
|
285
|
+
React.createElement("div", { className: "jp-gis-temporal-slider-sub-controls", style: { minWidth: 0 } },
|
|
286
|
+
React.createElement("label", { htmlFor: "fps-number-input" }, "FPS:"),
|
|
287
|
+
React.createElement("input", { name: "fps-number-input", type: "number", value: fps, onChange: e => setFps(+e.target.value) }))),
|
|
288
|
+
React.createElement("div", null,
|
|
289
|
+
React.createElement(Slider, { min: minMax.min, max: minMax.max - step, value: currentValue, valueAsNumber: currentValue, step: step, onChange: handleChange, className: "jp-gis-temporal-slider" }))),
|
|
290
|
+
React.createElement("div", { className: "jp-gis-temporal-slider-row" },
|
|
291
|
+
React.createElement("div", null,
|
|
292
|
+
React.createElement("span", null, "Range: "),
|
|
293
|
+
millisecondsToDateString(minMax.min, dateFormat),
|
|
294
|
+
" ",
|
|
295
|
+
React.createElement("span", null, "to "),
|
|
296
|
+
millisecondsToDateString(minMax.max, dateFormat)),
|
|
297
|
+
React.createElement("div", null,
|
|
298
|
+
React.createElement("label", { htmlFor: "time-step-select" }, "Step: "),
|
|
299
|
+
React.createElement("select", { id: "time-step-select", onChange: e => {
|
|
300
|
+
setStep(+e.target.value);
|
|
301
|
+
} }, Object.entries(validSteps).map(([key, val]) => (React.createElement("option", { key: key, selected: val === step, value: val }, key))))))));
|
|
302
|
+
};
|
|
303
|
+
export default TemporalSlider;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IAnnotation, IDict, IJGISLayer, IJGISSource } from '@jupytergis/schema';
|
|
1
|
+
import { IAnnotation, IDict, IJGISFilterItem, IJGISLayer, IJGISSource } from '@jupytergis/schema';
|
|
2
2
|
import { User } from '@jupyterlab/services';
|
|
3
3
|
import { Layer } from 'ol/layer';
|
|
4
4
|
import * as React from 'react';
|
|
@@ -21,6 +21,13 @@ interface IStates {
|
|
|
21
21
|
};
|
|
22
22
|
loadingLayer: boolean;
|
|
23
23
|
scale: number;
|
|
24
|
+
loadingErrors: Array<{
|
|
25
|
+
id: string;
|
|
26
|
+
error: any;
|
|
27
|
+
index: number;
|
|
28
|
+
}>;
|
|
29
|
+
displayTemporalController: boolean;
|
|
30
|
+
filterStates: IDict<IJGISFilterItem | undefined>;
|
|
24
31
|
}
|
|
25
32
|
export declare class MainView extends React.Component<IProps, IStates> {
|
|
26
33
|
constructor(props: IProps);
|
|
@@ -29,7 +36,6 @@ export declare class MainView extends React.Component<IProps, IStates> {
|
|
|
29
36
|
generateScene(): Promise<void>;
|
|
30
37
|
createSelectInteraction: () => void;
|
|
31
38
|
addContextMenu: () => void;
|
|
32
|
-
private _loadGeoTIFFWithCache;
|
|
33
39
|
/**
|
|
34
40
|
* Add a source in the map.
|
|
35
41
|
*
|
|
@@ -97,7 +103,13 @@ export declare class MainView extends React.Component<IProps, IStates> {
|
|
|
97
103
|
* @param id - id of the layer.
|
|
98
104
|
* @param layer - the layer object.
|
|
99
105
|
*/
|
|
100
|
-
updateLayer(id: string, layer: IJGISLayer, mapLayer: Layer): Promise<void>;
|
|
106
|
+
updateLayer(id: string, layer: IJGISLayer, mapLayer: Layer, oldLayer?: IDict): Promise<void>;
|
|
107
|
+
/**
|
|
108
|
+
* Heatmap layers don't work with style based filtering.
|
|
109
|
+
* This modifies the features in the underlying source
|
|
110
|
+
* to work with the temporal controller
|
|
111
|
+
*/
|
|
112
|
+
handleTemporalController: (id: string, layer: IJGISLayer) => void;
|
|
101
113
|
/**
|
|
102
114
|
* Wait for all layers to be loaded.
|
|
103
115
|
*/
|
|
@@ -138,6 +150,12 @@ export declare class MainView extends React.Component<IProps, IStates> {
|
|
|
138
150
|
* @param index - expected index of the layer.
|
|
139
151
|
*/
|
|
140
152
|
moveLayer(id: string, index: number): void;
|
|
153
|
+
/**
|
|
154
|
+
* Remove and recreate layer
|
|
155
|
+
* @param id ID of layer being replaced
|
|
156
|
+
* @param layer New layer to replace with
|
|
157
|
+
*/
|
|
158
|
+
replaceLayer(id: string, layer: IJGISLayer): void;
|
|
141
159
|
private _onLayersChanged;
|
|
142
160
|
private _onLayerTreeChange;
|
|
143
161
|
private _onSourcesChange;
|
|
@@ -150,12 +168,14 @@ export declare class MainView extends React.Component<IProps, IStates> {
|
|
|
150
168
|
private _onPointerMove;
|
|
151
169
|
private _syncPointer;
|
|
152
170
|
private _identifyFeature;
|
|
171
|
+
private _triggerLayerUpdate;
|
|
172
|
+
private _convertFeatureToMs;
|
|
153
173
|
private _handleThemeChange;
|
|
154
174
|
private _handleWindowResize;
|
|
155
175
|
render(): JSX.Element;
|
|
156
176
|
private _clickCoords;
|
|
157
177
|
private _commands;
|
|
158
|
-
private
|
|
178
|
+
private _isPositionInitialized;
|
|
159
179
|
private divRef;
|
|
160
180
|
private _Map;
|
|
161
181
|
private _model;
|
|
@@ -166,5 +186,6 @@ export declare class MainView extends React.Component<IProps, IStates> {
|
|
|
166
186
|
private _documentPath?;
|
|
167
187
|
private _contextMenu;
|
|
168
188
|
private _loadingLayers;
|
|
189
|
+
private _originalFeatures;
|
|
169
190
|
}
|
|
170
191
|
export {};
|