@jupytergis/base 0.14.1 → 0.15.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/commands/BaseCommandIDs.d.ts +1 -0
- package/lib/commands/BaseCommandIDs.js +1 -0
- package/lib/commands/index.js +28 -9
- package/lib/constants.js +1 -0
- package/lib/dialogs/symbology/classificationModes.js +12 -16
- package/lib/dialogs/symbology/colorRampUtils.d.ts +47 -3
- package/lib/dialogs/symbology/colorRampUtils.js +112 -13
- package/lib/dialogs/symbology/components/color_ramp/ColorRampSelector.js +6 -14
- package/lib/dialogs/symbology/components/color_ramp/ColorRampSelectorEntry.d.ts +2 -2
- package/lib/dialogs/symbology/components/color_ramp/ColorRampSelectorEntry.js +3 -11
- package/lib/dialogs/symbology/components/color_ramp/RgbaColorPicker.d.ts +13 -0
- package/lib/dialogs/symbology/components/color_ramp/RgbaColorPicker.js +98 -0
- package/lib/dialogs/symbology/components/color_stops/StopContainer.js +3 -1
- package/lib/dialogs/symbology/components/color_stops/StopRow.d.ts +1 -1
- package/lib/dialogs/symbology/components/color_stops/StopRow.js +12 -7
- package/lib/dialogs/symbology/symbologyDialog.d.ts +2 -1
- package/lib/dialogs/symbology/symbologyUtils.d.ts +2 -2
- package/lib/dialogs/symbology/symbologyUtils.js +58 -40
- package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +14 -2
- package/lib/dialogs/symbology/vector_layer/types/Canonical.js +70 -5
- package/lib/dialogs/symbology/vector_layer/types/Categorized.js +81 -34
- package/lib/dialogs/symbology/vector_layer/types/Graduated.js +155 -43
- package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.js +31 -16
- package/lib/formbuilder/formselectors.js +4 -1
- package/lib/formbuilder/objectform/components/WmsTileSourceUrlInput.d.ts +3 -0
- package/lib/formbuilder/objectform/components/WmsTileSourceUrlInput.js +84 -0
- package/lib/formbuilder/objectform/source/index.d.ts +1 -0
- package/lib/formbuilder/objectform/source/index.js +1 -0
- package/lib/formbuilder/objectform/source/wmsTileSource.d.ts +4 -0
- package/lib/formbuilder/objectform/source/wmsTileSource.js +78 -0
- package/lib/formbuilder/objectform/useSchemaFormState.d.ts +1 -1
- package/lib/mainview/mainView.d.ts +3 -1
- package/lib/mainview/mainView.js +170 -23
- package/lib/menus.js +4 -0
- package/lib/panelview/components/layers.js +19 -2
- package/lib/panelview/components/legendItem.js +14 -4
- package/lib/stacBrowser/components/filter-extension/QueryableComboBox.js +60 -17
- package/lib/stacBrowser/hooks/useStacFilterExtension.d.ts +1 -1
- package/lib/stacBrowser/hooks/useStacFilterExtension.js +195 -111
- package/lib/stacBrowser/hooks/useStacSearch.d.ts +1 -0
- package/lib/stacBrowser/hooks/useStacSearch.js +18 -10
- package/lib/tools.d.ts +1 -1
- package/lib/tools.js +3 -3
- package/lib/types.d.ts +6 -0
- package/package.json +5 -2
- package/style/shared/tabs.css +2 -2
- package/style/storyPanel.css +2 -0
- package/style/symbologyDialog.css +45 -1
package/lib/mainview/mainView.js
CHANGED
|
@@ -9,11 +9,11 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
9
9
|
}
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
|
-
import { JupyterGISModel, } from '@jupytergis/schema';
|
|
12
|
+
import { JupyterGISModel, DEFAULT_PROJECTION, } from '@jupytergis/schema';
|
|
13
13
|
import { showErrorMessage } from '@jupyterlab/apputils';
|
|
14
14
|
import { CommandRegistry } from '@lumino/commands';
|
|
15
15
|
import { UUID } from '@lumino/coreutils';
|
|
16
|
-
import { ContextMenu } from '@lumino/widgets';
|
|
16
|
+
import { ContextMenu, Menu } from '@lumino/widgets';
|
|
17
17
|
import { Collection, Map as OlMap, View, getUid, } from 'ol';
|
|
18
18
|
import Feature from 'ol/Feature';
|
|
19
19
|
import { FullScreen, ScaleLine, Zoom } from 'ol/control';
|
|
@@ -27,7 +27,7 @@ import TileLayer from 'ol/layer/Tile';
|
|
|
27
27
|
import { fromLonLat, get as getProjection, toLonLat, transformExtent, } from 'ol/proj';
|
|
28
28
|
import { register } from 'ol/proj/proj4.js';
|
|
29
29
|
import RenderFeature, { toGeometry } from 'ol/render/Feature';
|
|
30
|
-
import { GeoTIFF as GeoTIFFSource, ImageTile as ImageTileSource, Vector as VectorSource, VectorTile as VectorTileSource, XYZ as XYZSource, Tile as TileSource, } from 'ol/source';
|
|
30
|
+
import { GeoTIFF as GeoTIFFSource, ImageTile as ImageTileSource, TileWMS as TileWMSSource, Vector as VectorSource, VectorTile as VectorTileSource, XYZ as XYZSource, Tile as TileSource, } from 'ol/source';
|
|
31
31
|
import Static from 'ol/source/ImageStatic';
|
|
32
32
|
import { Circle, Fill, Icon, Stroke, Style } from 'ol/style';
|
|
33
33
|
//@ts-expect-error no types for ol-pmtiles
|
|
@@ -161,11 +161,54 @@ export class MainView extends React.Component {
|
|
|
161
161
|
});
|
|
162
162
|
},
|
|
163
163
|
});
|
|
164
|
+
this._commands.addCommand('Copy-Coordinates-Map-CRS', {
|
|
165
|
+
label: () => {
|
|
166
|
+
if (!this._Map || !this._clickCoords) {
|
|
167
|
+
return 'Map CRS';
|
|
168
|
+
}
|
|
169
|
+
const proj = this._Map.getView().getProjection().getCode();
|
|
170
|
+
const coord = this._clickCoords;
|
|
171
|
+
return `Map CRS — ${proj} (${coord[0].toFixed(0)}E, ${coord[1].toFixed(0)}N)`;
|
|
172
|
+
},
|
|
173
|
+
execute: async () => {
|
|
174
|
+
const coord = this._clickCoords;
|
|
175
|
+
const text = `${coord[0].toFixed(0)}, ${coord[1].toFixed(0)}`;
|
|
176
|
+
await navigator.clipboard.writeText(text);
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
this._commands.addCommand('Copy-Coordinates-LonLat', {
|
|
180
|
+
label: () => {
|
|
181
|
+
if (!this._Map || !this._clickCoords) {
|
|
182
|
+
return 'Latitude/Longitude';
|
|
183
|
+
}
|
|
184
|
+
const lonLat = toLonLat(this._clickCoords, this._Map.getView().getProjection());
|
|
185
|
+
return `Latitude/Longitude: (${lonLat[1].toFixed(6)}N, ${lonLat[0].toFixed(6)}E)`;
|
|
186
|
+
},
|
|
187
|
+
execute: async () => {
|
|
188
|
+
const lonLat = toLonLat(this._clickCoords, this._Map.getView().getProjection());
|
|
189
|
+
const text = `${lonLat[1].toFixed(6)}, ${lonLat[0].toFixed(6)}`;
|
|
190
|
+
await navigator.clipboard.writeText(text);
|
|
191
|
+
},
|
|
192
|
+
});
|
|
164
193
|
this._contextMenu.addItem({
|
|
165
194
|
command: CommandIDs.addAnnotation,
|
|
166
195
|
selector: '.ol-viewport',
|
|
167
196
|
rank: 1,
|
|
168
197
|
});
|
|
198
|
+
const copyCoordinatesMenu = new Menu({ commands: this._commands });
|
|
199
|
+
copyCoordinatesMenu.title.label = 'Copy Coordinates';
|
|
200
|
+
copyCoordinatesMenu.addItem({
|
|
201
|
+
command: 'Copy-Coordinates-Map-CRS',
|
|
202
|
+
});
|
|
203
|
+
copyCoordinatesMenu.addItem({
|
|
204
|
+
command: 'Copy-Coordinates-LonLat',
|
|
205
|
+
});
|
|
206
|
+
this._contextMenu.addItem({
|
|
207
|
+
type: 'submenu',
|
|
208
|
+
submenu: copyCoordinatesMenu,
|
|
209
|
+
selector: '.ol-viewport',
|
|
210
|
+
rank: 2,
|
|
211
|
+
});
|
|
169
212
|
};
|
|
170
213
|
this.vectorLayerStyleRuleBuilder = (layer) => {
|
|
171
214
|
var _a, _b;
|
|
@@ -213,6 +256,70 @@ export class MainView extends React.Component {
|
|
|
213
256
|
}
|
|
214
257
|
const newStyle = Object.assign(Object.assign({}, defaultStyle), layerParams.color);
|
|
215
258
|
layerStyle.style = newStyle;
|
|
259
|
+
// When fallbackColor[3] === 0, add an OL filter so features that would be
|
|
260
|
+
// drawn with a transparent color are excluded from rendering entirely.
|
|
261
|
+
// alpha === 0 is the contract: the default TRANSPARENT [0,0,0,0] triggers
|
|
262
|
+
// this automatically, and a future dedicated picker option can set it.
|
|
263
|
+
const symbologyState = layerParams.symbologyState;
|
|
264
|
+
if (Array.isArray(symbologyState === null || symbologyState === void 0 ? void 0 : symbologyState.fallbackColor) &&
|
|
265
|
+
symbologyState.fallbackColor[3] === 0) {
|
|
266
|
+
let matchFilter;
|
|
267
|
+
if (symbologyState.renderType === 'Categorized') {
|
|
268
|
+
const fillExpr = layerParams.color['fill-color'];
|
|
269
|
+
if (Array.isArray(fillExpr) &&
|
|
270
|
+
fillExpr[0] === 'case' &&
|
|
271
|
+
fillExpr.length >= 4) {
|
|
272
|
+
// Extract conditions from ['case', cond, val, cond, val, ..., fallback],
|
|
273
|
+
// skipping any whose matched output color is also fully transparent.
|
|
274
|
+
const conditions = [];
|
|
275
|
+
for (let i = 1; i < fillExpr.length - 1; i += 2) {
|
|
276
|
+
const output = fillExpr[i + 1];
|
|
277
|
+
if (Array.isArray(output) && output[3] === 0) {
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
conditions.push(fillExpr[i]);
|
|
281
|
+
}
|
|
282
|
+
// If every stop is transparent, use a never-true filter to hide all.
|
|
283
|
+
matchFilter =
|
|
284
|
+
conditions.length === 0
|
|
285
|
+
? ['==', 0, 1]
|
|
286
|
+
: conditions.length === 1
|
|
287
|
+
? conditions[0]
|
|
288
|
+
: ['any', ...conditions];
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
else if (symbologyState.renderType === 'Graduated') {
|
|
292
|
+
const fillExpr = layerParams.color['fill-color'];
|
|
293
|
+
// Graduated fill is ['case', ['has', field], interpolateExpr, fallback].
|
|
294
|
+
// Features missing the attribute fall through to the fallback, so filter
|
|
295
|
+
// them out with the same ['has', field] condition.
|
|
296
|
+
if (Array.isArray(fillExpr) &&
|
|
297
|
+
fillExpr[0] === 'case' &&
|
|
298
|
+
Array.isArray(fillExpr[1]) &&
|
|
299
|
+
fillExpr[1][0] === 'has') {
|
|
300
|
+
matchFilter = fillExpr[1];
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
else if (symbologyState.renderType === 'Canonical') {
|
|
304
|
+
const fillExpr = layerParams.color['fill-color'];
|
|
305
|
+
// Canonical fill is ['coalesce', ['get', field], fallback].
|
|
306
|
+
// Features missing the attribute fall through to the fallback, so filter
|
|
307
|
+
// them out with ['has', field]. Note: features that have the field but
|
|
308
|
+
// with a non-color value also get the fallback — that gap is accepted.
|
|
309
|
+
if (Array.isArray(fillExpr) &&
|
|
310
|
+
fillExpr[0] === 'coalesce' &&
|
|
311
|
+
Array.isArray(fillExpr[1]) &&
|
|
312
|
+
fillExpr[1][0] === 'get') {
|
|
313
|
+
matchFilter = ['has', fillExpr[1][1]];
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (matchFilter) {
|
|
317
|
+
// Combine with any existing user-applied filter.
|
|
318
|
+
layerStyle.filter = layerStyle.filter
|
|
319
|
+
? ['all', layerStyle.filter, matchFilter]
|
|
320
|
+
: matchFilter;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
216
323
|
return [layerStyle];
|
|
217
324
|
};
|
|
218
325
|
/**
|
|
@@ -268,7 +375,7 @@ export class MainView extends React.Component {
|
|
|
268
375
|
* to work with the temporal controller
|
|
269
376
|
*/
|
|
270
377
|
this.handleTemporalController = (id, layer) => {
|
|
271
|
-
var _a, _b, _c, _d, _e, _f;
|
|
378
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
272
379
|
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;
|
|
273
380
|
// Temporal Controller shouldn't be active if more than one layer is selected
|
|
274
381
|
if (!selectedLayer || Object.keys(selectedLayer).length !== 1) {
|
|
@@ -286,13 +393,13 @@ export class MainView extends React.Component {
|
|
|
286
393
|
const activeFilter = layer.filters.appliedFilters[0];
|
|
287
394
|
// Save original features on first filter application
|
|
288
395
|
if (!Object.keys(this._originalFeatures).includes(id)) {
|
|
289
|
-
this._originalFeatures[id] = source.getFeatures();
|
|
396
|
+
this._originalFeatures[id] = (_e = source.getFeatures()) !== null && _e !== void 0 ? _e : [];
|
|
290
397
|
}
|
|
291
398
|
// clear current features
|
|
292
399
|
source.clear();
|
|
293
|
-
const startTime = (
|
|
294
|
-
const endTime = (
|
|
295
|
-
const filteredFeatures = this._originalFeatures[id].filter(feature => {
|
|
400
|
+
const startTime = (_f = activeFilter.betweenMin) !== null && _f !== void 0 ? _f : 0;
|
|
401
|
+
const endTime = (_g = activeFilter.betweenMax) !== null && _g !== void 0 ? _g : 1000;
|
|
402
|
+
const filteredFeatures = ((_h = this._originalFeatures[id]) !== null && _h !== void 0 ? _h : []).filter(feature => {
|
|
296
403
|
const featureTime = feature.get(activeFilter.feature);
|
|
297
404
|
return featureTime >= startTime && featureTime <= endTime;
|
|
298
405
|
});
|
|
@@ -302,7 +409,7 @@ export class MainView extends React.Component {
|
|
|
302
409
|
}
|
|
303
410
|
else {
|
|
304
411
|
// Restore original features when no filters are applied
|
|
305
|
-
source.addFeatures(this._originalFeatures[id]);
|
|
412
|
+
source.addFeatures((_j = this._originalFeatures[id]) !== null && _j !== void 0 ? _j : []);
|
|
306
413
|
delete this._originalFeatures[id];
|
|
307
414
|
}
|
|
308
415
|
};
|
|
@@ -421,6 +528,11 @@ export class MainView extends React.Component {
|
|
|
421
528
|
this._setupSpectaMode = () => {
|
|
422
529
|
this._removeAllInteractions();
|
|
423
530
|
this._setupStoryScrollListener();
|
|
531
|
+
// Ensure keybindings have a focused target in Specta mode.
|
|
532
|
+
window.requestAnimationFrame(() => {
|
|
533
|
+
var _a;
|
|
534
|
+
(_a = this.mainViewRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
535
|
+
});
|
|
424
536
|
};
|
|
425
537
|
this._removeAllInteractions = () => {
|
|
426
538
|
// Remove all default interactions
|
|
@@ -555,6 +667,7 @@ export class MainView extends React.Component {
|
|
|
555
667
|
});
|
|
556
668
|
this.setState(old => (Object.assign(Object.assign({}, old), { annotations: newState })));
|
|
557
669
|
};
|
|
670
|
+
this._lastPointerCoord = null;
|
|
558
671
|
this._syncPointer = throttle((coordinates) => {
|
|
559
672
|
const pointer = {
|
|
560
673
|
coordinates: { x: coordinates[0], y: coordinates[1] },
|
|
@@ -597,6 +710,7 @@ export class MainView extends React.Component {
|
|
|
597
710
|
};
|
|
598
711
|
this._isPositionInitialized = false;
|
|
599
712
|
this.divRef = React.createRef(); // Reference of render div
|
|
713
|
+
this.mainViewRef = React.createRef();
|
|
600
714
|
this.controlsToolbarRef = React.createRef();
|
|
601
715
|
this.spectaContainerRef = React.createRef();
|
|
602
716
|
this.storyViewerPanelRef = React.createRef();
|
|
@@ -683,13 +797,15 @@ export class MainView extends React.Component {
|
|
|
683
797
|
this._updateCenter = debounce(this.updateCenter, 100);
|
|
684
798
|
}
|
|
685
799
|
async componentDidMount() {
|
|
800
|
+
var _a;
|
|
686
801
|
window.addEventListener('resize', this._handleWindowResize);
|
|
687
802
|
const options = this._model.getOptions();
|
|
803
|
+
const projection = (_a = options.projection) !== null && _a !== void 0 ? _a : DEFAULT_PROJECTION;
|
|
688
804
|
const center = options.longitude !== undefined && options.latitude !== undefined
|
|
689
|
-
? fromLonLat([options.longitude, options.latitude])
|
|
805
|
+
? fromLonLat([options.longitude, options.latitude], projection)
|
|
690
806
|
: [0, 0];
|
|
691
807
|
const zoom = options.zoom !== undefined ? options.zoom : 1;
|
|
692
|
-
await this.generateMap(center, zoom);
|
|
808
|
+
await this.generateMap(center, zoom, projection);
|
|
693
809
|
this._mainViewModel.initSignal();
|
|
694
810
|
if (window.jupytergisMaps !== undefined && this._documentPath) {
|
|
695
811
|
window.jupytergisMaps[this._documentPath] = this._Map;
|
|
@@ -717,7 +833,7 @@ export class MainView extends React.Component {
|
|
|
717
833
|
this._cleanupStoryScrollListener();
|
|
718
834
|
this._mainViewModel.dispose();
|
|
719
835
|
}
|
|
720
|
-
async generateMap(center, zoom) {
|
|
836
|
+
async generateMap(center, zoom, projection = DEFAULT_PROJECTION) {
|
|
721
837
|
const layers = this._model.getLayers();
|
|
722
838
|
this._initialLayersCount = Object.values(layers).filter(layer => layer.type !== 'StorySegmentLayer').length;
|
|
723
839
|
const scaleLine = new ScaleLine({
|
|
@@ -741,6 +857,7 @@ export class MainView extends React.Component {
|
|
|
741
857
|
view: new View({
|
|
742
858
|
center,
|
|
743
859
|
zoom,
|
|
860
|
+
projection,
|
|
744
861
|
}),
|
|
745
862
|
controls,
|
|
746
863
|
});
|
|
@@ -803,11 +920,12 @@ export class MainView extends React.Component {
|
|
|
803
920
|
}
|
|
804
921
|
});
|
|
805
922
|
this._Map.on('moveend', () => {
|
|
923
|
+
var _a;
|
|
806
924
|
const currentOptions = this._model.getOptions();
|
|
807
925
|
const view = this._Map.getView();
|
|
808
926
|
const center = view.getCenter() || [0, 0];
|
|
809
927
|
const zoom = view.getZoom() || 0;
|
|
810
|
-
const projection = view.getProjection();
|
|
928
|
+
const projection = (_a = getProjection(currentOptions.projection)) !== null && _a !== void 0 ? _a : view.getProjection();
|
|
811
929
|
const latLng = toLonLat(center, projection);
|
|
812
930
|
const bearing = view.getRotation();
|
|
813
931
|
const resolution = view.getResolution();
|
|
@@ -842,14 +960,18 @@ export class MainView extends React.Component {
|
|
|
842
960
|
this._Map.getViewport().addEventListener('contextmenu', event => {
|
|
843
961
|
event.preventDefault();
|
|
844
962
|
event.stopPropagation();
|
|
845
|
-
|
|
846
|
-
|
|
963
|
+
if (this._lastPointerCoord) {
|
|
964
|
+
this._clickCoords = this._lastPointerCoord;
|
|
965
|
+
}
|
|
847
966
|
this._contextMenu.open(event);
|
|
848
967
|
});
|
|
849
|
-
this.setState(old =>
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
968
|
+
this.setState(old => {
|
|
969
|
+
var _a;
|
|
970
|
+
return (Object.assign(Object.assign({}, old), { loading: false, viewProjection: {
|
|
971
|
+
code: projection,
|
|
972
|
+
units: ((_a = getProjection(projection)) !== null && _a !== void 0 ? _a : view.getProjection()).getUnits(),
|
|
973
|
+
} }));
|
|
974
|
+
});
|
|
853
975
|
}
|
|
854
976
|
}
|
|
855
977
|
/**
|
|
@@ -859,7 +981,7 @@ export class MainView extends React.Component {
|
|
|
859
981
|
* @param source - the source object.
|
|
860
982
|
*/
|
|
861
983
|
async addSource(id, source) {
|
|
862
|
-
var _a, _b;
|
|
984
|
+
var _a, _b, _c;
|
|
863
985
|
let newSource;
|
|
864
986
|
switch (source.type) {
|
|
865
987
|
case 'RasterSource': {
|
|
@@ -1074,6 +1196,21 @@ export class MainView extends React.Component {
|
|
|
1074
1196
|
newSource = new VectorSource({
|
|
1075
1197
|
features: [marker],
|
|
1076
1198
|
});
|
|
1199
|
+
break;
|
|
1200
|
+
}
|
|
1201
|
+
case 'WmsTileSource': {
|
|
1202
|
+
const sourceParameters = source.parameters;
|
|
1203
|
+
const url = sourceParameters.url;
|
|
1204
|
+
const selectedLayer = (_c = sourceParameters === null || sourceParameters === void 0 ? void 0 : sourceParameters.params) === null || _c === void 0 ? void 0 : _c.layers;
|
|
1205
|
+
newSource = new TileWMSSource({
|
|
1206
|
+
attributions: sourceParameters === null || sourceParameters === void 0 ? void 0 : sourceParameters.attribution,
|
|
1207
|
+
url,
|
|
1208
|
+
params: {
|
|
1209
|
+
LAYERS: selectedLayer,
|
|
1210
|
+
TILED: true,
|
|
1211
|
+
},
|
|
1212
|
+
});
|
|
1213
|
+
break;
|
|
1077
1214
|
}
|
|
1078
1215
|
}
|
|
1079
1216
|
newSource.set('id', id);
|
|
@@ -1580,6 +1717,9 @@ export class MainView extends React.Component {
|
|
|
1580
1717
|
}
|
|
1581
1718
|
}
|
|
1582
1719
|
_onSharedOptionsChanged() {
|
|
1720
|
+
if (!this._Map) {
|
|
1721
|
+
return;
|
|
1722
|
+
}
|
|
1583
1723
|
// ! would prefer a model ready signal or something, this feels hacky
|
|
1584
1724
|
const enableSpectaPresentation = this._model.isSpectaMode();
|
|
1585
1725
|
// Handle initialization based on specta presentation state
|
|
@@ -1632,6 +1772,12 @@ export class MainView extends React.Component {
|
|
|
1632
1772
|
if (projection !== undefined && currentProjection !== projection) {
|
|
1633
1773
|
const newProjection = getProjection(projection);
|
|
1634
1774
|
if (newProjection) {
|
|
1775
|
+
this.setState(old => ({
|
|
1776
|
+
viewProjection: {
|
|
1777
|
+
code: newProjection.getCode(),
|
|
1778
|
+
units: newProjection.getUnits(),
|
|
1779
|
+
},
|
|
1780
|
+
}));
|
|
1635
1781
|
view = new View({ projection: newProjection });
|
|
1636
1782
|
}
|
|
1637
1783
|
else {
|
|
@@ -1639,6 +1785,8 @@ export class MainView extends React.Component {
|
|
|
1639
1785
|
return;
|
|
1640
1786
|
}
|
|
1641
1787
|
}
|
|
1788
|
+
view.setRotation(bearing || 0);
|
|
1789
|
+
this._Map.setView(view);
|
|
1642
1790
|
// Use the extent only if explicitly requested (QGIS files).
|
|
1643
1791
|
if (useExtent && extent) {
|
|
1644
1792
|
view.fit(extent);
|
|
@@ -1652,8 +1800,6 @@ export class MainView extends React.Component {
|
|
|
1652
1800
|
this._model.setOptions(options);
|
|
1653
1801
|
}
|
|
1654
1802
|
}
|
|
1655
|
-
view.setRotation(bearing || 0);
|
|
1656
|
-
this._Map.setView(view);
|
|
1657
1803
|
}
|
|
1658
1804
|
_onViewChanged(sender, change) {
|
|
1659
1805
|
// TODO SOMETHING
|
|
@@ -1932,6 +2078,7 @@ export class MainView extends React.Component {
|
|
|
1932
2078
|
_onPointerMove(e) {
|
|
1933
2079
|
const pixel = this._Map.getEventPixel(e);
|
|
1934
2080
|
const coordinates = this._Map.getCoordinateFromPixel(pixel);
|
|
2081
|
+
this._lastPointerCoord = coordinates;
|
|
1935
2082
|
this._syncPointer(coordinates);
|
|
1936
2083
|
}
|
|
1937
2084
|
async _addMarker(e) {
|
|
@@ -2107,7 +2254,7 @@ export class MainView extends React.Component {
|
|
|
2107
2254
|
}),
|
|
2108
2255
|
React.createElement("div", { className: "jGIS-Mainview-Container" },
|
|
2109
2256
|
this.state.displayTemporalController && (React.createElement(TemporalSlider, { model: this._model, filterStates: this.state.filterStates })),
|
|
2110
|
-
React.createElement("div", { className: "jGIS-Mainview data-jgis-keybinding", tabIndex: 0, style: {
|
|
2257
|
+
React.createElement("div", { ref: this.mainViewRef, className: "jGIS-Mainview data-jgis-keybinding", tabIndex: 0, style: {
|
|
2111
2258
|
border: this.state.remoteUser
|
|
2112
2259
|
? `solid 3px ${this.state.remoteUser.color}`
|
|
2113
2260
|
: 'unset',
|
package/lib/menus.js
CHANGED
|
@@ -33,6 +33,10 @@ export const rasterSubMenu = (commands) => {
|
|
|
33
33
|
type: 'command',
|
|
34
34
|
command: CommandIDs.openNewRasterDialog,
|
|
35
35
|
});
|
|
36
|
+
subMenu.addItem({
|
|
37
|
+
type: 'command',
|
|
38
|
+
command: CommandIDs.openNewWmsDialog,
|
|
39
|
+
});
|
|
36
40
|
subMenu.addItem({
|
|
37
41
|
type: 'command',
|
|
38
42
|
command: CommandIDs.openNewHillshadeDialog,
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
1
12
|
import { DOMUtils } from '@jupyterlab/apputils';
|
|
2
13
|
import { Button, LabIcon, caretDownIcon, caretRightIcon, } from '@jupyterlab/ui-components';
|
|
3
14
|
import React, { useEffect, useState, } from 'react';
|
|
@@ -104,8 +115,14 @@ export const LayersBodyComponent = props => {
|
|
|
104
115
|
};
|
|
105
116
|
}
|
|
106
117
|
else {
|
|
107
|
-
// If types are the same
|
|
108
|
-
|
|
118
|
+
// If types are the same modify the selection (either add or remove to multi-selection)
|
|
119
|
+
if (item in selectedValue) {
|
|
120
|
+
const _a = selectedValue, _b = item, _ = _a[_b], rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]);
|
|
121
|
+
newSelection = rest;
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
newSelection = Object.assign(Object.assign({}, selectedValue), { [item]: { type } });
|
|
125
|
+
}
|
|
109
126
|
}
|
|
110
127
|
}
|
|
111
128
|
// Set the selection
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { findExprNode } from "../../dialogs/symbology/colorRampUtils";
|
|
2
3
|
import { useGetSymbology } from "../../dialogs/symbology/hooks/useGetSymbology";
|
|
3
4
|
export const LegendItem = ({ layerId, model }) => {
|
|
4
5
|
const { symbology, isLoading, error } = useGetSymbology({ layerId, model });
|
|
5
6
|
const [content, setContent] = useState(null);
|
|
6
7
|
const parseColorStops = (expr) => {
|
|
7
|
-
|
|
8
|
+
const interpolate = findExprNode(expr, 'interpolate');
|
|
9
|
+
if (!interpolate) {
|
|
8
10
|
return [];
|
|
9
11
|
}
|
|
10
12
|
const stops = [];
|
|
11
|
-
for (let i = 3; i <
|
|
12
|
-
const value =
|
|
13
|
-
const rgba =
|
|
13
|
+
for (let i = 3; i < interpolate.length; i += 2) {
|
|
14
|
+
const value = interpolate[i];
|
|
15
|
+
const rgba = interpolate[i + 1];
|
|
14
16
|
const color = Array.isArray(rgba)
|
|
15
17
|
? `rgba(${rgba[0]},${rgba[1]},${rgba[2]},${rgba[3]})`
|
|
16
18
|
: String(rgba);
|
|
@@ -130,6 +132,14 @@ export const LegendItem = ({ layerId, model }) => {
|
|
|
130
132
|
}))));
|
|
131
133
|
return;
|
|
132
134
|
}
|
|
135
|
+
// Canonical
|
|
136
|
+
if (renderType === 'Canonical') {
|
|
137
|
+
setContent(React.createElement("div", { style: { padding: 6 } },
|
|
138
|
+
property && (React.createElement("div", { style: { fontSize: '1em' } },
|
|
139
|
+
React.createElement("strong", null, property))),
|
|
140
|
+
React.createElement("div", { style: { fontSize: '0.8em', opacity: 0.7 } }, "hex color attribute")));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
133
143
|
// Categorized
|
|
134
144
|
if (renderType === 'Categorized') {
|
|
135
145
|
const cats = parseCaseCategories(fill || stroke);
|
|
@@ -1,10 +1,55 @@
|
|
|
1
|
-
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
|
|
2
13
|
import { Combobox } from "../../../shared/components/Combobox";
|
|
3
14
|
import { Input } from "../../../shared/components/Input";
|
|
4
15
|
import { Select } from "../../../shared/components/Select";
|
|
5
16
|
import QueryableRow from "./QueryableRow";
|
|
17
|
+
import { debounce } from "../../../tools";
|
|
6
18
|
import SingleDatePicker from '../../../shared/components/SingleDatePicker';
|
|
7
19
|
export function QueryableComboBox({ queryables, selectedQueryables, updateSelectedQueryables, }) {
|
|
20
|
+
const [draftValues, setDraftValues] = useState({});
|
|
21
|
+
const selectedQueryablesRef = useRef(selectedQueryables);
|
|
22
|
+
const debouncedCommitByKeyRef = useRef({});
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
selectedQueryablesRef.current = selectedQueryables;
|
|
25
|
+
}, [selectedQueryables]);
|
|
26
|
+
const normalizeInputValue = useCallback((schema, value) => {
|
|
27
|
+
let valueToStore = value;
|
|
28
|
+
if (schema.type === 'string' &&
|
|
29
|
+
schema.format === 'date-time' &&
|
|
30
|
+
typeof value === 'string') {
|
|
31
|
+
try {
|
|
32
|
+
const localDate = new Date(value);
|
|
33
|
+
valueToStore = localDate.toISOString();
|
|
34
|
+
}
|
|
35
|
+
catch (_a) {
|
|
36
|
+
valueToStore = value;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return valueToStore;
|
|
40
|
+
}, []);
|
|
41
|
+
const scheduleQueryableCommit = useCallback((key, value) => {
|
|
42
|
+
if (!debouncedCommitByKeyRef.current[key]) {
|
|
43
|
+
debouncedCommitByKeyRef.current[key] = debounce((nextValue) => {
|
|
44
|
+
const latestFilter = selectedQueryablesRef.current[key];
|
|
45
|
+
if (!latestFilter) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
updateSelectedQueryables(key, Object.assign(Object.assign({}, latestFilter), { inputValue: nextValue }));
|
|
49
|
+
}, 500);
|
|
50
|
+
}
|
|
51
|
+
debouncedCommitByKeyRef.current[key](value);
|
|
52
|
+
}, [updateSelectedQueryables]);
|
|
8
53
|
// Derive selected items from selectedQueryables
|
|
9
54
|
const selectedItems = useMemo(() => {
|
|
10
55
|
return queryables.filter(([key]) => key in selectedQueryables);
|
|
@@ -14,6 +59,11 @@ export function QueryableComboBox({ queryables, selectedQueryables, updateSelect
|
|
|
14
59
|
const isCurrentlySelected = key in selectedQueryables;
|
|
15
60
|
if (isCurrentlySelected) {
|
|
16
61
|
// Remove if already selected - pass null to explicitly remove
|
|
62
|
+
delete debouncedCommitByKeyRef.current[key];
|
|
63
|
+
setDraftValues(prev => {
|
|
64
|
+
const _a = prev, _b = key, _ = _a[_b], rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]);
|
|
65
|
+
return rest;
|
|
66
|
+
});
|
|
17
67
|
updateSelectedQueryables(key, null);
|
|
18
68
|
}
|
|
19
69
|
else {
|
|
@@ -152,26 +202,19 @@ export function QueryableComboBox({ queryables, selectedQueryables, updateSelect
|
|
|
152
202
|
operator: ((_b = operators[0]) === null || _b === void 0 ? void 0 : _b.value) || '=',
|
|
153
203
|
inputValue: undefined,
|
|
154
204
|
};
|
|
205
|
+
const inputValue = draftValues[key] !== undefined
|
|
206
|
+
? draftValues[key]
|
|
207
|
+
: currentFilter.inputValue;
|
|
155
208
|
const handleInputChange = (value) => {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
try {
|
|
162
|
-
// Parse local time and convert to UTC ISO string
|
|
163
|
-
const localDate = new Date(value);
|
|
164
|
-
valueToStore = localDate.toISOString();
|
|
165
|
-
}
|
|
166
|
-
catch (_a) {
|
|
167
|
-
valueToStore = value;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
updateSelectedQueryables(key, Object.assign(Object.assign({}, currentFilter), { inputValue: valueToStore }));
|
|
209
|
+
const normalizedValue = normalizeInputValue(val, value);
|
|
210
|
+
setDraftValues(prev => (Object.assign(Object.assign({}, prev), { [key]: normalizedValue })));
|
|
211
|
+
// Uses a stable per-field debounced function
|
|
212
|
+
// inline debounce would recreate each render and reset its timer
|
|
213
|
+
scheduleQueryableCommit(key, normalizedValue);
|
|
171
214
|
};
|
|
172
215
|
const handleOperatorChange = (operator) => {
|
|
173
216
|
updateSelectedQueryables(key, Object.assign(Object.assign({}, currentFilter), { operator }));
|
|
174
217
|
};
|
|
175
|
-
return (React.createElement(QueryableRow, { key: key, qKey: key, qVal: val, operators: operators, currentFilter: currentFilter, inputComponent: getInputBasedOnType(val,
|
|
218
|
+
return (React.createElement(QueryableRow, { key: key, qKey: key, qVal: val, operators: operators, currentFilter: currentFilter, inputComponent: getInputBasedOnType(val, inputValue, handleInputChange), onOperatorChange: handleOperatorChange }));
|
|
176
219
|
}))));
|
|
177
220
|
}
|
|
@@ -14,7 +14,7 @@ export declare function useStacFilterExtension({ model, baseUrl, limit, }: IUseS
|
|
|
14
14
|
queryableFields: IStacQueryables | undefined;
|
|
15
15
|
collections: FilteredCollection[];
|
|
16
16
|
selectedCollection: string;
|
|
17
|
-
setSelectedCollection:
|
|
17
|
+
setSelectedCollection: (nextSelectedCollection: string) => void;
|
|
18
18
|
handleSubmit: () => Promise<void>;
|
|
19
19
|
startTime: Date | undefined;
|
|
20
20
|
endTime: Date | undefined;
|