@jupytergis/base 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +3 -8
- package/lib/dialogs/symbology/hooks/useGetBandInfo.js +16 -28
- 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/components/BandRow.js +3 -1
- 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 +51 -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 +26 -5
- package/lib/mainview/mainView.js +221 -108
- 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 +6 -16
- package/lib/tools.js +54 -56
- package/lib/types.d.ts +2 -0
- package/lib/widget.d.ts +30 -6
- package/lib/widget.js +43 -9
- package/package.json +4 -3
- package/style/base.css +10 -0
- package/style/symbologyDialog.css +7 -1
- package/style/temporalSlider.css +47 -0
package/lib/mainview/mainView.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { JupyterGISModel } from '@jupytergis/schema';
|
|
2
|
+
import { showErrorMessage } from '@jupyterlab/apputils';
|
|
2
3
|
import { CommandRegistry } from '@lumino/commands';
|
|
3
4
|
import { UUID } from '@lumino/coreutils';
|
|
4
5
|
import { ContextMenu } from '@lumino/widgets';
|
|
@@ -9,26 +10,26 @@ import { ScaleLine } from 'ol/control';
|
|
|
9
10
|
import { singleClick } from 'ol/events/condition';
|
|
10
11
|
import { GeoJSON, MVT } from 'ol/format';
|
|
11
12
|
import { DragAndDrop, Select } from 'ol/interaction';
|
|
12
|
-
import { Image as ImageLayer, Vector as VectorLayer, VectorTile as VectorTileLayer, WebGLTile as WebGlTileLayer } from 'ol/layer';
|
|
13
|
+
import { Heatmap as HeatmapLayer, Image as ImageLayer, Vector as VectorLayer, VectorTile as VectorTileLayer, WebGLTile as WebGlTileLayer } from 'ol/layer';
|
|
13
14
|
import TileLayer from 'ol/layer/Tile';
|
|
14
15
|
import { fromLonLat, get as getRegisteredProjection, toLonLat, transformExtent } from 'ol/proj';
|
|
15
16
|
import { get as getProjection } from 'ol/proj.js';
|
|
16
17
|
import { register } from 'ol/proj/proj4.js';
|
|
17
|
-
import
|
|
18
|
+
import RenderFeature from 'ol/render/Feature';
|
|
18
19
|
import { GeoTIFF as GeoTIFFSource, ImageTile as ImageTileSource, Vector as VectorSource, VectorTile as VectorTileSource, XYZ as XYZSource } from 'ol/source';
|
|
19
20
|
import Static from 'ol/source/ImageStatic';
|
|
20
21
|
import TileSource from 'ol/source/Tile';
|
|
21
22
|
import { Circle, Fill, Stroke, Style } from 'ol/style';
|
|
22
23
|
import proj4 from 'proj4';
|
|
23
|
-
//@ts-expect-error no types for proj4list
|
|
24
24
|
import proj4list from 'proj4-list';
|
|
25
25
|
import * as React from 'react';
|
|
26
26
|
import AnnotationFloater from '../annotations/components/AnnotationFloater';
|
|
27
27
|
import { CommandIDs } from '../constants';
|
|
28
28
|
import StatusBar from '../statusbar/StatusBar';
|
|
29
|
-
import { isLightTheme, loadFile,
|
|
29
|
+
import { isLightTheme, loadFile, throttle } from '../tools';
|
|
30
30
|
import CollaboratorPointers from './CollaboratorPointers';
|
|
31
31
|
import { FollowIndicator } from './FollowIndicator';
|
|
32
|
+
import TemporalSlider from './TemporalSlider';
|
|
32
33
|
import { Spinner } from './spinner';
|
|
33
34
|
export class MainView extends React.Component {
|
|
34
35
|
constructor(props) {
|
|
@@ -128,6 +129,7 @@ export class MainView extends React.Component {
|
|
|
128
129
|
});
|
|
129
130
|
};
|
|
130
131
|
this.vectorLayerStyleRuleBuilder = (layer) => {
|
|
132
|
+
var _a, _b;
|
|
131
133
|
const layerParams = layer.parameters;
|
|
132
134
|
if (!layerParams) {
|
|
133
135
|
return;
|
|
@@ -145,27 +147,25 @@ export class MainView extends React.Component {
|
|
|
145
147
|
style: defaultStyle
|
|
146
148
|
};
|
|
147
149
|
const layerStyle = Object.assign({}, defaultRules);
|
|
148
|
-
if (layer.filters &&
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
150
|
+
if (((_a = layer.filters) === null || _a === void 0 ? void 0 : _a.logicalOp) && ((_b = layer.filters.appliedFilters) === null || _b === void 0 ? void 0 : _b.length) > 0) {
|
|
151
|
+
const buildCondition = (filter) => {
|
|
152
|
+
const base = [filter.operator, ['get', filter.feature]];
|
|
153
|
+
return filter.operator === 'between'
|
|
154
|
+
? [...base, filter.betweenMin, filter.betweenMax]
|
|
155
|
+
: [...base, filter.value];
|
|
156
|
+
};
|
|
157
|
+
let filterExpr;
|
|
152
158
|
// 'Any' and 'All' operators require more than one argument
|
|
153
159
|
// So if there's only one filter, skip that part to avoid error
|
|
154
160
|
if (layer.filters.appliedFilters.length === 1) {
|
|
155
|
-
layer.filters.appliedFilters
|
|
156
|
-
filterExpr.push(filter.operator, ['get', filter.feature], filter.value);
|
|
157
|
-
});
|
|
161
|
+
filterExpr = buildCondition(layer.filters.appliedFilters[0]);
|
|
158
162
|
}
|
|
159
163
|
else {
|
|
160
|
-
filterExpr.push(layer.filters.logicalOp);
|
|
161
164
|
// Arguments for "Any" and 'All' need to be wrapped in brackets
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
filter.value
|
|
167
|
-
]);
|
|
168
|
-
});
|
|
165
|
+
filterExpr = [
|
|
166
|
+
layer.filters.logicalOp,
|
|
167
|
+
...layer.filters.appliedFilters.map(buildCondition)
|
|
168
|
+
];
|
|
169
169
|
}
|
|
170
170
|
layerStyle.filter = filterExpr;
|
|
171
171
|
}
|
|
@@ -223,16 +223,64 @@ export class MainView extends React.Component {
|
|
|
223
223
|
const scaled = ['*', 255, cosIncidence];
|
|
224
224
|
return scaled;
|
|
225
225
|
};
|
|
226
|
+
/**
|
|
227
|
+
* Heatmap layers don't work with style based filtering.
|
|
228
|
+
* This modifies the features in the underlying source
|
|
229
|
+
* to work with the temporal controller
|
|
230
|
+
*/
|
|
231
|
+
this.handleTemporalController = (id, layer) => {
|
|
232
|
+
var _a, _b, _c, _d, _e, _f;
|
|
233
|
+
const selectedLayer = (_c = (_b = (_a = this._model) === null || _a === void 0 ? void 0 : _a.localState) === null || _b === void 0 ? void 0 : _b.selected) === null || _c === void 0 ? void 0 : _c.value;
|
|
234
|
+
// Temporal Controller shouldn't be active if more than one layer is selected
|
|
235
|
+
if (!selectedLayer || Object.keys(selectedLayer).length !== 1) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const selectedLayerId = Object.keys(selectedLayer)[0];
|
|
239
|
+
// Don't do anything to unselected layers
|
|
240
|
+
if (selectedLayerId !== id) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const layerParams = layer.parameters;
|
|
244
|
+
const source = this._sources[layerParams.source];
|
|
245
|
+
if ((_d = layer.filters) === null || _d === void 0 ? void 0 : _d.appliedFilters.length) {
|
|
246
|
+
// Heatmaps don't work with existing filter system so this should be fine
|
|
247
|
+
const activeFilter = layer.filters.appliedFilters[0];
|
|
248
|
+
// Save original features on first filter application
|
|
249
|
+
if (!Object.keys(this._originalFeatures).includes(id)) {
|
|
250
|
+
this._originalFeatures[id] = source.getFeatures();
|
|
251
|
+
}
|
|
252
|
+
// clear current features
|
|
253
|
+
source.clear();
|
|
254
|
+
const startTime = (_e = activeFilter.betweenMin) !== null && _e !== void 0 ? _e : 0;
|
|
255
|
+
const endTime = (_f = activeFilter.betweenMax) !== null && _f !== void 0 ? _f : 1000;
|
|
256
|
+
const filteredFeatures = this._originalFeatures[id].filter(feature => {
|
|
257
|
+
const featureTime = feature.get(activeFilter.feature);
|
|
258
|
+
return featureTime >= startTime && featureTime <= endTime;
|
|
259
|
+
});
|
|
260
|
+
// set state for restoration
|
|
261
|
+
this.setState(old => (Object.assign(Object.assign({}, old), { filterStates: Object.assign(Object.assign({}, this.state.filterStates), { [selectedLayerId]: activeFilter }) })));
|
|
262
|
+
source.addFeatures(filteredFeatures);
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
// Restore original features when no filters are applied
|
|
266
|
+
source.addFeatures(this._originalFeatures[id]);
|
|
267
|
+
delete this._originalFeatures[id];
|
|
268
|
+
}
|
|
269
|
+
};
|
|
226
270
|
this._onClientSharedStateChanged = (sender, clients) => {
|
|
227
|
-
var _a, _b, _c
|
|
228
|
-
const
|
|
271
|
+
var _a, _b, _c;
|
|
272
|
+
const localState = this._model.localState;
|
|
273
|
+
if (!localState) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const remoteUser = localState.remoteUser;
|
|
229
277
|
// If we are in following mode, we update our position and selection
|
|
230
278
|
if (remoteUser) {
|
|
231
279
|
const remoteState = clients.get(remoteUser);
|
|
232
280
|
if (!remoteState) {
|
|
233
281
|
return;
|
|
234
282
|
}
|
|
235
|
-
if (((
|
|
283
|
+
if (((_a = remoteState.user) === null || _a === void 0 ? void 0 : _a.username) !== ((_b = this.state.remoteUser) === null || _b === void 0 ? void 0 : _b.username)) {
|
|
236
284
|
this.setState(old => (Object.assign(Object.assign({}, old), { remoteUser: remoteState.user })));
|
|
237
285
|
}
|
|
238
286
|
const remoteViewport = remoteState.viewportState;
|
|
@@ -246,7 +294,7 @@ export class MainView extends React.Component {
|
|
|
246
294
|
// If we are unfollowing a remote user, we reset our center and zoom to their previous values
|
|
247
295
|
if (this.state.remoteUser !== null) {
|
|
248
296
|
this.setState(old => (Object.assign(Object.assign({}, old), { remoteUser: null })));
|
|
249
|
-
const viewportState = (
|
|
297
|
+
const viewportState = (_c = localState.viewportState) === null || _c === void 0 ? void 0 : _c.value;
|
|
250
298
|
if (viewportState) {
|
|
251
299
|
this._moveToPosition(viewportState.coordinates, viewportState.zoom);
|
|
252
300
|
}
|
|
@@ -289,6 +337,13 @@ export class MainView extends React.Component {
|
|
|
289
337
|
}
|
|
290
338
|
this.setState(old => (Object.assign(Object.assign({}, old), { clientPointers: clientPointers })));
|
|
291
339
|
});
|
|
340
|
+
// Temporal controller bit
|
|
341
|
+
// ? There's probably a better way to get changes in the model to trigger react rerenders
|
|
342
|
+
const isTemporalControllerActive = localState.isTemporalControllerActive;
|
|
343
|
+
if (isTemporalControllerActive !== this.state.displayTemporalController) {
|
|
344
|
+
this.setState(old => (Object.assign(Object.assign({}, old), { displayTemporalController: isTemporalControllerActive })));
|
|
345
|
+
this._mainViewModel.commands.notifyCommandChanged(CommandIDs.temporalController);
|
|
346
|
+
}
|
|
292
347
|
};
|
|
293
348
|
this._onSharedModelStateChange = (_, change) => {
|
|
294
349
|
var _a;
|
|
@@ -343,10 +398,11 @@ export class MainView extends React.Component {
|
|
|
343
398
|
this._handleWindowResize = () => {
|
|
344
399
|
// TODO SOMETHING
|
|
345
400
|
};
|
|
346
|
-
this.
|
|
401
|
+
this._isPositionInitialized = false;
|
|
347
402
|
this.divRef = React.createRef(); // Reference of render div
|
|
348
403
|
this._ready = false;
|
|
349
404
|
this._sourceToLayerMap = new Map();
|
|
405
|
+
this._originalFeatures = {};
|
|
350
406
|
this._mainViewModel = this.props.viewModel;
|
|
351
407
|
this._mainViewModel.viewSettingChanged.connect(this._onViewChanged, this);
|
|
352
408
|
this._model = this._mainViewModel.jGISModel;
|
|
@@ -357,8 +413,10 @@ export class MainView extends React.Component {
|
|
|
357
413
|
this._model.sharedLayerTreeChanged.connect(this._onLayerTreeChange, this);
|
|
358
414
|
this._model.sharedSourcesChanged.connect(this._onSourcesChange, this);
|
|
359
415
|
this._model.sharedModel.changed.connect(this._onSharedModelStateChange);
|
|
360
|
-
this.
|
|
416
|
+
this._model.sharedMetadataChanged.connect(this._onSharedMetadataChanged, this);
|
|
361
417
|
this._model.zoomToPositionSignal.connect(this._onZoomToPosition, this);
|
|
418
|
+
this._model.updateLayerSignal.connect(this._triggerLayerUpdate, this);
|
|
419
|
+
this._model.addFeatureAsMsSignal.connect(this._convertFeatureToMs, this);
|
|
362
420
|
this.state = {
|
|
363
421
|
id: this._mainViewModel.id,
|
|
364
422
|
lightTheme: isLightTheme(),
|
|
@@ -368,7 +426,10 @@ export class MainView extends React.Component {
|
|
|
368
426
|
clientPointers: {},
|
|
369
427
|
viewProjection: { code: '', units: '' },
|
|
370
428
|
loadingLayer: false,
|
|
371
|
-
scale: 0
|
|
429
|
+
scale: 0,
|
|
430
|
+
loadingErrors: [],
|
|
431
|
+
displayTemporalController: false,
|
|
432
|
+
filterStates: {}
|
|
372
433
|
};
|
|
373
434
|
this._sources = [];
|
|
374
435
|
this._loadingLayers = new Set();
|
|
@@ -418,7 +479,7 @@ export class MainView extends React.Component {
|
|
|
418
479
|
parameters: { path: event.file.name }
|
|
419
480
|
};
|
|
420
481
|
const layerId = UUID.uuid4();
|
|
421
|
-
this.addSource(sourceId, sourceModel
|
|
482
|
+
this.addSource(sourceId, sourceModel);
|
|
422
483
|
this._model.sharedModel.addSource(sourceId, sourceModel);
|
|
423
484
|
const layerModel = {
|
|
424
485
|
type: 'VectorLayer',
|
|
@@ -458,9 +519,6 @@ export class MainView extends React.Component {
|
|
|
458
519
|
}
|
|
459
520
|
});
|
|
460
521
|
this._Map.on('moveend', () => {
|
|
461
|
-
if (!this._initializedPosition) {
|
|
462
|
-
return;
|
|
463
|
-
}
|
|
464
522
|
const currentOptions = this._model.getOptions();
|
|
465
523
|
const view = this._Map.getView();
|
|
466
524
|
const center = view.getCenter() || [0, 0];
|
|
@@ -495,7 +553,6 @@ export class MainView extends React.Component {
|
|
|
495
553
|
await this._updateLayersImpl(JupyterGISModel.getOrderedLayerIds(this._model));
|
|
496
554
|
const options = this._model.getOptions();
|
|
497
555
|
this.updateOptions(options);
|
|
498
|
-
this._initializedPosition = true;
|
|
499
556
|
}
|
|
500
557
|
this._Map.getViewport().addEventListener('contextmenu', event => {
|
|
501
558
|
event.preventDefault();
|
|
@@ -510,18 +567,17 @@ export class MainView extends React.Component {
|
|
|
510
567
|
} })));
|
|
511
568
|
}
|
|
512
569
|
}
|
|
513
|
-
async _loadGeoTIFFWithCache(sourceInfo) {
|
|
514
|
-
const result = await loadGeoTIFFWithCache(sourceInfo);
|
|
515
|
-
return result === null || result === void 0 ? void 0 : result.file;
|
|
516
|
-
}
|
|
517
570
|
/**
|
|
518
571
|
* Add a source in the map.
|
|
519
572
|
*
|
|
520
573
|
* @param id - the source id.
|
|
521
574
|
* @param source - the source object.
|
|
522
575
|
*/
|
|
523
|
-
async addSource(id, source
|
|
576
|
+
async addSource(id, source) {
|
|
524
577
|
var _a, _b;
|
|
578
|
+
const rasterSourceCommon = {
|
|
579
|
+
interpolate: false
|
|
580
|
+
};
|
|
525
581
|
let newSource;
|
|
526
582
|
switch (source.type) {
|
|
527
583
|
case 'RasterSource': {
|
|
@@ -529,29 +585,16 @@ export class MainView extends React.Component {
|
|
|
529
585
|
const pmTiles = sourceParameters.url.endsWith('.pmtiles');
|
|
530
586
|
const url = this.computeSourceUrl(source);
|
|
531
587
|
if (!pmTiles) {
|
|
532
|
-
newSource = new XYZSource({
|
|
533
|
-
attributions: sourceParameters.attribution,
|
|
534
|
-
minZoom: sourceParameters.minZoom,
|
|
535
|
-
maxZoom: sourceParameters.maxZoom,
|
|
536
|
-
tileSize: 256,
|
|
537
|
-
url: url
|
|
538
|
-
});
|
|
588
|
+
newSource = new XYZSource(Object.assign(Object.assign({}, rasterSourceCommon), { attributions: sourceParameters.attribution, minZoom: sourceParameters.minZoom, maxZoom: sourceParameters.maxZoom, tileSize: 256, url: url }));
|
|
539
589
|
}
|
|
540
590
|
else {
|
|
541
|
-
newSource = new PMTilesRasterSource({
|
|
542
|
-
attributions: sourceParameters.attribution,
|
|
543
|
-
tileSize: 256,
|
|
544
|
-
url: url
|
|
545
|
-
});
|
|
591
|
+
newSource = new PMTilesRasterSource(Object.assign(Object.assign({}, rasterSourceCommon), { attributions: sourceParameters.attribution, tileSize: 256, url: url }));
|
|
546
592
|
}
|
|
547
593
|
break;
|
|
548
594
|
}
|
|
549
595
|
case 'RasterDemSource': {
|
|
550
596
|
const sourceParameters = source.parameters;
|
|
551
|
-
newSource = new ImageTileSource({
|
|
552
|
-
url: this.computeSourceUrl(source),
|
|
553
|
-
attributions: sourceParameters.attribution
|
|
554
|
-
});
|
|
597
|
+
newSource = new ImageTileSource(Object.assign(Object.assign({}, rasterSourceCommon), { url: this.computeSourceUrl(source), attributions: sourceParameters.attribution }));
|
|
555
598
|
break;
|
|
556
599
|
}
|
|
557
600
|
case 'VectorTileSource': {
|
|
@@ -564,7 +607,7 @@ export class MainView extends React.Component {
|
|
|
564
607
|
minZoom: sourceParameters.minZoom,
|
|
565
608
|
maxZoom: sourceParameters.maxZoom,
|
|
566
609
|
url: url,
|
|
567
|
-
format: new MVT({ featureClass:
|
|
610
|
+
format: new MVT({ featureClass: RenderFeature })
|
|
568
611
|
});
|
|
569
612
|
}
|
|
570
613
|
else {
|
|
@@ -638,12 +681,7 @@ export class MainView extends React.Component {
|
|
|
638
681
|
type: 'ImageSource',
|
|
639
682
|
model: this._model
|
|
640
683
|
});
|
|
641
|
-
newSource = new Static({
|
|
642
|
-
imageExtent: extent,
|
|
643
|
-
url: imageUrl,
|
|
644
|
-
interpolate: false,
|
|
645
|
-
crossOrigin: ''
|
|
646
|
-
});
|
|
684
|
+
newSource = new Static(Object.assign(Object.assign({}, rasterSourceCommon), { imageExtent: extent, url: imageUrl, crossOrigin: '' }));
|
|
647
685
|
break;
|
|
648
686
|
}
|
|
649
687
|
case 'VideoSource': {
|
|
@@ -655,15 +693,10 @@ export class MainView extends React.Component {
|
|
|
655
693
|
const addNoData = (url) => {
|
|
656
694
|
return Object.assign(Object.assign({}, url), { nodata: 0 });
|
|
657
695
|
};
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
return Object.assign(Object.assign({}, addNoData(sourceInfo)), { blob });
|
|
696
|
+
const sources = await Promise.all(sourceParameters.urls.map(async (sourceInfo) => {
|
|
697
|
+
return Object.assign(Object.assign({}, addNoData(sourceInfo)), { min: sourceInfo.min, max: sourceInfo.max, url: sourceInfo.url });
|
|
661
698
|
}));
|
|
662
|
-
newSource = new GeoTIFFSource({
|
|
663
|
-
sources: sourcesWithBlobs,
|
|
664
|
-
normalize: sourceParameters.normalize,
|
|
665
|
-
wrapX: sourceParameters.wrapX
|
|
666
|
-
});
|
|
699
|
+
newSource = new GeoTIFFSource(Object.assign(Object.assign({}, rasterSourceCommon), { sources, normalize: sourceParameters.normalize, wrapX: sourceParameters.wrapX }));
|
|
667
700
|
break;
|
|
668
701
|
}
|
|
669
702
|
}
|
|
@@ -704,7 +737,7 @@ export class MainView extends React.Component {
|
|
|
704
737
|
// remove source being updated
|
|
705
738
|
this.removeSource(id);
|
|
706
739
|
// create updated source
|
|
707
|
-
await this.addSource(id, source
|
|
740
|
+
await this.addSource(id, source);
|
|
708
741
|
// change source of target layer
|
|
709
742
|
mapLayer.setSource(this._sources[id]);
|
|
710
743
|
}
|
|
@@ -773,7 +806,7 @@ export class MainView extends React.Component {
|
|
|
773
806
|
* @returns - the map layer.
|
|
774
807
|
*/
|
|
775
808
|
async _buildMapLayer(id, layer) {
|
|
776
|
-
var _a;
|
|
809
|
+
var _a, _b, _c;
|
|
777
810
|
const sourceId = (_a = layer.parameters) === null || _a === void 0 ? void 0 : _a.source;
|
|
778
811
|
const source = this._model.sharedModel.getLayerSource(sourceId);
|
|
779
812
|
if (!source) {
|
|
@@ -782,7 +815,7 @@ export class MainView extends React.Component {
|
|
|
782
815
|
this.setState(old => (Object.assign(Object.assign({}, old), { loadingLayer: true })));
|
|
783
816
|
this._loadingLayers.add(id);
|
|
784
817
|
if (!this._sources[sourceId]) {
|
|
785
|
-
await this.addSource(sourceId, source
|
|
818
|
+
await this.addSource(sourceId, source);
|
|
786
819
|
}
|
|
787
820
|
this._loadingLayers.add(id);
|
|
788
821
|
let newMapLayer;
|
|
@@ -815,7 +848,6 @@ export class MainView extends React.Component {
|
|
|
815
848
|
opacity: layerParameters.opacity,
|
|
816
849
|
source: this._sources[layerParameters.source]
|
|
817
850
|
});
|
|
818
|
-
this.updateLayer(id, layer, newMapLayer);
|
|
819
851
|
break;
|
|
820
852
|
}
|
|
821
853
|
case 'HillshadeLayer': {
|
|
@@ -850,6 +882,17 @@ export class MainView extends React.Component {
|
|
|
850
882
|
newMapLayer = new WebGlTileLayer(layerOptions);
|
|
851
883
|
break;
|
|
852
884
|
}
|
|
885
|
+
case 'HeatmapLayer': {
|
|
886
|
+
layerParameters = layer.parameters;
|
|
887
|
+
newMapLayer = new HeatmapLayer({
|
|
888
|
+
opacity: layerParameters.opacity,
|
|
889
|
+
source: this._sources[layerParameters.source],
|
|
890
|
+
blur: (_b = layerParameters.blur) !== null && _b !== void 0 ? _b : 15,
|
|
891
|
+
radius: (_c = layerParameters.radius) !== null && _c !== void 0 ? _c : 8,
|
|
892
|
+
gradient: layerParameters.color
|
|
893
|
+
});
|
|
894
|
+
break;
|
|
895
|
+
}
|
|
853
896
|
}
|
|
854
897
|
await this._waitForSourceReady(newMapLayer);
|
|
855
898
|
// OpenLayers doesn't have name/id field so add it
|
|
@@ -897,10 +940,29 @@ export class MainView extends React.Component {
|
|
|
897
940
|
// Layer already exists
|
|
898
941
|
return;
|
|
899
942
|
}
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
943
|
+
try {
|
|
944
|
+
const newMapLayer = await this._buildMapLayer(id, layer);
|
|
945
|
+
if (newMapLayer !== undefined) {
|
|
946
|
+
await this._waitForReady();
|
|
947
|
+
// Adjust index to ensure it's within bounds
|
|
948
|
+
const numLayers = this._Map.getLayers().getLength();
|
|
949
|
+
const safeIndex = Math.min(index, numLayers);
|
|
950
|
+
this._Map.getLayers().insertAt(safeIndex, newMapLayer);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
catch (error) {
|
|
954
|
+
if (this.state.loadingErrors.find(item => item.id === id && item.error === error.message)) {
|
|
955
|
+
this._loadingLayers.delete(id);
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
await showErrorMessage(`Error Adding ${layer.name}`, `Failed to add ${layer.name}: ${error.message || 'invalid file path'}`);
|
|
959
|
+
this.setState(old => (Object.assign(Object.assign({}, old), { loadingLayer: false })));
|
|
960
|
+
this.state.loadingErrors.push({
|
|
961
|
+
id,
|
|
962
|
+
error: error.message || 'invalid file path',
|
|
963
|
+
index
|
|
964
|
+
});
|
|
965
|
+
this._loadingLayers.delete(id);
|
|
904
966
|
}
|
|
905
967
|
}
|
|
906
968
|
/**
|
|
@@ -909,15 +971,15 @@ export class MainView extends React.Component {
|
|
|
909
971
|
* @param id - id of the layer.
|
|
910
972
|
* @param layer - the layer object.
|
|
911
973
|
*/
|
|
912
|
-
async updateLayer(id, layer, mapLayer) {
|
|
913
|
-
var _a, _b, _c, _d;
|
|
974
|
+
async updateLayer(id, layer, mapLayer, oldLayer) {
|
|
975
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
914
976
|
const sourceId = (_a = layer.parameters) === null || _a === void 0 ? void 0 : _a.source;
|
|
915
977
|
const source = this._model.sharedModel.getLayerSource(sourceId);
|
|
916
978
|
if (!source) {
|
|
917
979
|
return;
|
|
918
980
|
}
|
|
919
981
|
if (!this._sources[sourceId]) {
|
|
920
|
-
await this.addSource(sourceId, source
|
|
982
|
+
await this.addSource(sourceId, source);
|
|
921
983
|
}
|
|
922
984
|
mapLayer.setVisible(layer.visible);
|
|
923
985
|
switch (layer.type) {
|
|
@@ -953,6 +1015,16 @@ export class MainView extends React.Component {
|
|
|
953
1015
|
}
|
|
954
1016
|
break;
|
|
955
1017
|
}
|
|
1018
|
+
case 'HeatmapLayer': {
|
|
1019
|
+
const layerParams = layer.parameters;
|
|
1020
|
+
const heatmap = mapLayer;
|
|
1021
|
+
heatmap.setOpacity((_e = layerParams.opacity) !== null && _e !== void 0 ? _e : 1);
|
|
1022
|
+
heatmap.setBlur((_f = layerParams.blur) !== null && _f !== void 0 ? _f : 15);
|
|
1023
|
+
heatmap.setRadius((_g = layerParams.radius) !== null && _g !== void 0 ? _g : 8);
|
|
1024
|
+
heatmap.setGradient((_h = layerParams.color) !== null && _h !== void 0 ? _h : ['#00f', '#0ff', '#0f0', '#ff0', '#f00']);
|
|
1025
|
+
this.handleTemporalController(id, layer);
|
|
1026
|
+
break;
|
|
1027
|
+
}
|
|
956
1028
|
}
|
|
957
1029
|
}
|
|
958
1030
|
/**
|
|
@@ -1006,11 +1078,11 @@ export class MainView extends React.Component {
|
|
|
1006
1078
|
this._Map.removeLayer(mapLayer);
|
|
1007
1079
|
}
|
|
1008
1080
|
}
|
|
1009
|
-
_onSharedOptionsChanged(
|
|
1010
|
-
if (!this.
|
|
1081
|
+
_onSharedOptionsChanged() {
|
|
1082
|
+
if (!this._isPositionInitialized) {
|
|
1011
1083
|
const options = this._model.getOptions();
|
|
1012
1084
|
this.updateOptions(options);
|
|
1013
|
-
this.
|
|
1085
|
+
this._isPositionInitialized = true;
|
|
1014
1086
|
}
|
|
1015
1087
|
}
|
|
1016
1088
|
async updateOptions(options) {
|
|
@@ -1097,7 +1169,20 @@ export class MainView extends React.Component {
|
|
|
1097
1169
|
if (currentIndex < index) {
|
|
1098
1170
|
nextIndex -= 1;
|
|
1099
1171
|
}
|
|
1100
|
-
|
|
1172
|
+
// Adjust index to ensure it's within bounds
|
|
1173
|
+
const numLayers = this._Map.getLayers().getLength();
|
|
1174
|
+
const safeIndex = Math.min(index, numLayers);
|
|
1175
|
+
this._Map.getLayers().insertAt(safeIndex, layer);
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Remove and recreate layer
|
|
1179
|
+
* @param id ID of layer being replaced
|
|
1180
|
+
* @param layer New layer to replace with
|
|
1181
|
+
*/
|
|
1182
|
+
replaceLayer(id, layer) {
|
|
1183
|
+
const layerIndex = this.getLayerIndex(id);
|
|
1184
|
+
this.removeLayer(id);
|
|
1185
|
+
this.addLayer(id, layer, layerIndex);
|
|
1101
1186
|
}
|
|
1102
1187
|
_onLayersChanged(_, change) {
|
|
1103
1188
|
var _a;
|
|
@@ -1107,21 +1192,25 @@ export class MainView extends React.Component {
|
|
|
1107
1192
|
return;
|
|
1108
1193
|
}
|
|
1109
1194
|
(_a = change.layerChange) === null || _a === void 0 ? void 0 : _a.forEach(change => {
|
|
1110
|
-
const
|
|
1111
|
-
if (!
|
|
1112
|
-
this.removeLayer(
|
|
1195
|
+
const { id, oldValue: oldLayer, newValue: newLayer } = change;
|
|
1196
|
+
if (!newLayer || Object.keys(newLayer).length === 0) {
|
|
1197
|
+
this.removeLayer(id);
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
if (oldLayer && oldLayer.type !== newLayer.type) {
|
|
1201
|
+
this.replaceLayer(id, newLayer);
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
const mapLayer = this.getLayer(id);
|
|
1205
|
+
const layerTree = JupyterGISModel.getOrderedLayerIds(this._model);
|
|
1206
|
+
if (!mapLayer) {
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
if (layerTree.includes(id)) {
|
|
1210
|
+
this.updateLayer(id, newLayer, mapLayer, oldLayer);
|
|
1113
1211
|
}
|
|
1114
1212
|
else {
|
|
1115
|
-
|
|
1116
|
-
const layerTree = JupyterGISModel.getOrderedLayerIds(this._model);
|
|
1117
|
-
if (mapLayer) {
|
|
1118
|
-
if (layerTree.includes(change.id)) {
|
|
1119
|
-
this.updateLayer(change.id, layer, mapLayer);
|
|
1120
|
-
}
|
|
1121
|
-
else {
|
|
1122
|
-
this.updateLayers(layerTree);
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1213
|
+
this.updateLayers(layerTree);
|
|
1125
1214
|
}
|
|
1126
1215
|
});
|
|
1127
1216
|
}
|
|
@@ -1256,6 +1345,28 @@ export class MainView extends React.Component {
|
|
|
1256
1345
|
}
|
|
1257
1346
|
}
|
|
1258
1347
|
}
|
|
1348
|
+
_triggerLayerUpdate(_, args) {
|
|
1349
|
+
// ? could send just the filters object and modify that instead of emitting whole layer
|
|
1350
|
+
const json = JSON.parse(args);
|
|
1351
|
+
const { layerId, layer: jgisLayer } = json;
|
|
1352
|
+
const olLayer = this.getLayer(layerId);
|
|
1353
|
+
if (!jgisLayer || !olLayer) {
|
|
1354
|
+
console.log('Layer not found');
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
this.updateLayer(layerId, jgisLayer, olLayer);
|
|
1358
|
+
}
|
|
1359
|
+
_convertFeatureToMs(_, args) {
|
|
1360
|
+
const json = JSON.parse(args);
|
|
1361
|
+
const { id: layerId, selectedFeature } = json;
|
|
1362
|
+
const olLayer = this.getLayer(layerId);
|
|
1363
|
+
const source = olLayer.getSource();
|
|
1364
|
+
source.forEachFeature(feature => {
|
|
1365
|
+
const time = feature.get(selectedFeature);
|
|
1366
|
+
const parsedTime = typeof time === 'string' ? Date.parse(time) : time;
|
|
1367
|
+
feature.set(`${selectedFeature}ms`, parsedTime);
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1259
1370
|
render() {
|
|
1260
1371
|
return (React.createElement(React.Fragment, null,
|
|
1261
1372
|
Object.entries(this.state.annotations).map(([key, annotation]) => {
|
|
@@ -1269,18 +1380,20 @@ export class MainView extends React.Component {
|
|
|
1269
1380
|
}, className: 'jGIS-Popup-Wrapper' },
|
|
1270
1381
|
React.createElement(AnnotationFloater, { itemId: key, annotationModel: this._model.annotationModel, open: false }))));
|
|
1271
1382
|
}),
|
|
1272
|
-
React.createElement("div", { className: "jGIS-Mainview",
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
:
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1383
|
+
React.createElement("div", { className: "jGIS-Mainview-Container" },
|
|
1384
|
+
this.state.displayTemporalController && (React.createElement(TemporalSlider, { model: this._model, filterStates: this.state.filterStates })),
|
|
1385
|
+
React.createElement("div", { className: "jGIS-Mainview", style: {
|
|
1386
|
+
border: this.state.remoteUser
|
|
1387
|
+
? `solid 3px ${this.state.remoteUser.color}`
|
|
1388
|
+
: 'unset'
|
|
1389
|
+
} },
|
|
1390
|
+
React.createElement(Spinner, { loading: this.state.loading }),
|
|
1391
|
+
React.createElement(FollowIndicator, { remoteUser: this.state.remoteUser }),
|
|
1392
|
+
React.createElement(CollaboratorPointers, { clients: this.state.clientPointers }),
|
|
1393
|
+
React.createElement("div", { ref: this.divRef, style: {
|
|
1394
|
+
width: '100%',
|
|
1395
|
+
height: '100%'
|
|
1396
|
+
} })),
|
|
1397
|
+
React.createElement(StatusBar, { jgisModel: this._model, loading: this.state.loadingLayer, projection: this.state.viewProjection, scale: this.state.scale }))));
|
|
1285
1398
|
}
|
|
1286
1399
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { IAnnotation, IJupyterGISModel } from '@jupytergis/schema';
|
|
2
2
|
import { ObservableMap } from '@jupyterlab/observables';
|
|
3
|
+
import { CommandRegistry } from '@lumino/commands';
|
|
3
4
|
import { JSONValue } from '@lumino/coreutils';
|
|
4
5
|
import { IDisposable } from '@lumino/disposable';
|
|
5
6
|
export declare class MainViewModel implements IDisposable {
|
|
@@ -8,12 +9,14 @@ export declare class MainViewModel implements IDisposable {
|
|
|
8
9
|
get id(): string;
|
|
9
10
|
get jGISModel(): IJupyterGISModel;
|
|
10
11
|
get viewSettingChanged(): import("@lumino/signaling").ISignal<ObservableMap<JSONValue>, import("@jupyterlab/observables").IObservableMap.IChangedArgs<JSONValue>>;
|
|
12
|
+
get commands(): CommandRegistry;
|
|
11
13
|
dispose(): void;
|
|
12
14
|
initSignal(): void;
|
|
13
15
|
addAnnotation(value: IAnnotation): void;
|
|
14
16
|
private _onsharedLayersChanged;
|
|
15
17
|
private _jGISModel;
|
|
16
18
|
private _viewSetting;
|
|
19
|
+
private _commands;
|
|
17
20
|
private _id;
|
|
18
21
|
private _isDisposed;
|
|
19
22
|
}
|
|
@@ -21,5 +24,6 @@ export declare namespace MainViewModel {
|
|
|
21
24
|
interface IOptions {
|
|
22
25
|
jGISModel: IJupyterGISModel;
|
|
23
26
|
viewSetting: ObservableMap<JSONValue>;
|
|
27
|
+
commands: CommandRegistry;
|
|
24
28
|
}
|
|
25
29
|
}
|
|
@@ -4,6 +4,7 @@ export class MainViewModel {
|
|
|
4
4
|
this._isDisposed = false;
|
|
5
5
|
this._jGISModel = options.jGISModel;
|
|
6
6
|
this._viewSetting = options.viewSetting;
|
|
7
|
+
this._commands = options.commands;
|
|
7
8
|
}
|
|
8
9
|
get isDisposed() {
|
|
9
10
|
return this._isDisposed;
|
|
@@ -17,6 +18,9 @@ export class MainViewModel {
|
|
|
17
18
|
get viewSettingChanged() {
|
|
18
19
|
return this._viewSetting.changed;
|
|
19
20
|
}
|
|
21
|
+
get commands() {
|
|
22
|
+
return this._commands;
|
|
23
|
+
}
|
|
20
24
|
dispose() {
|
|
21
25
|
if (this._isDisposed) {
|
|
22
26
|
return;
|
|
@@ -3,8 +3,6 @@ import { MainViewModel } from './mainviewmodel';
|
|
|
3
3
|
export declare class JupyterGISMainViewPanel extends ReactWidget {
|
|
4
4
|
/**
|
|
5
5
|
* Construct a `JupyterGISPanel`.
|
|
6
|
-
*
|
|
7
|
-
* @param context - The documents context.
|
|
8
6
|
*/
|
|
9
7
|
constructor(options: {
|
|
10
8
|
mainViewModel: MainViewModel;
|