source: git/src/cavern.c @ 03e1a95

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

Fix component counting bugs

The component count was wrong in some cases, and we calculate the number
of loops using this component count, so the loop count would be wrong by
the same amount in these cases.

  • Property mode set to 100644
File size: 14.8 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#include "whichos.h"
45
46#if OS_WIN32
47# include <conio.h> /* for _kbhit() and _getch() */
48#endif
49
50/* Globals */
51node *fixedlist = NULL; // Fixed points
52node *stnlist = NULL; // Unfixed stations
53settings *pcs;
54prefix *root;
55prefix *anon_list = NULL;
56long cLegs, cStns;
57long cComponents;
58bool hanging_surveys = false;
59bool fExportUsed = false;
60char * proj_str_out = NULL;
61PJ * pj_cached = NULL;
62
63FILE *fhErrStat = NULL;
64img *pimg = NULL;
65bool fQuiet = false; /* just show brief summary + errors */
66bool fMute = false; /* just show errors */
67bool fSuppress = false; /* only output 3d file */
68static bool fLog = false; /* stdout to .log file */
69static bool f_warnings_are_errors = false; /* turn warnings into errors */
70
71nosurveylink *nosurveyhead;
72
73real totadj, total, totplan, totvert;
74real min[6], max[6];
75prefix *pfxHi[6], *pfxLo[6];
76
77string survey_title = S_INIT;
78
79bool fExplicitTitle = false;
80
81char *fnm_output_base = NULL;
82int fnm_output_base_is_dir = 0;
83
84lrudlist * model = NULL;
85lrud ** next_lrud = NULL;
86
87char output_separator = '.';
88
89static void do_stats(void);
90
91static const struct option long_opts[] = {
92   /* const char *name; int has_arg (0 no_argument, 1 required_*, 2 optional_*); int *flag; int val; */
93   {"percentage", no_argument, 0, 'p'},
94   /* Ignore for compatibility with older versions. */
95   {"no-percentage", no_argument, 0, 0},
96   {"output", required_argument, 0, 'o'},
97   {"quiet", no_argument, 0, 'q'},
98   {"no-auxiliary-files", no_argument, 0, 's'},
99   {"warnings-are-errors", no_argument, 0, 'w'},
100   {"log", no_argument, 0, 1},
101   {"3d-version", required_argument, 0, 'v'},
102#if OS_WIN32
103   {"pause", no_argument, 0, 2},
104#endif
105   {"help", no_argument, 0, HLP_HELP},
106   {"version", no_argument, 0, HLP_VERSION},
107   {0, 0, 0, 0}
108};
109
110#define short_opts "pao:qsv:wz:"
111
112static struct help_msg help[] = {
113/*                              <-- */
114   /* TRANSLATORS: --help output for cavern --output option */
115   {HLP_ENCODELONG(2),        /*set location for output files*/162, 0},
116   /* TRANSLATORS: --help output for cavern --quiet option */
117   {HLP_ENCODELONG(3),        /*only show brief summary (-qq for errors only)*/163, 0},
118   /* TRANSLATORS: --help output for cavern --no-auxiliary-files option */
119   {HLP_ENCODELONG(4),        /*do not create .err file*/164, 0},
120   /* TRANSLATORS: --help output for cavern --warnings-are-errors option */
121   {HLP_ENCODELONG(5),        /*turn warnings into errors*/165, 0},
122   /* TRANSLATORS: --help output for cavern --log option */
123   {HLP_ENCODELONG(6),        /*log output to .log file*/170, 0},
124   /* TRANSLATORS: --help output for cavern --3d-version option */
125   {HLP_ENCODELONG(7),        /*specify the 3d file format version to output*/171, 0},
126 /*{'z',                        "set optimizations for network reduction"},*/
127   {0, 0, 0}
128};
129
130/* atexit functions */
131static void
132delete_output_on_error(void)
133{
134   if (msg_errors || (f_warnings_are_errors && msg_warnings))
135      filename_delete_output();
136}
137
138#if OS_WIN32
139static void
140pause_on_exit(void)
141{
142   while (_kbhit()) _getch();
143   _getch();
144}
145#endif
146
147int current_days_since_1900;
148
149static void discarding_proj_logger(void *ctx, int level, const char *message) {
150    (void)ctx;
151    (void)level;
152    (void)message;
153}
154
155extern int
156main(int argc, char **argv)
157{
158   int d;
159   time_t tmUserStart = time(NULL);
160   clock_t tmCPUStart = clock();
161   {
162       /* FIXME: localtime? */
163       struct tm * t = localtime(&tmUserStart);
164       int y = t->tm_year + 1900;
165       current_days_since_1900 = days_since_1900(y, t->tm_mon + 1, t->tm_mday);
166   }
167
168   /* Always buffer by line for aven's benefit. */
169   setvbuf(stdout, NULL, _IOLBF, 0);
170
171   /* Prevent stderr spew from PROJ. */
172   proj_log_func(PJ_DEFAULT_CTX, NULL, discarding_proj_logger);
173
174   msg_init(argv);
175
176   pcs = osnew(settings);
177   pcs->next = NULL;
178   pcs->from_equals_to_is_only_a_warning = false;
179   pcs->Translate = ((short*) osmalloc(ossizeof(short) * 257)) + 1;
180   pcs->meta = NULL;
181   pcs->proj_str = NULL;
182   pcs->declination = HUGE_REAL;
183   pcs->convergence = HUGE_REAL;
184   pcs->input_convergence = HUGE_REAL;
185   pcs->dec_filename = NULL;
186   pcs->dec_line = 0;
187   pcs->dec_context = NULL;
188   pcs->dec_lat = HUGE_VAL;
189   pcs->dec_lon = HUGE_VAL;
190   pcs->dec_alt = HUGE_VAL;
191   pcs->min_declination = HUGE_VAL;
192   pcs->max_declination = -HUGE_VAL;
193   pcs->cartesian_north = TRUE_NORTH;
194   pcs->cartesian_rotation = 0.0;
195
196   /* Set up root of prefix hierarchy */
197   root = osnew(prefix);
198   root->up = root->right = root->down = NULL;
199   root->stn = NULL;
200   root->pos = NULL;
201   root->ident.p = NULL;
202   root->min_export = root->max_export = 0;
203   root->sflags = BIT(SFLAGS_SURVEY);
204   root->filename = NULL;
205
206   nosurveyhead = NULL;
207
208   fixedlist = NULL;
209   stnlist = NULL;
210   cLegs = cStns = cComponents = 0;
211   totadj = total = totplan = totvert = 0.0;
212
213   for (d = 0; d < 6; d++) {
214      min[d] = HUGE_REAL;
215      max[d] = -HUGE_REAL;
216      pfxHi[d] = pfxLo[d] = NULL;
217   }
218
219   // TRANSLATORS: Here "survey" is a "cave map" rather than list of questions
220   // - it should be translated to the terminology that cavers using the
221   // language would use.
222   //
223   // Part of cavern --help
224   cmdline_set_syntax_message(/*[SURVEY_DATA_FILE]*/507, 0, NULL);
225   /* at least one argument must be given */
226   cmdline_init(argc, argv, short_opts, long_opts, NULL, help, 1, -1);
227   while (1) {
228      int opt = cmdline_getopt();
229      if (opt == EOF) break;
230      switch (opt) {
231       case 'p':
232         /* Ignore for compatibility with older versions. */
233         break;
234       case 'o': {
235         osfree(fnm_output_base); /* in case of multiple -o options */
236         /* can be a directory (in which case use basename of leaf input)
237          * or a file (in which case just trim the extension off) */
238         if (fDirectory(optarg)) {
239            /* this is a little tricky - we need to note the path here,
240             * and then add the leaf later on (in datain.c) */
241            fnm_output_base = base_from_fnm(optarg);
242            fnm_output_base_is_dir = 1;
243         } else {
244            fnm_output_base = base_from_fnm(optarg);
245         }
246         break;
247       }
248       case 'q':
249         if (fQuiet) fMute = 1;
250         fQuiet = 1;
251         break;
252       case 's':
253         fSuppress = 1;
254         break;
255       case 'v': {
256         int v = atoi(optarg);
257         if (v < IMG_VERSION_MIN || v > IMG_VERSION_MAX)
258            fatalerror(/*3d file format versions %d to %d supported*/88,
259                       IMG_VERSION_MIN, IMG_VERSION_MAX);
260         img_output_version = v;
261         break;
262       }
263       case 'w':
264         f_warnings_are_errors = 1;
265         break;
266       case 'z': {
267         /* Control which network optimisations are used (development tool) */
268         static int first_opt_z = 1;
269         char c;
270         if (first_opt_z) {
271            optimize = 0;
272            first_opt_z = 0;
273         }
274         /* Lollipops, Parallel legs, Iterate mx, Delta* */
275         while ((c = *optarg++) != '\0')
276            if (islower((unsigned char)c)) optimize |= BITA(c);
277         break;
278       case 1:
279         fLog = true;
280         break;
281#if OS_WIN32
282       case 2:
283         atexit(pause_on_exit);
284         break;
285#endif
286       }
287      }
288   }
289
290   if (fLog) {
291      char *fnm;
292      if (!fnm_output_base) {
293         char *p;
294         p = baseleaf_from_fnm(argv[optind]);
295         fnm = add_ext(p, EXT_LOG);
296         osfree(p);
297      } else if (fnm_output_base_is_dir) {
298         char *p;
299         fnm = baseleaf_from_fnm(argv[optind]);
300         p = use_path(fnm_output_base, fnm);
301         osfree(fnm);
302         fnm = add_ext(p, EXT_LOG);
303         osfree(p);
304      } else {
305         fnm = add_ext(fnm_output_base, EXT_LOG);
306      }
307
308      if (!freopen(fnm, "w", stdout))
309         fatalerror(/*Failed to open output file “%s”*/47, fnm);
310
311      osfree(fnm);
312   }
313
314   if (!fMute) {
315      const char *p = COPYRIGHT_MSG;
316      puts(PRETTYPACKAGE" "VERSION);
317      while (1) {
318          const char *q = p;
319          p = strstr(p, "(C)");
320          if (p == NULL) {
321              puts(q);
322              break;
323          }
324          fwrite(q, 1, p - q, stdout);
325          fputs(msg(/*©*/0), stdout);
326          p += 3;
327      }
328   }
329
330   atexit(delete_output_on_error);
331
332   /* end of options, now process data files */
333   while (argv[optind]) {
334      const char *fnm = argv[optind];
335
336      if (!fExplicitTitle) {
337          char *lf = baseleaf_from_fnm(fnm);
338          if (s_empty(&survey_title)) {
339              s_donate(&survey_title, lf);
340          } else {
341              s_appendch(&survey_title, ' ');
342              s_append(&survey_title, lf);
343              osfree(lf);
344          }
345      }
346
347      /* Select defaults settings */
348      default_all(pcs);
349      data_file(NULL, fnm); /* first argument is current path */
350
351      optind++;
352   }
353
354   validate();
355
356   report_declination(pcs);
357
358   solve_network(); /* Find coordinates of all points */
359   validate();
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      if (msg_errors || (f_warnings_are_errors && msg_warnings)) {
404         printf(msg(/*There were %d warning(s) and %d error(s) - no output files produced.*/113),
405                msg_warnings, msg_errors);
406         putnl();
407         return EXIT_FAILURE;
408      }
409      printf(msg(/*There were %d warning(s).*/16), msg_warnings);
410      putnl();
411   }
412   return EXIT_SUCCESS;
413}
414
415static void
416do_range(int d, int msgno, real length_factor, const char * units)
417{
418   if (d < 3) {
419      /* If the bound including anonymous stations is at an anonymous station
420       * but the bound only considering named stations is the same, use the
421       * named station for the anonymous bound too.
422       */
423      if (TSTBIT(pfxHi[d]->sflags, SFLAGS_ANON) && max[d] == max[d + 3]) {
424         pfxHi[d] = pfxHi[d + 3];
425      }
426      if (TSTBIT(pfxLo[d]->sflags, SFLAGS_ANON) && min[d] == min[d + 3]) {
427         pfxLo[d] = pfxLo[d + 3];
428      }
429   }
430
431   /* sprint_prefix uses a single buffer, so to report two stations in one
432    * message we need to make a temporary copy of the string for one of them.
433    */
434   char * pfx_hi = osstrdup(sprint_prefix(pfxHi[d]));
435   char * pfx_lo = sprint_prefix(pfxLo[d]);
436   real hi = max[d] * length_factor;
437   real lo = min[d] * length_factor;
438   printf(msg(msgno), hi - lo, units, pfx_hi, hi, units, pfx_lo, lo, units);
439   osfree(pfx_hi);
440   putnl();
441
442   /* Range without anonymous stations at offset 3. */
443   if (d < 3 && (pfxHi[d] != pfxHi[d + 3] || pfxLo[d] != pfxLo[d + 3])) {
444      do_range(d + 3, msgno, length_factor, units);
445   }
446}
447
448static void
449do_stats(void)
450{
451   long cLoops = cComponents + cLegs - cStns;
452   int length_units = get_length_units(Q_LENGTH);
453   const char * units = get_units_string(length_units);
454   real length_factor = 1.0 / get_units_factor(length_units);
455
456   putnl();
457
458   if (cStns == 1) {
459      fputs(msg(/*Survey contains 1 survey station,*/172), stdout);
460   } else {
461      printf(msg(/*Survey contains %ld survey stations,*/173), cStns);
462   }
463
464   if (cLegs == 1) {
465      fputs(msg(/* joined by 1 leg.*/174), stdout);
466   } else {
467      printf(msg(/* joined by %ld legs.*/175), cLegs);
468   }
469
470   putnl();
471
472   // FIXME: We potentially need to adjust cComponents if there are hanging
473   // surveys for these statistics to be correct.
474   if (!hanging_surveys) {
475      if (cLoops == 1) {
476         fputs(msg(/*There is 1 loop.*/138), stdout);
477      } else {
478         printf(msg(/*There are %ld loops.*/139), cLoops);
479      }
480
481      putnl();
482
483      if (cComponents != 1) {
484         /* TRANSLATORS: "Connected component" in the graph theory sense - it
485          * means there are %ld bits of survey with no connections between them.
486          * This message is only used if there are more than 1. */
487         printf(msg(/*Survey has %ld connected components.*/178), cComponents);
488         putnl();
489      }
490   }
491
492   printf(msg(/*Total length of survey legs = %7.2f%s (%7.2f%s adjusted)*/132),
493          total * length_factor, units, totadj * length_factor, units);
494   putnl();
495   printf(msg(/*Total plan length of survey legs = %7.2f%s*/133),
496          totplan * length_factor, units);
497   putnl();
498   printf(msg(/*Total vertical length of survey legs = %7.2f%s*/134),
499          totvert * length_factor, units);
500   putnl();
501
502   /* If there's no underground survey, we've no ranges */
503   if (pfxHi[0]) {
504      /* TRANSLATORS: numbers are altitudes of highest and lowest stations */
505      do_range(2, /*Vertical range = %4.2f%s (from %s at %4.2f%s to %s at %4.2f%s)*/135,
506               length_factor, units);
507      /* TRANSLATORS: c.f. previous message */
508      do_range(1, /*North-South range = %4.2f%s (from %s at %4.2f%s to %s at %4.2f%s)*/136,
509               length_factor, units);
510      /* TRANSLATORS: c.f. previous two messages */
511      do_range(0, /*East-West range = %4.2f%s (from %s at %4.2f%s to %s at %4.2f%s)*/137,
512               length_factor, units);
513   }
514
515   print_node_stats();
516   /* Also, could give:
517    *  # nodes stations (ie have other than two references or are fixed)
518    *  # fixed stations (list of?)
519    */
520}
Note: See TracBrowser for help on using the repository browser.