@jupytergis/base 0.1.6 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/annotations/components/Annotation.d.ts +11 -0
- package/lib/annotations/components/Annotation.js +61 -0
- package/lib/annotations/components/AnnotationFloater.d.ts +7 -0
- package/lib/annotations/components/AnnotationFloater.js +30 -0
- package/lib/annotations/components/Message.d.ts +8 -0
- package/lib/annotations/components/Message.js +17 -0
- package/lib/annotations/index.d.ts +3 -0
- package/lib/annotations/index.js +3 -0
- package/lib/annotations/model.d.ts +28 -0
- package/lib/annotations/model.js +67 -0
- package/lib/classificationModes.d.ts +13 -0
- package/lib/classificationModes.js +326 -0
- package/lib/commands.js +52 -7
- package/lib/constants.d.ts +2 -0
- package/lib/constants.js +5 -1
- package/lib/dialogs/symbology/classificationModes.d.ts +13 -0
- package/lib/dialogs/symbology/classificationModes.js +326 -0
- package/lib/dialogs/symbology/components/color_ramp/CanvasSelectComponent.d.ts +11 -0
- package/lib/dialogs/symbology/components/color_ramp/CanvasSelectComponent.js +119 -0
- package/lib/dialogs/symbology/components/color_ramp/ColorRamp.d.ts +15 -0
- package/lib/dialogs/symbology/components/color_ramp/ColorRamp.js +33 -0
- package/lib/dialogs/symbology/components/color_ramp/ColorRampEntry.d.ts +9 -0
- package/lib/dialogs/symbology/components/color_ramp/ColorRampEntry.js +24 -0
- package/lib/dialogs/symbology/components/color_ramp/ModeSelectRow.d.ts +10 -0
- package/lib/dialogs/symbology/components/color_ramp/ModeSelectRow.js +11 -0
- package/lib/dialogs/symbology/components/color_stops/StopContainer.d.ts +9 -0
- package/lib/dialogs/symbology/components/color_stops/StopContainer.js +28 -0
- package/lib/dialogs/{components/symbology → symbology/components/color_stops}/StopRow.js +9 -2
- package/lib/dialogs/symbology/hooks/useGetProperties.d.ts +12 -0
- package/lib/dialogs/symbology/hooks/useGetProperties.js +47 -0
- package/lib/dialogs/{symbologyDialog.js → symbology/symbologyDialog.js} +3 -3
- package/lib/dialogs/symbology/symbologyUtils.d.ts +9 -0
- package/lib/dialogs/symbology/symbologyUtils.js +94 -0
- package/lib/dialogs/symbology/tiff_layer/TiffRendering.d.ts +4 -0
- package/lib/dialogs/{components/symbology/BandRendering.js → symbology/tiff_layer/TiffRendering.js} +3 -3
- package/lib/dialogs/{components/symbology → symbology/tiff_layer/components}/BandRow.d.ts +1 -1
- package/lib/dialogs/{components/symbology → symbology/tiff_layer/types}/SingleBandPseudoColor.d.ts +9 -1
- package/lib/dialogs/{components/symbology → symbology/tiff_layer/types}/SingleBandPseudoColor.js +131 -83
- package/lib/dialogs/{components/symbology → symbology/vector_layer}/VectorRendering.d.ts +1 -1
- package/lib/dialogs/{components/symbology → symbology/vector_layer}/VectorRendering.js +10 -13
- package/lib/dialogs/symbology/vector_layer/components/ValueSelect.d.ts +8 -0
- package/lib/dialogs/symbology/vector_layer/components/ValueSelect.js +7 -0
- package/lib/dialogs/symbology/vector_layer/types/Categorized.d.ts +4 -0
- package/lib/dialogs/symbology/vector_layer/types/Categorized.js +94 -0
- package/lib/dialogs/symbology/vector_layer/types/Graduated.js +169 -0
- package/lib/dialogs/{components/symbology → symbology/vector_layer/types}/SimpleSymbol.js +8 -13
- package/lib/formbuilder/formselectors.js +4 -0
- package/lib/formbuilder/objectform/baseform.d.ts +1 -1
- package/lib/formbuilder/objectform/baseform.js +31 -42
- package/lib/formbuilder/objectform/geojsonsource.js +33 -30
- package/lib/formbuilder/objectform/geotiffsource.d.ts +16 -0
- package/lib/formbuilder/objectform/geotiffsource.js +71 -0
- package/lib/formbuilder/objectform/vectorlayerform.js +1 -0
- package/lib/formbuilder/objectform/webGlLayerForm.js +1 -0
- package/lib/index.d.ts +7 -4
- package/lib/index.js +7 -4
- package/lib/mainview/CollaboratorPointers.d.ts +17 -0
- package/lib/mainview/CollaboratorPointers.js +37 -0
- package/lib/mainview/FollowIndicator.d.ts +7 -0
- package/lib/mainview/FollowIndicator.js +9 -0
- package/lib/mainview/mainView.d.ts +39 -3
- package/lib/mainview/mainView.js +451 -41
- package/lib/mainview/mainviewmodel.d.ts +2 -1
- package/lib/mainview/mainviewmodel.js +5 -0
- package/lib/panelview/annotationPanel.d.ts +27 -0
- package/lib/panelview/annotationPanel.js +45 -0
- package/lib/panelview/components/filter-panel/Filter.d.ts +7 -2
- package/lib/panelview/components/filter-panel/Filter.js +1 -1
- package/lib/panelview/components/filter-panel/FilterRow.js +3 -3
- package/lib/panelview/components/identify-panel/IdentifyPanel.d.ts +15 -0
- package/lib/panelview/components/identify-panel/IdentifyPanel.js +108 -0
- package/lib/panelview/components/layers.js +4 -4
- package/lib/panelview/leftpanel.js +8 -0
- package/lib/panelview/rightpanel.d.ts +4 -1
- package/lib/panelview/rightpanel.js +28 -7
- package/lib/store.d.ts +9 -0
- package/lib/store.js +25 -0
- package/lib/toolbar/widget.js +12 -2
- package/lib/tools.d.ts +35 -0
- package/lib/tools.js +86 -0
- package/lib/types.d.ts +14 -0
- package/package.json +18 -20
- package/style/base.css +4 -8
- package/style/dialog.css +1 -1
- package/style/icons/logo_mini.svg +70 -148
- package/style/icons/nonvisibility.svg +2 -7
- package/style/icons/visibility.svg +2 -6
- package/style/leftPanel.css +5 -0
- package/style/symbologyDialog.css +104 -3
- package/lib/dialogs/components/symbology/BandRendering.d.ts +0 -4
- package/lib/dialogs/components/symbology/Graduated.js +0 -188
- /package/lib/dialogs/{components/symbology → symbology/components/color_stops}/StopRow.d.ts +0 -0
- /package/lib/dialogs/{symbologyDialog.d.ts → symbology/symbologyDialog.d.ts} +0 -0
- /package/lib/dialogs/{components/symbology → symbology/tiff_layer/components}/BandRow.js +0 -0
- /package/lib/dialogs/{components/symbology → symbology/vector_layer/types}/Graduated.d.ts +0 -0
- /package/lib/dialogs/{components/symbology → symbology/vector_layer/types}/SimpleSymbol.d.ts +0 -0
package/lib/mainview/mainView.js
CHANGED
|
@@ -3,7 +3,7 @@ import { UUID } from '@lumino/coreutils';
|
|
|
3
3
|
import { Collection, Map as OlMap, View } from 'ol';
|
|
4
4
|
import { ScaleLine } from 'ol/control';
|
|
5
5
|
import { GeoJSON, MVT } from 'ol/format';
|
|
6
|
-
import DragAndDrop from 'ol/interaction
|
|
6
|
+
import { DragAndDrop, Select } from 'ol/interaction';
|
|
7
7
|
import { Image as ImageLayer, Vector as VectorLayer, VectorTile as VectorTileLayer, WebGLTile as WebGlTileLayer } from 'ol/layer';
|
|
8
8
|
import TileLayer from 'ol/layer/Tile';
|
|
9
9
|
import { fromLonLat, toLonLat } from 'ol/proj';
|
|
@@ -12,13 +12,52 @@ import { GeoTIFF as GeoTIFFSource, ImageTile as ImageTileSource, Vector as Vecto
|
|
|
12
12
|
import Static from 'ol/source/ImageStatic';
|
|
13
13
|
//@ts-expect-error no types for ol-pmtiles
|
|
14
14
|
import { PMTilesRasterSource, PMTilesVectorSource } from 'ol-pmtiles';
|
|
15
|
+
import { register } from 'ol/proj/proj4.js';
|
|
16
|
+
import { get as getProjection } from 'ol/proj.js';
|
|
17
|
+
import proj4 from 'proj4';
|
|
15
18
|
import * as React from 'react';
|
|
16
19
|
import shp from 'shpjs';
|
|
17
|
-
import { isLightTheme } from '../tools';
|
|
20
|
+
import { isLightTheme, loadGeoTIFFWithCache, throttle } from '../tools';
|
|
18
21
|
import { Spinner } from './spinner';
|
|
22
|
+
//@ts-expect-error no types for proj4-list
|
|
23
|
+
import proj4list from 'proj4-list';
|
|
24
|
+
import { ContextMenu } from '@lumino/widgets';
|
|
25
|
+
import { CommandRegistry } from '@lumino/commands';
|
|
26
|
+
import AnnotationFloater from '../annotations/components/AnnotationFloater';
|
|
27
|
+
import { CommandIDs } from '../constants';
|
|
28
|
+
import { FollowIndicator } from './FollowIndicator';
|
|
29
|
+
import CollaboratorPointers from './CollaboratorPointers';
|
|
30
|
+
import { Circle, Fill, Stroke, Style } from 'ol/style';
|
|
31
|
+
import { singleClick } from 'ol/events/condition';
|
|
19
32
|
export class MainView extends React.Component {
|
|
20
33
|
constructor(props) {
|
|
21
34
|
super(props);
|
|
35
|
+
this.addContextMenu = () => {
|
|
36
|
+
this._commands.addCommand(CommandIDs.addAnnotation, {
|
|
37
|
+
execute: () => {
|
|
38
|
+
var _a;
|
|
39
|
+
if (!this._Map) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
this._mainViewModel.addAnnotation({
|
|
43
|
+
position: { x: this._clickCoords[0], y: this._clickCoords[1] },
|
|
44
|
+
zoom: (_a = this._Map.getView().getZoom()) !== null && _a !== void 0 ? _a : 0,
|
|
45
|
+
label: 'New annotation',
|
|
46
|
+
contents: [],
|
|
47
|
+
parent: this._Map.getViewport().id
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
label: 'Add annotation',
|
|
51
|
+
isEnabled: () => {
|
|
52
|
+
return !!this._Map;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
this._contextMenu.addItem({
|
|
56
|
+
command: CommandIDs.addAnnotation,
|
|
57
|
+
selector: '.ol-viewport',
|
|
58
|
+
rank: 1
|
|
59
|
+
});
|
|
60
|
+
};
|
|
22
61
|
this.vectorLayerStyleRuleBuilder = (layer) => {
|
|
23
62
|
const layerParams = layer.parameters;
|
|
24
63
|
if (!layerParams) {
|
|
@@ -116,8 +155,117 @@ export class MainView extends React.Component {
|
|
|
116
155
|
return scaled;
|
|
117
156
|
};
|
|
118
157
|
this._onClientSharedStateChanged = (sender, clients) => {
|
|
119
|
-
|
|
158
|
+
var _a, _b, _c, _d, _e;
|
|
159
|
+
const remoteUser = (_a = this._model.localState) === null || _a === void 0 ? void 0 : _a.remoteUser;
|
|
160
|
+
// If we are in following mode, we update our position and selection
|
|
161
|
+
if (remoteUser) {
|
|
162
|
+
const remoteState = clients.get(remoteUser);
|
|
163
|
+
if (!remoteState) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (((_b = remoteState.user) === null || _b === void 0 ? void 0 : _b.username) !== ((_c = this.state.remoteUser) === null || _c === void 0 ? void 0 : _c.username)) {
|
|
167
|
+
this.setState(old => (Object.assign(Object.assign({}, old), { remoteUser: remoteState.user })));
|
|
168
|
+
}
|
|
169
|
+
const remoteViewport = remoteState.viewportState;
|
|
170
|
+
if (remoteViewport.value) {
|
|
171
|
+
const { x, y } = remoteViewport.value.coordinates;
|
|
172
|
+
const zoom = remoteViewport.value.zoom;
|
|
173
|
+
this._moveToPosition({ x, y }, zoom, 0);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
// If we are unfollowing a remote user, we reset our center and zoom to their previous values
|
|
178
|
+
if (this.state.remoteUser !== null) {
|
|
179
|
+
this.setState(old => (Object.assign(Object.assign({}, old), { remoteUser: null })));
|
|
180
|
+
const viewportState = (_e = (_d = this._model.localState) === null || _d === void 0 ? void 0 : _d.viewportState) === null || _e === void 0 ? void 0 : _e.value;
|
|
181
|
+
if (viewportState) {
|
|
182
|
+
this._moveToPosition(viewportState.coordinates, viewportState.zoom);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// cursors
|
|
187
|
+
clients.forEach((client, clientId) => {
|
|
188
|
+
var _a;
|
|
189
|
+
if (!(client === null || client === void 0 ? void 0 : client.user)) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const pointer = (_a = client.pointer) === null || _a === void 0 ? void 0 : _a.value;
|
|
193
|
+
// We already display our own cursor on mouse move
|
|
194
|
+
if (this._model.getClientId() === clientId) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const clientPointers = this.state.clientPointers;
|
|
198
|
+
let currentClientPointer = clientPointers[clientId];
|
|
199
|
+
if (pointer) {
|
|
200
|
+
const pixel = this._Map.getPixelFromCoordinate([
|
|
201
|
+
pointer.coordinates.x,
|
|
202
|
+
pointer.coordinates.y
|
|
203
|
+
]);
|
|
204
|
+
const lonLat = toLonLat([pointer.coordinates.x, pointer.coordinates.y]);
|
|
205
|
+
if (!currentClientPointer) {
|
|
206
|
+
currentClientPointer = clientPointers[clientId] = {
|
|
207
|
+
username: client.user.username,
|
|
208
|
+
displayName: client.user.display_name,
|
|
209
|
+
color: client.user.color,
|
|
210
|
+
coordinates: { x: pixel[0], y: pixel[1] },
|
|
211
|
+
lonLat: { longitude: lonLat[0], latitude: lonLat[1] }
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
currentClientPointer.coordinates.x = pixel[0];
|
|
215
|
+
currentClientPointer.coordinates.y = pixel[1];
|
|
216
|
+
clientPointers[clientId] = currentClientPointer;
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
delete clientPointers[clientId];
|
|
220
|
+
}
|
|
221
|
+
this.setState(old => (Object.assign(Object.assign({}, old), { clientPointers: clientPointers })));
|
|
222
|
+
});
|
|
223
|
+
};
|
|
224
|
+
this._onSharedModelStateChange = (_, change) => {
|
|
225
|
+
var _a;
|
|
226
|
+
const changedState = (_a = change.stateChange) === null || _a === void 0 ? void 0 : _a.map(value => value.name);
|
|
227
|
+
if (!(changedState === null || changedState === void 0 ? void 0 : changedState.includes('path'))) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const path = this._model.sharedModel.getState('path');
|
|
231
|
+
if (path !== this._documentPath && typeof path === 'string') {
|
|
232
|
+
if (window.jupytergisMaps !== undefined && this._documentPath) {
|
|
233
|
+
delete window.jupytergisMaps[this._documentPath];
|
|
234
|
+
}
|
|
235
|
+
this._documentPath = path;
|
|
236
|
+
if (window.jupytergisMaps !== undefined) {
|
|
237
|
+
window.jupytergisMaps[this._documentPath] = this._Map;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
120
240
|
};
|
|
241
|
+
this._onSharedMetadataChanged = (_, changes) => {
|
|
242
|
+
const newState = Object.assign({}, this.state.annotations);
|
|
243
|
+
changes.forEach((val, key) => {
|
|
244
|
+
if (!key.startsWith('annotation')) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const data = this._model.sharedModel.getMetadata(key);
|
|
248
|
+
let open = true;
|
|
249
|
+
if (this.state.firstLoad) {
|
|
250
|
+
open = false;
|
|
251
|
+
}
|
|
252
|
+
if (data && (val.action === 'add' || val.action === 'update')) {
|
|
253
|
+
const jsonData = JSON.parse(data);
|
|
254
|
+
jsonData['open'] = open;
|
|
255
|
+
newState[key] = jsonData;
|
|
256
|
+
}
|
|
257
|
+
else if (val.action === 'delete') {
|
|
258
|
+
delete newState[key];
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
this.setState(old => (Object.assign(Object.assign({}, old), { annotations: newState, firstLoad: false })));
|
|
262
|
+
};
|
|
263
|
+
this._syncPointer = throttle((coordinates) => {
|
|
264
|
+
const pointer = {
|
|
265
|
+
coordinates: { x: coordinates[0], y: coordinates[1] }
|
|
266
|
+
};
|
|
267
|
+
this._model.syncPointer(pointer);
|
|
268
|
+
});
|
|
121
269
|
this._handleThemeChange = () => {
|
|
122
270
|
const lightTheme = isLightTheme();
|
|
123
271
|
// TODO SOMETHING
|
|
@@ -130,6 +278,8 @@ export class MainView extends React.Component {
|
|
|
130
278
|
this.divRef = React.createRef(); // Reference of render div
|
|
131
279
|
this._ready = false;
|
|
132
280
|
this._sourceToLayerMap = new Map();
|
|
281
|
+
proj4.defs(Array.from(proj4list));
|
|
282
|
+
register(proj4);
|
|
133
283
|
this._mainViewModel = this.props.viewModel;
|
|
134
284
|
this._mainViewModel.viewSettingChanged.connect(this._onViewChanged, this);
|
|
135
285
|
this._model = this._mainViewModel.jGISModel;
|
|
@@ -139,20 +289,34 @@ export class MainView extends React.Component {
|
|
|
139
289
|
this._model.sharedLayersChanged.connect(this._onLayersChanged, this);
|
|
140
290
|
this._model.sharedLayerTreeChanged.connect(this._onLayerTreeChange, this);
|
|
141
291
|
this._model.sharedSourcesChanged.connect(this._onSourcesChange, this);
|
|
292
|
+
this._model.sharedModel.changed.connect(this._onSharedModelStateChange);
|
|
293
|
+
this._mainViewModel.jGISModel.sharedMetadataChanged.connect(this._onSharedMetadataChanged, this);
|
|
294
|
+
this._model.zoomToAnnotationSignal.connect(this._onZoomToAnnotation, this);
|
|
142
295
|
this.state = {
|
|
143
296
|
id: this._mainViewModel.id,
|
|
144
297
|
lightTheme: isLightTheme(),
|
|
145
298
|
loading: true,
|
|
146
|
-
firstLoad: true
|
|
299
|
+
firstLoad: true,
|
|
300
|
+
annotations: {},
|
|
301
|
+
clientPointers: {}
|
|
147
302
|
};
|
|
148
303
|
this._sources = [];
|
|
304
|
+
this._commands = new CommandRegistry();
|
|
305
|
+
this._contextMenu = new ContextMenu({ commands: this._commands });
|
|
149
306
|
}
|
|
150
307
|
async componentDidMount() {
|
|
151
308
|
window.addEventListener('resize', this._handleWindowResize);
|
|
152
309
|
await this.generateScene();
|
|
310
|
+
this.addContextMenu();
|
|
153
311
|
this._mainViewModel.initSignal();
|
|
312
|
+
if (window.jupytergisMaps !== undefined && this._documentPath) {
|
|
313
|
+
window.jupytergisMaps[this._documentPath] = this._Map;
|
|
314
|
+
}
|
|
154
315
|
}
|
|
155
316
|
componentWillUnmount() {
|
|
317
|
+
if (window.jupytergisMaps !== undefined && this._documentPath) {
|
|
318
|
+
delete window.jupytergisMaps[this._documentPath];
|
|
319
|
+
}
|
|
156
320
|
window.removeEventListener('resize', this._handleWindowResize);
|
|
157
321
|
this._mainViewModel.viewSettingChanged.disconnect(this._onViewChanged, this);
|
|
158
322
|
this._model.themeChanged.disconnect(this._handleThemeChange, this);
|
|
@@ -171,6 +335,7 @@ export class MainView extends React.Component {
|
|
|
171
335
|
}),
|
|
172
336
|
controls: [new ScaleLine()]
|
|
173
337
|
});
|
|
338
|
+
// Add map interactions
|
|
174
339
|
const dragAndDropInteraction = new DragAndDrop({
|
|
175
340
|
formatConstructors: [GeoJSON]
|
|
176
341
|
});
|
|
@@ -181,7 +346,8 @@ export class MainView extends React.Component {
|
|
|
181
346
|
name: 'Drag and Drop source',
|
|
182
347
|
parameters: { path: event.file.name }
|
|
183
348
|
};
|
|
184
|
-
|
|
349
|
+
const layerId = UUID.uuid4();
|
|
350
|
+
this.addSource(sourceId, sourceModel, layerId);
|
|
185
351
|
this._model.sharedModel.addSource(sourceId, sourceModel);
|
|
186
352
|
const layerModel = {
|
|
187
353
|
type: 'VectorLayer',
|
|
@@ -194,11 +360,68 @@ export class MainView extends React.Component {
|
|
|
194
360
|
source: sourceId
|
|
195
361
|
}
|
|
196
362
|
};
|
|
197
|
-
|
|
198
|
-
this.addLayer(layerId, layerModel, this.getLayers().length);
|
|
363
|
+
this.addLayer(layerId, layerModel, this.getLayerIDs().length);
|
|
199
364
|
this._model.addLayer(layerId, layerModel);
|
|
200
365
|
});
|
|
201
366
|
this._Map.addInteraction(dragAndDropInteraction);
|
|
367
|
+
const selectInteraction = new Select({
|
|
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);
|
|
404
|
+
const view = this._Map.getView();
|
|
405
|
+
// TODO: Note for the future, will need to update listeners if view changes
|
|
406
|
+
view.on('change:center', throttle(() => {
|
|
407
|
+
var _a;
|
|
408
|
+
// Not syncing center if following someone else
|
|
409
|
+
if ((_a = this._model.localState) === null || _a === void 0 ? void 0 : _a.remoteUser) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const view = this._Map.getView();
|
|
413
|
+
const center = view.getCenter();
|
|
414
|
+
const zoom = view.getZoom();
|
|
415
|
+
if (!center || !zoom) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
this._model.syncViewport({ coordinates: { x: center[0], y: center[1] }, zoom }, this._mainViewModel.id);
|
|
419
|
+
}));
|
|
420
|
+
this._Map.on('postrender', () => {
|
|
421
|
+
if (this.state.annotations) {
|
|
422
|
+
this._updateAnnotation();
|
|
423
|
+
}
|
|
424
|
+
});
|
|
202
425
|
this._Map.on('moveend', () => {
|
|
203
426
|
if (!this._initializedPosition) {
|
|
204
427
|
return;
|
|
@@ -220,12 +443,23 @@ export class MainView extends React.Component {
|
|
|
220
443
|
updatedOptions.extent = view.calculateExtent();
|
|
221
444
|
this._model.setOptions(Object.assign(Object.assign({}, currentOptions), updatedOptions));
|
|
222
445
|
});
|
|
446
|
+
this._Map.on('click', this._identifyFeature.bind(this));
|
|
447
|
+
this._Map
|
|
448
|
+
.getViewport()
|
|
449
|
+
.addEventListener('pointermove', this._onPointerMove.bind(this));
|
|
223
450
|
if (JupyterGISModel.getOrderedLayerIds(this._model).length !== 0) {
|
|
224
451
|
await this._updateLayersImpl(JupyterGISModel.getOrderedLayerIds(this._model));
|
|
225
452
|
const options = this._model.getOptions();
|
|
226
453
|
this.updateOptions(options);
|
|
227
454
|
this._initializedPosition = true;
|
|
228
455
|
}
|
|
456
|
+
this._Map.getViewport().addEventListener('contextmenu', event => {
|
|
457
|
+
event.preventDefault();
|
|
458
|
+
event.stopPropagation();
|
|
459
|
+
const coordinate = this._Map.getEventCoordinate(event);
|
|
460
|
+
this._clickCoords = coordinate;
|
|
461
|
+
this._contextMenu.open(event);
|
|
462
|
+
});
|
|
229
463
|
this.setState(old => (Object.assign(Object.assign({}, old), { loading: false })));
|
|
230
464
|
}
|
|
231
465
|
}
|
|
@@ -241,13 +475,17 @@ export class MainView extends React.Component {
|
|
|
241
475
|
throw error;
|
|
242
476
|
}
|
|
243
477
|
}
|
|
478
|
+
async _loadGeoTIFFWithCache(sourceInfo) {
|
|
479
|
+
const result = await loadGeoTIFFWithCache(sourceInfo);
|
|
480
|
+
return result === null || result === void 0 ? void 0 : result.file;
|
|
481
|
+
}
|
|
244
482
|
/**
|
|
245
483
|
* Add a source in the map.
|
|
246
484
|
*
|
|
247
485
|
* @param id - the source id.
|
|
248
486
|
* @param source - the source object.
|
|
249
487
|
*/
|
|
250
|
-
async addSource(id, source) {
|
|
488
|
+
async addSource(id, source, layerId) {
|
|
251
489
|
var _a, _b;
|
|
252
490
|
let newSource;
|
|
253
491
|
switch (source.type) {
|
|
@@ -363,8 +601,15 @@ export class MainView extends React.Component {
|
|
|
363
601
|
}
|
|
364
602
|
case 'GeoTiffSource': {
|
|
365
603
|
const sourceParameters = source.parameters;
|
|
604
|
+
const addNoData = (url) => {
|
|
605
|
+
return Object.assign(Object.assign({}, url), { nodata: 0 });
|
|
606
|
+
};
|
|
607
|
+
const sourcesWithBlobs = await Promise.all(sourceParameters.urls.map(async (sourceInfo) => {
|
|
608
|
+
const blob = await this._loadGeoTIFFWithCache(sourceInfo);
|
|
609
|
+
return Object.assign(Object.assign({}, addNoData(sourceInfo)), { blob });
|
|
610
|
+
}));
|
|
366
611
|
newSource = new GeoTIFFSource({
|
|
367
|
-
sources:
|
|
612
|
+
sources: sourcesWithBlobs,
|
|
368
613
|
normalize: sourceParameters.normalize,
|
|
369
614
|
wrapX: sourceParameters.wrapX
|
|
370
615
|
});
|
|
@@ -408,7 +653,7 @@ export class MainView extends React.Component {
|
|
|
408
653
|
// remove source being updated
|
|
409
654
|
this.removeSource(id);
|
|
410
655
|
// create updated source
|
|
411
|
-
this.addSource(id, source);
|
|
656
|
+
await this.addSource(id, source, layerId);
|
|
412
657
|
// change source of target layer
|
|
413
658
|
mapLayer.setSource(this._sources[id]);
|
|
414
659
|
}
|
|
@@ -428,20 +673,45 @@ export class MainView extends React.Component {
|
|
|
428
673
|
updateLayers(layerIds) {
|
|
429
674
|
this._updateLayersImpl(layerIds);
|
|
430
675
|
}
|
|
676
|
+
/**
|
|
677
|
+
* Updates the position and existence of layers in the OL map based on the layer IDs.
|
|
678
|
+
*
|
|
679
|
+
* @param layerIds - An array of layer IDs that should be present on the map.
|
|
680
|
+
* @returns {} Nothing is returned.
|
|
681
|
+
*/
|
|
431
682
|
async _updateLayersImpl(layerIds) {
|
|
432
|
-
|
|
433
|
-
|
|
683
|
+
// get layers that are currently on the OL map
|
|
684
|
+
const previousLayerIds = this.getLayerIDs();
|
|
685
|
+
// Iterate over the new layer IDs:
|
|
686
|
+
// * Add layers to the map that are present in the list but not the map.
|
|
687
|
+
// * Remove layers from the map that are present in the map but not the list.
|
|
688
|
+
// * Update layer positions to match the list.
|
|
689
|
+
for (let targetLayerPosition = 0; targetLayerPosition < layerIds.length; targetLayerPosition++) {
|
|
690
|
+
const layerId = layerIds[targetLayerPosition];
|
|
434
691
|
const layer = this._model.sharedModel.getLayer(layerId);
|
|
435
692
|
if (!layer) {
|
|
436
|
-
console.
|
|
693
|
+
console.warn(`Layer with ID ${layerId} does not exist in the shared model.`);
|
|
437
694
|
continue;
|
|
438
695
|
}
|
|
439
|
-
const
|
|
440
|
-
if (
|
|
441
|
-
|
|
696
|
+
const mapLayer = this.getLayer(layerId);
|
|
697
|
+
if (mapLayer !== undefined) {
|
|
698
|
+
this.moveLayer(layerId, targetLayerPosition);
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
await this.addLayer(layerId, layer, targetLayerPosition);
|
|
702
|
+
}
|
|
703
|
+
const previousIndex = previousLayerIds.indexOf(layerId);
|
|
704
|
+
if (previousIndex > -1) {
|
|
705
|
+
previousLayerIds.splice(previousIndex, 1);
|
|
442
706
|
}
|
|
443
707
|
}
|
|
444
|
-
|
|
708
|
+
// Remove layers that are no longer in the `layerIds` list.
|
|
709
|
+
previousLayerIds.forEach(layerId => {
|
|
710
|
+
const layer = this.getLayer(layerId);
|
|
711
|
+
if (layer !== undefined) {
|
|
712
|
+
this._Map.removeLayer(layer);
|
|
713
|
+
}
|
|
714
|
+
});
|
|
445
715
|
this._ready = true;
|
|
446
716
|
}
|
|
447
717
|
/**
|
|
@@ -454,12 +724,12 @@ export class MainView extends React.Component {
|
|
|
454
724
|
async _buildMapLayer(id, layer) {
|
|
455
725
|
var _a;
|
|
456
726
|
const sourceId = (_a = layer.parameters) === null || _a === void 0 ? void 0 : _a.source;
|
|
457
|
-
const source = this._model.sharedModel.
|
|
727
|
+
const source = this._model.sharedModel.getLayerSource(sourceId);
|
|
458
728
|
if (!source) {
|
|
459
729
|
return;
|
|
460
730
|
}
|
|
461
731
|
if (!this._sources[sourceId]) {
|
|
462
|
-
await this.addSource(sourceId, source);
|
|
732
|
+
await this.addSource(sourceId, source, id);
|
|
463
733
|
}
|
|
464
734
|
let newMapLayer;
|
|
465
735
|
let layerParameters;
|
|
@@ -559,12 +829,12 @@ export class MainView extends React.Component {
|
|
|
559
829
|
async updateLayer(id, layer, mapLayer) {
|
|
560
830
|
var _a, _b, _c, _d;
|
|
561
831
|
const sourceId = (_a = layer.parameters) === null || _a === void 0 ? void 0 : _a.source;
|
|
562
|
-
const source = this._model.sharedModel.
|
|
832
|
+
const source = this._model.sharedModel.getLayerSource(sourceId);
|
|
563
833
|
if (!source) {
|
|
564
834
|
return;
|
|
565
835
|
}
|
|
566
836
|
if (!this._sources[sourceId]) {
|
|
567
|
-
await this.addSource(sourceId, source);
|
|
837
|
+
await this.addSource(sourceId, source, id);
|
|
568
838
|
}
|
|
569
839
|
mapLayer.setVisible(layer.visible);
|
|
570
840
|
switch (layer.type) {
|
|
@@ -620,23 +890,36 @@ export class MainView extends React.Component {
|
|
|
620
890
|
this._initializedPosition = true;
|
|
621
891
|
}
|
|
622
892
|
}
|
|
623
|
-
updateOptions(options) {
|
|
624
|
-
const
|
|
893
|
+
async updateOptions(options) {
|
|
894
|
+
const { projection, extent, useExtent, latitude, longitude, zoom, bearing } = options;
|
|
895
|
+
let view = this._Map.getView();
|
|
896
|
+
const currentProjection = view.getProjection().getCode();
|
|
897
|
+
// Need to recreate view if the projection changes
|
|
898
|
+
if (projection !== undefined && currentProjection !== projection) {
|
|
899
|
+
const newProjection = getProjection(projection);
|
|
900
|
+
if (newProjection) {
|
|
901
|
+
view = new View({ projection: newProjection });
|
|
902
|
+
}
|
|
903
|
+
else {
|
|
904
|
+
console.warn(`Invalid projection: ${projection}`);
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
625
908
|
// Use the extent only if explicitly requested (QGIS files).
|
|
626
|
-
if (
|
|
627
|
-
view.fit(
|
|
909
|
+
if (useExtent && extent) {
|
|
910
|
+
view.fit(extent);
|
|
628
911
|
}
|
|
629
912
|
else {
|
|
630
|
-
const centerCoord = fromLonLat([
|
|
631
|
-
this.
|
|
632
|
-
this._Map.getView().setCenter(centerCoord);
|
|
913
|
+
const centerCoord = fromLonLat([longitude || 0, latitude || 0], view.getProjection());
|
|
914
|
+
this._moveToPosition({ x: centerCoord[0], y: centerCoord[1] }, zoom || 0);
|
|
633
915
|
// Save the extent if it does not exists, to allow proper export to qgis.
|
|
634
|
-
if (options.extent
|
|
916
|
+
if (!options.extent) {
|
|
635
917
|
options.extent = view.calculateExtent();
|
|
636
918
|
this._model.setOptions(options);
|
|
637
919
|
}
|
|
638
920
|
}
|
|
639
|
-
view.setRotation(
|
|
921
|
+
view.setRotation(bearing || 0);
|
|
922
|
+
this._Map.setView(view);
|
|
640
923
|
}
|
|
641
924
|
_onViewChanged(sender, change) {
|
|
642
925
|
// TODO SOMETHING
|
|
@@ -651,15 +934,48 @@ export class MainView extends React.Component {
|
|
|
651
934
|
.getArray()
|
|
652
935
|
.find(layer => layer.get('id') === id);
|
|
653
936
|
}
|
|
937
|
+
/**
|
|
938
|
+
* Convenience method to get a specific layer index from OpenLayers Map
|
|
939
|
+
* @param id Layer to retrieve
|
|
940
|
+
*/
|
|
941
|
+
getLayerIndex(id) {
|
|
942
|
+
return this._Map
|
|
943
|
+
.getLayers()
|
|
944
|
+
.getArray()
|
|
945
|
+
.findIndex(layer => layer.get('id') === id);
|
|
946
|
+
}
|
|
654
947
|
/**
|
|
655
948
|
* Convenience method to get list layer IDs from the OpenLayers Map
|
|
656
949
|
*/
|
|
657
|
-
|
|
950
|
+
getLayerIDs() {
|
|
658
951
|
return this._Map
|
|
659
952
|
.getLayers()
|
|
660
953
|
.getArray()
|
|
661
954
|
.map(layer => layer.get('id'));
|
|
662
955
|
}
|
|
956
|
+
/**
|
|
957
|
+
* Move layer `id` in the stack to `index`.
|
|
958
|
+
*
|
|
959
|
+
* @param id - id of the layer.
|
|
960
|
+
* @param index - expected index of the layer.
|
|
961
|
+
*/
|
|
962
|
+
moveLayer(id, index) {
|
|
963
|
+
const currentIndex = this.getLayerIndex(id);
|
|
964
|
+
if (currentIndex === index || currentIndex === -1) {
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
const layer = this.getLayer(id);
|
|
968
|
+
let nextIndex = index;
|
|
969
|
+
// should not be undefined since the id exists above
|
|
970
|
+
if (layer === undefined) {
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
this._Map.getLayers().removeAt(currentIndex);
|
|
974
|
+
if (currentIndex < index) {
|
|
975
|
+
nextIndex -= 1;
|
|
976
|
+
}
|
|
977
|
+
this._Map.getLayers().insertAt(nextIndex, layer);
|
|
978
|
+
}
|
|
663
979
|
_onLayersChanged(_, change) {
|
|
664
980
|
var _a;
|
|
665
981
|
// Avoid concurrency update on layers on first load, if layersTreeChanged and
|
|
@@ -709,16 +1025,110 @@ export class MainView extends React.Component {
|
|
|
709
1025
|
}
|
|
710
1026
|
});
|
|
711
1027
|
}
|
|
1028
|
+
_computeAnnotationPosition(annotation) {
|
|
1029
|
+
const { x, y } = annotation.position;
|
|
1030
|
+
const pixels = this._Map.getPixelFromCoordinate([x, y]);
|
|
1031
|
+
if (pixels) {
|
|
1032
|
+
return { x: pixels[0], y: pixels[1] };
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
_updateAnnotation() {
|
|
1036
|
+
Object.keys(this.state.annotations).forEach(key => {
|
|
1037
|
+
var _a;
|
|
1038
|
+
const el = document.getElementById(key);
|
|
1039
|
+
if (el) {
|
|
1040
|
+
const annotation = (_a = this._model.annotationModel) === null || _a === void 0 ? void 0 : _a.getAnnotation(key);
|
|
1041
|
+
if (annotation) {
|
|
1042
|
+
const screenPosition = this._computeAnnotationPosition(annotation);
|
|
1043
|
+
if (screenPosition) {
|
|
1044
|
+
el.style.left = `${Math.round(screenPosition.x)}px`;
|
|
1045
|
+
el.style.top = `${Math.round(screenPosition.y)}px`;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
_onZoomToAnnotation(_, id) {
|
|
1052
|
+
var _a;
|
|
1053
|
+
const annotation = (_a = this._model.annotationModel) === null || _a === void 0 ? void 0 : _a.getAnnotation(id);
|
|
1054
|
+
if (annotation) {
|
|
1055
|
+
this._moveToPosition(annotation.position, annotation.zoom);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
_moveToPosition(center, zoom, duration = 1000) {
|
|
1059
|
+
const view = this._Map.getView();
|
|
1060
|
+
// Zoom needs to be set before changing center
|
|
1061
|
+
if (!view.animate === undefined) {
|
|
1062
|
+
view.animate({ zoom, duration });
|
|
1063
|
+
view.animate({ center: [center.x, center.y], duration });
|
|
1064
|
+
}
|
|
1065
|
+
else {
|
|
1066
|
+
view.setZoom(zoom);
|
|
1067
|
+
view.setCenter([center.x, center.y]);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
_onPointerMove(e) {
|
|
1071
|
+
const pixel = this._Map.getEventPixel(e);
|
|
1072
|
+
const coordinates = this._Map.getCoordinateFromPixel(pixel);
|
|
1073
|
+
this._syncPointer(coordinates);
|
|
1074
|
+
}
|
|
1075
|
+
_identifyFeature(e) {
|
|
1076
|
+
var _a, _b;
|
|
1077
|
+
if (!this._model.isIdentifying) {
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
const localState = (_a = this._model) === null || _a === void 0 ? void 0 : _a.sharedModel.awareness.getLocalState();
|
|
1081
|
+
const selectedLayer = (_b = localState === null || localState === void 0 ? void 0 : localState.selected) === null || _b === void 0 ? void 0 : _b.value;
|
|
1082
|
+
if (!selectedLayer) {
|
|
1083
|
+
console.warn('Layer must be selected to use identify tool');
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
const layerId = Object.keys(selectedLayer)[0];
|
|
1087
|
+
const jgisLayer = this._model.getLayer(layerId);
|
|
1088
|
+
switch (jgisLayer === null || jgisLayer === void 0 ? void 0 : jgisLayer.type) {
|
|
1089
|
+
case 'WebGlLayer': {
|
|
1090
|
+
const layer = this.getLayer(layerId);
|
|
1091
|
+
const data = layer.getData(e.pixel);
|
|
1092
|
+
// TODO: Handle dataviews?
|
|
1093
|
+
if (!data || data instanceof DataView) {
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
const bandValues = {};
|
|
1097
|
+
// Data is an array of band values
|
|
1098
|
+
for (let i = 0; i < data.length - 1; i++) {
|
|
1099
|
+
bandValues[`Band ${i + 1}`] = data[i];
|
|
1100
|
+
}
|
|
1101
|
+
// last element is alpha
|
|
1102
|
+
bandValues['Alpha'] = data[data.length - 1];
|
|
1103
|
+
this._model.syncIdentifiedFeatures([bandValues], this._mainViewModel.id);
|
|
1104
|
+
break;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
712
1108
|
render() {
|
|
713
|
-
return (React.createElement(
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
1109
|
+
return (React.createElement(React.Fragment, null,
|
|
1110
|
+
Object.entries(this.state.annotations).map(([key, annotation]) => {
|
|
1111
|
+
if (!this._model.annotationModel) {
|
|
1112
|
+
return null;
|
|
1113
|
+
}
|
|
1114
|
+
const screenPosition = this._computeAnnotationPosition(annotation);
|
|
1115
|
+
return (screenPosition && (React.createElement("div", { key: key, id: key, style: {
|
|
1116
|
+
left: screenPosition.x,
|
|
1117
|
+
top: screenPosition.y
|
|
1118
|
+
}, className: 'jGIS-Popup-Wrapper' },
|
|
1119
|
+
React.createElement(AnnotationFloater, { itemId: key, annotationModel: this._model.annotationModel, open: false }))));
|
|
1120
|
+
}),
|
|
1121
|
+
React.createElement("div", { className: "jGIS-Mainview", style: {
|
|
1122
|
+
border: this.state.remoteUser
|
|
1123
|
+
? `solid 3px ${this.state.remoteUser.color}`
|
|
1124
|
+
: 'unset'
|
|
1125
|
+
} },
|
|
1126
|
+
React.createElement(Spinner, { loading: this.state.loading }),
|
|
1127
|
+
React.createElement(FollowIndicator, { remoteUser: this.state.remoteUser }),
|
|
1128
|
+
React.createElement(CollaboratorPointers, { clients: this.state.clientPointers }),
|
|
1129
|
+
React.createElement("div", { ref: this.divRef, style: {
|
|
1130
|
+
width: '100%',
|
|
1131
|
+
height: 'calc(100%)'
|
|
1132
|
+
} }))));
|
|
723
1133
|
}
|
|
724
1134
|
}
|