source: git/src/cavern.c @ 8d74d05

stereo-2025
Last change on this file since 8d74d05 was c77682a, checked in by Olly Betts <olly@…>, 4 months ago

Stop reporting node stats

These are kind of interesting, but since the advent of surveying with
Disto-X and similar devices which make it quick to shoot multiple
splay legs from each station the number of larger order nodes has
increased and this information is now quite verbose and any utility
it had has substantially declined.

If they're still wanted they could make a reappearance in the future in
aven, with splays excluded when counting the number of legs at each
station.

Fixes #86, reported by Wookey.

  • Property mode set to 100644
File size: 16.6 KB
Line 
1/* cavern.c
2 * SURVEX Cave surveying software: data reduction main and related functions
3 * Copyright (C) 1991-2024 Olly Betts
4 *
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.
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
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
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
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18 */
19
20#include <config.h>
21
22#define MSG_SETUP_PROJ_SEARCH_PATH 1
23
24#include <limits.h>
25#include <stdlib.h>
26#include <time.h>
27
28#include "cavern.h"
29#include "cmdline.h"
30#include "commands.h"
31#include "date.h"
32#include "datain.h"
33#include "debug.h"
34#include "message.h"
35#include "filename.h"
36#include "filelist.h"
37#include "img_hosted.h"
38#include "listpos.h"
39#include "netbits.h"
40#include "netskel.h"
41#include "out.h"
42#include "str.h"
43#include "validate.h"
44
45#ifdef _WIN32
46# include <conio.h> /* for _kbhit() and _getch() */
47#endif
48
49/* Globals */
50node *fixedlist = NULL; // Fixed points
51node *stnlist = NULL; // Unfixed stations
52settings *pcs;
53prefix *root;
54prefix *anon_list = NULL;
55long cLegs = 0, cStns = 0;
56long cComponents = 0;
57long cSolves = 0;
58bool fExportUsed = false;
59char * proj_str_out = NULL;
60PJ * pj_cached = NULL;
61
62FILE *fhErrStat = NULL;
63img *pimg = NULL;
64bool fQuiet = false; /* just show brief summary + errors */
65bool fMute = false; /* just show errors */
66bool fSuppress = false; /* only output 3d file */
67static bool fLog = false; /* stdout to .log file */
68static bool f_warnings_are_errors = false; /* turn warnings into errors */
69
70nosurveylink *nosurveyhead;
71
72real totadj, total, totplan, totvert;
73real min[9], max[9];
74prefix *pfxHi[9], *pfxLo[9];
75
76string survey_title = S_INIT;
77
78bool fExplicitTitle = false;
79
80char *fnm_output_base = NULL;
81int fnm_output_base_is_dir = 0;
82
83lrudlist * model = NULL;
84lrud ** next_lrud = NULL;
85
86char output_separator = '.';
87
88static void do_stats(void);
89
90static const struct option long_opts[] = {
91   /* const char *name; int has_arg (0 no_argument, 1 required_*, 2 optional_*); int *flag; int val; */
92   {"percentage", no_argument, 0, 'p'},
93   /* Ignore for compatibility with older versions. */
94   {"no-percentage", no_argument, 0, 0},
95   {"output", required_argument, 0, 'o'},
96   {"quiet", no_argument, 0, 'q'},
97   {"no-auxiliary-files", no_argument, 0, 's'},
98   {"warnings-are-errors", no_argument, 0, 'w'},
99   {"log", no_argument, 0, 1},
100   {"3d-version", required_argument, 0, 'v'},
101#ifdef _WIN32
102   {"pause", no_argument, 0, 2},
103#endif
104   {"help", no_argument, 0, HLP_HELP},
105   {"version", no_argument, 0, HLP_VERSION},
106   {0, 0, 0, 0}
107};
108
109#define short_opts "pao:qsv:wz:"
110
111static struct help_msg help[] = {
112/*                              <-- */
113   /* TRANSLATORS: --help output for cavern --output option */
114   {HLP_ENCODELONG(2),        /*set location for output files*/162, 0, 0},
115   /* TRANSLATORS: --help output for cavern --quiet option */
116   {HLP_ENCODELONG(3),        /*only show brief summary (-qq for errors only)*/163, 0, 0},
117   /* TRANSLATORS: --help output for cavern --no-auxiliary-files option */
118   {HLP_ENCODELONG(4),        /*do not create .err file*/164, 0, 0},
119   /* TRANSLATORS: --help output for cavern --warnings-are-errors option */
120   {HLP_ENCODELONG(5),        /*turn warnings into errors*/165, 0, 0},
121   /* TRANSLATORS: --help output for cavern --log option */
122   {HLP_ENCODELONG(6),        /*log output to .log file*/170, 0, 0},
123   /* TRANSLATORS: --help output for cavern --3d-version option */
124   {HLP_ENCODELONG(7),        /*specify the 3d file format version to output*/171, 0, 0},
125 /*{'z',                        "set optimizations for network reduction"},*/
126   {0, 0, 0, 0}
127};
128
129/* atexit functions */
130static void
131delete_output_on_error(void)
132{
133   if (msg_errors || (f_warnings_are_errors && msg_warnings))
134      filename_delete_output();
135}
136
137#ifdef _WIN32
138static void
139pause_on_exit(void)
140{
141   while (_kbhit()) _getch();
142   _getch();
143}
144#endif
145
146int current_days_since_1900;
147
148static void discarding_proj_logger(void *ctx, int level, const char *message) {
149    (void)ctx;
150    (void)level;
151    (void)message;
152}
153
154extern int
155main(int argc, char **argv)
156{
157   int d;
158   time_t tmUserStart = time(NULL);
159   clock_t tmCPUStart = clock();
160   {
161       /* FIXME: localtime? */
162       struct tm * t = localtime(&tmUserStart);
163       int y = t->tm_year + 1900;
164       current_days_since_1900 = days_since_1900(y, t->tm_mon + 1, t->tm_mday);
165   }
166
167   /* Always buffer by line for aven's benefit. */
168   setvbuf(stdout, NULL, _IOLBF, 0);
169
170   /* Prevent stderr spew from PROJ. */
171   proj_log_func(PJ_DEFAULT_CTX, NULL, discarding_proj_logger);
172
173   msg_init(argv);
174
175   pcs = osnew(settings);
176   pcs->next = NULL;
177   pcs->from_equals_to_is_only_a_warning = false;
178   pcs->Translate = ((short*) osmalloc(ossizeof(short) * 257)) + 1;
179   pcs->meta = NULL;
180   pcs->proj_str = NULL;
181   pcs->declination = HUGE_REAL;
182   pcs->convergence = HUGE_REAL;
183   pcs->input_convergence = HUGE_REAL;
184   pcs->dec_filename = NULL;
185   pcs->dec_line = 0;
186   pcs->dec_context = NULL;
187   pcs->dec_lat = HUGE_VAL;
188   pcs->dec_lon = HUGE_VAL;
189   pcs->dec_alt = HUGE_VAL;
190   pcs->min_declination = HUGE_VAL;
191   pcs->max_declination = -HUGE_VAL;
192   pcs->cartesian_north = TRUE_NORTH;
193   pcs->cartesian_rotation = 0.0;
194
195   /* Set up root of prefix hierarchy */
196   root = osnew(prefix);
197   root->up = root->right = root->down = NULL;
198   root->stn = NULL;
199   root->pos = NULL;
200   root->ident.p = NULL;
201   root->min_export = root->max_export = 0;
202   root->sflags = BIT(SFLAGS_SURVEY);
203   root->filename = NULL;
204
205   nosurveyhead = NULL;
206
207   fixedlist = NULL;
208   stnlist = NULL;
209   totadj = total = totplan = totvert = 0.0;
210
211   for (d = 0; d < 9; d++) {
212      min[d] = HUGE_REAL;
213      max[d] = -HUGE_REAL;
214      pfxHi[d] = pfxLo[d] = NULL;
215   }
216
217   // TRANSLATORS: Here "survey" is a "cave map" rather than list of questions
218   // - it should be translated to the terminology that cavers using the
219   // language would use.
220   //
221   // Part of cavern --help
222   cmdline_set_syntax_message(/*[SURVEY_DATA_FILE]*/507, 0, NULL);
223   /* at least one argument must be given */
224   cmdline_init(argc, argv, short_opts, long_opts, NULL, help, 1, -1);
225   while (1) {
226      int opt = cmdline_getopt();
227      if (opt == EOF) break;
228      switch (opt) {
229       case 'p':
230         /* Ignore for compatibility with older versions. */
231         break;
232       case 'o': {
233         osfree(fnm_output_base); /* in case of multiple -o options */
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       }
246       case 'q':
247         if (fQuiet) fMute = 1;
248         fQuiet = 1;
249         break;
250       case 's':
251         fSuppress = 1;
252         break;
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       }
261       case 'w':
262         f_warnings_are_errors = 1;
263         break;
264       case 'z': {
265         /* Control which network optimisations are used (development tool) */
266         static int first_opt_z = 1;
267         char c;
268         if (first_opt_z) {
269            optimize = 0;
270            first_opt_z = 0;
271         }
272         /* Lollipops, Parallel legs, Iterate mx, Delta* */
273         while ((c = *optarg++) != '\0')
274            if (islower((unsigned char)c)) optimize |= BITA(c);
275         break;
276       case 1:
277         fLog = true;
278         break;
279#ifdef _WIN32
280       case 2:
281         atexit(pause_on_exit);
282         break;
283#endif
284       }
285      }
286   }
287
288   if (fLog) {
289      char *fnm;
290      if (!fnm_output_base) {
291         char *p;
292         p = baseleaf_from_fnm(argv[optind]);
293         fnm = add_ext(p, EXT_LOG);
294         osfree(p);
295      } else if (fnm_output_base_is_dir) {
296         char *p;
297         fnm = baseleaf_from_fnm(argv[optind]);
298         p = use_path(fnm_output_base, fnm);
299         osfree(fnm);
300         fnm = add_ext(p, EXT_LOG);
301         osfree(p);
302      } else {
303         fnm = add_ext(fnm_output_base, EXT_LOG);
304      }
305
306      if (!freopen(fnm, "w", stdout))
307         fatalerror(/*Failed to open output file “%s”*/47, fnm);
308
309      osfree(fnm);
310   }
311
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);
323          fputs(msg(/*©*/0), stdout);
324          p += 3;
325      }
326   }
327
328   atexit(delete_output_on_error);
329
330   /* end of options, now process data files */
331   while (argv[optind]) {
332      const char *fnm = argv[optind];
333
334      if (!fExplicitTitle) {
335          char *lf = baseleaf_from_fnm(fnm);
336          if (s_empty(&survey_title)) {
337              s_donate(&survey_title, lf);
338          } else {
339              s_appendch(&survey_title, ' ');
340              s_append(&survey_title, lf);
341              osfree(lf);
342          }
343      }
344
345      /* Select defaults settings */
346      default_all(pcs);
347      data_file(NULL, fnm); /* first argument is current path */
348
349      optind++;
350   }
351
352   validate();
353
354   report_declination(pcs);
355
356   solve_network(); /* Find coordinates of all points */
357   validate();
358
359   check_for_unused_fixed_points();
360
361   /* close .3d file */
362   if (!img_close(pimg)) {
363      char *fnm = add_ext(fnm_output_base, EXT_SVX_3D);
364      fatalerror(img_error2msg(img_error()), fnm);
365   }
366   if (fhErrStat) safe_fclose(fhErrStat);
367
368   out_current_action(msg(/*Calculating statistics*/120));
369   if (!fMute) do_stats();
370   if (!fQuiet) {
371      /* clock() typically wraps after 72 minutes, but there doesn't seem
372       * to be a better way.  Still 72 minutes means some cave!
373       * We detect if clock() could have wrapped and suppress CPU time
374       * printing in this case.
375       */
376      double tmUser = difftime(time(NULL), tmUserStart);
377      double tmCPU;
378      clock_t now = clock();
379#define CLOCK_T_WRAP \
380        (sizeof(clock_t)<sizeof(long)?(1ul << (CHAR_BIT * sizeof(clock_t))):0)
381      tmCPU = (now - (unsigned long)tmCPUStart)
382         / (double)CLOCKS_PER_SEC;
383      if (now < tmCPUStart)
384         tmCPU += CLOCK_T_WRAP / (double)CLOCKS_PER_SEC;
385      if (tmUser >= tmCPU + CLOCK_T_WRAP / (double)CLOCKS_PER_SEC)
386         tmCPU = 0;
387
388      /* tmUser is integer, tmCPU not - equivalent to (ceil(tmCPU) >= tmUser) */
389      if (tmCPU + 1 > tmUser) {
390         printf(msg(/*CPU time used %5.2fs*/140), tmCPU);
391      } else if (tmCPU == 0) {
392         if (tmUser != 0.0) {
393            printf(msg(/*Time used %5.2fs*/141), tmUser);
394         } else {
395            fputs(msg(/*Time used unavailable*/142), stdout);
396         }
397      } else {
398         printf(msg(/*Time used %5.2fs (%5.2fs CPU time)*/143), tmUser, tmCPU);
399      }
400      putnl();
401   }
402   if (msg_warnings || msg_errors) {
403      putnl();
404      if (msg_errors || (f_warnings_are_errors && msg_warnings)) {
405         printf(msg(/*There were %d warning(s) and %d error(s) - no output files produced.*/113),
406                msg_warnings, msg_errors);
407         putnl();
408         return EXIT_FAILURE;
409      }
410      printf(msg(/*There were %d warning(s).*/16), msg_warnings);
411      putnl();
412   }
413   return EXIT_SUCCESS;
414}
415
416static void
417do_range(int d, int msgno, real length_factor, const char * units)
418{
419   if (d < 3) {
420      /* If the bound including anonymous stations is at an anonymous station
421       * but the bound only considering named stations is the same, use the
422       * named station for the anonymous bound too.
423       */
424      if (TSTBIT(pfxHi[d]->sflags, SFLAGS_ANON) && max[d] == max[d + 3]) {
425         pfxHi[d] = pfxHi[d + 3];
426      }
427      if (TSTBIT(pfxLo[d]->sflags, SFLAGS_ANON) && min[d] == min[d + 3]) {
428         pfxLo[d] = pfxLo[d + 3];
429      }
430   }
431
432   /* sprint_prefix uses a single buffer, so to report two stations in one
433    * message we need to make a temporary copy of the string for one of them.
434    */
435   char * pfx_hi = osstrdup(sprint_prefix(pfxHi[d]));
436   char * pfx_lo = sprint_prefix(pfxLo[d]);
437   real hi = max[d] * length_factor;
438   real lo = min[d] * length_factor;
439   printf(msg(msgno), hi - lo, units, pfx_hi, hi, units, pfx_lo, lo, units);
440   osfree(pfx_hi);
441   putnl();
442
443   /* Range without anonymous stations at offset 3. */
444   if (d < 3 && (pfxHi[d] != pfxHi[d + 3] || pfxLo[d] != pfxLo[d + 3])) {
445      do_range(d + 3, msgno, length_factor, units);
446   }
447}
448
449static void
450do_stats(void)
451{
452   putnl();
453
454   if (proj_str_out) {
455       prefix *name_min = NULL;
456       prefix *name_max = NULL;
457       double convergence_min = HUGE_VAL;
458       double convergence_max = -HUGE_VAL;
459       prefix **pfx = pfxLo;
460       for (int bound = 0; bound < 2; ++bound) {
461           for (int d = 6; d < 9; ++d) {
462               if (pfx[d]) {
463                   pos *p = pfx[d]->pos;
464                   double convergence = calculate_convergence_xy(proj_str_out,
465                                                                 p->p[0],
466                                                                 p->p[1],
467                                                                 p->p[2]);
468                   if (convergence < convergence_min) {
469                       convergence_min = convergence;
470                       name_min = pfx[d];
471                   }
472                   if (convergence > convergence_max) {
473                       convergence_max = convergence;
474                       name_max = pfx[d];
475                   }
476               }
477           }
478           pfx = pfxHi;
479       }
480       if (name_min && name_max) {
481           const char* deg_sign = msg(/*°*/344);
482           /* sprint_prefix uses a single buffer, so to report two stations in
483            * one message we need to make a temporary copy of the string for
484            * one of them.
485            */
486           char *pfx_hi = osstrdup(sprint_prefix(name_max));
487           char *pfx_lo = sprint_prefix(name_min);
488           // TRANSLATORS: Cavern computes the grid convergence at the
489           // representative location(s) specified by the
490           // `*declination auto` command(s).  The convergence values
491           // for the most N, S, E and W survey stations with legs
492           // attached are also computed and the range of these values
493           // is reported in this message.  It's approximate because the
494           // min or max convergence could actually be beyond this range
495           // but it's unlikely to be very wrong.
496           //
497           // Each %.1f%s will be replaced with a convergence angle (e.g.
498           // 0.9°) and the following %s with the station name where that
499           // convergence angle was computed.
500           printf(msg(/*Approximate full range of grid convergence: %.1f%s at %s to %.1f%s at %s\n*/531),
501                  deg(convergence_min), deg_sign, pfx_lo,
502                  deg(convergence_max), deg_sign, pfx_hi);
503           osfree(pfx_hi);
504       }
505   }
506
507   if (cStns == 1) {
508      fputs(msg(/*Survey contains 1 survey station,*/172), stdout);
509   } else {
510      printf(msg(/*Survey contains %ld survey stations,*/173), cStns);
511   }
512
513   if (cLegs == 1) {
514      fputs(msg(/* joined by 1 leg.*/174), stdout);
515   } else {
516      printf(msg(/* joined by %ld legs.*/175), cLegs);
517   }
518   putnl();
519
520   if (cSolves == 1) {
521       // If *solve is used then cComponents will often be wrong.  Rather than
522       // reporting incorrect counts of loops and components we omit these in
523       // this case for now.
524       long cLoops = cComponents + cLegs - cStns;
525       if (cLoops == 1) {
526          fputs(msg(/*There is 1 loop.*/138), stdout);
527       } else {
528          printf(msg(/*There are %ld loops.*/139), cLoops);
529       }
530       putnl();
531
532       if (cComponents != 1) {
533          /* TRANSLATORS: "Connected component" in the graph theory sense - it
534           * means there are %ld bits of survey with no connections between
535           * them.  This message is only used if there are more than 1. */
536          printf(msg(/*Survey has %ld connected components.*/178), cComponents);
537          putnl();
538       }
539   }
540
541   int length_units = get_length_units(Q_LENGTH);
542   const char * units = get_units_string(length_units);
543   real length_factor = 1.0 / get_units_factor(length_units);
544
545   printf(msg(/*Total length of survey legs = %7.2f%s (%7.2f%s adjusted)*/132),
546          total * length_factor, units, totadj * length_factor, units);
547   putnl();
548   printf(msg(/*Total plan length of survey legs = %7.2f%s*/133),
549          totplan * length_factor, units);
550   putnl();
551   printf(msg(/*Total vertical length of survey legs = %7.2f%s*/134),
552          totvert * length_factor, units);
553   putnl();
554
555   /* If there's no underground survey, we've no ranges */
556   if (pfxHi[0]) {
557      /* TRANSLATORS: numbers are altitudes of highest and lowest stations */
558      do_range(2, /*Vertical range = %4.2f%s (from %s at %4.2f%s to %s at %4.2f%s)*/135,
559               length_factor, units);
560      /* TRANSLATORS: c.f. previous message */
561      do_range(1, /*North-South range = %4.2f%s (from %s at %4.2f%s to %s at %4.2f%s)*/136,
562               length_factor, units);
563      /* TRANSLATORS: c.f. previous two messages */
564      do_range(0, /*East-West range = %4.2f%s (from %s at %4.2f%s to %s at %4.2f%s)*/137,
565               length_factor, units);
566   }
567
568   check_node_stats();
569}
Note: See TracBrowser for help on using the repository browser.