Skip to content

Commit d638d73

Browse files
authored
Let project -G accept increment unit and +n modifier (#5984)
* Let project -G accept units This has been long overdue. * Delay inc parsing * updates * update docs * Update project.rst
1 parent 1d1e7e5 commit d638d73

File tree

3 files changed

+73
-24
lines changed

3 files changed

+73
-24
lines changed

doc/rst/source/project.rst

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,20 @@ Synopsis
1212

1313
.. include:: common_SYN_OPTs.rst_
1414

15-
**gmt project** [ *table* ] |-C|\ *cx*/*cy* [ |-A|\ *azimuth* ]
16-
[ |-E|\ *bx*/*by* ] [ |-F|\ *flags* ]
17-
[ |-G|\ *dist*\ [/*colat*][**+c**\|\ **h**] ]
15+
**gmt project** [ *table* ]
16+
|-C|\ *cx*/*cy*
17+
[ |-A|\ *azimuth* ]
18+
[ |-E|\ *bx*/*by* ]
19+
[ |-F|\ *flags* ]
20+
[ |-G|\ *dist*\ [*unit*][/*colat*][**+c**][**+h**][**+n**] ]
1821
[ |-L|\ [**w**\|\ *lmin*/*lmax*] ]
19-
[ |-N| ] [ |-Q| ] [ |-S| ]
22+
[ |-N| ]
23+
[ |-Q| ]
24+
[ |-S| ]
2025
[ |-T|\ *px*/*py* ]
2126
[ |SYN_OPT-V| ]
2227
[ |-W|\ *wmin*/*wmax* ]
23-
[ |-Z|\ *major*\ [*unit*][/*minor*/*azimuth*][**+e**\|\ **n**] ]
28+
[ |-Z|\ *major*\ [*unit*][/*minor*/*azimuth*][**+e**] ]
2429
[ |SYN_OPT-b| ]
2530
[ |SYN_OPT-d| ]
2631
[ |SYN_OPT-e| ]
@@ -150,10 +155,10 @@ Optional Arguments
150155

151156
.. _-G:
152157

153-
**-G**\ *dist*\ [/*colat*][**+c**\|\ **h**]
158+
**-G**\ *dist*\ [*unit*][/*colat*][**+c**][**+h**][**+n**]
154159
Create (*r*, *s*, *p*) output points every *dist* units of *p*, assuming all units are the same unless
155-
:math:`x, y, r, s` are set to degrees using |-Q|. No input is read when |-G| is used. The following directives
156-
and modifiers are supported:
160+
:math:`x, y, r, s` are set to degrees using |-Q|. No input is read when |-G| is used. See `Units`_ for
161+
selecting geographic distance units [km]. The following directives and modifiers are supported:
157162

158163
- Optionally, append /*colat* for a small circle instead [Default is a colatitude of 90, i.e., a great circle]. Note,
159164
when using |-C| and |-E| to generate a circle that goes through the center and end point, the center and end point
@@ -162,6 +167,8 @@ Optional Arguments
162167
going through the center *cx*/*cy*.
163168
- Optionally, append **+h** to report the position of the pole as part of the segment header when using |-T|
164169
[Default is no header].
170+
- Optionally, append **+n** to indicate a desired number of points rather than an increment. Requires |-C| and |-E| or |-Z|
171+
so that a length can be computed.
165172

166173
.. _-L:
167174

@@ -206,7 +213,7 @@ Optional Arguments
206213

207214
.. _-Z:
208215

209-
**-Z**\ *major*\ [*unit*][/*minor*/*azimuth*][**+e**\|\ **n**]
216+
**-Z**\ *major*\ [*unit*][/*minor*/*azimuth*][**+e**]
210217
Create the coordinates of an ellipse with *major* and *minor* axes given in km (unless |-N| is given for a
211218
Cartesian ellipse) and the *azimuth* of the major axis in degrees; used in conjunction with |-C| (sets its center)
212219
and |-G| (sets the distance increment). **Note**: For the Cartesian ellipse (which requires |-N|), we expect
@@ -217,7 +224,6 @@ Optional Arguments
217224

218225
- Append **+e** to adjust the increment set via |-G| so that the ellipse has equal distance increments [Default
219226
uses the given increment and closes the ellipse].
220-
- Append **+n** to set a specific number of unique equidistant points via |-G|.
221227

222228
.. |Add_-bi| replace:: [Default is 2 input columns].
223229
.. include:: explain_-bi.rst_

src/project.c

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,16 @@ struct PROJECT_CTRL { /* All control options for this program (except common arg
5555
bool active;
5656
char col[PROJECT_N_FARGS]; /* Character codes for desired output in the right order */
5757
} F;
58-
struct PROJECT_G { /* -G<inc>[/<colat>][+h] */
58+
struct PROJECT_G { /* -G<inc>[<unit>[/<colat>][+c|h][+n] */
5959
bool active;
6060
bool header;
61+
bool number;
6162
bool through_C;
6263
unsigned int mode;
6364
double inc;
6465
double colat;
66+
char unit;
67+
int d_mode; /* Could be negative */
6568
} G;
6669
struct PROJECT_L { /* -L[w][<l_min>/<l_max>] */
6770
bool active;
@@ -323,8 +326,8 @@ static void Free_Ctrl (struct GMT_CTRL *GMT, struct PROJECT_CTRL *C) { /* Deallo
323326
static int usage (struct GMTAPI_CTRL *API, int level) {
324327
const char *name = gmt_show_name_and_purpose (API, THIS_MODULE_LIB, THIS_MODULE_CLASSIC_NAME, THIS_MODULE_PURPOSE);
325328
if (level == GMT_MODULE_PURPOSE) return (GMT_NOERROR);
326-
GMT_Usage (API, 0, "usage: %s [<table>] -C<ox>/<oy> [-A<azimuth>] [-E<bx>/<by>] [-F<flags>] [-G<dist>[/<colat>][+c|h]] "
327-
"[-L[w|<l_min>/<l_max>]] [-N] [-Q] [-S] [-T<px>/<py>] [%s] [-W<w_min>/<w_max>] [-Z<major>[unit][/<minor>/<azimuth>][+e|n]] "
329+
GMT_Usage (API, 0, "usage: %s [<table>] -C<ox>/<oy> [-A<azimuth>] [-E<bx>/<by>] [-F<flags>] [-G<dist>[unit][/<colat>][+c][+h][+n]] "
330+
"[-L[w|<l_min>/<l_max>]] [-N] [-Q] [-S] [-T<px>/<py>] [%s] [-W<w_min>/<w_max>] [-Z<major>[unit][/<minor>/<azimuth>][+e]] "
328331
"[%s] [%s] [%s] [%s] [%s] [%s] [%s] [%s] [%s] [%s] [%s] [%s]\n",
329332
name, GMT_V_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,
330333
GMT_q_OPT, GMT_s_OPT, GMT_colon_OPT, GMT_PAR_OPT);
@@ -361,12 +364,15 @@ static int usage (struct GMTAPI_CTRL *API, int level) {
361364
"Note 2: z refers to all input data columns beyond the required x,y. "
362365
"Note 3: If -G is set, -F is not available and output defaults to rsp "
363366
"[Default is all fields, i.e., -Fxyzpqrs].");
364-
GMT_Usage (API, 1, "\n-G<dist>[/<colat>][+c|h]");
367+
GMT_Usage (API, 1, "\n-G<dist>[<unit>][/<colat>][+c|h]");
365368
GMT_Usage (API, -2, "Generate (r,s,p) points along profile every <dist> units. (No input data used.) "
366369
"If E given, will generate from C to E; else must give -L<l_min>/<l_max> for length. "
367370
"Optionally, append /<colat> for a small circle path through C and E (requires -C -E). Some modifiers:");
368371
GMT_Usage (API, 3, "+c If given -T and -C, compute and use <colat> of C [90].");
369372
GMT_Usage (API, 3, "+h Place information about the pole in a segment header [no header].");
373+
GMT_Usage (API, 3, "+n Increment specifies the number of points instead (requires -C -E or -Z).");
374+
GMT_Usage (API, -2, "For geographic profile, append e (meter), f (foot), k (km), M (mile), n (nautical mile), u (survey foot), d (arc degree), "
375+
"m (arc minute), or s (arc second) to <inc> [k]. ");
370376
GMT_Usage (API, 1, "\n-L[w|<l_min>/<l_max>]");
371377
GMT_Usage (API, -2, "Check the Length along the projected track and use only certain points:n");
372378
GMT_Usage (API, 3, "%s Append w to only use those points Within the span from C to E (Must have set -E).", GMT_LINE_BULLET);
@@ -387,7 +393,6 @@ static int usage (struct GMTAPI_CTRL *API, int level) {
387393
GMT_Usage (API, 1, "\n-Z<major>[unit][/<minor>/<azimuth>][+e|n]");
388394
GMT_Usage (API, -2, "With -G and -C, generate an ellipse of given major and minor axes and the azimuth of the major axis.");
389395
GMT_Usage (API, 3, "+e Adjust increment to fix perimeter exactly [use increment as given in -G].");
390-
GMT_Usage (API, 3, "+n Indicate that increment in -G specifies the number of unique perimeter points instead.");
391396
GMT_Usage (API, -2, "For a degenerate ellipse, i.e., circle, you may just give <major> only as the <diameter>. "
392397
"Append e (meter), f (foot), k (km), M (mile), n (nautical mile), u (survey foot), d (arc degree), "
393398
"m (arc minute), or s (arc second) to <major> [k]; we assume -G is in the same units (unless +n is used). "
@@ -411,7 +416,14 @@ static int parse (struct GMT_CTRL *GMT, struct PROJECT_CTRL *Ctrl, struct GMT_OP
411416
struct GMT_OPTION *opt = NULL;
412417
struct GMTAPI_CTRL *API = GMT->parent;
413418

414-
for (opt = options; opt; opt = opt->next) if (opt->option == 'N') Ctrl->N.active = true; /* Must find -N first, if given */
419+
for (opt = options; opt; opt = opt->next) {
420+
if (opt->option == 'N') /* Must find -N first, if given */
421+
Ctrl->N.active = true;
422+
else if (opt->option == 'Q') { /* Geographic coordinates */
423+
gmt_set_geographic (GMT, GMT_IN);
424+
gmt_set_geographic (GMT, GMT_OUT);
425+
}
426+
}
415427

416428
for (opt = options; opt; opt = opt->next) {
417429
switch (opt->option) {
@@ -488,24 +500,33 @@ static int parse (struct GMT_CTRL *GMT, struct PROJECT_CTRL *Ctrl, struct GMT_OP
488500
opt->arg[len] = 0; /* Temporarily remove the trailing + sign */
489501
ch = &opt->arg[len];
490502
}
491-
if ((c = gmt_first_modifier (GMT, opt->arg, "ch"))) { /* Process any modifiers */
503+
if ((c = gmt_first_modifier (GMT, opt->arg, "chn"))) { /* Process any modifiers */
492504
pos = 0; /* Reset to start of new word */
493-
while (gmt_getmodopt (GMT, 'G', c, "ch", &pos, p, &n_errors) && n_errors == 0) {
505+
while (gmt_getmodopt (GMT, 'G', c, "chn", &pos, p, &n_errors) && n_errors == 0) {
494506
switch (p[0]) {
495-
case 'c': Ctrl->G.through_C = true; break; /* Compute required colatitude for small ircle to go through C */
507+
case 'c': Ctrl->G.through_C = true; break; /* Compute required colatitude for small circle to go through C */
496508
case 'h': Ctrl->G.header = true; break; /* Output segment header */
509+
case 'n': Ctrl->G.number = true; break; /* Gave number of points rather than increment */
497510
default: break; /* These are caught in gmt_getmodopt so break is just for Coverity */
498511
}
499512
}
500513
c[0] = '\0'; /* Hide modifiers */
501514
}
502515
if (sscanf (opt->arg, "%[^/]/%s", txt_a, txt_b) == 2) { /* Got dist/colat */
503516
Ctrl->G.mode = 1;
504-
Ctrl->G.inc = atof (txt_a);
505517
n_errors += gmt_verify_expectations (GMT, gmt_M_type (GMT, GMT_IN, GMT_Y), gmt_scanf_arg (GMT, txt_b, gmt_M_type (GMT, GMT_IN, GMT_Y), false, &Ctrl->G.colat), txt_b);
506518
}
507519
else
508-
Ctrl->G.inc = atof (opt->arg);
520+
strcpy (txt_a, opt->arg);
521+
if (Ctrl->G.number) {
522+
Ctrl->Z.number = true; /* Since -Z +n needs backwards compatibility support */
523+
Ctrl->G.inc = atof (txt_a);
524+
}
525+
else {
526+
if (gmt_M_is_geographic (GMT, GMT_IN) && strchr (GMT_LEN_UNITS, txt_a[strlen(txt_a)-1]) == NULL) /* No unit given, append k for default km */
527+
strcat (txt_a, "k");
528+
Ctrl->G.d_mode = gmt_get_distance (GMT, txt_a, &(Ctrl->G.inc), &(Ctrl->G.unit));
529+
}
509530
if (ch) ch[0] = '+'; /* Restore the obsolete plus-sign */
510531
if (c) c[0] = '+'; /* Restore modifier */
511532
break;
@@ -568,6 +589,8 @@ static int parse (struct GMT_CTRL *GMT, struct PROJECT_CTRL *Ctrl, struct GMT_OP
568589
Ctrl->Z.minor = atof (txt_b);
569590
}
570591
else { /* Watch out for units and convert to km */
592+
if (gmt_M_is_geographic (GMT, GMT_IN) && strchr (GMT_LEN_UNITS, txt_a[strlen(txt_a)-1]) == NULL) /* No unit given, append k for default km */
593+
strcat (txt_a, "k");
571594
Ctrl->Z.mode = gmt_get_distance (GMT, txt_a, &(Ctrl->Z.major), &(Ctrl->Z.unit));
572595
(void) gmt_get_distance (GMT, txt_b, &(Ctrl->Z.minor), &dummy);
573596
}
@@ -588,6 +611,9 @@ static int parse (struct GMT_CTRL *GMT, struct PROJECT_CTRL *Ctrl, struct GMT_OP
588611
if (Ctrl->N.active)
589612
Ctrl->Z.minor = Ctrl->Z.major = atof (opt->arg);
590613
else {
614+
strcpy (txt_a, opt->arg);
615+
if (gmt_M_is_geographic (GMT, GMT_IN) && strchr (GMT_LEN_UNITS, txt_a[strlen(txt_a)-1]) == NULL) /* No unit given, append k for default km */
616+
strcat (txt_a, "k");
591617
Ctrl->Z.mode = gmt_get_distance (GMT, opt->arg, &(Ctrl->Z.minor), &(Ctrl->Z.unit));
592618
Ctrl->Z.major = Ctrl->Z.minor;
593619
}
@@ -611,6 +637,8 @@ static int parse (struct GMT_CTRL *GMT, struct PROJECT_CTRL *Ctrl, struct GMT_OP
611637
n_errors++;
612638
}
613639

640+
n_errors += gmt_M_check_condition (GMT, Ctrl->G.number && !(Ctrl->C.active && Ctrl->E.active),
641+
"Option -G: Can only use +n if both -C and -E are specified\n");
614642
n_errors += gmt_M_check_condition (GMT, Ctrl->L.active && !Ctrl->L.constrain && Ctrl->L.min >= Ctrl->L.max,
615643
"Option -L: w_min must be < w_max\n");
616644
n_errors += gmt_M_check_condition (GMT, Ctrl->W.active && Ctrl->W.min >= Ctrl->W.max,
@@ -635,6 +663,8 @@ static int parse (struct GMT_CTRL *GMT, struct PROJECT_CTRL *Ctrl, struct GMT_OP
635663
"Option -N: Cannot be used with -G<dist>/<colat>\n");
636664
n_errors += gmt_M_check_condition (GMT, Ctrl->G.through_C && !Ctrl->C.active,
637665
"Option -G: Modifier +c requires both -C and -T\n");
666+
n_errors += gmt_M_check_condition (GMT, Ctrl->Z.active && !Ctrl->G.active,
667+
"Option -Z: Requires option -G as well\n");
638668
n_errors += gmt_M_check_condition (GMT, Ctrl->G.through_C && !Ctrl->T.active,
639669
"Option -G: Modifier +c requires both -C and -T\n");
640670
n_errors += gmt_check_binary_io (GMT, 2);
@@ -761,12 +791,10 @@ EXTERN_MSC int GMT_project (void *V_API, int mode, void *args) {
761791
if (GMT->current.map.dist[GMT_MAP_DIST].arc) { /* Got angular measures, convert to km */
762792
Ctrl->Z.minor *= (GMT->current.proj.KM_PR_DEG / GMT->current.map.dist[GMT_MAP_DIST].scale); /* Now in km */
763793
Ctrl->Z.major *= (GMT->current.proj.KM_PR_DEG / GMT->current.map.dist[GMT_MAP_DIST].scale); /* Now in km */
764-
if (!Ctrl->Z.number) Ctrl->G.inc *= (GMT->current.proj.KM_PR_DEG / GMT->current.map.dist[GMT_MAP_DIST].scale); /* Now in km */
765794
}
766795
else {
767796
Ctrl->Z.minor /= (GMT->current.map.dist[GMT_MAP_DIST].scale * METERS_IN_A_KM); /* Now in km */
768797
Ctrl->Z.major /= (GMT->current.map.dist[GMT_MAP_DIST].scale * METERS_IN_A_KM); /* Now in km */
769-
if (!Ctrl->Z.number) Ctrl->G.inc /= (GMT->current.map.dist[GMT_MAP_DIST].scale * METERS_IN_A_KM); /* Now in km */
770798
}
771799
}
772800
}
@@ -813,10 +841,24 @@ EXTERN_MSC int GMT_project (void *V_API, int mode, void *args) {
813841
P.find_new_point = true;
814842
}
815843
if (Ctrl->G.active) { /* Hardwire 3 output columns and set their types */
844+
if (gmt_M_is_geographic (GMT, GMT_IN) && Ctrl->G.d_mode) {
845+
if (gmt_init_distaz (GMT, Ctrl->G.unit, Ctrl->G.d_mode, GMT_MAP_DIST) == GMT_NOT_A_VALID_TYPE)
846+
Return (GMT_NOT_A_VALID_TYPE);
847+
}
816848
P.n_outputs = 3;
817849
gmt_set_column_type (GMT, GMT_OUT, GMT_X, (Ctrl->N.active) ? GMT_IS_FLOAT : GMT_IS_LON);
818850
gmt_set_column_type (GMT, GMT_OUT, GMT_Y, (Ctrl->N.active) ? GMT_IS_FLOAT : GMT_IS_LAT);
819851
gmt_set_column_type (GMT, GMT_OUT, GMT_Z, GMT_IS_FLOAT);
852+
if (Ctrl->G.number) { /* Must compute great circle separation and divide to get increment */
853+
double L;
854+
if (gmt_M_is_geographic (GMT, GMT_IN))
855+
L = 0.001 * gmt_great_circle_dist_meter (GMT, Ctrl->C.x, Ctrl->C.y, Ctrl->E.x, Ctrl->E.y);
856+
else
857+
L = hypot (Ctrl->C.x - Ctrl->E.x, Ctrl->C.y - Ctrl->E.y);
858+
Ctrl->G.inc = L / (Ctrl->G.inc - 1.0);
859+
}
860+
else if (Ctrl->Q.active && Ctrl->G.unit != 'k')
861+
Ctrl->G.inc *= 0.001 / GMT->current.map.dist[GMT_MAP_DIST].scale; /* Now in km */
820862
}
821863
else if (!Ctrl->N.active) { /* Decode and set the various output column types in the geographic case */
822864
for (col = 0; col < P.n_outputs; col++) {
@@ -937,6 +979,7 @@ EXTERN_MSC int GMT_project (void *V_API, int mode, void *args) {
937979
double h = pow (Ctrl->Z.major - Ctrl->Z.minor, 2.0) / pow (Ctrl->Z.major + Ctrl->Z.minor, 2.0);
938980
Ctrl->L.min = 0.0;
939981
Ctrl->L.max = M_PI * (Ctrl->Z.major + Ctrl->Z.minor) * (1.0 + (3.0 * h)/(10.0 + sqrt (4.0 - 3.0 * h))); /* Ramanujan approximation of ellipse circumference */
982+
if (gmt_M_is_geographic (GMT, GMT_IN)) Ctrl->L.max /= GMT->current.proj.DIST_KM_PR_DEG;
940983
if (Ctrl->Z.number) /* Want a specific number of points */
941984
Ctrl->G.inc = Ctrl->L.max / rint (Ctrl->G.inc);
942985
else if (Ctrl->Z.exact) { /* Adjust inc to fit the ellipse perimeter exactly */

test/project/ellipses.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66

77
gmt begin ellipses ps
88
gmt project -N -Z4/3/0+e -G0.1 -C2/1 | gmt plot -R-2/5/-2/5 -Jx1.5c -Bafg1 -W1p -Glightgray
9-
gmt project -Z400/300/0+e -G10 -C2/1 | gmt plot -Jm1.5c -Bafg1 -W1p -Glightgray -Y13c
9+
gmt project -Z400/300/0+e -G10 -C2/1 -Q | gmt plot -Jm1.5c -Bafg1 -W1p -Glightgray -Y13c
1010
gmt end show

0 commit comments

Comments
 (0)