@jupytergis/base 0.4.4 → 0.5.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/AnnotationFloater.d.ts +1 -4
- package/lib/annotations/components/AnnotationFloater.js +11 -6
- package/lib/annotations/model.d.ts +1 -0
- package/lib/annotations/model.js +9 -0
- package/lib/commands.js +50 -221
- package/lib/constants.d.ts +2 -16
- package/lib/constants.js +9 -24
- package/lib/icons.d.ts +8 -0
- package/lib/icons.js +40 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/mainview/mainView.d.ts +6 -1
- package/lib/mainview/mainView.js +127 -11
- package/lib/menus.d.ts +4 -0
- package/lib/menus.js +45 -0
- package/lib/panelview/annotationPanel.js +2 -2
- package/lib/panelview/components/identify-panel/IdentifyPanel.js +29 -7
- package/lib/panelview/components/layers.js +1 -0
- package/lib/panelview/leftpanel.js +0 -8
- package/lib/panelview/rightpanel.js +1 -0
- package/lib/toolbar/widget.js +44 -57
- package/lib/tools.d.ts +1 -1
- package/lib/tools.js +15 -7
- package/lib/widget.d.ts +1 -0
- package/lib/widget.js +3 -0
- package/package.json +2 -2
- package/style/icons/book_open.svg +19 -0
- package/style/icons/clock-solid.svg +17 -0
- package/style/icons/geolocation.svg +15 -0
- package/style/icons/info-solid.svg +20 -0
- package/style/icons/raster.svg +3 -2
- package/style/icons/target_with_center.svg +9 -0
- package/style/icons/target_without_center.svg +17 -0
- package/style/icons/terminal_toolbar.svg +12 -0
- package/style/icons/vector_square.svg +21 -0
- package/style/leftPanel.css +0 -1
- package/lib/panelview/components/sources.d.ts +0 -10
- package/lib/panelview/components/sources.js +0 -147
package/lib/mainview/mainView.js
CHANGED
|
@@ -6,6 +6,7 @@ import { ContextMenu } from '@lumino/widgets';
|
|
|
6
6
|
import { Collection, Map as OlMap, View, getUid } from 'ol';
|
|
7
7
|
//@ts-expect-error no types for ol-pmtiles
|
|
8
8
|
import { PMTilesRasterSource, PMTilesVectorSource } from 'ol-pmtiles';
|
|
9
|
+
import Feature from 'ol/Feature';
|
|
9
10
|
import { ScaleLine } from 'ol/control';
|
|
10
11
|
import { singleClick } from 'ol/events/condition';
|
|
11
12
|
import { GeoJSON, MVT } from 'ol/format';
|
|
@@ -31,6 +32,7 @@ import CollaboratorPointers from './CollaboratorPointers';
|
|
|
31
32
|
import { FollowIndicator } from './FollowIndicator';
|
|
32
33
|
import TemporalSlider from './TemporalSlider';
|
|
33
34
|
import { Spinner } from './spinner';
|
|
35
|
+
import { Point } from 'ol/geom';
|
|
34
36
|
export class MainView extends React.Component {
|
|
35
37
|
constructor(props) {
|
|
36
38
|
super(props);
|
|
@@ -114,7 +116,8 @@ export class MainView extends React.Component {
|
|
|
114
116
|
zoom: (_a = this._Map.getView().getZoom()) !== null && _a !== void 0 ? _a : 0,
|
|
115
117
|
label: 'New annotation',
|
|
116
118
|
contents: [],
|
|
117
|
-
parent: this._Map.getViewport().id
|
|
119
|
+
parent: this._Map.getViewport().id,
|
|
120
|
+
open: true
|
|
118
121
|
});
|
|
119
122
|
},
|
|
120
123
|
label: 'Add annotation',
|
|
@@ -418,6 +421,17 @@ export class MainView extends React.Component {
|
|
|
418
421
|
this._model.zoomToPositionSignal.connect(this._onZoomToPosition, this);
|
|
419
422
|
this._model.updateLayerSignal.connect(this._triggerLayerUpdate, this);
|
|
420
423
|
this._model.addFeatureAsMsSignal.connect(this._convertFeatureToMs, this);
|
|
424
|
+
this._model.geolocationChanged.connect(this._handleGeolocationChanged, this);
|
|
425
|
+
this._model.flyToGeometrySignal.connect(this.flyToGeometry, this);
|
|
426
|
+
this._model.highlightFeatureSignal.connect(this.highlightFeatureOnMap, this);
|
|
427
|
+
// Watch isIdentifying and clear the highlight when Identify Tool is turned off
|
|
428
|
+
this._model.sharedModel.awareness.on('change', () => {
|
|
429
|
+
var _a;
|
|
430
|
+
const isIdentifying = this._model.isIdentifying;
|
|
431
|
+
if (!isIdentifying && this._highlightLayer) {
|
|
432
|
+
(_a = this._highlightLayer.getSource()) === null || _a === void 0 ? void 0 : _a.clear();
|
|
433
|
+
}
|
|
434
|
+
});
|
|
421
435
|
this.state = {
|
|
422
436
|
id: this._mainViewModel.id,
|
|
423
437
|
lightTheme: isLightTheme(),
|
|
@@ -439,7 +453,12 @@ export class MainView extends React.Component {
|
|
|
439
453
|
}
|
|
440
454
|
async componentDidMount() {
|
|
441
455
|
window.addEventListener('resize', this._handleWindowResize);
|
|
442
|
-
|
|
456
|
+
const options = this._model.getOptions();
|
|
457
|
+
const center = options.longitude !== undefined && options.latitude !== undefined
|
|
458
|
+
? fromLonLat([options.longitude, options.latitude])
|
|
459
|
+
: [0, 0];
|
|
460
|
+
const zoom = options.zoom !== undefined ? options.zoom : 1;
|
|
461
|
+
await this.generateScene(center, zoom);
|
|
443
462
|
this.addContextMenu();
|
|
444
463
|
this._mainViewModel.initSignal();
|
|
445
464
|
if (window.jupytergisMaps !== undefined && this._documentPath) {
|
|
@@ -457,14 +476,14 @@ export class MainView extends React.Component {
|
|
|
457
476
|
this._model.clientStateChanged.disconnect(this._onClientSharedStateChanged, this);
|
|
458
477
|
this._mainViewModel.dispose();
|
|
459
478
|
}
|
|
460
|
-
async generateScene() {
|
|
479
|
+
async generateScene(center, zoom) {
|
|
461
480
|
if (this.divRef.current) {
|
|
462
481
|
this._Map = new OlMap({
|
|
463
482
|
target: this.divRef.current,
|
|
464
483
|
layers: [],
|
|
465
484
|
view: new View({
|
|
466
|
-
center
|
|
467
|
-
zoom
|
|
485
|
+
center,
|
|
486
|
+
zoom
|
|
468
487
|
}),
|
|
469
488
|
controls: [new ScaleLine()]
|
|
470
489
|
});
|
|
@@ -1064,6 +1083,86 @@ export class MainView extends React.Component {
|
|
|
1064
1083
|
}
|
|
1065
1084
|
}
|
|
1066
1085
|
}
|
|
1086
|
+
flyToGeometry(sender, geometry) {
|
|
1087
|
+
if (!geometry || typeof geometry.getExtent !== 'function') {
|
|
1088
|
+
console.warn('Invalid geometry for flyToGeometry:', geometry);
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
const view = this._Map.getView();
|
|
1092
|
+
const extent = geometry.getExtent();
|
|
1093
|
+
view.fit(extent, {
|
|
1094
|
+
padding: [50, 50, 50, 50],
|
|
1095
|
+
duration: 1000,
|
|
1096
|
+
maxZoom: 16
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
highlightFeatureOnMap(sender, featureOrGeometry) {
|
|
1100
|
+
const geometry = (featureOrGeometry === null || featureOrGeometry === void 0 ? void 0 : featureOrGeometry.geometry) ||
|
|
1101
|
+
(featureOrGeometry === null || featureOrGeometry === void 0 ? void 0 : featureOrGeometry._geometry) ||
|
|
1102
|
+
featureOrGeometry;
|
|
1103
|
+
if (!geometry) {
|
|
1104
|
+
console.warn('No geometry found in feature:', featureOrGeometry);
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
const isOlGeometry = typeof geometry.getCoordinates === 'function';
|
|
1108
|
+
const parsedGeometry = isOlGeometry
|
|
1109
|
+
? geometry
|
|
1110
|
+
: new GeoJSON().readGeometry(geometry, {
|
|
1111
|
+
featureProjection: this._Map.getView().getProjection()
|
|
1112
|
+
});
|
|
1113
|
+
const olFeature = new Feature(Object.assign({ geometry: parsedGeometry }, (geometry !== featureOrGeometry ? featureOrGeometry : {})));
|
|
1114
|
+
if (!this._highlightLayer) {
|
|
1115
|
+
this._highlightLayer = new VectorLayer({
|
|
1116
|
+
source: new VectorSource(),
|
|
1117
|
+
style: feature => {
|
|
1118
|
+
var _a;
|
|
1119
|
+
const geomType = (_a = feature.getGeometry()) === null || _a === void 0 ? void 0 : _a.getType();
|
|
1120
|
+
switch (geomType) {
|
|
1121
|
+
case 'Point':
|
|
1122
|
+
case 'MultiPoint':
|
|
1123
|
+
return new Style({
|
|
1124
|
+
image: new Circle({
|
|
1125
|
+
radius: 6,
|
|
1126
|
+
fill: new Fill({ color: 'rgba(255, 255, 0, 0.8)' }),
|
|
1127
|
+
stroke: new Stroke({ color: '#ff0', width: 2 })
|
|
1128
|
+
})
|
|
1129
|
+
});
|
|
1130
|
+
case 'LineString':
|
|
1131
|
+
case 'MultiLineString':
|
|
1132
|
+
return new Style({
|
|
1133
|
+
stroke: new Stroke({
|
|
1134
|
+
color: 'rgba(255, 255, 0, 0.8)',
|
|
1135
|
+
width: 3
|
|
1136
|
+
})
|
|
1137
|
+
});
|
|
1138
|
+
case 'Polygon':
|
|
1139
|
+
case 'MultiPolygon':
|
|
1140
|
+
return new Style({
|
|
1141
|
+
stroke: new Stroke({
|
|
1142
|
+
color: '#f00',
|
|
1143
|
+
width: 2
|
|
1144
|
+
}),
|
|
1145
|
+
fill: new Fill({
|
|
1146
|
+
color: 'rgba(255, 255, 0, 0.8)'
|
|
1147
|
+
})
|
|
1148
|
+
});
|
|
1149
|
+
default:
|
|
1150
|
+
return new Style({
|
|
1151
|
+
stroke: new Stroke({
|
|
1152
|
+
color: '#000',
|
|
1153
|
+
width: 2
|
|
1154
|
+
})
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
1157
|
+
},
|
|
1158
|
+
zIndex: 999
|
|
1159
|
+
});
|
|
1160
|
+
this._Map.addLayer(this._highlightLayer);
|
|
1161
|
+
}
|
|
1162
|
+
const source = this._highlightLayer.getSource();
|
|
1163
|
+
source === null || source === void 0 ? void 0 : source.clear();
|
|
1164
|
+
source === null || source === void 0 ? void 0 : source.addFeature(olFeature);
|
|
1165
|
+
}
|
|
1067
1166
|
/**
|
|
1068
1167
|
* Wait for all layers to be loaded.
|
|
1069
1168
|
*/
|
|
@@ -1302,7 +1401,7 @@ export class MainView extends React.Component {
|
|
|
1302
1401
|
// Check if the id is an annotation
|
|
1303
1402
|
const annotation = (_a = this._model.annotationModel) === null || _a === void 0 ? void 0 : _a.getAnnotation(id);
|
|
1304
1403
|
if (annotation) {
|
|
1305
|
-
this.
|
|
1404
|
+
this._flyToPosition(annotation.position, annotation.zoom);
|
|
1306
1405
|
return;
|
|
1307
1406
|
}
|
|
1308
1407
|
// The id is a layer
|
|
@@ -1334,15 +1433,18 @@ export class MainView extends React.Component {
|
|
|
1334
1433
|
}
|
|
1335
1434
|
_moveToPosition(center, zoom, duration = 1000) {
|
|
1336
1435
|
const view = this._Map.getView();
|
|
1436
|
+
view.setZoom(zoom);
|
|
1437
|
+
view.setCenter([center.x, center.y]);
|
|
1337
1438
|
// Zoom needs to be set before changing center
|
|
1338
1439
|
if (!view.animate === undefined) {
|
|
1339
1440
|
view.animate({ zoom, duration });
|
|
1340
1441
|
view.animate({ center: [center.x, center.y], duration });
|
|
1341
1442
|
}
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
}
|
|
1443
|
+
}
|
|
1444
|
+
_flyToPosition(center, zoom, duration = 1000) {
|
|
1445
|
+
const view = this._Map.getView();
|
|
1446
|
+
view.animate({ zoom, duration });
|
|
1447
|
+
view.animate({ center: [center.x, center.y], duration });
|
|
1346
1448
|
}
|
|
1347
1449
|
_onPointerMove(e) {
|
|
1348
1450
|
const pixel = this._Map.getEventPixel(e);
|
|
@@ -1378,6 +1480,10 @@ export class MainView extends React.Component {
|
|
|
1378
1480
|
// last element is alpha
|
|
1379
1481
|
bandValues['Alpha'] = data[data.length - 1];
|
|
1380
1482
|
this._model.syncIdentifiedFeatures([bandValues], this._mainViewModel.id);
|
|
1483
|
+
const coordinate = this._Map.getCoordinateFromPixel(e.pixel);
|
|
1484
|
+
const point = new Point(coordinate);
|
|
1485
|
+
// trigger highlight via signal
|
|
1486
|
+
this._model.highlightFeatureSignal.emit(point);
|
|
1381
1487
|
break;
|
|
1382
1488
|
}
|
|
1383
1489
|
}
|
|
@@ -1404,6 +1510,16 @@ export class MainView extends React.Component {
|
|
|
1404
1510
|
feature.set(`${selectedFeature}ms`, parsedTime);
|
|
1405
1511
|
});
|
|
1406
1512
|
}
|
|
1513
|
+
_handleGeolocationChanged(sender, newPosition) {
|
|
1514
|
+
const view = this._Map.getView();
|
|
1515
|
+
const zoom = view.getZoom();
|
|
1516
|
+
if (zoom) {
|
|
1517
|
+
this._flyToPosition(newPosition, zoom);
|
|
1518
|
+
}
|
|
1519
|
+
else {
|
|
1520
|
+
throw new Error('Could not move to geolocation, because current zoom is not defined.');
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1407
1523
|
render() {
|
|
1408
1524
|
return (React.createElement(React.Fragment, null,
|
|
1409
1525
|
Object.entries(this.state.annotations).map(([key, annotation]) => {
|
|
@@ -1415,7 +1531,7 @@ export class MainView extends React.Component {
|
|
|
1415
1531
|
left: screenPosition.x,
|
|
1416
1532
|
top: screenPosition.y
|
|
1417
1533
|
}, className: 'jGIS-Popup-Wrapper' },
|
|
1418
|
-
React.createElement(AnnotationFloater, { itemId: key, annotationModel: this._model.annotationModel
|
|
1534
|
+
React.createElement(AnnotationFloater, { itemId: key, annotationModel: this._model.annotationModel }))));
|
|
1419
1535
|
}),
|
|
1420
1536
|
React.createElement("div", { className: "jGIS-Mainview-Container" },
|
|
1421
1537
|
this.state.displayTemporalController && (React.createElement(TemporalSlider, { model: this._model, filterStates: this.state.filterStates })),
|
package/lib/menus.d.ts
ADDED
package/lib/menus.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Menu } from '@lumino/widgets';
|
|
2
|
+
import { CommandIDs } from './constants';
|
|
3
|
+
import { rasterIcon } from './icons';
|
|
4
|
+
export const vectorSubMenu = (commands) => {
|
|
5
|
+
const subMenu = new Menu({ commands });
|
|
6
|
+
subMenu.title.label = 'Add Vector Layer';
|
|
7
|
+
subMenu.title.iconClass = 'fa fa-vector-square';
|
|
8
|
+
subMenu.id = 'jp-gis-toolbar-vector-menu';
|
|
9
|
+
subMenu.addItem({
|
|
10
|
+
type: 'command',
|
|
11
|
+
command: CommandIDs.newVectorTileEntry
|
|
12
|
+
});
|
|
13
|
+
subMenu.addItem({
|
|
14
|
+
type: 'command',
|
|
15
|
+
command: CommandIDs.newGeoJSONEntry
|
|
16
|
+
});
|
|
17
|
+
subMenu.addItem({
|
|
18
|
+
type: 'command',
|
|
19
|
+
command: CommandIDs.newShapefileEntry
|
|
20
|
+
});
|
|
21
|
+
return subMenu;
|
|
22
|
+
};
|
|
23
|
+
export const rasterSubMenu = (commands) => {
|
|
24
|
+
const subMenu = new Menu({ commands });
|
|
25
|
+
subMenu.title.label = 'Add Raster Layer';
|
|
26
|
+
subMenu.title.icon = rasterIcon;
|
|
27
|
+
subMenu.id = 'jp-gis-toolbar-raster-menu';
|
|
28
|
+
subMenu.addItem({
|
|
29
|
+
type: 'command',
|
|
30
|
+
command: CommandIDs.newRasterEntry
|
|
31
|
+
});
|
|
32
|
+
subMenu.addItem({
|
|
33
|
+
type: 'command',
|
|
34
|
+
command: CommandIDs.newHillshadeEntry
|
|
35
|
+
});
|
|
36
|
+
subMenu.addItem({
|
|
37
|
+
type: 'command',
|
|
38
|
+
command: CommandIDs.newImageEntry
|
|
39
|
+
});
|
|
40
|
+
subMenu.addItem({
|
|
41
|
+
type: 'command',
|
|
42
|
+
command: CommandIDs.newGeoTiffEntry
|
|
43
|
+
});
|
|
44
|
+
return subMenu;
|
|
45
|
+
};
|
|
@@ -29,14 +29,14 @@ export class AnnotationsPanel extends Component {
|
|
|
29
29
|
React.createElement(Annotation, { rightPanelModel: this._rightPanelModel, annotationModel: this._annotationModel, itemId: id }),
|
|
30
30
|
React.createElement("hr", { className: "jGIS-Annotations-Separator" })));
|
|
31
31
|
});
|
|
32
|
-
return React.createElement("div",
|
|
32
|
+
return React.createElement("div", { className: "jgis-scrollable" }, annotations);
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
export class Annotations extends PanelWithToolbar {
|
|
36
36
|
constructor(options) {
|
|
37
37
|
super({});
|
|
38
38
|
this.title.label = 'Annotations';
|
|
39
|
-
this.addClass('
|
|
39
|
+
this.addClass('jgis-scrollable');
|
|
40
40
|
this._annotationModel = options.annotationModel;
|
|
41
41
|
this._rightPanelModel = options.rightPanelModel;
|
|
42
42
|
this._widget = ReactWidget.create(React.createElement(AnnotationsPanel, { rightPanelModel: this._rightPanelModel, annotationModel: this._annotationModel }));
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { LabIcon, ReactWidget, caretDownIcon } from '@jupyterlab/ui-components';
|
|
2
2
|
import { Panel } from '@lumino/widgets';
|
|
3
3
|
import React, { useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
5
|
+
import { faMagnifyingGlass } from '@fortawesome/free-solid-svg-icons';
|
|
4
6
|
export class IdentifyPanel extends Panel {
|
|
5
7
|
constructor(options) {
|
|
6
8
|
super();
|
|
@@ -9,6 +11,7 @@ export class IdentifyPanel extends Panel {
|
|
|
9
11
|
this.id = 'jupytergis::identifyPanel';
|
|
10
12
|
this.title.caption = 'Identify';
|
|
11
13
|
this.title.label = 'Identify';
|
|
14
|
+
this.addClass('jgis-scrollable');
|
|
12
15
|
this.addWidget(ReactWidget.create(React.createElement(IdentifyPanelComponent, { controlPanelModel: this._model, tracker: this._tracker })));
|
|
13
16
|
}
|
|
14
17
|
}
|
|
@@ -80,8 +83,13 @@ const IdentifyPanelComponent = ({ controlPanelModel, tracker }) => {
|
|
|
80
83
|
jgisModel === null || jgisModel === void 0 ? void 0 : jgisModel.clientStateChanged.disconnect(handleClientStateChanged);
|
|
81
84
|
};
|
|
82
85
|
}, [jgisModel]);
|
|
86
|
+
const highlightFeatureOnMap = (feature) => {
|
|
87
|
+
var _a, _b;
|
|
88
|
+
(_a = jgisModel === null || jgisModel === void 0 ? void 0 : jgisModel.highlightFeatureSignal) === null || _a === void 0 ? void 0 : _a.emit(feature);
|
|
89
|
+
const geometry = feature.geometry || feature._geometry;
|
|
90
|
+
(_b = jgisModel === null || jgisModel === void 0 ? void 0 : jgisModel.flyToGeometrySignal) === null || _b === void 0 ? void 0 : _b.emit(geometry);
|
|
91
|
+
};
|
|
83
92
|
const toggleFeatureVisibility = (index) => {
|
|
84
|
-
console.log('visibleFeatures', visibleFeatures);
|
|
85
93
|
setVisibleFeatures(prev => (Object.assign(Object.assign({}, prev), { [index]: !prev[index] })));
|
|
86
94
|
};
|
|
87
95
|
return (React.createElement("div", { className: "jgis-identify-wrapper", style: {
|
|
@@ -90,14 +98,28 @@ const IdentifyPanelComponent = ({ controlPanelModel, tracker }) => {
|
|
|
90
98
|
: 'unset'
|
|
91
99
|
} }, features &&
|
|
92
100
|
Object.values(features).map((feature, featureIndex) => (React.createElement("div", { key: featureIndex, className: "jgis-identify-grid-item" },
|
|
93
|
-
React.createElement("div", { className: "jgis-identify-grid-item-header"
|
|
94
|
-
React.createElement(
|
|
95
|
-
|
|
96
|
-
"
|
|
97
|
-
|
|
98
|
-
|
|
101
|
+
React.createElement("div", { className: "jgis-identify-grid-item-header" },
|
|
102
|
+
React.createElement("span", { onClick: () => toggleFeatureVisibility(featureIndex) },
|
|
103
|
+
React.createElement(LabIcon.resolveReact, { icon: caretDownIcon, className: `jp-gis-layerGroupCollapser${visibleFeatures[featureIndex] ? ' jp-mod-expanded' : ''}`, tag: 'span' }),
|
|
104
|
+
React.createElement("span", null,
|
|
105
|
+
"Feature ",
|
|
106
|
+
featureIndex + 1)),
|
|
107
|
+
(() => {
|
|
108
|
+
const isRasterFeature = !feature.geometry &&
|
|
109
|
+
!feature._geometry &&
|
|
110
|
+
typeof (feature === null || feature === void 0 ? void 0 : feature.x) !== 'number' &&
|
|
111
|
+
typeof (feature === null || feature === void 0 ? void 0 : feature.y) !== 'number';
|
|
112
|
+
return (React.createElement("button", { className: "jgis-highlight-button", onClick: e => {
|
|
113
|
+
e.stopPropagation();
|
|
114
|
+
highlightFeatureOnMap(feature);
|
|
115
|
+
}, title: isRasterFeature
|
|
116
|
+
? 'Highlight not available for raster features'
|
|
117
|
+
: 'Highlight feature on map', disabled: isRasterFeature },
|
|
118
|
+
React.createElement(FontAwesomeIcon, { icon: faMagnifyingGlass })));
|
|
119
|
+
})()),
|
|
99
120
|
visibleFeatures[featureIndex] && (React.createElement(React.Fragment, null, Object.entries(feature)
|
|
100
121
|
.filter(([key, value]) => typeof value !== 'object' || value === null)
|
|
122
|
+
.sort(([keyA], [keyB]) => keyA.localeCompare(keyB))
|
|
101
123
|
.map(([key, value]) => (React.createElement("div", { key: key, className: "jgis-identify-grid-body" },
|
|
102
124
|
React.createElement("strong", null,
|
|
103
125
|
key,
|
|
@@ -94,6 +94,7 @@ function LayersBodyComponent(props) {
|
|
|
94
94
|
};
|
|
95
95
|
model === null || model === void 0 ? void 0 : model.sharedModel.layersChanged.connect(updateLayers);
|
|
96
96
|
model === null || model === void 0 ? void 0 : model.sharedModel.layerTreeChanged.connect(updateLayers);
|
|
97
|
+
updateLayers();
|
|
97
98
|
return () => {
|
|
98
99
|
model === null || model === void 0 ? void 0 : model.sharedModel.layersChanged.disconnect(updateLayers);
|
|
99
100
|
model === null || model === void 0 ? void 0 : model.sharedModel.layerTreeChanged.disconnect(updateLayers);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { SidePanel } from '@jupyterlab/ui-components';
|
|
2
2
|
import { LayersPanel } from './components/layers';
|
|
3
|
-
import { SourcesPanel } from './components/sources';
|
|
4
3
|
import { ControlPanelHeader } from './header';
|
|
5
4
|
import { FilterPanel } from './components/filter-panel/Filter';
|
|
6
5
|
import { CommandIDs } from '../constants';
|
|
@@ -62,13 +61,6 @@ export class LeftPanelWidget extends SidePanel {
|
|
|
62
61
|
this._commands = options.commands;
|
|
63
62
|
const header = new ControlPanelHeader();
|
|
64
63
|
this.header.addWidget(header);
|
|
65
|
-
const sourcesPanel = new SourcesPanel({
|
|
66
|
-
model: this._model,
|
|
67
|
-
onSelect: this._onSelect
|
|
68
|
-
});
|
|
69
|
-
sourcesPanel.title.caption = 'Sources';
|
|
70
|
-
sourcesPanel.title.label = 'Sources';
|
|
71
|
-
this.addWidget(sourcesPanel);
|
|
72
64
|
const layerTree = new LayersPanel({
|
|
73
65
|
model: this._model,
|
|
74
66
|
state: this._state,
|
|
@@ -30,6 +30,7 @@ export class RightPanelWidget extends SidePanel {
|
|
|
30
30
|
});
|
|
31
31
|
identifyPanel.title.caption = 'Identify';
|
|
32
32
|
identifyPanel.title.label = 'Identify';
|
|
33
|
+
identifyPanel.addClass('jgis-scrollable');
|
|
33
34
|
this.addWidget(identifyPanel);
|
|
34
35
|
this._model.documentChanged.connect((_, changed) => {
|
|
35
36
|
if (changed) {
|
package/lib/toolbar/widget.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { CommandToolbarButton } from '@jupyterlab/apputils';
|
|
2
|
-
import { MenuSvg, ReactWidget, ReactiveToolbar, ToolbarButton, addIcon, redoIcon,
|
|
3
|
-
import {
|
|
2
|
+
import { MenuSvg, ReactWidget, ReactiveToolbar, ToolbarButton, addIcon, redoIcon, undoIcon } from '@jupyterlab/ui-components';
|
|
3
|
+
import { Widget } from '@lumino/widgets';
|
|
4
4
|
import * as React from 'react';
|
|
5
5
|
import { CommandIDs } from '../constants';
|
|
6
|
-
import {
|
|
6
|
+
import { terminalToolbarIcon } from '../icons';
|
|
7
|
+
import { rasterSubMenu, vectorSubMenu } from '../menus';
|
|
7
8
|
import { UsersItem } from './usertoolbaritem';
|
|
8
9
|
export const TOOLBAR_SEPARATOR_CLASS = 'jGIS-Toolbar-Separator';
|
|
9
10
|
export const TOOLBAR_GROUPNAME_CLASS = 'jGIS-Toolbar-GroupName';
|
|
@@ -21,75 +22,48 @@ export class ToolbarWidget extends ReactiveToolbar {
|
|
|
21
22
|
super();
|
|
22
23
|
this.addClass('jGIS-toolbar-widget');
|
|
23
24
|
if (options.commands) {
|
|
24
|
-
|
|
25
|
+
const undoButton = new CommandToolbarButton({
|
|
25
26
|
id: CommandIDs.undo,
|
|
26
27
|
label: '',
|
|
27
28
|
icon: undoIcon,
|
|
28
29
|
commands: options.commands
|
|
29
|
-
})
|
|
30
|
-
this.addItem('
|
|
30
|
+
});
|
|
31
|
+
this.addItem('undo', undoButton);
|
|
32
|
+
undoButton.node.dataset.testid = 'undo-button';
|
|
33
|
+
const redoButton = new CommandToolbarButton({
|
|
31
34
|
id: CommandIDs.redo,
|
|
32
35
|
label: '',
|
|
33
36
|
icon: redoIcon,
|
|
34
37
|
commands: options.commands
|
|
35
|
-
})
|
|
38
|
+
});
|
|
39
|
+
this.addItem('redo', redoButton);
|
|
36
40
|
this.addItem('separator0', new Separator());
|
|
37
|
-
|
|
41
|
+
const toggleConsoleButton = new CommandToolbarButton({
|
|
38
42
|
id: CommandIDs.toggleConsole,
|
|
39
43
|
commands: options.commands,
|
|
40
44
|
label: '',
|
|
41
|
-
icon:
|
|
42
|
-
})
|
|
45
|
+
icon: terminalToolbarIcon
|
|
46
|
+
});
|
|
47
|
+
this.addItem('Toggle console', toggleConsoleButton);
|
|
48
|
+
toggleConsoleButton.node.dataset.testid = 'toggle-console-button';
|
|
43
49
|
this.addItem('separator1', new Separator());
|
|
44
|
-
|
|
50
|
+
const openLayersBrowserButton = new CommandToolbarButton({
|
|
45
51
|
id: CommandIDs.openLayerBrowser,
|
|
46
52
|
label: '',
|
|
47
53
|
commands: options.commands
|
|
48
|
-
}));
|
|
49
|
-
this.addItem('newRasterEntry', new CommandToolbarButton({
|
|
50
|
-
id: CommandIDs.newRasterEntry,
|
|
51
|
-
label: '',
|
|
52
|
-
commands: options.commands
|
|
53
|
-
}));
|
|
54
|
-
this.addItem('newVectorTileEntry', new CommandToolbarButton({
|
|
55
|
-
id: CommandIDs.newVectorTileEntry,
|
|
56
|
-
label: '',
|
|
57
|
-
commands: options.commands
|
|
58
|
-
}));
|
|
59
|
-
// vector sub menu
|
|
60
|
-
const vectorSubMenu = new Menu({ commands: options.commands });
|
|
61
|
-
vectorSubMenu.title.label = 'Add Vector Layer';
|
|
62
|
-
vectorSubMenu.title.iconClass = 'fa fa-vector-square';
|
|
63
|
-
vectorSubMenu.id = 'jp-gis-toolbar-vector-menu';
|
|
64
|
-
vectorSubMenu.addItem({
|
|
65
|
-
type: 'command',
|
|
66
|
-
command: CommandIDs.newGeoJSONEntry
|
|
67
|
-
});
|
|
68
|
-
vectorSubMenu.addItem({
|
|
69
|
-
type: 'command',
|
|
70
|
-
command: CommandIDs.newShapefileLayer
|
|
71
|
-
});
|
|
72
|
-
//raster submenu
|
|
73
|
-
const rasterSubMenu = new Menu({ commands: options.commands });
|
|
74
|
-
rasterSubMenu.title.label = 'Add Raster Layer';
|
|
75
|
-
rasterSubMenu.title.icon = rasterIcon;
|
|
76
|
-
rasterSubMenu.id = 'jp-gis-toolbar-raster-menu';
|
|
77
|
-
rasterSubMenu.addItem({
|
|
78
|
-
type: 'command',
|
|
79
|
-
command: CommandIDs.newHillshadeEntry
|
|
80
|
-
});
|
|
81
|
-
rasterSubMenu.addItem({
|
|
82
|
-
type: 'command',
|
|
83
|
-
command: CommandIDs.newImageEntry
|
|
84
|
-
});
|
|
85
|
-
rasterSubMenu.addItem({
|
|
86
|
-
type: 'command',
|
|
87
|
-
command: CommandIDs.newGeoTiffEntry
|
|
88
54
|
});
|
|
55
|
+
this.addItem('openLayerBrowser', openLayersBrowserButton);
|
|
56
|
+
openLayersBrowserButton.node.dataset.testid = 'open-layers-browser';
|
|
89
57
|
const NewSubMenu = new MenuSvg({ commands: options.commands });
|
|
90
58
|
NewSubMenu.title.label = 'Add Layer';
|
|
91
|
-
NewSubMenu.addItem({
|
|
92
|
-
|
|
59
|
+
NewSubMenu.addItem({
|
|
60
|
+
type: 'submenu',
|
|
61
|
+
submenu: rasterSubMenu(options.commands)
|
|
62
|
+
});
|
|
63
|
+
NewSubMenu.addItem({
|
|
64
|
+
type: 'submenu',
|
|
65
|
+
submenu: vectorSubMenu(options.commands)
|
|
66
|
+
});
|
|
93
67
|
const NewEntryButton = new ToolbarButton({
|
|
94
68
|
icon: addIcon,
|
|
95
69
|
noFocusOnClick: false,
|
|
@@ -101,18 +75,31 @@ export class ToolbarWidget extends ReactiveToolbar {
|
|
|
101
75
|
NewSubMenu.open(bbox.x, bbox.bottom);
|
|
102
76
|
}
|
|
103
77
|
});
|
|
78
|
+
NewEntryButton.node.dataset.testid = 'new-entry-button';
|
|
104
79
|
this.addItem('New', NewEntryButton);
|
|
105
80
|
this.addItem('separator2', new Separator());
|
|
106
|
-
|
|
81
|
+
const geolocationButton = new CommandToolbarButton({
|
|
82
|
+
id: CommandIDs.getGeolocation,
|
|
83
|
+
commands: options.commands,
|
|
84
|
+
label: ''
|
|
85
|
+
});
|
|
86
|
+
this.addItem('Geolocation', geolocationButton);
|
|
87
|
+
geolocationButton.node.dataset.testid = 'geolocation-button';
|
|
88
|
+
const identifyButton = new CommandToolbarButton({
|
|
107
89
|
id: CommandIDs.identify,
|
|
108
90
|
label: '',
|
|
109
91
|
commands: options.commands
|
|
110
|
-
})
|
|
111
|
-
this.addItem('
|
|
92
|
+
});
|
|
93
|
+
this.addItem('identify', identifyButton);
|
|
94
|
+
identifyButton.node.dataset.testid = 'identify-button';
|
|
95
|
+
const temporalControllerButton = new CommandToolbarButton({
|
|
112
96
|
id: CommandIDs.temporalController,
|
|
113
97
|
label: '',
|
|
114
98
|
commands: options.commands
|
|
115
|
-
})
|
|
99
|
+
});
|
|
100
|
+
this.addItem('temporalController', temporalControllerButton);
|
|
101
|
+
temporalControllerButton.node.dataset.testid =
|
|
102
|
+
'temporal-controller-button';
|
|
116
103
|
this.addItem('spacer', ReactiveToolbar.createSpacerItem());
|
|
117
104
|
// Users
|
|
118
105
|
this.addItem('users', ReactWidget.create(React.createElement(UsersItem, { model: options.model })));
|
package/lib/tools.d.ts
CHANGED
|
@@ -65,7 +65,7 @@ export declare const getFromIndexedDB: (key: string) => Promise<{
|
|
|
65
65
|
*/
|
|
66
66
|
export declare const loadGeoTiff: (sourceInfo: {
|
|
67
67
|
url?: string | undefined;
|
|
68
|
-
}, file?: Contents.IModel | null) => Promise<{
|
|
68
|
+
}, model: IJupyterGISModel, file?: Contents.IModel | null) => Promise<{
|
|
69
69
|
file: any;
|
|
70
70
|
metadata: any;
|
|
71
71
|
sourceUrl: string;
|
package/lib/tools.js
CHANGED
|
@@ -301,11 +301,19 @@ export const getFromIndexedDB = async (key) => {
|
|
|
301
301
|
request.onerror = () => reject(request.error);
|
|
302
302
|
});
|
|
303
303
|
};
|
|
304
|
-
const fetchWithProxies = async (url, parseResponse) => {
|
|
304
|
+
const fetchWithProxies = async (url, model, parseResponse) => {
|
|
305
|
+
let settings = null;
|
|
306
|
+
try {
|
|
307
|
+
settings = await model.getSettings();
|
|
308
|
+
}
|
|
309
|
+
catch (e) {
|
|
310
|
+
console.warn('Failed to get settings from model. Falling back.', e);
|
|
311
|
+
}
|
|
312
|
+
const proxyUrl = settings && settings.proxyUrl ? settings.proxyUrl : 'https://corsproxy.io';
|
|
305
313
|
const proxyUrls = [
|
|
306
314
|
url, // Direct fetch
|
|
307
315
|
`/jupytergis_core/proxy?url=${encodeURIComponent(url)}`, // Internal proxy
|
|
308
|
-
|
|
316
|
+
`${proxyUrl}/?url=${encodeURIComponent(url)}` // External proxy
|
|
309
317
|
];
|
|
310
318
|
for (const proxyUrl of proxyUrls) {
|
|
311
319
|
try {
|
|
@@ -328,7 +336,7 @@ const fetchWithProxies = async (url, parseResponse) => {
|
|
|
328
336
|
* @param sourceInfo object containing the URL of the GeoTIFF file.
|
|
329
337
|
* @returns A promise that resolves to the file as a Blob, or undefined .
|
|
330
338
|
*/
|
|
331
|
-
export const loadGeoTiff = async (sourceInfo, file) => {
|
|
339
|
+
export const loadGeoTiff = async (sourceInfo, model, file) => {
|
|
332
340
|
if (!(sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.url)) {
|
|
333
341
|
return null;
|
|
334
342
|
}
|
|
@@ -347,7 +355,7 @@ export const loadGeoTiff = async (sourceInfo, file) => {
|
|
|
347
355
|
}
|
|
348
356
|
let fileBlob = null;
|
|
349
357
|
if (!file) {
|
|
350
|
-
fileBlob = await fetchWithProxies(url, async (response) => response.blob());
|
|
358
|
+
fileBlob = await fetchWithProxies(url, model, async (response) => response.blob());
|
|
351
359
|
if (!fileBlob) {
|
|
352
360
|
showErrorMessage('Network error', `Failed to fetch ${url}`);
|
|
353
361
|
throw new Error(`Failed to fetch ${url}`);
|
|
@@ -403,7 +411,7 @@ export const loadFile = async (fileInfo) => {
|
|
|
403
411
|
if (cached) {
|
|
404
412
|
return cached.file;
|
|
405
413
|
}
|
|
406
|
-
const geojson = await fetchWithProxies(filepath, async (response) => {
|
|
414
|
+
const geojson = await fetchWithProxies(filepath, model, async (response) => {
|
|
407
415
|
const arrayBuffer = await response.arrayBuffer();
|
|
408
416
|
return shp(arrayBuffer);
|
|
409
417
|
});
|
|
@@ -419,7 +427,7 @@ export const loadFile = async (fileInfo) => {
|
|
|
419
427
|
if (cached) {
|
|
420
428
|
return cached.file;
|
|
421
429
|
}
|
|
422
|
-
const geojson = await fetchWithProxies(filepath, async (response) => response.json());
|
|
430
|
+
const geojson = await fetchWithProxies(filepath, model, async (response) => response.json());
|
|
423
431
|
if (geojson) {
|
|
424
432
|
await saveToIndexedDB(filepath, geojson);
|
|
425
433
|
return geojson;
|
|
@@ -476,7 +484,7 @@ export const loadFile = async (fileInfo) => {
|
|
|
476
484
|
}
|
|
477
485
|
case 'GeoTiffSource': {
|
|
478
486
|
if (typeof file.content === 'string') {
|
|
479
|
-
const tiff = loadGeoTiff({ url: filepath }, file);
|
|
487
|
+
const tiff = loadGeoTiff({ url: filepath }, model, file);
|
|
480
488
|
return tiff;
|
|
481
489
|
}
|
|
482
490
|
else {
|
package/lib/widget.d.ts
CHANGED
|
@@ -53,6 +53,7 @@ export declare class JupyterGISPanel extends SplitPanel {
|
|
|
53
53
|
dispose(): void;
|
|
54
54
|
get currentViewModel(): MainViewModel;
|
|
55
55
|
get consolePanel(): ConsolePanel | undefined;
|
|
56
|
+
get consoleOpened(): boolean;
|
|
56
57
|
executeConsole(): void;
|
|
57
58
|
removeConsole(): void;
|
|
58
59
|
toggleConsole(jgisPath: string): Promise<void>;
|
package/lib/widget.js
CHANGED
|
@@ -116,6 +116,9 @@ export class JupyterGISPanel extends SplitPanel {
|
|
|
116
116
|
var _a;
|
|
117
117
|
return (_a = this._consoleView) === null || _a === void 0 ? void 0 : _a.consolePanel;
|
|
118
118
|
}
|
|
119
|
+
get consoleOpened() {
|
|
120
|
+
return this._consoleOpened;
|
|
121
|
+
}
|
|
119
122
|
executeConsole() {
|
|
120
123
|
if (this._consoleView) {
|
|
121
124
|
this._consoleView.execute();
|