source: git/src/cavern.c @ b58dbb4

debug-cidebug-ci-sanitisersfaster-cavernloglog-selectstereo-2025walls-datawalls-data-hanging-as-warningwarn-only-for-hanging-survey
Last change on this file since b58dbb4 was 4bea0f8, checked in by Olly Betts <olly@…>, 21 months ago

Fix two grid convergence bugs

One is due to a bug in PROJ < 9.3.0 with projected coordinate systems
with northing/easting axis order (such as EPSG:3042) which results in
the grid convergence being wrong by 90°. We now work around this
problem if building with an affected PROJ version. Reported by
Patrick Warren on the mailing list.

The other is that we weren't checking the output coordinate system
was set before calculating the convergence, which resulted in a
grid convergence of 0° being used if *declination auto was used
before *cs out. We now handle this case correctly, and the
convergence is now calculated lazily which happens when we read a
compass reading, and gives an error if the output coordinate system
hasn't been set by then.

  • Property mode set to 100644
File size: 14.2 KB
RevLine 
[0156ccfc]1/* cavern.c
[bb90203]2 * SURVEX Cave surveying software: data reduction main and related functions
[4bea0f8]3 * Copyright (C) 1991-2023 Olly Betts
[846746e]4 *
[89231c4]5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
[846746e]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
[89231c4]12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
[846746e]14 *
[89231c4]15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
[ecbc6c18]17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
[bb90203]18 */
19
20#ifdef HAVE_CONFIG_H
21#include <config.h>
22#endif
23
[9990aab]24#define MSG_SETUP_PROJ_SEARCH_PATH 1
25
[be97baf]26#include <limits.h>
[4833448]27#include <stdlib.h>
[bb90203]28#include <time.h>
29
[5853657]30#include "cavern.h"
31#include "cmdline.h"
32#include "commands.h"
[1ee204e]33#include "date.h"
[bb90203]34#include "datain.h"
35#include "debug.h"
36#include "message.h"
37#include "filename.h"
38#include "filelist.h"
[a405bc1]39#include "img_hosted.h"
[bb90203]40#include "listpos.h"
[5853657]41#include "netbits.h"
42#include "netskel.h"
43#include "osdepend.h"
[bb90203]44#include "out.h"
[8e8057c]45#include "str.h"
[5853657]46#include "validate.h"
[d18e778]47#include "whichos.h"
[bb90203]48
[affaeee]49#if OS_WIN32
50# include <conio.h> /* for _kbhit() and _getch() */
[e8cf429]51#endif
52
[bb90203]53/* For funcs which want to be immune from messing around with different
54 * calling conventions */
55#ifndef CDECL
56# define CDECL
57#endif
58
59/* Globals */
60node *stnlist = NULL;
61settings *pcs;
62prefix *root;
[a2c33ae]63prefix *anon_list = NULL;
[bb90203]64long cLegs, cStns;
65long cComponents;
[d1878c51]66bool fExportUsed = fFalse;
[c092d72]67char * proj_str_out = NULL;
[da9163b]68PJ * pj_cached = NULL;
[bb90203]69
70FILE *fhErrStat = NULL;
[693388e]71img *pimg = NULL;
[647407d]72bool fQuiet = fFalse; /* just show brief summary + errors */
[ed0f5b6]73bool fMute = fFalse; /* just show errors */
[c0b279c]74bool fSuppress = fFalse; /* only output 3d file */
[2b6eca8]75static bool fLog = fFalse; /* stdout to .log file */
[421b7d2]76static bool f_warnings_are_errors = fFalse; /* turn warnings into errors */
[647407d]77
78nosurveylink *nosurveyhead;
[bb90203]79
80real totadj, total, totplan, totvert;
[dfac588]81real min[6], max[6];
82prefix *pfxHi[6], *pfxLo[6];
[bb90203]83
84char *survey_title = NULL;
85int survey_title_len;
86
87bool fExplicitTitle = fFalse;
88
[8e8057c]89char *fnm_output_base = NULL;
90int fnm_output_base_is_dir = 0;
91
[ee05463]92lrudlist * model = NULL;
93lrud ** next_lrud = NULL;
94
[06a871f]95static void do_stats(void);
[bb90203]96
97static const struct option long_opts[] = {
98   /* const char *name; int has_arg (0 no_argument, 1 required_*, 2 optional_*); int *flag; int val; */
99   {"percentage", no_argument, 0, 'p'},
[21904d3]100   /* Ignore for compatibility with older versions. */
101   {"no-percentage", no_argument, 0, 0},
[8e8057c]102   {"output", required_argument, 0, 'o'},
[647407d]103   {"quiet", no_argument, 0, 'q'},
104   {"no-auxiliary-files", no_argument, 0, 's'},
[bb9d869]105   {"warnings-are-errors", no_argument, 0, 'w'},
[0dab87a]106   {"log", no_argument, 0, 1},
[4833448]107   {"3d-version", required_argument, 0, 'v'},
[affaeee]108#if OS_WIN32
[0dab87a]109   {"pause", no_argument, 0, 2},
[647407d]110#endif
[bb90203]111   {"help", no_argument, 0, HLP_HELP},
112   {"version", no_argument, 0, HLP_VERSION},
113   {0, 0, 0, 0}
114};
115
[4833448]116#define short_opts "pao:qsv:wz:"
[bb90203]117
118static struct help_msg help[] = {
119/*                              <-- */
[736f7df]120   /* TRANSLATORS: --help output for cavern --output option */
[45af761]121   {HLP_ENCODELONG(2),        /*set location for output files*/162, 0},
[736f7df]122   /* TRANSLATORS: --help output for cavern --quiet option */
[45af761]123   {HLP_ENCODELONG(3),        /*only show brief summary (-qq for errors only)*/163, 0},
[736f7df]124   /* TRANSLATORS: --help output for cavern --no-auxiliary-files option */
[45af761]125   {HLP_ENCODELONG(4),        /*do not create .err file*/164, 0},
[736f7df]126   /* TRANSLATORS: --help output for cavern --warnings-are-errors option */
[45af761]127   {HLP_ENCODELONG(5),        /*turn warnings into errors*/165, 0},
[736f7df]128   /* TRANSLATORS: --help output for cavern --log option */
[45af761]129   {HLP_ENCODELONG(6),        /*log output to .log file*/170, 0},
[736f7df]130   /* TRANSLATORS: --help output for cavern --3d-version option */
[45af761]131   {HLP_ENCODELONG(7),        /*specify the 3d file format version to output*/171, 0},
[a4ae909]132 /*{'z',                        "set optimizations for network reduction"},*/
[45af761]133   {0, 0, 0}
[bb90203]134};
135
[5b68ae1]136/* atexit functions */
[25ab06b]137static void
138delete_output_on_error(void)
139{
[bb9d869]140   if (msg_errors || (f_warnings_are_errors && msg_warnings))
141      filename_delete_output();
[25ab06b]142}
143
[affaeee]144#if OS_WIN32
[5b68ae1]145static void
146pause_on_exit(void)
147{
148   while (_kbhit()) _getch();
149   _getch();
150}
151#endif
152
[1ee204e]153int current_days_since_1900;
[e0c7cd1]154
[b39e24a]155static void discarding_proj_logger(void *ctx, int level, const char *message) {
156    (void)ctx;
157    (void)level;
158    (void)message;
159}
160
[bb90203]161extern CDECL int
162main(int argc, char **argv)
163{
164   int d;
[1ee204e]165   time_t tmUserStart = time(NULL);
166   clock_t tmCPUStart = clock();
167   {
[eafe1a3]168       /* FIXME: localtime? */
[1ee204e]169       struct tm * t = localtime(&tmUserStart);
170       int y = t->tm_year + 1900;
171       current_days_since_1900 = days_since_1900(y, t->tm_mon + 1, t->tm_mday);
172   }
[b88b171]173
174   /* Always buffer by line for aven's benefit. */
175   setvbuf(stdout, NULL, _IOLBF, 0);
176
[b39e24a]177   /* Prevent stderr spew from PROJ. */
178   proj_log_func(PJ_DEFAULT_CTX, NULL, discarding_proj_logger);
179
[bdfe97f]180   msg_init(argv);
[bb90203]181
182   pcs = osnew(settings);
183   pcs->next = NULL;
184   pcs->Translate = ((short*) osmalloc(ossizeof(short) * 257)) + 1;
[b5a3219]185   pcs->meta = NULL;
[b39e24a]186   pcs->proj_str = NULL;
[95b0f1d]187   pcs->declination = HUGE_REAL;
[4bea0f8]188   pcs->convergence = HUGE_REAL;
[37d6b84]189   pcs->dec_filename = NULL;
190   pcs->dec_line = 0;
191   pcs->dec_context = NULL;
192   pcs->dec_lat = HUGE_VAL;
193   pcs->dec_lon = HUGE_VAL;
194   pcs->dec_alt = HUGE_VAL;
195   pcs->min_declination = HUGE_VAL;
196   pcs->max_declination = -HUGE_VAL;
[bb90203]197
198   /* Set up root of prefix hierarchy */
199   root = osnew(prefix);
200   root->up = root->right = root->down = NULL;
201   root->stn = NULL;
[421b7d2]202   root->pos = NULL;
[ff6cfe1]203   root->ident = NULL;
[932f7e9]204   root->min_export = root->max_export = 0;
[95c3272]205   root->sflags = BIT(SFLAGS_SURVEY);
[016068a]206   root->filename = NULL;
[647407d]207
208   nosurveyhead = NULL;
[bb90203]209
210   stnlist = NULL;
211   cLegs = cStns = cComponents = 0;
212   totadj = total = totplan = totvert = 0.0;
213
[0590246]214   for (d = 0; d < 6; d++) {
[fa42426]215      min[d] = HUGE_REAL;
216      max[d] = -HUGE_REAL;
[bb90203]217      pfxHi[d] = pfxLo[d] = NULL;
218   }
219
[d06141c]220   /* at least one argument must be given */
[b85e20f]221   cmdline_init(argc, argv, short_opts, long_opts, NULL, help, 1, -1);
[bb90203]222   while (1) {
[b85e20f]223      int opt = cmdline_getopt();
[bb90203]224      if (opt == EOF) break;
225      switch (opt) {
226       case 'p':
[21904d3]227         /* Ignore for compatibility with older versions. */
[b4fe9fb]228         break;
[8e8057c]229       case 'o': {
[9206096]230         osfree(fnm_output_base); /* in case of multiple -o options */
[8e8057c]231         /* can be a directory (in which case use basename of leaf input)
232          * or a file (in which case just trim the extension off) */
233         if (fDirectory(optarg)) {
234            /* this is a little tricky - we need to note the path here,
235             * and then add the leaf later on (in datain.c) */
236            fnm_output_base = base_from_fnm(optarg);
237            fnm_output_base_is_dir = 1;
238         } else {
239            fnm_output_base = base_from_fnm(optarg);
240         }
241         break;
242       }
[647407d]243       case 'q':
244         if (fQuiet) fMute = 1;
245         fQuiet = 1;
246         break;
247       case 's':
248         fSuppress = 1;
249         break;
[4833448]250       case 'v': {
251         int v = atoi(optarg);
252         if (v < IMG_VERSION_MIN || v > IMG_VERSION_MAX)
253            fatalerror(/*3d file format versions %d to %d supported*/88,
254                       IMG_VERSION_MIN, IMG_VERSION_MAX);
255         img_output_version = v;
256         break;
257       }
[bb9d869]258       case 'w':
259         f_warnings_are_errors = 1;
260         break;
[8e8057c]261       case 'z': {
262         /* Control which network optimisations are used (development tool) */
263         static int first_opt_z = 1;
[eb18f4d]264         char c;
[8e8057c]265         if (first_opt_z) {
[bb90203]266            optimize = 0;
[8e8057c]267            first_opt_z = 0;
[bb90203]268         }
[c50391b8]269         /* Lollipops, Parallel legs, Iterate mx, Delta* */
[eb18f4d]270         while ((c = *optarg++) != '\0')
[0580c6a]271            if (islower((unsigned char)c)) optimize |= BITA(c);
[0dab87a]272         break;
273       case 1:
274         fLog = fTrue;
[bb90203]275         break;
[affaeee]276#if OS_WIN32
[0dab87a]277       case 2:
[5b68ae1]278         atexit(pause_on_exit);
279         break;
280#endif
[bb90203]281       }
282      }
283   }
284
[0dab87a]285   if (fLog) {
286      char *fnm;
[9887ea01]287      if (!fnm_output_base) {
288         char *p;
289         p = baseleaf_from_fnm(argv[optind]);
[0156ccfc]290         fnm = add_ext(p, EXT_LOG);
[421b7d2]291         osfree(p);
[9887ea01]292      } else if (fnm_output_base_is_dir) {
[09e8f4c]293         char *p;
294         fnm = baseleaf_from_fnm(argv[optind]);
295         p = use_path(fnm_output_base, fnm);
296         osfree(fnm);
[0156ccfc]297         fnm = add_ext(p, EXT_LOG);
[09e8f4c]298         osfree(p);
299      } else {
[0156ccfc]300         fnm = add_ext(fnm_output_base, EXT_LOG);
[09e8f4c]301      }
[421b7d2]302
[0dab87a]303      if (!freopen(fnm, "w", stdout))
[0804fbe]304         fatalerror(/*Failed to open output file “%s”*/47, fnm);
[0dab87a]305
306      osfree(fnm);
307   }
308
[facdf7a]309   if (!fMute) {
310      const char *p = COPYRIGHT_MSG;
311      puts(PRETTYPACKAGE" "VERSION);
312      while (1) {
313          const char *q = p;
314          p = strstr(p, "(C)");
315          if (p == NULL) {
316              puts(q);
317              break;
318          }
319          fwrite(q, 1, p - q, stdout);
[ee7511a]320          fputs(msg(/*©*/0), stdout);
[facdf7a]321          p += 3;
322      }
323   }
[bb90203]324
[25ab06b]325   atexit(delete_output_on_error);
326
[bb90203]327   /* end of options, now process data files */
328   while (argv[optind]) {
329      const char *fnm = argv[optind];
330
331      if (!fExplicitTitle) {
332         char *lf;
[8e8057c]333         lf = baseleaf_from_fnm(fnm);
[bb90203]334         if (survey_title) s_catchar(&survey_title, &survey_title_len, ' ');
335         s_cat(&survey_title, &survey_title_len, lf);
336         osfree(lf);
337      }
338
339      /* Select defaults settings */
340      default_all(pcs);
[f4b609d]341      data_file(NULL, fnm); /* first argument is current path */
[cb3d1e2]342
[bb90203]343      optind++;
344   }
[cb3d1e2]345
[bb90203]346   validate();
347
[37d6b84]348   report_declination(pcs);
349
[bb90203]350   solve_network(/*stnlist*/); /* Find coordinates of all points */
351   validate();
[421b7d2]352
[a4ae909]353   /* close .3d file */
354   if (!img_close(pimg)) {
355      char *fnm = add_ext(fnm_output_base, EXT_SVX_3D);
[a405bc1]356      fatalerror(img_error2msg(img_error()), fnm);
[647407d]357   }
[7104f16]358   if (fhErrStat) safe_fclose(fhErrStat);
[bb90203]359
360   out_current_action(msg(/*Calculating statistics*/120));
[2b6eca8]361   if (!fMute) do_stats();
[647407d]362   if (!fQuiet) {
[f03053a7]363      /* clock() typically wraps after 72 minutes, but there doesn't seem
364       * to be a better way.  Still 72 minutes means some cave!
[be97baf]365       * We detect if clock() could have wrapped and suppress CPU time
366       * printing in this case.
[f03053a7]367       */
[be97baf]368      double tmUser = difftime(time(NULL), tmUserStart);
369      double tmCPU;
370      clock_t now = clock();
371#define CLOCK_T_WRAP \
372        (sizeof(clock_t)<sizeof(long)?(1ul << (CHAR_BIT * sizeof(clock_t))):0)
373      tmCPU = (now - (unsigned long)tmCPUStart)
374         / (double)CLOCKS_PER_SEC;
375      if (now < tmCPUStart)
376         tmCPU += CLOCK_T_WRAP / (double)CLOCKS_PER_SEC;
377      if (tmUser >= tmCPU + CLOCK_T_WRAP / (double)CLOCKS_PER_SEC)
378         tmCPU = 0;
[647407d]379
[27b8b59]380      /* tmUser is integer, tmCPU not - equivalent to (ceil(tmCPU) >= tmUser) */
[647407d]381      if (tmCPU + 1 > tmUser) {
[421b7d2]382         printf(msg(/*CPU time used %5.2fs*/140), tmCPU);
[647407d]383      } else if (tmCPU == 0) {
[27b8b59]384         if (tmUser != 0.0) {
[421b7d2]385            printf(msg(/*Time used %5.2fs*/141), tmUser);
[647407d]386         } else {
[421b7d2]387            fputs(msg(/*Time used unavailable*/142), stdout);
[647407d]388         }
[bb90203]389      } else {
[5b68ae1]390         printf(msg(/*Time used %5.2fs (%5.2fs CPU time)*/143), tmUser, tmCPU);
[bb90203]391      }
[5b68ae1]392      putnl();
[647407d]393   }
[25ab06b]394   if (msg_warnings || msg_errors) {
[7ebee5b]395      if (msg_errors || (f_warnings_are_errors && msg_warnings)) {
[5311876]396         printf(msg(/*There were %d warning(s) and %d error(s) - no output files produced.*/113),
[7ebee5b]397                msg_warnings, msg_errors);
398         putnl();
399         return EXIT_FAILURE;
400      }
401      printf(msg(/*There were %d warning(s).*/16), msg_warnings);
[25ab06b]402      putnl();
403   }
[bb9d869]404   return EXIT_SUCCESS;
[bb90203]405}
406
407static void
[85c0078]408do_range(int d, int msgno, real length_factor, const char * units)
[bb90203]409{
[1a21d04]410   if (d < 3) {
411      /* If the bound including anonymous stations is at an anonymous station
412       * but the bound only considering named stations is the same, use the
413       * named station for the anonymous bound too.
414       */
415      if (TSTBIT(pfxHi[d]->sflags, SFLAGS_ANON) && max[d] == max[d + 3]) {
416         pfxHi[d] = pfxHi[d + 3];
417      }
418      if (TSTBIT(pfxLo[d]->sflags, SFLAGS_ANON) && min[d] == min[d + 3]) {
419         pfxLo[d] = pfxLo[d + 3];
420      }
421   }
422
[ccc815d5]423   /* sprint_prefix uses a single buffer, so to report two stations in one
424    * message we need to make a temporary copy of the string for one of them.
425    */
[aecd032]426   char * pfx_hi = osstrdup(sprint_prefix(pfxHi[d]));
427   char * pfx_lo = sprint_prefix(pfxLo[d]);
[85c0078]428   real hi = max[d] * length_factor;
429   real lo = min[d] * length_factor;
430   printf(msg(msgno), hi - lo, units, pfx_hi, hi, units, pfx_lo, lo, units);
[aecd032]431   osfree(pfx_hi);
[2b6eca8]432   putnl();
[dfac588]433
[378112a]434   /* Range without anonymous stations at offset 3. */
[dfac588]435   if (d < 3 && (pfxHi[d] != pfxHi[d + 3] || pfxLo[d] != pfxLo[d + 3])) {
436      do_range(d + 3, msgno, length_factor, units);
437   }
[bb90203]438}
439
440static void
[06a871f]441do_stats(void)
[bb90203]442{
443   long cLoops = cComponents + cLegs - cStns;
[85c0078]444   int length_units = get_length_units(Q_LENGTH);
445   const char * units = get_units_string(length_units);
446   real length_factor = 1.0 / get_units_factor(length_units);
[bb90203]447
[2b6eca8]448   putnl();
[bb90203]449
[a63fdd2a]450   if (cStns == 1) {
[2b6eca8]451      fputs(msg(/*Survey contains 1 survey station,*/172), stdout);
[a63fdd2a]452   } else {
[2b6eca8]453      printf(msg(/*Survey contains %ld survey stations,*/173), cStns);
[a63fdd2a]454   }
[bb90203]455
[a63fdd2a]456   if (cLegs == 1) {
[2b6eca8]457      fputs(msg(/* joined by 1 leg.*/174), stdout);
[a63fdd2a]458   } else {
[2b6eca8]459      printf(msg(/* joined by %ld legs.*/175), cLegs);
[a63fdd2a]460   }
[bb90203]461
[2b6eca8]462   putnl();
[bb90203]463
[a63fdd2a]464   if (cLoops == 1) {
[2b6eca8]465      fputs(msg(/*There is 1 loop.*/138), stdout);
[a63fdd2a]466   } else {
[2b6eca8]467      printf(msg(/*There are %ld loops.*/139), cLoops);
[a63fdd2a]468   }
[bb90203]469
[2b6eca8]470   putnl();
[bb90203]471
472   if (cComponents != 1) {
[736f7df]473      /* TRANSLATORS: "Connected component" in the graph theory sense - it
474       * means there are %ld bits of survey with no connections between them.
475       * This message is only used if there are more than 1. */
[2b6eca8]476      printf(msg(/*Survey has %ld connected components.*/178), cComponents);
[a63fdd2a]477      putnl();
478   }
[bb90203]479
[85c0078]480   printf(msg(/*Total length of survey legs = %7.2f%s (%7.2f%s adjusted)*/132),
481          total * length_factor, units, totadj * length_factor, units);
[2b6eca8]482   putnl();
[85c0078]483   printf(msg(/*Total plan length of survey legs = %7.2f%s*/133),
484          totplan * length_factor, units);
[2b6eca8]485   putnl();
[85c0078]486   printf(msg(/*Total vertical length of survey legs = %7.2f%s*/134),
487          totvert * length_factor, units);
[2b6eca8]488   putnl();
489
[4ae2ea4]490   /* If there's no underground survey, we've no ranges */
491   if (pfxHi[0]) {
[736f7df]492      /* TRANSLATORS: numbers are altitudes of highest and lowest stations */
[85c0078]493      do_range(2, /*Vertical range = %4.2f%s (from %s at %4.2f%s to %s at %4.2f%s)*/135,
494               length_factor, units);
[736f7df]495      /* TRANSLATORS: c.f. previous message */
[85c0078]496      do_range(1, /*North-South range = %4.2f%s (from %s at %4.2f%s to %s at %4.2f%s)*/136,
497               length_factor, units);
[736f7df]498      /* TRANSLATORS: c.f. previous two messages */
[85c0078]499      do_range(0, /*East-West range = %4.2f%s (from %s at %4.2f%s to %s at %4.2f%s)*/137,
500               length_factor, units);
[4ae2ea4]501   }
[bb90203]502
[2b6eca8]503   print_node_stats();
[bb90203]504   /* Also, could give:
505    *  # nodes stations (ie have other than two references or are fixed)
506    *  # fixed stations (list of?)
507    */
508}
Note: See TracBrowser for help on using the repository browser.