diff --git a/src/gmt_init.c b/src/gmt_init.c index 203387270d0..d2bb1e25ac3 100644 --- a/src/gmt_init.c +++ b/src/gmt_init.c @@ -6858,7 +6858,11 @@ EXTERN_MSC void gmtinit_conf_classic (struct GMT_CTRL *GMT) { GMT->current.setting.given_unit[GMTCASE_MAP_ANNOT_OFFSET_PRIMARY] = 'p'; GMT->current.setting.given_unit[GMTCASE_MAP_ANNOT_OFFSET_SECONDARY] = 'p'; /* MAP_ANNOT_OBLIQUE */ - GMT->current.setting.map_annot_oblique = GMT_OBL_ANNOT_LON_HORIZONTAL | GMT_OBL_ANNOT_LAT_HORIZONTAL | GMT_OBL_ANNOT_EXTEND_TICKS; + /* The anywhere bit was lost when this setting went from a bit-sum to a keyword list; without it an oblique frame + * only annotates longitudes on the S/N borders and latitudes on the W/E borders, which throws away most of the + * annotations as soon as the graticule is rotated far from the frame axes. The documentation has said "default + * is anywhere" all along, and that is what GMT 6.1.0 did [issue #8418] */ + GMT->current.setting.map_annot_oblique = GMT_OBL_ANNOT_ANYWHERE | GMT_OBL_ANNOT_LON_HORIZONTAL | GMT_OBL_ANNOT_LAT_HORIZONTAL | GMT_OBL_ANNOT_EXTEND_TICKS; /* MAP_ANNOT_MIN_ANGLE */ GMT->current.setting.map_annot_min_angle = 20; /* MAP_ANNOT_MIN_SPACING */ diff --git a/src/gmt_plot.c b/src/gmt_plot.c index 8e7a7cbb68a..91c51e64c5b 100644 --- a/src/gmt_plot.c +++ b/src/gmt_plot.c @@ -1837,11 +1837,12 @@ GMT_LOCAL void gmtplot_map_symbol (struct GMT_CTRL *GMT, struct PSL_CTRL *PSL, s double line_angle, text_angle, div, tick_length, o_len, len, ca, sa, boost; double *xx = xings->xx, *yy = xings->yy, *line_angles = xings->angle; unsigned int i, annot_type, justify, *sides = xings->sides, nx = xings->nx; - bool flip; + bool flip, no_tick; len = gmtplot_get_annot_offset (GMT, &flip, level); /* Get annotation offset, and flip justification if "inside" */ annot_type = 2 << type; /* 2 = NS, 4 = EW */ for (i = 0; i < nx; i++) { + no_tick = false; if (!(GMT->current.setting.map_annot_oblique & GMT_OBL_ANNOT_ANYWHERE) && ((type == 0 && (sides[i] % 2)) || (type == 1 && !(sides[i] % 2)))) continue; if (gmtlib_prepare_label (GMT, line_angles[i], sides[i], xx[i], yy[i], type, &line_angle, &text_angle, &justify)) continue; @@ -1853,12 +1854,28 @@ GMT_LOCAL void gmtplot_map_symbol (struct GMT_CTRL *GMT, struct PSL_CTRL *PSL, s if (!flip && GMT->current.setting.map_annot_oblique & annot_type) o_len = tick_length; if (GMT->current.setting.map_annot_oblique & GMT_OBL_ANNOT_EXTEND_TICKS) { div = ((sides[i] % 2) ? fabs (ca) : fabs (sa)); - o_len /= div; + /* We only ride out to the tip of the extended tick if there actually is one: gmtplot_map_tick skips + * ticks whose gridline meets the border at less than MAP_ANNOT_MIN_ANGLE, and dividing by that same + * tiny div here would fling the annotation far along the border, away from the crossing it belongs + * to and with no tick to connect the two. Below that angle we step straight out from the border + * instead, so the annotation stays where its gridline meets the frame [issue #8418] */ + if (div > sind (GMT->current.setting.map_annot_min_angle)) + o_len /= div; + else + no_tick = true; } else o_len += copysign (boost, o_len); - xx[i] += o_len * ca; - yy[i] += o_len * sa; + if (no_tick) { /* Offset normal to the border since there is no oblique tick to follow */ + if (sides[i] % 2) + xx[i] += (sides[i] == 1) ? o_len : -o_len; + else + yy[i] += (sides[i] == 2) ? o_len : -o_len; + } + else { + xx[i] += o_len * ca; + yy[i] += o_len * sa; + } if (!flip && (GMT->current.setting.map_annot_oblique & annot_type) && GMT->current.setting.map_annot_offset[level] > 0.0) { if (sides[i] % 2) xx[i] += (sides[i] == 1) ? GMT->current.setting.map_annot_offset[level] : -GMT->current.setting.map_annot_offset[level]; diff --git a/test/psbasemap/oblique_annot.sh b/test/psbasemap/oblique_annot.sh new file mode 100644 index 00000000000..9a302877d68 --- /dev/null +++ b/test/psbasemap/oblique_annot.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# +# Oblique frames must annotate a gridline where it actually meets the border. +# +# With MAP_ANNOT_OBLIQUE's tick_extend the annotation goes at the tip of the tick that is extended +# along the gridline. gmtplot_map_tick draws no such tick once the gridline meets the border at +# less than MAP_ANNOT_MIN_ANGLE, but the annotation code used to extend regardless, dividing its +# offset by a vanishing sine and flinging the label far along the border, detached from the crossing +# it belongs to. Here the 175E meridian meets the N border at about 6 degrees: its annotation +# belongs at x = 3.1 cm and used to be drawn at x = 2.0 cm on a map only 6 cm wide. See issue #8418. +# +# The positions below were checked against the crossings computed independently with gmt mapproject. + +# Pin everything the extracted table depends on, so that it is the annotation placement being tested +gmt set FORMAT_GEO_MAP ddd:mm:ssF MAP_ANNOT_MIN_SPACING 0 \ + MAP_ANNOT_OBLIQUE anywhere,lon_horizontal,lat_horizontal,tick_extend + +gmt psbasemap -JOb168/51/172/32.5/6c -R-500/2300/-400/400+uk -BeNsW -Bxa5f1 -Bya5f1 -P > map.ps + +# Pull the annotations out of the PostScript as "label border position_along_that_border_in_cm". +# PSL units are 1/1200 inch measured from the lower left map corner, hence the 2.54/1200 scaling. +tr '\r' '\n' < map.ps | awk ' + { for (i = 3; i <= NF; i++) if ($i == "M") { x = $(i-2); y = $(i-1) } } # Remember latest moveto + /\([0-9]+.[EN]\) [a-z][a-z] Z/ { + for (i = 1; i <= NF; i++) if ($i ~ /^\(/) { lab = $i; just = $(i+1) } + if (just == "bc") printf "%s N %.1f\n", lab, x*2.54/1200 + else if (just == "tc") printf "%s S %.1f\n", lab, x*2.54/1200 + else if (just == "mr") printf "%s W %.1f\n", lab, y*2.54/1200 + else if (just == "ml") printf "%s E %.1f\n", lab, y*2.54/1200 + }' | sort > result.txt + +printf '%b' '(165\260E) W 0.7\n(170\260E) W 1.4\n(175\260E) N 3.1\n(35\260N) N 5.1\n(40\260N) N 3.9\n(45\260N) N 2.7\n(50\260N) N 1.5\n(55\260N) N 0.3\n(55\260N) W 0.4\n' > answer.txt + +diff -q --strip-trailing-cr answer.txt result.txt