-
Notifications
You must be signed in to change notification settings - Fork 375
/
Copy pathgmtconnect.c
942 lines (842 loc) · 54.6 KB
/
gmtconnect.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
/*--------------------------------------------------------------------
*
* Copyright (c) 1991-2025 by the GMT Team (https://www.generic-mapping-tools.org/team.html)
* See LICENSE.TXT file for copying and redistribution conditions.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 3 or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* Contact info: www.generic-mapping-tools.org
*--------------------------------------------------------------------*/
/*
* API functions to support the gmtconnect application.
*
* Author: Paul Wessel
* Date: 1-JAN-2010
* Version: 6 API
*
* Brief synopsis: gmtconnect will combine pieces of coastlines or similar segments
* into a continuous line, polygon, or group of lines/polygons so that the jump
* between segment endpoints exceeds a specified threshold.
*/
#include "gmt_dev.h"
#include "longopt/gmtconnect_inc.h"
#define THIS_MODULE_CLASSIC_NAME "gmtconnect"
#define THIS_MODULE_MODERN_NAME "gmtconnect"
#define THIS_MODULE_LIB "core"
#define THIS_MODULE_PURPOSE "Connect individual lines whose end points match within tolerance"
#define THIS_MODULE_KEYS "<D{,>D},CD),LD),QD)"
#define THIS_MODULE_NEEDS ""
#define THIS_MODULE_OPTIONS "-:>Vabdefghioqs" GMT_OPT("HMm")
/* Control structure for gmtconnect */
struct GMTCONNECT_CTRL {
struct GMTCONNECT_Out { /* -> */
bool active;
char *file;
} Out;
struct GMTCONNECT_C { /* -C[<file>] */
bool active;
char *file; /* File for storing closed polygons */
} C;
struct GMTCONNECT_D { /* -D[<file>] */
bool active;
char *format;
} D;
struct GMTCONNECT_L { /* -L[<file>] */
bool active;
char *file;
} L;
struct GMTCONNECT_Q { /* -Q[<file>] */
bool active;
char *file;
} Q;
struct GMTCONNECT_T { /* -T[<cutoff>[+s<sdist>]] */
bool active[2];
int mode;
double dist[2];
char unit;
} T;
};
#define SEG_I 0
#define SEG_J 1
#define END_A 0
#define END_B 1
#define CLOSED 0
#define OPEN 1
struct GMTCONNECT_NEAREST { /* Holds information about the nearest segment to one of the two end of a segment */
uint64_t id; /* Running number ID starting at 0 for open segments only */
uint64_t orig_id; /* The original ID before expelling duplicates and closed segments */
unsigned int end_order; /* Index of line-end-point (0 = beginning and 1 = end) that is closest to another line */
double dist, next_dist; /* The nearest and next nearest distance to another segment */
};
struct GMTCONNECT_LINK { /* Information on linking segments together basd on end point properties */
uint64_t id; /* Running number ID starting at 0 for open segments only */
uint64_t orig_id; /* The original ID before expelling duplicates and closed segments */
uint64_t seg; /* Original segment number in table */
uint64_t n; /* Number of points in the segment */
uint64_t tbl; /* Which file (table) this segment came from (0, 1, 2, ...) */
bool used; /* True when we have connected this segment into a longer chain */
double x_end[2]; /* The x-coordinates of the two line end-points (0 = beginning and 1 = end) */
double y_end[2]; /* The y-coordinates of the two line end-points (0 = beginning and 1 = end) */
struct GMTCONNECT_NEAREST nearest[2]; /* The nearest line segment to the two end-points (0 = beginning and 1 = end) */
};
static void *New_Ctrl (struct GMT_CTRL *GMT) { /* Allocate and initialize a new control structure */
struct GMTCONNECT_CTRL *C = gmt_M_memory (GMT, NULL, 1, struct GMTCONNECT_CTRL);
/* Initialize values whose defaults are not 0/false/NULL */
C->T.unit = 'X'; /* Cartesian units unless overridden during parsing */
return (C);
}
static void Free_Ctrl (struct GMT_CTRL *GMT, struct GMTCONNECT_CTRL *C) { /* Deallocate control structure */
if (!C) return;
gmt_M_str_free (C->Out.file);
gmt_M_str_free (C->C.file);
gmt_M_str_free (C->D.format);
gmt_M_str_free (C->L.file);
gmt_M_str_free (C->Q.file);
gmt_M_free (GMT, C);
}
static int usage (struct GMTAPI_CTRL *API, int level) {
const char *name = gmt_show_name_and_purpose (API, THIS_MODULE_LIB, THIS_MODULE_CLASSIC_NAME, THIS_MODULE_PURPOSE);
if (level == GMT_MODULE_PURPOSE) return (GMT_NOERROR);
GMT_Usage (API, 0, "usage: %s [<table>] [-C<closedfile>] [-D[<template>]] [-L[<linkfile>]] [-Q<listfile>] "
"[-T[%s[+s<sdist>]]] [%s] [%s] [%s] [%s] [%s] [%s] [%s] [%s] [%s] [%s] [%s] [%s] [%s] [%s]\n",
name, GMT_DIST_OPT, GMT_V_OPT, GMT_a_OPT, GMT_b_OPT, GMT_d_OPT, GMT_e_OPT, GMT_f_OPT, GMT_g_OPT,
GMT_h_OPT, GMT_i_OPT, GMT_o_OPT, GMT_q_OPT, GMT_s_OPT, GMT_colon_OPT, GMT_PAR_OPT);
if (level == GMT_SYNOPSIS) return (GMT_MODULE_SYNOPSIS);
GMT_Message (API, GMT_TIME_NONE, " REQUIRED ARGUMENTS:\n");
GMT_Option (API, "<");
GMT_Message (API, GMT_TIME_NONE, "\n OPTIONAL ARGUMENTS:\n");
GMT_Usage (API, 1, "\n-C<closedfile>");
GMT_Usage (API, -2, "Write already-closed polygons to a separate <closedfile> [gmtconnect_closed.txt], "
"with the remaining open segments written separately to standard output "
"[All segments are written to a single output file; but see -D].");
GMT_Usage (API, 1, "\n-D[<template>]");
GMT_Usage (API, -2, "Write individual segments to separate files [Default writes one multisegment file to standard output]. "
"Append file name template which MUST contain a C-format specifier for an integer (e.g., %%d). "
"If the format also includes a %%c string BEFORE the %%d part we replace it with C(losed) or O(pen) "
"[Default uses gmtconnect_segment_%%d.txt].");
GMT_Usage (API, 1, "\n-L[<linkfile>]");
GMT_Usage (API, -2, "Write link information (seg id, begin/end nearest seg id, end, and distance) to file [gmtconnect_link.txt]. "
"Link output excludes duplicates and segments already forming a closed polygon.");
GMT_Usage (API, 1, "\n-Q<listfile>");
GMT_Usage (API, -2, "Used with -D to write names of files to a list. Optionally give listfile name [gmtconnect_list.txt]. "
"Embed %%c in the list name to write two separate lists: one for C(losed) and one for O(pen).");
gmt_dist_syntax (API->GMT, "T[" GMT_DIST_OPT "[+s<sdist>]]", "Set minimum <dist> to determine if a segment is closed [0].");
GMT_Usage (API, -2, "If two lines have endpoints closer than <dist> then they will be joined. "
"Optionally, append +s<sdist> which adds the requirement that the second closest "
"match must exceed <sdist> to connect (use same units as for <dist>). "
"If no arguments are given the we close all polygons regardless of the gaps.");
GMT_Option (API, "V,a,bi2,bo,d,e,f,g,h,i,o,q,s,:,.");
return (GMT_MODULE_USAGE);
}
static int parse (struct GMT_CTRL *GMT, struct GMTCONNECT_CTRL *Ctrl, struct GMT_OPTION *options) {
/* This parses the options provided to gmtconnect and sets parameters in CTRL.
* Any GMT common options will override values set previously by other commands.
* It also replaces any file names specified as input or output with the data ID
* returned when registering these sources/destinations with the API.
*/
unsigned int n_errors = 0;
int n = 0;
char A[GMT_LEN64] = {""}, B[GMT_LEN64] = {""};
struct GMT_OPTION *opt = NULL;
struct GMTAPI_CTRL *API = GMT->parent;
for (opt = options; opt; opt = opt->next) {
switch (opt->option) {
case '<': /* Skip input files */
if (GMT_Get_FilePath (API, GMT_IS_DATASET, GMT_IN, GMT_FILE_REMOTE, &(opt->arg))) n_errors++;
break;
case '>': /* Got named output file */
n_errors += gmt_M_repeated_module_option (API, Ctrl->Out.active);
n_errors += gmt_get_required_file (GMT, opt->arg, opt->option, 0, GMT_IS_DATASET, GMT_OUT, GMT_FILE_LOCAL, &(Ctrl->Out.file));
break;
/* Processes program-specific parameters */
case 'C': /* Separate closed from open segments */
n_errors += gmt_M_repeated_module_option (API, Ctrl->C.active);
gmt_M_str_free (Ctrl->C.file);
if (opt->arg[0]) Ctrl->C.file = strdup (opt->arg);
break;
case 'D': /* Write each segment to a separate output file */
n_errors += gmt_M_repeated_module_option (API, Ctrl->D.active);
gmt_M_str_free (Ctrl->D.format);
if (opt->arg[0]) Ctrl->D.format = strdup (opt->arg);
break;
case 'L': /* Write link information to file */
n_errors += gmt_M_repeated_module_option (API, Ctrl->L.active);
gmt_M_str_free (Ctrl->L.file);
if (opt->arg[0]) Ctrl->L.file = strdup (opt->arg);
break;
case 'Q': /* Write names of individual files to list(s) */
n_errors += gmt_M_repeated_module_option (API, Ctrl->Q.active);
gmt_M_str_free (Ctrl->Q.file);
if (opt->arg[0]) Ctrl->Q.file = strdup (opt->arg);
break;
case 'T': /* Set threshold distance */
n_errors += gmt_M_repeated_module_option (API, Ctrl->T.active[0]);
if (opt->arg[0]) { /* Specified a distance */
char *c = NULL;
if ((c = strstr (opt->arg, "+s"))) { /* Gave second distance via +s<dist> */
n_errors += gmt_M_repeated_module_option (API, Ctrl->T.active[1]);
c[0] = '\0'; /* Temporarily chop off modifier */
Ctrl->T.mode = gmt_get_distance (GMT, A, &(Ctrl->T.dist[0]), &(Ctrl->T.unit));
Ctrl->T.dist[1] = atof (&c[2]);
c[0] = '+'; /* Restore modifier */
}
else { /* Only one distance but possibly old-style /dist */
n = sscanf (opt->arg, "%[^/]/%s", A, B);
Ctrl->T.mode = gmt_get_distance (GMT, A, &(Ctrl->T.dist[0]), &(Ctrl->T.unit));
if (n == 2) { /* Gave second distance via /<dist> */
n_errors += gmt_M_repeated_module_option (API, Ctrl->T.active[1]);
Ctrl->T.dist[1] = atof (B);
}
}
}
else /* Do distance, connect segments regardless of separation */
Ctrl->T.dist[0] = DBL_MAX;
break;
default: /* Report bad options */
n_errors += gmt_default_option_error (GMT, opt);
break;
}
}
n_errors += gmt_M_check_condition (GMT, Ctrl->T.mode == -1, "Option -T: Unrecognized unit\n");
n_errors += gmt_M_check_condition (GMT, Ctrl->T.mode == -2, "Option -T: Unable to decode distance\n");
n_errors += gmt_M_check_condition (GMT, Ctrl->T.mode == -3, "Option -T: Distance is negative\n");
n_errors += gmt_M_check_condition (GMT, Ctrl->T.active[1] && Ctrl->T.dist[1] < 0.0, "Option -T: Secondary distance is negative\n");
if (GMT->common.b.active[GMT_IN] && GMT->common.b.ncol[GMT_IN] == 0) GMT->common.b.ncol[GMT_IN] = 2;
n_errors += gmt_M_check_condition (GMT, GMT->common.b.active[GMT_IN] && GMT->common.b.ncol[GMT_IN] < 2, "Binary input data (-bi) must have at least 2 columns\n");
n_errors += gmt_M_check_condition (GMT, Ctrl->C.active && Ctrl->D.active, "Option -C cannot be used with -D!\n");
n_errors += gmt_M_check_condition (GMT, Ctrl->C.active && Ctrl->D.active, "Option -C cannot be used with -D!\n");
return (n_errors ? GMT_PARSE_ERROR : GMT_NOERROR);
}
GMT_LOCAL int gmtconnect_found_a_near_segment (struct GMTCONNECT_LINK *S, uint64_t id, int order, double threshold, bool nn_check, double sdist) {
/* Checks if OK to connect this segment to its nearest neighbor and returns true if OK */
if (S[S[id].nearest[order].id].used) return (false); /* Segment has been conncted already */
if (S[id].nearest[order].dist > threshold) return (false); /* Distance exceeds the selected threshold distance, so no cigar */
if (!nn_check) return (true); /* No other requirement specified, so we select this candidate segment */
if (S[id].nearest[order].next_dist > sdist) return (true); /* Next nearest neighbor is far enough away, so we select this candidate segment */
return (false); /* Failed the next nearest neighbor distance test */
}
GMT_LOCAL uint64_t gmtconnect_copy_this_segment (struct GMT_DATASEGMENT *in, struct GMT_DATASEGMENT *out, uint64_t out_start, uint64_t in_start, uint64_t in_end) {
uint64_t row_in, row_out, col;
int64_t inc;
bool done = false;
/* We will copy the records from the in segment from rows in_start up to and including in_end.
* If in_start > in_end then we will end up reversing the order of the records on output.
* The records are copied to the out segment starting at output record out_start (initially 0).
* We return the next row number to be used for output.
*/
inc = (in_start < in_end) ? +1 : -1; /* Go forwards or backwards through the input */
for (row_in = in_start, row_out = out_start; !done; row_in += inc, row_out++) { /* Either loop from 1st to last or the other way */
for (col = 0; col < in->n_columns; col++) out->data[col][row_out] = in->data[col][row_in]; /* Copy this row */
done = (row_in == in_end); /* Stop when finishing the end row */
}
return (row_out); /* The next output record number */
}
#define bailout(code) {gmt_M_free_options (mode); return (code);}
#define Return(code) {Free_Ctrl (GMT, Ctrl); gmt_M_free (GMT, segment); gmt_end_module (GMT, GMT_cpy); bailout (code);}
EXTERN_MSC int GMT_gmtconnect (void *V_API, int mode, void *args) {
int error = 0;
bool separate_open_and_closed = false, first, wrap_up = false, done, closed, *skip = NULL;
unsigned int nearest_end[2][2], j, n_qfiles = 0, end_order, smode = GMT_NO_STRINGS;
unsigned int io_mode = GMT_WRITE_SET, q_mode = GMT_WRITE_SET, d_mode = 0, ii, end, first_end_order;
size_t n_seg_alloc[2] = {0, 0}, n_alloc_pts, b_alloc = GMT_BUFSIZ, b_len = 0, len;
uint64_t tbl, n_columns, n, k, n_rows, seg, np, ns, n_open, out_seg, out_p, id, id2;
uint64_t n_closed_orig = 0, n_trouble = 0, n_closed = 0, chain = 0, match = 0, L, n_steps_pass_1;
uint64_t dim_tscr[4] = {1, 1, 0, 0}, start_id, iseg, jseg, n_seg_length, G, n_steps_pass_2;
double dd[2][2], p_last_x, p_last_y, p_first_x, p_first_y, distance, closed_dist = 0.0;
char *buffer = NULL, msg[GMT_BUFSIZ] = {""}, text[GMT_LEN64] = {""}, *BE = "BE", *ofile = NULL;
struct GMTCONNECT_LINK *segment = NULL;
struct GMT_DATASET *D[2] = {NULL, NULL}, *C = NULL;
struct GMT_DATASET *Q = NULL;
struct GMT_DATASEGMENT **T[2] = {NULL, NULL}, *S = NULL;
struct GMT_DATASEGMENT *QT[2] = {NULL, NULL};
struct GMT_DATATABLE_HIDDEN *TH = NULL;
struct GMT_DATASEGMENT_HIDDEN *SH = NULL;
struct GMTCONNECT_CTRL *Ctrl = NULL;
struct GMT_CTRL *GMT = NULL, *GMT_cpy = NULL;
struct GMT_OPTION *options = NULL;
struct GMTAPI_CTRL *API = gmt_get_api_ptr (V_API); /* Cast from void to GMTAPI_CTRL pointer */
/*----------------------- Standard module initialization and parsing ----------------------*/
if (API == NULL) return (GMT_NOT_A_SESSION);
if (mode == GMT_MODULE_PURPOSE) return (usage (API, GMT_MODULE_PURPOSE)); /* Return the purpose of program */
options = GMT_Create_Options (API, mode, args); if (API->error) return (API->error); /* Set or get option list */
if ((error = gmt_report_usage (API, options, 0, usage)) != GMT_NOERROR) bailout (error); /* Give usage if requested */
/* Parse the command-line arguments */
if ((GMT = gmt_init_module (API, THIS_MODULE_LIB, THIS_MODULE_CLASSIC_NAME, THIS_MODULE_KEYS, THIS_MODULE_NEEDS, module_kw, &options, &GMT_cpy)) == NULL) bailout (API->error); /* Save current state */
if (GMT_Parse_Common (API, THIS_MODULE_OPTIONS, options)) Return (API->error);
Ctrl = New_Ctrl (GMT); /* Allocate and initialize defaults in a new control structure */
if ((error = parse (GMT, Ctrl, options)) != 0) Return (error);
/*---------------------------- This is the gmtconnect main code ----------------------------*/
/* Now we are ready to take on some input values */
buffer = gmt_M_memory (GMT, NULL, b_alloc, char); /* Initial buffer size for header */
GMT_Report (API, GMT_MSG_INFORMATION, "Processing input table data\n");
if (Ctrl->D.active) { /* We want output to go to individual files for each segment [Default writes everything to stdout] */
io_mode = GMT_WRITE_SEGMENT; /* This means "write segments to separate files" */
if (!Ctrl->D.format) Ctrl->D.format = strdup ("gmtconnect_segment_%d.txt"); /* Default naming convention for segments if none given by -D */
if (strstr (Ctrl->D.format, "%c")) separate_open_and_closed = true; /* Also add letters C (closed) or O (open) to the filenames */
if (Ctrl->Q.active) { /* We also want to build a segment connection file(s) */
if (!Ctrl->Q.file) Ctrl->Q.file = strdup ("gmtconnect_list.txt"); /* Default -Q list name if none was given via -Q */
dim_tscr[GMT_TBL] = n_qfiles = (strstr (Ctrl->Q.file, "%c")) ? 2 : 1; /* Build one (all segments) or two tables (separate list for closed and open) */
/* Allocate one or two tables with one segment each */
if ((Q = GMT_Create_Data (GMT->parent, GMT_IS_DATASET, GMT_IS_TEXT, GMT_WITH_STRINGS, dim_tscr, NULL, NULL, 0, 0, NULL)) == NULL) {
GMT_Report (API, GMT_MSG_ERROR, "Unable to create a data set for segment lists\n");
gmt_M_free (GMT, buffer);
Return (API->error);
}
if (n_qfiles == 2) { /* We want to build two lists (closed and open) */
static char status[2] = {'C', 'O'};
q_mode = GMT_WRITE_TABLE; /* This means write tables to separate files */
dim_tscr[GMT_TBL] = 1; /* Reset from 2 to 1 */
/* Create the two filenames with C and O identifiers */
for (k = CLOSED; k <= OPEN; k++) {
TH = gmt_get_DT_hidden (Q->table[k]); /* Hidden information about this table */
sprintf (buffer, Ctrl->Q.file, status[k]); /* Create the file name */
TH->file[GMT_OUT] = strdup (buffer); /* Place the file name in table header */
QT[k] = Q->table[k]->segment[0]; /* Shorthand for segments in closed polygon list */
SH = gmt_get_DS_hidden (QT[k]); /* Get hidden segment information */
SH->n_alloc = GMT_CHUNK; /* Initial allocation sizes */
QT[k]->text = gmt_M_memory (GMT, NULL, SH->n_alloc, char *); /* Allocate n_alloc records for now */
}
}
else { /* A single list will do */
q_mode = GMT_WRITE_SET; /* This means write tables to a single file */
TH = gmt_get_DT_hidden (Q->table[0]); /* Hidden information about this table */
TH->file[GMT_OUT] = strdup (Ctrl->Q.file); /* Place the file name in table header */
QT[OPEN] = QT[CLOSED] = Q->table[0]->segment[0]; /* The two QT pointers point to the same table, which is 0 [OPEN] */
SH = gmt_get_DS_hidden (QT[OPEN]); /* Get hidden segment information */
SH->n_alloc = GMT_CHUNK; /* Initial allocation sizes */
QT[OPEN]->text = gmt_M_memory (GMT, NULL, SH->n_alloc, char *); /* Allocate n_alloc records for now */
}
}
}
if (gmt_init_distaz (GMT, Ctrl->T.unit, Ctrl->T.mode, GMT_MAP_DIST) == GMT_NOT_A_VALID_TYPE) { /* Initialize distance-computing machinery with proper unit */
gmt_M_free (GMT, buffer);
Return (GMT_NOT_A_VALID_TYPE);
}
if (GMT_Init_IO (API, GMT_IS_DATASET, GMT_IS_LINE, GMT_IN, GMT_ADD_DEFAULT, 0, options) != GMT_NOERROR) { /* Establishes data input assuming lines */
gmt_M_free (GMT, buffer);
Return (API->error);
}
/* Read in all the input segments into one dataset D[GMT_IN] */
if ((D[GMT_IN] = GMT_Read_Data (API, GMT_IS_DATASET, GMT_IS_FILE, 0, GMT_READ_NORMAL, NULL, NULL, NULL)) == NULL) {
gmt_M_free (GMT, buffer);
Return (API->error);
}
if (D[GMT_IN]->n_columns < 2) {
GMT_Report (API, GMT_MSG_ERROR, "Input data have %d column(s) but at least 2 are needed\n", (int)D[GMT_IN]->n_columns);
gmt_M_free (GMT, buffer);
Return (GMT_DIM_TOO_SMALL);
}
if (D[GMT_IN]->n_records == 0) { /* Empty files, nothing to do */
GMT_Report (API, GMT_MSG_WARNING, "No data records found.\n");
gmt_M_free (GMT, buffer);
Return (GMT_RUNTIME_ERROR);
}
/* Surely we don't need any more segment space than the number of input segments */
segment = gmt_M_memory (GMT, NULL, D[GMT_IN]->n_segments, struct GMTCONNECT_LINK);
id = ns = out_seg = 0;
GMT_Report (API, GMT_MSG_INFORMATION, "Check for already-closed polygons\n");
/* Closed polygons are already finished - just identify, write them out, and move on */
/* Allocate D[GMT_OUT] and possibly C, both with nrows = ncolumns so just segment structs are allocated */
n_columns = dim_tscr[GMT_COL] = D[GMT_IN]->n_columns; /* Set the required columns for output to match that of input file */
dim_tscr[GMT_SEG] = 0; /* Allocate no segments for now - we will do this as needed */
if ((D[GMT_OUT] = GMT_Create_Data (API, GMT_IS_DATASET, D[GMT_IN]->geometry, 0, dim_tscr, NULL, NULL, 0, 0, NULL)) == NULL) {
GMT_Report (API, GMT_MSG_ERROR, "Unable to create a data set for output segments\n");
gmt_M_free (GMT, buffer);
Return (API->error);
}
n_seg_alloc[OPEN] = D[GMT_IN]->n_segments; /* Cannot end up with more open segments than given on input so this is an upper limit */
T[OPEN] = gmt_M_memory (GMT, NULL, n_seg_alloc[OPEN], struct GMT_DATASEGMENT *);
if (Ctrl->C.active) { /* Wish to return already-closed polygons via a separate file */
if (Ctrl->C.file == NULL) /* No such filename given, select default name */
Ctrl->C.file = strdup ("gmtconnect_closed.txt");
if ((C = GMT_Create_Data (API, GMT_IS_DATASET, GMT_IS_POLY, 0, dim_tscr, NULL, NULL, 0, 0, NULL)) == NULL) {
GMT_Report (API, GMT_MSG_ERROR, "Unable to create a data set for closed segments\n");
gmt_M_free (GMT, buffer);
Return (API->error);
}
n_seg_alloc[CLOSED] = n_seg_alloc[OPEN]; /* Cannot end up with more closed segments than given on input so this is an upper limit */
T[CLOSED] = gmt_M_memory (GMT, NULL, n_seg_alloc[CLOSED], struct GMT_DATASEGMENT *);
}
else
T[CLOSED] = T[OPEN]; /* Everything returned via the same dataset */
/* Start processing all the input segments in D[GMT_IN] */
ns = 0; /* So the ID of the very first segment will be 0 */
n_open = n_closed = 0; /* Numbers of open segments and closed polygons found */
closed_dist = (Ctrl->C.active) ? Ctrl->T.dist[0] : 0.0;
for (tbl = 0; tbl < D[GMT_IN]->n_tables; tbl++) { /* For each input data table */
for (seg = 0; seg < D[GMT_IN]->table[tbl]->n_segments; seg++) { /* For each input segment */
np = D[GMT_IN]->table[tbl]->segment[seg]->n_rows; /* Short-hand to avoid this full expression below */
S = D[GMT_IN]->table[tbl]->segment[seg]; /* Short hand to current input segment */
/* Get distance between first and last point in this segment */
distance = gmt_distance (GMT, S->data[GMT_X][0], S->data[GMT_Y][0], S->data[GMT_X][np-1], S->data[GMT_Y][np-1]);
if (np > 2 && distance <= closed_dist) { /* Already a closed segment by our -T definition, just write out and forget about it in the rest of the program */
/* Allocate space for this output segment */
smode = (S->text) ? GMT_WITH_STRINGS : GMT_NO_STRINGS;
n_rows = (Ctrl->C.active && distance > 0.0) ? np + 1 : np; /* Add one extra row if closure is not exact so we can add that closing repeat point */
T[CLOSED][out_seg] = GMT_Alloc_Segment (GMT->parent, smode, n_rows, n_columns, S->header, NULL);
if (Ctrl->D.active) { /* Write closed polygons to individual files */
SH = gmt_get_DS_hidden (T[CLOSED][out_seg]);
(separate_open_and_closed) ? sprintf (buffer, Ctrl->D.format, 'C', out_seg) : sprintf (buffer, Ctrl->D.format, out_seg);
SH->file[GMT_OUT] = strdup (buffer); /* Assign the name of this segment-file */
if (Ctrl->Q.active) { /* Also maintain list of such files */
SH = gmt_get_DS_hidden (QT[CLOSED]);
QT[CLOSED]->text[QT[CLOSED]->n_rows++] = strdup (buffer);
if (QT[CLOSED]->n_rows == SH->n_alloc) QT[CLOSED]->text = gmt_M_memory (GMT, QT[CLOSED]->text, (SH->n_alloc <<= 1), char *);
}
}
out_p = gmtconnect_copy_this_segment (S, T[CLOSED][out_seg], 0, 0, np-1); /* Duplicate input segment to output */
if (Ctrl->C.active && distance > 0.0) (void) gmtconnect_copy_this_segment (S, T[CLOSED][out_seg], out_p, 0, 0); /* Close polygon explicitly */
n_closed_orig++; /* Number of originally closed polygons as found in input */
out_seg++; /* Number of closed segments placed in T[CLOSED] so far */
n_closed++; /* Number of closed polygons so far (which will grow when we connect below) */
}
else if (Ctrl->C.active) { /* This polygon is not closed and -C is active: Copy this open segment to the separate output dataset */
/* Allocate space for this segment */
smode = (S->text) ? GMT_WITH_STRINGS : GMT_NO_STRINGS;
T[OPEN][n_open] = GMT_Alloc_Segment (GMT->parent, smode, np, n_columns, S->header, NULL);
(void) gmtconnect_copy_this_segment (S, T[OPEN][n_open], 0, 0, np-1); /* Duplicate input to output */
n_open++; /* Number of open segments placed in T[OPEN] so far */
}
else { /* No -C was given: Here we have a segment that is not closed. Store metadata about segment to LINK structure and copy end points; more work on linking takes place below */
/* Store information about this segment (end points, ID, etc.) in the array "segment" with info on all open lines */
if (np == 1) GMT_Report (API, GMT_MSG_WARNING, "Segment %" PRIu64 " only consists of a single point. May require additional connections.\n", id);
segment[id].id = id; /* Running segment ID starting at 0 (for open segments only) */
segment[id].orig_id = ns; /* ns is original input segment number */
segment[id].tbl = tbl; /* Remember which input table this segment came from */
segment[id].seg = seg; /* Remember which input segment in this table it came from */
segment[id].n = np; /* Number of points in this segment */
/* Record start and end coordinates for this segment, and initialize the nearest structures to having no nearest neighbor segment yet */
segment[id].x_end[END_A] = S->data[GMT_X][0]; /* First point in segment */
segment[id].y_end[END_A] = S->data[GMT_Y][0];
segment[id].x_end[END_B] = S->data[GMT_X][np-1]; /* Last point in segment */
segment[id].y_end[END_B] = S->data[GMT_Y][np-1];
segment[id].nearest[END_A].dist = segment[id].nearest[END_B].dist = DBL_MAX;
segment[id].nearest[END_A].next_dist = segment[id].nearest[END_B].next_dist = DBL_MAX;
id++; /* Increment open segment ID number */
}
ns++; /* Increment running segment ID (open or closed) */
}
}
/* Here we have gone through all input segments and separated open from closed */
if (Ctrl->C.active) { /* Finalize allocation for closed segments now that we know how many there were */
C->table[0]->segment = gmt_M_memory (GMT, T[CLOSED], n_closed, struct GMT_DATASEGMENT *);
C->n_segments = C->table[0]->n_segments = n_closed;
/* With -C we only separate closed from open and then we are done */
GMT_Report (API, GMT_MSG_INFORMATION, "Separated %" PRIu64 " closed and %" PRIu64 " open segments\n", n_closed, n_open);
wrap_up = true; /* Means to quit once we have written those results to file - no nesting will take place */
}
else if (id == 0) { /* All segments were already closed polygons */
GMT_Report (API, GMT_MSG_INFORMATION, "All segments already form closed polygons - no new segment file created\n");
wrap_up = true; /* Means to quit once we have written those results to file - no nesting possible */
}
gmt_set_segmentheader (GMT, GMT_OUT, n_open > 1 || n_closed > 1); /* Turn on segment headers on output if we have more than one output segment */
if (wrap_up) { /* Write out results and exit */
D[GMT_OUT]->table[0]->segment = gmt_M_memory (GMT, T[OPEN], n_open, struct GMT_DATASEGMENT *); /* Finalize output egment allocation */
D[GMT_OUT]->n_segments = D[GMT_OUT]->table[0]->n_segments = n_open;
if (Ctrl->C.active) { /* Write n_open segments from D[OUT] and n_closed from C; here we do C first */
if (GMT_Write_Data (API, GMT_IS_DATASET, GMT_IS_FILE, GMT_IS_POLY, GMT_WRITE_SET, NULL, Ctrl->C.file, C) != GMT_NOERROR) {
gmt_M_free (GMT, buffer);
Return (API->error);
}
}
/* Write open segments to the outfile (probably stdout) if not set */
if (GMT_Write_Data (API, GMT_IS_DATASET, GMT_IS_FILE, GMT_IS_LINE, GMT_WRITE_SET, NULL, Ctrl->Out.file, D[GMT_OUT]) != GMT_NOERROR) {
gmt_M_free (GMT, buffer);
Return (API->error);
}
if (Ctrl->Q.active) { /* Also finalize link file and write it out to 1 (or 2) files depending on q_mode */
Q->table[CLOSED]->segment[0]->text = gmt_M_memory (GMT, QT[CLOSED]->text, QT[CLOSED]->n_rows, char *);
if (n_qfiles == 2) Q->table[OPEN]->segment[0]->text = gmt_M_memory (GMT, QT[OPEN]->text, QT[OPEN]->n_rows, char *);
if (GMT_Write_Data (API, GMT_IS_DATASET, GMT_IS_FILE, GMT_IS_TEXT, q_mode, NULL, Ctrl->Q.file, Q) != GMT_NOERROR) {
gmt_M_free (GMT, buffer);
Return (API->error);
}
}
gmt_M_free (GMT, buffer);
Return (GMT_NOERROR); /* That is it, we are done here */
}
/* Below here, -C was NOT given since those cases have already been dealt with.
* Also T[OPEN] = T[CLOSED] and we already have found out_seg segments.
* Here we need to do the connecting work. We already have n_closed polygons stored n D[GMT_OUT] at this point */
ns = id; /* Number of open segments remaining, adjust necessary memory */
if (ns < D[GMT_IN]->n_segments) segment = gmt_M_memory (GMT, segment, ns, struct GMTCONNECT_LINK); /* Trim the fat */
skip = gmt_M_memory (GMT, NULL, ns, bool); /* Used when looking for duplicate segments only */
GMT_Report (API, GMT_MSG_INFORMATION, "Found %" PRIu64 " closed individual polygons\n", n_closed_orig);
/* The connect algorithm will be confused if there are identical duplicates of segments - thus we check for this case first */
GMT_Report (API, GMT_MSG_INFORMATION, "Check for duplicate line segments\n");
for (iseg = 0; iseg < ns; iseg++) { /* Loop over remaining open lines */
if (skip[iseg]) continue; /* Skip lines that has been determined to be a duplicate line */
for (jseg = iseg + 1; jseg < ns; jseg++) { /* Loop over all other open lines */
if (skip[jseg]) continue; /* But skip line that has been determined to be a duplicate line */
if (segment[iseg].n != segment[jseg].n) continue; /* Different number of points mean not a duplicate*/
/* See if any pair of endpoints matches (duplicate line may be reversed so must check both ways) */
if (((doubleAlmostEqualZero (segment[iseg].x_end[END_A], segment[jseg].x_end[END_A]) && doubleAlmostEqualZero (segment[iseg].y_end[END_A], segment[jseg].y_end[END_A])) &&
(doubleAlmostEqualZero (segment[iseg].x_end[END_B], segment[jseg].x_end[END_B]) && doubleAlmostEqualZero (segment[iseg].y_end[END_B], segment[jseg].y_end[END_B]))) ||
((doubleAlmostEqualZero (segment[iseg].x_end[END_A], segment[jseg].x_end[END_B]) && doubleAlmostEqualZero (segment[iseg].y_end[END_A], segment[jseg].y_end[END_B])) &&
(doubleAlmostEqualZero (segment[iseg].x_end[END_B], segment[jseg].x_end[END_A]) && doubleAlmostEqualZero (segment[iseg].y_end[END_B], segment[jseg].y_end[END_A])))) { /* Yes, identical end points */
for (k = match = 0; k < segment[iseg].n && k == match; k++) { /* Compute number of duplicate points */
match += (doubleAlmostEqualZero (D[GMT_IN]->table[segment[iseg].tbl]->segment[segment[iseg].seg]->data[GMT_X][k], D[GMT_IN]->table[segment[jseg].tbl]->segment[segment[jseg].seg]->data[GMT_X][k]) &&
doubleAlmostEqualZero (D[GMT_IN]->table[segment[iseg].tbl]->segment[segment[iseg].seg]->data[GMT_Y][k], D[GMT_IN]->table[segment[jseg].tbl]->segment[segment[jseg].seg]->data[GMT_Y][k]));
}
if (match == segment[iseg].n) { /* An exact match */
GMT_Report (API, GMT_MSG_INFORMATION, "Line segments %" PRIu64 " and %" PRIu64 "are duplicates - Line segment %" PRIu64 " will be ignored\n", iseg, jseg, jseg);
skip[jseg] = true; /* Flag this line for skipping */
}
}
}
}
/* Eliminate the duplicate segments from further consideration by shuffling the others up in the array */
for (iseg = jseg = 0; iseg < ns; iseg++) {
if (skip[iseg]) continue;
if (iseg > jseg) segment[jseg] = segment[iseg];
jseg++;
}
GMT_Report (API, GMT_MSG_INFORMATION, "Found %" PRIu64 " duplicate segments\n", ns - jseg);
if (jseg < ns) GMT_Report (API, GMT_MSG_INFORMATION, "%" PRIu64 " duplicate segment removed\n", ns - jseg);
ns = jseg; /* The new number of open segments after duplicates have been removed */
gmt_M_free (GMT, skip); /* Done with this array */
/* Duplicates eliminated, now we can try to make connections */
GMT_Report (API, GMT_MSG_INFORMATION, "Try to connect %" PRIu64 " open segments\n", ns);
if (Ctrl->T.unit == 'X')
GMT_Report (API, GMT_MSG_INFORMATION, "Calculate and rank end point separations [cutoff = %g sdist = %g]\n", Ctrl->T.dist[0], Ctrl->T.dist[1]);
else
GMT_Report (API, GMT_MSG_INFORMATION, "Calculate and rank end point separations [cutoff = %g%c sdist = %g%c]\n", Ctrl->T.dist[0], Ctrl->T.unit, Ctrl->T.dist[1], Ctrl->T.unit);
/* We determine the distance from each segment's two endpoints to the two endpoints on every other
* segment; this yields four distances per segment. We then assign the nearest endpoint to each end
* of a segment to the nearest structure which keeps the id of the nearest segment found so far.
* Below dd[segment][end] is a 2x2 array with the four distances for one pair of segments SEG_I and SEG_J.
*/
for (iseg = 0; iseg < ns; iseg++) { /* Loop over all open line segments and their two endpoints... */
for (jseg = iseg; jseg < ns; jseg++) { /* ...and compare distances to all other open line segments' two endpoints */
/* nearest_end indicates which end is closest to this end */
if (iseg == jseg) { /* Store offset between the endpoints of a single segment (would be 0 if closed but those polygons have already been dealt with) */
dd[SEG_I][END_A] = dd[SEG_J][END_B] = DBL_MAX; /* Flag as single line segment so two ends are not used to connect them */
/* Only compute a distance for this single segment if it has more than 2 points. 2 point cannot connect back on itself as it would make a polygon with zero area */
dd[SEG_I][END_B] = dd[SEG_J][END_A] = (segment[iseg].n < 3) ? DBL_MAX : gmt_distance (GMT, segment[iseg].x_end[END_A], segment[iseg].y_end[END_A], segment[iseg].x_end[END_B], segment[iseg].y_end[END_B]);
nearest_end[SEG_I][END_A] = nearest_end[SEG_J][END_A] = END_B; /* Duplicate the nearest ID info since it is a single line segment compared to itself */
nearest_end[SEG_J][END_B] = nearest_end[SEG_I][END_B] = END_A;
}
else { /* Store the distances between the 4 possible end-to-end configurations */
dd[SEG_I][END_A] = gmt_distance (GMT, segment[iseg].x_end[END_A], segment[iseg].y_end[END_A], segment[jseg].x_end[END_A], segment[jseg].y_end[END_A]);
dd[SEG_I][END_B] = gmt_distance (GMT, segment[iseg].x_end[END_A], segment[iseg].y_end[END_A], segment[jseg].x_end[END_B], segment[jseg].y_end[END_B]);
dd[SEG_J][END_A] = gmt_distance (GMT, segment[iseg].x_end[END_B], segment[iseg].y_end[END_B], segment[jseg].x_end[END_A], segment[jseg].y_end[END_A]);
dd[SEG_J][END_B] = gmt_distance (GMT, segment[iseg].x_end[END_B], segment[iseg].y_end[END_B], segment[jseg].x_end[END_B], segment[jseg].y_end[END_B]);
/* Determine which end is nearest */
for (end = 0; end < 2; end++) nearest_end[SEG_I][end] = (dd[end][END_A] <= dd[end][END_B]) ? END_A : END_B;
for (end = 0; end < 2; end++) nearest_end[SEG_J][end] = (dd[END_A][end] <= dd[END_B][end]) ? END_A : END_B;
}
sprintf (msg, "Pair %d - %d, dd[i][j] = %g, %g, %g, %g\n", (int)iseg, (int)jseg, dd[0][0], dd[0][1], dd[1][0], dd[1][1]);
GMT_Report (API, GMT_MSG_DEBUG, msg);
/* Update list of closest matches for both ends */
for (ii = 0; ii < 2; ii++) { /* For each end of the segments */
end = nearest_end[SEG_I][ii]; /* The end of segment jseg that so far is closest to segment iseg's end ii */
if (dd[ii][end] < segment[iseg].nearest[ii].dist) { /* This distance is shorter than the previous shortest distance, so time to update it */
segment[iseg].nearest[ii].next_dist = segment[iseg].nearest[ii].dist; /* Previous closest distance becomes the next-nearest distance */
segment[iseg].nearest[ii].orig_id = segment[jseg].orig_id;
segment[iseg].nearest[ii].id = jseg;
segment[iseg].nearest[ii].dist = dd[ii][end];
segment[iseg].nearest[ii].end_order = end;
}
else if (dd[ii][end] < segment[iseg].nearest[ii].next_dist) /* Update the next nearest distance only (no need to know what segment this is) */
segment[iseg].nearest[ii].next_dist = dd[ii][end];
end = nearest_end[SEG_J][ii]; /* The end of segment iseg that was closest to segment jseg's end ii */
if (dd[end][ii] < segment[jseg].nearest[ii].dist) { /* This distance is shorter than the previous shortest distance, so time to update it */
segment[jseg].nearest[ii].next_dist = segment[jseg].nearest[ii].dist; /* Previous closest distance becomes the next-nearest distance */
segment[jseg].nearest[ii].orig_id = segment[iseg].orig_id;
segment[jseg].nearest[ii].id = iseg;
segment[jseg].nearest[ii].dist = dd[end][ii];
segment[jseg].nearest[ii].end_order = end;
}
else if (dd[end][ii] < segment[jseg].nearest[ii].next_dist) /* Update the next nearest distance only (no need to know what segment this is) */
segment[jseg].nearest[ii].next_dist = dd[end][ii];
}
}
/* Final information for segment iseg */
sprintf (msg, "Seg %d dist[0], next_dist[0], dist[1], next_dist[1] = %g, %g, %g, %g\n", (int)iseg, segment[iseg].nearest[0].dist, segment[iseg].nearest[0].next_dist, segment[iseg].nearest[1].dist, segment[iseg].nearest[1].next_dist);
GMT_Report (API, GMT_MSG_DEBUG, msg);
}
/* Done determining distances from endpoints to nearest endpoints for all line segments */
if (Ctrl->L.active) { /* We can now write out the link information we found */
struct GMT_DATASET *LNK = NULL;
char name[GMT_BUFSIZ] = {""}, name0[GMT_BUFSIZ] = {""}, name1[GMT_BUFSIZ] = {""}, fmt[GMT_BUFSIZ] = {""};
char *pp = NULL, *s = GMT->current.setting.io_col_separator;
if (!Ctrl->L.file) Ctrl->L.file = strdup ("gmtconnect_link.txt"); /* Use default output filename since none was provided */
dim_tscr[GMT_TBL] = 1; dim_tscr[GMT_SEG] = 1; dim_tscr[GMT_ROW] = ns; dim_tscr[GMT_COL] = 0; /* Dimensions of single output table with single segment of ns rows */
if ((LNK = GMT_Create_Data (API, GMT_IS_DATASET, GMT_IS_TEXT, GMT_WITH_STRINGS, dim_tscr, NULL, NULL, 0, 0, NULL)) == NULL) {
GMT_Report (API, GMT_MSG_ERROR, "Unable to create a data set for link lists\n");
gmt_M_free (GMT, buffer);
Return (API->error);
}
/* Set up a format statement for the link output */
sprintf (fmt, "%%" PRIu64 "%s%%s%s%%s%s%%c%s%s%s%s%s%%s%s%%c%s%s%s%s", s, s, s, s, GMT->current.setting.format_float_out, s, GMT->current.setting.format_float_out, \
s, s, s, GMT->current.setting.format_float_out, s, GMT->current.setting.format_float_out);
/* Create a single table header */
gmt_set_tableheader (GMT, GMT_OUT, true); /* Turn on table headers on output */
sprintf (buffer, "#id%ssegid%s@begin%sb_pt%sb_dist%sb_next%s@end%se_pt%se_dist%se_next", s, s, s, s, s, s, s, s, s);
if (GMT_Set_Comment (API, GMT_IS_DATASET, GMT_COMMENT_IS_COLNAMES, buffer, LNK)) Return (API->error);
for (iseg = 0; iseg < ns; iseg++) { /* Loop over open segments */
G = segment[iseg].tbl; L = segment[iseg].seg; /* Short hand notation */
/* If -L is in the segment header, extract the ID from that, else use the input running number as ID */
if (D[GMT_IN]->table[G]->segment[L]->header && (pp = strstr (D[GMT_IN]->table[G]->segment[L]->header, "-L")) != NULL) {
strncpy (name, &pp[2], GMT_BUFSIZ);
for (j = 0; name[j]; j++) if (name[j] == ' ') name[j] = '\0'; /* Just truncate after 1st word */
} else sprintf (name, "%" PRIu64, segment[iseg].orig_id);
G = segment[segment[iseg].nearest[0].id].tbl; L = segment[segment[iseg].nearest[0].id].seg;
/* If -L is in the segment header, extract the ID from that, else use the input running number as ID */
if (D[GMT_IN]->table[G]->segment[L]->header && (pp = strstr (D[GMT_IN]->table[G]->segment[L]->header, "-L")) != NULL) {
strncpy (name0, &pp[2], GMT_BUFSIZ);
for (j = 0; name0[j]; j++) if (name0[j] == ' ') name0[j] = '\0'; /* Just truncate after 1st word */
} else sprintf (name0, "%" PRIu64, segment[iseg].nearest[0].orig_id);
G = segment[segment[iseg].nearest[1].id].tbl; L = segment[segment[iseg].nearest[1].id].seg;
/* If -L is in the segment header, extract the ID from that, else use the input running number as ID */
if (D[GMT_IN]->table[G]->segment[L]->header && (pp = strstr (D[GMT_IN]->table[G]->segment[L]->header, "-L")) != NULL) {
strncpy (name1, &pp[2], GMT_BUFSIZ);
for (j = 0; name1[j]; j++) if (name1[j] == ' ') name1[j] = '\0'; /* Just truncate after 1st word */
} else sprintf (name1, "%" PRIu64, segment[iseg].nearest[1].orig_id);
/* OK, compose the output record using the format and information provided */
sprintf (buffer, fmt, segment[iseg].orig_id, name, name0, BE[segment[iseg].nearest[0].end_order], segment[iseg].nearest[0].dist, segment[iseg].nearest[0].next_dist, name1, \
BE[segment[iseg].nearest[1].end_order], segment[iseg].nearest[1].dist, segment[iseg].nearest[1].next_dist);
LNK->table[0]->segment[0]->text[iseg] = strdup (buffer);
}
LNK->table[0]->n_records = LNK->table[0]->segment[0]->n_rows = ns; /* Number of records for this file */
if (GMT_Write_Data (API, GMT_IS_DATASET, GMT_IS_FILE, GMT_IS_TEXT, GMT_WRITE_SET, NULL, Ctrl->L.file, LNK) != GMT_NOERROR) {
gmt_M_free (GMT, buffer);
Return (API->error);
}
if (GMT_Destroy_Data (API, &LNK) != GMT_NOERROR) {
gmt_M_free (GMT, buffer);
Return (API->error);
}
}
start_id = n_closed = n_open = 0; /* Initialize counters for the connection of line segments into closed polygons */
done = false;
GMT_Report (API, GMT_MSG_INFORMATION, "Assemble new line segments and polygons\n");
/* We start at the very first open line segment (start_id = 0) and trace through its nearest line segments
* until closed or running out of new line segments */
while (!done) {
/* Find the 'beginning' of the chain that this segment belongs to by tracing the connections
* until we either reappear at the starting point (a closed loop) or we reach an end (i.e.,
* the nearest next endpoint is beyond the separation threshold. */
id = start_id; /* This is the first line segment in a new chain */
sprintf (buffer, "%" PRIu64, segment[id].orig_id);
b_len = strlen (buffer);
/* Select the endpoint that has the nearest segment */
end_order = first_end_order = (segment[id].nearest[0].dist < segment[id].nearest[1].dist) ? 0 : 1;
closed = false; /* Growing line segment is not closed yet */
n_steps_pass_1 = 0; /* Nothing appended yet to this single line segment */
n_alloc_pts = segment[id].n; /* Number of points needed so far is just those from this first (start_id) segment */
while (!done && gmtconnect_found_a_near_segment (segment, id, end_order, Ctrl->T.dist[0], Ctrl->T.active[1], Ctrl->T.dist[1])) { /* gmtconnect_found_a_near_segment returns true if its nearest segment at this end is close enough */
id2 = segment[id].nearest[end_order].id; /* ID of nearest segment at end end_order */
snprintf (text, GMT_LEN64, " -> %" PRIu64, segment[id2].orig_id);
b_len += strlen (text);
if (b_len >= b_alloc) { /* Resize buffer for header */
b_alloc <<= 1;
buffer = gmt_M_memory (GMT, buffer, b_alloc, char);
}
strcat (buffer, text); /* Append this connection */
if (id2 == start_id) /* Ended up at the starting polygon so it is now a closed polygon */
done = closed = true;
if (id2 == id || n_steps_pass_1 > ns) { /* Not good... [NOT SURE WHAT THIS MEANS BUT SEEMS LIKE A CRAZY SAFETY VALVE AND SHOULD NEVER HAPPEN] */
done = true;
n_trouble++;
}
else { /* Good. Trace the connection onwards to the next segment */
/* Having hooked line segment to current end_order, we must flip to the other end for the next connection */
#if 0
GMT_Report (API, GMT_MSG_INFORMATION, "Connecting segment %" PRIu64 " to segment %" PRIu64 "\n", id, id2);
#endif
end_order = !segment[id].nearest[end_order].end_order; /* Move to other end of segment */
id = id2; /* Update what is the current segment */
n_alloc_pts += segment[id].n; /* Update length of combined line segment so far */
}
n_steps_pass_1++; /* Number of segments added in this growing chain */
}
if (!closed) { /* Now search backwards from the starting line to see what may be hooked on by going in that direction */
id = start_id; /* Back to the first line segment in the chain */
end_order = (first_end_order + 1) % 2; /* Go the opposite way than what we did the first time above */
while (!done && gmtconnect_found_a_near_segment (segment, id, end_order, Ctrl->T.dist[0], Ctrl->T.active[1], Ctrl->T.dist[1])) { /* gmtconnect_found_a_near_segment returns true if its nearest segment at this end is close enough */
id2 = segment[id].nearest[end_order].id; /* ID of nearest segment at end end_order */
snprintf (text, GMT_LEN64, "%" PRIu64 " <- ", segment[id2].orig_id);
len = strlen (text);
if ((b_len + len) >= b_alloc) { /* Resize buffer for header */
b_alloc <<= 1;
buffer = gmt_M_memory (GMT, buffer, b_alloc, char);
}
/* Shift buffer to the right first by len characters */
memmove (&buffer[len], buffer, b_len);
b_len += len;
memcpy (buffer, text, len); /* Prepend to buffer */
if (id2 == start_id) /* Ended up at the starting polygon so it is now a closed polygon */
done = closed = true;
if (id2 == id || n_steps_pass_1 > ns) { /* Not good... [NOT SURE WHAT THIS MEANS BUT SEEMS LIKE A CRAZY SAFETY VALVE AND SHOULD NEVER HAPPEN] */
done = true;
n_trouble++;
}
else { /* Good. Trace the connection onwards to the next segment */
/* Having hooked line segment to current end_order, we must flip to the other end for the next connection */
#if 0
GMT_Report (API, GMT_MSG_INFORMATION, "Connecting segment %" PRIu64 " to segment %" PRIu64 "\n", id, id2);
#endif
end_order = !segment[id].nearest[end_order].end_order; /* Move to other end of segment */
id = id2; /* Update what is the current segment */
n_alloc_pts += segment[id].n; /* Update length of combined line segment so far */
}
n_steps_pass_1++; /* Number of segments added in this growing chain */
}
}
/* Here we either have closed a polygon or just have a (possibly much longer) open line segment. */
/* This id should be the beginning of a segment. Now trace forward and dump out the chain */
/* Get a new segment structure with enough rows */
T[CLOSED][out_seg] = GMT_Alloc_Segment (GMT->parent, smode, n_alloc_pts, n_columns, NULL, NULL);
if (n_steps_pass_1 == 1) {
GMT_Report (API, GMT_MSG_INFORMATION, "Connect %s -> none [1 segment]\n", buffer);
sprintf (buffer, "Single open segment not enlarged by connection");
}
else {
GMT_Report (API, GMT_MSG_INFORMATION, "Connect %s [%" PRIu64 " segments]\n", buffer, n_steps_pass_1);
sprintf (buffer, "Composite segment made from %" PRIu64 " line segments", n_steps_pass_1);
}
T[OPEN][out_seg]->header = strdup (buffer);
start_id = id; /* Having reached the end of a chain, we let the last line segment be our starting line segment for the output */
/* Note this start line segment has current end_order 0 or 1; if 1 we must first reverse below */
gmt_M_memset (GMT->current.io.segment_header, GMT_BUFSIZ, char); /* Blank the current segment header */
if (Ctrl->D.active) { /* Prepare and set segment output file name */
SH = gmt_get_DS_hidden (T[OPEN][out_seg]);
d_mode = OPEN;
(separate_open_and_closed) ? sprintf (buffer, Ctrl->D.format, 'O', out_seg) : sprintf (buffer, Ctrl->D.format, out_seg);
SH->file[GMT_OUT] = strdup (buffer);
}
/* Initialize values of first and previous end point coordinates */
p_first_x = p_last_x = DBL_MAX;
p_first_y = p_last_y = DBL_MAX;
n_steps_pass_2 = out_p = n_seg_length = 0;
first = true;
do {
G = segment[id].tbl; /* This is which table this line segment came from */
L = segment[id].seg; /* This is the segment number in that table */
np = segment[id].n; /* Length of this line segment */
S = D[GMT_IN]->table[G]->segment[L]; /* Short hand for that current segment */
if (end_order == END_A) { /* Already in the right order */
if (doubleAlmostEqualZero (S->data[GMT_X][0], p_last_x) && doubleAlmostEqualZero (S->data[GMT_Y][0], p_last_y)) { /* Skip duplicate anchor point */
j = 1; /* Start at 1 instead of 0 to skip this point */
n = np - 1; /* Hence there is one less point to copy */
}
else { /* We need all the points */
j = 0;
n = np;
}
GMT_Report (API, GMT_MSG_DEBUG, "Forward Segment no %" PRIu64 " [Table %d Segment %" PRIu64 "]\n", segment[id].orig_id, G, L);
out_p = gmtconnect_copy_this_segment (S, T[OPEN][out_seg], out_p, j, np-1); /* Copy points, return array index where next point goes */
/* Remember the last point we copied as that is the end of the growing output line segment */
p_last_x = S->data[GMT_X][np-1]; /* End of the line since we went forward */
p_last_y = S->data[GMT_Y][np-1];
if (first) p_first_x = S->data[GMT_X][0], p_first_y = S->data[GMT_Y][0]; /* Also remember start point of this chain */
}
else { /* Must reverse the segment's order of points on output */
if (doubleAlmostEqualZero (S->data[GMT_X][np-1], p_last_x) && doubleAlmostEqualZero (S->data[GMT_Y][np-1], p_last_y)) { /* Skip duplicate anchor point */
j = np - 2; /* Start at the penultimate rather than last point to skip this duplicate point */
n = np - 1; /* Hence there is one less point to copy */
}
else { /* We need all the points */
j = np - 1;
n = np;
}
GMT_Report (API, GMT_MSG_DEBUG, "Reverse Segment no %" PRIu64 " [Table %d Segment %" PRIu64 "]\n", segment[id].orig_id, G, L);
out_p = gmtconnect_copy_this_segment (S, T[OPEN][out_seg], out_p, j, 0); /* Copy points in reverse order, return array index where next point goes */
/* Remember the last point we copied as that is the end of the growing output line segment */
p_last_x = S->data[GMT_X][0]; /* Start of the line since we went backward */
p_last_y = S->data[GMT_Y][0];
if (first) p_first_x = S->data[GMT_X][j], p_first_y = S->data[GMT_Y][j]; /* Also remember start point of line segment */
}
n_seg_length += n; /* Length of combined line segment after adding this one */
first = false; /* Done with setting the very first line segment in the composite output chain */
end_order = !end_order; /* Go to the other end of the line segment */
segment[id].used = true; /* Finished appending this line segment to our output line segmnent */
if (segment[id].nearest[end_order].dist <= Ctrl->T.dist[0] && !segment[segment[id].nearest[end_order].id].used) {
/* Not done, trace into the next connecting segment */
id2 = segment[id].nearest[end_order].id; /* The ID of the nearest line segment */
end_order = segment[id].nearest[end_order].end_order; /* Which end of this line segment is closest to our end */
done = (id2 == start_id || id2 == id); /* We are done if they are the same line segment */
id = id2; /* Update what is the current line segment */
n_steps_pass_2++; /* Count of number of pieces being connected into this single line segment */
}
else /* End of the D[GMT_IN]->table for this segment */
done = true;
//n_steps_pass_2++; /* Count of number of pieces being connected into this single line segment */
} while (!done);
if (n_steps_pass_2 != n_steps_pass_1) {
GMT_Report (API, GMT_MSG_DEBUG, "Pass 1 found %" PRIu64 " while pass 2 found %" PRIu64 " connections!\n", n_steps_pass_1, n_steps_pass_2);
}
if (n_seg_length < n_alloc_pts) /* Trim memory allocation */
T[OPEN][out_seg] = GMT_Alloc_Segment (GMT->parent, smode, n_seg_length, n_columns, NULL, T[OPEN][out_seg]);
if (doubleAlmostEqualZero (p_first_x, p_last_x) && doubleAlmostEqualZero (p_first_y, p_last_y)) { /* Definitively closed polygon resulting from connections */
GMT_Report (API, GMT_MSG_INFORMATION, "New closed segment %" PRIu64 " made from %" PRIu64 " pieces\n", out_seg, n_steps_pass_2);
if (Ctrl->D.active && separate_open_and_closed) { /* Ended up closed, rename output filename with the C type instead of O set above */
SH = gmt_get_DS_hidden (T[OPEN][out_seg]);
sprintf (buffer, Ctrl->D.format, 'C', out_seg);
gmt_M_str_free (SH->file[GMT_OUT]);
SH->file[GMT_OUT] = strdup (buffer);
d_mode = CLOSED; /* Mode is used with -Q only */
}
n_closed++; /* Another closed polygon completed */
}
else { /* This one remained open */
n_open++;
GMT_Report (API, GMT_MSG_INFORMATION, "New open segment %" PRIu64 " made from %" PRIu64 " pieces\n", out_seg, n_steps_pass_2);
}
if (Ctrl->Q.active) { /* Add this polygon info to the info list */
SH = gmt_get_DS_hidden (QT[d_mode]);
QT[d_mode]->text[QT[d_mode]->n_rows++] = strdup (buffer);
if (QT[d_mode]->n_rows == SH->n_alloc) QT[d_mode]->text = gmt_M_memory (GMT, QT[d_mode]->text, (SH->n_alloc <<= 1), char *);
}
chain++; /* Number of composite line segments (closed or open) processed via connecting */
out_seg++; /* Number of output segment so far */
/* Wind to the next unused segments to start the connection search again */
start_id = 0; /* Reset and wind forward */
while (start_id < ns && segment[start_id].used) start_id++;
done = (start_id == ns); /* No more unused segments */
}
gmt_set_segmentheader (GMT, GMT_OUT, out_seg > 1); /* Turn on|off segment headers on output */
if (Ctrl->Q.active) { /* Write out the list(s) with individual file names */
Q->table[CLOSED]->segment[0]->text = gmt_M_memory (GMT, QT[CLOSED]->text, QT[CLOSED]->n_rows, char *);
if (n_qfiles == 2) Q->table[OPEN]->segment[0]->text = gmt_M_memory (GMT, QT[OPEN]->text, QT[OPEN]->n_rows, char *);
if (GMT_Write_Data (API, GMT_IS_DATASET, GMT_IS_FILE, GMT_IS_TEXT, q_mode, NULL, Ctrl->Q.file, Q) != GMT_NOERROR) {
gmt_M_free (GMT, buffer);
Return (API->error);
}
}
/* Write out the new multisegment file with polygons and segments */
D[GMT_OUT]->table[0]->segment = gmt_M_memory (GMT, T[OPEN], out_seg, struct GMT_DATASEGMENT *);
D[GMT_OUT]->n_segments = D[GMT_OUT]->table[0]->n_segments = out_seg;
ofile = (Ctrl->D.active) ? Ctrl->D.format : Ctrl->Out.file;
if (GMT_Write_Data (API, GMT_IS_DATASET, GMT_IS_FILE, GMT_IS_LINE, io_mode, NULL, ofile, D[GMT_OUT]) != GMT_NOERROR) {
gmt_M_free (GMT, buffer);
Return (API->error);
}
/* Tell us some statistics of what we found, if -V */
GMT_Report (API, GMT_MSG_INFORMATION, "%" PRIu64 " segments read\n", ns + n_closed_orig);
GMT_Report (API, GMT_MSG_INFORMATION, "%" PRIu64 " new open segments\n", n_open);
GMT_Report (API, GMT_MSG_INFORMATION, "%" PRIu64 " new closed segments\n", n_closed);
GMT_Report (API, GMT_MSG_INFORMATION, "%" PRIu64 " segments were already closed\n", n_closed_orig);
if (n_trouble) GMT_Report (API, GMT_MSG_INFORMATION, "%" PRIu64 " trouble spots\n", n_trouble);
if (chain) GMT_Report (API, GMT_MSG_DEBUG, "%" PRIu64 " composite line segments (closed or open) processed via connecting\n", chain);
gmt_M_free (GMT, buffer);
Return (GMT_NOERROR);
}
EXTERN_MSC int GMT_gmtstitch (void *V_API, int mode, void *args) {
/* This was the GMT4 name */
struct GMTAPI_CTRL *API = gmt_get_api_ptr (V_API); /* Cast from void to GMTAPI_CTRL pointer */
if (gmt_M_compat_check (API->GMT, 4)) {
GMT_Report (API, GMT_MSG_COMPAT, "Module gmtstitch is deprecated; use gmtconnect.\n");
return (GMT_Call_Module (API, "gmtconnect", mode, args));
}
GMT_Report (API, GMT_MSG_ERROR, "Shared GMT module not found: gmtstitch\n");
return (GMT_NOT_A_VALID_MODULE);
}