1 /*--------------------------------------------------------------------
2 *
3 * Copyright (c) 1991-2021 by the GMT Team (https://www.generic-mapping-tools.org/team.html)
4 * See LICENSE.TXT file for copying and redistribution conditions.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; version 3 or any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * Contact info: www.generic-mapping-tools.org
16 *--------------------------------------------------------------------*/
17 /*
18 * API functions to support the gmtconvert application.
19 *
20 * Author: Paul Wessel
21 * Date: 1-JAN-2010
22 * Version: 6 API
23 *
24 * Brief synopsis: Read one or more data tables and can concatenated them
25 * vertically [Default] or horizontally (pasting), select certain columns,
26 * report only first and/or last record per segment, only print segment
27 * headers, and only report segments that passes a segment header search.
28 */
29
30 #include "gmt_dev.h"
31
32 #define THIS_MODULE_CLASSIC_NAME "gmtconvert"
33 #define THIS_MODULE_MODERN_NAME "gmtconvert"
34 #define THIS_MODULE_LIB "core"
35 #define THIS_MODULE_PURPOSE "Convert, paste, or extract columns from data tables"
36 #define THIS_MODULE_KEYS "<D{,>D}"
37 #define THIS_MODULE_NEEDS ""
38 #define THIS_MODULE_OPTIONS "-:>Vabdefghioqsw" GMT_OPT("HMm")
39
40 #define INV_ROWS 1
41 #define INV_SEGS 2
42 #define INV_TBLS 4
43
44 #define EXCLUDE_HEADERS 0
45 #define EXCLUDE_DUPLICATES 1
46
47 /* Control structure for gmtconvert */
48
49 struct GMTCONVERT_CTRL {
50 struct GMTCONVERT_Out { /* -> */
51 bool active;
52 char *file;
53 } Out;
54 struct GMTCONVERT_A { /* -A */
55 bool active;
56 } A;
57 struct GMTCONVERT_C { /* -C[+l<min>+u<max>+i>] */
58 bool active, invert;
59 uint64_t min, max;
60 } C;
61 struct GMTCONVERT_D { /* -D[<template>][+o<orig>] */
62 bool active;
63 bool origin;
64 unsigned int mode;
65 unsigned int t_orig, s_orig;
66 char *name;
67 } D;
68 struct GMTCONVERT_E { /* -E */
69 bool active;
70 bool end;
71 int mode; /* -3, -1, -1, 0, or increment stride */
72 } E;
73 struct GMTCONVERT_F { /* -F<mode> */
74 bool active;
75 struct GMT_SEGMENTIZE S;
76 } F;
77 struct GMTCONVERT_I { /* -I[ast] */
78 bool active;
79 unsigned int mode;
80 } I;
81 struct GMTCONVERT_L { /* -L */
82 bool active;
83 } L;
84 struct GMTCONVERT_N { /* -N<col>[+a|d] sorting */
85 bool active;
86 int dir; /* +1 ascending [default], -1 descending */
87 uint64_t col;
88 } N;
89 struct GMTCONVERT_Q { /* -Q<selections> */
90 bool active;
91 struct GMT_INT_SELECTION *select;
92 } Q;
93 struct GMTCONVERT_S { /* -S[~]\"search string\" */
94 bool active;
95 struct GMT_TEXT_SELECTION *select;
96 } S;
97 struct GMTCONVERT_T { /* -T[h][d[[~]selection]] */
98 bool active[2];
99 bool text;
100 struct GMT_INT_SELECTION *C;
101 } T;
102 struct GMTCONVERT_W { /* -W[+n] */
103 bool active;
104 unsigned int mode;
105 } W;
106 struct GMTCONVERT_Z { /* -Z[<first>]:[<last>] [DEPRECATED - use -q instead]*/
107 bool active;
108 int64_t first, last;
109 } Z;
110 };
111
New_Ctrl(struct GMT_CTRL * GMT)112 static void *New_Ctrl (struct GMT_CTRL *GMT) { /* Allocate and initialize a new control structure */
113 struct GMTCONVERT_CTRL *C;
114
115 C = gmt_M_memory (GMT, NULL, 1, struct GMTCONVERT_CTRL);
116
117 /* Initialize values whose defaults are not 0/false/NULL */
118 C->C.max = ULONG_MAX; /* Max records possible in one segment */
119
120 return (C);
121 }
122
Free_Ctrl(struct GMT_CTRL * GMT,struct GMTCONVERT_CTRL * C)123 static void Free_Ctrl (struct GMT_CTRL *GMT, struct GMTCONVERT_CTRL *C) { /* Deallocate control structure */
124 if (!C) return;
125 gmt_M_str_free (C->Out.file);
126 gmt_M_str_free (C->D.name);
127 if (C->S.active) gmt_free_text_selection (GMT, &C->S.select);
128 if (C->Q.active) gmt_free_int_selection (GMT, &C->Q.select);
129 if (C->T.active[EXCLUDE_DUPLICATES]) gmt_free_int_selection (GMT, &C->T.C);
130 gmt_M_free (GMT, C);
131 }
132
usage(struct GMTAPI_CTRL * API,int level)133 static int usage (struct GMTAPI_CTRL *API, int level) {
134 const char *name = gmt_show_name_and_purpose (API, THIS_MODULE_LIB, THIS_MODULE_CLASSIC_NAME, THIS_MODULE_PURPOSE);
135 if (level == GMT_MODULE_PURPOSE) return (GMT_NOERROR);
136 GMT_Usage (API, 0, "usage: %s [<table>] [-A] [-C[+l<min>][+u<max>][+i]] [-D[<template>[+o<orig>]]] "
137 "[-E[f|l|m|M<stride>]] [-F%s] [-I[tsr]] [-L] [-N<col>[+a|d]] [-Q[~]<selection>] [-S[~]\"search string\"] "
138 "[-T[h][d[[~]<selection>]]] [%s] [-W[+n]] [%s] [%s] [%s] [%s] [%s] [%s] [%s] [%s] [%s] [%s] [%s] [%s] [%s] [%s]\n",
139 name, GMT_SEGMENTIZE4, GMT_V_OPT, GMT_a_OPT, GMT_b_OPT, GMT_d_OPT, GMT_e_OPT, GMT_f_OPT, GMT_g_OPT,
140 GMT_h_OPT, GMT_i_OPT, GMT_o_OPT, GMT_q_OPT, GMT_s_OPT, GMT_w_OPT, GMT_colon_OPT, GMT_PAR_OPT);
141
142 if (level == GMT_SYNOPSIS) return (GMT_MODULE_SYNOPSIS);
143
144 GMT_Message (API, GMT_TIME_NONE, " REQUIRED ARGUMENTS:\n");
145 GMT_Option (API, "<");
146 GMT_Message (API, GMT_TIME_NONE, "\n OPTIONAL ARGUMENTS:\n");
147 GMT_Usage (API, 1, "\n-A Paste files horizontally, not concatenate vertically [Default]. "
148 "All files must have the same number of segments and rows, "
149 "but they may differ in their number of columns.");
150 GMT_Usage (API, 1, "\n-C[+l<min>][+u<max>][+i]");
151 GMT_Usage (API, -2, "Only output segments whose number of records matches criteria:");
152 GMT_Usage (API, 3, "+l Segment must have at least <min> records [0].");
153 GMT_Usage (API, 3, "+u Segment must have at most <max> records [inf].");
154 GMT_Usage (API, 3, "+i Invert the test.");
155 GMT_Usage (API, 1, "\n-D[<template>[+o<orig>]]");
156 GMT_Usage (API, -2, "Write individual segments to separate files [Default writes one "
157 "multisegment file to standard output]. Append file name template which MUST "
158 "contain a C-style format for an integer (e.g., %%d) that represents "
159 "a sequential segment number across all tables (if more than one table) "
160 "[Default uses gmtconvert_segment_%%d.txt (or .bin for binary)]. "
161 "Use +o<orig> to start numbering at <orig> [0]. "
162 "Alternatively, supply a template with two long formats and we will "
163 "replace them with the table number and table segment numbers. "
164 "Use +o<t_orig>/<s_orig> to start numbering at <t_orig> for tables and <s_orig> for segments [0/0].");
165 GMT_Usage (API, 1, "\n-E[f|l|m|M<stride>]");
166 GMT_Usage (API, -2, "Extract first and last record per segment only [Output all records]. Optional directives:");
167 GMT_Usage (API, 3, "f: Output first record only.");
168 GMT_Usage (API, 3, "l: Output last record only.");
169 GMT_Usage (API, 3, "m: Output every <stride> records.");
170 GMT_Usage (API, 3, "M: Same as m but always includes the last record.");
171 gmt_segmentize_syntax (API->GMT, 'F', 0);
172 GMT_Usage (API, 1, "\n-I[tsr]");
173 GMT_Usage (API, -2, "Invert output order of (t)ables, (s)egments, or (r)ecords. Append any combination of directives:");
174 GMT_Usage (API, 3, "t: Reverse the order of input tables on output.");
175 GMT_Usage (API, 3, "s: Reverse the order of segments within each table on output.");
176 GMT_Usage (API, 3, "r: Reverse the order of records within each segment on output [Default].");
177 GMT_Usage (API, 1, "\n-L Output only segment headers and skip all data records. "
178 "Requires ASCII input data [Output headers and data].");
179 GMT_Usage (API, 1, "\n-N<col>[+a|d]");
180 GMT_Usage (API, -2, "Numerically sort all records per segment based on data in column <col>:");
181 GMT_Usage (API, 3, "+a Sort into ascending order [Default].");
182 GMT_Usage (API, 3, "+d Sort into descending order.");
183 GMT_Usage (API, 1, "\n-Q[~]<selection>");
184 GMT_Usage (API, -2, "Only output specified segment numbers in <selection> [All]. "
185 "<selection> syntax is [~]<range>[,<range>,...] where each <range> of items is "
186 "either a single number, start-stop (for range), start:step:stop (for stepped range), "
187 "or +f<file> for a file list with one <range> selection per line. "
188 "A leading ~ will invert the selection and write all segments but the ones listed.");
189 GMT_Usage (API, 1, "\n-S[~]\"search string\"");
190 GMT_Usage (API, -2, "Only output segments whose headers contain the pattern \"string\". "
191 "Use -S~\"string\" to output segment that DO NOT contain this pattern. "
192 "If your pattern begins with ~, escape it with \\~. "
193 "To match OGR aspatial values, use name=value, and to match headers against "
194 "extended regular expressions use -S[~]/regexp/[i] (i for case-insensitive). "
195 "Give +f<file> for a file list with such patterns, one per line. "
196 "To give a single pattern starting with +f, escape it with \\+f.");
197 GMT_Usage (API, 1, "\n-T[h][d[[~]<selection>]]");
198 GMT_Usage (API, -2, "Skip certain types of records. Append one or both of these directives:");
199 GMT_Usage (API, 3, "h: Prevent the writing of segment headers [Default].");
200 GMT_Usage (API, 3, "d: Prevent the writing of duplicate data records.");
201 GMT_Usage (API, -2, "Optionally, append selection of columns to consider in the test [all]. "
202 "<selection> syntax is [~]<range>[,<range>,...] where each <range> of items is "
203 "either a single number, start-stop (for range), start:step:stop (for stepped range). "
204 "To include trailing text in the comparison, add column t. If no numerical columns "
205 "are specified, only t, then we only use trailing text comparisons to decide.");
206 GMT_Option (API, "V");
207 GMT_Usage (API, 1, "\n-W[+n]");
208 GMT_Usage (API, -2, "Convert trailing text to numbers, if possible. Append +n to suppress NaN columns.");
209 GMT_Option (API, "a,bi,bo,d,e,f,g,h,i,o,q,s,w,:,.");
210
211 return (GMT_MODULE_USAGE);
212 }
213
parse(struct GMT_CTRL * GMT,struct GMTCONVERT_CTRL * Ctrl,struct GMT_OPTION * options)214 static int parse (struct GMT_CTRL *GMT, struct GMTCONVERT_CTRL *Ctrl, struct GMT_OPTION *options) {
215 /* This parses the options provided to gmtconvert and sets parameters in CTRL.
216 * Any GMT common options will override values set previously by other commands.
217 * It also replaces any file names specified as input or output with the data ID
218 * returned when registering these sources/destinations with the API.
219 */
220
221 unsigned int pos, n_errors = 0, k, n_files = 0;
222 int n = 0;
223 int64_t value = 0;
224 char p[GMT_BUFSIZ] = {""}, *c = NULL;
225 struct GMT_OPTION *opt = NULL;
226 struct GMTAPI_CTRL *API = GMT->parent;
227
228 for (opt = options; opt; opt = opt->next) {
229 switch (opt->option) {
230
231 case '<': /* Skip input files */
232 if (GMT_Get_FilePath (API, GMT_IS_DATASET, GMT_IN, GMT_FILE_REMOTE, &(opt->arg))) n_errors++;;
233 break;
234 case '>': /* Got named output file */
235 if (n_files++ > 0) { n_errors++; continue; }
236 Ctrl->Out.active = true;
237 if (opt->arg[0]) Ctrl->Out.file = strdup (opt->arg);
238 if (GMT_Get_FilePath (API, GMT_IS_DATASET, GMT_OUT, GMT_FILE_LOCAL, &(Ctrl->Out.file))) n_errors++;
239 break;
240
241 /* Processes program-specific parameters */
242
243 case 'A': /* pAste mode */
244 n_errors += gmt_M_repeated_module_option (API, Ctrl->A.active);
245 Ctrl->A.active = true;
246 break;
247 case 'C': /* record-count selection mode */
248 n_errors += gmt_M_repeated_module_option (API, Ctrl->C.active);
249 Ctrl->C.active = true;
250 pos = 0;
251 while (gmt_getmodopt (GMT, 'C', opt->arg, "ilu", &pos, p, &n_errors) && n_errors == 0) { /* Looking for +i, +l, +u */
252 switch (p[0]) {
253 case 'i': /* Invert selection */
254 Ctrl->C.invert = true; break;
255 case 'l': /* Set fewest records required */
256 if ((value = atol (&p[1])) < 0)
257 GMT_Report (API, GMT_MSG_ERROR, "The -C+l modifier was given negative record count!\n");
258 else
259 Ctrl->C.min = (uint64_t)value;
260 break;
261 case 'u': /* Set max records required */
262 if ((value = atol (&p[1])) < 0)
263 GMT_Report (API, GMT_MSG_ERROR, "The -C+u modifier was given negative record count!\n");
264 else
265 Ctrl->C.max = (uint64_t)value;
266 break;
267 default: /* These are caught in gmt_getmodopt so break is just for Coverity */
268 break;
269 }
270 }
271 break;
272 case 'D': /* Write each segment to a separate output file */
273 n_errors += gmt_M_repeated_module_option (API, Ctrl->D.active);
274 Ctrl->D.active = true;
275 if ((c = strstr (opt->arg, "+o"))) { /* Gave new origins for tables and segments (or just segments) */
276 n = sscanf (&c[2], "%d/%d", &Ctrl->D.t_orig, &Ctrl->D.s_orig);
277 if (n == 1) Ctrl->D.s_orig = Ctrl->D.t_orig, Ctrl->D.t_orig = 0;
278 c[0] = '\0'; /* Chop off modifier */
279 Ctrl->D.origin = true;
280 }
281 if (*opt->arg) /* optarg is optional */
282 Ctrl->D.name = strdup (opt->arg);
283 if (c) c[0] = '+'; /* Restore modifier */
284 break;
285 case 'E': /* Extract ends only */
286 n_errors += gmt_M_repeated_module_option (API, Ctrl->E.active);
287 Ctrl->E.active = true;
288 switch (opt->arg[0]) {
289 case 'f': /* Get first point only */
290 Ctrl->E.mode = -1; break;
291 case 'l': /* Get last point only */
292 Ctrl->E.mode = -2; break;
293 case 'M': /* Set modulo step */
294 Ctrl->E.end = true; /* Include last point */
295 /* Intentionally fall through - to set the step */
296 case 'm': /* Set modulo step */
297 Ctrl->E.mode = atoi (&opt->arg[1]); break;
298 default: /* Get first and last point only */
299 Ctrl->E.mode = -3; break;
300 }
301 break;
302 case 'F':
303 n_errors += gmt_M_repeated_module_option (API, Ctrl->F.active);
304 Ctrl->F.active = true;
305 if (opt->arg[0] == '\0') { /* No arguments, must be old GMT4 option -F */
306 if (gmt_M_compat_check (GMT, 4)) {
307 GMT_Report (API, GMT_MSG_COMPAT,
308 "Option -F for output columns is deprecated; use -o instead\n");
309 gmt_parse_o_option (GMT, opt->arg);
310 }
311 else
312 n_errors += gmt_default_error (GMT, opt->option);
313 break;
314 }
315 /* Modern options */
316 n_errors += gmt_parse_segmentize (GMT, opt->option, opt->arg, 0, &(Ctrl->F.S));
317 break;
318 case 'I': /* Invert order or tables, segments, rows as indicated */
319 n_errors += gmt_M_repeated_module_option (API, Ctrl->I.active);
320 Ctrl->I.active = true;
321 for (k = 0; opt->arg[k]; k++) {
322 switch (opt->arg[k]) {
323 case 't': Ctrl->I.mode |= INV_TBLS; break; /* Reverse table order */
324 case 's': Ctrl->I.mode |= INV_SEGS; break; /* Reverse segment order */
325 case 'r': Ctrl->I.mode |= INV_ROWS; break; /* Reverse record order */
326 default:
327 GMT_Report (API, GMT_MSG_ERROR,
328 "The -I option does not recognize modifier %c\n", (int)opt->arg[k]);
329 n_errors++;
330 break;
331 }
332 }
333 if (Ctrl->I.mode == 0) Ctrl->I.mode = INV_ROWS; /* Default is -Ir */
334 break;
335 case 'L': /* Only output segment headers */
336 n_errors += gmt_M_repeated_module_option (API, Ctrl->L.active);
337 Ctrl->L.active = true;
338 break;
339 case 'N': /* Sort per segment on specified column */
340 n_errors += gmt_M_repeated_module_option (API, Ctrl->N.active);
341 Ctrl->N.active = true;
342 if ((c = strstr (opt->arg, "+a")) || (c = strstr (opt->arg, "+d"))) { /* New syntax */
343 Ctrl->N.dir = (c[1] == 'd') ? -1 : +1;
344 c[0] = '\0'; /* Chop off modifier */
345 }
346 value = atol (opt->arg);
347 if (c)
348 c[0] = '+'; /* Restore modifier */
349 else
350 Ctrl->N.dir = (value < 0 || opt->arg[0] == '-') ? -1 : +1;
351 Ctrl->N.col = int64_abs (value);
352 break;
353 case 'Q': /* Only report for specified segment numbers */
354 n_errors += gmt_M_repeated_module_option (API, Ctrl->Q.active);
355 Ctrl->Q.active = true;
356 Ctrl->Q.select = gmt_set_int_selection (GMT, opt->arg);
357 break;
358 case 'S': /* Segment header pattern search */
359 n_errors += gmt_M_repeated_module_option (API, Ctrl->S.active);
360 Ctrl->S.active = true;
361 Ctrl->S.select = gmt_set_text_selection (GMT, opt->arg);
362 break;
363 case 'T': /* -T[h]: Do not write segment headers, -Td: Skip duplicate records */
364 strncpy (p, opt->arg, GMT_BUFSIZ-1);
365 if ((c = strchr (p, 'd'))) { /* Skip duplicates */
366 char *d = NULL;
367 n_errors += gmt_M_repeated_module_option (API, Ctrl->T.active[EXCLUDE_DUPLICATES]);
368 Ctrl->T.active[EXCLUDE_DUPLICATES] = true;
369 if ((d = strstr (c, ",t")) || (d = strchr (c, 't'))) { /* Got either d<cols>,t or just t */
370 Ctrl->T.text = true;
371 d[0] = '\0';
372 }
373 if (c[1]) Ctrl->T.C = gmt_set_int_selection (GMT, &c[1]); /* If we gave -Tdt then no columns and c[1] is 0 */
374 }
375 if (!p[0] || strchr (p, 'h')) { /* Skip segment headers */
376 n_errors += gmt_M_repeated_module_option (API, Ctrl->T.active[EXCLUDE_HEADERS]);
377 Ctrl->T.active[EXCLUDE_HEADERS] = true;
378 }
379 break;
380 case 'W':
381 n_errors += gmt_M_repeated_module_option (API, Ctrl->W.active);
382 Ctrl->W.active = true;
383 if (!strncmp (opt->arg, "+n", 2U))
384 Ctrl->W.mode = 1;
385 break;
386 case 'Z':
387 n_errors += gmt_M_repeated_module_option (API, Ctrl->Z.active);
388 Ctrl->Z.active = true;
389 GMT_Report (API, GMT_MSG_COMPAT, "Option -Z is deprecated (but still works); Use common option -q instead\n");
390 if ((c = strchr (opt->arg, ':')) || (c = strchr (opt->arg, '/'))) { /* Got [<first>]:[<last>] or [<first>]/[<last>] */
391 char div = c[0]; /* Either : or / */
392 if (opt->arg[0] == div) /* No first given, default to 0 */
393 Ctrl->Z.last = atol (&opt->arg[1]);
394 else { /* Gave first, and maybe last */
395 c[0] = '\0'; /* Chop off last */
396 Ctrl->Z.first = atol (opt->arg);
397 Ctrl->Z.last = (c[1]) ? (int64_t)atol (&c[1]) : INTMAX_MAX; /* Last record if not given */
398 c[0] = div; /* Restore */
399 }
400 }
401 else /* No colon means first is 0 and given value is last */
402 Ctrl->Z.last = atol (opt->arg);
403 /* Adjust to system where first record is 1 since we must increment n_in_rows before applying -Z check later */
404 Ctrl->Z.first++; if (Ctrl->Z.last < INTMAX_MAX) Ctrl->Z.last++;
405 GMT_Report (API, GMT_MSG_DEBUG, "Output record numbers %" PRIu64 " through = %" PRIu64 "\n", Ctrl->Z.first, Ctrl->Z.last);
406 break;
407
408 default: /* Report bad options */
409 n_errors += gmt_default_error (GMT, opt->option);
410 break;
411 }
412 }
413
414 if (Ctrl->D.active) { /* Validate the name template, if given */
415 /* Must write individual segments to separate files so create the needed name template */
416 unsigned int n_formats = 0;
417 if (!Ctrl->D.name) { /* None give, auto-assign to segment files with extension based on binary or not */
418 Ctrl->D.name = GMT->common.b.active[GMT_OUT] ?
419 strdup ("gmtconvert_segment_%d.bin") : strdup ("gmtconvert_segment_%d.txt");
420 Ctrl->D.mode = GMT_WRITE_SEGMENT;
421 }
422 else { /* Ctrl->D.name given, need to check correct format */
423 char *p = Ctrl->D.name;
424 while ( (p = strchr (p, '%')) ) {
425 /* found %, now check format */
426 ++p; /* Skip the % sign */
427 p += strspn (p, "0123456789."); /* span past digits and decimal */
428 if ( strspn (p, "diu") == 0 ) {
429 /* No valid conversion specifier */
430 GMT_Report (API, GMT_MSG_ERROR,
431 "Option -D: Use of unsupported conversion specifier at position %" PRIuS " in format string '%s'.\n",
432 p - Ctrl->D.name + 1, Ctrl->D.name);
433 n_errors++;
434 }
435 ++n_formats;
436 }
437 if ( n_formats == 0 || n_formats > 2 ) { /* Incorrect number of format specifiers */
438 GMT_Report (API, GMT_MSG_ERROR,
439 "Option -D: Incorrect number of format specifiers in format string '%s'.\n",
440 Ctrl->D.name);
441 n_errors++;
442 }
443 /* The io_mode tells the i/o function to split tables or segments into files, if requested */
444 Ctrl->D.mode = (n_formats == 2) ? GMT_WRITE_TABLE_SEGMENT: GMT_WRITE_SEGMENT;
445 }
446 }
447 n_errors += gmt_M_check_condition (GMT, GMT->common.b.active[GMT_IN] && GMT->common.b.ncol[GMT_IN] == 0,
448 "Must specify number of columns in binary input data (-bi)\n");
449 n_errors += gmt_M_check_condition (GMT, GMT->common.b.active[GMT_IN] && (Ctrl->L.active || Ctrl->S.active),
450 "Options -L or -S requires ASCII input data\n");
451 n_errors += gmt_M_check_condition (GMT, Ctrl->C.active && (Ctrl->C.min > Ctrl->C.max),
452 "Option -C: minimum records cannot exceed maximum records\n");
453 n_errors += gmt_M_check_condition (GMT, Ctrl->D.active && Ctrl->D.name && !strstr (Ctrl->D.name, "%"),
454 "Option -D: Output template must contain %%d\n");
455 n_errors += gmt_M_check_condition (GMT, Ctrl->Q.active && Ctrl->S.active,
456 "Only one of -Q and -S can be used simultaneously\n");
457 n_errors += gmt_M_check_condition (GMT, Ctrl->N.active && Ctrl->F.active,
458 "The -N option cannot be used with -F\n");
459 n_errors += gmt_M_check_condition (GMT, Ctrl->Z.active && GMT->common.q.active[GMT_OUT],
460 "The deprecated -Z option cannot be used with -qo\n");
461 n_errors += gmt_M_check_condition (GMT, n_files > 1, "Only one output destination can be specified\n");
462
463 return (n_errors ? GMT_PARSE_ERROR : GMT_NOERROR);
464 }
465
gmtconvert_is_duplicate_row(struct GMT_DATASEGMENT * S,struct GMT_INT_SELECTION * C,bool text,uint64_t row)466 GMT_LOCAL bool gmtconvert_is_duplicate_row (struct GMT_DATASEGMENT *S, struct GMT_INT_SELECTION *C, bool text, uint64_t row) {
467 uint64_t col, k;
468 if (C == NULL) {
469 /* Loop over all columns and compare the two records, if any differ then return false.
470 * If passes all columns then they are the same and we return true. */
471 if (!text) {
472 for (col = 0; col < S->n_columns; col++)
473 if (!doubleAlmostEqualZero (S->data[col][row], S->data[col][row-1])) return false;
474 }
475 }
476 else if (C->invert) { /* Only compare the columns not given in select */
477 for (col = k = 0; col < S->n_columns; col++) {
478 if (col == C->item[k]) { /* Skip this guy */
479 k++;
480 continue;
481 }
482 if (!doubleAlmostEqualZero (S->data[col][row], S->data[col][row-1])) return false;
483 }
484 }
485 else { /* Only compare the columns given in select */
486 for (k = 0; k < C->n; k++) {
487 col = C->item[k];
488 if (!doubleAlmostEqualZero (S->data[col][row], S->data[col][row-1])) return false;
489 }
490 }
491 if (text && S->text && S->text[row] && S->text[row-1]) return (!strcmp (S->text[row], S->text[row-1])); /* Also compare trailing text */
492 return true;
493 }
494
495 /* Must free allocated memory before returning */
496 #define bailout(code) {gmt_M_free_options (mode); return (code);}
497 #define Return(code) {Free_Ctrl (GMT, Ctrl); gmt_end_module (GMT, GMT_cpy); bailout (code);}
498
GMT_gmtconvert(void * V_API,int mode,void * args)499 EXTERN_MSC int GMT_gmtconvert (void *V_API, int mode, void *args) {
500 bool match = false, prevent_seg_headers = false;
501 int error = 0;
502 uint64_t out_col, col, n_cols_in = 0, n_cols_out, tbl, tlen, n_duplicates = 0;
503 uint64_t n_horizontal_tbls, n_vertical_tbls, tbl_ver, tbl_hor, use_tbl;
504 uint64_t last_row, n_rows, row, seg, n_out_seg = 0, out_seg = 0;
505 int64_t n_in_rows;
506
507 double *val = NULL;
508
509 char *method[2] = {"concatenated", "pasted"}, *p = NULL;
510
511 struct GMTCONVERT_CTRL *Ctrl = NULL;
512 struct GMT_DATASET *D[2] = {NULL, NULL}; /* Pointer to GMT multisegment table(s) in and out */
513 struct GMT_DATASEGMENT *S = NULL, *Si = NULL, *So = NULL;
514 struct GMT_DATASET_HIDDEN *DHi = NULL, *DHo = NULL;
515 struct GMT_DATATABLE_HIDDEN *THi = NULL, *THo = NULL;
516 struct GMT_DATASEGMENT_HIDDEN *SHi = NULL, *SHo = NULL;
517 struct GMT_CTRL *GMT = NULL, *GMT_cpy = NULL;
518 struct GMT_OPTION *options = NULL;
519 struct GMTAPI_CTRL *API = gmt_get_api_ptr (V_API); /* Cast from void to GMTAPI_CTRL pointer */
520
521 /*----------------------- Standard module initialization and parsing ----------------------*/
522
523 if (API == NULL) return (GMT_NOT_A_SESSION);
524 if (mode == GMT_MODULE_PURPOSE) return (usage (API, GMT_MODULE_PURPOSE)); /* Return the purpose of program */
525 options = GMT_Create_Options (API, mode, args); if (API->error) return (API->error); /* Set or get option list */
526
527 if ((error = gmt_report_usage (API, options, 1, usage)) != GMT_NOERROR) bailout (error); /* Give usage if requested */
528
529 /* Parse the command-line arguments */
530
531 if ((GMT = gmt_init_module (API, THIS_MODULE_LIB, THIS_MODULE_CLASSIC_NAME, THIS_MODULE_KEYS, THIS_MODULE_NEEDS, NULL, &options, &GMT_cpy)) == NULL) bailout (API->error); /* Save current state */
532 if (GMT_Parse_Common (API, THIS_MODULE_OPTIONS, options)) Return (API->error);
533 Ctrl = New_Ctrl (GMT); /* Allocate and initialize a new control structure */
534 if ((error = parse (GMT, Ctrl, options)) != 0) Return (error);
535
536 /*---------------------------- This is the gmtconvert main code ----------------------------*/
537
538 GMT_Report (API, GMT_MSG_INFORMATION, "Processing input table data\n");
539
540 if (GMT_Init_IO (API, GMT_IS_DATASET, GMT_IS_POINT, GMT_IN, GMT_ADD_DEFAULT, 0, options) != GMT_NOERROR) {
541 Return (API->error); /* Establishes data files or stdin */
542 }
543
544 /* Read in the input tables */
545
546 if ((D[GMT_IN] = GMT_Read_Data (API, GMT_IS_DATASET, GMT_IS_FILE, 0, GMT_READ_NORMAL, NULL, NULL, NULL)) == NULL) {
547 Return (API->error);
548 }
549
550 if (D[GMT_IN]->n_records == 0) {
551 GMT_Report (API, GMT_MSG_WARNING, "No data records provided\n");
552 Return (GMT_NOERROR);
553 }
554 if (GMT->common.a.active && D[GMT_IN]->n_tables > 1) {
555 GMT_Report (API, GMT_MSG_ERROR, "The -a option requires a single table only.\n");
556 Return (GMT_RUNTIME_ERROR);
557 }
558 DHi = gmt_get_DD_hidden (D[GMT_IN]);
559 THi = gmt_get_DT_hidden (D[GMT_IN]->table[0]);
560 if (GMT->common.a.active && THi->ogr) {
561 GMT_Report (API, GMT_MSG_ERROR, "The -a option requires a single table without OGR metadata.\n");
562 Return (GMT_RUNTIME_ERROR);
563 }
564
565 if (Ctrl->T.active[EXCLUDE_HEADERS] && !gmt_M_file_is_memory (Ctrl->Out.file))
566 prevent_seg_headers = true;
567
568 if (Ctrl->T.active[EXCLUDE_DUPLICATES] && Ctrl->T.C) {
569 for (unsigned int k = 0; k < Ctrl->T.C->n; k++) {
570 if (Ctrl->T.C->item[k] >= D[GMT_IN]->n_columns) {
571 GMT_Report (API, GMT_MSG_ERROR, "The -Td<cols> option has entries (%d) exceeding the number of data columns (%d).\n", (unsigned int)Ctrl->T.C->item[k], (unsigned int)D[GMT_IN]->n_columns);
572 Return (GMT_RUNTIME_ERROR);
573 }
574 }
575 }
576
577 if (prevent_seg_headers) /* Turn off segment headers on file output */
578 GMT->current.io.skip_headers_on_outout = true;
579
580 if (Ctrl->F.active) { /* Segmentizing happens here and then we are done */
581 D[GMT_OUT] = gmt_segmentize_data (GMT, D[GMT_IN], &(Ctrl->F.S)); /* Segmentize the data */
582 if (GMT_Destroy_Data (API, &D[GMT_IN]) != GMT_NOERROR) { /* Be gone with the original */
583 Return (API->error);
584 }
585 if (D[GMT_OUT]->n_segments > 1) gmt_set_segmentheader (GMT, GMT_OUT, true); /* Turn on segment headers on output */
586
587 DHo = gmt_get_DD_hidden (D[GMT_OUT]);
588 if (GMT_Write_Data (API, GMT_IS_DATASET, GMT_IS_FILE, D[GMT_OUT]->geometry, DHo->io_mode, NULL, Ctrl->Out.file, D[GMT_OUT]) != GMT_NOERROR) {
589 Return (API->error);
590 }
591 if (prevent_seg_headers) GMT->current.io.skip_headers_on_outout = false; /* Restore to default if it was changed */
592 Return (GMT_NOERROR); /* We are done! */
593 }
594
595 if (Ctrl->W.active) { /* Text to data happens here and then we are done */
596 double out[GMT_BUFSIZ], *tmp = NULL;
597 struct GMT_RECORD *Out = NULL;
598 char *nan = NULL;
599 uint64_t n_col = 0, k;
600 S = D[GMT_IN]->table[0]->segment[0]; /* Short-hand */
601 if (S->text) { /* Has trailing text */
602 n_col = n_cols_in = GMT_Get_Values (API, S->text[0], out, GMT_BUFSIZ);
603 if (Ctrl->W.mode) { /* Only wants non-NaN columns from trailing text */
604 nan = gmt_M_memory (GMT, NULL, n_col, char);
605 tmp = gmt_M_memory (GMT, NULL, n_col, double);
606 for (col = 0; col < n_col; col++) if (gmt_M_is_dnan (out[col])) nan[col] = 1, n_cols_in--;
607 GMT_Report (API, GMT_MSG_INFORMATION, "First record trailing text converted to %" PRIu64 " columns, with %" PRIu64 " of them yielding NaNs [skipped]\n", n_col, n_col - n_cols_in);
608 }
609 else
610 GMT_Report (API, GMT_MSG_INFORMATION, "First record trailing text converted to %" PRIu64 " columns\n", n_col);
611 }
612 else
613 GMT_Report (API, GMT_MSG_WARNING, "No trialing text found in first record; -W will not have any effect.\n");
614 if (GMT_Init_IO (API, GMT_IS_DATASET, GMT_IS_POINT, GMT_OUT, GMT_ADD_DEFAULT, 0, options) != GMT_NOERROR) { /* Registers default output destination, unless already set */
615 if (Ctrl->W.mode) {gmt_M_free (GMT, nan); gmt_M_free (GMT, tmp);}
616 Return (API->error);
617 }
618 if (GMT_Begin_IO (API, GMT_IS_DATASET, GMT_OUT, GMT_HEADER_ON) != GMT_NOERROR) { /* Enables data output and sets access mode */
619 if (Ctrl->W.mode) {gmt_M_free (GMT, nan); gmt_M_free (GMT, tmp);}
620 Return (API->error);
621 }
622 if ((error = GMT_Set_Columns (API, GMT_OUT, (unsigned int)(n_cols_in + S->n_columns), GMT_COL_FIX_NO_TEXT)) != GMT_NOERROR) {
623 if (Ctrl->W.mode) {gmt_M_free (GMT, nan); gmt_M_free (GMT, tmp);}
624 Return (error);
625 }
626 Out = gmt_new_record (GMT, out, NULL); /* Since we only need to worry about numerics in this module */
627 for (tbl = 0; tbl < D[GMT_IN]->n_tables; tbl++) {
628 for (seg = 0; seg < D[GMT_IN]->table[tbl]->n_segments; seg++) { /* For each segment in the tables */
629 S = D[GMT_IN]->table[tbl]->segment[seg]; /* Short-hand */
630 if (D[GMT_IN]->n_segments > 1 || S->header) GMT_Put_Record (API, GMT_WRITE_SEGMENT_HEADER, S->header);
631 for (row = 0; row < S->n_rows; row++) {
632 for (col = 0; col < S->n_columns; col++)
633 out[col] = S->data[col][row];
634 if (S->text) {
635 if (Ctrl->W.mode) { /* Exclude NaN columns */
636 n_cols_in = GMT_Get_Values (API, S->text[row], tmp, (int)n_col);
637 for (col = k = 0; col < n_cols_in; col++) if (!nan[col]) out[S->n_columns+k] = tmp[col], k++;
638 }
639 else
640 GMT_Get_Values (API, S->text[row], &out[S->n_columns], (int)n_col);
641 }
642 GMT_Put_Record (API, GMT_WRITE_DATA, Out); /* Write this to output */
643 }
644 }
645 }
646 gmt_M_free (GMT, Out);
647 if (Ctrl->W.mode) {
648 gmt_M_free (GMT, nan);
649 gmt_M_free (GMT, tmp);
650 }
651 if (GMT_End_IO (API, GMT_OUT, 0) != GMT_NOERROR) { /* Disables further data output */
652 Return (API->error);
653 }
654 Return (GMT_NOERROR); /* We are done! */
655 }
656
657 /* Determine number of input and output columns for the selected options.
658 * For -A, require all tables to have the same number of segments and records. */
659
660 for (tbl = n_cols_in = n_cols_out = 0; tbl < D[GMT_IN]->n_tables; tbl++) {
661 if (Ctrl->A.active) { /* All tables must be of the same vertical shape */
662 if (tbl && D[GMT_IN]->table[tbl]->n_records != D[GMT_IN]->table[tbl-1]->n_records) error = true;
663 if (tbl && D[GMT_IN]->table[tbl]->n_segments != D[GMT_IN]->table[tbl-1]->n_segments) error = true;
664 }
665 n_cols_in += D[GMT_IN]->table[tbl]->n_columns; /* This is the case for -A */
666 n_cols_out = MAX (n_cols_out, D[GMT_IN]->table[tbl]->n_columns); /* The widest table encountered */
667 }
668 n_cols_out = (Ctrl->A.active) ? n_cols_in : n_cols_out; /* Default or Reset since we did not have -A */
669
670 if (error) {
671 GMT_Report (API, GMT_MSG_ERROR, "Parsing requires files with same number of records.\n");
672 Return (GMT_RUNTIME_ERROR);
673 }
674 if (n_cols_out == 0 && !GMT->current.io.trailing_text[GMT_OUT]) {
675 GMT_Report (API, GMT_MSG_ERROR, "Selection led to no output columns.\n");
676 Return (GMT_RUNTIME_ERROR);
677
678 }
679 if ((error = GMT_Set_Columns (API, GMT_OUT, (unsigned int)n_cols_out, (D[GMT_IN]->type == GMT_READ_DATA) ? GMT_COL_FIX_NO_TEXT : GMT_COL_FIX)) != GMT_NOERROR) {
680 Return (error);
681 }
682
683 if (Ctrl->S.active && GMT->current.io.ogr == GMT_OGR_TRUE && (p = strchr (Ctrl->S.select->pattern[0], '=')) != NULL) { /* Want to search for an aspatial value */
684 *p = 0; /* Skip the = sign */
685 if ((Ctrl->S.select->ogr_item = gmt_get_ogr_id (GMT->current.io.OGR, Ctrl->S.select->pattern[0])) != GMT_NOTSET) {
686 Ctrl->S.select->ogr_match = true;
687 p++;
688 strcpy (Ctrl->S.select->pattern[0], p); /* Move the value over to the start */
689 }
690 }
691
692 /* We now know the exact number of segments and columns and an upper limit on total records.
693 * Allocate data set with a single table with those proportions. This copies headers as well */
694
695 DHi->dim[GMT_COL] = n_cols_out; /* State we want a different set of columns on output */
696 D[GMT_OUT] = GMT_Duplicate_Data (API, GMT_IS_DATASET, GMT_DUPLICATE_ALLOC + ((Ctrl->A.active) ? GMT_ALLOC_HORIZONTAL : GMT_ALLOC_NORMAL), D[GMT_IN]);
697 DHo = gmt_get_DD_hidden (D[GMT_OUT]);
698
699 n_horizontal_tbls = (Ctrl->A.active) ? D[GMT_IN]->n_tables : 1; /* Only with pasting do we go horizontally */
700 n_vertical_tbls = (Ctrl->A.active) ? 1 : D[GMT_IN]->n_tables; /* Only for concatenation do we go vertically */
701 val = gmt_M_memory (GMT, NULL, n_cols_in, double);
702
703 for (tbl_ver = 0; tbl_ver < n_vertical_tbls; tbl_ver++) { /* Number of tables to place vertically */
704 D[GMT_OUT]->table[tbl_ver]->n_records = 0; /* Reset record count per table since we may return fewer than the original */
705 THo = gmt_get_DT_hidden (D[GMT_OUT]->table[tbl_ver]);
706 for (seg = 0; seg < D[GMT_IN]->table[tbl_ver]->n_segments; seg++) { /* For each segment in the tables */
707 S = D[GMT_IN]->table[tbl_ver]->segment[seg]; /* Current input segment */
708 So = D[GMT_OUT]->table[tbl_ver]->segment[seg]; /* Current output segment */
709 SHi = gmt_get_DS_hidden (S);
710 SHo = gmt_get_DS_hidden (So);
711 if (Ctrl->L.active) SHo->mode = GMT_WRITE_HEADER; /* Only write segment header */
712 if (Ctrl->S.active) { /* See if the combined segment header has text matching our search string */
713 match = gmt_get_segtext_selection (GMT, Ctrl->S.select, S, match);
714 if (Ctrl->S.select->invert == match) SHo->mode = GMT_WRITE_SKIP; /* Mark segment to be skipped */
715 }
716 if (Ctrl->Q.active && !gmt_get_int_selection (GMT, Ctrl->Q.select, seg)) SHo->mode = GMT_WRITE_SKIP; /* Mark segment to be skipped */
717 if (Ctrl->C.active) { /* See if the number of records in this segment passes our test for output */
718 match = (S->n_rows >= Ctrl->C.min && S->n_rows <= Ctrl->C.max);
719 if (Ctrl->C.invert == match) SHo->mode = GMT_WRITE_SKIP; /* Mark segment to be skipped */
720 }
721 if (SHo->mode) continue; /* No point copying values given segment content will be skipped */
722 n_out_seg++; /* Number of segments that passed the test */
723 last_row = S->n_rows - 1;
724 for (row = n_rows = 0, n_in_rows = 0; row <= last_row; row++) { /* Go down all the rows */
725 n_in_rows++;
726 if (Ctrl->Z.active && (n_in_rows < Ctrl->Z.first || n_in_rows > Ctrl->Z.last)) continue; /* Skip if outside limited record range */
727 if (!Ctrl->E.active) {
728 if (Ctrl->T.active[EXCLUDE_DUPLICATES] && row && gmtconvert_is_duplicate_row (S, Ctrl->T.C, Ctrl->T.text, row)) {
729 n_duplicates++;
730 continue; /* Skip duplicate records */
731 }
732 }
733 else if (Ctrl->E.mode < 0) { /* Only pass first or last or both of them, skipping all others */
734 if (row > 0 && row < last_row) continue; /* Always skip the middle of the segment */
735 if (row == 0 && !(-Ctrl->E.mode & 1)) continue; /* First record, but we are to skip it */
736 if (row == last_row && !(-Ctrl->E.mode & 2)) continue; /* Last record, but we are to skip it */
737 }
738 else { /* Only pass modulo E.mode records (this always includes the first row), if -EM then make sure we also write the last row */
739 if ((row % Ctrl->E.mode) != 0) { /* Check if last row and -EM was used */
740 if (!Ctrl->E.end || row < last_row) continue;
741 }
742 }
743 /* Pull out current virtual row (may consist of a single or many (-A) table rows) */
744 for (tbl_hor = out_col = tlen = 0; tbl_hor < n_horizontal_tbls; tbl_hor++) { /* Number of tables to place horizontally */
745 use_tbl = (Ctrl->A.active) ? tbl_hor : tbl_ver;
746 Si = D[GMT_IN]->table[use_tbl]->segment[seg]; /* Current input segment in this table */
747 if (Si->text && Si->text[row]) tlen += strlen (Si->text[row]) + 1; /* String + separator */
748 for (col = 0; col < Si->n_columns; col++, out_col++) { /* Now go across all columns in current table */
749 val[out_col] = Si->data[col][row];
750 }
751 }
752 for (col = 0; col < n_cols_out; col++) { /* Now go across the single virtual row */
753 if (col >= n_cols_in) continue; /* This column is beyond end of this table */
754 So->data[col][n_rows] = val[col];
755 }
756 if (tlen) { /* must deal with trailing text(s) */
757 bool add_separator = false;
758 if (So->text == NULL) So->text = gmt_M_memory (GMT, NULL, S->n_rows, char *);
759 So->text[n_rows] = calloc (tlen+1, sizeof(char)); /* Space for trailing \0 */
760 for (tbl_hor = 0; tbl_hor < n_horizontal_tbls; tbl_hor++) { /* Number of tables to place horizontally */
761 use_tbl = (Ctrl->A.active) ? tbl_hor : tbl_ver;
762 Si = D[GMT_IN]->table[use_tbl]->segment[seg]; /* Current input segment in this table */
763 if (Si->text && Si->text[row]) {
764 if (add_separator) strcat (So->text[n_rows], GMT->current.setting.io_col_separator);
765 strcat (So->text[n_rows], Si->text[row]);
766 add_separator = true;
767 }
768 }
769 }
770 n_rows++;
771 }
772 SHo->id = out_seg++;
773 So->n_rows = n_rows; /* Possibly shorter than originally allocated if -E is used */
774 D[GMT_OUT]->table[tbl_ver]->n_records += n_rows;
775 D[GMT_OUT]->n_records = D[GMT_OUT]->table[tbl_ver]->n_records;
776 if (SHi->ogr) gmt_duplicate_ogr_seg (GMT, So, S);
777 }
778 THo->id = tbl_ver;
779 }
780 gmt_M_free (GMT, val);
781
782 if (Ctrl->I.active) { /* Must reverse the order of tables, segments and/or records */
783 uint64_t tbl1, tbl2, row1, row2, seg1, seg2;
784 void *p = NULL;
785 if (Ctrl->I.mode & INV_ROWS) { /* Must actually swap rows */
786 GMT_Report (API, GMT_MSG_INFORMATION, "Reversing order of records within each segment.\n");
787 for (tbl = 0; tbl < D[GMT_OUT]->n_tables; tbl++) { /* Number of output tables */
788 for (seg = 0; seg < D[GMT_OUT]->table[tbl]->n_segments; seg++) { /* For each segment in the tables */
789 for (row1 = 0, row2 = D[GMT_OUT]->table[tbl]->segment[seg]->n_rows - 1; row1 < D[GMT_OUT]->table[tbl]->segment[seg]->n_rows/2; row1++, row2--) {
790 for (col = 0; col < D[GMT_OUT]->table[tbl]->segment[seg]->n_columns; col++)
791 gmt_M_double_swap (D[GMT_OUT]->table[tbl]->segment[seg]->data[col][row1], D[GMT_OUT]->table[tbl]->segment[seg]->data[col][row2]);
792 }
793 }
794 }
795 }
796 if (Ctrl->I.mode & INV_SEGS) { /* Must reorder pointers to segments within each table */
797 GMT_Report (API, GMT_MSG_INFORMATION, "Reversing order of segments within each table.\n");
798 for (tbl = 0; tbl < D[GMT_OUT]->n_tables; tbl++) { /* Number of output tables */
799 for (seg1 = 0, seg2 = D[GMT_OUT]->table[tbl]->n_segments-1; seg1 < D[GMT_OUT]->table[tbl]->n_segments/2; seg1++, seg2--) { /* For each segment in the table */
800 p = D[GMT_OUT]->table[tbl]->segment[seg1];
801 D[GMT_OUT]->table[tbl]->segment[seg1] = D[GMT_OUT]->table[tbl]->segment[seg2];
802 D[GMT_OUT]->table[tbl]->segment[seg2] = p;
803 }
804 }
805 }
806 if (Ctrl->I.mode & INV_TBLS) { /* Must reorder pointers to tables within dataset */
807 GMT_Report (API, GMT_MSG_INFORMATION, "Reversing order of tables within the data set.\n");
808 for (tbl1 = 0, tbl2 = D[GMT_OUT]->n_tables-1; tbl1 < D[GMT_OUT]->n_tables/2; tbl1++, tbl2--) { /* For each table */
809 p = D[GMT_OUT]->table[tbl1];
810 D[GMT_OUT]->table[tbl1] = D[GMT_OUT]->table[tbl2];
811 D[GMT_OUT]->table[tbl2] = p;
812 }
813 }
814 }
815
816 /* Now ready for output */
817
818 if (Ctrl->D.active) { /* Set composite name and io-mode */
819 gmt_M_str_free (Ctrl->Out.file);
820 Ctrl->Out.file = strdup (Ctrl->D.name);
821 DHo->io_mode = Ctrl->D.mode;
822 if (Ctrl->D.origin) { /* Update IDs */
823 for (tbl = 0; tbl < D[GMT_OUT]->n_tables; tbl++) { /* For each table */
824 THo = gmt_get_DT_hidden (D[GMT_OUT]->table[tbl]);
825 THo->id += Ctrl->D.t_orig;
826 for (seg = 0; seg < D[GMT_OUT]->table[tbl]->n_segments; seg++) { /* For each segment */
827 SHo = gmt_get_DS_hidden (D[GMT_OUT]->table[tbl]->segment[seg]);
828 SHo->id += Ctrl->D.s_orig;
829 }
830 }
831 }
832 }
833 else { /* Just register output to stdout or the given file via ->outfile */
834 if (GMT->common.a.output) /* Must notify the machinery of this output type */
835 DHo->io_mode = GMT_WRITE_OGR;
836 }
837
838 if (Ctrl->T.active[EXCLUDE_HEADERS] && gmt_M_file_is_memory (Ctrl->Out.file) && D[GMT_OUT]->n_segments > 1) {
839 /* Since no file is written we must physically collate segments into a single segment per table first */
840 unsigned int flag[3] = {0, 0, GMT_WRITE_SEGMENT};
841 struct GMT_DATASET *D2 = NULL;
842 if ((D2 = GMT_Convert_Data (API, D[GMT_OUT], GMT_IS_DATASET, NULL, GMT_IS_DATASET, flag)) == NULL) {
843 GMT_Report (API, GMT_MSG_ERROR, "Failure while collating each table's segments into a single segment per table.\n");
844 Return (API->error);
845 }
846 if (GMT_Destroy_Data (API, &D[GMT_OUT]) != GMT_NOERROR) { /* Remove the previously registered output dataset */
847 Return (API->error);
848 }
849 D[GMT_OUT] = D2; /* Hook up the reformatted dataset */
850 }
851 if (Ctrl->N.active) { /* Sort the output data on selected column before writing */
852 struct GMT_ORDER *Z = NULL;
853 uint64_t max_len = 0;
854 bool do_it = true;
855 char *way[3] = {"descending", "", "ascending"};
856 if (Ctrl->N.col >= D[GMT_OUT]->n_columns) {
857 GMT_Report (API, GMT_MSG_WARNING, "Column selected (%d) as sorting key is outside range of valid columns [0-%d]. No sorting performed\n", (int)Ctrl->N.col, (int)(D[GMT_OUT]->n_columns - 1));
858 do_it = false;
859 }
860 else
861 GMT_Report (API, GMT_MSG_INFORMATION, "Sort data based on column %d in %s order\n", (int)Ctrl->N.col, way[Ctrl->N.dir+1]);
862 GMT->current.io.record_type[GMT_OUT] = GMT->current.io.record_type[GMT_IN];
863 for (tbl = 0; do_it && tbl < D[GMT_OUT]->n_tables; tbl++) { /* Number of output tables */
864 for (seg = 0; seg < D[GMT_OUT]->table[tbl]->n_segments; seg++) { /* For each segment in the tables */
865 S = D[GMT_OUT]->table[tbl]->segment[seg]; /* Current segment */
866 if (S->n_rows > max_len) { /* (Re-)allocate memory for sort order array */
867 Z = gmt_M_memory (GMT, Z, S->n_rows, struct GMT_ORDER);
868 max_len = S->n_rows;
869 }
870 for (row = 0; row < S->n_rows; row++) { /* Load up value/order struct array */
871 Z[row].value = S->data[Ctrl->N.col][row];
872 Z[row].order = row;
873 }
874 gmt_sort_order (GMT, Z, S->n_rows, Ctrl->N.dir); /* Sort per segment */
875 gmt_prep_tmp_arrays (GMT, GMT_OUT, S->n_rows, 1); /* Init or reallocate tmp vector */
876 for (col = 0; col < S->n_columns; col++) {
877 for (row = 0; row < S->n_rows; row++) { /* Do the shuffle via a temp vector */
878 GMT->hidden.mem_coord[GMT_X][row] = S->data[col][Z[row].order];
879 if (S->text) GMT->hidden.mem_txt[row] = S->text[Z[row].order];
880 }
881 gmt_M_memcpy (S->data[col], GMT->hidden.mem_coord[GMT_X], S->n_rows, double);
882 if (S->text) gmt_M_memcpy (S->text, GMT->hidden.mem_txt, S->n_rows, char *);
883 }
884 }
885 }
886 if (do_it) gmt_M_free (GMT, Z);
887 }
888
889 if (GMT_Write_Data (API, GMT_IS_DATASET, GMT_IS_FILE, D[GMT_IN]->geometry, DHo->io_mode, NULL, Ctrl->Out.file, D[GMT_OUT]) != GMT_NOERROR) {
890 Return (API->error);
891 }
892 if (prevent_seg_headers) GMT->current.io.skip_headers_on_outout = false; /* Restore to default if it was changed for file output */
893
894 GMT_Report (API, GMT_MSG_INFORMATION, "%" PRIu64 " tables %s, %" PRIu64 " records passed (input cols = %d; output cols = %d)\n",
895 D[GMT_IN]->n_tables, method[Ctrl->A.active], D[GMT_OUT]->n_records, n_cols_in, n_cols_out);
896 if (Ctrl->Q.active || Ctrl->S.active) GMT_Report (API, GMT_MSG_INFORMATION, "Extracted %" PRIu64 " from a total of %" PRIu64 " segments\n", n_out_seg, D[GMT_OUT]->n_segments);
897 if (n_duplicates) GMT_Report (API, GMT_MSG_INFORMATION, "Eliminated %" PRIu64 " duplicate records\n", n_duplicates);
898
899 Return (GMT_NOERROR);
900 }
901