@jupytergis/base 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/annotations/components/Annotation.js +2 -2
- 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 +146 -62
- package/lib/constants.d.ts +3 -0
- package/lib/constants.js +5 -1
- package/lib/dialogs/formdialog.d.ts +5 -0
- 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/components/color_ramp/ModeSelectRow.js +2 -1
- package/lib/dialogs/symbology/hooks/useGetBandInfo.d.ts +26 -0
- package/lib/dialogs/symbology/hooks/useGetBandInfo.js +64 -0
- package/lib/dialogs/symbology/hooks/useGetProperties.d.ts +1 -1
- package/lib/dialogs/symbology/hooks/useGetProperties.js +12 -9
- 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 +16 -3
- package/lib/dialogs/symbology/tiff_layer/components/BandRow.d.ts +16 -3
- package/lib/dialogs/symbology/tiff_layer/components/BandRow.js +21 -7
- package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.d.ts +4 -0
- package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.js +85 -0
- package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.d.ts +1 -20
- package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +25 -65
- 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 +6 -2
- package/lib/formbuilder/creationform.js +6 -6
- package/lib/formbuilder/editform.d.ts +2 -2
- package/lib/formbuilder/editform.js +14 -9
- package/lib/formbuilder/formselectors.js +11 -1
- package/lib/formbuilder/objectform/baseform.d.ts +12 -3
- package/lib/formbuilder/objectform/baseform.js +39 -0
- package/lib/formbuilder/objectform/fileselectorwidget.d.ts +2 -0
- package/lib/formbuilder/objectform/fileselectorwidget.js +88 -0
- package/lib/formbuilder/objectform/geojsonsource.d.ts +5 -7
- package/lib/formbuilder/objectform/geojsonsource.js +8 -24
- 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/layerform.d.ts +2 -0
- package/lib/formbuilder/objectform/layerform.js +6 -0
- package/lib/formbuilder/objectform/pathbasedsource.d.ts +19 -0
- package/lib/formbuilder/objectform/pathbasedsource.js +98 -0
- package/lib/formbuilder/objectform/vectorlayerform.d.ts +0 -2
- package/lib/formbuilder/objectform/vectorlayerform.js +0 -59
- package/lib/icons.d.ts +1 -0
- package/lib/icons.js +5 -0
- package/lib/keybindings.json +62 -0
- package/lib/mainview/TemporalSlider.d.ts +8 -0
- package/lib/mainview/TemporalSlider.js +303 -0
- package/lib/mainview/mainView.d.ts +46 -8
- package/lib/mainview/mainView.js +431 -144
- 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 +8 -24
- 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/statusbar/StatusBar.d.ts +13 -0
- package/lib/statusbar/StatusBar.js +52 -0
- package/lib/toolbar/widget.d.ts +1 -1
- package/lib/toolbar/widget.js +44 -37
- package/lib/tools.d.ts +50 -7
- package/lib/tools.js +394 -12
- package/lib/types.d.ts +2 -0
- package/lib/widget.d.ts +29 -5
- package/lib/widget.js +41 -7
- package/package.json +17 -5
- package/style/base.css +11 -0
- package/style/icons/logo_mini_qgz.svg +31 -0
- package/style/leftPanel.css +8 -0
- package/style/statusBar.css +16 -0
- package/style/symbologyDialog.css +7 -1
- package/style/temporalSlider.css +47 -0
package/lib/mainview/mainView.js
CHANGED
|
@@ -1,37 +1,107 @@
|
|
|
1
1
|
import { JupyterGISModel } from '@jupytergis/schema';
|
|
2
|
+
import { showErrorMessage } from '@jupyterlab/apputils';
|
|
3
|
+
import { CommandRegistry } from '@lumino/commands';
|
|
2
4
|
import { UUID } from '@lumino/coreutils';
|
|
5
|
+
import { ContextMenu } from '@lumino/widgets';
|
|
3
6
|
import { Collection, Map as OlMap, View, getUid } from 'ol';
|
|
7
|
+
//@ts-expect-error no types for ol-pmtiles
|
|
8
|
+
import { PMTilesRasterSource, PMTilesVectorSource } from 'ol-pmtiles';
|
|
4
9
|
import { ScaleLine } from 'ol/control';
|
|
10
|
+
import { singleClick } from 'ol/events/condition';
|
|
5
11
|
import { GeoJSON, MVT } from 'ol/format';
|
|
6
12
|
import { DragAndDrop, Select } from 'ol/interaction';
|
|
7
|
-
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';
|
|
8
14
|
import TileLayer from 'ol/layer/Tile';
|
|
9
|
-
import { fromLonLat, toLonLat } from 'ol/proj';
|
|
10
|
-
import
|
|
15
|
+
import { fromLonLat, get as getRegisteredProjection, toLonLat, transformExtent } from 'ol/proj';
|
|
16
|
+
import { get as getProjection } from 'ol/proj.js';
|
|
17
|
+
import { register } from 'ol/proj/proj4.js';
|
|
18
|
+
import RenderFeature from 'ol/render/Feature';
|
|
11
19
|
import { GeoTIFF as GeoTIFFSource, ImageTile as ImageTileSource, Vector as VectorSource, VectorTile as VectorTileSource, XYZ as XYZSource } from 'ol/source';
|
|
12
20
|
import Static from 'ol/source/ImageStatic';
|
|
13
|
-
|
|
14
|
-
import {
|
|
15
|
-
import { register } from 'ol/proj/proj4.js';
|
|
16
|
-
import { get as getProjection } from 'ol/proj.js';
|
|
21
|
+
import TileSource from 'ol/source/Tile';
|
|
22
|
+
import { Circle, Fill, Stroke, Style } from 'ol/style';
|
|
17
23
|
import proj4 from 'proj4';
|
|
18
|
-
import * as React from 'react';
|
|
19
|
-
import shp from 'shpjs';
|
|
20
|
-
import { isLightTheme, loadGeoTIFFWithCache, throttle } from '../tools';
|
|
21
|
-
import { Spinner } from './spinner';
|
|
22
|
-
//@ts-expect-error no types for proj4-list
|
|
23
24
|
import proj4list from 'proj4-list';
|
|
24
|
-
import
|
|
25
|
-
import { CommandRegistry } from '@lumino/commands';
|
|
25
|
+
import * as React from 'react';
|
|
26
26
|
import AnnotationFloater from '../annotations/components/AnnotationFloater';
|
|
27
27
|
import { CommandIDs } from '../constants';
|
|
28
|
-
import
|
|
28
|
+
import StatusBar from '../statusbar/StatusBar';
|
|
29
|
+
import { isLightTheme, loadFile, throttle } from '../tools';
|
|
29
30
|
import CollaboratorPointers from './CollaboratorPointers';
|
|
30
|
-
import {
|
|
31
|
-
import
|
|
31
|
+
import { FollowIndicator } from './FollowIndicator';
|
|
32
|
+
import TemporalSlider from './TemporalSlider';
|
|
33
|
+
import { Spinner } from './spinner';
|
|
32
34
|
export class MainView extends React.Component {
|
|
33
35
|
constructor(props) {
|
|
34
36
|
super(props);
|
|
37
|
+
this.createSelectInteraction = () => {
|
|
38
|
+
const pointStyle = new Style({
|
|
39
|
+
image: new Circle({
|
|
40
|
+
radius: 5,
|
|
41
|
+
fill: new Fill({
|
|
42
|
+
color: '#C52707'
|
|
43
|
+
}),
|
|
44
|
+
stroke: new Stroke({
|
|
45
|
+
color: '#171717',
|
|
46
|
+
width: 2
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
});
|
|
50
|
+
const lineStyle = new Style({
|
|
51
|
+
stroke: new Stroke({
|
|
52
|
+
color: '#171717',
|
|
53
|
+
width: 2
|
|
54
|
+
})
|
|
55
|
+
});
|
|
56
|
+
const polygonStyle = new Style({
|
|
57
|
+
fill: new Fill({ color: '#C5270780' }),
|
|
58
|
+
stroke: new Stroke({
|
|
59
|
+
color: '#171717',
|
|
60
|
+
width: 2
|
|
61
|
+
})
|
|
62
|
+
});
|
|
63
|
+
const styleFunction = (feature) => {
|
|
64
|
+
var _a;
|
|
65
|
+
const geometryType = (_a = feature.getGeometry()) === null || _a === void 0 ? void 0 : _a.getType();
|
|
66
|
+
switch (geometryType) {
|
|
67
|
+
case 'Point':
|
|
68
|
+
case 'MultiPoint':
|
|
69
|
+
return pointStyle;
|
|
70
|
+
case 'LineString':
|
|
71
|
+
case 'MultiLineString':
|
|
72
|
+
return lineStyle;
|
|
73
|
+
case 'Polygon':
|
|
74
|
+
case 'MultiPolygon':
|
|
75
|
+
return polygonStyle;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const selectInteraction = new Select({
|
|
79
|
+
hitTolerance: 5,
|
|
80
|
+
multi: true,
|
|
81
|
+
layers: layer => {
|
|
82
|
+
var _a, _b;
|
|
83
|
+
const localState = (_a = this._model) === null || _a === void 0 ? void 0 : _a.sharedModel.awareness.getLocalState();
|
|
84
|
+
const selectedLayers = (_b = localState === null || localState === void 0 ? void 0 : localState.selected) === null || _b === void 0 ? void 0 : _b.value;
|
|
85
|
+
if (!selectedLayers) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
const selectedLayerId = Object.keys(selectedLayers)[0];
|
|
89
|
+
return layer === this.getLayer(selectedLayerId);
|
|
90
|
+
},
|
|
91
|
+
condition: (event) => {
|
|
92
|
+
return singleClick(event) && this._model.isIdentifying;
|
|
93
|
+
},
|
|
94
|
+
style: styleFunction
|
|
95
|
+
});
|
|
96
|
+
selectInteraction.on('select', event => {
|
|
97
|
+
const identifiedFeatures = [];
|
|
98
|
+
selectInteraction.getFeatures().forEach(feature => {
|
|
99
|
+
identifiedFeatures.push(feature.getProperties());
|
|
100
|
+
});
|
|
101
|
+
this._model.syncIdentifiedFeatures(identifiedFeatures, this._mainViewModel.id);
|
|
102
|
+
});
|
|
103
|
+
this._Map.addInteraction(selectInteraction);
|
|
104
|
+
};
|
|
35
105
|
this.addContextMenu = () => {
|
|
36
106
|
this._commands.addCommand(CommandIDs.addAnnotation, {
|
|
37
107
|
execute: () => {
|
|
@@ -59,6 +129,7 @@ export class MainView extends React.Component {
|
|
|
59
129
|
});
|
|
60
130
|
};
|
|
61
131
|
this.vectorLayerStyleRuleBuilder = (layer) => {
|
|
132
|
+
var _a, _b;
|
|
62
133
|
const layerParams = layer.parameters;
|
|
63
134
|
if (!layerParams) {
|
|
64
135
|
return;
|
|
@@ -76,27 +147,25 @@ export class MainView extends React.Component {
|
|
|
76
147
|
style: defaultStyle
|
|
77
148
|
};
|
|
78
149
|
const layerStyle = Object.assign({}, defaultRules);
|
|
79
|
-
if (layer.filters &&
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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;
|
|
83
158
|
// 'Any' and 'All' operators require more than one argument
|
|
84
159
|
// So if there's only one filter, skip that part to avoid error
|
|
85
160
|
if (layer.filters.appliedFilters.length === 1) {
|
|
86
|
-
layer.filters.appliedFilters
|
|
87
|
-
filterExpr.push(filter.operator, ['get', filter.feature], filter.value);
|
|
88
|
-
});
|
|
161
|
+
filterExpr = buildCondition(layer.filters.appliedFilters[0]);
|
|
89
162
|
}
|
|
90
163
|
else {
|
|
91
|
-
filterExpr.push(layer.filters.logicalOp);
|
|
92
164
|
// Arguments for "Any" and 'All' need to be wrapped in brackets
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
filter.value
|
|
98
|
-
]);
|
|
99
|
-
});
|
|
165
|
+
filterExpr = [
|
|
166
|
+
layer.filters.logicalOp,
|
|
167
|
+
...layer.filters.appliedFilters.map(buildCondition)
|
|
168
|
+
];
|
|
100
169
|
}
|
|
101
170
|
layerStyle.filter = filterExpr;
|
|
102
171
|
}
|
|
@@ -154,16 +223,64 @@ export class MainView extends React.Component {
|
|
|
154
223
|
const scaled = ['*', 255, cosIncidence];
|
|
155
224
|
return scaled;
|
|
156
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
|
+
};
|
|
157
270
|
this._onClientSharedStateChanged = (sender, clients) => {
|
|
158
|
-
var _a, _b, _c
|
|
159
|
-
const
|
|
271
|
+
var _a, _b, _c;
|
|
272
|
+
const localState = this._model.localState;
|
|
273
|
+
if (!localState) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const remoteUser = localState.remoteUser;
|
|
160
277
|
// If we are in following mode, we update our position and selection
|
|
161
278
|
if (remoteUser) {
|
|
162
279
|
const remoteState = clients.get(remoteUser);
|
|
163
280
|
if (!remoteState) {
|
|
164
281
|
return;
|
|
165
282
|
}
|
|
166
|
-
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)) {
|
|
167
284
|
this.setState(old => (Object.assign(Object.assign({}, old), { remoteUser: remoteState.user })));
|
|
168
285
|
}
|
|
169
286
|
const remoteViewport = remoteState.viewportState;
|
|
@@ -177,7 +294,7 @@ export class MainView extends React.Component {
|
|
|
177
294
|
// If we are unfollowing a remote user, we reset our center and zoom to their previous values
|
|
178
295
|
if (this.state.remoteUser !== null) {
|
|
179
296
|
this.setState(old => (Object.assign(Object.assign({}, old), { remoteUser: null })));
|
|
180
|
-
const viewportState = (
|
|
297
|
+
const viewportState = (_c = localState.viewportState) === null || _c === void 0 ? void 0 : _c.value;
|
|
181
298
|
if (viewportState) {
|
|
182
299
|
this._moveToPosition(viewportState.coordinates, viewportState.zoom);
|
|
183
300
|
}
|
|
@@ -220,6 +337,13 @@ export class MainView extends React.Component {
|
|
|
220
337
|
}
|
|
221
338
|
this.setState(old => (Object.assign(Object.assign({}, old), { clientPointers: clientPointers })));
|
|
222
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
|
+
}
|
|
223
347
|
};
|
|
224
348
|
this._onSharedModelStateChange = (_, change) => {
|
|
225
349
|
var _a;
|
|
@@ -274,12 +398,11 @@ export class MainView extends React.Component {
|
|
|
274
398
|
this._handleWindowResize = () => {
|
|
275
399
|
// TODO SOMETHING
|
|
276
400
|
};
|
|
277
|
-
this.
|
|
401
|
+
this._isPositionInitialized = false;
|
|
278
402
|
this.divRef = React.createRef(); // Reference of render div
|
|
279
403
|
this._ready = false;
|
|
280
404
|
this._sourceToLayerMap = new Map();
|
|
281
|
-
|
|
282
|
-
register(proj4);
|
|
405
|
+
this._originalFeatures = {};
|
|
283
406
|
this._mainViewModel = this.props.viewModel;
|
|
284
407
|
this._mainViewModel.viewSettingChanged.connect(this._onViewChanged, this);
|
|
285
408
|
this._model = this._mainViewModel.jGISModel;
|
|
@@ -290,17 +413,26 @@ export class MainView extends React.Component {
|
|
|
290
413
|
this._model.sharedLayerTreeChanged.connect(this._onLayerTreeChange, this);
|
|
291
414
|
this._model.sharedSourcesChanged.connect(this._onSourcesChange, this);
|
|
292
415
|
this._model.sharedModel.changed.connect(this._onSharedModelStateChange);
|
|
293
|
-
this.
|
|
294
|
-
this._model.
|
|
416
|
+
this._model.sharedMetadataChanged.connect(this._onSharedMetadataChanged, this);
|
|
417
|
+
this._model.zoomToPositionSignal.connect(this._onZoomToPosition, this);
|
|
418
|
+
this._model.updateLayerSignal.connect(this._triggerLayerUpdate, this);
|
|
419
|
+
this._model.addFeatureAsMsSignal.connect(this._convertFeatureToMs, this);
|
|
295
420
|
this.state = {
|
|
296
421
|
id: this._mainViewModel.id,
|
|
297
422
|
lightTheme: isLightTheme(),
|
|
298
423
|
loading: true,
|
|
299
424
|
firstLoad: true,
|
|
300
425
|
annotations: {},
|
|
301
|
-
clientPointers: {}
|
|
426
|
+
clientPointers: {},
|
|
427
|
+
viewProjection: { code: '', units: '' },
|
|
428
|
+
loadingLayer: false,
|
|
429
|
+
scale: 0,
|
|
430
|
+
loadingErrors: [],
|
|
431
|
+
displayTemporalController: false,
|
|
432
|
+
filterStates: {}
|
|
302
433
|
};
|
|
303
434
|
this._sources = [];
|
|
435
|
+
this._loadingLayers = new Set();
|
|
304
436
|
this._commands = new CommandRegistry();
|
|
305
437
|
this._contextMenu = new ContextMenu({ commands: this._commands });
|
|
306
438
|
}
|
|
@@ -364,43 +496,7 @@ export class MainView extends React.Component {
|
|
|
364
496
|
this._model.addLayer(layerId, layerModel);
|
|
365
497
|
});
|
|
366
498
|
this._Map.addInteraction(dragAndDropInteraction);
|
|
367
|
-
|
|
368
|
-
hitTolerance: 5,
|
|
369
|
-
multi: true,
|
|
370
|
-
layers: layer => {
|
|
371
|
-
var _a, _b;
|
|
372
|
-
const localState = (_a = this._model) === null || _a === void 0 ? void 0 : _a.sharedModel.awareness.getLocalState();
|
|
373
|
-
const selectedLayers = (_b = localState === null || localState === void 0 ? void 0 : localState.selected) === null || _b === void 0 ? void 0 : _b.value;
|
|
374
|
-
if (!selectedLayers) {
|
|
375
|
-
return false;
|
|
376
|
-
}
|
|
377
|
-
const selectedLayerId = Object.keys(selectedLayers)[0];
|
|
378
|
-
return layer === this.getLayer(selectedLayerId);
|
|
379
|
-
},
|
|
380
|
-
condition: (event) => {
|
|
381
|
-
return singleClick(event) && this._model.isIdentifying;
|
|
382
|
-
},
|
|
383
|
-
style: new Style({
|
|
384
|
-
image: new Circle({
|
|
385
|
-
radius: 5,
|
|
386
|
-
fill: new Fill({
|
|
387
|
-
color: '#C52707'
|
|
388
|
-
}),
|
|
389
|
-
stroke: new Stroke({
|
|
390
|
-
color: '#171717',
|
|
391
|
-
width: 2
|
|
392
|
-
})
|
|
393
|
-
})
|
|
394
|
-
})
|
|
395
|
-
});
|
|
396
|
-
selectInteraction.on('select', event => {
|
|
397
|
-
const identifiedFeatures = [];
|
|
398
|
-
selectInteraction.getFeatures().forEach(feature => {
|
|
399
|
-
identifiedFeatures.push(feature.getProperties());
|
|
400
|
-
});
|
|
401
|
-
this._model.syncIdentifiedFeatures(identifiedFeatures, this._mainViewModel.id);
|
|
402
|
-
});
|
|
403
|
-
this._Map.addInteraction(selectInteraction);
|
|
499
|
+
this.createSelectInteraction();
|
|
404
500
|
const view = this._Map.getView();
|
|
405
501
|
// TODO: Note for the future, will need to update listeners if view changes
|
|
406
502
|
view.on('change:center', throttle(() => {
|
|
@@ -423,9 +519,6 @@ export class MainView extends React.Component {
|
|
|
423
519
|
}
|
|
424
520
|
});
|
|
425
521
|
this._Map.on('moveend', () => {
|
|
426
|
-
if (!this._initializedPosition) {
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
522
|
const currentOptions = this._model.getOptions();
|
|
430
523
|
const view = this._Map.getView();
|
|
431
524
|
const center = view.getCenter() || [0, 0];
|
|
@@ -433,6 +526,7 @@ export class MainView extends React.Component {
|
|
|
433
526
|
const projection = view.getProjection();
|
|
434
527
|
const latLng = toLonLat(center, projection);
|
|
435
528
|
const bearing = view.getRotation();
|
|
529
|
+
const resolution = view.getResolution();
|
|
436
530
|
const updatedOptions = {
|
|
437
531
|
latitude: latLng[1],
|
|
438
532
|
longitude: latLng[0],
|
|
@@ -442,6 +536,14 @@ export class MainView extends React.Component {
|
|
|
442
536
|
};
|
|
443
537
|
updatedOptions.extent = view.calculateExtent();
|
|
444
538
|
this._model.setOptions(Object.assign(Object.assign({}, currentOptions), updatedOptions));
|
|
539
|
+
// Calculate scale
|
|
540
|
+
if (resolution) {
|
|
541
|
+
// DPI and inches per meter values taken from OpenLayers
|
|
542
|
+
const dpi = 25.4 / 0.28;
|
|
543
|
+
const inchesPerMeter = 1000 / 25.4;
|
|
544
|
+
const scale = resolution * inchesPerMeter * dpi;
|
|
545
|
+
this.setState(old => (Object.assign(Object.assign({}, old), { scale })));
|
|
546
|
+
}
|
|
445
547
|
});
|
|
446
548
|
this._Map.on('click', this._identifyFeature.bind(this));
|
|
447
549
|
this._Map
|
|
@@ -451,7 +553,6 @@ export class MainView extends React.Component {
|
|
|
451
553
|
await this._updateLayersImpl(JupyterGISModel.getOrderedLayerIds(this._model));
|
|
452
554
|
const options = this._model.getOptions();
|
|
453
555
|
this.updateOptions(options);
|
|
454
|
-
this._initializedPosition = true;
|
|
455
556
|
}
|
|
456
557
|
this._Map.getViewport().addEventListener('contextmenu', event => {
|
|
457
558
|
event.preventDefault();
|
|
@@ -460,25 +561,12 @@ export class MainView extends React.Component {
|
|
|
460
561
|
this._clickCoords = coordinate;
|
|
461
562
|
this._contextMenu.open(event);
|
|
462
563
|
});
|
|
463
|
-
this.setState(old => (Object.assign(Object.assign({}, old), { loading: false
|
|
564
|
+
this.setState(old => (Object.assign(Object.assign({}, old), { loading: false, viewProjection: {
|
|
565
|
+
code: view.getProjection().getCode(),
|
|
566
|
+
units: view.getProjection().getUnits()
|
|
567
|
+
} })));
|
|
464
568
|
}
|
|
465
569
|
}
|
|
466
|
-
async _loadShapefileAsGeoJSON(url) {
|
|
467
|
-
try {
|
|
468
|
-
const response = await fetch(`/jupytergis_core/proxy?url=${url}`);
|
|
469
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
470
|
-
const geojson = await shp(arrayBuffer);
|
|
471
|
-
return geojson;
|
|
472
|
-
}
|
|
473
|
-
catch (error) {
|
|
474
|
-
console.error('Error loading shapefile:', error);
|
|
475
|
-
throw error;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
async _loadGeoTIFFWithCache(sourceInfo) {
|
|
479
|
-
const result = await loadGeoTIFFWithCache(sourceInfo);
|
|
480
|
-
return result === null || result === void 0 ? void 0 : result.file;
|
|
481
|
-
}
|
|
482
570
|
/**
|
|
483
571
|
* Add a source in the map.
|
|
484
572
|
*
|
|
@@ -529,7 +617,7 @@ export class MainView extends React.Component {
|
|
|
529
617
|
minZoom: sourceParameters.minZoom,
|
|
530
618
|
maxZoom: sourceParameters.maxZoom,
|
|
531
619
|
url: url,
|
|
532
|
-
format: new MVT({ featureClass:
|
|
620
|
+
format: new MVT({ featureClass: RenderFeature })
|
|
533
621
|
});
|
|
534
622
|
}
|
|
535
623
|
else {
|
|
@@ -542,7 +630,11 @@ export class MainView extends React.Component {
|
|
|
542
630
|
}
|
|
543
631
|
case 'GeoJSONSource': {
|
|
544
632
|
const data = ((_a = source.parameters) === null || _a === void 0 ? void 0 : _a.data) ||
|
|
545
|
-
(await
|
|
633
|
+
(await loadFile({
|
|
634
|
+
filepath: (_b = source.parameters) === null || _b === void 0 ? void 0 : _b.path,
|
|
635
|
+
type: 'GeoJSONSource',
|
|
636
|
+
model: this._model
|
|
637
|
+
}));
|
|
546
638
|
const format = new GeoJSON({
|
|
547
639
|
featureProjection: this._Map.getView().getProjection()
|
|
548
640
|
});
|
|
@@ -562,7 +654,11 @@ export class MainView extends React.Component {
|
|
|
562
654
|
}
|
|
563
655
|
case 'ShapefileSource': {
|
|
564
656
|
const parameters = source.parameters;
|
|
565
|
-
const geojson = await
|
|
657
|
+
const geojson = await loadFile({
|
|
658
|
+
filepath: parameters.path,
|
|
659
|
+
type: 'ShapefileSource',
|
|
660
|
+
model: this._model
|
|
661
|
+
});
|
|
566
662
|
const geojsonData = Array.isArray(geojson) ? geojson[0] : geojson;
|
|
567
663
|
const format = new GeoJSON();
|
|
568
664
|
newSource = new VectorSource({
|
|
@@ -590,10 +686,15 @@ export class MainView extends React.Component {
|
|
|
590
686
|
const maxX = bottomRight[0];
|
|
591
687
|
const minY = bottomRight[1];
|
|
592
688
|
const extent = [minX, minY, maxX, maxY];
|
|
689
|
+
const imageUrl = await loadFile({
|
|
690
|
+
filepath: sourceParameters.path,
|
|
691
|
+
type: 'ImageSource',
|
|
692
|
+
model: this._model
|
|
693
|
+
});
|
|
593
694
|
newSource = new Static({
|
|
594
695
|
imageExtent: extent,
|
|
595
|
-
url:
|
|
596
|
-
interpolate:
|
|
696
|
+
url: imageUrl,
|
|
697
|
+
interpolate: false,
|
|
597
698
|
crossOrigin: ''
|
|
598
699
|
});
|
|
599
700
|
break;
|
|
@@ -608,8 +709,13 @@ export class MainView extends React.Component {
|
|
|
608
709
|
return Object.assign(Object.assign({}, url), { nodata: 0 });
|
|
609
710
|
};
|
|
610
711
|
const sourcesWithBlobs = await Promise.all(sourceParameters.urls.map(async (sourceInfo) => {
|
|
611
|
-
|
|
612
|
-
|
|
712
|
+
var _a;
|
|
713
|
+
const geotiff = await loadFile({
|
|
714
|
+
filepath: (_a = sourceInfo.url) !== null && _a !== void 0 ? _a : '',
|
|
715
|
+
type: 'GeoTiffSource',
|
|
716
|
+
model: this._model
|
|
717
|
+
});
|
|
718
|
+
return Object.assign(Object.assign({}, addNoData(sourceInfo)), { geotiff, url: URL.createObjectURL(geotiff.file) });
|
|
613
719
|
}));
|
|
614
720
|
newSource = new GeoTIFFSource({
|
|
615
721
|
sources: sourcesWithBlobs,
|
|
@@ -725,15 +831,18 @@ export class MainView extends React.Component {
|
|
|
725
831
|
* @returns - the map layer.
|
|
726
832
|
*/
|
|
727
833
|
async _buildMapLayer(id, layer) {
|
|
728
|
-
var _a;
|
|
834
|
+
var _a, _b, _c;
|
|
729
835
|
const sourceId = (_a = layer.parameters) === null || _a === void 0 ? void 0 : _a.source;
|
|
730
836
|
const source = this._model.sharedModel.getLayerSource(sourceId);
|
|
731
837
|
if (!source) {
|
|
732
838
|
return;
|
|
733
839
|
}
|
|
840
|
+
this.setState(old => (Object.assign(Object.assign({}, old), { loadingLayer: true })));
|
|
841
|
+
this._loadingLayers.add(id);
|
|
734
842
|
if (!this._sources[sourceId]) {
|
|
735
843
|
await this.addSource(sourceId, source, id);
|
|
736
844
|
}
|
|
845
|
+
this._loadingLayers.add(id);
|
|
737
846
|
let newMapLayer;
|
|
738
847
|
let layerParameters;
|
|
739
848
|
// TODO: OpenLayers provides a bunch of sources for specific tile
|
|
@@ -764,7 +873,6 @@ export class MainView extends React.Component {
|
|
|
764
873
|
opacity: layerParameters.opacity,
|
|
765
874
|
source: this._sources[layerParameters.source]
|
|
766
875
|
});
|
|
767
|
-
this.updateLayer(id, layer, newMapLayer);
|
|
768
876
|
break;
|
|
769
877
|
}
|
|
770
878
|
case 'HillshadeLayer': {
|
|
@@ -799,13 +907,52 @@ export class MainView extends React.Component {
|
|
|
799
907
|
newMapLayer = new WebGlTileLayer(layerOptions);
|
|
800
908
|
break;
|
|
801
909
|
}
|
|
910
|
+
case 'HeatmapLayer': {
|
|
911
|
+
layerParameters = layer.parameters;
|
|
912
|
+
newMapLayer = new HeatmapLayer({
|
|
913
|
+
opacity: layerParameters.opacity,
|
|
914
|
+
source: this._sources[layerParameters.source],
|
|
915
|
+
blur: (_b = layerParameters.blur) !== null && _b !== void 0 ? _b : 15,
|
|
916
|
+
radius: (_c = layerParameters.radius) !== null && _c !== void 0 ? _c : 8,
|
|
917
|
+
gradient: layerParameters.color
|
|
918
|
+
});
|
|
919
|
+
break;
|
|
920
|
+
}
|
|
802
921
|
}
|
|
922
|
+
await this._waitForSourceReady(newMapLayer);
|
|
803
923
|
// OpenLayers doesn't have name/id field so add it
|
|
804
924
|
newMapLayer.set('id', id);
|
|
805
925
|
// we need to keep track of which source has which layers
|
|
806
926
|
this._sourceToLayerMap.set(layerParameters.source, id);
|
|
927
|
+
this.addProjection(newMapLayer);
|
|
928
|
+
this._loadingLayers.delete(id);
|
|
807
929
|
return newMapLayer;
|
|
808
930
|
}
|
|
931
|
+
addProjection(newMapLayer) {
|
|
932
|
+
var _a;
|
|
933
|
+
const sourceProjection = (_a = newMapLayer.getSource()) === null || _a === void 0 ? void 0 : _a.getProjection();
|
|
934
|
+
if (!sourceProjection) {
|
|
935
|
+
console.warn('Layer source projection is undefined or invalid');
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
const projectionCode = sourceProjection.getCode();
|
|
939
|
+
const isProjectionRegistered = getRegisteredProjection(projectionCode);
|
|
940
|
+
if (!isProjectionRegistered) {
|
|
941
|
+
// Check if the projection exists in proj4list
|
|
942
|
+
if (!proj4list[projectionCode]) {
|
|
943
|
+
console.warn(`Projection code '${projectionCode}' not found in proj4list`);
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
try {
|
|
947
|
+
proj4.defs([proj4list[projectionCode]]);
|
|
948
|
+
register(proj4);
|
|
949
|
+
}
|
|
950
|
+
catch (error) {
|
|
951
|
+
console.warn(`Failed to register projection '${projectionCode}'. Error: ${error.message}`);
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
809
956
|
/**
|
|
810
957
|
* Add a layer to the map.
|
|
811
958
|
*
|
|
@@ -818,9 +965,29 @@ export class MainView extends React.Component {
|
|
|
818
965
|
// Layer already exists
|
|
819
966
|
return;
|
|
820
967
|
}
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
968
|
+
try {
|
|
969
|
+
const newMapLayer = await this._buildMapLayer(id, layer);
|
|
970
|
+
if (newMapLayer !== undefined) {
|
|
971
|
+
await this._waitForReady();
|
|
972
|
+
// Adjust index to ensure it's within bounds
|
|
973
|
+
const numLayers = this._Map.getLayers().getLength();
|
|
974
|
+
const safeIndex = Math.min(index, numLayers);
|
|
975
|
+
this._Map.getLayers().insertAt(safeIndex, newMapLayer);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
catch (error) {
|
|
979
|
+
if (this.state.loadingErrors.find(item => item.id === id && item.error === error.message)) {
|
|
980
|
+
this._loadingLayers.delete(id);
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
await showErrorMessage(`Error Adding ${layer.name}`, `Failed to add ${layer.name}: ${error.message || 'invalid file path'}`);
|
|
984
|
+
this.setState(old => (Object.assign(Object.assign({}, old), { loadingLayer: false })));
|
|
985
|
+
this.state.loadingErrors.push({
|
|
986
|
+
id,
|
|
987
|
+
error: error.message || 'invalid file path',
|
|
988
|
+
index
|
|
989
|
+
});
|
|
990
|
+
this._loadingLayers.delete(id);
|
|
824
991
|
}
|
|
825
992
|
}
|
|
826
993
|
/**
|
|
@@ -829,8 +996,8 @@ export class MainView extends React.Component {
|
|
|
829
996
|
* @param id - id of the layer.
|
|
830
997
|
* @param layer - the layer object.
|
|
831
998
|
*/
|
|
832
|
-
async updateLayer(id, layer, mapLayer) {
|
|
833
|
-
var _a, _b, _c, _d;
|
|
999
|
+
async updateLayer(id, layer, mapLayer, oldLayer) {
|
|
1000
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
834
1001
|
const sourceId = (_a = layer.parameters) === null || _a === void 0 ? void 0 : _a.source;
|
|
835
1002
|
const source = this._model.sharedModel.getLayerSource(sourceId);
|
|
836
1003
|
if (!source) {
|
|
@@ -873,8 +1040,58 @@ export class MainView extends React.Component {
|
|
|
873
1040
|
}
|
|
874
1041
|
break;
|
|
875
1042
|
}
|
|
1043
|
+
case 'HeatmapLayer': {
|
|
1044
|
+
const layerParams = layer.parameters;
|
|
1045
|
+
const heatmap = mapLayer;
|
|
1046
|
+
heatmap.setOpacity((_e = layerParams.opacity) !== null && _e !== void 0 ? _e : 1);
|
|
1047
|
+
heatmap.setBlur((_f = layerParams.blur) !== null && _f !== void 0 ? _f : 15);
|
|
1048
|
+
heatmap.setRadius((_g = layerParams.radius) !== null && _g !== void 0 ? _g : 8);
|
|
1049
|
+
heatmap.setGradient((_h = layerParams.color) !== null && _h !== void 0 ? _h : ['#00f', '#0ff', '#0f0', '#ff0', '#f00']);
|
|
1050
|
+
this.handleTemporalController(id, layer);
|
|
1051
|
+
break;
|
|
1052
|
+
}
|
|
876
1053
|
}
|
|
877
1054
|
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Wait for all layers to be loaded.
|
|
1057
|
+
*/
|
|
1058
|
+
_waitForReady() {
|
|
1059
|
+
return new Promise(resolve => {
|
|
1060
|
+
const checkReady = () => {
|
|
1061
|
+
if (this._loadingLayers.size === 0) {
|
|
1062
|
+
this.setState(old => (Object.assign(Object.assign({}, old), { loadingLayer: false })));
|
|
1063
|
+
resolve();
|
|
1064
|
+
}
|
|
1065
|
+
else {
|
|
1066
|
+
setTimeout(checkReady, 50);
|
|
1067
|
+
}
|
|
1068
|
+
};
|
|
1069
|
+
checkReady();
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Wait for a layers source state to be 'ready'
|
|
1074
|
+
* @param layer The Layer to check
|
|
1075
|
+
*/
|
|
1076
|
+
_waitForSourceReady(layer) {
|
|
1077
|
+
return new Promise((resolve, reject) => {
|
|
1078
|
+
const checkState = () => {
|
|
1079
|
+
const state = layer.getSourceState();
|
|
1080
|
+
if (state === 'ready') {
|
|
1081
|
+
layer.un('change', checkState);
|
|
1082
|
+
resolve();
|
|
1083
|
+
}
|
|
1084
|
+
else if (state === 'error') {
|
|
1085
|
+
layer.un('change', checkState);
|
|
1086
|
+
reject(new Error('Source failed to load.'));
|
|
1087
|
+
}
|
|
1088
|
+
};
|
|
1089
|
+
// Listen for state changes
|
|
1090
|
+
layer.on('change', checkState);
|
|
1091
|
+
// Check the state immediately in case it's already 'ready'
|
|
1092
|
+
checkState();
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
878
1095
|
/**
|
|
879
1096
|
* Remove a layer from the map.
|
|
880
1097
|
*
|
|
@@ -886,11 +1103,11 @@ export class MainView extends React.Component {
|
|
|
886
1103
|
this._Map.removeLayer(mapLayer);
|
|
887
1104
|
}
|
|
888
1105
|
}
|
|
889
|
-
_onSharedOptionsChanged(
|
|
890
|
-
if (!this.
|
|
1106
|
+
_onSharedOptionsChanged() {
|
|
1107
|
+
if (!this._isPositionInitialized) {
|
|
891
1108
|
const options = this._model.getOptions();
|
|
892
1109
|
this.updateOptions(options);
|
|
893
|
-
this.
|
|
1110
|
+
this._isPositionInitialized = true;
|
|
894
1111
|
}
|
|
895
1112
|
}
|
|
896
1113
|
async updateOptions(options) {
|
|
@@ -977,7 +1194,20 @@ export class MainView extends React.Component {
|
|
|
977
1194
|
if (currentIndex < index) {
|
|
978
1195
|
nextIndex -= 1;
|
|
979
1196
|
}
|
|
980
|
-
|
|
1197
|
+
// Adjust index to ensure it's within bounds
|
|
1198
|
+
const numLayers = this._Map.getLayers().getLength();
|
|
1199
|
+
const safeIndex = Math.min(index, numLayers);
|
|
1200
|
+
this._Map.getLayers().insertAt(safeIndex, layer);
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Remove and recreate layer
|
|
1204
|
+
* @param id ID of layer being replaced
|
|
1205
|
+
* @param layer New layer to replace with
|
|
1206
|
+
*/
|
|
1207
|
+
replaceLayer(id, layer) {
|
|
1208
|
+
const layerIndex = this.getLayerIndex(id);
|
|
1209
|
+
this.removeLayer(id);
|
|
1210
|
+
this.addLayer(id, layer, layerIndex);
|
|
981
1211
|
}
|
|
982
1212
|
_onLayersChanged(_, change) {
|
|
983
1213
|
var _a;
|
|
@@ -987,21 +1217,25 @@ export class MainView extends React.Component {
|
|
|
987
1217
|
return;
|
|
988
1218
|
}
|
|
989
1219
|
(_a = change.layerChange) === null || _a === void 0 ? void 0 : _a.forEach(change => {
|
|
990
|
-
const
|
|
991
|
-
if (!
|
|
992
|
-
this.removeLayer(
|
|
1220
|
+
const { id, oldValue: oldLayer, newValue: newLayer } = change;
|
|
1221
|
+
if (!newLayer || Object.keys(newLayer).length === 0) {
|
|
1222
|
+
this.removeLayer(id);
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
if (oldLayer && oldLayer.type !== newLayer.type) {
|
|
1226
|
+
this.replaceLayer(id, newLayer);
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
const mapLayer = this.getLayer(id);
|
|
1230
|
+
const layerTree = JupyterGISModel.getOrderedLayerIds(this._model);
|
|
1231
|
+
if (!mapLayer) {
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
if (layerTree.includes(id)) {
|
|
1235
|
+
this.updateLayer(id, newLayer, mapLayer, oldLayer);
|
|
993
1236
|
}
|
|
994
1237
|
else {
|
|
995
|
-
|
|
996
|
-
const layerTree = JupyterGISModel.getOrderedLayerIds(this._model);
|
|
997
|
-
if (mapLayer) {
|
|
998
|
-
if (layerTree.includes(change.id)) {
|
|
999
|
-
this.updateLayer(change.id, layer, mapLayer);
|
|
1000
|
-
}
|
|
1001
|
-
else {
|
|
1002
|
-
this.updateLayers(layerTree);
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1238
|
+
this.updateLayers(layerTree);
|
|
1005
1239
|
}
|
|
1006
1240
|
});
|
|
1007
1241
|
}
|
|
@@ -1051,12 +1285,40 @@ export class MainView extends React.Component {
|
|
|
1051
1285
|
}
|
|
1052
1286
|
});
|
|
1053
1287
|
}
|
|
1054
|
-
|
|
1288
|
+
_onZoomToPosition(_, id) {
|
|
1055
1289
|
var _a;
|
|
1290
|
+
// Check if the id is an annotation
|
|
1056
1291
|
const annotation = (_a = this._model.annotationModel) === null || _a === void 0 ? void 0 : _a.getAnnotation(id);
|
|
1057
1292
|
if (annotation) {
|
|
1058
1293
|
this._moveToPosition(annotation.position, annotation.zoom);
|
|
1294
|
+
return;
|
|
1059
1295
|
}
|
|
1296
|
+
// The id is a layer
|
|
1297
|
+
let extent;
|
|
1298
|
+
const layer = this.getLayer(id);
|
|
1299
|
+
const source = layer === null || layer === void 0 ? void 0 : layer.getSource();
|
|
1300
|
+
if (source instanceof VectorSource) {
|
|
1301
|
+
extent = source.getExtent();
|
|
1302
|
+
}
|
|
1303
|
+
if (source instanceof TileSource) {
|
|
1304
|
+
// Tiled sources don't have getExtent() so we get it from the grid
|
|
1305
|
+
const tileGrid = source.getTileGrid();
|
|
1306
|
+
extent = tileGrid === null || tileGrid === void 0 ? void 0 : tileGrid.getExtent();
|
|
1307
|
+
}
|
|
1308
|
+
if (!extent) {
|
|
1309
|
+
console.warn('Layer has no extent.');
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
// Convert layer extent value to view projection if needed
|
|
1313
|
+
const sourceProjection = source === null || source === void 0 ? void 0 : source.getProjection();
|
|
1314
|
+
const viewProjection = this._Map.getView().getProjection();
|
|
1315
|
+
const transformedExtent = sourceProjection && sourceProjection !== viewProjection
|
|
1316
|
+
? transformExtent(extent, sourceProjection, viewProjection)
|
|
1317
|
+
: extent;
|
|
1318
|
+
this._Map.getView().fit(transformedExtent, {
|
|
1319
|
+
size: this._Map.getSize(),
|
|
1320
|
+
duration: 500
|
|
1321
|
+
});
|
|
1060
1322
|
}
|
|
1061
1323
|
_moveToPosition(center, zoom, duration = 1000) {
|
|
1062
1324
|
const view = this._Map.getView();
|
|
@@ -1108,6 +1370,28 @@ export class MainView extends React.Component {
|
|
|
1108
1370
|
}
|
|
1109
1371
|
}
|
|
1110
1372
|
}
|
|
1373
|
+
_triggerLayerUpdate(_, args) {
|
|
1374
|
+
// ? could send just the filters object and modify that instead of emitting whole layer
|
|
1375
|
+
const json = JSON.parse(args);
|
|
1376
|
+
const { layerId, layer: jgisLayer } = json;
|
|
1377
|
+
const olLayer = this.getLayer(layerId);
|
|
1378
|
+
if (!jgisLayer || !olLayer) {
|
|
1379
|
+
console.log('Layer not found');
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
this.updateLayer(layerId, jgisLayer, olLayer);
|
|
1383
|
+
}
|
|
1384
|
+
_convertFeatureToMs(_, args) {
|
|
1385
|
+
const json = JSON.parse(args);
|
|
1386
|
+
const { id: layerId, selectedFeature } = json;
|
|
1387
|
+
const olLayer = this.getLayer(layerId);
|
|
1388
|
+
const source = olLayer.getSource();
|
|
1389
|
+
source.forEachFeature(feature => {
|
|
1390
|
+
const time = feature.get(selectedFeature);
|
|
1391
|
+
const parsedTime = typeof time === 'string' ? Date.parse(time) : time;
|
|
1392
|
+
feature.set(`${selectedFeature}ms`, parsedTime);
|
|
1393
|
+
});
|
|
1394
|
+
}
|
|
1111
1395
|
render() {
|
|
1112
1396
|
return (React.createElement(React.Fragment, null,
|
|
1113
1397
|
Object.entries(this.state.annotations).map(([key, annotation]) => {
|
|
@@ -1121,17 +1405,20 @@ export class MainView extends React.Component {
|
|
|
1121
1405
|
}, className: 'jGIS-Popup-Wrapper' },
|
|
1122
1406
|
React.createElement(AnnotationFloater, { itemId: key, annotationModel: this._model.annotationModel, open: false }))));
|
|
1123
1407
|
}),
|
|
1124
|
-
React.createElement("div", { className: "jGIS-Mainview",
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
:
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1408
|
+
React.createElement("div", { className: "jGIS-Mainview-Container" },
|
|
1409
|
+
this.state.displayTemporalController && (React.createElement(TemporalSlider, { model: this._model, filterStates: this.state.filterStates })),
|
|
1410
|
+
React.createElement("div", { className: "jGIS-Mainview", style: {
|
|
1411
|
+
border: this.state.remoteUser
|
|
1412
|
+
? `solid 3px ${this.state.remoteUser.color}`
|
|
1413
|
+
: 'unset'
|
|
1414
|
+
} },
|
|
1415
|
+
React.createElement(Spinner, { loading: this.state.loading }),
|
|
1416
|
+
React.createElement(FollowIndicator, { remoteUser: this.state.remoteUser }),
|
|
1417
|
+
React.createElement(CollaboratorPointers, { clients: this.state.clientPointers }),
|
|
1418
|
+
React.createElement("div", { ref: this.divRef, style: {
|
|
1419
|
+
width: '100%',
|
|
1420
|
+
height: '100%'
|
|
1421
|
+
} })),
|
|
1422
|
+
React.createElement(StatusBar, { jgisModel: this._model, loading: this.state.loadingLayer, projection: this.state.viewProjection, scale: this.state.scale }))));
|
|
1136
1423
|
}
|
|
1137
1424
|
}
|