@jupytergis/base 0.2.1 → 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 +224 -75
  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
package/lib/tools.js CHANGED
@@ -3,6 +3,8 @@ import { VectorTile } from '@mapbox/vector-tile';
3
3
  import { URLExt } from '@jupyterlab/coreutils';
4
4
  import { ServerConnection } from '@jupyterlab/services';
5
5
  import * as d3Color from 'd3-color';
6
+ import { PathExt } from '@jupyterlab/coreutils';
7
+ import shp from 'shpjs';
6
8
  import RASTER_LAYER_GALLERY from '../rasterlayer_gallery/raster_layer_gallery.json';
7
9
  import { getGdal } from './gdal';
8
10
  export const debounce = (func, timeout = 100) => {
@@ -337,3 +339,309 @@ export const loadGeoTIFFWithCache = async (sourceInfo) => {
337
339
  sourceUrl: sourceInfo.url
338
340
  };
339
341
  };
342
+ /**
343
+ * Generalized file reader for different source types.
344
+ *
345
+ * @param fileInfo - Object containing the file path and source type.
346
+ * @returns A promise that resolves to the file content.
347
+ */
348
+ export const loadFile = async (fileInfo) => {
349
+ const { filepath, type, model } = fileInfo;
350
+ if (filepath.startsWith('http://') || filepath.startsWith('https://')) {
351
+ switch (type) {
352
+ case 'ImageSource': {
353
+ try {
354
+ const response = await fetch(filepath);
355
+ if (!response.ok) {
356
+ throw new Error(`Failed to fetch image from URL: ${filepath}`);
357
+ }
358
+ const contentType = response.headers.get('Content-Type');
359
+ if (!contentType || !contentType.startsWith('image/')) {
360
+ throw new Error(`Invalid image URL. Content-Type: ${contentType}`);
361
+ }
362
+ // load the image to verify it's not corrupted
363
+ await validateImage(await response.blob());
364
+ return filepath;
365
+ }
366
+ catch (error) {
367
+ console.error('Error validating remote image:', error);
368
+ throw error;
369
+ }
370
+ }
371
+ case 'ShapefileSource': {
372
+ try {
373
+ const response = await fetch(`/jupytergis_core/proxy?url=${filepath}`);
374
+ const arrayBuffer = await response.arrayBuffer();
375
+ const geojson = await shp(arrayBuffer);
376
+ return geojson;
377
+ }
378
+ catch (error) {
379
+ console.error('Error loading remote shapefile:', error);
380
+ throw error;
381
+ }
382
+ }
383
+ case 'GeoJSONSource': {
384
+ try {
385
+ const response = await fetch(`/jupytergis_core/proxy?url=${filepath}`);
386
+ if (!response.ok) {
387
+ throw new Error(`Failed to fetch GeoJSON from URL: ${filepath}`);
388
+ }
389
+ const geojson = await response.json();
390
+ return geojson;
391
+ }
392
+ catch (error) {
393
+ console.error('Error loading remote GeoJSON:', error);
394
+ throw error;
395
+ }
396
+ }
397
+ default: {
398
+ throw new Error(`Unsupported URL handling for source type: ${type}`);
399
+ }
400
+ }
401
+ }
402
+ if (!model.contentsManager || !model.filePath) {
403
+ throw new Error('ContentsManager or filePath is not initialized.');
404
+ }
405
+ const absolutePath = PathExt.resolve(PathExt.dirname(model.filePath), filepath);
406
+ try {
407
+ const file = await model.contentsManager.get(absolutePath, {
408
+ content: true
409
+ });
410
+ if (!file.content) {
411
+ throw new Error(`File at ${absolutePath} is empty or inaccessible.`);
412
+ }
413
+ switch (type) {
414
+ case 'GeoJSONSource': {
415
+ return typeof file.content === 'string'
416
+ ? JSON.parse(file.content)
417
+ : file.content;
418
+ }
419
+ case 'ShapefileSource': {
420
+ const arrayBuffer = await stringToArrayBuffer(file.content);
421
+ const geojson = await shp(arrayBuffer);
422
+ return geojson;
423
+ }
424
+ case 'ImageSource': {
425
+ if (typeof file.content === 'string') {
426
+ const mimeType = getMimeType(filepath);
427
+ if (!mimeType.startsWith('image/')) {
428
+ throw new Error(`Invalid image file. MIME type: ${mimeType}`);
429
+ }
430
+ // Attempt to decode the base64 data
431
+ try {
432
+ await validateImage(await base64ToBlob(file.content, mimeType));
433
+ return `data:${mimeType};base64,${file.content}`;
434
+ }
435
+ catch (error) {
436
+ console.error('Error image content failed to decode.:', error);
437
+ throw error;
438
+ }
439
+ }
440
+ else {
441
+ throw new Error('Invalid file format for image content.');
442
+ }
443
+ }
444
+ default: {
445
+ throw new Error(`Unsupported source type: ${type}`);
446
+ }
447
+ }
448
+ }
449
+ catch (error) {
450
+ console.error(`Error reading file '${filepath}':`, error);
451
+ throw error;
452
+ }
453
+ };
454
+ /**
455
+ * Validates whether a given Blob represents a valid image.
456
+ *
457
+ * @param blob - The Blob to validate.
458
+ * @returns A promise that resolves if the Blob is a valid image, or rejects with an error otherwise.
459
+ */
460
+ const validateImage = async (blob) => {
461
+ return new Promise((resolve, reject) => {
462
+ const img = new Image();
463
+ img.onload = () => resolve(); // Valid image
464
+ img.onerror = () => reject(new Error('Invalid image content.'));
465
+ img.src = URL.createObjectURL(blob);
466
+ });
467
+ };
468
+ /**
469
+ * Converts a base64-encoded string to a Blob.
470
+ *
471
+ * @param base64 - The base64-encoded string representing the file data.
472
+ * @param mimeType - The MIME type of the data.
473
+ * @returns A promise that resolves to a Blob representing the decoded data.
474
+ */
475
+ export const base64ToBlob = async (base64, mimeType) => {
476
+ const response = await fetch(`data:${mimeType};base64,${base64}`);
477
+ return await response.blob();
478
+ };
479
+ /**
480
+ * A mapping of file extensions to their corresponding MIME types.
481
+ */
482
+ export const MIME_TYPES = {
483
+ // from https://github.com/python/cpython/blob/3.9/Lib/mimetypes.py
484
+ '.a': 'application/octet-stream',
485
+ '.ai': 'application/postscript',
486
+ '.aif': 'audio/x-aiff',
487
+ '.aifc': 'audio/x-aiff',
488
+ '.aiff': 'audio/x-aiff',
489
+ '.au': 'audio/basic',
490
+ '.avi': 'video/x-msvideo',
491
+ '.bat': 'text/plain',
492
+ '.bcpio': 'application/x-bcpio',
493
+ '.bin': 'application/octet-stream',
494
+ '.bmp': 'image/bmp',
495
+ '.c': 'text/plain',
496
+ '.cdf': 'application/x-netcdf',
497
+ '.cpio': 'application/x-cpio',
498
+ '.csh': 'application/x-csh',
499
+ '.css': 'text/css',
500
+ '.csv': 'text/csv',
501
+ '.dll': 'application/octet-stream',
502
+ '.doc': 'application/msword',
503
+ '.dot': 'application/msword',
504
+ '.dvi': 'application/x-dvi',
505
+ '.eml': 'message/rfc822',
506
+ '.eps': 'application/postscript',
507
+ '.etx': 'text/x-setext',
508
+ '.exe': 'application/octet-stream',
509
+ '.gif': 'image/gif',
510
+ '.gtar': 'application/x-gtar',
511
+ '.h': 'text/plain',
512
+ '.hdf': 'application/x-hdf',
513
+ '.htm': 'text/html',
514
+ '.html': 'text/html',
515
+ '.ico': 'image/vnd.microsoft.icon',
516
+ '.ief': 'image/ief',
517
+ '.jpe': 'image/jpeg',
518
+ '.jpeg': 'image/jpeg',
519
+ '.jpg': 'image/jpg',
520
+ '.js': 'application/javascript',
521
+ '.json': 'application/json',
522
+ '.ksh': 'text/plain',
523
+ '.latex': 'application/x-latex',
524
+ '.m1v': 'video/mpeg',
525
+ '.m3u': 'application/vnd.apple.mpegurl',
526
+ '.m3u8': 'application/vnd.apple.mpegurl',
527
+ '.man': 'application/x-troff-man',
528
+ '.me': 'application/x-troff-me',
529
+ '.mht': 'message/rfc822',
530
+ '.mhtml': 'message/rfc822',
531
+ '.mid': 'audio/midi',
532
+ '.midi': 'audio/midi',
533
+ '.mif': 'application/x-mif',
534
+ '.mjs': 'application/javascript',
535
+ '.mov': 'video/quicktime',
536
+ '.movie': 'video/x-sgi-movie',
537
+ '.mp2': 'audio/mpeg',
538
+ '.mp3': 'audio/mpeg',
539
+ '.mp4': 'video/mp4',
540
+ '.mpa': 'video/mpeg',
541
+ '.mpe': 'video/mpeg',
542
+ '.mpeg': 'video/mpeg',
543
+ '.mpg': 'video/mpeg',
544
+ '.ms': 'application/x-troff-ms',
545
+ '.nc': 'application/x-netcdf',
546
+ '.nws': 'message/rfc822',
547
+ '.o': 'application/octet-stream',
548
+ '.obj': 'application/octet-stream',
549
+ '.oda': 'application/oda',
550
+ '.p12': 'application/x-pkcs12',
551
+ '.p7c': 'application/pkcs7-mime',
552
+ '.pbm': 'image/x-portable-bitmap',
553
+ '.pct': 'image/pict',
554
+ '.pdf': 'application/pdf',
555
+ '.pfx': 'application/x-pkcs12',
556
+ '.pgm': 'image/x-portable-graymap',
557
+ '.pic': 'image/pict',
558
+ '.pict': 'image/pict',
559
+ '.pl': 'text/plain',
560
+ '.png': 'image/png',
561
+ '.pnm': 'image/x-portable-anymap',
562
+ '.pot': 'application/vnd.ms-powerpoint',
563
+ '.ppa': 'application/vnd.ms-powerpoint',
564
+ '.ppm': 'image/x-portable-pixmap',
565
+ '.pps': 'application/vnd.ms-powerpoint',
566
+ '.ppt': 'application/vnd.ms-powerpoint',
567
+ '.ps': 'application/postscript',
568
+ '.pwz': 'application/vnd.ms-powerpoint',
569
+ '.py': 'text/x-python',
570
+ '.pyc': 'application/x-python-code',
571
+ '.pyo': 'application/x-python-code',
572
+ '.qt': 'video/quicktime',
573
+ '.ra': 'audio/x-pn-realaudio',
574
+ '.ram': 'application/x-pn-realaudio',
575
+ '.ras': 'image/x-cmu-raster',
576
+ '.rdf': 'application/xml',
577
+ '.rgb': 'image/x-rgb',
578
+ '.roff': 'application/x-troff',
579
+ '.rtf': 'application/rtf',
580
+ '.rtx': 'text/richtext',
581
+ '.sgm': 'text/x-sgml',
582
+ '.sgml': 'text/x-sgml',
583
+ '.sh': 'application/x-sh',
584
+ '.shar': 'application/x-shar',
585
+ '.snd': 'audio/basic',
586
+ '.so': 'application/octet-stream',
587
+ '.src': 'application/x-wais-source',
588
+ '.sv4cpio': 'application/x-sv4cpio',
589
+ '.sv4crc': 'application/x-sv4crc',
590
+ '.svg': 'image/svg+xml',
591
+ '.swf': 'application/x-shockwave-flash',
592
+ '.t': 'application/x-troff',
593
+ '.tar': 'application/x-tar',
594
+ '.tcl': 'application/x-tcl',
595
+ '.tex': 'application/x-tex',
596
+ '.texi': 'application/x-texinfo',
597
+ '.texinfo': 'application/x-texinfo',
598
+ '.tif': 'image/tiff',
599
+ '.tiff': 'image/tiff',
600
+ '.tr': 'application/x-troff',
601
+ '.tsv': 'text/tab-separated-values',
602
+ '.txt': 'text/plain',
603
+ '.ustar': 'application/x-ustar',
604
+ '.vcf': 'text/x-vcard',
605
+ '.wasm': 'application/wasm',
606
+ '.wav': 'audio/x-wav',
607
+ '.webm': 'video/webm',
608
+ '.webmanifest': 'application/manifest+json',
609
+ '.wiz': 'application/msword',
610
+ '.wsdl': 'application/xml',
611
+ '.xbm': 'image/x-xbitmap',
612
+ '.xlb': 'application/vnd.ms-excel',
613
+ '.xls': 'application/vnd.ms-excel',
614
+ '.xml': 'text/xml',
615
+ '.xpdl': 'application/xml',
616
+ '.xpm': 'image/x-xpixmap',
617
+ '.xsl': 'application/xml',
618
+ '.xul': 'text/xul',
619
+ '.xwd': 'image/x-xwindowdump',
620
+ '.zip': 'application/zip',
621
+ '.ipynb': 'application/json'
622
+ };
623
+ /**
624
+ * Determine the MIME type based on the file extension.
625
+ *
626
+ * @param filename - The name of the file.
627
+ * @returns A string representing the MIME type.
628
+ */
629
+ export const getMimeType = (filename) => {
630
+ var _a;
631
+ const extension = `.${((_a = filename.split('.').pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || ''}`;
632
+ if (MIME_TYPES[extension]) {
633
+ return MIME_TYPES[extension];
634
+ }
635
+ console.warn(`Unknown file extension: ${extension}, defaulting to 'application/octet-stream'.`);
636
+ return 'application/octet-stream';
637
+ };
638
+ /**
639
+ * Helper to convert a string (base64) to ArrayBuffer.
640
+ *
641
+ * @param content - File content as a base64 string.
642
+ * @returns An ArrayBuffer.
643
+ */
644
+ export const stringToArrayBuffer = async (content) => {
645
+ const base64Response = await fetch(`data:application/octet-stream;base64,${content}`);
646
+ return await base64Response.arrayBuffer();
647
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jupytergis/base",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "A JupyterLab extension for 3D modelling.",
5
5
  "keywords": [
6
6
  "jupyter",
@@ -38,8 +38,12 @@
38
38
  "watch": "tsc -w"
39
39
  },
40
40
  "dependencies": {
41
+ "@fortawesome/fontawesome-svg-core": "^6.5.2",
42
+ "@fortawesome/free-solid-svg-icons": "^6.5.2",
43
+ "@fortawesome/react-fontawesome": "latest",
44
+ "@jupyter/react-components": "^0.16.6",
41
45
  "@jupyter/ydoc": "^2.0.0 || ^3.0.0",
42
- "@jupytergis/schema": "^0.2.1",
46
+ "@jupytergis/schema": "^0.3.0",
43
47
  "@jupyterlab/application": "^4.3.0",
44
48
  "@jupyterlab/apputils": "^4.3.0",
45
49
  "@jupyterlab/completer": "^4.3.0",
@@ -61,13 +65,18 @@
61
65
  "@naisutech/react-tree": "^3.0.1",
62
66
  "@rjsf/core": "^4.2.0",
63
67
  "@rjsf/validator-ajv8": "^5.23.1",
64
- "@types/d3-color": "^3.1.0",
65
- "@types/three": "^0.134.0",
66
68
  "ajv": "^8.14.0",
69
+ "colormap": "^2.3.2",
67
70
  "d3-color": "^3.1.0",
68
71
  "gdal3.js": "^2.8.1",
72
+ "geojson-vt": "^4.0.2",
73
+ "geotiff": "^2.1.3",
74
+ "ol": "^10.1.0",
69
75
  "ol-pmtiles": "^0.5.0",
70
76
  "pbf": "^4.0.1",
77
+ "pmtiles": "^3.0.7",
78
+ "proj4": "^2.14.0",
79
+ "proj4-list": "^1.0.2",
71
80
  "react": "^18.0.1",
72
81
  "shpjs": "^6.1.0",
73
82
  "styled-components": "^5.3.6",
@@ -77,9 +86,11 @@
77
86
  },
78
87
  "devDependencies": {
79
88
  "@apidevtools/json-schema-ref-parser": "^9.0.9",
89
+ "@types/colormap": "^2.3.4",
90
+ "@types/d3-color": "^3.1.0",
80
91
  "@types/node": "^18.15.11",
92
+ "@types/proj4": "^2.5.5",
81
93
  "@types/shpjs": "^3.4.7",
82
- "@types/three": "^0.135.0",
83
94
  "@types/uuid": "^10.0.0",
84
95
  "rimraf": "^3.0.2",
85
96
  "typescript": "^5"
package/style/base.css CHANGED
@@ -8,6 +8,7 @@
8
8
  @import url('./leftPanel.css');
9
9
  @import url('./filterPanel.css');
10
10
  @import url('./symbologyDialog.css');
11
+ @import url('./statusBar.css');
11
12
  @import url('ol/ol.css');
12
13
 
13
14
  .jGIS-Toolbar-GroupName {
@@ -0,0 +1,31 @@
1
+ <svg
2
+ xmlns="http://www.w3.org/2000/svg"
3
+ version="1.1"
4
+ width="256"
5
+ height="256"
6
+ viewBox="0 0 256 256"
7
+ class="jp-icon-selectable">
8
+ <g class="jp-icon-selectable">
9
+ <path
10
+ class="jp-icon-selectable"
11
+ d="M137.607,136.899 L172.044,136.899 L142.898,108.083 L107.19,108.083 L107.19,142.392 L137.607,172.708 z"
12
+ fill="#EE7913" />
13
+ <path
14
+ class="jp-icon-selectable"
15
+ d="M248.061,212.048 L186.618,151.306 L152.01,151.306 L152.01,187.067 L210.891,245.749 L248.061,245.749 z"
16
+ fill="#589632" />
17
+ <path
18
+ class="jp-icon-selectable"
19
+ d="M152.01,151.306 L186.618,151.306 L172.044,136.899 L137.607,136.899 L137.607,172.708 L152.01,187.067 z"
20
+ fill="#F0E64A" />
21
+ <path
22
+ class="jp-icon-selectable"
23
+ d="M144.826,199.3 C139.606,200.502 134.183,201.154 128.588,201.154 C88.561,201.154 54.516,168.24 54.516,125.564 C54.516,82.887 88.182,50.695 128.588,50.695 C168.989,50.695 201.158,82.882 201.158,125.564 C201.158,132.501 200.295,139.183 198.697,145.509 L235.744,182.551 C245.296,165.815 250.64,146.354 250.64,125.277 C250.64,59.499 198.147,10.251 127.839,10.251 C57.849,10.251 5.36,59.178 5.36,125.277 C5.36,191.698 57.849,241.598 127.839,241.598 C145.905,241.598 162.791,238.275 177.916,232.227 L144.826,199.3 z"
24
+ fill="#93B023" />
25
+ <path
26
+ class="jp-icon-selectable"
27
+ d="M107.19,108.083 L248.061,245.749 L248.061,212.048 L186.618,151.306 L172.044,136.898 L142.898,108.083 z"
28
+ fill="#FFFFFF"
29
+ fill-opacity="0.172" />
30
+ </g>
31
+ </svg>
@@ -64,6 +64,14 @@
64
64
  .jp-gis-layerGroupHeader.jp-mod-selected .jp-icon-selectable {
65
65
  fill: var(--jp-ui-inverse-font-color1);
66
66
  }
67
+ .jp-gis-layer.jp-mod-selected path,
68
+ .jp-gis-source.jp-mod-selected path {
69
+ fill: var(--jp-ui-inverse-font-color1);
70
+ }
71
+ .jp-gis-layer.jp-mod-selected button:hover,
72
+ .jp-gis-source.jp-mod-selected button:hover {
73
+ background: none;
74
+ }
67
75
 
68
76
  .jp-gis-layer button,
69
77
  .jp-gis-source button {
@@ -0,0 +1,16 @@
1
+ .jgis-status-bar {
2
+ display: flex;
3
+ justify-content: space-around;
4
+ align-items: center;
5
+ height: 16px;
6
+ background-color: var(--jp-layout-color1);
7
+ font-size: var(--jp-ui-font-size0);
8
+ }
9
+
10
+ .jgis-status-bar-item {
11
+ flex: 0 0 content;
12
+ }
13
+
14
+ .jgis-status-bar-coords {
15
+ min-width: 160px;
16
+ }