@jupytergis/base 0.4.1 → 0.4.3

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.
Files changed (58) hide show
  1. package/lib/commands.js +120 -7
  2. package/lib/constants.d.ts +3 -0
  3. package/lib/constants.js +4 -0
  4. package/lib/dialogs/ProcessingFormDialog.d.ts +30 -0
  5. package/lib/dialogs/ProcessingFormDialog.js +109 -0
  6. package/lib/dialogs/layerBrowserDialog.js +1 -1
  7. package/lib/dialogs/{formdialog.d.ts → layerCreationFormDialog.d.ts} +1 -1
  8. package/lib/dialogs/{formdialog.js → layerCreationFormDialog.js} +1 -1
  9. package/lib/dialogs/symbology/hooks/useGetBandInfo.js +22 -3
  10. package/lib/formbuilder/formselectors.d.ts +3 -3
  11. package/lib/formbuilder/formselectors.js +3 -11
  12. package/lib/formbuilder/index.d.ts +2 -4
  13. package/lib/formbuilder/index.js +2 -4
  14. package/lib/formbuilder/objectform/baseform.d.ts +1 -10
  15. package/lib/formbuilder/objectform/baseform.js +1 -1
  16. package/lib/formbuilder/objectform/fileselectorwidget.js +3 -3
  17. package/lib/formbuilder/objectform/{heatmapLayerForm.js → layer/heatmapLayerForm.js} +1 -1
  18. package/lib/formbuilder/objectform/layer/index.d.ts +5 -0
  19. package/lib/formbuilder/objectform/layer/index.js +5 -0
  20. package/lib/formbuilder/objectform/{layerform.d.ts → layer/layerform.d.ts} +6 -1
  21. package/lib/formbuilder/objectform/{layerform.js → layer/layerform.js} +1 -1
  22. package/lib/formbuilder/objectform/process/dissolveProcessForm.d.ts +20 -0
  23. package/lib/formbuilder/objectform/process/dissolveProcessForm.js +62 -0
  24. package/lib/formbuilder/objectform/process/index.d.ts +1 -0
  25. package/lib/formbuilder/objectform/process/index.js +1 -0
  26. package/lib/formbuilder/objectform/{geojsonsource.d.ts → source/geojsonsource.d.ts} +2 -2
  27. package/lib/formbuilder/objectform/{geojsonsource.js → source/geojsonsource.js} +1 -1
  28. package/lib/formbuilder/objectform/{geotiffsource.d.ts → source/geotiffsource.d.ts} +3 -3
  29. package/lib/formbuilder/objectform/{geotiffsource.js → source/geotiffsource.js} +16 -3
  30. package/lib/formbuilder/objectform/source/index.d.ts +5 -0
  31. package/lib/formbuilder/objectform/source/index.js +5 -0
  32. package/lib/formbuilder/objectform/{pathbasedsource.d.ts → source/pathbasedsource.d.ts} +3 -3
  33. package/lib/formbuilder/objectform/{pathbasedsource.js → source/pathbasedsource.js} +4 -4
  34. package/lib/formbuilder/objectform/source/sourceform.d.ts +24 -0
  35. package/lib/formbuilder/objectform/source/sourceform.js +13 -0
  36. package/lib/formbuilder/objectform/{tilesourceform.d.ts → source/tilesourceform.d.ts} +2 -2
  37. package/lib/formbuilder/objectform/{tilesourceform.js → source/tilesourceform.js} +2 -2
  38. package/lib/index.d.ts +1 -1
  39. package/lib/index.js +1 -1
  40. package/lib/keybindings.json +5 -0
  41. package/lib/mainview/mainView.js +52 -15
  42. package/lib/panelview/components/layers.js +5 -5
  43. package/lib/panelview/leftpanel.d.ts +1 -0
  44. package/lib/panelview/leftpanel.js +8 -1
  45. package/lib/panelview/rightpanel.js +2 -0
  46. package/lib/processing.d.ts +25 -0
  47. package/lib/processing.js +177 -0
  48. package/lib/tools.d.ts +16 -0
  49. package/lib/tools.js +93 -0
  50. package/package.json +2 -2
  51. package/style/leftPanel.css +0 -1
  52. /package/lib/formbuilder/objectform/{heatmapLayerForm.d.ts → layer/heatmapLayerForm.d.ts} +0 -0
  53. /package/lib/formbuilder/objectform/{hillshadeLayerForm.d.ts → layer/hillshadeLayerForm.d.ts} +0 -0
  54. /package/lib/formbuilder/objectform/{hillshadeLayerForm.js → layer/hillshadeLayerForm.js} +0 -0
  55. /package/lib/formbuilder/objectform/{vectorlayerform.d.ts → layer/vectorlayerform.d.ts} +0 -0
  56. /package/lib/formbuilder/objectform/{vectorlayerform.js → layer/vectorlayerform.js} +0 -0
  57. /package/lib/formbuilder/objectform/{webGlLayerForm.d.ts → layer/webGlLayerForm.d.ts} +0 -0
  58. /package/lib/formbuilder/objectform/{webGlLayerForm.js → layer/webGlLayerForm.js} +0 -0
@@ -0,0 +1 @@
1
+ export * from './dissolveProcessForm';
@@ -0,0 +1 @@
1
+ export * from './dissolveProcessForm';
@@ -1,12 +1,12 @@
1
1
  import { IDict } from '@jupytergis/schema';
2
- import { IBaseFormProps } from './baseform';
3
2
  import { PathBasedSourcePropertiesForm } from './pathbasedsource';
3
+ import { ISourceFormProps } from './sourceform';
4
4
  /**
5
5
  * The form to modify a GeoJSON source.
6
6
  */
7
7
  export declare class GeoJSONSourcePropertiesForm extends PathBasedSourcePropertiesForm {
8
8
  private _validate;
9
- constructor(props: IBaseFormProps);
9
+ constructor(props: ISourceFormProps);
10
10
  protected processSchema(data: IDict<any> | undefined, schema: IDict, uiSchema: IDict): void;
11
11
  /**
12
12
  * Validate the path, to avoid invalid path or invalid GeoJSON.
@@ -1,7 +1,7 @@
1
1
  import { Ajv } from 'ajv';
2
2
  import * as geojson from '@jupytergis/schema/src/schema/geojson.json';
3
3
  import { PathBasedSourcePropertiesForm } from './pathbasedsource';
4
- import { loadFile } from '../../tools';
4
+ import { loadFile } from '../../../tools';
5
5
  /**
6
6
  * The form to modify a GeoJSON source.
7
7
  */
@@ -1,12 +1,12 @@
1
1
  import { IDict } from '@jupytergis/schema';
2
2
  import { IChangeEvent, ISubmitEvent } from '@rjsf/core';
3
- import { BaseForm, IBaseFormProps } from './baseform';
3
+ import { ISourceFormProps, SourcePropertiesForm } from './sourceform';
4
4
  /**
5
5
  * The form to modify a GeoTiff source.
6
6
  */
7
- export declare class GeoTiffSourcePropertiesForm extends BaseForm {
7
+ export declare class GeoTiffSourcePropertiesForm extends SourcePropertiesForm {
8
8
  private _isSubmitted;
9
- constructor(props: IBaseFormProps);
9
+ constructor(props: ISourceFormProps);
10
10
  protected processSchema(data: IDict<any> | undefined, schema: IDict, uiSchema: IDict): void;
11
11
  protected onFormChange(e: IChangeEvent): void;
12
12
  protected onFormBlur(id: string, value: any): void;
@@ -1,10 +1,11 @@
1
1
  import { showErrorMessage } from '@jupyterlab/apputils';
2
- import { BaseForm } from './baseform';
3
- import { getMimeType } from '../../tools';
2
+ import { getMimeType } from '../../../tools';
3
+ import { SourcePropertiesForm } from './sourceform';
4
+ import { FileSelectorWidget } from '../fileselectorwidget';
4
5
  /**
5
6
  * The form to modify a GeoTiff source.
6
7
  */
7
- export class GeoTiffSourcePropertiesForm extends BaseForm {
8
+ export class GeoTiffSourcePropertiesForm extends SourcePropertiesForm {
8
9
  constructor(props) {
9
10
  var _a, _b;
10
11
  super(props);
@@ -12,10 +13,22 @@ export class GeoTiffSourcePropertiesForm extends BaseForm {
12
13
  this._validateUrls((_b = (_a = props.sourceData) === null || _a === void 0 ? void 0 : _a.urls) !== null && _b !== void 0 ? _b : []);
13
14
  }
14
15
  processSchema(data, schema, uiSchema) {
16
+ var _a;
15
17
  super.processSchema(data, schema, uiSchema);
16
18
  if (!schema.properties || !data) {
17
19
  return;
18
20
  }
21
+ // Customize the widget for urls
22
+ if (schema.properties && schema.properties.urls) {
23
+ const docManager = (_a = this.props.formChangedSignal) === null || _a === void 0 ? void 0 : _a.sender.props.formSchemaRegistry.getDocManager();
24
+ uiSchema.urls = Object.assign(Object.assign({}, uiSchema.urls), { items: Object.assign(Object.assign({}, uiSchema.urls.items), { url: {
25
+ 'ui:widget': FileSelectorWidget,
26
+ 'ui:options': {
27
+ docManager,
28
+ formOptions: this.props
29
+ }
30
+ } }) });
31
+ }
19
32
  // This is not user-editable
20
33
  delete schema.properties.valid;
21
34
  }
@@ -0,0 +1,5 @@
1
+ export * from './geojsonsource';
2
+ export * from './geotiffsource';
3
+ export * from './pathbasedsource';
4
+ export * from './sourceform';
5
+ export * from './tilesourceform';
@@ -0,0 +1,5 @@
1
+ export * from './geojsonsource';
2
+ export * from './geotiffsource';
3
+ export * from './pathbasedsource';
4
+ export * from './sourceform';
5
+ export * from './tilesourceform';
@@ -1,11 +1,11 @@
1
1
  import { IDict } from '@jupytergis/schema';
2
2
  import { IChangeEvent, ISubmitEvent } from '@rjsf/core';
3
- import { BaseForm, IBaseFormProps } from './baseform';
3
+ import { ISourceFormProps, SourcePropertiesForm } from './sourceform';
4
4
  /**
5
5
  * The form to modify a PathBasedSource source.
6
6
  */
7
- export declare class PathBasedSourcePropertiesForm extends BaseForm {
8
- constructor(props: IBaseFormProps);
7
+ export declare class PathBasedSourcePropertiesForm extends SourcePropertiesForm {
8
+ constructor(props: ISourceFormProps);
9
9
  protected processSchema(data: IDict<any> | undefined, schema: IDict, uiSchema: IDict): void;
10
10
  protected onFormBlur(id: string, value: any): void;
11
11
  protected onFormChange(e: IChangeEvent): void;
@@ -1,11 +1,11 @@
1
1
  import { showErrorMessage } from '@jupyterlab/apputils';
2
- import { BaseForm } from './baseform';
3
- import { loadFile } from '../../tools';
4
- import { FileSelectorWidget } from './fileselectorwidget';
2
+ import { loadFile } from '../../../tools';
3
+ import { FileSelectorWidget } from '../fileselectorwidget';
4
+ import { SourcePropertiesForm } from './sourceform';
5
5
  /**
6
6
  * The form to modify a PathBasedSource source.
7
7
  */
8
- export class PathBasedSourcePropertiesForm extends BaseForm {
8
+ export class PathBasedSourcePropertiesForm extends SourcePropertiesForm {
9
9
  constructor(props) {
10
10
  var _a, _b;
11
11
  super(props);
@@ -0,0 +1,24 @@
1
+ import { IDict, SourceType } from '@jupytergis/schema';
2
+ import { BaseForm, IBaseFormProps } from '../baseform';
3
+ import { Signal } from '@lumino/signaling';
4
+ import { IChangeEvent } from '@rjsf/core';
5
+ export interface ISourceFormProps extends IBaseFormProps {
6
+ /**
7
+ * The source type for this form.
8
+ */
9
+ sourceType: SourceType;
10
+ /**
11
+ * The signal emitted when the source form has changed.
12
+ */
13
+ sourceFormChangedSignal?: Signal<any, IDict<any>>;
14
+ /**
15
+ * Configuration options for the dialog, including settings for source data and other parameters.
16
+ */
17
+ dialogOptions?: any;
18
+ }
19
+ export declare class SourcePropertiesForm extends BaseForm {
20
+ props: ISourceFormProps;
21
+ protected sourceFormChangedSignal: Signal<any, IDict<any>> | undefined;
22
+ constructor(props: ISourceFormProps);
23
+ protected onFormChange(e: IChangeEvent): void;
24
+ }
@@ -0,0 +1,13 @@
1
+ import { BaseForm } from '../baseform';
2
+ export class SourcePropertiesForm extends BaseForm {
3
+ constructor(props) {
4
+ super(props);
5
+ this.sourceFormChangedSignal = props.sourceFormChangedSignal;
6
+ }
7
+ onFormChange(e) {
8
+ super.onFormChange(e);
9
+ if (this.props.dialogOptions) {
10
+ this.props.dialogOptions.sourceData = Object.assign({}, e.formData);
11
+ }
12
+ }
13
+ }
@@ -1,6 +1,6 @@
1
1
  import { IDict } from '@jupytergis/schema';
2
- import { BaseForm } from './baseform';
3
- export declare class TileSourcePropertiesForm extends BaseForm {
2
+ import { SourcePropertiesForm } from './sourceform';
3
+ export declare class TileSourcePropertiesForm extends SourcePropertiesForm {
4
4
  private _urlParameters;
5
5
  protected processSchema(data: IDict<any> | undefined, schema: IDict, uiSchema: IDict): void;
6
6
  protected onFormBlur(id: string, value: any): void;
@@ -1,5 +1,5 @@
1
- import { BaseForm } from './baseform';
2
- export class TileSourcePropertiesForm extends BaseForm {
1
+ import { SourcePropertiesForm } from './sourceform';
2
+ export class TileSourcePropertiesForm extends SourcePropertiesForm {
3
3
  constructor() {
4
4
  super(...arguments);
5
5
  this._urlParameters = [];
package/lib/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export * from './classificationModes';
2
2
  export * from './commands';
3
3
  export * from './constants';
4
- export * from './dialogs/formdialog';
4
+ export * from './dialogs/layerCreationFormDialog';
5
5
  export * from './formbuilder/objectform/baseform';
6
6
  export * from './icons';
7
7
  export * from './mainview';
package/lib/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export * from './classificationModes';
2
2
  export * from './commands';
3
3
  export * from './constants';
4
- export * from './dialogs/formdialog';
4
+ export * from './dialogs/layerCreationFormDialog';
5
5
  export * from './formbuilder/objectform/baseform';
6
6
  export * from './icons';
7
7
  export * from './mainview';
@@ -14,6 +14,11 @@
14
14
  "keys": ["Escape"],
15
15
  "selector": ".data-jgis-keybinding"
16
16
  },
17
+ {
18
+ "command": "jupytergis:identify",
19
+ "keys": ["I"],
20
+ "selector": ".data-jgis-keybinding"
21
+ },
17
22
  {
18
23
  "command": "jupytergis:removeSource",
19
24
  "keys": ["Delete"],
@@ -311,7 +311,7 @@ export class MainView extends React.Component {
311
311
  if (this._model.getClientId() === clientId) {
312
312
  return;
313
313
  }
314
- const clientPointers = this.state.clientPointers;
314
+ const clientPointers = Object.assign({}, this.state.clientPointers);
315
315
  let currentClientPointer = clientPointers[clientId];
316
316
  if (pointer) {
317
317
  const pixel = this._Map.getPixelFromCoordinate([
@@ -320,7 +320,7 @@ export class MainView extends React.Component {
320
320
  ]);
321
321
  const lonLat = toLonLat([pointer.coordinates.x, pointer.coordinates.y]);
322
322
  if (!currentClientPointer) {
323
- currentClientPointer = clientPointers[clientId] = {
323
+ currentClientPointer = {
324
324
  username: client.user.username,
325
325
  displayName: client.user.display_name,
326
326
  color: client.user.color,
@@ -328,14 +328,15 @@ export class MainView extends React.Component {
328
328
  lonLat: { longitude: lonLat[0], latitude: lonLat[1] }
329
329
  };
330
330
  }
331
- currentClientPointer.coordinates.x = pixel[0];
332
- currentClientPointer.coordinates.y = pixel[1];
331
+ else {
332
+ currentClientPointer = Object.assign(Object.assign({}, currentClientPointer), { coordinates: { x: pixel[0], y: pixel[1] }, lonLat: { longitude: lonLat[0], latitude: lonLat[1] } });
333
+ }
333
334
  clientPointers[clientId] = currentClientPointer;
334
335
  }
335
336
  else {
336
337
  delete clientPointers[clientId];
337
338
  }
338
- this.setState(old => (Object.assign(Object.assign({}, old), { clientPointers: clientPointers })));
339
+ this.setState(old => (Object.assign(Object.assign({}, old), { clientPointers })));
339
340
  });
340
341
  // Temporal controller bit
341
342
  // ? There's probably a better way to get changes in the model to trigger react rerenders
@@ -575,9 +576,6 @@ export class MainView extends React.Component {
575
576
  */
576
577
  async addSource(id, source) {
577
578
  var _a, _b;
578
- const rasterSourceCommon = {
579
- interpolate: false
580
- };
581
579
  let newSource;
582
580
  switch (source.type) {
583
581
  case 'RasterSource': {
@@ -585,16 +583,32 @@ export class MainView extends React.Component {
585
583
  const pmTiles = sourceParameters.url.endsWith('.pmtiles');
586
584
  const url = this.computeSourceUrl(source);
587
585
  if (!pmTiles) {
588
- newSource = new XYZSource(Object.assign(Object.assign({}, rasterSourceCommon), { attributions: sourceParameters.attribution, minZoom: sourceParameters.minZoom, maxZoom: sourceParameters.maxZoom, tileSize: 256, url: url }));
586
+ newSource = new XYZSource({
587
+ interpolate: sourceParameters.interpolate,
588
+ attributions: sourceParameters.attribution,
589
+ minZoom: sourceParameters.minZoom,
590
+ maxZoom: sourceParameters.maxZoom,
591
+ tileSize: 256,
592
+ url: url
593
+ });
589
594
  }
590
595
  else {
591
- newSource = new PMTilesRasterSource(Object.assign(Object.assign({}, rasterSourceCommon), { attributions: sourceParameters.attribution, tileSize: 256, url: url }));
596
+ newSource = new PMTilesRasterSource({
597
+ interpolate: sourceParameters.interpolate,
598
+ attributions: sourceParameters.attribution,
599
+ tileSize: 256,
600
+ url: url
601
+ });
592
602
  }
593
603
  break;
594
604
  }
595
605
  case 'RasterDemSource': {
596
606
  const sourceParameters = source.parameters;
597
- newSource = new ImageTileSource(Object.assign(Object.assign({}, rasterSourceCommon), { url: this.computeSourceUrl(source), attributions: sourceParameters.attribution }));
607
+ newSource = new ImageTileSource({
608
+ interpolate: sourceParameters.interpolate,
609
+ url: this.computeSourceUrl(source),
610
+ attributions: sourceParameters.attribution
611
+ });
598
612
  break;
599
613
  }
600
614
  case 'VectorTileSource': {
@@ -681,7 +695,12 @@ export class MainView extends React.Component {
681
695
  type: 'ImageSource',
682
696
  model: this._model
683
697
  });
684
- newSource = new Static(Object.assign(Object.assign({}, rasterSourceCommon), { imageExtent: extent, url: imageUrl, crossOrigin: '' }));
698
+ newSource = new Static({
699
+ interpolate: sourceParameters.interpolate,
700
+ imageExtent: extent,
701
+ url: imageUrl,
702
+ crossOrigin: ''
703
+ });
685
704
  break;
686
705
  }
687
706
  case 'VideoSource': {
@@ -694,9 +713,27 @@ export class MainView extends React.Component {
694
713
  return Object.assign(Object.assign({}, url), { nodata: 0 });
695
714
  };
696
715
  const sources = await Promise.all(sourceParameters.urls.map(async (sourceInfo) => {
697
- return Object.assign(Object.assign({}, addNoData(sourceInfo)), { min: sourceInfo.min, max: sourceInfo.max, url: sourceInfo.url });
716
+ var _a, _b, _c;
717
+ const isRemote = ((_a = sourceInfo.url) === null || _a === void 0 ? void 0 : _a.startsWith('http://')) ||
718
+ ((_b = sourceInfo.url) === null || _b === void 0 ? void 0 : _b.startsWith('https://'));
719
+ if (isRemote) {
720
+ return Object.assign(Object.assign({}, addNoData(sourceInfo)), { min: sourceInfo.min, max: sourceInfo.max, url: sourceInfo.url });
721
+ }
722
+ else {
723
+ const geotiff = await loadFile({
724
+ filepath: (_c = sourceInfo.url) !== null && _c !== void 0 ? _c : '',
725
+ type: 'GeoTiffSource',
726
+ model: this._model
727
+ });
728
+ return Object.assign(Object.assign({}, addNoData(sourceInfo)), { min: sourceInfo.min, max: sourceInfo.max, geotiff, url: URL.createObjectURL(geotiff.file) });
729
+ }
698
730
  }));
699
- newSource = new GeoTIFFSource(Object.assign(Object.assign({}, rasterSourceCommon), { sources, normalize: sourceParameters.normalize, wrapX: sourceParameters.wrapX }));
731
+ newSource = new GeoTIFFSource({
732
+ interpolate: sourceParameters.interpolate,
733
+ sources,
734
+ normalize: sourceParameters.normalize,
735
+ wrapX: sourceParameters.wrapX
736
+ });
700
737
  break;
701
738
  }
702
739
  }
@@ -1382,7 +1419,7 @@ export class MainView extends React.Component {
1382
1419
  }),
1383
1420
  React.createElement("div", { className: "jGIS-Mainview-Container" },
1384
1421
  this.state.displayTemporalController && (React.createElement(TemporalSlider, { model: this._model, filterStates: this.state.filterStates })),
1385
- React.createElement("div", { className: "jGIS-Mainview", style: {
1422
+ React.createElement("div", { className: "jGIS-Mainview data-jgis-keybinding", tabIndex: -2, style: {
1386
1423
  border: this.state.remoteUser
1387
1424
  ? `solid 3px ${this.state.remoteUser.color}`
1388
1425
  : 'unset'
@@ -12,7 +12,7 @@ const LAYER_ITEM_CLASS = 'jp-gis-layerItem';
12
12
  const LAYER_CLASS = 'jp-gis-layer';
13
13
  const LAYER_TITLE_CLASS = 'jp-gis-layerTitle';
14
14
  const LAYER_ICON_CLASS = 'jp-gis-layerIcon';
15
- const LAYER_TEXT_CLASS = 'jp-gis-layerText';
15
+ const LAYER_TEXT_CLASS = 'jp-gis-layerText data-jgis-keybinding';
16
16
  /**
17
17
  * The layers panel widget.
18
18
  */
@@ -162,7 +162,7 @@ function LayerGroupComponent(props) {
162
162
  return (React.createElement("div", { className: `${LAYER_ITEM_CLASS} ${LAYER_GROUP_CLASS}`, draggable: true, onDragStart: Private.onDragStart, onDragEnd: Private.onDragEnd, "data-id": name },
163
163
  React.createElement("div", { onClick: handleExpand, onContextMenu: handleRightClick, className: `${LAYER_GROUP_HEADER_CLASS}${selected ? ' jp-mod-selected' : ''}`, onDragOver: Private.onDragOver, "data-id": name },
164
164
  React.createElement(LabIcon.resolveReact, { icon: caretDownIcon, className: `${LAYER_GROUP_COLLAPSER_CLASS}${open ? ' jp-mod-expanded' : ''}`, tag: 'span' }),
165
- React.createElement("span", { id: id, className: LAYER_TEXT_CLASS }, name)),
165
+ React.createElement("span", { id: id, className: LAYER_TEXT_CLASS, tabIndex: -2 }, name)),
166
166
  open && (React.createElement("div", null, layers
167
167
  .slice()
168
168
  .reverse()
@@ -219,10 +219,10 @@ function LayerComponent(props) {
219
219
  };
220
220
  return (React.createElement("div", { className: `${LAYER_ITEM_CLASS} ${LAYER_CLASS}${selected ? ' jp-mod-selected' : ''}`, draggable: true, onDragStart: Private.onDragStart, onDragOver: Private.onDragOver, onDragEnd: Private.onDragEnd, "data-id": layerId },
221
221
  React.createElement("div", { className: LAYER_TITLE_CLASS, onClick: setSelection, onContextMenu: setSelection },
222
+ React.createElement(Button, { title: layer.visible ? 'Hide layer' : 'Show layer', onClick: toggleVisibility, minimal: true },
223
+ React.createElement(LabIcon.resolveReact, { icon: layer.visible ? visibilityIcon : nonVisibilityIcon, className: `${LAYER_ICON_CLASS}${layer.visible ? '' : ' jp-gis-mod-hidden'}`, tag: "span" })),
222
224
  icons.has(layer.type) && (React.createElement(LabIcon.resolveReact, Object.assign({}, icons.get(layer.type), { className: LAYER_ICON_CLASS }))),
223
- React.createElement("span", { id: id, className: LAYER_TEXT_CLASS }, name)),
224
- React.createElement(Button, { title: layer.visible ? 'Hide layer' : 'Show layer', onClick: toggleVisibility, minimal: true },
225
- React.createElement(LabIcon.resolveReact, { icon: layer.visible ? visibilityIcon : nonVisibilityIcon, className: `${LAYER_ICON_CLASS}${layer.visible ? '' : ' jp-gis-mod-hidden'}`, tag: "span" }))));
225
+ React.createElement("span", { id: id, className: LAYER_TEXT_CLASS, tabIndex: -2 }, name))));
226
226
  }
227
227
  var Private;
228
228
  (function (Private) {
@@ -35,6 +35,7 @@ export declare class LeftPanelWidget extends SidePanel {
35
35
  */
36
36
  private _onSelect;
37
37
  resetSelected(type: SelectionType, nodeId?: string, item?: string): void;
38
+ private _notifyCommands;
38
39
  private _lastSelectedNodeId;
39
40
  private _model;
40
41
  private _state;
@@ -51,10 +51,12 @@ export class LeftPanelWidget extends SidePanel {
51
51
  const updatedSelectedValue = Object.assign(Object.assign({}, selectedValue), { [item]: { type, selectedNodeId: nodeId } });
52
52
  this._lastSelectedNodeId = nodeId;
53
53
  jGISModel.syncSelected(updatedSelectedValue, this.id);
54
- this._commands.notifyCommandChanged(CommandIDs.temporalController);
54
+ this._notifyCommands();
55
55
  }
56
56
  };
57
57
  this.addClass('jGIS-sidepanel-widget');
58
+ this.addClass('data-jgis-keybinding');
59
+ this.node.tabIndex = 0;
58
60
  this._model = options.model;
59
61
  this._state = options.state;
60
62
  this._commands = options.commands;
@@ -132,6 +134,11 @@ export class LeftPanelWidget extends SidePanel {
132
134
  this._lastSelectedNodeId = nodeId;
133
135
  }
134
136
  (_b = (_a = this._model) === null || _a === void 0 ? void 0 : _a.jGISModel) === null || _b === void 0 ? void 0 : _b.syncSelected(selection, this.id);
137
+ this._notifyCommands();
138
+ }
139
+ _notifyCommands() {
140
+ // Notify commands that need updating
141
+ this._commands.notifyCommandChanged(CommandIDs.identify);
135
142
  this._commands.notifyCommandChanged(CommandIDs.temporalController);
136
143
  }
137
144
  }
@@ -7,6 +7,8 @@ export class RightPanelWidget extends SidePanel {
7
7
  constructor(options) {
8
8
  super();
9
9
  this.addClass('jGIS-sidepanel-widget');
10
+ this.addClass('data-jgis-keybinding');
11
+ this.node.tabIndex = 0;
10
12
  this._model = options.model;
11
13
  this._annotationModel = options.annotationModel;
12
14
  const header = new ControlPanelHeader();
@@ -0,0 +1,25 @@
1
+ import { IDict, IJGISLayer, IJupyterGISModel, IJGISFormSchemaRegistry, LayerType } from '@jupytergis/schema';
2
+ import { JupyterGISTracker } from './types';
3
+ import { JupyterFrontEnd } from '@jupyterlab/application';
4
+ /**
5
+ * Get the currently selected layer from the shared model. Returns null if there is no selection or multiple layer is selected.
6
+ */
7
+ export declare function getSingleSelectedLayer(tracker: JupyterGISTracker): IJGISLayer | null;
8
+ /**
9
+ * Check if the selected layer is of one of the specified types
10
+ */
11
+ export declare function selectedLayerIsOfType(allowedTypes: LayerType[], tracker: JupyterGISTracker): boolean;
12
+ /**
13
+ * Extract GeoJSON from selected layer's source
14
+ */
15
+ export declare function getLayerGeoJSON(layer: IJGISLayer, sources: IDict, model: IJupyterGISModel): Promise<string | null>;
16
+ export type GdalFunctions = 'ogr2ogr' | 'gdal_rasterize' | 'gdalwarp' | 'gdal_translate';
17
+ /**
18
+ * Generalized processing function for Buffer & Dissolve
19
+ */
20
+ export declare function processSelectedLayer(tracker: JupyterGISTracker, formSchemaRegistry: IJGISFormSchemaRegistry, processingType: 'Buffer' | 'Dissolve', processingOptions: {
21
+ sqlQueryFn: (layerName: string, param: any) => string;
22
+ gdalFunction: GdalFunctions;
23
+ options: (sqlQuery: string) => string[];
24
+ }, app: JupyterFrontEnd): Promise<void>;
25
+ export declare function executeSQLProcessing(model: IJupyterGISModel, geojsonString: string, gdalFunction: GdalFunctions, options: string[], layerNamePrefix: string, processingType: 'Buffer' | 'Dissolve', embedOutputLayer: boolean, tracker: JupyterGISTracker, app: JupyterFrontEnd): Promise<void>;
@@ -0,0 +1,177 @@
1
+ import { getGdal } from './gdal';
2
+ import { UUID } from '@lumino/coreutils';
3
+ import { ProcessingFormDialog } from './dialogs/ProcessingFormDialog';
4
+ import { getGeoJSONDataFromLayerSource } from './tools';
5
+ /**
6
+ * Get the currently selected layer from the shared model. Returns null if there is no selection or multiple layer is selected.
7
+ */
8
+ export function getSingleSelectedLayer(tracker) {
9
+ var _a, _b, _c;
10
+ const model = (_a = tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.model;
11
+ if (!model) {
12
+ return null;
13
+ }
14
+ const localState = model.sharedModel.awareness.getLocalState();
15
+ if (!localState || !((_b = localState['selected']) === null || _b === void 0 ? void 0 : _b.value)) {
16
+ return null;
17
+ }
18
+ const selectedLayers = Object.keys(localState['selected'].value);
19
+ // Ensure only one layer is selected
20
+ if (selectedLayers.length !== 1) {
21
+ return null;
22
+ }
23
+ const selectedLayerId = selectedLayers[0];
24
+ const layers = (_c = model.sharedModel.layers) !== null && _c !== void 0 ? _c : {};
25
+ const selectedLayer = layers[selectedLayerId];
26
+ return selectedLayer && selectedLayer.parameters ? selectedLayer : null;
27
+ }
28
+ /**
29
+ * Check if the selected layer is of one of the specified types
30
+ */
31
+ export function selectedLayerIsOfType(allowedTypes, tracker) {
32
+ const selectedLayer = getSingleSelectedLayer(tracker);
33
+ return selectedLayer ? allowedTypes.includes(selectedLayer.type) : false;
34
+ }
35
+ /**
36
+ * Extract GeoJSON from selected layer's source
37
+ */
38
+ export async function getLayerGeoJSON(layer, sources, model) {
39
+ if (!layer.parameters || !layer.parameters.source) {
40
+ console.error('Selected layer does not have a valid source.');
41
+ return null;
42
+ }
43
+ const source = sources[layer.parameters.source];
44
+ if (!source || !source.parameters) {
45
+ console.error(`Source with ID ${layer.parameters.source} not found or missing path.`);
46
+ return null;
47
+ }
48
+ return await getGeoJSONDataFromLayerSource(source, model);
49
+ }
50
+ /**
51
+ * Generalized processing function for Buffer & Dissolve
52
+ */
53
+ export async function processSelectedLayer(tracker, formSchemaRegistry, processingType, processingOptions, app) {
54
+ var _a, _b, _c;
55
+ const selected = getSingleSelectedLayer(tracker);
56
+ if (!selected || !tracker.currentWidget) {
57
+ return;
58
+ }
59
+ const model = tracker.currentWidget.model;
60
+ const sources = (_a = model === null || model === void 0 ? void 0 : model.sharedModel.sources) !== null && _a !== void 0 ? _a : {};
61
+ const geojsonString = await getLayerGeoJSON(selected, sources, model);
62
+ if (!geojsonString) {
63
+ return;
64
+ }
65
+ const schema = Object.assign({}, formSchemaRegistry.getSchemas().get(processingType));
66
+ const selectedLayerId = Object.keys(((_c = (_b = model === null || model === void 0 ? void 0 : model.sharedModel.awareness.getLocalState()) === null || _b === void 0 ? void 0 : _b.selected) === null || _c === void 0 ? void 0 : _c.value) || {})[0];
67
+ // Open ProcessingFormDialog
68
+ const formValues = await new Promise(resolve => {
69
+ const dialog = new ProcessingFormDialog({
70
+ title: processingType.charAt(0).toUpperCase() + processingType.slice(1),
71
+ schema,
72
+ model,
73
+ sourceData: {
74
+ inputLayer: selectedLayerId,
75
+ outputLayerName: selected.name
76
+ },
77
+ formContext: 'create',
78
+ processingType,
79
+ syncData: (props) => {
80
+ resolve(props);
81
+ dialog.dispose();
82
+ }
83
+ });
84
+ dialog.launch();
85
+ });
86
+ if (!formValues) {
87
+ return;
88
+ }
89
+ let processParam;
90
+ switch (processingType) {
91
+ case 'Buffer':
92
+ processParam = formValues.bufferDistance;
93
+ break;
94
+ case 'Dissolve':
95
+ processParam = formValues.dissolveField;
96
+ break;
97
+ default:
98
+ console.error(`Unsupported processing type: ${processingType}`);
99
+ return;
100
+ }
101
+ const embedOutputLayer = formValues.embedOutputLayer;
102
+ const fileBlob = new Blob([geojsonString], {
103
+ type: 'application/geo+json'
104
+ });
105
+ const geoFile = new File([fileBlob], 'data.geojson', {
106
+ type: 'application/geo+json'
107
+ });
108
+ const Gdal = await getGdal();
109
+ const result = await Gdal.open(geoFile);
110
+ const dataset = result.datasets[0];
111
+ const layerName = dataset.info.layers[0].name;
112
+ const sqlQuery = processingOptions.sqlQueryFn(layerName, processParam);
113
+ const fullOptions = processingOptions.options(sqlQuery);
114
+ await executeSQLProcessing(model, geojsonString, processingOptions.gdalFunction, fullOptions, formValues.outputLayerName, processingType, embedOutputLayer, tracker, app);
115
+ }
116
+ export async function executeSQLProcessing(model, geojsonString, gdalFunction, options, layerNamePrefix, processingType, embedOutputLayer, tracker, app) {
117
+ var _a;
118
+ const geoFile = new File([new Blob([geojsonString], { type: 'application/geo+json' })], 'data.geojson', { type: 'application/geo+json' });
119
+ const Gdal = await getGdal();
120
+ const result = await Gdal.open(geoFile);
121
+ if (result.datasets.length === 0) {
122
+ return;
123
+ }
124
+ const dataset = result.datasets[0];
125
+ const outputFilePath = await Gdal[gdalFunction](dataset, options);
126
+ const processedBytes = await Gdal.getFileBytes(outputFilePath);
127
+ const processedGeoJSONString = new TextDecoder().decode(processedBytes);
128
+ Gdal.close(dataset);
129
+ if (!embedOutputLayer) {
130
+ // Save the output as a file
131
+ const jgisFilePath = (_a = tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.model.filePath;
132
+ const jgisDir = jgisFilePath
133
+ ? jgisFilePath.substring(0, jgisFilePath.lastIndexOf('/'))
134
+ : '';
135
+ const outputFileName = `${layerNamePrefix}_${processingType}.json`;
136
+ const savePath = jgisDir ? `${jgisDir}/${outputFileName}` : outputFileName;
137
+ await app.serviceManager.contents.save(savePath, {
138
+ type: 'file',
139
+ format: 'text',
140
+ content: processedGeoJSONString
141
+ });
142
+ const newSourceId = UUID.uuid4();
143
+ const sourceModel = {
144
+ type: 'GeoJSONSource',
145
+ name: outputFileName,
146
+ parameters: {
147
+ path: outputFileName
148
+ }
149
+ };
150
+ const layerModel = {
151
+ type: 'VectorLayer',
152
+ parameters: { source: newSourceId },
153
+ visible: true,
154
+ name: outputFileName
155
+ };
156
+ model.sharedModel.addSource(newSourceId, sourceModel);
157
+ model.addLayer(UUID.uuid4(), layerModel);
158
+ }
159
+ else {
160
+ // Embed the output directly into the model
161
+ const processedGeoJSON = JSON.parse(processedGeoJSONString);
162
+ const newSourceId = UUID.uuid4();
163
+ const sourceModel = {
164
+ type: 'GeoJSONSource',
165
+ name: `${layerNamePrefix} ${processingType.charAt(0).toUpperCase() + processingType.slice(1)}`,
166
+ parameters: { data: processedGeoJSON }
167
+ };
168
+ const layerModel = {
169
+ type: 'VectorLayer',
170
+ parameters: { source: newSourceId },
171
+ visible: true,
172
+ name: `${layerNamePrefix} ${processingType.charAt(0).toUpperCase() + processingType.slice(1)}`
173
+ };
174
+ model.sharedModel.addSource(newSourceId, sourceModel);
175
+ model.addLayer(UUID.uuid4(), layerModel);
176
+ }
177
+ }