@@ -17,7 +17,7 @@ import { ITranslator } from '@jupyterlab/translation';
17
17
import { CommandRegistry } from '@lumino/commands' ;
18
18
import { ReadonlyPartialJSONObject } from '@lumino/coreutils' ;
19
19
import { CommandIDs , icons } from './constants' ;
20
- import { CreationFormDialog } from './dialogs/formdialog ' ;
20
+ import { LayerCreationFormDialog } from './dialogs/layerCreationFormDialog ' ;
21
21
import { LayerBrowserWidget } from './dialogs/layerBrowserDialog' ;
22
22
import { SymbologyWidget } from './dialogs/symbology/symbologyDialog' ;
23
23
import keybindings from './keybindings.json' ;
@@ -27,7 +27,7 @@ import { getGdal } from './gdal';
27
27
import { getGeoJSONDataFromLayerSource , downloadFile } from './tools' ;
28
28
import { IJGISLayer , IJGISSource } from '@jupytergis/schema' ;
29
29
import { UUID } from '@lumino/coreutils' ;
30
- import { FormDialog } from './formbuilder/formdialog ' ;
30
+ import { ProcessingFormDialog } from './dialogs/ProcessingFormDialog ' ;
31
31
32
32
interface ICreateEntry {
33
33
tracker : JupyterGISTracker ;
@@ -376,7 +376,7 @@ export function addCommands(
376
376
377
377
// Open form and get user input
378
378
const formValues = await new Promise < IDict > ( resolve => {
379
- const dialog = new FormDialog ( {
379
+ const dialog = new ProcessingFormDialog ( {
380
380
title : 'Buffer' ,
381
381
schema : schema ,
382
382
model : model ,
@@ -385,7 +385,8 @@ export function addCommands(
385
385
bufferDistance : 10 ,
386
386
projection : 'EPSG:4326'
387
387
} ,
388
- cancelButton : false ,
388
+ formContext : 'create' ,
389
+ processingType : 'buffer' ,
389
390
syncData : ( props : IDict ) => {
390
391
resolve ( props ) ;
391
392
dialog . dispose ( ) ;
@@ -480,6 +481,165 @@ export function addCommands(
480
481
}
481
482
} ) ;
482
483
484
+ commands . addCommand ( CommandIDs . dissolve , {
485
+ label : trans . __ ( 'Dissolve' ) ,
486
+ isEnabled : ( ) => {
487
+ const selectedLayer = getSingleSelectedLayer ( tracker ) ;
488
+ if ( ! selectedLayer ) {
489
+ return false ;
490
+ }
491
+ return [ 'VectorLayer' , 'ShapefileLayer' ] . includes ( selectedLayer . type ) ;
492
+ } ,
493
+ execute : async ( ) => {
494
+ const selected = getSingleSelectedLayer ( tracker ) ;
495
+ if ( ! selected ) {
496
+ console . error ( 'No valid selected layer.' ) ;
497
+ return ;
498
+ }
499
+
500
+ const sources = tracker . currentWidget ?. model . sharedModel . sources ?? { } ;
501
+ const model = tracker . currentWidget ?. model ;
502
+ const localState = model ?. sharedModel . awareness . getLocalState ( ) ;
503
+
504
+ if (
505
+ ! model ||
506
+ ! localState ||
507
+ ! localState [ 'selected' ] ?. value ||
508
+ ! selected . parameters
509
+ ) {
510
+ return ;
511
+ }
512
+
513
+ const sourceId = selected . parameters . source ;
514
+ const source = sources [ sourceId ] ;
515
+
516
+ if ( ! source || ! source . parameters ) {
517
+ console . error ( `Source with ID ${ sourceId } not found or missing path.` ) ;
518
+ return ;
519
+ }
520
+
521
+ // Load GeoJSON data
522
+ const geojsonString = await getGeoJSONDataFromLayerSource ( source , model ) ;
523
+ if ( ! geojsonString ) {
524
+ return ;
525
+ }
526
+
527
+ const geojson = JSON . parse ( geojsonString ) ;
528
+ if ( ! geojson . features || geojson . features . length === 0 ) {
529
+ console . error ( 'Invalid GeoJSON: No features found.' ) ;
530
+ return ;
531
+ }
532
+
533
+ // Extract field names from the first feature's properties
534
+ const properties = geojson . features [ 0 ] . properties ;
535
+ const fieldNames = Object . keys ( properties ) ;
536
+
537
+ if ( fieldNames . length === 0 ) {
538
+ console . error ( 'No attribute fields found in GeoJSON.' ) ;
539
+ return ;
540
+ }
541
+
542
+ // Retrieve dissolve schema and update fields dynamically
543
+ const schema = {
544
+ ...( formSchemaRegistry . getSchemas ( ) . get ( 'Dissolve' ) as IDict ) ,
545
+ properties : {
546
+ ...formSchemaRegistry . getSchemas ( ) . get ( 'Dissolve' ) ?. properties ,
547
+ dissolveField : {
548
+ type : 'string' ,
549
+ enum : fieldNames , // Populate dropdown with field names
550
+ description : 'Select the field for dissolving features.'
551
+ }
552
+ }
553
+ } ;
554
+
555
+ const selectedLayer = localState [ 'selected' ] . value ;
556
+ const selectedLayerId = Object . keys ( selectedLayer ) [ 0 ] ;
557
+
558
+ // Open form and get user input
559
+ const formValues = await new Promise < IDict > ( resolve => {
560
+ const dialog = new ProcessingFormDialog ( {
561
+ title : 'Dissolve' ,
562
+ schema : schema ,
563
+ model : model ,
564
+ sourceData : {
565
+ inputLayer : selectedLayerId ,
566
+ dissolveField : fieldNames [ 0 ] // Default to the first field
567
+ } ,
568
+ formContext : 'create' ,
569
+ processingType : 'dissolve' ,
570
+ syncData : ( props : IDict ) => {
571
+ resolve ( props ) ;
572
+ dialog . dispose ( ) ;
573
+ }
574
+ } ) ;
575
+
576
+ dialog . launch ( ) ;
577
+ } ) ;
578
+
579
+ if ( ! formValues ) {
580
+ return ;
581
+ }
582
+
583
+ const dissolveField = formValues . dissolveField ;
584
+ const fileBlob = new Blob ( [ geojsonString ] , {
585
+ type : 'application/geo+json'
586
+ } ) ;
587
+ const geoFile = new File ( [ fileBlob ] , 'data.geojson' , {
588
+ type : 'application/geo+json'
589
+ } ) ;
590
+
591
+ const Gdal = await getGdal ( ) ;
592
+ const result = await Gdal . open ( geoFile ) ;
593
+
594
+ if ( result . datasets . length > 0 ) {
595
+ const dataset = result . datasets [ 0 ] as any ;
596
+ const layerName = dataset . info . layers [ 0 ] . name ;
597
+
598
+ const sqlQuery = `
599
+ SELECT ST_Union(geometry) AS geometry, ${ dissolveField }
600
+ FROM ${ layerName }
601
+ GROUP BY ${ dissolveField }
602
+ ` ;
603
+
604
+ const options = [
605
+ '-f' ,
606
+ 'GeoJSON' ,
607
+ '-nlt' ,
608
+ 'PROMOTE_TO_MULTI' ,
609
+ '-dialect' ,
610
+ 'sqlite' ,
611
+ '-sql' ,
612
+ sqlQuery ,
613
+ 'output.geojson'
614
+ ] ;
615
+
616
+ const outputFilePath = await Gdal . ogr2ogr ( dataset , options ) ;
617
+ const dissolvedBytes = await Gdal . getFileBytes ( outputFilePath ) ;
618
+ const dissolvedGeoJSONString = new TextDecoder ( ) . decode ( dissolvedBytes ) ;
619
+ Gdal . close ( dataset ) ;
620
+
621
+ const dissolvedGeoJSON = JSON . parse ( dissolvedGeoJSONString ) ;
622
+
623
+ const newSourceId = UUID . uuid4 ( ) ;
624
+ const sourceModel : IJGISSource = {
625
+ type : 'GeoJSONSource' ,
626
+ name : selected . name + ' Dissolved' ,
627
+ parameters : { data : dissolvedGeoJSON }
628
+ } ;
629
+
630
+ const layerModel : IJGISLayer = {
631
+ type : 'VectorLayer' ,
632
+ parameters : { source : newSourceId } ,
633
+ visible : true ,
634
+ name : selected . name + ' Dissolved'
635
+ } ;
636
+
637
+ model . sharedModel . addSource ( newSourceId , sourceModel ) ;
638
+ model . addLayer ( UUID . uuid4 ( ) , layerModel ) ;
639
+ }
640
+ }
641
+ } ) ;
642
+
483
643
commands . addCommand ( CommandIDs . newGeoJSONEntry , {
484
644
label : trans . __ ( 'New GeoJSON layer' ) ,
485
645
isEnabled : ( ) => {
@@ -1206,12 +1366,13 @@ export function addCommands(
1206
1366
} ;
1207
1367
1208
1368
const formValues = await new Promise < IDict > ( resolve => {
1209
- const dialog = new FormDialog ( {
1369
+ const dialog = new ProcessingFormDialog ( {
1210
1370
title : 'Download GeoJSON' ,
1211
1371
schema : exportSchema ,
1212
1372
model,
1213
1373
sourceData : { exportFormat : 'GeoJSON' } ,
1214
- cancelButton : false ,
1374
+ formContext : 'create' ,
1375
+ processingType : 'export' ,
1215
1376
syncData : ( props : IDict ) => {
1216
1377
resolve ( props ) ;
1217
1378
dialog . dispose ( ) ;
@@ -1304,7 +1465,7 @@ namespace Private {
1304
1465
return ;
1305
1466
}
1306
1467
1307
- const dialog = new CreationFormDialog ( {
1468
+ const dialog = new LayerCreationFormDialog ( {
1308
1469
model : current . model ,
1309
1470
title,
1310
1471
createLayer,
0 commit comments