@jupytergis/base 0.2.0 → 0.3.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.
Files changed (49) hide show
  1. package/lib/annotations/components/Annotation.js +1 -1
  2. package/lib/commands.js +30 -1
  3. package/lib/constants.d.ts +1 -0
  4. package/lib/constants.js +1 -0
  5. package/lib/dialogs/formdialog.d.ts +5 -0
  6. package/lib/dialogs/formdialog.js +2 -2
  7. package/lib/dialogs/symbology/components/color_ramp/ModeSelectRow.js +2 -1
  8. package/lib/dialogs/symbology/hooks/useGetBandInfo.d.ts +27 -0
  9. package/lib/dialogs/symbology/hooks/useGetBandInfo.js +59 -0
  10. package/lib/dialogs/symbology/hooks/useGetProperties.js +6 -1
  11. package/lib/dialogs/symbology/tiff_layer/TiffRendering.d.ts +1 -1
  12. package/lib/dialogs/symbology/tiff_layer/TiffRendering.js +14 -1
  13. package/lib/dialogs/symbology/tiff_layer/components/BandRow.d.ts +16 -3
  14. package/lib/dialogs/symbology/tiff_layer/components/BandRow.js +21 -7
  15. package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.d.ts +4 -0
  16. package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.js +84 -0
  17. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.d.ts +0 -19
  18. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +18 -59
  19. package/lib/formbuilder/creationform.d.ts +5 -0
  20. package/lib/formbuilder/creationform.js +2 -2
  21. package/lib/formbuilder/editform.d.ts +1 -0
  22. package/lib/formbuilder/editform.js +8 -3
  23. package/lib/formbuilder/formselectors.js +7 -0
  24. package/lib/formbuilder/objectform/baseform.d.ts +10 -0
  25. package/lib/formbuilder/objectform/baseform.js +39 -0
  26. package/lib/formbuilder/objectform/fileselectorwidget.d.ts +2 -0
  27. package/lib/formbuilder/objectform/fileselectorwidget.js +81 -0
  28. package/lib/formbuilder/objectform/geojsonsource.d.ts +5 -7
  29. package/lib/formbuilder/objectform/geojsonsource.js +8 -24
  30. package/lib/formbuilder/objectform/layerform.d.ts +2 -0
  31. package/lib/formbuilder/objectform/layerform.js +6 -0
  32. package/lib/formbuilder/objectform/pathbasedsource.d.ts +19 -0
  33. package/lib/formbuilder/objectform/pathbasedsource.js +98 -0
  34. package/lib/icons.d.ts +1 -0
  35. package/lib/icons.js +5 -0
  36. package/lib/keybindings.json +62 -0
  37. package/lib/mainview/mainView.d.ts +22 -5
  38. package/lib/mainview/mainView.js +228 -76
  39. package/lib/panelview/components/filter-panel/Filter.js +6 -1
  40. package/lib/statusbar/StatusBar.d.ts +13 -0
  41. package/lib/statusbar/StatusBar.js +52 -0
  42. package/lib/toolbar/widget.js +0 -5
  43. package/lib/tools.d.ts +40 -1
  44. package/lib/tools.js +308 -0
  45. package/package.json +16 -5
  46. package/style/base.css +1 -0
  47. package/style/icons/logo_mini_qgz.svg +31 -0
  48. package/style/leftPanel.css +8 -0
  49. package/style/statusBar.css +16 -0
@@ -1,37 +1,106 @@
1
1
  import { JupyterGISModel } from '@jupytergis/schema';
2
+ import { CommandRegistry } from '@lumino/commands';
2
3
  import { UUID } from '@lumino/coreutils';
3
- import { Collection, Map as OlMap, View } from 'ol';
4
+ import { ContextMenu } from '@lumino/widgets';
5
+ import { Collection, Map as OlMap, View, getUid } from 'ol';
6
+ //@ts-expect-error no types for ol-pmtiles
7
+ import { PMTilesRasterSource, PMTilesVectorSource } from 'ol-pmtiles';
4
8
  import { ScaleLine } from 'ol/control';
9
+ import { singleClick } from 'ol/events/condition';
5
10
  import { GeoJSON, MVT } from 'ol/format';
6
11
  import { DragAndDrop, Select } from 'ol/interaction';
7
12
  import { Image as ImageLayer, Vector as VectorLayer, VectorTile as VectorTileLayer, WebGLTile as WebGlTileLayer } from 'ol/layer';
8
13
  import TileLayer from 'ol/layer/Tile';
9
- import { fromLonLat, toLonLat } from 'ol/proj';
14
+ import { fromLonLat, get as getRegisteredProjection, toLonLat, transformExtent } from 'ol/proj';
15
+ import { get as getProjection } from 'ol/proj.js';
16
+ import { register } from 'ol/proj/proj4.js';
10
17
  import Feature from 'ol/render/Feature';
11
18
  import { GeoTIFF as GeoTIFFSource, ImageTile as ImageTileSource, Vector as VectorSource, VectorTile as VectorTileSource, XYZ as XYZSource } from 'ol/source';
12
19
  import Static from 'ol/source/ImageStatic';
13
- //@ts-expect-error no types for ol-pmtiles
14
- import { PMTilesRasterSource, PMTilesVectorSource } from 'ol-pmtiles';
15
- import { register } from 'ol/proj/proj4.js';
16
- import { get as getProjection } from 'ol/proj.js';
20
+ import TileSource from 'ol/source/Tile';
21
+ import { Circle, Fill, Stroke, Style } from 'ol/style';
17
22
  import proj4 from 'proj4';
18
- import * as React from 'react';
19
- import shp from 'shpjs';
20
- import { isLightTheme, loadGeoTIFFWithCache, throttle } from '../tools';
21
- import { Spinner } from './spinner';
22
- //@ts-expect-error no types for proj4-list
23
+ //@ts-expect-error no types for proj4list
23
24
  import proj4list from 'proj4-list';
24
- import { ContextMenu } from '@lumino/widgets';
25
- import { CommandRegistry } from '@lumino/commands';
25
+ import * as React from 'react';
26
26
  import AnnotationFloater from '../annotations/components/AnnotationFloater';
27
27
  import { CommandIDs } from '../constants';
28
- import { FollowIndicator } from './FollowIndicator';
28
+ import StatusBar from '../statusbar/StatusBar';
29
+ import { isLightTheme, loadFile, loadGeoTIFFWithCache, throttle } from '../tools';
29
30
  import CollaboratorPointers from './CollaboratorPointers';
30
- import { Circle, Fill, Stroke, Style } from 'ol/style';
31
- import { singleClick } from 'ol/events/condition';
31
+ import { FollowIndicator } from './FollowIndicator';
32
+ import { Spinner } from './spinner';
32
33
  export class MainView extends React.Component {
33
34
  constructor(props) {
34
35
  super(props);
36
+ this.createSelectInteraction = () => {
37
+ const pointStyle = new Style({
38
+ image: new Circle({
39
+ radius: 5,
40
+ fill: new Fill({
41
+ color: '#C52707'
42
+ }),
43
+ stroke: new Stroke({
44
+ color: '#171717',
45
+ width: 2
46
+ })
47
+ })
48
+ });
49
+ const lineStyle = new Style({
50
+ stroke: new Stroke({
51
+ color: '#171717',
52
+ width: 2
53
+ })
54
+ });
55
+ const polygonStyle = new Style({
56
+ fill: new Fill({ color: '#C5270780' }),
57
+ stroke: new Stroke({
58
+ color: '#171717',
59
+ width: 2
60
+ })
61
+ });
62
+ const styleFunction = (feature) => {
63
+ var _a;
64
+ const geometryType = (_a = feature.getGeometry()) === null || _a === void 0 ? void 0 : _a.getType();
65
+ switch (geometryType) {
66
+ case 'Point':
67
+ case 'MultiPoint':
68
+ return pointStyle;
69
+ case 'LineString':
70
+ case 'MultiLineString':
71
+ return lineStyle;
72
+ case 'Polygon':
73
+ case 'MultiPolygon':
74
+ return polygonStyle;
75
+ }
76
+ };
77
+ const selectInteraction = new Select({
78
+ hitTolerance: 5,
79
+ multi: true,
80
+ layers: layer => {
81
+ var _a, _b;
82
+ const localState = (_a = this._model) === null || _a === void 0 ? void 0 : _a.sharedModel.awareness.getLocalState();
83
+ const selectedLayers = (_b = localState === null || localState === void 0 ? void 0 : localState.selected) === null || _b === void 0 ? void 0 : _b.value;
84
+ if (!selectedLayers) {
85
+ return false;
86
+ }
87
+ const selectedLayerId = Object.keys(selectedLayers)[0];
88
+ return layer === this.getLayer(selectedLayerId);
89
+ },
90
+ condition: (event) => {
91
+ return singleClick(event) && this._model.isIdentifying;
92
+ },
93
+ style: styleFunction
94
+ });
95
+ selectInteraction.on('select', event => {
96
+ const identifiedFeatures = [];
97
+ selectInteraction.getFeatures().forEach(feature => {
98
+ identifiedFeatures.push(feature.getProperties());
99
+ });
100
+ this._model.syncIdentifiedFeatures(identifiedFeatures, this._mainViewModel.id);
101
+ });
102
+ this._Map.addInteraction(selectInteraction);
103
+ };
35
104
  this.addContextMenu = () => {
36
105
  this._commands.addCommand(CommandIDs.addAnnotation, {
37
106
  execute: () => {
@@ -278,8 +347,6 @@ export class MainView extends React.Component {
278
347
  this.divRef = React.createRef(); // Reference of render div
279
348
  this._ready = false;
280
349
  this._sourceToLayerMap = new Map();
281
- proj4.defs(Array.from(proj4list));
282
- register(proj4);
283
350
  this._mainViewModel = this.props.viewModel;
284
351
  this._mainViewModel.viewSettingChanged.connect(this._onViewChanged, this);
285
352
  this._model = this._mainViewModel.jGISModel;
@@ -291,16 +358,20 @@ export class MainView extends React.Component {
291
358
  this._model.sharedSourcesChanged.connect(this._onSourcesChange, this);
292
359
  this._model.sharedModel.changed.connect(this._onSharedModelStateChange);
293
360
  this._mainViewModel.jGISModel.sharedMetadataChanged.connect(this._onSharedMetadataChanged, this);
294
- this._model.zoomToAnnotationSignal.connect(this._onZoomToAnnotation, this);
361
+ this._model.zoomToPositionSignal.connect(this._onZoomToPosition, this);
295
362
  this.state = {
296
363
  id: this._mainViewModel.id,
297
364
  lightTheme: isLightTheme(),
298
365
  loading: true,
299
366
  firstLoad: true,
300
367
  annotations: {},
301
- clientPointers: {}
368
+ clientPointers: {},
369
+ viewProjection: { code: '', units: '' },
370
+ loadingLayer: false,
371
+ scale: 0
302
372
  };
303
373
  this._sources = [];
374
+ this._loadingLayers = new Set();
304
375
  this._commands = new CommandRegistry();
305
376
  this._contextMenu = new ContextMenu({ commands: this._commands });
306
377
  }
@@ -364,43 +435,7 @@ export class MainView extends React.Component {
364
435
  this._model.addLayer(layerId, layerModel);
365
436
  });
366
437
  this._Map.addInteraction(dragAndDropInteraction);
367
- const selectInteraction = new Select({
368
- hitTolerance: 5,
369
- multi: true,
370
- layers: layer => {
371
- var _a, _b;
372
- const localState = (_a = this._model) === null || _a === void 0 ? void 0 : _a.sharedModel.awareness.getLocalState();
373
- const selectedLayers = (_b = localState === null || localState === void 0 ? void 0 : localState.selected) === null || _b === void 0 ? void 0 : _b.value;
374
- if (!selectedLayers) {
375
- return false;
376
- }
377
- const selectedLayerId = Object.keys(selectedLayers)[0];
378
- return layer === this.getLayer(selectedLayerId);
379
- },
380
- condition: (event) => {
381
- return singleClick(event) && this._model.isIdentifying;
382
- },
383
- style: new Style({
384
- image: new Circle({
385
- radius: 5,
386
- fill: new Fill({
387
- color: '#C52707'
388
- }),
389
- stroke: new Stroke({
390
- color: '#171717',
391
- width: 2
392
- })
393
- })
394
- })
395
- });
396
- selectInteraction.on('select', event => {
397
- const identifiedFeatures = [];
398
- selectInteraction.getFeatures().forEach(feature => {
399
- identifiedFeatures.push(feature.getProperties());
400
- });
401
- this._model.syncIdentifiedFeatures(identifiedFeatures, this._mainViewModel.id);
402
- });
403
- this._Map.addInteraction(selectInteraction);
438
+ this.createSelectInteraction();
404
439
  const view = this._Map.getView();
405
440
  // TODO: Note for the future, will need to update listeners if view changes
406
441
  view.on('change:center', throttle(() => {
@@ -433,6 +468,7 @@ export class MainView extends React.Component {
433
468
  const projection = view.getProjection();
434
469
  const latLng = toLonLat(center, projection);
435
470
  const bearing = view.getRotation();
471
+ const resolution = view.getResolution();
436
472
  const updatedOptions = {
437
473
  latitude: latLng[1],
438
474
  longitude: latLng[0],
@@ -442,6 +478,14 @@ export class MainView extends React.Component {
442
478
  };
443
479
  updatedOptions.extent = view.calculateExtent();
444
480
  this._model.setOptions(Object.assign(Object.assign({}, currentOptions), updatedOptions));
481
+ // Calculate scale
482
+ if (resolution) {
483
+ // DPI and inches per meter values taken from OpenLayers
484
+ const dpi = 25.4 / 0.28;
485
+ const inchesPerMeter = 1000 / 25.4;
486
+ const scale = resolution * inchesPerMeter * dpi;
487
+ this.setState(old => (Object.assign(Object.assign({}, old), { scale })));
488
+ }
445
489
  });
446
490
  this._Map.on('click', this._identifyFeature.bind(this));
447
491
  this._Map
@@ -460,19 +504,10 @@ export class MainView extends React.Component {
460
504
  this._clickCoords = coordinate;
461
505
  this._contextMenu.open(event);
462
506
  });
463
- this.setState(old => (Object.assign(Object.assign({}, old), { loading: false })));
464
- }
465
- }
466
- async _loadShapefileAsGeoJSON(url) {
467
- try {
468
- const response = await fetch(`/jupytergis_core/proxy?url=${url}`);
469
- const arrayBuffer = await response.arrayBuffer();
470
- const geojson = await shp(arrayBuffer);
471
- return geojson;
472
- }
473
- catch (error) {
474
- console.error('Error loading shapefile:', error);
475
- throw error;
507
+ this.setState(old => (Object.assign(Object.assign({}, old), { loading: false, viewProjection: {
508
+ code: view.getProjection().getCode(),
509
+ units: view.getProjection().getUnits()
510
+ } })));
476
511
  }
477
512
  }
478
513
  async _loadGeoTIFFWithCache(sourceInfo) {
@@ -542,7 +577,11 @@ export class MainView extends React.Component {
542
577
  }
543
578
  case 'GeoJSONSource': {
544
579
  const data = ((_a = source.parameters) === null || _a === void 0 ? void 0 : _a.data) ||
545
- (await this._model.readGeoJSON((_b = source.parameters) === null || _b === void 0 ? void 0 : _b.path));
580
+ (await loadFile({
581
+ filepath: (_b = source.parameters) === null || _b === void 0 ? void 0 : _b.path,
582
+ type: 'GeoJSONSource',
583
+ model: this._model
584
+ }));
546
585
  const format = new GeoJSON({
547
586
  featureProjection: this._Map.getView().getProjection()
548
587
  });
@@ -552,6 +591,9 @@ export class MainView extends React.Component {
552
591
  featureProjection: this._Map.getView().getProjection()
553
592
  });
554
593
  const featureCollection = new Collection(featureArray);
594
+ featureCollection.forEach(feature => {
595
+ feature.setId(getUid(feature));
596
+ });
555
597
  newSource = new VectorSource({
556
598
  features: featureCollection
557
599
  });
@@ -559,7 +601,11 @@ export class MainView extends React.Component {
559
601
  }
560
602
  case 'ShapefileSource': {
561
603
  const parameters = source.parameters;
562
- const geojson = await this._loadShapefileAsGeoJSON(parameters.path);
604
+ const geojson = await loadFile({
605
+ filepath: parameters.path,
606
+ type: 'ShapefileSource',
607
+ model: this._model
608
+ });
563
609
  const geojsonData = Array.isArray(geojson) ? geojson[0] : geojson;
564
610
  const format = new GeoJSON();
565
611
  newSource = new VectorSource({
@@ -587,10 +633,15 @@ export class MainView extends React.Component {
587
633
  const maxX = bottomRight[0];
588
634
  const minY = bottomRight[1];
589
635
  const extent = [minX, minY, maxX, maxY];
636
+ const imageUrl = await loadFile({
637
+ filepath: sourceParameters.path,
638
+ type: 'ImageSource',
639
+ model: this._model
640
+ });
590
641
  newSource = new Static({
591
642
  imageExtent: extent,
592
- url: sourceParameters.url,
593
- interpolate: true,
643
+ url: imageUrl,
644
+ interpolate: false,
594
645
  crossOrigin: ''
595
646
  });
596
647
  break;
@@ -728,9 +779,12 @@ export class MainView extends React.Component {
728
779
  if (!source) {
729
780
  return;
730
781
  }
782
+ this.setState(old => (Object.assign(Object.assign({}, old), { loadingLayer: true })));
783
+ this._loadingLayers.add(id);
731
784
  if (!this._sources[sourceId]) {
732
785
  await this.addSource(sourceId, source, id);
733
786
  }
787
+ this._loadingLayers.add(id);
734
788
  let newMapLayer;
735
789
  let layerParameters;
736
790
  // TODO: OpenLayers provides a bunch of sources for specific tile
@@ -797,12 +851,40 @@ export class MainView extends React.Component {
797
851
  break;
798
852
  }
799
853
  }
854
+ await this._waitForSourceReady(newMapLayer);
800
855
  // OpenLayers doesn't have name/id field so add it
801
856
  newMapLayer.set('id', id);
802
857
  // we need to keep track of which source has which layers
803
858
  this._sourceToLayerMap.set(layerParameters.source, id);
859
+ this.addProjection(newMapLayer);
860
+ this._loadingLayers.delete(id);
804
861
  return newMapLayer;
805
862
  }
863
+ addProjection(newMapLayer) {
864
+ var _a;
865
+ const sourceProjection = (_a = newMapLayer.getSource()) === null || _a === void 0 ? void 0 : _a.getProjection();
866
+ if (!sourceProjection) {
867
+ console.warn('Layer source projection is undefined or invalid');
868
+ return;
869
+ }
870
+ const projectionCode = sourceProjection.getCode();
871
+ const isProjectionRegistered = getRegisteredProjection(projectionCode);
872
+ if (!isProjectionRegistered) {
873
+ // Check if the projection exists in proj4list
874
+ if (!proj4list[projectionCode]) {
875
+ console.warn(`Projection code '${projectionCode}' not found in proj4list`);
876
+ return;
877
+ }
878
+ try {
879
+ proj4.defs([proj4list[projectionCode]]);
880
+ register(proj4);
881
+ }
882
+ catch (error) {
883
+ console.warn(`Failed to register projection '${projectionCode}'. Error: ${error.message}`);
884
+ return;
885
+ }
886
+ }
887
+ }
806
888
  /**
807
889
  * Add a layer to the map.
808
890
  *
@@ -817,6 +899,7 @@ export class MainView extends React.Component {
817
899
  }
818
900
  const newMapLayer = await this._buildMapLayer(id, layer);
819
901
  if (newMapLayer !== undefined) {
902
+ await this._waitForReady();
820
903
  this._Map.getLayers().insertAt(index, newMapLayer);
821
904
  }
822
905
  }
@@ -872,6 +955,46 @@ export class MainView extends React.Component {
872
955
  }
873
956
  }
874
957
  }
958
+ /**
959
+ * Wait for all layers to be loaded.
960
+ */
961
+ _waitForReady() {
962
+ return new Promise(resolve => {
963
+ const checkReady = () => {
964
+ if (this._loadingLayers.size === 0) {
965
+ this.setState(old => (Object.assign(Object.assign({}, old), { loadingLayer: false })));
966
+ resolve();
967
+ }
968
+ else {
969
+ setTimeout(checkReady, 50);
970
+ }
971
+ };
972
+ checkReady();
973
+ });
974
+ }
975
+ /**
976
+ * Wait for a layers source state to be 'ready'
977
+ * @param layer The Layer to check
978
+ */
979
+ _waitForSourceReady(layer) {
980
+ return new Promise((resolve, reject) => {
981
+ const checkState = () => {
982
+ const state = layer.getSourceState();
983
+ if (state === 'ready') {
984
+ layer.un('change', checkState);
985
+ resolve();
986
+ }
987
+ else if (state === 'error') {
988
+ layer.un('change', checkState);
989
+ reject(new Error('Source failed to load.'));
990
+ }
991
+ };
992
+ // Listen for state changes
993
+ layer.on('change', checkState);
994
+ // Check the state immediately in case it's already 'ready'
995
+ checkState();
996
+ });
997
+ }
875
998
  /**
876
999
  * Remove a layer from the map.
877
1000
  *
@@ -1048,12 +1171,40 @@ export class MainView extends React.Component {
1048
1171
  }
1049
1172
  });
1050
1173
  }
1051
- _onZoomToAnnotation(_, id) {
1174
+ _onZoomToPosition(_, id) {
1052
1175
  var _a;
1176
+ // Check if the id is an annotation
1053
1177
  const annotation = (_a = this._model.annotationModel) === null || _a === void 0 ? void 0 : _a.getAnnotation(id);
1054
1178
  if (annotation) {
1055
1179
  this._moveToPosition(annotation.position, annotation.zoom);
1180
+ return;
1181
+ }
1182
+ // The id is a layer
1183
+ let extent;
1184
+ const layer = this.getLayer(id);
1185
+ const source = layer === null || layer === void 0 ? void 0 : layer.getSource();
1186
+ if (source instanceof VectorSource) {
1187
+ extent = source.getExtent();
1188
+ }
1189
+ if (source instanceof TileSource) {
1190
+ // Tiled sources don't have getExtent() so we get it from the grid
1191
+ const tileGrid = source.getTileGrid();
1192
+ extent = tileGrid === null || tileGrid === void 0 ? void 0 : tileGrid.getExtent();
1056
1193
  }
1194
+ if (!extent) {
1195
+ console.warn('Layer has no extent.');
1196
+ return;
1197
+ }
1198
+ // Convert layer extent value to view projection if needed
1199
+ const sourceProjection = source === null || source === void 0 ? void 0 : source.getProjection();
1200
+ const viewProjection = this._Map.getView().getProjection();
1201
+ const transformedExtent = sourceProjection && sourceProjection !== viewProjection
1202
+ ? transformExtent(extent, sourceProjection, viewProjection)
1203
+ : extent;
1204
+ this._Map.getView().fit(transformedExtent, {
1205
+ size: this._Map.getSize(),
1206
+ duration: 500
1207
+ });
1057
1208
  }
1058
1209
  _moveToPosition(center, zoom, duration = 1000) {
1059
1210
  const view = this._Map.getView();
@@ -1129,6 +1280,7 @@ export class MainView extends React.Component {
1129
1280
  React.createElement("div", { ref: this.divRef, style: {
1130
1281
  width: '100%',
1131
1282
  height: 'calc(100%)'
1132
- } }))));
1283
+ } })),
1284
+ React.createElement(StatusBar, { jgisModel: this._model, loading: this.state.loadingLayer, projection: this.state.viewProjection, scale: this.state.scale })));
1133
1285
  }
1134
1286
  }
@@ -4,6 +4,7 @@ import { cloneDeep } from 'lodash';
4
4
  import React, { useEffect, useRef, useState } from 'react';
5
5
  import { debounce, getLayerTileInfo } from '../../../tools';
6
6
  import FilterRow from './FilterRow';
7
+ import { loadFile } from '../../../tools';
7
8
  /**
8
9
  * The filters panel widget.
9
10
  */
@@ -150,7 +151,11 @@ const FilterComponent = (props) => {
150
151
  break;
151
152
  }
152
153
  case 'GeoJSONSource': {
153
- const data = await (model === null || model === void 0 ? void 0 : model.readGeoJSON((_d = source.parameters) === null || _d === void 0 ? void 0 : _d.path));
154
+ const data = await loadFile({
155
+ filepath: (_d = source.parameters) === null || _d === void 0 ? void 0 : _d.path,
156
+ type: 'GeoJSONSource',
157
+ model: model
158
+ });
154
159
  data === null || data === void 0 ? void 0 : data.features.forEach((feature) => {
155
160
  feature.properties &&
156
161
  addFeatureValue(feature.properties, aggregatedProperties);
@@ -0,0 +1,13 @@
1
+ import { IJupyterGISModel } from '@jupytergis/schema';
2
+ import React from 'react';
3
+ interface IStatusBarProps {
4
+ jgisModel: IJupyterGISModel;
5
+ loading?: boolean;
6
+ projection?: {
7
+ code: string;
8
+ units: string;
9
+ };
10
+ scale: number;
11
+ }
12
+ declare const StatusBar: ({ jgisModel, loading, projection, scale }: IStatusBarProps) => React.JSX.Element;
13
+ export default StatusBar;
@@ -0,0 +1,52 @@
1
+ import { faGlobe, faLocationDot, faRuler } from '@fortawesome/free-solid-svg-icons';
2
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3
+ import { Progress } from '@jupyter/react-components';
4
+ import React, { useEffect, useState } from 'react';
5
+ import { version } from '../../package.json'; // Adjust the path as necessary
6
+ const StatusBar = ({ jgisModel, loading, projection, scale }) => {
7
+ var _a;
8
+ const [coords, setCoords] = useState({ x: 0, y: 0 });
9
+ useEffect(() => {
10
+ const handleClientStateChanged = () => {
11
+ var _a, _b;
12
+ const pointer = (_b = (_a = jgisModel === null || jgisModel === void 0 ? void 0 : jgisModel.localState) === null || _a === void 0 ? void 0 : _a.pointer) === null || _b === void 0 ? void 0 : _b.value;
13
+ if (!pointer) {
14
+ return;
15
+ }
16
+ setCoords({ x: pointer === null || pointer === void 0 ? void 0 : pointer.coordinates.x, y: pointer === null || pointer === void 0 ? void 0 : pointer.coordinates.y });
17
+ };
18
+ jgisModel.clientStateChanged.connect(handleClientStateChanged);
19
+ return () => {
20
+ jgisModel.clientStateChanged.disconnect(handleClientStateChanged);
21
+ };
22
+ }, [jgisModel]);
23
+ return (React.createElement("div", { className: "jgis-status-bar" },
24
+ loading && (React.createElement("div", { style: { width: '16%', padding: '0 6px' } },
25
+ React.createElement(Progress, { height: 14 }))),
26
+ React.createElement("div", { className: "jgis-status-bar-item" },
27
+ React.createElement("span", null,
28
+ "jgis: ",
29
+ version)),
30
+ React.createElement("div", { className: "jgis-status-bar-item jgis-status-bar-coords" },
31
+ React.createElement(FontAwesomeIcon, { icon: faLocationDot }),
32
+ React.createElement("span", null,
33
+ ' ',
34
+ "x: ",
35
+ Math.trunc(coords.x),
36
+ " y: ",
37
+ Math.trunc(coords.y))),
38
+ React.createElement("div", { className: "jgis-status-bar-item" },
39
+ React.createElement(FontAwesomeIcon, { icon: faRuler }),
40
+ ' ',
41
+ React.createElement("span", null,
42
+ "Scale: 1: ",
43
+ Math.trunc(scale))),
44
+ React.createElement("div", { className: "jgis-status-bar-item" },
45
+ React.createElement(FontAwesomeIcon, { icon: faGlobe }),
46
+ ' ',
47
+ React.createElement("span", null, (_a = projection === null || projection === void 0 ? void 0 : projection.code) !== null && _a !== void 0 ? _a : null)),
48
+ React.createElement("div", { className: "jgis-status-bar-item" },
49
+ "Units: ", projection === null || projection === void 0 ? void 0 :
50
+ projection.units)));
51
+ };
52
+ export default StatusBar;
@@ -101,11 +101,6 @@ export class ToolbarWidget extends ReactiveToolbar {
101
101
  label: '',
102
102
  commands: options.commands
103
103
  }));
104
- options.commands.addKeyBinding({
105
- command: CommandIDs.identify,
106
- keys: ['Escape'],
107
- selector: '#main'
108
- });
109
104
  this.addItem('spacer', ReactiveToolbar.createSpacerItem());
110
105
  // Users
111
106
  this.addItem('users', ReactWidget.create(React.createElement(UsersItem, { model: options.model })));
package/lib/tools.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { VectorTile } from '@mapbox/vector-tile';
2
- import { IDict, IJGISLayerBrowserRegistry, IJGISOptions } from '@jupytergis/schema';
2
+ import { IDict, IJGISLayerBrowserRegistry, IJGISOptions, IJGISSource, IJupyterGISModel } from '@jupytergis/schema';
3
3
  export declare const debounce: (func: CallableFunction, timeout?: number) => CallableFunction;
4
4
  export declare function throttle<T extends (...args: any[]) => void>(callback: T, delay?: number): T;
5
5
  export declare function getElementFromProperty(filePath?: string | null, prop?: string | null): HTMLElement | undefined | null;
@@ -67,3 +67,42 @@ export declare const loadGeoTIFFWithCache: (sourceInfo: {
67
67
  metadata: any;
68
68
  sourceUrl: string;
69
69
  } | null>;
70
+ /**
71
+ * Generalized file reader for different source types.
72
+ *
73
+ * @param fileInfo - Object containing the file path and source type.
74
+ * @returns A promise that resolves to the file content.
75
+ */
76
+ export declare const loadFile: (fileInfo: {
77
+ filepath: string;
78
+ type: IJGISSource["type"];
79
+ model: IJupyterGISModel;
80
+ }) => Promise<any>;
81
+ /**
82
+ * Converts a base64-encoded string to a Blob.
83
+ *
84
+ * @param base64 - The base64-encoded string representing the file data.
85
+ * @param mimeType - The MIME type of the data.
86
+ * @returns A promise that resolves to a Blob representing the decoded data.
87
+ */
88
+ export declare const base64ToBlob: (base64: string, mimeType: string) => Promise<Blob>;
89
+ /**
90
+ * A mapping of file extensions to their corresponding MIME types.
91
+ */
92
+ export declare const MIME_TYPES: {
93
+ [ext: string]: string;
94
+ };
95
+ /**
96
+ * Determine the MIME type based on the file extension.
97
+ *
98
+ * @param filename - The name of the file.
99
+ * @returns A string representing the MIME type.
100
+ */
101
+ export declare const getMimeType: (filename: string) => string;
102
+ /**
103
+ * Helper to convert a string (base64) to ArrayBuffer.
104
+ *
105
+ * @param content - File content as a base64 string.
106
+ * @returns An ArrayBuffer.
107
+ */
108
+ export declare const stringToArrayBuffer: (content: string) => Promise<ArrayBuffer>;