source: git/src/cavern.c

main
Last change on this file was 0b99107, checked in by Olly Betts <olly@…>, 3 weeks ago

Eliminate old FSF addresses

Update GPL/LGPL licence files and boilerplate to direct people who
didn't receive the licence text to the FSF website, as the current
versions of the FSF licence texts now do, rather than giving a postal
address.

  • Property mode set to 100644
File size: 16.7 KB
Line 
1/* cavern.c
2 * SURVEX Cave surveying software: data reduction main and related functions
3 * Copyright (C) 1991-2025 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, see
17 * <https://www.gnu.org/licenses/>.
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_for_survex.h"
38#include "listpos.h"
39#include "netbits.h"
40#include "netskel.h"
41#include "osalloc.h"
42#include "out.h"
43#include "str.h"
44#include "validate.h"
45
46#ifdef _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 = 0, cStns = 0;
57long cComponents = 0;
58long cSolves = 0;
59bool fExportUsed = false;
60char * proj_str_out = NULL;
61PJ * pj_cached = NULL;
62
63FILE *fhErrStat = NULL;
64img *pimg = NULL;
65int quiet = 0; // 1 to turn off progress messages; >=2 turns off summary too.
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;
81bool fnm_output_base_is_dir = false;
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),        /*fewer messages (-qq for even fewer)*/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;
147unsigned current_year;
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       // Convert the current date in the local timezone to the number of days
163       // since 1900 which we use to warn if a `*date` command specifies a date
164       // in the future.
165       struct tm * t = localtime(&tmUserStart);
166       int y = t->tm_year + 1900;
167       current_year = (unsigned)y;
168       current_days_since_1900 = days_since_1900(y, t->tm_mon + 1, t->tm_mday);
169   }
170
171   /* Always buffer by line for aven's benefit. */
172   setvbuf(stdout, NULL, _IOLBF, 0);
173
174   /* Prevent stderr spew from PROJ. */
175   proj_log_func(PJ_DEFAULT_CTX, NULL, discarding_proj_logger);
176
177   msg_init(argv);
178
179   pcs = osnew(settings);
180   pcs->next = NULL;
181   pcs->from_equals_to_is_only_a_warning = false;
182   pcs->Translate = ((short*) osmalloc(sizeof(short) * 257)) + 1;
183   pcs->meta = NULL;
184   pcs->proj_str = NULL;
185   pcs->declination = HUGE_REAL;
186   pcs->convergence = HUGE_REAL;
187   pcs->input_convergence = HUGE_REAL;
188   pcs->dec_filename = NULL;
189   pcs->dec_line = 0;
190   pcs->dec_context = NULL;
191   pcs->dec_lat = HUGE_VAL;
192   pcs->dec_lon = HUGE_VAL;
193   pcs->dec_alt = HUGE_VAL;
194   pcs->min_declination = HUGE_VAL;
195   pcs->max_declination = -HUGE_VAL;
196   pcs->cartesian_north = TRUE_NORTH;
197   pcs->cartesian_rotation = 0.0;
198
199   /* Set up root of prefix hierarchy */
200   root = osnew(prefix);
201   root->up = root->right = root->down = NULL;
202   root->stn = NULL;
203   root->pos = NULL;
204   root->ident.p = NULL;
205   root->min_export = root->max_export = 0;
206   root->sflags = BIT(SFLAGS_SURVEY);
207   root->filename = NULL;
208
209   nosurveyhead = NULL;
210
211   fixedlist = NULL;
212   stnlist = NULL;
213   totadj = total = totplan = totvert = 0.0;
214
215   for (d = 0; d < 9; d++) {
216      min[d] = HUGE_REAL;
217      max[d] = -HUGE_REAL;
218      pfxHi[d] = pfxLo[d] = NULL;
219   }
220
221   // TRANSLATORS: Here "survey" is a "cave map" rather than list of questions
222   // - it should be translated to the terminology that cavers using the
223   // language would use.
224   //
225   // Part of cavern --help
226   cmdline_set_syntax_message(/*[SURVEY_DATA_FILE]*/507, 0, NULL);
227   /* at least one argument must be given */
228   cmdline_init(argc, argv, short_opts, long_opts, NULL, help, 1, -1);
229   while (1) {
230      int opt = cmdline_getopt();
231      if (opt == EOF) break;
232      switch (opt) {
233       case 'p':
234         /* Ignore for compatibility with older versions. */
235         break;
236       case 'o': {
237         free(fnm_output_base); /* in case of multiple -o options */
238         /* can be a directory (in which case use basename of leaf input)
239          * or a file (in which case just trim the extension off) */
240         if (fDirectory(optarg)) {
241            /* this is a little tricky - we need to note the path here,
242             * and then add the leaf later on (in datain.c) */
243            fnm_output_base = base_from_fnm(optarg);
244            fnm_output_base_is_dir = true;
245         } else {
246            fnm_output_base = base_from_fnm(optarg);
247         }
248         break;
249       }
250       case 'q':
251         ++quiet;
252         break;
253       case 's':
254         fSuppress = 1;
255         break;
256       case 'v': {
257         int v = atoi(optarg);
258         if (v < IMG_VERSION_MIN || v > IMG_VERSION_MAX)
259            fatalerror(/*3d file format versions %d to %d supported*/88,
260                       IMG_VERSION_MIN, IMG_VERSION_MAX);
261         img_output_version = v;
262         break;
263       }
264       case 'w':
265         f_warnings_are_errors = 1;
266         break;
267       case 'z': {
268         /* Control which network optimisations are used (development tool) */
269         static bool seen_opt_z = false;
270         char c;
271         if (!seen_opt_z) {
272            optimize = 0;
273            seen_opt_z = true;
274         }
275         /* Lollipops, Parallel legs, Iterate mx, Delta* */
276         while ((c = *optarg++) != '\0')
277            if (islower((unsigned char)c)) optimize |= BITA(c);
278         break;
279       case 1:
280         fLog = true;
281         break;
282#ifdef _WIN32
283       case 2:
284         atexit(pause_on_exit);
285         break;
286#endif
287       }
288      }
289   }
290
291   if (fLog) {
292      char *fnm;
293      if (!fnm_output_base) {
294         char *p;
295         p = baseleaf_from_fnm(argv[optind]);
296         fnm = add_ext(p, EXT_LOG);
297         free(p);
298      } else if (fnm_output_base_is_dir) {
299         char *p;
300         fnm = baseleaf_from_fnm(argv[optind]);
301         p = use_path(fnm_output_base, fnm);
302         free(fnm);
303         fnm = add_ext(p, EXT_LOG);
304         free(p);
305      } else {
306         fnm = add_ext(fnm_output_base, EXT_LOG);
307      }
308
309      if (!freopen(fnm, "w", stdout))
310         fatalerror(/*Failed to open output file “%s”*/3, fnm);
311
312      free(fnm);
313   }
314
315   if (quiet < 2) {
316      const char *p = COPYRIGHT_MSG;
317      puts(PRETTYPACKAGE" "VERSION);
318      while (1) {
319          const char *q = p;
320          p = strstr(p, "(C)");
321          if (p == NULL) {
322              puts(q);
323              break;
324          }
325          FWRITE_(q, 1, p - q, stdout);
326          fputs(msg(/*©*/0), stdout);
327          p += 3;
328      }
329   }
330
331   atexit(delete_output_on_error);
332
333   /* end of options, now process data files */
334   while (argv[optind]) {
335      const char *fnm = argv[optind];
336
337      if (!fExplicitTitle) {
338          char *lf = baseleaf_from_fnm(fnm);
339          if (s_empty(&survey_title)) {
340              s_donate(&survey_title, lf);
341          } else {
342              s_appendch(&survey_title, ' ');
343              s_append(&survey_title, lf);
344              free(lf);
345          }
346      }
347
348      /* Select defaults settings */
349      default_all(pcs);
350      data_file(NULL, fnm); /* first argument is current path */
351
352      optind++;
353   }
354
355   validate();
356
357   report_declination(pcs);
358
359   solve_network(); /* Find coordinates of all points */
360   validate();
361
362   check_for_unused_fixed_points();
363
364   /* close .3d file */
365   if (!img_close(pimg)) {
366      char *fnm = add_ext(fnm_output_base, EXT_SVX_3D);
367      fatalerror(img_error2msg(img_error()), fnm);
368   }
369   if (fhErrStat) safe_fclose(fhErrStat);
370
371   out_current_action(msg(/*Calculating statistics*/120));
372   if (quiet < 2) do_stats();
373   if (!quiet) {
374      /* clock() typically wraps after 72 minutes, but there doesn't seem
375       * to be a better way.  Still 72 minutes means some cave!
376       * We detect if clock() could have wrapped and suppress CPU time
377       * printing in this case.
378       */
379      double tmUser = difftime(time(NULL), tmUserStart);
380      double tmCPU;
381      clock_t now = clock();
382#define CLOCK_T_WRAP \
383        (sizeof(clock_t)<sizeof(long)?(1ul << (CHAR_BIT * sizeof(clock_t))):0)
384      tmCPU = (now - (unsigned long)tmCPUStart)
385         / (double)CLOCKS_PER_SEC;
386      if (now < tmCPUStart)
387         tmCPU += CLOCK_T_WRAP / (double)CLOCKS_PER_SEC;
388      if (tmUser >= tmCPU + CLOCK_T_WRAP / (double)CLOCKS_PER_SEC)
389         tmCPU = 0;
390
391      /* tmUser is integer, tmCPU not - equivalent to (ceil(tmCPU) >= tmUser) */
392      if (tmCPU + 1 > tmUser) {
393         printf(msg(/*CPU time used %5.2fs*/140), tmCPU);
394      } else if (tmCPU == 0) {
395         if (tmUser != 0.0) {
396            printf(msg(/*Time used %5.2fs*/141), tmUser);
397         } else {
398            fputs(msg(/*Time used unavailable*/142), stdout);
399         }
400      } else {
401         printf(msg(/*Time used %5.2fs (%5.2fs CPU time)*/143), tmUser, tmCPU);
402      }
403      putnl();
404   }
405   if (msg_warnings || msg_errors) {
406      putnl();
407      if (msg_errors || (f_warnings_are_errors && msg_warnings)) {
408         printf(msg(/*There were %d warning(s) and %d error(s) - no output files produced.*/113),
409                msg_warnings, msg_errors);
410         putnl();
411         return EXIT_FAILURE;
412      }
413      printf(msg(/*There were %d warning(s).*/16), msg_warnings);
414      putnl();
415   }
416   return EXIT_SUCCESS;
417}
418
419static void
420do_range(int d, int msgno, real length_factor, const char * units)
421{
422   if (d < 3) {
423      /* If the bound including anonymous stations is at an anonymous station
424       * but the bound only considering named stations is the same, use the
425       * named station for the anonymous bound too.
426       */
427      if (TSTBIT(pfxHi[d]->sflags, SFLAGS_ANON) && max[d] == max[d + 3]) {
428         pfxHi[d] = pfxHi[d + 3];
429      }
430      if (TSTBIT(pfxLo[d]->sflags, SFLAGS_ANON) && min[d] == min[d + 3]) {
431         pfxLo[d] = pfxLo[d + 3];
432      }
433   }
434
435   /* sprint_prefix uses a single buffer, so to report two stations in one
436    * message we need to make a temporary copy of the string for one of them.
437    */
438   char * pfx_hi = osstrdup(sprint_prefix(pfxHi[d]));
439   char * pfx_lo = sprint_prefix(pfxLo[d]);
440   real hi = max[d] * length_factor;
441   real lo = min[d] * length_factor;
442   printf(msg(msgno), hi - lo, units, pfx_hi, hi, units, pfx_lo, lo, units);
443   free(pfx_hi);
444   putnl();
445
446   /* Range without anonymous stations at offset 3. */
447   if (d < 3 && (pfxHi[d] != pfxHi[d + 3] || pfxLo[d] != pfxLo[d + 3])) {
448      do_range(d + 3, msgno, length_factor, units);
449   }
450}
451
452static void
453do_stats(void)
454{
455   putnl();
456
457   if (proj_str_out) {
458       prefix *name_min = NULL;
459       prefix *name_max = NULL;
460       double convergence_min = HUGE_VAL;
461       double convergence_max = -HUGE_VAL;
462       prefix **pfx = pfxLo;
463       for (int bound = 0; bound < 2; ++bound) {
464           for (int d = 6; d < 9; ++d) {
465               if (pfx[d]) {
466                   pos *p = pfx[d]->pos;
467                   double convergence = calculate_convergence_xy(proj_str_out,
468                                                                 p->p[0],
469                                                                 p->p[1],
470                                                                 p->p[2]);
471                   if (convergence < convergence_min) {
472                       convergence_min = convergence;
473                       name_min = pfx[d];
474                   }
475                   if (convergence > convergence_max) {
476                       convergence_max = convergence;
477                       name_max = pfx[d];
478                   }
479               }
480           }
481           pfx = pfxHi;
482       }
483       if (name_min && name_max) {
484           const char* deg_sign = msg(/*°*/344);
485           /* sprint_prefix uses a single buffer, so to report two stations in
486            * one message we need to make a temporary copy of the string for
487            * one of them.
488            */
489           char *pfx_hi = osstrdup(sprint_prefix(name_max));
490           char *pfx_lo = sprint_prefix(name_min);
491           // TRANSLATORS: Cavern computes the grid convergence at the
492           // representative location(s) specified by the
493           // `*declination auto` command(s).  The convergence values
494           // for the most N, S, E and W survey stations with legs
495           // attached are also computed and the range of these values
496           // is reported in this message.  It's approximate because the
497           // min or max convergence could actually be beyond this range
498           // but it's unlikely to be very wrong.
499           //
500           // Each %.1f%s will be replaced with a convergence angle (e.g.
501           // 0.9°) and the following %s with the station name where that
502           // convergence angle was computed.
503           printf(msg(/*Approximate full range of grid convergence: %.1f%s at %s to %.1f%s at %s\n*/531),
504                  deg(convergence_min), deg_sign, pfx_lo,
505                  deg(convergence_max), deg_sign, pfx_hi);
506           free(pfx_hi);
507       }
508   }
509
510   if (cStns == 1) {
511      fputs(msg(/*Survey contains 1 survey station,*/172), stdout);
512   } else {
513      printf(msg(/*Survey contains %ld survey stations,*/173), cStns);
514   }
515
516   if (cLegs == 1) {
517      fputs(msg(/* joined by 1 leg.*/174), stdout);
518   } else {
519      printf(msg(/* joined by %ld legs.*/175), cLegs);
520   }
521   putnl();
522
523   if (cSolves == 1) {
524       // If *solve is used then cComponents will often be wrong.  Rather than
525       // reporting incorrect counts of loops and components we omit these in
526       // this case for now.
527       long cLoops = cComponents + cLegs - cStns;
528       if (cLoops == 1) {
529          fputs(msg(/*There is 1 loop.*/138), stdout);
530       } else {
531          printf(msg(/*There are %ld loops.*/139), cLoops);
532       }
533       putnl();
534
535       if (cComponents != 1) {
536          /* TRANSLATORS: "Connected component" in the graph theory sense - it
537           * means there are %ld bits of survey with no connections between
538           * them.  This message is only used if there are more than 1. */
539          printf(msg(/*Survey has %ld connected components.*/178), cComponents);
540          putnl();
541       }
542   }
543
544   int length_units = get_length_units(Q_LENGTH);
545   const char * units = get_units_string(length_units);
546   real length_factor = 1.0 / get_units_factor(length_units);
547
548   printf(msg(/*Total length of survey legs = %7.2f%s (%7.2f%s adjusted)*/132),
549          total * length_factor, units, totadj * length_factor, units);
550   putnl();
551   printf(msg(/*Total plan length of survey legs = %7.2f%s*/133),
552          totplan * length_factor, units);
553   putnl();
554   printf(msg(/*Total vertical length of survey legs = %7.2f%s*/134),
555          totvert * length_factor, units);
556   putnl();
557
558   /* If there's no underground survey, we've no ranges */
559   if (pfxHi[0]) {
560      /* TRANSLATORS: numbers are altitudes of highest and lowest stations */
561      do_range(2, /*Vertical range = %4.2f%s (from %s at %4.2f%s to %s at %4.2f%s)*/135,
562               length_factor, units);
563      /* TRANSLATORS: c.f. previous message */
564      do_range(1, /*North-South range = %4.2f%s (from %s at %4.2f%s to %s at %4.2f%s)*/136,
565               length_factor, units);
566      /* TRANSLATORS: c.f. previous two messages */
567      do_range(0, /*East-West range = %4.2f%s (from %s at %4.2f%s to %s at %4.2f%s)*/137,
568               length_factor, units);
569   }
570
571   check_node_stats();
572}
Note: See TracBrowser for help on using the repository browser.