source: git/src/cavern.c @ cfef352

stereo-2025
Last change on this file since cfef352 was cfef352, checked in by Olly Betts <olly@…>, 12 months ago

Only warn about hanging surveys

This has been an error previously, but at least for Walls data it seems
having hanging surveys is the norm, and Walls itself only warns about
them.

It seems we might as well support this for native Survex data too as
it allows viewing the connected parts of a survey with missing
connections without having to comment out the unconnected parts (and
then remember to fully uncomment once connections are surveyed).

If there are hanging surveys we may need to adjust the component
count, but don't currently so these statistics may be wrong so
for now we just suppress reporting them if there are hanging surveys.

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