From 6b6581025c4a172875dc6f35ec58ad413314e2fa Mon Sep 17 00:00:00 2001 From: Esteban82 Date: Tue, 7 Jul 2026 16:58:53 -0300 Subject: [PATCH 1/3] Fix geo annotation rounding mismatch --- src/gmt_map.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/gmt_map.c b/src/gmt_map.c index 373c46c9980..77e4e8399d6 100644 --- a/src/gmt_map.c +++ b/src/gmt_map.c @@ -7085,8 +7085,24 @@ void gmt_auto_frame_interval (struct GMT_CTRL *GMT, unsigned int axis, unsigned d *= MAX (0.05, MIN (5.0 * GMT->current.setting.font_annot[item].size / f, 0.2)); /* Now determine 'round' major and minor tick intervals */ - if (gmt_M_axis_is_geo (GMT, axis)) /* Geographical coordinate */ - p = (d < GMT_MIN2DEG) ? GMT_SEC2DEG : (d < 1.0) ? GMT_MIN2DEG : 1.0; + if (gmt_M_axis_is_geo (GMT, axis)) { /* Geographical coordinate */ + struct GMT_GEO_IO *S = &GMT->current.plot.calclock.geo; + if (S->order[1] == GMT_NOTSET && S->n_sec_decimals > 0) { + /* FORMAT_GEO_MAP is a plain decimal-degree template with a fixed number + * of decimals (e.g., ddd.xxF or ddd.xxxF). If we let the interval be + * picked in arcsec/arcmin/degree "nice" units as usual it may end up as + * a value (e.g., 25" = 0.006944...9 degrees) that cannot be represented + * exactly at the requested decimal precision. The annotation label is + * then rounded for display while the tick/gridline is still placed at + * the true (unrounded) coordinate, so the label and its position visibly + * disagree -- see GMT issue #8490. To avoid this we pick the interval + * directly in units of the smallest displayable decimal step so that any + * "nice" multiple of it is exactly representable in the chosen format. */ + p = pow (10.0, -(double)S->n_sec_decimals); + } + else + p = (d < GMT_MIN2DEG) ? GMT_SEC2DEG : (d < 1.0) ? GMT_MIN2DEG : 1.0; + } else if (GMT->current.io.cycle_col == axis && GMT->current.io.cycle_operator != GMT_CYCLE_CUSTOM) { switch (GMT->current.io.cycle_operator) { case GMT_CYCLE_MIN: case GMT_CYCLE_HOUR: /* With a range of 60 it behaves like geo */ From 528b9cd10a2798240968c55eb55b468ccd622d41 Mon Sep 17 00:00:00 2001 From: Esteban82 Date: Tue, 7 Jul 2026 16:59:04 -0300 Subject: [PATCH 2/3] Fix geo annotation rounding mismatch --- issue-8490-fix.patch | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 issue-8490-fix.patch diff --git a/issue-8490-fix.patch b/issue-8490-fix.patch new file mode 100644 index 00000000000..d12e2ce220f --- /dev/null +++ b/issue-8490-fix.patch @@ -0,0 +1,31 @@ +diff --git a/src/gmt_map.c b/src/gmt_map.c +index 373c46c..77e4e83 100644 +--- a/src/gmt_map.c ++++ b/src/gmt_map.c +@@ -7085,8 +7085,24 @@ void gmt_auto_frame_interval (struct GMT_CTRL *GMT, unsigned int axis, unsigned + d *= MAX (0.05, MIN (5.0 * GMT->current.setting.font_annot[item].size / f, 0.2)); + + /* Now determine 'round' major and minor tick intervals */ +- if (gmt_M_axis_is_geo (GMT, axis)) /* Geographical coordinate */ +- p = (d < GMT_MIN2DEG) ? GMT_SEC2DEG : (d < 1.0) ? GMT_MIN2DEG : 1.0; ++ if (gmt_M_axis_is_geo (GMT, axis)) { /* Geographical coordinate */ ++ struct GMT_GEO_IO *S = &GMT->current.plot.calclock.geo; ++ if (S->order[1] == GMT_NOTSET && S->n_sec_decimals > 0) { ++ /* FORMAT_GEO_MAP is a plain decimal-degree template with a fixed number ++ * of decimals (e.g., ddd.xxF or ddd.xxxF). If we let the interval be ++ * picked in arcsec/arcmin/degree "nice" units as usual it may end up as ++ * a value (e.g., 25" = 0.006944...9 degrees) that cannot be represented ++ * exactly at the requested decimal precision. The annotation label is ++ * then rounded for display while the tick/gridline is still placed at ++ * the true (unrounded) coordinate, so the label and its position visibly ++ * disagree -- see GMT issue #8490. To avoid this we pick the interval ++ * directly in units of the smallest displayable decimal step so that any ++ * "nice" multiple of it is exactly representable in the chosen format. */ ++ p = pow (10.0, -(double)S->n_sec_decimals); ++ } ++ else ++ p = (d < GMT_MIN2DEG) ? GMT_SEC2DEG : (d < 1.0) ? GMT_MIN2DEG : 1.0; ++ } + else if (GMT->current.io.cycle_col == axis && GMT->current.io.cycle_operator != GMT_CYCLE_CUSTOM) { + switch (GMT->current.io.cycle_operator) { + case GMT_CYCLE_MIN: case GMT_CYCLE_HOUR: /* With a range of 60 it behaves like geo */ From d2a0c9c30efa06f01e1e0b099d076dc0807541d2 Mon Sep 17 00:00:00 2001 From: Esteban82 Date: Tue, 7 Jul 2026 17:13:56 -0300 Subject: [PATCH 3/3] Remove patch --- issue-8490-fix.patch | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 issue-8490-fix.patch diff --git a/issue-8490-fix.patch b/issue-8490-fix.patch deleted file mode 100644 index d12e2ce220f..00000000000 --- a/issue-8490-fix.patch +++ /dev/null @@ -1,31 +0,0 @@ -diff --git a/src/gmt_map.c b/src/gmt_map.c -index 373c46c..77e4e83 100644 ---- a/src/gmt_map.c -+++ b/src/gmt_map.c -@@ -7085,8 +7085,24 @@ void gmt_auto_frame_interval (struct GMT_CTRL *GMT, unsigned int axis, unsigned - d *= MAX (0.05, MIN (5.0 * GMT->current.setting.font_annot[item].size / f, 0.2)); - - /* Now determine 'round' major and minor tick intervals */ -- if (gmt_M_axis_is_geo (GMT, axis)) /* Geographical coordinate */ -- p = (d < GMT_MIN2DEG) ? GMT_SEC2DEG : (d < 1.0) ? GMT_MIN2DEG : 1.0; -+ if (gmt_M_axis_is_geo (GMT, axis)) { /* Geographical coordinate */ -+ struct GMT_GEO_IO *S = &GMT->current.plot.calclock.geo; -+ if (S->order[1] == GMT_NOTSET && S->n_sec_decimals > 0) { -+ /* FORMAT_GEO_MAP is a plain decimal-degree template with a fixed number -+ * of decimals (e.g., ddd.xxF or ddd.xxxF). If we let the interval be -+ * picked in arcsec/arcmin/degree "nice" units as usual it may end up as -+ * a value (e.g., 25" = 0.006944...9 degrees) that cannot be represented -+ * exactly at the requested decimal precision. The annotation label is -+ * then rounded for display while the tick/gridline is still placed at -+ * the true (unrounded) coordinate, so the label and its position visibly -+ * disagree -- see GMT issue #8490. To avoid this we pick the interval -+ * directly in units of the smallest displayable decimal step so that any -+ * "nice" multiple of it is exactly representable in the chosen format. */ -+ p = pow (10.0, -(double)S->n_sec_decimals); -+ } -+ else -+ p = (d < GMT_MIN2DEG) ? GMT_SEC2DEG : (d < 1.0) ? GMT_MIN2DEG : 1.0; -+ } - else if (GMT->current.io.cycle_col == axis && GMT->current.io.cycle_operator != GMT_CYCLE_CUSTOM) { - switch (GMT->current.io.cycle_operator) { - case GMT_CYCLE_MIN: case GMT_CYCLE_HOUR: /* With a range of 60 it behaves like geo */