Resolve WPJ .PATH relative to innermost containing book
[survex.git] / src / datain.c
blobb27f2a221503342fd2a4ea85ed7bf9268fe877fd
1 /* datain.c
2 * Reads in survey files, dealing with special characters, keywords & data
3 * Copyright (C) 1991-2024 Olly Betts
4 * Copyright (C) 2004 Simeon Warner
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 #include <config.h>
23 #include <limits.h>
24 #include <stdarg.h>
26 #include "debug.h"
27 #include "cavern.h"
28 #include "date.h"
29 #include "hash.h"
30 #include "img.h"
31 #include "filename.h"
32 #include "message.h"
33 #include "filelist.h"
34 #include "netbits.h"
35 #include "netskel.h"
36 #include "readval.h"
37 #include "datain.h"
38 #include "commands.h"
39 #include "out.h"
40 #include "str.h"
41 #include "thgeomag.h"
43 #include <proj.h>
44 #if PROJ_VERSION_MAJOR < 8
45 # define proj_context_errno_string(CTX, ERR) proj_errno_string(ERR)
46 #endif
47 #if PROJ_VERSION_MAJOR < 8 || \
48 (PROJ_VERSION_MAJOR == 8 && PROJ_VERSION_MINOR < 2)
49 /* Needed for proj_factors workaround */
50 # include <proj_experimental.h>
51 #endif
53 #define EPSILON (REAL_EPSILON * 1000)
55 #define var(I) (pcs->Var[(I)])
57 /* Test for a not-a-number value in Compass data (999.0 or -999.0).
59 * Compass itself uses -999.0 but reportedly understands Karst data which used
60 * 999.0 (information from Larry Fish via Simeon Warner in 2004). However
61 * testing with Compass in early 2024 it seems 999.0 is treated like any other
62 * reading.
64 * When "corrected" backsights are specified in FORMAT, Compass seems to write
65 * out -999 with the correction applied to the CLP file.
67 * Valid readings should be 0 to 360 for the compass and -90 to 90 for the
68 * clino, and the correction should have absolute value < 360, so we test for
69 * any reading with an absolute value greater than 999 - 360 = 639, which is
70 * well outside the valid range.
72 #define is_compass_NaN(x) (fabs(x) > (999.0 - 360.0))
74 static int
75 read_compass_date_as_days_since_1900(void)
77 /* NB order is *month* *day* year */
78 int month = read_uint();
79 int day = read_uint();
80 int year = read_uint();
81 /* Note: Larry says a 2 digit year is always 19XX */
82 if (year < 100) year += 1900;
84 /* Compass uses 1901-01-01 when no date was specified. */
85 if (year == 1901 && day == 1 && month == 1) return -1;
87 return days_since_1900(year, month, day);
89 int ch;
91 typedef enum {
92 // Clino omitted. VAL() should be set to 0.0.
93 CTYPE_OMIT,
94 // An actual clino reading.
95 CTYPE_READING,
96 // An explicit plumb (U/D/UP/DOWN/+V/-V for reading).
97 CTYPE_PLUMB,
98 // An inferred plumb (+90 or -90 and *infer plumbs).
99 CTYPE_INFERPLUMB,
100 // An explicit horizontal leg (H/LEVEL for reading).
101 CTYPE_HORIZ
102 } clino_type;
104 /* Don't explicitly initialise as we can't set the jmp_buf - this has
105 * static scope so will be initialised like this anyway */
106 parse file /* = { NULL, NULL, 0, false, NULL } */ ;
108 bool f_export_ok;
110 static real value[Fr - 1];
111 #define VAL(N) value[(N)-1]
112 static real variance[Fr - 1];
113 #define VAR(N) variance[(N)-1]
114 static long location[Fr - 1];
115 #define LOC(N) location[(N)-1]
116 static int location_width[Fr - 1];
117 #define WID(N) location_width[(N)-1]
119 /* style functions */
120 static void data_normal(void);
121 static void data_cartesian(void);
122 static void data_passage(void);
123 static void data_nosurvey(void);
124 static void data_ignore(void);
126 void
127 get_pos(filepos *fp)
129 fp->ch = ch;
130 fp->offset = ftell(file.fh);
131 if (fp->offset == -1)
132 fatalerror_in_file(file.filename, 0, /*Error reading file*/18);
135 void
136 set_pos(const filepos *fp)
138 ch = fp->ch;
139 if (fseek(file.fh, fp->offset, SEEK_SET) == -1)
140 fatalerror_in_file(file.filename, 0, /*Error reading file*/18);
143 static void
144 report_parent(parse * p) {
145 if (p->parent)
146 report_parent(p->parent);
147 /* Force re-report of include tree for further errors in
148 * parent files */
149 p->reported_where = false;
150 /* TRANSLATORS: %s is replaced by the filename of the parent file, and %u
151 * by the line number in that file. Your translation should also contain
152 * %s:%u so that automatic parsing of error messages to determine the file
153 * and line number still works. */
154 fprintf(STDERR, msg(/*In file included from %s:%u:\n*/5), p->filename, p->line);
157 static void
158 error_list_parent_files(void)
160 if (!file.reported_where && file.parent) {
161 report_parent(file.parent);
162 /* Suppress reporting of full include tree for further errors
163 * in this file */
164 file.reported_where = true;
168 static void
169 show_line(int col, int width)
171 /* Rewind to beginning of line. */
172 long cur_pos = ftell(file.fh);
173 int tabs = 0;
174 if (cur_pos < 0 || fseek(file.fh, file.lpos, SEEK_SET) == -1)
175 fatalerror_in_file(file.filename, 0, /*Error reading file*/18);
177 /* Read the whole line and write it out. */
178 PUTC(' ', STDERR);
179 while (1) {
180 int c = GETC(file.fh);
181 /* Note: isEol() is true for EOF */
182 if (isEol(c)) break;
183 if (c == '\t') ++tabs;
184 PUTC(c, STDERR);
186 fputnl(STDERR);
188 /* If we have a location in the line for the error, indicate it. */
189 if (col > 0) {
190 PUTC(' ', STDERR);
191 if (tabs == 0) {
192 while (--col) PUTC(' ', STDERR);
193 } else {
194 /* Copy tabs from line, replacing other characters with spaces - this
195 * means that the caret should line up correctly. */
196 if (fseek(file.fh, file.lpos, SEEK_SET) == -1)
197 fatalerror_in_file(file.filename, 0, /*Error reading file*/18);
198 while (--col) {
199 int c = GETC(file.fh);
200 if (c != '\t') c = ' ';
201 PUTC(c, STDERR);
204 PUTC('^', STDERR);
205 while (width > 1) {
206 PUTC('~', STDERR);
207 --width;
209 fputnl(STDERR);
212 /* Revert to where we were. */
213 if (fseek(file.fh, cur_pos, SEEK_SET) == -1)
214 fatalerror_in_file(file.filename, 0, /*Error reading file*/18);
217 char*
218 grab_line(void)
220 /* Rewind to beginning of line. */
221 long cur_pos = ftell(file.fh);
222 string p = S_INIT;
223 if (cur_pos < 0 || fseek(file.fh, file.lpos, SEEK_SET) == -1)
224 fatalerror_in_file(file.filename, 0, /*Error reading file*/18);
226 /* Read the whole line into a string. */
227 while (1) {
228 int c = GETC(file.fh);
229 /* Note: isEol() is true for EOF */
230 if (isEol(c)) break;
231 s_catchar(&p, c);
234 /* Revert to where we were. */
235 if (fseek(file.fh, cur_pos, SEEK_SET) == -1) {
236 s_free(&p);
237 fatalerror_in_file(file.filename, 0, /*Error reading file*/18);
240 return s_steal(&p);
243 static int caret_width = 0;
245 static void
246 compile_v_report_fpos(int severity, long fpos, int en, va_list ap)
248 int col = 0;
249 error_list_parent_files();
250 if (fpos >= file.lpos)
251 col = fpos - file.lpos - caret_width;
252 v_report(severity, file.filename, file.line, col, en, ap);
253 if (file.fh) show_line(col, caret_width);
256 static void
257 compile_v_report(int diag_flags, int en, va_list ap)
259 int severity = (diag_flags & DIAG_SEVERITY_MASK);
260 if (diag_flags & (DIAG_COL|DIAG_TOKEN)) {
261 if (file.fh) {
262 if (diag_flags & DIAG_TOKEN) caret_width = s_len(&token);
263 compile_v_report_fpos(severity, ftell(file.fh), en, ap);
264 if (diag_flags & DIAG_TOKEN) caret_width = 0;
265 if (diag_flags & DIAG_SKIP) skipline();
266 return;
269 error_list_parent_files();
270 v_report(severity, file.filename, file.line, 0, en, ap);
271 if (file.fh) {
272 if (diag_flags & DIAG_TOKEN) {
273 show_line(0, s_len(&token));
274 } else {
275 show_line(0, caret_width);
278 if (diag_flags & DIAG_SKIP) skipline();
281 void
282 compile_diagnostic(int diag_flags, int en, ...)
284 va_list ap;
285 va_start(ap, en);
286 if (diag_flags & (DIAG_DATE|DIAG_NUM|DIAG_UINT|DIAG_WORD|DIAG_TAIL|DIAG_FROM_)) {
287 int len = 0;
288 skipblanks();
289 if (diag_flags & DIAG_WORD) {
290 while (!isBlank(ch) && !isComm(ch) && !isEol(ch)) {
291 ++len;
292 nextch();
294 } else if (diag_flags & DIAG_UINT) {
295 while (isdigit(ch)) {
296 ++len;
297 nextch();
299 } else if (diag_flags & DIAG_DATE) {
300 while (isdigit(ch) || ch == '.') {
301 ++len;
302 nextch();
304 } else if (diag_flags & DIAG_TAIL) {
305 int len_last_nonblank = len;
306 while (!isComm(ch) && !isEol(ch)) {
307 ++len;
308 if (!isBlank(ch)) len_last_nonblank = len;
309 nextch();
311 len = len_last_nonblank;
312 } else if (diag_flags & DIAG_FROM_) {
313 len = diag_flags >> DIAG_FROM_SHIFT;
314 } else {
315 if (isMinus(ch) || isPlus(ch)) {
316 ++len;
317 nextch();
319 while (isdigit(ch)) {
320 ++len;
321 nextch();
323 if (isDecimal(ch)) {
324 ++len;
325 nextch();
327 while (isdigit(ch)) {
328 ++len;
329 nextch();
332 caret_width = len;
333 compile_v_report(diag_flags|DIAG_COL, en, ap);
334 caret_width = 0;
335 } else if (diag_flags & DIAG_STRING) {
336 string p = S_INIT;
337 skipblanks();
338 caret_width = ftell(file.fh);
339 read_string(&p);
340 s_free(&p);
341 /* We want to include any quotes, so can't use s_len(&p). */
342 caret_width = ftell(file.fh) - caret_width;
343 compile_v_report(diag_flags|DIAG_COL, en, ap);
344 caret_width = 0;
345 } else {
346 compile_v_report(diag_flags, en, ap);
348 va_end(ap);
351 static void
352 compile_diagnostic_reading(int diag_flags, reading r, int en, ...)
354 va_list ap;
355 int severity = (diag_flags & DIAG_SEVERITY_MASK);
356 va_start(ap, en);
357 caret_width = WID(r);
358 compile_v_report_fpos(severity, LOC(r) + caret_width, en, ap);
359 caret_width = 0;
360 va_end(ap);
363 static void
364 compile_error_reading_skip(reading r, int en, ...)
366 va_list ap;
367 va_start(ap, en);
368 caret_width = WID(r);
369 compile_v_report_fpos(DIAG_ERR, LOC(r) + caret_width, en, ap);
370 caret_width = 0;
371 va_end(ap);
372 skipline();
375 void
376 compile_diagnostic_at(int diag_flags, const char * filename, unsigned line, int en, ...)
378 va_list ap;
379 int severity = (diag_flags & DIAG_SEVERITY_MASK);
380 va_start(ap, en);
381 v_report(severity, filename, line, 0, en, ap);
382 va_end(ap);
385 void
386 compile_diagnostic_pfx(int diag_flags, const prefix * pfx, int en, ...)
388 va_list ap;
389 int severity = (diag_flags & DIAG_SEVERITY_MASK);
390 va_start(ap, en);
391 v_report(severity, pfx->filename, pfx->line, 0, en, ap);
392 va_end(ap);
395 void
396 compile_diagnostic_token_show(int diag_flags, int en)
398 string p = S_INIT;
399 skipblanks();
400 while (!isBlank(ch) && !isComm(ch) && !isEol(ch)) {
401 s_catchar(&p, (char)ch);
402 nextch();
404 if (!s_empty(&p)) {
405 caret_width = s_len(&p);
406 compile_diagnostic(diag_flags|DIAG_COL, en, s_str(&p));
407 caret_width = 0;
408 s_free(&p);
409 } else {
410 compile_diagnostic(DIAG_ERR|DIAG_COL, en, "");
414 static void
415 compile_error_string(const char * s, int en, ...)
417 va_list ap;
418 va_start(ap, en);
419 caret_width = strlen(s);
420 compile_v_report(DIAG_ERR|DIAG_COL, en, ap);
421 va_end(ap);
422 caret_width = 0;
425 /* This function makes a note where to put output files */
426 static void
427 using_data_file(const char *fnm)
429 if (!fnm_output_base) {
430 /* was: fnm_output_base = base_from_fnm(fnm); */
431 fnm_output_base = baseleaf_from_fnm(fnm);
432 } else if (fnm_output_base_is_dir) {
433 /* --output pointed to directory so use the leaf basename in that dir */
434 char *lf, *p;
435 lf = baseleaf_from_fnm(fnm);
436 p = use_path(fnm_output_base, lf);
437 osfree(lf);
438 osfree(fnm_output_base);
439 fnm_output_base = p;
440 fnm_output_base_is_dir = 0;
444 static void
445 skipword(void)
447 while (!isBlank(ch) && !isComm(ch) && !isEol(ch)) nextch();
450 extern void
451 skipblanks(void)
453 while (isBlank(ch)) nextch();
456 extern void
457 skipline(void)
459 while (!isEol(ch)) nextch();
462 static void
463 process_eol(void)
465 int eolchar;
467 skipblanks();
469 if (!isEol(ch)) {
470 if (!isComm(ch))
471 compile_diagnostic(DIAG_ERR|DIAG_TAIL, /*End of line not blank*/15);
472 skipline();
475 eolchar = ch;
476 file.line++;
477 /* skip any different eol characters so we get line counts correct on
478 * DOS text files and similar, but don't count several adjacent blank
479 * lines as one */
480 while (ch != EOF) {
481 nextch();
482 if (ch == eolchar || !isEol(ch)) {
483 break;
485 if (ch == '\n') eolchar = ch;
487 file.lpos = ftell(file.fh) - 1;
490 static bool
491 process_non_data_line(void)
493 skipblanks();
495 if (isData(ch)) return false;
497 if (isKeywd(ch)) {
498 nextch();
499 handle_command();
502 process_eol();
504 return true;
507 static void
508 read_reading(reading r, bool f_optional)
510 int n_readings;
511 q_quantity q;
512 switch (r) {
513 case Tape: q = Q_LENGTH; break;
514 case BackTape: q = Q_BACKLENGTH; break;
515 case Comp: q = Q_BEARING; break;
516 case BackComp: q = Q_BACKBEARING; break;
517 case Clino: q = Q_GRADIENT; break;
518 case BackClino: q = Q_BACKGRADIENT; break;
519 case FrDepth: case ToDepth: q = Q_DEPTH; break;
520 case Dx: q = Q_DX; break;
521 case Dy: q = Q_DY; break;
522 case Dz: q = Q_DZ; break;
523 case FrCount: case ToCount: q = Q_COUNT; break;
524 case Left: q = Q_LEFT; break;
525 case Right: q = Q_RIGHT; break;
526 case Up: q = Q_UP; break;
527 case Down: q = Q_DOWN; break;
528 default:
529 q = Q_NULL; /* Suppress compiler warning */;
530 BUG("Unexpected case");
532 LOC(r) = ftell(file.fh);
533 /* since we don't handle bearings in read_readings, it's never quadrant */
534 VAL(r) = read_numeric_multi(f_optional, false, &n_readings);
535 WID(r) = ftell(file.fh) - LOC(r);
536 VAR(r) = var(q);
537 if (n_readings > 1) VAR(r) /= sqrt(n_readings);
540 static void
541 read_bearing_or_omit(reading r)
543 int n_readings;
544 bool quadrants = false;
545 q_quantity q = Q_NULL;
546 switch (r) {
547 case Comp:
548 q = Q_BEARING;
549 if (pcs->f_bearing_quadrants)
550 quadrants = true;
551 break;
552 case BackComp:
553 q = Q_BACKBEARING;
554 if (pcs->f_backbearing_quadrants)
555 quadrants = true;
556 break;
557 default:
558 q = Q_NULL; /* Suppress compiler warning */;
559 BUG("Unexpected case");
561 LOC(r) = ftell(file.fh);
562 VAL(r) = read_bearing_multi_or_omit(quadrants, &n_readings);
563 WID(r) = ftell(file.fh) - LOC(r);
564 VAR(r) = var(q);
565 if (n_readings > 1) VAR(r) /= sqrt(n_readings);
568 // Set up settings for reading Compass DAT or MAK.
569 static void
570 initialise_common_compass_settings(void)
572 short *t = ((short*)osmalloc(ossizeof(short) * 257)) + 1;
573 int i;
574 t[EOF] = SPECIAL_EOL;
575 memset(t, 0, sizeof(short) * 33);
576 for (i = 33; i < 127; i++) t[i] = SPECIAL_NAMES;
577 t[127] = 0;
578 for (i = 128; i < 256; i++) t[i] = SPECIAL_NAMES;
579 t['\t'] |= SPECIAL_BLANK;
580 t[' '] |= SPECIAL_BLANK;
581 t['\032'] |= SPECIAL_EOL; /* Ctrl-Z, so olde DOS text files are handled ok */
582 t['\n'] |= SPECIAL_EOL;
583 t['\r'] |= SPECIAL_EOL;
584 t['.'] |= SPECIAL_DECIMAL;
585 t['-'] |= SPECIAL_MINUS;
586 t['+'] |= SPECIAL_PLUS;
588 settings *pcsNew = osnew(settings);
589 *pcsNew = *pcs; /* copy contents */
590 pcsNew->begin_lineno = 0;
591 pcsNew->Translate = t;
592 pcsNew->Case = OFF;
593 pcsNew->Truncate = INT_MAX;
594 pcsNew->next = pcs;
595 pcs = pcsNew;
597 update_output_separator();
600 /* For reading Compass MAK files which have a freeform syntax */
601 static void
602 nextch_handling_eol(void)
604 nextch();
605 while (ch != EOF && isEol(ch)) {
606 process_eol();
610 static bool
611 get_token_and_check(const char *expect)
613 get_token();
614 if (s_eq(&token, expect))
615 return true;
616 compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Expecting ā€œ%sā€*/497, expect);
617 return false;
620 static bool
621 check_colon(void)
623 if (ch != ':') {
624 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting ā€œ%sā€*/497, ":");
625 return false;
627 nextch();
628 return true;
631 static bool
632 get_token_and_check_colon(const char *expect)
634 return get_token_and_check(expect) && check_colon();
637 static void
638 data_file_compass_dat_or_clp(bool is_clp)
640 initialise_common_compass_settings();
641 default_units(pcs);
642 default_calib(pcs);
643 pcs->z[Q_DECLINATION] = HUGE_REAL;
645 pcs->style = STYLE_NORMAL;
646 pcs->units[Q_LENGTH] = METRES_PER_FOOT;
647 pcs->infer = BIT(INFER_EQUATES) |
648 BIT(INFER_EQUATES_SELF_OK) |
649 BIT(INFER_EXPORTS) |
650 BIT(INFER_PLUMBS);
651 /* We need to update separator_map so we don't pick a separator character
652 * which occurs in a station name. However Compass DAT allows everything
653 * >= ASCII char 33 except 127 in station names so if we just added all
654 * the valid station name characters we'd always pick space as the
655 * separator for any dataset which included a DAT file, yet in practice
656 * '.' is never used in any of the sample DAT files I've seen. So
657 * instead we scan the characters actually used in station names when we
658 * process CompassDATFr and CompassDATTo fields.
661 #ifdef HAVE_SETJMP_H
662 /* errors in nested functions can longjmp here */
663 if (setjmp(file.jbSkipLine)) {
664 skipline();
665 process_eol();
667 #endif
669 while (ch != EOF && !ferror(file.fh)) {
670 static const reading compass_order[] = {
671 CompassDATFr, CompassDATTo, Tape, CompassDATComp, CompassDATClino,
672 CompassDATLeft, CompassDATUp, CompassDATDown, CompassDATRight,
673 CompassDATFlags, IgnoreAll
675 static const reading compass_order_backsights[] = {
676 CompassDATFr, CompassDATTo, Tape, CompassDATComp, CompassDATClino,
677 CompassDATLeft, CompassDATUp, CompassDATDown, CompassDATRight,
678 CompassDATBackComp, CompassDATBackClino,
679 CompassDATFlags, IgnoreAll
682 copy_on_write_meta(pcs);
683 pcs->meta->days1 = pcs->meta->days2 = -1;
684 pcs->declination = HUGE_REAL;
685 pcs->ordering = compass_order;
686 pcs->recorded_style = STYLE_NORMAL;
688 /* <Cave name> */
689 skipline();
690 process_eol();
691 /* SURVEY NAME: <Short name> */
692 if (get_token_and_check("SURVEY") &&
693 get_token_and_check_colon("NAME")) {
694 // Survey short name currently ignored.
695 get_token();
697 skipline();
698 process_eol();
700 /* SURVEY DATE: 7 10 79 COMMENT:<Long name> */
701 if (get_token_and_check("SURVEY") &&
702 get_token_and_check_colon("DATE")) {
703 int days = read_compass_date_as_days_since_1900();
704 pcs->meta->days1 = pcs->meta->days2 = days;
705 // Ignore "COMMENT:<Long name>" part for now.
707 skipline();
708 process_eol();
709 /* SURVEY TEAM: */
710 if (get_token_and_check("SURVEY") &&
711 get_token_and_check_colon("TEAM")) {
712 // Value is on the next line.
714 process_eol();
715 /* <Survey team> */
716 skipline();
717 process_eol();
718 /* DECLINATION: 1.00 FORMAT: DDDDLUDRADLN CORRECTIONS: 2.00 3.00 4.00 */
719 if (get_token_and_check_colon("DECLINATION")) {
720 if (pcs->dec_filename == NULL) {
721 pcs->z[Q_DECLINATION] = -read_numeric(false);
722 pcs->z[Q_DECLINATION] *= pcs->units[Q_DECLINATION];
723 } else {
724 (void)read_numeric(false);
727 get_token();
728 pcs->ordering = compass_order;
729 // "FORMAT" is optional.
730 if (S_EQ(&token, "FORMAT") && check_colon()) {
731 /* This documents the format in the original survey notebook - we
732 * don't need to fully parse it to be able to parse the survey data
733 * in the file, which gets converted to a fixed order and units.
735 get_token();
736 size_t token_len = s_len(&token);
737 if (token_len >= 4 && s_str(&token)[3] == 'W') {
738 /* Original "Inclination Units" were "Depth Gauge". */
739 pcs->recorded_style = STYLE_DIVING;
741 if (token_len >= 12) {
742 char backsight_type = s_str(&token)[token_len >= 15 ? 13 : 11];
743 // B means redundant backsight; C means redundant backsights
744 // but displayed "corrected" (i.e. reversed to make visually
745 // comparing easier).
746 if (backsight_type == 'B' || backsight_type == 'C') {
747 /* We have backsights for compass and clino */
748 pcs->ordering = compass_order_backsights;
751 get_token();
754 // CORRECTIONS and CORRECTIONS2 have already been applied to data in
755 // the CLP file.
756 if (!is_clp) {
757 if (S_EQ(&token, "CORRECTIONS") && check_colon()) {
758 pcs->z[Q_BACKBEARING] = pcs->z[Q_BEARING] = -rad(read_numeric(false));
759 pcs->z[Q_BACKGRADIENT] = pcs->z[Q_GRADIENT] = -rad(read_numeric(false));
760 pcs->z[Q_LENGTH] = -METRES_PER_FOOT * read_numeric(false);
761 get_token();
764 /* get_token() only reads alphas so we need special handling for
765 * CORRECTIONS2 here.
767 if (ch == '2') {
768 s_catchar(&token, ch);
769 nextch();
771 if (S_EQ(&token, "CORRECTIONS2") && check_colon()) {
772 pcs->z[Q_BACKBEARING] = -rad(read_numeric(false));
773 pcs->z[Q_BACKGRADIENT] = -rad(read_numeric(false));
774 get_token();
778 #if 0
779 // FIXME Parse once we handle discovery dates...
780 // NB: Need to skip unread CORRECTIONS* for the `is_clp` case.
781 if (S_EQ(&token, "DISCOVERY") && check_colon()) {
782 // Discovery date, e.g. DISCOVERY: 2 28 2024
783 int days = read_compass_date_as_days_since_1900();
785 #endif
786 skipline();
787 process_eol();
788 /* BLANK LINE */
789 process_eol();
790 /* heading line */
791 skipline();
792 process_eol();
793 /* BLANK LINE */
794 process_eol();
795 while (ch != EOF) {
796 if (ch == '\x0c') {
797 nextch();
798 process_eol();
799 break;
801 data_normal();
803 clear_last_leg();
805 pcs->ordering = NULL; /* Avoid free() of static array. */
806 pop_settings();
809 static void
810 data_file_compass_dat(void)
812 data_file_compass_dat_or_clp(false);
815 static void
816 data_file_compass_clp(void)
818 data_file_compass_dat_or_clp(true);
821 static void
822 data_file_compass_mak(void)
824 initialise_common_compass_settings();
825 short *t = pcs->Translate;
826 // In a Compass MAK file a station name can't contain these three
827 // characters due to how the syntax works.
828 t['['] = t[','] = t[';'] = 0;
830 #ifdef HAVE_SETJMP_H
831 /* errors in nested functions can longjmp here */
832 if (setjmp(file.jbSkipLine)) {
833 skipline();
834 process_eol();
836 #endif
838 int datum = 0;
839 int utm_zone = 0;
840 real base_x = 0.0, base_y = 0.0, base_z = 0.0;
841 int base_utm_zone = 0;
842 unsigned int base_line = 0;
843 long base_lpos = 0;
844 string path = S_INIT;
845 s_donate(&path, path_from_fnm(file.filename));
846 struct mak_folder {
847 struct mak_folder *next;
848 int len;
849 } *folder_stack = NULL;
851 while (ch != EOF && !ferror(file.fh)) {
852 switch (ch) {
853 case '#': {
854 /* include a file */
855 int ch_store;
856 string dat_fnm = S_INIT;
857 nextch_handling_eol();
858 while (ch != ',' && ch != ';' && ch != EOF) {
859 while (isEol(ch)) process_eol();
860 s_catchar(&dat_fnm, (char)ch);
861 nextch_handling_eol();
863 if (!s_empty(&dat_fnm)) {
864 if (base_utm_zone) {
865 // Process the previous @ command using the datum from &.
866 char *proj_str = img_compass_utm_proj_str(datum,
867 base_utm_zone);
868 if (proj_str) {
869 // Temporarily reset line and lpos so dec_context and
870 // dec_line refer to the @ command.
871 unsigned saved_line = file.line;
872 file.line = base_line;
873 long saved_lpos = file.lpos;
874 file.lpos = base_lpos;
875 set_declination_location(base_x, base_y, base_z,
876 proj_str);
877 file.line = saved_line;
878 file.lpos = saved_lpos;
879 if (!pcs->proj_str) {
880 pcs->proj_str = proj_str;
881 if (!proj_str_out) {
882 proj_str_out = osstrdup(proj_str);
884 } else {
885 osfree(proj_str);
889 ch_store = ch;
890 data_file(s_str(&path), s_str(&dat_fnm));
891 ch = ch_store;
892 s_free(&dat_fnm);
894 while (ch != ';' && ch != EOF) {
895 nextch_handling_eol();
896 filepos fp_name;
897 get_pos(&fp_name);
898 prefix *name = read_prefix(PFX_STATION|PFX_OPT);
899 if (name) {
900 scan_compass_station_name(name);
901 skipblanks();
902 if (ch == '[') {
903 /* fixed pt */
904 real coords[3];
905 bool in_feet = false;
906 // Compass treats these fixed points as entrances
907 // ("distance from entrance" in a .DAT file counts
908 // from 0.0 at these points) so we do too.
909 name->sflags |= BIT(SFLAGS_FIXED) |
910 BIT(SFLAGS_ENTRANCE);
911 nextch_handling_eol();
912 if (ch == 'F' || ch == 'f') {
913 in_feet = true;
914 nextch_handling_eol();
915 } else if (ch == 'M' || ch == 'm') {
916 nextch_handling_eol();
917 } else {
918 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting ā€œ%sā€ or ā€œ%sā€*/103, "F", "M");
920 while (!isdigit(ch) && ch != '+' && ch != '-' &&
921 ch != '.' && ch != ']' && ch != EOF) {
922 nextch_handling_eol();
924 coords[0] = read_numeric(false);
925 while (!isdigit(ch) && ch != '+' && ch != '-' &&
926 ch != '.' && ch != ']' && ch != EOF) {
927 nextch_handling_eol();
929 coords[1] = read_numeric(false);
930 while (!isdigit(ch) && ch != '+' && ch != '-' &&
931 ch != '.' && ch != ']' && ch != EOF) {
932 nextch_handling_eol();
934 coords[2] = read_numeric(false);
935 if (in_feet) {
936 coords[0] *= METRES_PER_FOOT;
937 coords[1] *= METRES_PER_FOOT;
938 coords[2] *= METRES_PER_FOOT;
940 int fix_result = fix_station(name, coords);
941 if (fix_result) {
942 filepos fp;
943 get_pos(&fp);
944 set_pos(&fp_name);
945 if (fix_result < 0) {
946 compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Station already fixed or equated to a fixed point*/46);
947 } else {
948 compile_diagnostic(DIAG_WARN|DIAG_WORD, /*Station already fixed at the same coordinates*/55);
950 set_pos(&fp);
951 compile_diagnostic_pfx(DIAG_INFO, name, /*Previously fixed or equated here*/493);
953 while (ch != ']' && ch != EOF) nextch_handling_eol();
954 if (ch == ']') {
955 nextch_handling_eol();
956 skipblanks();
958 } else {
959 /* FIXME: link station - ignore for now */
960 /* FIXME: perhaps issue warning? Other station names
961 * can be "reused", which is problematic... */
963 while (ch != ',' && ch != ';' && ch != EOF)
964 nextch_handling_eol();
967 break;
969 case '$':
970 /* UTM zone */
971 nextch();
972 skipblanks();
973 utm_zone = read_int(-60, 60);
974 skipblanks();
975 if (ch == ';') nextch_handling_eol();
977 update_proj_str:
978 if (!pcs->next || pcs->proj_str != pcs->next->proj_str)
979 osfree(pcs->proj_str);
980 pcs->proj_str = NULL;
981 if (datum && utm_zone && abs(utm_zone) <= 60) {
982 /* Set up coordinate system. */
983 char *proj_str = img_compass_utm_proj_str(datum, utm_zone);
984 if (proj_str) {
985 pcs->proj_str = proj_str;
986 if (!proj_str_out) {
987 proj_str_out = osstrdup(proj_str);
991 invalidate_pj_cached();
992 break;
993 case '&': {
994 /* Datum */
995 string p = S_INIT;
996 int datum_len = 0;
997 int c = 0;
998 nextch();
999 skipblanks();
1000 while (ch != ';' && !isEol(ch)) {
1001 s_catchar(&p, (char)ch);
1002 ++c;
1003 /* Ignore trailing blanks. */
1004 if (!isBlank(ch)) datum_len = c;
1005 nextch();
1007 if (ch == ';') nextch_handling_eol();
1008 datum = img_parse_compass_datum_string(s_str(&p), datum_len);
1009 s_free(&p);
1010 goto update_proj_str;
1012 case '[': {
1013 // Enter subdirectory.
1014 struct mak_folder *p = folder_stack;
1015 folder_stack = osnew(struct mak_folder);
1016 folder_stack->next = p;
1017 folder_stack->len = s_len(&path);
1018 if (!s_empty(&path))
1019 s_catchar(&path, FNM_SEP_LEV);
1020 nextch();
1021 while (ch != ';' && !isEol(ch)) {
1022 if (ch == '\\') {
1023 ch = FNM_SEP_LEV;
1025 s_catchar(&path, (char)ch);
1026 nextch();
1028 if (ch == ';') nextch_handling_eol();
1029 break;
1031 case ']': {
1032 // Leave subdirectory.
1033 struct mak_folder *p = folder_stack;
1034 if (folder_stack == NULL) {
1035 // FIXME: Error? Check what Compass does.
1036 break;
1038 s_truncate(&path, folder_stack->len);
1039 folder_stack = folder_stack->next;
1040 osfree(p);
1041 nextch();
1042 skipblanks();
1043 if (ch == ';') nextch_handling_eol();
1044 break;
1046 case '@': {
1047 /* "Base Location" to calculate magnetic declination at:
1048 * UTM East, UTM North, Elevation, UTM Zone, Convergence Angle
1049 * The first three are in metres.
1051 nextch();
1052 real easting = read_numeric(false);
1053 skipblanks();
1054 if (ch != ',') break;
1055 nextch();
1056 real northing = read_numeric(false);
1057 skipblanks();
1058 if (ch != ',') break;
1059 nextch();
1060 real elevation = read_numeric(false);
1061 skipblanks();
1062 if (ch != ',') break;
1063 nextch();
1064 int zone = read_int(-60, 60);
1065 skipblanks();
1066 if (ch != ',') break;
1067 nextch();
1068 real convergence_angle = read_numeric(false);
1069 /* We've now read them all successfully so store them. The
1070 * Compass documentation gives an example which specifies the
1071 * datum *AFTER* the base location, so we need to convert lazily.
1073 base_x = easting;
1074 base_y = northing;
1075 base_z = elevation;
1076 base_utm_zone = zone;
1077 base_line = file.line;
1078 base_lpos = file.lpos;
1079 // We ignore the stored UTM grid convergence angle since we get
1080 // this from PROJ.
1081 (void)convergence_angle;
1082 if (ch == ';') nextch_handling_eol();
1083 break;
1085 default:
1086 nextch_handling_eol();
1087 break;
1091 while (folder_stack) {
1092 // FIXME: Error? Check what Compass does.
1093 struct mak_folder *next = folder_stack->next;
1094 osfree(folder_stack);
1095 folder_stack = next;
1098 pop_settings();
1099 s_free(&path);
1102 // The current Walls reference point and CRS details.
1103 static struct {
1104 real x, y, z;
1105 int zone;
1106 // img_DATUM_* code from src/img.h or -1 if no .REF in effect.
1107 int img_datum_code;
1108 } walls_ref = { 0.0, 0.0, 0.0, 0, -1 };
1110 // We don't expect a huge number of macros, and this doesn't limit how many we
1111 // can handle, only at what point access time stops being O(1).
1112 #define WALLS_MACRO_HASH_SIZE 0x100
1114 typedef struct walls_macro {
1115 struct walls_macro *next;
1116 char *name;
1117 char *value;
1118 } walls_macro;
1120 // Macros set in the WPJ persist, but those set in an SRV only apply for that
1121 // SRV so we keep a table for each and when expanding them in the SRV we use
1122 // a definition from the SRV file in preference to one from the WPJ file.
1124 // Definitions actually also get added to walls_macros, but we swap the tables
1125 // around both before and after processing an SRV file (and we clear the
1126 // table from the SRV file at the end of the file).
1128 // Testing with Walls, macro definitions are NOT affected by SAVE, RESTORE or
1129 // RESET.
1130 static walls_macro **walls_macros_wpj = NULL;
1131 static walls_macro **walls_macros = NULL;
1133 static void
1134 walls_swap_macro_tables()
1136 walls_macro **tmp = walls_macros_wpj;
1137 walls_macros_wpj = walls_macros;
1138 walls_macros = tmp;
1141 // Takes ownership of the contents of p_name and of value.
1142 // Passing NULL for value sets empty string.
1143 static void
1144 walls_set_macro(walls_macro ***table, string *p_name, char *val)
1146 //printf("MACRO: $|%s|=\"%s\":\n", name, val);
1147 if (!*table) {
1148 *table = osmalloc(WALLS_MACRO_HASH_SIZE * ossizeof(walls_macro*));
1149 for (size_t i = 0; i < WALLS_MACRO_HASH_SIZE; i++)
1150 (*table)[i] = NULL;
1153 unsigned h = hash_data(s_str(p_name), s_len(p_name)) &
1154 (WALLS_MACRO_HASH_SIZE - 1);
1155 walls_macro *p = (*table)[h];
1156 while (p) {
1157 if (s_eq(p_name, p->name)) {
1158 // Update existing definition of macro.
1159 s_free(p_name);
1160 osfree(p->value);
1161 p->value = val;
1162 return;
1164 p = p->next;
1167 walls_macro *entry = osnew(walls_macro);
1168 entry->name = s_steal(p_name);
1169 entry->value = val;
1170 entry->next = (*table)[h];
1171 (*table)[h] = entry;
1174 // Returns NULL if not set.
1175 static const char*
1176 walls_get_macro(walls_macro ***table, const char *name, int name_len)
1178 if (!*table) return NULL;
1180 unsigned h = hash_data(name, name_len) & (WALLS_MACRO_HASH_SIZE - 1);
1181 walls_macro *p = (*table)[h];
1182 while (p) {
1183 if (strcmp(p->name, name) == 0) {
1184 return p->value ? p->value : "";
1186 p = p->next;
1189 return NULL;
1192 typedef enum {
1193 WALLS_CMD_DATE,
1194 WALLS_CMD_FLAG,
1195 WALLS_CMD_FIX,
1196 WALLS_CMD_NOTE,
1197 WALLS_CMD_PREFIX,
1198 WALLS_CMD_PREFIX2,
1199 WALLS_CMD_PREFIX3,
1200 WALLS_CMD_SEGMENT,
1201 WALLS_CMD_SYMBOL,
1202 WALLS_CMD_UNITS,
1203 WALLS_CMD_NULL = -1
1204 } walls_cmd;
1206 static const sztok walls_cmd_tab[] = {
1207 {"DATE", WALLS_CMD_DATE},
1208 {"F", WALLS_CMD_FLAG}, // Abbreviated form.
1209 {"FIX", WALLS_CMD_FIX},
1210 {"FLAG", WALLS_CMD_FLAG},
1211 {"N", WALLS_CMD_NOTE}, // Abbreviated form.
1212 {"NOTE", WALLS_CMD_NOTE},
1213 {"P", WALLS_CMD_PREFIX}, // Abbreviated form.
1214 {"PREFIX", WALLS_CMD_PREFIX},
1215 {"PREFIX1", WALLS_CMD_PREFIX}, // Alias.
1216 {"PREFIX2", WALLS_CMD_PREFIX2},
1217 {"PREFIX3", WALLS_CMD_PREFIX3},
1218 {"S", WALLS_CMD_SEGMENT}, // Abbreviated form.
1219 {"SEG", WALLS_CMD_SEGMENT}, // Abbreviated form.
1220 {"SEGMENT", WALLS_CMD_SEGMENT},
1221 {"SYM", WALLS_CMD_SYMBOL},
1222 {"SYMBOL", WALLS_CMD_SYMBOL},
1223 {"U", WALLS_CMD_UNITS}, // Abbreviated form.
1224 {"UNITS", WALLS_CMD_UNITS},
1225 {NULL, WALLS_CMD_NULL}
1228 typedef enum {
1229 WALLS_UNITS_OPT_A,
1230 WALLS_UNITS_OPT_AB,
1231 WALLS_UNITS_OPT_CASE,
1232 WALLS_UNITS_OPT_CT,
1233 WALLS_UNITS_OPT_D,
1234 WALLS_UNITS_OPT_DECL,
1235 WALLS_UNITS_OPT_FEET,
1236 WALLS_UNITS_OPT_FLAG,
1237 WALLS_UNITS_OPT_GRID,
1238 WALLS_UNITS_OPT_INCA,
1239 WALLS_UNITS_OPT_INCAB,
1240 WALLS_UNITS_OPT_INCD,
1241 WALLS_UNITS_OPT_INCH,
1242 WALLS_UNITS_OPT_INCV,
1243 WALLS_UNITS_OPT_INCVB,
1244 WALLS_UNITS_OPT_LRUD,
1245 WALLS_UNITS_OPT_METERS,
1246 WALLS_UNITS_OPT_ORDER,
1247 WALLS_UNITS_OPT_PREFIX,
1248 WALLS_UNITS_OPT_PREFIX2,
1249 WALLS_UNITS_OPT_PREFIX3,
1250 WALLS_UNITS_OPT_RECT,
1251 WALLS_UNITS_OPT_RESET,
1252 WALLS_UNITS_OPT_RESTORE,
1253 WALLS_UNITS_OPT_S,
1254 WALLS_UNITS_OPT_SAVE,
1255 WALLS_UNITS_OPT_TAPE,
1256 WALLS_UNITS_OPT_TYPEAB,
1257 WALLS_UNITS_OPT_TYPEVB,
1258 WALLS_UNITS_OPT_UV,
1259 WALLS_UNITS_OPT_UVH,
1260 WALLS_UNITS_OPT_UVV,
1261 WALLS_UNITS_OPT_V,
1262 WALLS_UNITS_OPT_VB,
1263 WALLS_UNITS_OPT_NULL = -1
1264 } walls_units_opt;
1266 // The aliases AZIMUTH, DISTANCE and VERTICAL don't seem to be documented.
1267 // They were found from `strings Walls32.exe` and then testing.
1268 static const sztok walls_units_opt_tab[] = {
1269 {"A", WALLS_UNITS_OPT_A},
1270 {"AB", WALLS_UNITS_OPT_AB},
1271 {"AZIMUTH", WALLS_UNITS_OPT_A}, // Alias (undocumented).
1272 {"CASE", WALLS_UNITS_OPT_CASE},
1273 {"CT", WALLS_UNITS_OPT_CT},
1274 {"D", WALLS_UNITS_OPT_D},
1275 {"DECL", WALLS_UNITS_OPT_DECL},
1276 {"DISTANCE", WALLS_UNITS_OPT_D}, // Alias (undocumented).
1277 {"F", WALLS_UNITS_OPT_FEET}, // Abbreviated form.
1278 {"FEET", WALLS_UNITS_OPT_FEET},
1279 {"FLAG", WALLS_UNITS_OPT_FLAG},
1280 {"GRID", WALLS_UNITS_OPT_GRID},
1281 {"INCA", WALLS_UNITS_OPT_INCA},
1282 {"INCAB", WALLS_UNITS_OPT_INCAB},
1283 {"INCD", WALLS_UNITS_OPT_INCD},
1284 {"INCH", WALLS_UNITS_OPT_INCH},
1285 {"INCV", WALLS_UNITS_OPT_INCV},
1286 {"INCVB", WALLS_UNITS_OPT_INCVB},
1287 {"LRUD", WALLS_UNITS_OPT_LRUD},
1288 {"M", WALLS_UNITS_OPT_METERS}, // Abbreviated form.
1289 {"METERS", WALLS_UNITS_OPT_METERS},
1290 // The Walls documentation mentions a NOTE option here:
1292 // Advanced or Seldom Used Parameters (LRUD=, CASE=, PREFIX=, TAPE=,
1293 // UV=, FLAG=, NOTE=, $name=)
1295 // However the documentation doesn't define it anywhere I can see, and
1296 // testing Walls32.exe it does not seem to be implemented either!
1297 // {"NOTE", WALLS_UNITS_OPT_NOTE},
1298 {"O", WALLS_UNITS_OPT_ORDER}, // Abbreviated form.
1299 {"ORDER", WALLS_UNITS_OPT_ORDER},
1300 {"P", WALLS_UNITS_OPT_PREFIX}, // Abbreviated form.
1301 {"PREFIX", WALLS_UNITS_OPT_PREFIX},
1302 {"PREFIX1", WALLS_UNITS_OPT_PREFIX}, // Alias.
1303 {"PREFIX2", WALLS_UNITS_OPT_PREFIX2},
1304 {"PREFIX3", WALLS_UNITS_OPT_PREFIX3},
1305 {"RECT", WALLS_UNITS_OPT_RECT},
1306 {"RESET", WALLS_UNITS_OPT_RESET},
1307 {"RESTORE", WALLS_UNITS_OPT_RESTORE},
1308 {"S", WALLS_UNITS_OPT_S},
1309 {"SAVE", WALLS_UNITS_OPT_SAVE},
1310 {"TAPE", WALLS_UNITS_OPT_TAPE},
1311 {"TYPEAB", WALLS_UNITS_OPT_TYPEAB},
1312 {"TYPEVB", WALLS_UNITS_OPT_TYPEVB},
1313 {"UV", WALLS_UNITS_OPT_UV},
1314 {"UVH", WALLS_UNITS_OPT_UVH},
1315 {"UVV", WALLS_UNITS_OPT_UVV},
1316 {"V", WALLS_UNITS_OPT_V},
1317 {"VB", WALLS_UNITS_OPT_VB},
1318 {"VERTICAL", WALLS_UNITS_OPT_V}, // Alias (undocumented).
1319 {NULL, WALLS_UNITS_OPT_NULL}
1322 #define WALLS_ORDER_CT(R1,R2,R3) ((R1) | ((R2) << 8) | ((R3) << 16))
1323 #define WALLS_ORDER_RECT(R1,R2,R3) ((R1) | ((R2) << 8) | ((R3) << 16) | (1 << 24))
1325 // Here we rely on the integer values of the reading codes used fitting in
1326 // a byte so assert that is the case.
1327 typedef int compiletimeassert_order_byte_encoding_ok[
1328 (WallsSRVTape|WallsSRVComp|WallsSRVClino|Dx|Dy|Dz) < 0x100 ? 1 : -1];
1330 static const sztok walls_order_tab[] = {
1331 {"AD", WALLS_ORDER_CT(WallsSRVComp, WallsSRVTape, 0)},
1332 {"ADV", WALLS_ORDER_CT(WallsSRVComp, WallsSRVTape, WallsSRVClino)},
1333 {"AVD", WALLS_ORDER_CT(WallsSRVComp, WallsSRVClino, WallsSRVTape)},
1334 {"DA", WALLS_ORDER_CT(WallsSRVTape, WallsSRVComp, 0)},
1335 {"DAV", WALLS_ORDER_CT(WallsSRVTape, WallsSRVComp, WallsSRVClino)},
1336 {"DVA", WALLS_ORDER_CT(WallsSRVTape, WallsSRVClino, WallsSRVComp)},
1337 {"EN", WALLS_ORDER_RECT(Dx, Dy, 0)},
1338 {"ENU", WALLS_ORDER_RECT(Dx, Dy, Dz)},
1339 {"EUN", WALLS_ORDER_RECT(Dx, Dz, Dy)},
1340 {"NE", WALLS_ORDER_RECT(Dy, Dx, 0)},
1341 {"NEU", WALLS_ORDER_RECT(Dy, Dx, Dz)},
1342 {"NUE", WALLS_ORDER_RECT(Dy, Dz, Dx)},
1343 {"UEN", WALLS_ORDER_RECT(Dz, Dx, Dy)},
1344 {"UNE", WALLS_ORDER_RECT(Dz, Dy, Dx)},
1345 {"VAD", WALLS_ORDER_CT(WallsSRVClino, WallsSRVComp, WallsSRVTape)},
1346 {"VDA", WALLS_ORDER_CT(WallsSRVClino, WallsSRVTape, WallsSRVComp)},
1347 {NULL, -1}
1350 // In #FLAG Walls seems to only document `/` but based on real-world use also
1351 // allows `\`. FIXME: Are there other places that allow `\`?
1352 static inline bool isWallsSlash(int c) { return c == '/' || c == '\\'; }
1354 // Walls-specific options. Options which Survex has a direct equivalent of
1355 // are stored in the `settings` struct instead.
1356 typedef struct walls_options {
1357 // The current values of the three prefix levels Walls supports.
1358 // NULL for any level not currently set (all NULL by default).
1359 char* prefix[3];
1361 // Data order for CT legs.
1362 reading data_order_ct[6];
1364 // Data order for RECT legs (also used for #Fix coordinate order).
1365 reading data_order_rect[6];
1367 // Is this from SAVE in .OPTIONS / #Units?
1368 bool explicit;
1370 // Flags to apply to stations in #FIX.
1371 int fix_station_flags;
1373 string path;
1375 struct walls_options *next;
1376 } walls_options;
1378 static walls_options* p_walls_options;
1380 static const walls_options walls_options_default = {
1381 // prefix[3]
1382 { NULL, NULL, NULL },
1384 // data_order_ct[6]
1386 WallsSRVFr, WallsSRVTo, WallsSRVTape, WallsSRVComp, WallsSRVClino,
1387 // FIXME CompassDATLeft, CompassDATUp, CompassDATDown, CompassDATRight,
1388 IgnoreAll
1391 // data_order_rect[6]
1393 WallsSRVFr, WallsSRVTo, Dx, Dy, Dz,
1394 // FIXME CompassDATLeft, CompassDATUp, CompassDATDown, CompassDATRight,
1395 IgnoreAll
1398 // explicit
1399 false,
1401 // fix_station_flags
1404 // path
1405 S_INIT,
1407 // next
1408 NULL
1411 static void
1412 push_walls_options(void)
1414 settings *pcsNew = osnew(settings);
1415 *pcsNew = *pcs; /* copy contents */
1416 pcsNew->begin_lineno = file.line;
1417 pcsNew->next = pcs;
1418 pcs = pcsNew;
1420 // Walls-specific settings.
1421 walls_options *new_options = osnew(walls_options);
1422 if (!p_walls_options) {
1423 *new_options = walls_options_default;
1424 } else {
1425 *new_options = *p_walls_options; /* copy contents */
1426 // There's only 3 prefix levels and typically only one seems to be
1427 // actually set so just copy the strings rather than trying to do
1428 // copy-on-write.
1429 for (int i = 0; i < 3; ++i) {
1430 if (new_options->prefix[i])
1431 new_options->prefix[i] = osstrdup(new_options->prefix[i]);
1433 // Actually copy path. FIXME: Maybe copy on write?
1434 string empty_string = S_INIT;
1435 new_options->path = empty_string;
1436 s_cats(&new_options->path, &p_walls_options->path);
1439 new_options->next = p_walls_options;
1440 p_walls_options = new_options;
1443 static void
1444 pop_walls_options(void)
1446 pcs->ordering = NULL; /* Avoid free() of static array. */
1447 pop_settings();
1448 walls_options *p = p_walls_options;
1449 p_walls_options = p_walls_options->next;
1450 for (int i = 0; i < 3; ++i) {
1451 osfree(p->prefix[i]);
1453 s_free(&p->path);
1454 osfree(p);
1457 static void
1458 walls_initialise_settings(void)
1460 push_walls_options();
1462 // Generic settings.
1463 short *t = ((short*)osmalloc(ossizeof(short) * 257)) + 1;
1464 // "Unprefixed names can have a maximum of eight characters and must not
1465 // contain any colons, semicolons, commas, pound signs (#), or embedded
1466 // tabs or spaces. In order to avoid possible problems when printing or
1467 // when exporting data to other programs, you are encouraged to restrict
1468 // names in new surveys to numbers with alphabetic prefixes or suffixes
1469 // (e.g., BR123)."
1471 // However in practice, `#` actually seems to be allowed so we allow it.
1472 // It still can't be used as the first character of the from station as
1473 // there it will be interpreted as introducing a command.
1475 // We assume other control characters aren't allowed either (so nothing
1476 // < 32 and not 127), but allow all top-bit-set characters.
1477 t[EOF] = SPECIAL_EOL;
1478 memset(t, 0, sizeof(short) * 33);
1479 for (int i = 33; i < 127; i++) t[i] = SPECIAL_NAMES;
1480 t[127] = 0;
1481 for (int i = 128; i < 256; i++) t[i] = SPECIAL_NAMES;
1482 t[':'] = 0;
1483 t[';'] = SPECIAL_COMMENT;
1484 // FIXME: `,` seems to be treated like a space almost everywhere, but right
1485 // after a directive name a comma instead gives a warning suggesting a
1486 // parse error in a different directive.
1487 t[','] = SPECIAL_BLANK;
1488 t['\t'] |= SPECIAL_BLANK;
1489 t[' '] |= SPECIAL_BLANK;
1490 t['\032'] |= SPECIAL_EOL; /* Ctrl-Z, so olde DOS text files are handled ok */
1491 t['\n'] |= SPECIAL_EOL;
1492 t['\r'] |= SPECIAL_EOL;
1493 t['-'] |= SPECIAL_OMIT;
1494 t[':'] |= SPECIAL_SEPARATOR;
1495 t['.'] |= SPECIAL_DECIMAL;
1496 t['-'] |= SPECIAL_MINUS;
1497 t['+'] |= SPECIAL_PLUS;
1498 pcs->Translate = t;
1500 pcs->begin_lineno = 0;
1501 // Spec says "maximum of eight characters" - we currently allow arbitrarily
1502 // many.
1503 pcs->Truncate = INT_MAX;
1504 pcs->infer = BIT(INFER_EXPORTS) | // FIXME?
1505 BIT(INFER_PLUMBS);
1508 static void
1509 walls_reset(void)
1511 // "[S]et all parameters (including the current name prefix) to their
1512 // defaults" but not the segment. We currently ignore the segment anyway.
1513 pcs->Case = OFF;
1515 default_units(pcs);
1516 default_calib(pcs);
1517 // FIXME: pcs->z[Q_DECLINATION] = HUGE_REAL;
1519 pcs->recorded_style = pcs->style = STYLE_NORMAL;
1520 pcs->ordering = p_walls_options->data_order_ct;
1522 for (int i = 0; i < 3; ++i) {
1523 osfree(p_walls_options->prefix[i]);
1525 *p_walls_options = walls_options_default;
1528 static real
1529 read_walls_angle(real default_units)
1531 real angle = read_numeric(false);
1532 if (isalpha((unsigned char)ch)) {
1533 get_token_walls();
1534 // Only one letter is allowed here.
1535 if (s_str(&uctoken)[1] != '\0') goto bad_angle_units;
1536 if (s_str(&uctoken)[0] == 'D') {
1537 // Degrees.
1538 angle *= M_PI / 180.0;
1539 } else if (s_str(&uctoken)[0] == 'G') {
1540 // Grads.
1541 angle *= M_PI / 200.0;
1542 } else if (s_str(&uctoken)[0] == 'M') {
1543 // Mils.
1544 angle *= M_PI / 3200.0;
1545 } else {
1546 bad_angle_units:
1547 compile_diagnostic(DIAG_ERR|DIAG_COL,
1548 /*Expecting ā€œ%sā€, ā€œ%sā€, or ā€œ%sā€*/188,
1549 "D", "G", "M");
1551 } else {
1552 angle *= default_units;
1554 return angle;
1557 static real
1558 read_walls_distance(real default_units)
1560 real distance = read_numeric(false);
1561 if (isalpha((unsigned char)ch)) {
1562 get_token_walls();
1563 // Only one letter is allowed here.
1564 if (s_str(&uctoken)[1] != '\0') goto bad_distance_units;
1565 if (s_str(&uctoken)[0] == 'M') {
1566 // Metres.
1567 } else if (s_str(&uctoken)[0] == 'F') {
1568 // Feet.
1569 distance *= METRES_PER_FOOT;
1570 } else {
1571 bad_distance_units:
1572 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting ā€œ%sā€ or ā€œ%sā€*/103, "F", "M");
1574 } else {
1575 distance *= default_units;
1577 return distance;
1580 // Walls #FLAG values seem to be arbitrary strings - we attempt to infer
1581 // suitable Survex station flags from a few key words.
1582 static int parse_walls_flags(bool check_for_quote)
1584 //#define DEBUG_WALLS_FLAGS
1585 int station_flags = 0;
1586 bool quoted = false;
1587 if (check_for_quote) {
1588 skipblanks();
1589 if (ch == '"') {
1590 quoted = true;
1591 nextch();
1595 #ifdef DEBUG_WALLS_FLAGS
1596 bool printed = false;
1597 #endif
1598 while (1) {
1599 skipblanks();
1600 if (isComm(ch) || isEol(ch)) break;
1601 if (quoted && ch == '"') {
1602 nextch();
1603 break;
1605 get_token_walls();
1606 if (S_EQ(&uctoken, "ENTRANCE")) {
1607 station_flags |= BIT(SFLAGS_ENTRANCE);
1608 } else if (S_EQ(&uctoken, "FIX")) {
1609 station_flags |= BIT(SFLAGS_FIXED);
1610 } else if (s_empty(&token)) {
1611 nextch();
1612 #ifdef DEBUG_WALLS_FLAGS
1613 } else if (S_EQ(&uctoken, "LOWER") ||
1614 S_EQ(&uctoken, "LEAD") ||
1615 S_EQ(&uctoken, "SPRING") ||
1616 S_EQ(&uctoken, "RESURGENCE") ||
1617 S_EQ(&uctoken, "RISING") ||
1618 S_EQ(&uctoken, "SINK") ||
1619 S_EQ(&uctoken, "SWALLET") ||
1620 S_EQ(&uctoken, "SUMP") ||
1621 S_EQ(&uctoken, "SHAFT") ||
1622 S_EQ(&uctoken, "UPPER") ||
1623 S_EQ(&uctoken, "CAVE") ||
1624 S_EQ(&uctoken, "GPS") || // -> FIXED flag?
1625 S_EQ(&uctoken, "SURVEYED") ||
1626 S_EQ(&uctoken, "BATS") ||
1627 S_EQ(&uctoken, "MYOTIS") ||
1628 S_EQ(&uctoken, "VELIFER") ||
1629 S_EQ(&uctoken, "GATED") ||
1630 S_EQ(&uctoken, "0") ||
1631 S_EQ(&uctoken, "LOCATION")) {
1632 // With DEBUG_WALLS_FLAGS defined, run on real-world
1633 // data to capture more words which might also be usefully
1634 // mapped to Survex station flags.
1635 } else {
1636 if (!printed) {
1637 printed = true;
1638 printf("*** Unknown flags:");
1640 printf(" %s", s_str(&token));
1641 #endif
1643 if (check_for_quote) break;
1645 #ifdef DEBUG_WALLS_FLAGS
1646 if (printed) printf("\n");
1647 #endif
1648 return station_flags;
1651 static void
1652 parse_options(void)
1654 skipblanks();
1655 while (!isEol(ch)) {
1656 get_token_walls();
1657 if (s_empty(&token) && isComm(ch)) {
1658 break;
1660 filepos fp_option;
1661 get_pos(&fp_option);
1663 // Assign to typed variable so we get a warning if we are
1664 // missing a case below.
1665 walls_units_opt opt = match_tok(walls_units_opt_tab,
1666 TABSIZE(walls_units_opt_tab));
1667 switch (opt) {
1668 case WALLS_UNITS_OPT_D:
1669 case WALLS_UNITS_OPT_A:
1670 case WALLS_UNITS_OPT_AB:
1671 case WALLS_UNITS_OPT_V:
1672 case WALLS_UNITS_OPT_VB:
1673 case WALLS_UNITS_OPT_S:
1674 case WALLS_UNITS_OPT_ORDER:
1675 case WALLS_UNITS_OPT_DECL:
1676 case WALLS_UNITS_OPT_INCA:
1677 case WALLS_UNITS_OPT_INCAB:
1678 case WALLS_UNITS_OPT_INCD:
1679 case WALLS_UNITS_OPT_INCH:
1680 case WALLS_UNITS_OPT_INCV:
1681 case WALLS_UNITS_OPT_INCVB:
1682 case WALLS_UNITS_OPT_GRID:
1683 case WALLS_UNITS_OPT_CASE:
1684 case WALLS_UNITS_OPT_LRUD:
1685 case WALLS_UNITS_OPT_TAPE:
1686 case WALLS_UNITS_OPT_TYPEAB:
1687 case WALLS_UNITS_OPT_TYPEVB:
1688 case WALLS_UNITS_OPT_UV:
1689 case WALLS_UNITS_OPT_UVH:
1690 case WALLS_UNITS_OPT_UVV:
1691 // These options all require an argument, so check for `=` and
1692 // advance past it.
1693 skipblanks();
1694 if (ch != '=') {
1695 compile_diagnostic(DIAG_ERR|DIAG_COL,
1696 /*Expecting ā€œ%sā€*/497, "=");
1697 break;
1699 nextch();
1700 break;
1701 default:
1702 break;
1704 switch (opt) {
1705 case WALLS_UNITS_OPT_METERS:
1706 pcs->units[Q_LENGTH] =
1707 pcs->units[Q_DX] =
1708 pcs->units[Q_DY] =
1709 pcs->units[Q_DZ] = 1.0;
1710 break;
1711 case WALLS_UNITS_OPT_FEET:
1712 pcs->units[Q_LENGTH] =
1713 pcs->units[Q_DX] =
1714 pcs->units[Q_DY] =
1715 pcs->units[Q_DZ] = METRES_PER_FOOT;
1716 break;
1717 case WALLS_UNITS_OPT_D:
1718 get_token_walls();
1719 // From testing it seems Walls only checks the initial letter - e.g.
1720 // "M", "METERS", "METRES", "F", "FEET" and even "FISH" are accepted,
1721 // but "X" gives an error.
1722 if (s_str(&uctoken)[0] == 'M') {
1723 pcs->units[Q_LENGTH] = 1.0;
1724 } else if (s_str(&uctoken)[0] == 'F') {
1725 pcs->units[Q_LENGTH] = METRES_PER_FOOT;
1726 } else {
1727 filepos fp;
1728 get_pos(&fp);
1729 set_pos(&fp_option);
1730 (void)nextch(); // Skip the `=`.
1731 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting ā€œ%sā€ or ā€œ%sā€*/103, "F", "M");
1732 set_pos(&fp);
1734 break;
1735 case WALLS_UNITS_OPT_A:
1736 get_token_walls();
1737 // It seems Walls only checks the initial letter.
1738 if (s_str(&uctoken)[0] == 'D') {
1739 // Degrees.
1740 pcs->units[Q_BEARING] = M_PI / 180.0;
1741 } else if (s_str(&uctoken)[0] == 'G') {
1742 // Grads.
1743 pcs->units[Q_BEARING] = M_PI / 200.0;
1744 } else if (s_str(&uctoken)[0] == 'M') {
1745 // Mils.
1746 pcs->units[Q_BEARING] = M_PI / 3200.0;
1747 } else {
1748 filepos fp;
1749 get_pos(&fp);
1750 set_pos(&fp_option);
1751 (void)nextch(); // Skip the `=`.
1752 compile_diagnostic(DIAG_ERR|DIAG_COL,
1753 /*Expecting ā€œ%sā€, ā€œ%sā€, or ā€œ%sā€*/188,
1754 "D", "G", "M");
1755 set_pos(&fp);
1757 break;
1758 case WALLS_UNITS_OPT_AB:
1759 get_token_walls();
1760 // It seems Walls only checks the initial letter.
1761 if (s_str(&uctoken)[0] == 'D') {
1762 // Degrees.
1763 pcs->units[Q_BACKBEARING] = M_PI / 180.0;
1764 } else if (s_str(&uctoken)[0] == 'G') {
1765 // Grads.
1766 pcs->units[Q_BACKBEARING] = M_PI / 200.0;
1767 } else if (s_str(&uctoken)[0] == 'M') {
1768 // Mils.
1769 pcs->units[Q_BACKBEARING] = M_PI / 3200.0;
1770 } else {
1771 filepos fp;
1772 get_pos(&fp);
1773 set_pos(&fp_option);
1774 (void)nextch(); // Skip the `=`.
1775 compile_diagnostic(DIAG_ERR|DIAG_COL,
1776 /*Expecting ā€œ%sā€, ā€œ%sā€, or ā€œ%sā€*/188,
1777 "D", "G", "M");
1778 set_pos(&fp);
1780 break;
1781 case WALLS_UNITS_OPT_V:
1782 get_token_walls();
1783 pcs->f_clino_percent = false;
1784 // It seems Walls only checks the initial letter.
1785 if (s_str(&uctoken)[0] == 'D') {
1786 // Degrees.
1787 pcs->units[Q_GRADIENT] = M_PI / 180.0;
1788 } else if (s_str(&uctoken)[0] == 'G') {
1789 // Grads.
1790 pcs->units[Q_GRADIENT] = M_PI / 200.0;
1791 } else if (s_str(&uctoken)[0] == 'M') {
1792 // Mils.
1793 pcs->units[Q_GRADIENT] = M_PI / 3200.0;
1794 } else if (s_str(&uctoken)[0] == 'P') {
1795 pcs->units[Q_GRADIENT] = 0.01;
1796 pcs->f_clino_percent = true;
1797 } else {
1798 filepos fp;
1799 get_pos(&fp);
1800 set_pos(&fp_option);
1801 (void)nextch(); // Skip the `=`.
1802 compile_diagnostic(DIAG_ERR|DIAG_COL,
1803 /*Expecting ā€œ%sā€, ā€œ%sā€, ā€œ%sā€, or ā€œ%sā€*/189,
1804 "D", "G", "M", "P");
1805 set_pos(&fp);
1807 break;
1808 case WALLS_UNITS_OPT_VB:
1809 get_token_walls();
1810 pcs->f_backclino_percent = false;
1811 // It seems Walls only checks the initial letter.
1812 if (s_str(&uctoken)[0] == 'D') {
1813 // Degrees.
1814 pcs->units[Q_BACKGRADIENT] = M_PI / 180.0;
1815 } else if (s_str(&uctoken)[0] == 'G') {
1816 // Grads.
1817 pcs->units[Q_BACKGRADIENT] = M_PI / 200.0;
1818 } else if (s_str(&uctoken)[0] == 'M') {
1819 // Mils.
1820 pcs->units[Q_BACKGRADIENT] = M_PI / 3200.0;
1821 } else if (s_str(&uctoken)[0] == 'P') {
1822 pcs->units[Q_BACKGRADIENT] = 0.01;
1823 pcs->f_backclino_percent = true;
1824 } else {
1825 filepos fp;
1826 get_pos(&fp);
1827 set_pos(&fp_option);
1828 (void)nextch(); // Skip the `=`.
1829 compile_diagnostic(DIAG_ERR|DIAG_COL,
1830 /*Expecting ā€œ%sā€, ā€œ%sā€, ā€œ%sā€, or ā€œ%sā€*/189,
1831 "D", "G", "M", "P");
1832 set_pos(&fp);
1834 break;
1835 case WALLS_UNITS_OPT_S:
1836 get_token_walls();
1837 // From testing it seems Walls only checks the initial letter - e.g.
1838 // "M", "METERS", "METRES", "F", "FEET" and even "FISH" are accepted,
1839 // but "X" gives an error.
1840 if (s_str(&uctoken)[0] == 'M') {
1841 pcs->units[Q_DX] =
1842 pcs->units[Q_DY] =
1843 pcs->units[Q_DZ] = 1.0;
1844 } else if (s_str(&uctoken)[0] == 'F') {
1845 pcs->units[Q_DX] =
1846 pcs->units[Q_DY] =
1847 pcs->units[Q_DZ] = METRES_PER_FOOT;
1848 } else {
1849 filepos fp;
1850 get_pos(&fp);
1851 set_pos(&fp_option);
1852 (void)nextch(); // Skip the `=`.
1853 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting ā€œ%sā€ or ā€œ%sā€*/103, "F", "M");
1854 set_pos(&fp);
1856 break;
1857 case WALLS_UNITS_OPT_ORDER:
1858 get_token_walls();
1859 int order = match_tok(walls_order_tab,
1860 TABSIZE(walls_order_tab));
1861 if (order < 0) {
1862 compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Data style ā€œ%sā€ unknown*/65, s_str(&token));
1863 break;
1865 reading* p;
1866 if (order & (1 << 24)) {
1867 order &= ((1 << 24) - 1);
1868 // "RECT" order.
1869 p = p_walls_options->data_order_rect + 2;
1870 } else {
1871 // "CT" order.
1872 p = p_walls_options->data_order_ct + 2;
1874 while (order) {
1875 *p++ = (order & 0xff);
1876 order >>= 8;
1878 *p = IgnoreAll;
1879 break;
1880 case WALLS_UNITS_OPT_DECL:
1881 //pcs->declination = HUGE_REAL;
1882 // if (pcs->dec_filename == NULL) {
1883 pcs->z[Q_DECLINATION] = -read_walls_angle(M_PI / 180.0);
1884 // } else {
1885 // (void)read_numeric(false);
1886 // }
1887 break;
1888 case WALLS_UNITS_OPT_INCA:
1889 pcs->z[Q_BEARING] = -read_walls_angle(pcs->units[Q_BEARING]);
1890 break;
1891 case WALLS_UNITS_OPT_INCAB:
1892 pcs->z[Q_BACKBEARING] = -read_walls_angle(pcs->units[Q_BACKBEARING]);
1893 break;
1894 case WALLS_UNITS_OPT_INCD:
1895 pcs->z[Q_LENGTH] = -read_walls_distance(pcs->units[Q_LENGTH]);
1896 break;
1897 case WALLS_UNITS_OPT_INCH:
1898 // INCH=0 is what we do anyway, so only warn about non-zero values.
1899 if (read_walls_distance(pcs->units[Q_LENGTH]) != 0.0) {
1900 filepos fp;
1901 get_pos(&fp);
1902 set_pos(&fp_option);
1903 compile_diagnostic(DIAG_WARN|DIAG_TOKEN, /*Unknown command ā€œ%sā€*/12, s_str(&token));
1904 set_pos(&fp);
1906 break;
1907 case WALLS_UNITS_OPT_INCV:
1908 pcs->z[Q_GRADIENT] = -read_walls_angle(pcs->units[Q_GRADIENT]);
1909 break;
1910 case WALLS_UNITS_OPT_INCVB:
1911 pcs->z[Q_BACKGRADIENT] = -read_walls_angle(pcs->units[Q_BACKGRADIENT]);
1912 break;
1913 case WALLS_UNITS_OPT_GRID:
1914 // FIXME: GRID= not useful with geo-referenced data?
1915 compile_diagnostic(DIAG_WARN|DIAG_TOKEN, /*Unknown command ā€œ%sā€*/12, s_str(&token));
1916 (void)read_walls_angle(M_PI / 180.0);
1917 break;
1918 case WALLS_UNITS_OPT_RECT:
1919 // There are two different RECT options, one with a
1920 // parameter and one without!
1921 skipblanks();
1922 if (ch == '=') {
1923 // FIXME: A bearing to rotate cartesian data by, which we don't
1924 // currently support. 0 means true North (Survex always uses
1925 // grid North currently).
1926 compile_diagnostic(DIAG_WARN|DIAG_TOKEN, /*Unknown command ā€œ%sā€*/12, s_str(&token));
1927 nextch();
1928 (void)read_walls_angle(M_PI / 180.0);
1929 } else {
1930 pcs->recorded_style = pcs->style = STYLE_CARTESIAN;
1931 pcs->ordering = p_walls_options->data_order_rect;
1933 break;
1934 case WALLS_UNITS_OPT_CASE:
1935 get_token_walls();
1936 // Walls documents `CASE = Upper / Lower / Mixed` which hints that
1937 // it only actually tests the first character. It also seems that
1938 // any other character is treated as `Mixed` too.
1939 switch (s_str(&uctoken)[0]) {
1940 case 'L':
1941 pcs->Case = LOWER;
1942 break;
1943 case 'U':
1944 pcs->Case = UPPER;
1945 break;
1946 default:
1947 pcs->Case = OFF;
1948 break;
1950 break;
1951 case WALLS_UNITS_OPT_CT:
1952 pcs->recorded_style = pcs->style = STYLE_NORMAL;
1953 pcs->ordering = p_walls_options->data_order_ct;
1954 break;
1955 case WALLS_UNITS_OPT_PREFIX:
1956 case WALLS_UNITS_OPT_PREFIX2:
1957 case WALLS_UNITS_OPT_PREFIX3: {
1958 // PREFIX, etc without a value clear that level of the prefix.
1959 char *new_prefix = NULL;
1960 skipblanks();
1961 if (ch == '=') {
1962 nextch();
1963 new_prefix = read_walls_prefix();
1965 int i = (int)WALLS_UNITS_OPT_PREFIX3 - (int)opt;
1966 osfree(p_walls_options->prefix[i]);
1967 p_walls_options->prefix[i] = new_prefix;
1968 break;
1970 case WALLS_UNITS_OPT_LRUD: {
1971 // Value is F, T, FB, TB or one of those followed by eg. :UDRL (no
1972 // spaces around :). Default is F:LRUD.
1974 // We currently ignore LRUD, so can also ignore the settings for
1975 // it.
1976 string val = S_INIT;
1977 read_string(&val);
1978 s_free(&val);
1979 break;
1981 case WALLS_UNITS_OPT_TAPE:
1982 get_token_walls();
1983 /* FIXME: Implement different taping methods? */
1984 /* IT, SS, IS, ST (default is IT). */
1985 break;
1986 case WALLS_UNITS_OPT_TYPEAB:
1987 get_token_walls();
1988 if (s_str(&uctoken)[0] == 'N') {
1989 pcs->z[Q_BACKBEARING] = 0.0;
1990 } else if (s_str(&uctoken)[0] == 'C') {
1991 pcs->z[Q_BACKBEARING] = M_PI;
1992 } else {
1993 filepos fp;
1994 get_pos(&fp);
1995 set_pos(&fp_option);
1996 (void)nextch(); // Skip the `=`.
1997 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting ā€œ%sā€ or ā€œ%sā€*/103, "C", "N");
1998 set_pos(&fp);
2000 if (ch == ',') {
2001 nextch();
2002 // FIXME: Use threshold value.
2003 (void)read_numeric(false);
2004 if (!isBlank(ch) && !isComm(ch) && !isEol(ch)) {
2005 // Walls quietly ignores junk after a valid number here.
2006 get_word();
2007 compile_diagnostic(DIAG_WARN|DIAG_TOKEN, /*Ignoring ā€œ%sā€*/506, s_str(&token));
2009 if (ch == ',') {
2010 nextch();
2011 if (toupper(ch) == 'X') {
2012 nextch();
2013 // FIXME: Only use foresight (but check backsight).
2017 break;
2018 case WALLS_UNITS_OPT_TYPEVB:
2019 get_token_walls();
2020 if (s_str(&uctoken)[0] == 'N') {
2021 pcs->sc[Q_BACKGRADIENT] = 1.0;
2022 } else if (s_str(&uctoken)[0] == 'C') {
2023 pcs->sc[Q_BACKGRADIENT] = -1.0;
2024 } else {
2025 filepos fp;
2026 get_pos(&fp);
2027 set_pos(&fp_option);
2028 (void)nextch(); // Skip the `=`.
2029 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting ā€œ%sā€ or ā€œ%sā€*/103, "C", "N");
2030 set_pos(&fp);
2032 if (ch == ',') {
2033 nextch();
2034 // FIXME: Use threshold value.
2035 (void)read_numeric(false);
2036 if (!isBlank(ch) && !isComm(ch) && !isEol(ch)) {
2037 // Walls quietly ignores junk after a valid number here.
2038 get_word();
2039 compile_diagnostic(DIAG_WARN|DIAG_TOKEN, /*Ignoring ā€œ%sā€*/506, s_str(&token));
2041 if (ch == ',') {
2042 nextch();
2043 if (toupper(ch) == 'X') {
2044 nextch();
2045 // FIXME: Only use foresight (but check backsight).
2049 break;
2050 case WALLS_UNITS_OPT_UV:
2051 case WALLS_UNITS_OPT_UVH:
2052 case WALLS_UNITS_OPT_UVV:
2053 // Scale factors for variances (with horizontal-only and
2054 // vertical-only variants). FIXME: Actually apply these!
2055 (void)read_numeric(false);
2056 if (!isBlank(ch) && !isComm(ch) && !isEol(ch)) {
2057 // Walls quietly ignores junk after a valid number here.
2058 get_word();
2059 compile_diagnostic(DIAG_WARN|DIAG_TOKEN, /*Ignoring ā€œ%sā€*/506, s_str(&token));
2061 break;
2062 case WALLS_UNITS_OPT_FLAG:
2063 // Default flag to apply to stations in #FIX.
2064 skipblanks();
2065 if (ch == '=') {
2066 nextch();
2067 p_walls_options->fix_station_flags = parse_walls_flags(true);
2068 } else {
2069 p_walls_options->fix_station_flags = 0;
2071 break;
2072 case WALLS_UNITS_OPT_RESET:
2073 // FIXME: This should be processed before other arguments!
2074 walls_reset();
2075 break;
2076 case WALLS_UNITS_OPT_RESTORE: {
2077 // FIXME: Should this be processed before other arguments?
2078 if (!p_walls_options->explicit) {
2079 /* TRANSLATORS: %s is replaced with e.g. BEGIN or .BOOK or #[ */
2080 compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*No matching %s*/192, "SAVE");
2081 break;
2083 pop_walls_options();
2084 break;
2086 case WALLS_UNITS_OPT_SAVE: {
2087 // FIXME: This should be processed before other arguments!
2088 push_walls_options();
2089 p_walls_options->explicit = true;
2090 break;
2092 case WALLS_UNITS_OPT_NULL:
2093 if (s_str(&uctoken)[0] == '\0' && ch == '$') {
2094 // Macro definition.
2095 filepos fp;
2096 get_pos(&fp);
2097 nextch();
2098 string name = S_INIT;
2099 while (!isBlank(ch) && !isComm(ch) && !isEol(ch) && ch != '=') {
2100 s_catchar(&name, ch);
2101 nextch();
2103 if (!s_empty(&name)) {
2104 skipblanks();
2105 if (ch != '=') {
2106 // Set an empty value.
2107 walls_set_macro(&walls_macros, &name, NULL);
2108 } else {
2109 nextch();
2110 string val = S_INIT;
2111 read_string(&val);
2112 walls_set_macro(&walls_macros, &name, s_steal(&val));
2114 break;
2116 s_free(&name);
2117 set_pos(&fp);
2118 s_clear(&token);
2120 compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Unknown command ā€œ%sā€*/12, s_str(&token));
2121 if (ch == '=') {
2122 // Skip over `=` and the rest of the argument so we handle a
2123 // typo-ed option name nicely.
2124 do {
2125 nextch();
2126 } while (!isBlank(ch) && !isComm(ch) && !isEol(ch));
2128 break;
2130 // pcs->z[Q_BACKBEARING] = pcs->z[Q_BEARING] = -rad(read_numeric(false));
2131 // pcs->z[Q_BACKGRADIENT] = pcs->z[Q_GRADIENT] = -rad(read_numeric(false));
2132 // pcs->z[Q_LENGTH] = -METRES_PER_FOOT * read_numeric(false);
2134 /* Original "Inclination Units" were "Depth Gauge". */
2135 //pcs->recorded_style = STYLE_DIVING;
2136 //skipline();
2137 skipblanks();
2141 static void
2142 data_file_walls_srv(void)
2144 // FIXME: Do any of these variables need to be volatile to protect them
2145 // from longjmp()? GCC isn't warning about them...
2147 bool standalone = (p_walls_options == NULL);
2148 if (standalone) {
2149 // We're being included standalone rather than from a WPJ file.
2150 walls_initialise_settings();
2151 walls_reset();
2154 // Default flags assigned to stations in #FIX.
2155 int fix_station_flags = p_walls_options->fix_station_flags;
2157 // FIXME: We need to update the separator_map to reflect what can be
2158 // SPECIAL_NAMES. Or should we use the Compass approach and base this
2159 // on what's actually used? The first approach would pick the separator
2160 // from {':', ';', ',', '#', space}; the latter would pick '.' if
2161 // the station naming recommendations in the Walls documentation are
2162 // followed.
2163 update_output_separator();
2165 #ifdef HAVE_SETJMP_H
2166 /* errors in nested functions can longjmp here */
2167 if (setjmp(file.jbSkipLine)) {
2168 skipline();
2169 process_eol();
2171 #endif
2173 if (pcs->style == STYLE_NORMAL)
2174 pcs->ordering = p_walls_options->data_order_ct;
2175 else
2176 pcs->ordering = p_walls_options->data_order_rect;
2178 while (ch != EOF && !ferror(file.fh)) {
2179 next_line:
2180 skipblanks();
2181 if (ch != '#') {
2182 if (ch == ';' || isEol(ch)) {
2183 skipline();
2184 process_eol();
2185 } else if (pcs->style == STYLE_NORMAL) {
2186 data_normal();
2187 } else {
2188 // Set up Dz in case it's omitted.
2189 VAL(Dz) = 0.0;
2190 VAR(Dz) = 10.0;
2191 data_cartesian();
2193 continue;
2196 // Directive:
2197 int leading_blanks = ftell(file.fh) - file.lpos - 1;
2198 nextch();
2199 if (ch == '[') {
2200 // "Commented out" data.
2201 int start_lineno = file.line;
2202 skipline();
2203 process_eol();
2205 int depth = 1;
2206 while (depth && ch != EOF) {
2207 skipblanks();
2208 if (ch == '#') {
2209 nextch();
2210 depth += (ch == '[') - (ch == ']');
2212 skipline();
2213 process_eol();
2215 if (depth) {
2216 /* TRANSLATORS: %s and %s are replaced with e.g. BEGIN and END
2217 * or #[ and #] */
2218 error_in_file(file.filename, start_lineno,
2219 /*%s with no matching %s in this file*/23,
2220 "#[", "#]");
2222 goto next_line;
2224 if (ch == ']') {
2225 /* TRANSLATORS: %s is replaced with e.g. BEGIN or .BOOK or #[ */
2226 compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*No matching %s*/192, "#[");
2228 skipblanks();
2229 int blanks_after_hash = ftell(file.fh) - file.lpos - leading_blanks - 2;
2230 get_token_walls();
2231 walls_cmd directive = match_tok(walls_cmd_tab, TABSIZE(walls_cmd_tab));
2232 parse file_store;
2233 volatile int ch_store;
2234 string line = S_INIT;
2235 if (directive != WALLS_CMD_FIX && directive != WALLS_CMD_NULL) {
2236 bool seen_macros = false;
2237 // Recreate the start of the line for error reporting.
2238 // FIXME: This will change tabs to a single space...
2239 if (leading_blanks)
2240 s_catn(&line, leading_blanks, ' ');
2241 s_catchar(&line, '#');
2242 if (blanks_after_hash)
2243 s_catn(&line, blanks_after_hash, ' ');
2244 s_cats(&line, &token);
2246 filepos fp_args;
2247 get_pos(&fp_args);
2249 // Expand macros such as $(foo) in rest of line.
2250 while (!isEol(ch)) {
2251 if (ch != '$') {
2252 s_catchar(&line, ch);
2253 nextch();
2254 continue;
2256 nextch();
2257 if (ch != '(') {
2258 s_catchar(&line, '$');
2259 continue;
2261 nextch();
2262 // Read name of macro onto the end of line, then replace it
2263 // with the value of the macro.
2264 int macro_start = s_len(&line);
2265 while (!isEol(ch) && ch != ')') {
2266 s_catchar(&line, ch);
2267 nextch();
2269 nextch();
2270 const char *name = s_str(&line) + macro_start;
2271 int name_len = s_len(&line) - macro_start;
2272 const char *macro = walls_get_macro(&walls_macros,
2273 name, name_len);
2274 if (!macro) {
2275 macro = walls_get_macro(&walls_macros_wpj, name, name_len);
2277 if (!macro) {
2278 compile_diagnostic(DIAG_ERR, /*Macro ā€œ%sā€ not defined*/499,
2279 s_str(&line) + macro_start);
2281 s_truncate(&line, macro_start);
2282 if (macro)
2283 s_cat(&line, macro);
2284 seen_macros = true;
2287 if (seen_macros) {
2288 //printf("MACRO EXPANSION <%s>\n", line);
2289 // Read from the buffered macro-expanded line instead of the
2290 // file.
2291 file_store = file;
2292 ch_store = ch;
2293 #ifdef HAVE_FMEMOPEN
2294 file.fh = fmemopen((char*)s_str(&line), s_len(&line), "rb");
2295 if (!file.fh) {
2296 fatalerror(/*Failed to create temporary file*/498);
2298 #else
2299 file.fh = tmpfile();
2300 if (!file.fh) {
2301 fatalerror(/*Failed to create temporary file*/498);
2303 fwrite(s_str(&line), s_len(&line), 1, file.fh);
2304 #endif
2305 fseek(file.fh, fp_args.offset - file.lpos, SEEK_SET);
2306 ch = (unsigned char)s_str(&line)[fp_args.offset - file.lpos - 1];
2307 file.lpos = 0;
2308 } else {
2309 //printf("no macros seen in <%s>\n", line);
2310 // No macros seen so rewind and read directly from the file.
2311 s_free(&line);
2312 set_pos(&fp_args);
2316 switch (directive) {
2317 case WALLS_CMD_UNITS:
2318 parse_options();
2319 break;
2320 case WALLS_CMD_DATE: {
2321 int year, month, day;
2322 read_walls_srv_date(&year, &month, &day);
2323 copy_on_write_meta(pcs);
2324 int days = days_since_1900(year, month, day);
2325 pcs->meta->days1 = pcs->meta->days2 = days;
2326 skipblanks();
2327 if (!isEol(ch) && !isComm(ch)) {
2328 // Walls seems to ignore anything after the date so make this a
2329 // warning not an error so we can process existing Walls
2330 // datasets (e.g. the Mammoth dataset reportedly has `#Date
2331 // 1978-07-01\`.
2332 compile_diagnostic(DIAG_WARN|DIAG_TAIL, /*End of line not blank*/15);
2333 skipline();
2335 break;
2337 case WALLS_CMD_FIX: {
2338 real coords[3];
2339 filepos fp_stn, fp;
2340 get_pos(&fp_stn);
2341 prefix *name = read_walls_station(p_walls_options->prefix, false);
2342 // FIXME: can be e.g. `W97:43:52.5 N31:16:45 323f`
2343 // Or E/S instead of W/N.
2345 enum { UNKNOWN, LATLONG, UTM } format = UNKNOWN;
2346 for (int i = 0; i < 3; ++i) {
2347 // The order of the coordinates is specified by data_order_rect.
2348 int compiletimeassert_dxdydz[Dy - Dx == 1 && Dz - Dy == 1 ? 1 : -1];
2349 (void)compiletimeassert_dxdydz;
2350 int dim = p_walls_options->data_order_rect[i + 2] - Dx;
2351 if ((unsigned)dim > 2) {
2352 // FIXME: Survex doesn't currently support horizontal-only
2353 // fixes.
2354 coords[2] = 0.0;
2355 break;
2358 real coord;
2359 skipblanks();
2360 int upper_ch = toupper(ch);
2361 if (dim == 2 || format == UTM || strchr("NWES", upper_ch) == NULL) {
2362 // Read as a distance if this is the altitude, or we've
2363 // already seen a distance for x or y, or if the coordinate
2364 // doesn't start with a compass point letter.
2365 coord = read_numeric(false);
2366 if (ch == 'F' || ch == 'f') {
2367 coord *= METRES_PER_FOOT;
2368 nextch();
2369 } else if (ch == 'M' || ch == 'm') {
2370 nextch();
2371 } else {
2372 coord *= pcs->units[Q_LENGTH];
2374 if (dim != 2) format = UTM;
2375 } else {
2376 // Set negate if S or W.
2377 bool negate = ((upper_ch & 3) == 3);
2378 bool e_or_w = ((upper_ch & 5) == 5);
2379 if (dim == e_or_w) {
2380 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting ā€œ%sā€ or ā€œ%sā€*/103,
2381 e_or_w ? "N" : "E", e_or_w ? "S" : "W");
2383 nextch();
2384 coord = read_number(false, true);
2385 if (negate) coord = -coord;
2387 format = LATLONG;
2390 coords[dim] = coord;
2393 real var_xy = 0.0, var_z = 0.0;
2394 skipblanks();
2395 if (ch == '(') {
2396 // Optional variance override "with no embedded tabs or spaces".
2397 // E.g. `(R5,?)` specifies horizontal and vertical so:
2398 // `R5` means 5m RMS horizontal error
2399 // `?` specifies no elevations were obtained - infinite variance.
2400 // `*` means ... probably the same as `?` for a fixed point.
2401 // <non-negative number> means treat as compass and tape vector
2402 // of that length (in length units from #units)
2403 // `` (empty) means don't override that one
2404 // no comma e.g. `(R5)` means apply to both h and v
2405 nextch();
2406 bool rms_h = false;
2407 real val_h;
2408 if (ch == ',' || ch == ')') {
2409 // Use default variance, which is exact for a fixed point.
2410 val_h = 0.0;
2411 } else if (ch == '?' || ch == '*') {
2412 // Infinite variance. It seems `?` and `*` effectively
2413 // mean the same for a fixed point. We don't really
2414 // support this in general but if it's set both
2415 // horizontally and vertically we can ignore it, and if
2416 // it's set for one we can bodge it with a large value.
2417 nextch();
2418 val_h = HUGE_REAL;
2419 } else {
2420 if (ch == 'R' || ch == 'r') {
2421 rms_h = true;
2422 nextch();
2425 val_h = read_number(false, true);
2426 if (ch == 'F' || ch == 'f') {
2427 val_h *= METRES_PER_FOOT;
2428 nextch();
2429 } else if (ch == 'M' || ch == 'm') {
2430 nextch();
2431 } else {
2432 val_h *= pcs->units[Q_LENGTH];
2435 bool rms_v = rms_h;
2436 real val_v = val_h;
2437 if (ch == ',') {
2438 rms_v = false;
2439 nextch();
2440 if (ch == ')') {
2441 // Use default variance, which is exact for a fixed point.
2442 val_v = 0.0;
2443 } else if (ch == '?' || ch == '*') {
2444 // Infinite variance. It seems `?` and `*` effectively
2445 // mean the same for a fixed point. We don't really
2446 // support this in general but if it's set both
2447 // horizontally and vertically we can ignore it, and if
2448 // it's set for one we can bodge it with a large value.
2449 nextch();
2450 val_v = HUGE_REAL;
2451 } else {
2452 if (ch == 'R' || ch == 'r') {
2453 rms_v = true;
2454 nextch();
2457 val_v = read_number(false, true);
2458 if (ch == 'F' || ch == 'f') {
2459 val_v *= METRES_PER_FOOT;
2460 nextch();
2461 } else if (ch == 'M' || ch == 'm') {
2462 nextch();
2463 } else {
2464 val_v *= pcs->units[Q_LENGTH];
2468 if (ch == ')') {
2469 nextch();
2470 skipblanks();
2471 } else {
2472 compile_diagnostic(DIAG_ERR|DIAG_COL,
2473 /*Expecting ā€œ%sā€*/497, ")");
2476 if (val_h == 0) {
2477 // Use default variance, which is exact for a fixed point.
2478 } else if (val_h == HUGE_REAL) {
2479 // Infinite variance. It seems `?` and `*` effectively
2480 // mean the same for a fixed point. We don't really
2481 // support this in general but if it's set both
2482 // horizontally and vertically we can ignore it, and if
2483 // it's set for one we can bodge it with a large value.
2484 var_xy = HUGE_REAL;
2485 } else if (rms_h) {
2486 // "Note that if you make an assignment like (R10), which
2487 // is the same as (R10,R10), you won't be giving all three
2488 // error components identical variances. The vertical
2489 // component in this case would be given variance 10Ā² =
2490 // 100, while each horizontal component would be given half
2491 // that variance, or 50".
2492 var_xy = val_h * val_h / 2.0;
2493 } else {
2494 // The value is to be treated as the length of a leg to use
2495 // the variances of, so this is based on the leg variance
2496 // calculations except we don't have any angles here. Note
2497 // that for Survex the leg length does not affect the
2498 // variances.
2499 var_xy = var(Q_POS) / 3.0 + var(Q_LENGTH) / 2.0;
2501 if (val_v < 0.0) {
2502 // Use default variance, which is exact for a fixed point.
2503 } else if (val_v == HUGE_REAL) {
2504 // Infinite variance.
2505 var_z = HUGE_REAL;
2506 } else if (rms_v) {
2507 var_z = val_v * val_v;
2508 } else {
2509 // The value is to be treated as the length of a leg to use
2510 // the variances of, so this is based on the leg variance
2511 // calculations except we don't have any angles here. Note
2512 // that for Survex the leg length does not affect the
2513 // variances.
2514 var_z = var(Q_POS) / 3.0 + var(Q_LENGTH) / 2.0;
2518 if (ch == '/') {
2519 // Station note - ignore for now. Note: Must be '/'.
2520 skipline();
2523 if (format == LATLONG) {
2524 // Convert coordinates based on the current coordinate system
2525 // set by .REF in the wpj.
2526 if (walls_ref.img_datum_code > 0 && proj_str_out) {
2527 int epsg_code =
2528 img_compass_longlat_epsg_code(walls_ref.img_datum_code);
2529 char proj_longlat[32];
2530 snprintf(proj_longlat, sizeof(proj_longlat),
2531 "EPSG:%d", epsg_code);
2532 PJ *transform = proj_create_crs_to_crs(PJ_DEFAULT_CTX,
2533 proj_longlat,
2534 proj_str_out,
2535 NULL);
2536 if (transform) {
2537 /* Normalise the output order so x is longitude and y
2538 * latitude - by default new PROJ has them switched for
2539 * EPSG:4326 which just seems confusing.
2541 PJ* pj_norm = proj_normalize_for_visualization(PJ_DEFAULT_CTX,
2542 transform);
2543 proj_destroy(transform);
2544 transform = pj_norm;
2547 if (proj_angular_input(transform, PJ_FWD)) {
2548 /* Input coordinate system expects radians. */
2549 coords[0] = rad(coords[0]);
2550 coords[1] = rad(coords[1]);
2553 PJ_COORD coord = {{coords[0], coords[1], coords[2], HUGE_VAL}};
2554 coord = proj_trans(transform, PJ_FWD, coord);
2555 coords[0] = coord.xyzt.x;
2556 coords[1] = coord.xyzt.y;
2557 coords[2] = coord.xyzt.z;
2559 if (coords[0] == HUGE_VAL || coords[1] == HUGE_VAL || coords[2] == HUGE_VAL) {
2560 compile_diagnostic(DIAG_ERR, /*Failed to convert coordinates: %s*/436,
2561 proj_context_errno_string(PJ_DEFAULT_CTX,
2562 proj_errno(transform)));
2563 /* Set dummy values which are finite. */
2564 coords[0] = coords[1] = coords[2] = 0;
2566 proj_destroy(transform);
2567 } else {
2568 if (walls_ref.img_datum_code == 0) {
2569 // We already emitted an error that this datum is not
2570 // supported so an error here doesn't seem helpful.
2571 } else {
2572 compile_diagnostic(DIAG_ERR, /*Output coordinate system not set*/488);
2574 /* Set dummy values which are finite. */
2575 coords[0] = coords[1] = coords[2] = 0;
2579 // Apply station flags inferred from Walls "default flag".
2580 name->sflags |= fix_station_flags;
2582 if (var_xy == 0.0 && var_z == 0.0) {
2583 // Exact fix.
2584 int fix_result = fix_station(name, coords);
2585 if (fix_result) {
2586 get_pos(&fp);
2587 set_pos(&fp_stn);
2588 if (fix_result < 0) {
2589 // The equivalent of this is an error for .svx files,
2590 // but Walls explicitly documents that a repeated exact
2591 // fix warns and uses the coordinates of the first fix.
2592 compile_diagnostic(DIAG_WARN|DIAG_WORD, /*Station already fixed or equated to a fixed point*/46);
2593 } else {
2594 compile_diagnostic(DIAG_WARN|DIAG_WORD, /*Station already fixed at the same coordinates*/55);
2596 compile_diagnostic_pfx(DIAG_INFO, name, /*Previously fixed or equated here*/493);
2597 set_pos(&fp);
2599 break;
2602 if (var_xy == HUGE_REAL && var_z == HUGE_REAL) {
2603 // We've been asked to not fix horizontally or vertically!
2604 break;
2607 // If only one variance is exact, make it 1mm instead. FIXME
2608 if (var_xy == 0.0) var_xy = 1e-6;
2609 if (var_z == 0.0) var_z = 1e-6;
2611 // We don't currently handle fixing only horizontally or only
2612 // vertically so for now just assign a large variance. FIXME
2613 if (var_xy == HUGE_REAL) var_xy = 1e6;
2614 if (var_z == HUGE_REAL) var_z = 1e6;
2616 fix_station_with_variance(name, coords, var_xy, var_xy, var_z
2617 #ifndef NO_COVARIANCES
2618 , 0.0, 0.0, 0.0
2619 #endif
2621 break;
2623 case WALLS_CMD_FLAG: {
2624 // The flag comes after a list of stations, so to avoid having to
2625 // store a list of the stations we note the position, scan ahead
2626 // and parse the flag, then come back and actually parse the
2627 // stations and apply the flag.
2628 skipblanks();
2629 if (isEol(ch) || isComm(ch)) {
2630 // Just "#FLAG" with no arguments clears the default flag.
2631 fix_station_flags = 0;
2632 break;
2634 bool setting_default_flag = isWallsSlash(ch);
2636 filepos fp;
2637 get_pos(&fp);
2638 while (!isWallsSlash(ch)) {
2639 if (isComm(ch) || isEol(ch)) {
2640 // The flag name is not required (it "can *optionally*
2641 // follow the list of station names" (my emphasis).
2642 // Elsewhere the docs say:
2644 // Another automatically assigned list item is
2645 // "{Unnamed Flag}", which is present only if
2646 // stations appear on a #Flag directive without a
2647 // name parameter -- for example, "#FLAG A1 A2 A3".
2648 // There's really no reason to have any of those,
2649 // although Walls has always allowed them.
2651 // These seem to occur in real data, but we ignore
2652 // unknown flag names, so it seems reasonable to just
2653 // ignore these too. Or maybe we should warn? FIXME
2654 process_eol();
2655 goto next_line;
2657 nextch();
2659 nextch();
2660 int station_flags = parse_walls_flags(false);
2662 if (setting_default_flag) {
2663 fix_station_flags = station_flags;
2664 break;
2667 // Suppress "unused fixed point" warnings for stations in #flag.
2668 station_flags |= BIT(SFLAGS_USED);
2670 // Go back and read stations and apply the flags.
2671 filepos fp_end;
2672 get_pos(&fp_end);
2673 set_pos(&fp);
2674 // It seems / and \ can't be used in #flag station names?
2675 // FIXME: Need to actually test this with Walls.
2676 int save_translate_slash = pcs->Translate['/'];
2677 int save_translate_bslash = pcs->Translate['\\'];
2678 pcs->Translate['/'] = 0;
2679 pcs->Translate['\\'] = 0;
2680 while (!isWallsSlash(ch)) {
2681 prefix *name = read_walls_station(p_walls_options->prefix, false);
2682 name->sflags |= station_flags;
2683 skipblanks();
2685 pcs->Translate['/'] = save_translate_slash;
2686 pcs->Translate['\\'] = save_translate_bslash;
2687 set_pos(&fp_end);
2688 break;
2690 case WALLS_CMD_PREFIX:
2691 case WALLS_CMD_PREFIX2:
2692 case WALLS_CMD_PREFIX3: {
2693 char *new_prefix = read_walls_prefix();
2694 int i = (int)WALLS_CMD_PREFIX3 - (int)directive;
2695 osfree(p_walls_options->prefix[i]);
2696 p_walls_options->prefix[i] = new_prefix;
2697 skipblanks();
2698 if (!isEol(ch) && !isComm(ch)) {
2699 // Walls seems to ignore anything after the prefix so make this a
2700 // warning not an error so we can process existing Walls
2701 // datasets (e.g. the Mammoth dataset reportedly has `#prefix
2702 // 4136 Lucys domes`).
2703 compile_diagnostic(DIAG_WARN|DIAG_TAIL, /*End of line not blank*/15);
2704 skipline();
2706 break;
2708 case WALLS_CMD_NOTE: {
2709 // A text note attached to a station - ignore for now except we
2710 // read the station name and flag it to avoid an "unused fixed
2711 // point" warning.
2712 prefix *name = read_walls_station(p_walls_options->prefix, false);
2713 name->sflags |= BIT(SFLAGS_USED);
2714 skipline();
2715 break;
2717 case WALLS_CMD_SEGMENT:
2718 // "Segments are optional and have no affect on the compilation of
2719 // survey data" so ignore for now.
2720 skipline();
2721 break;
2722 case WALLS_CMD_SYMBOL:
2723 // Now to draw symbols. Not really appropriate here as this is
2724 // presentation information, so we just ignore it.
2725 skipline();
2726 break;
2727 case WALLS_CMD_NULL:
2728 // FIXME it's a "directive" in Walls-speak.
2729 compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Unknown command ā€œ%sā€*/12, s_str(&token));
2730 break;
2733 if (!s_empty(&line)) {
2734 // Revert to reading from the file.
2735 fclose(file.fh);
2736 s_free(&line);
2737 file = file_store;
2738 ch = ch_store;
2741 process_eol();
2744 clear_last_leg();
2746 if (walls_macros) {
2747 // Clear all macros set in this SRV file.
2748 for (unsigned i = 0; i < WALLS_MACRO_HASH_SIZE; ++i) {
2749 walls_macro *p = walls_macros[i];
2750 while (p) {
2751 walls_macro *to_free = p;
2752 p = p->next;
2753 osfree(to_free);
2755 walls_macros[i] = NULL;
2759 while (p_walls_options->explicit) {
2760 // FIXME: Walls quietly allows SAVE without a corresponding RESTORE, but
2761 // probably worth at least a warning here.
2762 pop_walls_options();
2764 if (standalone) pop_walls_options();
2767 typedef enum {
2768 WALLS_WPJ_CMD_BOOK,
2769 WALLS_WPJ_CMD_ENDBOOK,
2770 WALLS_WPJ_CMD_NAME,
2771 WALLS_WPJ_CMD_OPTIONS,
2772 WALLS_WPJ_CMD_PATH,
2773 WALLS_WPJ_CMD_REF,
2774 WALLS_WPJ_CMD_STATUS,
2775 WALLS_WPJ_CMD_SURVEY,
2776 WALLS_WPJ_CMD_NULL = -1
2777 } walls_wpj_cmd;
2779 static const sztok walls_wpj_cmd_tab[] = {
2780 {"BOOK", WALLS_WPJ_CMD_BOOK},
2781 {"ENDBOOK", WALLS_WPJ_CMD_ENDBOOK},
2782 {"NAME", WALLS_WPJ_CMD_NAME},
2783 {"OPTIONS", WALLS_WPJ_CMD_OPTIONS},
2784 {"PATH", WALLS_WPJ_CMD_PATH},
2785 {"REF", WALLS_WPJ_CMD_REF},
2786 {"STATUS", WALLS_WPJ_CMD_STATUS},
2787 {"SURVEY", WALLS_WPJ_CMD_SURVEY},
2788 {NULL, WALLS_WPJ_CMD_NULL}
2791 static void
2792 data_file_walls_wpj(void)
2794 // FIXME: Do any of these variables need to be volatile to protect them
2795 // from longjmp()? GCC isn't warning about them...
2796 char *pth = path_from_fnm(file.filename);
2798 walls_initialise_settings();
2799 walls_reset();
2801 // FIXME: We need to update the separator_map to reflect what can be
2802 // SPECIAL_NAMES. Or should we use the Compass approach and base this
2803 // on what's actually used? The first approach would pick the separator
2804 // from {':', ';', ',', '#', space}; the latter would pick '.' if
2805 // the documentation station naming recommendations were followed.
2806 update_output_separator();
2808 /* We need to update separator_map so we don't pick a separator character
2809 * which occurs in a station name. However Compass DAT allows everything
2810 * >= ASCII char 33 except 127 in station names so if we just added all
2811 * the valid station name characters we'd always pick space as the
2812 * separator for any dataset which included a DAT file, yet in practice
2813 * '.' is never used in any of the sample DAT files I've seen. So
2814 * instead we scan the characters actually used in station names when we
2815 * process CompassDATFr and CompassDATTo fields. (FIXME)
2818 // Start from the location of this WPJ.
2819 s_cat(&p_walls_options->path, pth);
2821 #ifdef HAVE_SETJMP_H
2822 /* errors in nested functions can longjmp here */
2823 if (setjmp(file.jbSkipLine)) {
2824 skipline();
2825 process_eol();
2827 #endif
2829 int status = -1;
2830 long name_lpos = -1;
2831 unsigned name_lineno = 0;
2832 filepos fp_name;
2833 string name = S_INIT;
2835 walls_ref.x = walls_ref.y = walls_ref.z = HUGE_VAL;
2836 walls_ref.zone = 0;
2838 int depth = 0;
2839 int detached_nest_level = 0;
2840 bool in_survey = false;
2841 while (!ferror(file.fh)) {
2842 walls_wpj_cmd tok = WALLS_WPJ_CMD_NULL;
2843 skipblanks();
2844 if (ch != '.') {
2845 if (ch == EOF) {
2846 if (!in_survey) break;
2847 // Ensure that a survey at the end of the WPJ file is processed
2848 // even if not contained in a book. Not seen in example data, so
2849 // not sure if this is actually valid but this code will just be
2850 // unused if it isn't.
2851 goto process_entry;
2853 // If the line isn't blank or a comment then process_eol() will
2854 // issue a suitable error.
2855 process_eol();
2856 continue;
2859 nextch();
2860 get_token_no_blanks();
2861 tok = match_tok(walls_wpj_cmd_tab, TABSIZE(walls_wpj_cmd_tab));
2862 if (detached_nest_level) {
2863 switch (tok) {
2864 case WALLS_WPJ_CMD_BOOK:
2865 ++detached_nest_level;
2866 skipline();
2867 break;
2868 case WALLS_WPJ_CMD_ENDBOOK:
2869 --detached_nest_level;
2870 break;
2871 default:
2872 // Ignore everything else.
2873 skipline();
2874 break;
2876 process_eol();
2877 continue;
2879 if (in_survey &&
2880 (tok == WALLS_WPJ_CMD_SURVEY ||
2881 tok == WALLS_WPJ_CMD_BOOK ||
2882 tok == WALLS_WPJ_CMD_ENDBOOK)) {
2883 process_entry:
2884 // Process the current entry.
2886 // .STATUS is a decimal integer which is a bitmap of flags.
2887 // Meanings mostly cribbed from dewalls:
2889 // Set if a branch is expanded in the UI - we can ignore.
2890 #define WALLS_WPJ_STATUS_BOOK_OPEN 0x000001
2891 // Detached items are not processed as part of higher level
2892 // items.
2893 #define WALLS_WPJ_STATUS_DETACHED 0x000002
2894 // 0x000004 appears to be unused/no longer used. Setting it
2895 // externally in a WPJ file and then loading it into Walls and
2896 // forcing saving clears it.
2897 #define WALLS_WPJ_STATUS_UNUSED_BIT2 0x000004
2898 // We ignore segments currently so ignore this too.
2899 #define WALLS_WPJ_STATUS_NAME_DEFINES_SEGMENT 0x000008
2900 // Controls the units used in reporting data - we can ignore.
2901 #define WALLS_WPJ_STATUS_REVIEW_UNITS_FEET 0x000010
2902 // Comments in dewalls-java suggests this is no longer used.
2903 #define WALLS_WPJ_STATUS_UNUSED_BIT5 0x000020
2904 // These WALLS_WPJ_STATUS_*_TRISTATE values are (shifted) binary:
2905 // 00 Inherit value from parent
2906 // 01 Off
2907 // 10 On
2908 // 11 <not used>
2909 #define WALLS_WPJ_STATUS_USE_REFERENCE_TRISTATE 0x0000c0
2910 #define WALLS_WPJ_STATUS_DECLINATION_AUTO_TRISTATE 0x000300
2911 #define WALLS_WPJ_STATUS_UTM_GPS_RELATIVE_TRISTATE 0x000c00
2912 // AIUI these just control distributing loop misclosure:
2913 #define WALLS_WPJ_STATUS_PRESERVE_PLUMB_ORIENTATION_TRISTATE 0x003000
2914 #define WALLS_WPJ_STATUS_PRESERVE_PLUMB_LENGTH_TRISTATE 0x00c000
2915 // Attached file of arbitrary type (we can just ignore):
2916 #define WALLS_WPJ_STATUS_TYPE_OTHER 0x010000
2917 // We can ignore these:
2918 #define WALLS_WPJ_STATUS_EDIT_ON_LAUNCH 0x020000
2919 #define WALLS_WPJ_STATUS_OPEN_ON_LAUNCH 0x040000
2920 #define WALLS_WPJ_STATUS_DEFAULT_VIEW_MASK 0x380000
2921 #define WALLS_WPJ_STATUS_PROCESS_SVG 0x400000
2923 // A quirk is that the root item is flagged with
2924 // WALLS_WPJ_STATUS_DETACHED (seems the flag might be more like
2925 // "don't draw a connecting line left from here").
2926 if ((status & WALLS_WPJ_STATUS_DETACHED) && depth > 0) {
2927 // Detached survey.
2928 //printf("*** Detached survey\n");
2929 goto detached_or_not_srv;
2931 if ((status & WALLS_WPJ_STATUS_TYPE_OTHER)) {
2932 // Attached file of arbitrary type.
2933 goto detached_or_not_srv;
2935 if (s_empty(&name)) {
2936 printf("*** in_survey but no/empty NAME\n");
2937 goto detached_or_not_srv;
2940 // Include SRV file.
2941 #if 0
2942 printf("+++ %s %s .SRV :%s:%s:%s\n", s_str(&p_walls_options->path), s_str(&name),
2943 p_walls_options->prefix[0] ? p_walls_options->prefix[0] : "",
2944 p_walls_options->prefix[1] ? p_walls_options->prefix[1] : "",
2945 p_walls_options->prefix[2] ? p_walls_options->prefix[2] : "");
2946 #endif
2947 char *filename;
2948 FILE *fh = fopen_portable(s_str(&p_walls_options->path),
2949 s_str(&name), "srv", "rb", &filename);
2950 if (fh == NULL)
2951 fh = fopen_portable(s_str(&p_walls_options->path),
2952 s_str(&name), "SRV", "rb", &filename);
2954 if (fh == NULL) {
2955 // Report the diagnostic at the location of the ".NAME".
2956 unsigned save_line = file.line;
2957 long save_lpos = file.lpos;
2958 filepos fp;
2959 get_pos(&fp);
2960 set_pos(&fp_name);
2961 file.lpos = name_lpos;
2962 file.line = name_lineno;
2963 // Report the resolved path. FIXME: Maybe we should use
2964 // full_file in the fopen_portable() call above so things
2965 // align better?
2966 string full_file = S_INIT;
2967 s_cats(&full_file, &p_walls_options->path);
2968 s_catchar(&full_file, FNM_SEP_LEV);
2969 s_cats(&full_file, &name);
2970 s_cat(&full_file, ".SRV");
2971 if (!fDirectory(s_str(&p_walls_options->path))) {
2972 // Walls appears to quietly ignore file if the
2973 // directory does not exist, but it seems worth
2974 // warning about at least.
2976 // FIXME: This should take case into account like
2977 // opening the file does.
2978 compile_diagnostic(DIAG_WARN|DIAG_TAIL, /*Couldnā€™t open file ā€œ%sā€*/24, s_str(&full_file));
2979 } else {
2980 compile_diagnostic(DIAG_ERR|DIAG_TAIL, /*Couldnā€™t open file ā€œ%sā€*/24, s_str(&full_file));
2982 s_free(&full_file);
2983 set_pos(&fp);
2984 file.line = save_line;
2985 file.lpos = save_lpos;
2986 goto srv_not_found;
2990 parse file_store = file;
2991 int ch_store = ch;
2992 if (file.fh) file.parent = &file_store;
2993 file.fh = fh;
2994 file.filename = filename;
2995 file.line = 1;
2996 file.lpos = 0;
2997 file.reported_where = false;
2998 nextch();
3000 using_data_file(file.filename);
3002 push_walls_options();
3003 walls_swap_macro_tables();
3004 data_file_walls_srv();
3005 walls_swap_macro_tables();
3006 pop_walls_options();
3008 if (ferror(file.fh))
3009 fatalerror_in_file(file.filename, 0, /*Error reading file*/18);
3011 (void)fclose(file.fh);
3013 /* don't free this - it may be pointed to by prefix.file */
3014 /* osfree(file.filename); */
3016 file = file_store;
3017 ch = ch_store;
3020 srv_not_found:
3021 status = -1;
3022 s_clear(&name);
3023 //s_clear(&path);
3024 walls_ref.x = walls_ref.y = walls_ref.z = HUGE_VAL;
3025 walls_ref.zone = 0;
3026 detached_or_not_srv:
3027 pop_walls_options();
3028 in_survey = false;
3029 // Exit if at EOF.
3030 if (tok == WALLS_WPJ_CMD_NULL) break;
3033 switch (tok) {
3034 case WALLS_WPJ_CMD_BOOK:
3035 ++depth;
3036 push_walls_options();
3037 in_survey = false;
3038 skipline();
3039 break;
3040 case WALLS_WPJ_CMD_SURVEY:
3041 push_walls_options(); // FIXME: Eliminate redundant push and pop inside data_file_walls_srv()
3042 in_survey = true;
3043 skipline();
3044 break;
3045 case WALLS_WPJ_CMD_ENDBOOK:
3046 if (depth == 0) {
3047 compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*No matching %s*/192, ".BOOK");
3049 --depth;
3050 pop_walls_options();
3051 in_survey = false;
3052 break;
3053 case WALLS_WPJ_CMD_OPTIONS:
3054 parse_options();
3055 break;
3056 case WALLS_WPJ_CMD_PATH: {
3057 skipblanks();
3058 if (!isEol(ch)) {
3059 if (!s_empty(&p_walls_options->path)) {
3060 s_catchar(&p_walls_options->path, FNM_SEP_LEV);
3062 while (!isEol(ch)) {
3063 if (ch == '\\') {
3064 ch = FNM_SEP_LEV;
3066 s_catchar(&p_walls_options->path, ch);
3067 nextch();
3070 //printf("PATH: %s\n", s_str(&p_walls_options->path));
3071 break;
3073 case WALLS_WPJ_CMD_REF:
3074 walls_ref.y = read_numeric(false);
3075 walls_ref.x = read_numeric(false);
3076 // Walls supports UPS zones and uses -61 and 61 to specify them.
3077 walls_ref.zone = read_int(-61, 61);
3079 // Ignore pre-computed convergence as we compute that for ourselves.
3080 (void)read_numeric(false);
3082 walls_ref.z = read_numeric(false);
3084 // Ignore field which seems to be a bitmask.
3086 // From experimenting with Walls32.exe and looking at the saved
3087 // .wpj file contents, the bottom two bits (mask 0x03) seem to
3088 // encode the format (d/dm/dms) that the Walls32.exe UI uses to
3089 // display/enter lat/long.
3091 // Bit 0x04 seems to be "West of meridian?" and bit 0x08 seems
3092 // to be "South of equator?" (and the lat and long seem to be
3093 // stored without signs).
3095 // Other bits seem to be unused so it seems we can safely ignore
3096 // this field.
3097 (void)read_uint();
3099 // Ignore same location in lat/long.
3100 (void)read_numeric(false);
3101 (void)read_numeric(false);
3102 (void)read_numeric(false);
3103 (void)read_numeric(false);
3104 (void)read_numeric(false);
3105 (void)read_numeric(false);
3107 // Ignore integer index for the datum (e.g. 27 for "WGS 1984").
3108 // The string names seem more likely to have not changed over time.
3109 (void)read_uint();
3111 string datum_str = S_INIT;
3112 read_string(&datum_str);
3113 int datum = img_parse_compass_datum_string(s_str(&datum_str),
3114 s_len(&datum_str));
3115 if (datum == 0) {
3116 // Walls has different name strings to Compass for some datums.
3117 if (S_EQ(&datum_str, "NAD27 CONUS")) {
3118 // FIXME Assuming this is right. Walls seems to have 6
3119 // variant NAD27 datums, not sure what's going on.
3120 datum = img_DATUM_NAD27;
3121 } else if (S_EQ(&datum_str, "NAD83")) {
3122 datum = img_DATUM_NAD83;
3123 } else if (S_EQ(&datum_str, "Geodetic Datum `49")) {
3124 datum = img_DATUM_NZGD49;
3125 } else if (S_EQ(&datum_str, "Hu-Tzu-Shan")) {
3126 datum = img_DATUM_HUTZUSHAN1950;
3127 } else {
3128 compile_diagnostic(DIAG_ERR|DIAG_WIDTH(s_len(&datum_str)),
3129 /*Datum ā€œ%sā€ not supported*/503,
3130 s_str(&datum_str));
3133 s_free(&datum_str);
3135 if (datum && walls_ref.zone && abs(walls_ref.zone) <= 60) {
3136 char *proj_str = img_compass_utm_proj_str(datum,
3137 walls_ref.zone);
3138 set_declination_location(walls_ref.x, walls_ref.y, walls_ref.z,
3139 proj_str);
3140 if (!pcs->proj_str) {
3141 pcs->proj_str = proj_str;
3142 if (!proj_str_out) {
3143 proj_str_out = osstrdup(proj_str);
3145 } else {
3146 osfree(proj_str);
3148 } else if (datum == img_DATUM_WGS84 && abs(walls_ref.zone) == 61) {
3149 // Polar UPS zones.
3150 const char *proj_str =
3151 (walls_ref.zone > 0 ? "EPSG:5041" : "EPSG:5042");
3152 set_declination_location(walls_ref.x, walls_ref.y, walls_ref.z,
3153 proj_str);
3154 if (!pcs->proj_str) {
3155 pcs->proj_str = osstrdup(proj_str);
3156 if (!proj_str_out) {
3157 proj_str_out = osstrdup(proj_str);
3162 walls_ref.img_datum_code = datum;
3163 break;
3164 case WALLS_WPJ_CMD_STATUS:
3165 status = read_uint();
3166 // A quirk is that the root item is flagged with
3167 // WALLS_WPJ_STATUS_DETACHED (seems the flag might be more like
3168 // "don't draw a connecting line left from here").
3169 if ((status & WALLS_WPJ_STATUS_DETACHED) && !in_survey && depth > 1) {
3170 //printf("Detached book (status %d = 0x%06x)\n", status, status);
3171 // Detached BOOK - resume at the corresponding ENDBOOK.
3172 s_clear(&name);
3173 pop_walls_options();
3174 detached_nest_level = 1;
3176 break;
3177 case WALLS_WPJ_CMD_NAME:
3178 if (!s_empty(&name)) {
3179 // FIXME: NAME already set
3180 s_clear(&name);
3182 skipblanks();
3183 get_pos(&fp_name);
3184 name_lpos = file.lpos;
3185 name_lineno = file.line;
3186 while (!isEol(ch)) {
3187 s_catchar(&name, ch);
3188 nextch();
3190 break;
3191 case WALLS_WPJ_CMD_NULL:
3192 compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Unknown command ā€œ%sā€*/12, s_str(&token));
3194 process_eol();
3197 osfree(pth);
3199 pop_walls_options();
3202 static void
3203 data_file_survex(void)
3205 int begin_lineno_store = pcs->begin_lineno;
3206 pcs->begin_lineno = 0;
3208 if (ch == 0xef) {
3209 /* Maybe a UTF-8 "BOM" - skip if so. */
3210 if (nextch() == 0xbb && nextch() == 0xbf) {
3211 nextch();
3212 file.lpos = 3;
3213 } else {
3214 rewind(file.fh);
3215 ch = 0xef;
3219 #ifdef HAVE_SETJMP_H
3220 /* errors in nested functions can longjmp here */
3221 if (setjmp(file.jbSkipLine)) {
3222 skipline();
3223 process_eol();
3225 #endif
3227 while (ch != EOF && !ferror(file.fh)) {
3228 if (!process_non_data_line()) {
3229 f_export_ok = false;
3230 switch (pcs->style) {
3231 case STYLE_NORMAL:
3232 case STYLE_DIVING:
3233 case STYLE_CYLPOLAR:
3234 data_normal();
3235 break;
3236 case STYLE_CARTESIAN:
3237 data_cartesian();
3238 break;
3239 case STYLE_PASSAGE:
3240 data_passage();
3241 break;
3242 case STYLE_NOSURVEY:
3243 data_nosurvey();
3244 break;
3245 case STYLE_IGNORE:
3246 data_ignore();
3247 break;
3248 default:
3249 BUG("bad style");
3253 clear_last_leg();
3255 /* don't allow *BEGIN at the end of a file, then *EXPORT in the
3256 * including file */
3257 f_export_ok = false;
3259 if (pcs->begin_lineno) {
3260 /* TRANSLATORS: %s and %s are replaced with e.g. BEGIN and END
3261 * or #[ and #] */
3262 error_in_file(file.filename, pcs->begin_lineno,
3263 /*%s with no matching %s in this file*/23,
3264 "BEGIN", "END");
3265 /* Implicitly close any unclosed BEGINs from this file */
3266 do {
3267 pop_settings();
3268 } while (pcs->begin_lineno);
3271 pcs->begin_lineno = begin_lineno_store;
3274 #define EXT3(C1, C2, C3) (((C3) << 16) | ((C2) << 8) | (C1))
3276 extern void
3277 data_file(const char *pth, const char *fnm)
3279 parse file_store;
3280 unsigned ext = 0;
3283 char *filename;
3284 FILE *fh;
3285 size_t len;
3287 if (!pth) {
3288 /* file specified on command line - don't do special translation */
3289 fh = fopenWithPthAndExt(pth, fnm, EXT_SVX_DATA, "rb", &filename);
3290 } else {
3291 fh = fopen_portable(pth, fnm, EXT_SVX_DATA, "rb", &filename);
3294 if (fh == NULL) {
3295 compile_error_string(fnm, /*Couldnā€™t open file ā€œ%sā€*/24, fnm);
3296 return;
3299 len = strlen(filename);
3300 if (len > 4 && filename[len - 4] == FNM_SEP_EXT) {
3301 /* Read extension and pack into ext. */
3302 for (int i = 1; i < 4; ++i) {
3303 unsigned char ext_ch = filename[len - i];
3304 ext = (ext << 8) | tolower(ext_ch);
3308 file_store = file;
3309 if (file.fh) file.parent = &file_store;
3310 file.fh = fh;
3311 file.filename = filename;
3312 file.line = 1;
3313 file.lpos = 0;
3314 file.reported_where = false;
3315 nextch();
3318 using_data_file(file.filename);
3320 switch (ext) {
3321 case EXT3('d', 'a', 't'):
3322 // Compass survey data.
3323 data_file_compass_dat();
3324 break;
3325 case EXT3('c', 'l', 'p'):
3326 // Compass closed data. The format of .clp is the same as .dat,
3327 // but it contains loop-closed data. This might be useful to
3328 // read if you want to keep existing stations at the same
3329 // adjusted positions, for example to be able to draw extensions
3330 // on an existing drawn-up survey. Or if you managed to lose the
3331 // original .dat but still have the .clp.
3332 data_file_compass_clp();
3333 break;
3334 case EXT3('m', 'a', 'k'):
3335 // Compass project file.
3336 data_file_compass_mak();
3337 break;
3338 case EXT3('s', 'r', 'v'):
3339 // Walls survey data.
3340 data_file_walls_srv();
3341 break;
3342 case EXT3('w', 'p', 'j'):
3343 // Walls project file.
3344 data_file_walls_wpj();
3345 break;
3346 default:
3347 // Native Survex data.
3348 data_file_survex();
3349 break;
3352 if (ferror(file.fh))
3353 fatalerror_in_file(file.filename, 0, /*Error reading file*/18);
3355 (void)fclose(file.fh);
3357 file = file_store;
3359 /* don't free this - it may be pointed to by prefix.file */
3360 /* osfree(file.filename); */
3363 static real
3364 mod2pi(real a)
3366 return a - floor(a / (2 * M_PI)) * (2 * M_PI);
3369 static real
3370 handle_plumb(clino_type *p_ctype)
3372 typedef enum {
3373 CLINO_NULL=-1, CLINO_UP, CLINO_DOWN, CLINO_LEVEL
3374 } clino_tok;
3375 static const sztok clino_tab[] = {
3376 {"D", CLINO_DOWN},
3377 {"DOWN", CLINO_DOWN},
3378 {"H", CLINO_LEVEL},
3379 {"LEVEL", CLINO_LEVEL},
3380 {"U", CLINO_UP},
3381 {"UP", CLINO_UP},
3382 {NULL, CLINO_NULL}
3384 static const real clinos[] = {(real)M_PI_2, (real)(-M_PI_2), (real)0.0};
3385 clino_tok tok;
3387 skipblanks();
3388 if (isalpha(ch)) {
3389 filepos fp;
3390 get_pos(&fp);
3391 get_token();
3392 tok = match_tok(clino_tab, TABSIZE(clino_tab));
3393 if (tok != CLINO_NULL) {
3394 *p_ctype = (tok == CLINO_LEVEL ? CTYPE_HORIZ : CTYPE_PLUMB);
3395 return clinos[tok];
3397 set_pos(&fp);
3398 } else if (isSign(ch)) {
3399 int chOld = ch;
3400 nextch();
3401 if (toupper(ch) == 'V') {
3402 nextch();
3403 *p_ctype = CTYPE_PLUMB;
3404 return (!isMinus(chOld) ? M_PI_2 : -M_PI_2);
3407 if (isOmit(chOld)) {
3408 *p_ctype = CTYPE_OMIT;
3409 /* no clino reading, so assume 0 with large sd */
3410 return (real)0.0;
3412 } else if (isOmit(ch)) {
3413 /* OMIT char may not be a SIGN char too so we need to check here as
3414 * well as above... */
3415 nextch();
3416 *p_ctype = CTYPE_OMIT;
3417 /* no clino reading, so assume 0 with large sd */
3418 return (real)0.0;
3420 return HUGE_REAL;
3423 static void
3424 warn_readings_differ(int msgno, real diff, int units,
3425 reading r_fore, reading r_back)
3427 char buf[64];
3428 char *p;
3429 diff /= get_units_factor(units);
3430 snprintf(buf, sizeof(buf), "%.2f", fabs(diff));
3431 for (p = buf; *p; ++p) {
3432 if (*p == '.') {
3433 char *z = p;
3434 while (*++p) {
3435 if (*p != '0') z = p + 1;
3437 p = z;
3438 break;
3441 strcpy(p, get_units_string(units));
3442 // FIXME: Highlight r_fore too.
3443 (void)r_fore;
3444 compile_diagnostic_reading(DIAG_WARN, r_back, msgno, buf);
3447 // If one (or both) compass readings are given, return Comp or BackComp
3448 // so we can report plumb legs with compass readings. If neither are,
3449 // return End.
3450 static reading
3451 handle_comp_units(void)
3453 reading which_comp = End;
3454 if (VAL(Comp) != HUGE_REAL) {
3455 which_comp = Comp;
3456 VAL(Comp) *= pcs->units[Q_BEARING];
3457 if (VAL(Comp) < (real)0.0 || VAL(Comp) - M_PI * 2.0 > EPSILON) {
3458 /* TRANSLATORS: Suspicious means something like 410 degrees or -20
3459 * degrees */
3460 compile_diagnostic_reading(DIAG_WARN, Comp, /*Suspicious compass reading*/59);
3461 VAL(Comp) = mod2pi(VAL(Comp));
3464 if (VAL(BackComp) != HUGE_REAL) {
3465 if (which_comp == End) which_comp = BackComp;
3466 VAL(BackComp) *= pcs->units[Q_BACKBEARING];
3467 if (VAL(BackComp) < (real)0.0 || VAL(BackComp) - M_PI * 2.0 > EPSILON) {
3468 /* FIXME: different message for BackComp? */
3469 compile_diagnostic_reading(DIAG_WARN, BackComp, /*Suspicious compass reading*/59);
3470 VAL(BackComp) = mod2pi(VAL(BackComp));
3473 return which_comp;
3476 static real compute_convergence(real lon, real lat) {
3477 // PROJ < 8.1.0 dereferences the context without a NULL check inside
3478 // proj_create_ellipsoidal_2D_cs() but PJ_DEFAULT_CTX is really just
3479 // NULL so for affected PROJ versions we create a context temporarily to
3480 // avoid a segmentation fault.
3481 PJ_CONTEXT * ctx = PJ_DEFAULT_CTX;
3482 #if PROJ_VERSION_MAJOR < 8 || \
3483 (PROJ_VERSION_MAJOR == 8 && PROJ_VERSION_MINOR < 1)
3484 ctx = proj_context_create();
3485 #endif
3487 if (!proj_str_out) {
3488 compile_diagnostic(DIAG_ERR, /*Output coordinate system not set*/488);
3489 return 0.0;
3491 PJ * pj = proj_create(ctx, proj_str_out);
3492 PJ_COORD lp;
3493 lp.lp.lam = lon;
3494 lp.lp.phi = lat;
3495 #if PROJ_VERSION_MAJOR < 8 || \
3496 (PROJ_VERSION_MAJOR == 8 && PROJ_VERSION_MINOR < 2)
3497 /* Code adapted from fix in PROJ 8.2.0 to make proj_factors() work in
3498 * cases we need (e.g. a CRS specified as "EPSG:<number>").
3500 switch (proj_get_type(pj)) {
3501 case PJ_TYPE_PROJECTED_CRS: {
3502 /* If it is a projected CRS, then compute the factors on the conversion
3503 * associated to it. We need to start from a temporary geographic CRS
3504 * using the same datum as the one of the projected CRS, and with
3505 * input coordinates being in longitude, latitude order in radian,
3506 * to be consistent with the expectations of the lp input parameter.
3509 PJ * geodetic_crs = proj_get_source_crs(ctx, pj);
3510 if (!geodetic_crs)
3511 break;
3512 PJ * datum = proj_crs_get_datum(ctx, geodetic_crs);
3513 #if PROJ_VERSION_MAJOR == 8 || \
3514 (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
3515 /* PROJ 7.2.0 upgraded to EPSG 10.x which added the concept
3516 * of a datum ensemble, and this version of PROJ also added
3517 * an API to deal with these.
3519 * If we're using PROJ < 7.2.0 then its EPSG database won't
3520 * have datum ensembles, so we don't need any code to handle
3521 * them.
3523 if (!datum) {
3524 datum = proj_crs_get_datum_ensemble(ctx, geodetic_crs);
3526 #endif
3527 PJ * cs = proj_create_ellipsoidal_2D_cs(
3528 ctx, PJ_ELLPS2D_LONGITUDE_LATITUDE, "Radian", 1.0);
3529 PJ * temp = proj_create_geographic_crs_from_datum(
3530 ctx, "unnamed crs", datum, cs);
3531 proj_destroy(datum);
3532 proj_destroy(cs);
3533 proj_destroy(geodetic_crs);
3534 PJ * newOp = proj_create_crs_to_crs_from_pj(ctx, temp, pj, NULL, NULL);
3535 proj_destroy(temp);
3536 if (newOp) {
3537 proj_destroy(pj);
3538 pj = newOp;
3540 break;
3542 default:
3543 break;
3545 #endif
3546 #if PROJ_VERSION_MAJOR < 9 || \
3547 (PROJ_VERSION_MAJOR == 9 && PROJ_VERSION_MINOR < 3)
3548 if (pj) {
3549 /* In PROJ < 9.3.0 proj_factors() returns a grid convergence which is
3550 * off by 90Ā° for a projected coordinate system with northing/easting
3551 * axis order. We can't copy over the fix for this in PROJ 9.3.0's
3552 * proj_factors() since it uses non-public PROJ functions, but
3553 * normalising the output order here works too.
3555 PJ* pj_norm = proj_normalize_for_visualization(PJ_DEFAULT_CTX, pj);
3556 proj_destroy(pj);
3557 pj = pj_norm;
3559 #endif
3560 PJ_FACTORS factors = proj_factors(pj, lp);
3561 proj_destroy(pj);
3562 #if PROJ_VERSION_MAJOR < 8 || \
3563 (PROJ_VERSION_MAJOR == 8 && PROJ_VERSION_MINOR < 1)
3564 proj_context_destroy(ctx);
3565 #endif
3566 return factors.meridian_convergence;
3569 static real
3570 handle_compass(real *p_var)
3572 real compvar = VAR(Comp);
3573 real comp = VAL(Comp);
3574 real backcomp = VAL(BackComp);
3575 real declination;
3576 if (pcs->z[Q_DECLINATION] != HUGE_REAL) {
3577 declination = -pcs->z[Q_DECLINATION];
3578 } else if (pcs->declination != HUGE_REAL) {
3579 /* Cached value calculated for a previous compass reading taken on the
3580 * same date (by the 'else' just below).
3582 declination = pcs->declination;
3583 } else {
3584 if (!pcs->meta || pcs->meta->days1 == -1) {
3585 compile_diagnostic(DIAG_WARN, /*No survey date specified - using 0 for magnetic declination*/304);
3586 declination = 0;
3587 } else {
3588 int avg_days = (pcs->meta->days1 + pcs->meta->days2) / 2;
3589 double dat = julian_date_from_days_since_1900(avg_days);
3590 /* thgeomag() takes (lat, lon, h, dat) - i.e. (y, x, z, date). */
3591 declination = thgeomag(pcs->dec_lat, pcs->dec_lon, pcs->dec_alt, dat);
3592 if (declination < pcs->min_declination) {
3593 pcs->min_declination = declination;
3594 pcs->min_declination_days = avg_days;
3596 if (declination > pcs->max_declination) {
3597 pcs->max_declination = declination;
3598 pcs->max_declination_days = avg_days;
3601 if (pcs->convergence == HUGE_REAL) {
3602 /* Compute the convergence lazily. It only depends on the output
3603 * coordinate system so we can cache it for reuse to apply to
3604 * a declination value for a different date.
3606 pcs->convergence = compute_convergence(pcs->dec_lon, pcs->dec_lat);
3608 declination -= pcs->convergence;
3609 /* We cache the calculated declination as the calculation is relatively
3610 * expensive. We also cache an "assumed 0" answer so that we only
3611 * warn once per such survey rather than for every line with a compass
3612 * reading. */
3613 pcs->declination = declination;
3615 if (comp != HUGE_REAL) {
3616 comp = (comp - pcs->z[Q_BEARING]) * pcs->sc[Q_BEARING];
3617 comp += declination;
3619 if (backcomp != HUGE_REAL) {
3620 backcomp = (backcomp - pcs->z[Q_BACKBEARING])
3621 * pcs->sc[Q_BACKBEARING];
3622 backcomp += declination;
3623 backcomp -= M_PI;
3624 if (comp != HUGE_REAL) {
3625 real diff = comp - backcomp;
3626 real adj = fabs(diff) > M_PI ? M_PI : 0;
3627 diff -= floor((diff + M_PI) / (2 * M_PI)) * 2 * M_PI;
3628 if (sqrd(diff / 3.0) > compvar + VAR(BackComp)) {
3629 /* fore and back readings differ by more than 3 sds */
3630 /* TRANSLATORS: %s is replaced by the amount the readings disagree
3631 * by, e.g. "2.5Ā°" or "3įµ". */
3632 warn_readings_differ(/*COMPASS reading and BACKCOMPASS reading disagree by %s*/98,
3633 diff, get_angle_units(Q_BEARING), Comp, BackComp);
3635 comp = (comp / compvar + backcomp / VAR(BackComp));
3636 compvar = (compvar + VAR(BackComp)) / 4;
3637 comp *= compvar;
3638 comp += adj;
3639 } else {
3640 comp = backcomp;
3641 compvar = VAR(BackComp);
3644 *p_var = compvar;
3645 return comp;
3648 static real
3649 handle_clino(q_quantity q, reading r, real val, bool percent, clino_type *p_ctype)
3651 bool range_0_180;
3652 real z;
3653 real diff_from_abs90;
3654 val *= pcs->units[q];
3655 /* percentage scale */
3656 if (percent) val = atan(val);
3657 /* We want to warn if there's a reading which it would be impossible
3658 * to have read from the instrument (e.g. on a -90 to 90 degree scale
3659 * you can't read "96" (it's probably a typo for "69"). However, the
3660 * gradient reading from a topofil is typically in the range 0 to 180,
3661 * with 90 being horizontal.
3663 * Really we should allow the valid range to be specified, but for now
3664 * we infer it from the zero error - if this is within 45 degrees of
3665 * 90 then we assume the range is 0 to 180.
3667 z = pcs->z[q];
3668 range_0_180 = (z > M_PI_4 && z < 3*M_PI_4);
3669 diff_from_abs90 = fabs(val) - M_PI_2;
3670 if (diff_from_abs90 > EPSILON) {
3671 if (!range_0_180) {
3672 int clino_units = get_angle_units(q);
3673 const char * units = get_units_string(clino_units);
3674 real right_angle = M_PI_2 / get_units_factor(clino_units);
3675 /* FIXME: different message for BackClino? */
3676 /* TRANSLATORS: %.f%s will be replaced with a right angle in the
3677 * units currently in use, e.g. "90Ā°" or "100įµ". And "absolute
3678 * value" means the reading ignoring the sign (so it might be
3679 * < -90Ā° or > 90Ā°. */
3680 compile_diagnostic_reading(DIAG_WARN, r, /*Clino reading over %.f%s (absolute value)*/51,
3681 right_angle, units);
3683 } else if (TSTBIT(pcs->infer, INFER_PLUMBS) &&
3684 diff_from_abs90 >= -EPSILON) {
3685 *p_ctype = CTYPE_INFERPLUMB;
3687 if (range_0_180 && *p_ctype != CTYPE_INFERPLUMB) {
3688 /* FIXME: Warning message not ideal... */
3689 if (val < 0.0 || val - M_PI > EPSILON) {
3690 int clino_units = get_angle_units(q);
3691 const char * units = get_units_string(clino_units);
3692 real right_angle = M_PI_2 / get_units_factor(clino_units);
3693 compile_diagnostic_reading(DIAG_WARN, r, /*Clino reading over %.f%s (absolute value)*/51,
3694 right_angle, units);
3697 return val;
3700 static int
3701 process_normal(prefix *fr, prefix *to, bool fToFirst,
3702 clino_type ctype, clino_type backctype)
3704 real tape = VAL(Tape);
3705 real clin = VAL(Clino);
3706 real backclin = VAL(BackClino);
3708 real dx, dy, dz;
3709 real vx, vy, vz;
3710 #ifndef NO_COVARIANCES
3711 real cxy, cyz, czx;
3712 #endif
3714 /* adjusted tape is negative -- probably the calibration is wrong */
3715 if (tape < (real)0.0) {
3716 /* TRANSLATE different message for topofil? */
3717 compile_diagnostic_reading(DIAG_WARN, Tape, /*Negative adjusted tape reading*/79);
3720 reading comp_given = handle_comp_units();
3722 if (ctype == CTYPE_READING) {
3723 clin = handle_clino(Q_GRADIENT, Clino, clin,
3724 pcs->f_clino_percent, &ctype);
3727 if (backctype == CTYPE_READING) {
3728 backclin = handle_clino(Q_BACKGRADIENT, BackClino, backclin,
3729 pcs->f_backclino_percent, &backctype);
3732 /* un-infer the plumb if the backsight was just a reading */
3733 if (ctype == CTYPE_INFERPLUMB && backctype == CTYPE_READING) {
3734 ctype = CTYPE_READING;
3737 if (ctype != CTYPE_OMIT && backctype != CTYPE_OMIT && ctype != backctype) {
3738 /* TRANSLATORS: In data with backsights, the user has tried to give a
3739 * plumb for the foresight and a clino reading for the backsight, or
3740 * something similar. */
3741 compile_error_reading_skip(Clino, /*CLINO and BACKCLINO readings must be of the same type*/84);
3742 return 0;
3745 if (ctype == CTYPE_PLUMB || ctype == CTYPE_INFERPLUMB ||
3746 backctype == CTYPE_PLUMB || backctype == CTYPE_INFERPLUMB) {
3747 /* plumbed */
3748 if (comp_given != End) {
3749 if (ctype == CTYPE_PLUMB ||
3750 (ctype == CTYPE_INFERPLUMB && VAL(Comp) != 0.0) ||
3751 backctype == CTYPE_PLUMB ||
3752 (backctype == CTYPE_INFERPLUMB &&
3753 (VAL(BackComp) != 0.0 && VAL(BackComp) != M_PI))) {
3754 /* TRANSLATORS: A "plumbed leg" is one measured using a plumbline
3755 * (a weight on a string). So the problem here is that the leg is
3756 * vertical, so a compass reading has no meaning! */
3757 compile_diagnostic_reading(DIAG_WARN, comp_given, /*Compass reading given on plumbed leg*/21);
3761 dx = dy = (real)0.0;
3762 if (ctype != CTYPE_OMIT) {
3763 if (backctype != CTYPE_OMIT && (clin > 0) == (backclin > 0)) {
3764 /* TRANSLATORS: We've been told the foresight and backsight are
3765 * both "UP", or that they're both "DOWN". */
3766 compile_error_reading_skip(Clino, /*Plumbed CLINO and BACKCLINO readings can't be in the same direction*/92);
3767 return 0;
3769 dz = (clin > (real)0.0) ? tape : -tape;
3770 } else {
3771 dz = (backclin < (real)0.0) ? tape : -tape;
3773 vx = vy = var(Q_POS) / 3.0 + dz * dz * var(Q_PLUMB);
3774 vz = var(Q_POS) / 3.0 + VAR(Tape);
3775 #ifndef NO_COVARIANCES
3776 /* Correct values - no covariances in this case! */
3777 cxy = cyz = czx = (real)0.0;
3778 #endif
3779 } else {
3780 /* Each of ctype and backctype are either CTYPE_READING/CTYPE_HORIZ
3781 * or CTYPE_OMIT */
3782 /* clino */
3783 real L2, cosG, LcosG, cosG2, sinB, cosB, dx2, dy2, dz2, v, V;
3784 if (comp_given == End) {
3785 /* TRANSLATORS: Here "legs" are survey legs, i.e. measurements between
3786 * survey stations. */
3787 compile_error_reading_skip(Comp, /*Compass reading may not be omitted except on plumbed legs*/14);
3788 return 0;
3790 if (tape == (real)0.0) {
3791 dx = dy = dz = (real)0.0;
3792 vx = vy = vz = (real)(var(Q_POS) / 3.0); /* Position error only */
3793 #ifndef NO_COVARIANCES
3794 cxy = cyz = czx = (real)0.0;
3795 #endif
3796 #if DEBUG_DATAIN_1
3797 printf("Zero length leg: vx = %f, vy = %f, vz = %f\n", vx, vy, vz);
3798 #endif
3799 } else {
3800 real sinGcosG;
3801 /* take into account variance in LEVEL case */
3802 real var_clin = var(Q_LEVEL);
3803 real var_comp;
3804 real comp = handle_compass(&var_comp);
3805 /* ctype != CTYPE_READING is LEVEL case */
3806 if (ctype == CTYPE_READING) {
3807 clin = (clin - pcs->z[Q_GRADIENT]) * pcs->sc[Q_GRADIENT];
3808 var_clin = VAR(Clino);
3810 if (backctype == CTYPE_READING) {
3811 backclin = (backclin - pcs->z[Q_BACKGRADIENT])
3812 * pcs->sc[Q_BACKGRADIENT];
3813 if (ctype == CTYPE_READING) {
3814 if (sqrd((clin + backclin) / 3.0) > var_clin + VAR(BackClino)) {
3815 /* fore and back readings differ by more than 3 sds */
3816 /* TRANSLATORS: %s is replaced by the amount the readings disagree
3817 * by, e.g. "2.5Ā°" or "3įµ". */
3818 warn_readings_differ(/*CLINO reading and BACKCLINO reading disagree by %s*/99,
3819 clin + backclin, get_angle_units(Q_GRADIENT), Clino, BackClino);
3821 clin = (clin / var_clin - backclin / VAR(BackClino));
3822 var_clin = (var_clin + VAR(BackClino)) / 4;
3823 clin *= var_clin;
3824 } else {
3825 clin = -backclin;
3826 var_clin = VAR(BackClino);
3830 #if DEBUG_DATAIN
3831 printf(" %4.2f %4.2f %4.2f\n", tape, comp, clin);
3832 #endif
3833 cosG = cos(clin);
3834 LcosG = tape * cosG;
3835 sinB = sin(comp);
3836 cosB = cos(comp);
3837 #if DEBUG_DATAIN_1
3838 printf("sinB = %f, cosG = %f, LcosG = %f\n", sinB, cosG, LcosG);
3839 #endif
3840 dx = LcosG * sinB;
3841 dy = LcosG * cosB;
3842 dz = tape * sin(clin);
3843 /* printf("%.2f\n",clin); */
3844 #if DEBUG_DATAIN_1
3845 printf("dx = %f\ndy = %f\ndz = %f\n", dx, dy, dz);
3846 #endif
3847 dx2 = dx * dx;
3848 L2 = tape * tape;
3849 V = VAR(Tape) / L2;
3850 dy2 = dy * dy;
3851 cosG2 = cosG * cosG;
3852 sinGcosG = sin(clin) * cosG;
3853 dz2 = dz * dz;
3854 v = dz2 * var_clin;
3855 #ifdef NO_COVARIANCES
3856 vx = (var(Q_POS) / 3.0 + dx2 * V + dy2 * var_comp +
3857 (.5 + sinB * sinB * cosG2) * v);
3858 vy = (var(Q_POS) / 3.0 + dy2 * V + dx2 * var_comp +
3859 (.5 + cosB * cosB * cosG2) * v);
3860 if (ctype == CTYPE_OMIT && backctype == CTYPE_OMIT) {
3861 /* if no clino, assume sd=tape/sqrt(10) so 3sds = .95*tape */
3862 vz = var(Q_POS) / 3.0 + L2 * (real)0.1;
3863 } else {
3864 vz = var(Q_POS) / 3.0 + dz2 * V + L2 * cosG2 * var_clin;
3866 /* for Surveyor87 errors: vx=vy=vz=var(Q_POS)/3.0; */
3867 #else
3868 vx = var(Q_POS) / 3.0 + dx2 * V + dy2 * var_comp +
3869 (sinB * sinB * v);
3870 vy = var(Q_POS) / 3.0 + dy2 * V + dx2 * var_comp +
3871 (cosB * cosB * v);
3872 if (ctype == CTYPE_OMIT && backctype == CTYPE_OMIT) {
3873 /* if no clino, assume sd=tape/sqrt(10) so 3sds = .95*tape */
3874 vz = var(Q_POS) / 3.0 + L2 * (real)0.1;
3875 } else {
3876 vz = var(Q_POS) / 3.0 + dz2 * V + L2 * cosG2 * var_clin;
3878 /* usual covariance formulae are fine in no clino case since
3879 * dz = 0 so value of var_clin is ignored */
3880 cxy = sinB * cosB * (VAR(Tape) * cosG2 + var_clin * dz2)
3881 - var_comp * dx * dy;
3882 czx = VAR(Tape) * sinB * sinGcosG - var_clin * dx * dz;
3883 cyz = VAR(Tape) * cosB * sinGcosG - var_clin * dy * dz;
3884 #if 0
3885 printf("vx = %6.3f, vy = %6.3f, vz = %6.3f\n", vx, vy, vz);
3886 printf("cxy = %6.3f, cyz = %6.3f, czx = %6.3f\n", cxy, cyz, czx);
3887 #endif
3888 #endif
3889 #if DEBUG_DATAIN_1
3890 printf("In DATAIN.C, vx = %f, vy = %f, vz = %f\n", vx, vy, vz);
3891 #endif
3894 #if DEBUG_DATAIN_1
3895 printf("Just before addleg, vx = %f\n", vx);
3896 #endif
3897 /*printf("dx,dy,dz = %.2f %.2f %.2f\n\n", dx, dy, dz);*/
3898 addlegbyname(fr, to, fToFirst, dx, dy, dz, vx, vy, vz
3899 #ifndef NO_COVARIANCES
3900 , cyz, czx, cxy
3901 #endif
3903 return 1;
3906 static int
3907 process_diving(prefix *fr, prefix *to, bool fToFirst, bool fDepthChange)
3909 real tape = VAL(Tape);
3911 real dx, dy, dz;
3912 real vx, vy, vz;
3913 #ifndef NO_COVARIANCES
3914 real cxy = 0, cyz = 0, czx = 0;
3915 #endif
3917 handle_comp_units();
3919 /* depth gauge readings increase upwards with default calibration */
3920 if (fDepthChange) {
3921 SVX_ASSERT(VAL(FrDepth) == 0.0);
3922 dz = VAL(ToDepth) * pcs->units[Q_DEPTH] - pcs->z[Q_DEPTH];
3923 dz *= pcs->sc[Q_DEPTH];
3924 } else {
3925 dz = VAL(ToDepth) - VAL(FrDepth);
3926 dz *= pcs->units[Q_DEPTH] * pcs->sc[Q_DEPTH];
3929 /* adjusted tape is negative -- probably the calibration is wrong */
3930 if (tape < (real)0.0) {
3931 compile_diagnostic_reading(DIAG_WARN, Tape, /*Negative adjusted tape reading*/79);
3934 /* check if tape is less than depth change */
3935 if (tape < fabs(dz)) {
3936 /* FIXME: allow margin of error based on variances? */
3937 /* TRANSLATORS: This means that the data fed in said this.
3939 * It could be a gross error (e.g. the decimal point is missing from the
3940 * depth gauge reading) or it could just be due to random error on a near
3941 * vertical leg */
3942 compile_diagnostic_reading(DIAG_WARN, Tape, /*Tape reading is less than change in depth*/62);
3945 if (tape == (real)0.0 && dz == 0.0) {
3946 dx = dy = dz = (real)0.0;
3947 vx = vy = vz = (real)(var(Q_POS) / 3.0); /* Position error only */
3948 } else if (VAL(Comp) == HUGE_REAL &&
3949 VAL(BackComp) == HUGE_REAL) {
3950 /* plumb */
3951 dx = dy = (real)0.0;
3952 if (dz < 0) tape = -tape;
3953 /* FIXME: Should use FrDepth sometimes... */
3954 dz = (dz * VAR(Tape) + tape * 2 * VAR(ToDepth))
3955 / (VAR(Tape) * 2 * VAR(ToDepth));
3956 vx = vy = var(Q_POS) / 3.0 + dz * dz * var(Q_PLUMB);
3957 /* FIXME: Should use FrDepth sometimes... */
3958 vz = var(Q_POS) / 3.0 + VAR(Tape) * 2 * VAR(ToDepth)
3959 / (VAR(Tape) + VAR(ToDepth));
3960 } else {
3961 real L2, sinB, cosB, dz2, D2;
3962 real var_comp;
3963 real comp = handle_compass(&var_comp);
3964 sinB = sin(comp);
3965 cosB = cos(comp);
3966 L2 = tape * tape;
3967 dz2 = dz * dz;
3968 D2 = L2 - dz2;
3969 if (D2 <= (real)0.0) {
3970 /* FIXME: Should use FrDepth sometimes... */
3971 real vsum = VAR(Tape) + 2 * VAR(ToDepth);
3972 dx = dy = (real)0.0;
3973 vx = vy = var(Q_POS) / 3.0;
3974 /* FIXME: Should use FrDepth sometimes... */
3975 vz = var(Q_POS) / 3.0 + VAR(Tape) * 2 * VAR(ToDepth) / vsum;
3976 if (dz > 0) {
3977 /* FIXME: Should use FrDepth sometimes... */
3978 dz = (dz * VAR(Tape) + tape * 2 * VAR(ToDepth)) / vsum;
3979 } else {
3980 dz = (dz * VAR(Tape) - tape * 2 * VAR(ToDepth)) / vsum;
3982 } else {
3983 real D = sqrt(D2);
3984 /* FIXME: Should use FrDepth sometimes... */
3985 real F = VAR(Tape) * L2 + 2 * VAR(ToDepth) * D2;
3986 dx = D * sinB;
3987 dy = D * cosB;
3989 vx = var(Q_POS) / 3.0 +
3990 sinB * sinB * F / D2 + var_comp * dy * dy;
3991 vy = var(Q_POS) / 3.0 +
3992 cosB * cosB * F / D2 + var_comp * dx * dx;
3993 /* FIXME: Should use FrDepth sometimes... */
3994 vz = var(Q_POS) / 3.0 + 2 * VAR(ToDepth);
3996 #ifndef NO_COVARIANCES
3997 cxy = sinB * cosB * (F / D2 + var_comp * D2);
3998 /* FIXME: Should use FrDepth sometimes... */
3999 cyz = -2 * VAR(ToDepth) * dy / D;
4000 czx = -2 * VAR(ToDepth) * dx / D;
4001 #endif
4003 /* FIXME: If there's a clino reading, check it against the depth reading,
4004 * and average.
4005 * if (VAL(Clino) != HUGE_REAL || VAL(BackClino) != HUGE_REAL) { ... }
4008 addlegbyname(fr, to, fToFirst, dx, dy, dz, vx, vy, vz
4009 #ifndef NO_COVARIANCES
4010 , cxy, cyz, czx
4011 #endif
4013 return 1;
4016 static int
4017 process_cartesian(prefix *fr, prefix *to, bool fToFirst)
4019 real dx = (VAL(Dx) * pcs->units[Q_DX] - pcs->z[Q_DX]) * pcs->sc[Q_DX];
4020 real dy = (VAL(Dy) * pcs->units[Q_DY] - pcs->z[Q_DY]) * pcs->sc[Q_DY];
4021 real dz = (VAL(Dz) * pcs->units[Q_DZ] - pcs->z[Q_DZ]) * pcs->sc[Q_DZ];
4023 addlegbyname(fr, to, fToFirst, dx, dy, dz, VAR(Dx), VAR(Dy), VAR(Dz)
4024 #ifndef NO_COVARIANCES
4025 , 0, 0, 0
4026 #endif
4028 return 1;
4031 static void
4032 data_cartesian(void)
4034 prefix *fr = NULL, *to = NULL;
4036 bool fMulti = false;
4038 reading first_stn = End;
4040 const reading *ordering;
4042 again:
4044 for (ordering = pcs->ordering ; ; ordering++) {
4045 skipblanks();
4046 switch (*ordering) {
4047 case Fr:
4048 fr = read_prefix(PFX_STATION|PFX_ALLOW_ROOT);
4049 if (first_stn == End) first_stn = Fr;
4050 break;
4051 case To:
4052 to = read_prefix(PFX_STATION|PFX_ALLOW_ROOT);
4053 if (first_stn == End) first_stn = To;
4054 break;
4055 case Station:
4056 fr = to;
4057 to = read_prefix(PFX_STATION);
4058 first_stn = To;
4059 break;
4060 case Dx: case Dy: case Dz:
4061 read_reading(*ordering, false);
4062 break;
4063 case WallsSRVFr:
4064 // Walls SRV is always From then To.
4065 first_stn = Fr;
4066 fr = read_walls_station(p_walls_options->prefix, true);
4067 skipblanks();
4068 if (ch == '*' || ch == '<') {
4069 // Isolated LRUD. Ignore for now.
4070 skipline();
4071 process_eol();
4072 return;
4074 break;
4075 case WallsSRVTo:
4076 to = read_walls_station(p_walls_options->prefix, true);
4077 skipblanks();
4078 if (ch == '*' || ch == '<') {
4079 // Odd apparently undocumented variant of isolated LRUD. Ignore
4080 // for now.
4081 skipline();
4082 process_eol();
4083 return;
4085 break;
4086 case Ignore:
4087 skipword(); break;
4088 case IgnoreAllAndNewLine:
4089 skipline();
4090 /* fall through */
4091 case Newline:
4092 if (fr != NULL) {
4093 if (!process_cartesian(fr, to, first_stn == To))
4094 skipline();
4096 fMulti = true;
4097 while (1) {
4098 process_eol();
4099 skipblanks();
4100 if (isData(ch)) break;
4101 if (!isComm(ch)) {
4102 return;
4105 break;
4106 case IgnoreAll:
4107 skipline();
4108 /* fall through */
4109 case End:
4110 if (!fMulti) {
4111 process_cartesian(fr, to, first_stn == To);
4112 process_eol();
4113 return;
4115 do {
4116 process_eol();
4117 skipblanks();
4118 } while (isComm(ch));
4119 goto again;
4120 default: BUG("Unknown reading in ordering");
4125 static int
4126 process_cylpolar(prefix *fr, prefix *to, bool fToFirst, bool fDepthChange)
4128 real tape = VAL(Tape);
4130 real dx, dy, dz;
4131 real vx, vy, vz;
4132 #ifndef NO_COVARIANCES
4133 real cxy = 0;
4134 #endif
4136 handle_comp_units();
4138 /* depth gauge readings increase upwards with default calibration */
4139 if (fDepthChange) {
4140 SVX_ASSERT(VAL(FrDepth) == 0.0);
4141 dz = VAL(ToDepth) * pcs->units[Q_DEPTH] - pcs->z[Q_DEPTH];
4142 dz *= pcs->sc[Q_DEPTH];
4143 } else {
4144 dz = VAL(ToDepth) - VAL(FrDepth);
4145 dz *= pcs->units[Q_DEPTH] * pcs->sc[Q_DEPTH];
4148 /* adjusted tape is negative -- probably the calibration is wrong */
4149 if (tape < (real)0.0) {
4150 compile_diagnostic_reading(DIAG_WARN, Tape, /*Negative adjusted tape reading*/79);
4153 if (VAL(Comp) == HUGE_REAL && VAL(BackComp) == HUGE_REAL) {
4154 /* plumb */
4155 dx = dy = (real)0.0;
4156 vx = vy = var(Q_POS) / 3.0 + dz * dz * var(Q_PLUMB);
4157 /* FIXME: Should use FrDepth sometimes... */
4158 vz = var(Q_POS) / 3.0 + 2 * VAR(ToDepth);
4159 } else {
4160 real sinB, cosB;
4161 real var_comp;
4162 real comp = handle_compass(&var_comp);
4163 sinB = sin(comp);
4164 cosB = cos(comp);
4166 dx = tape * sinB;
4167 dy = tape * cosB;
4169 vx = var(Q_POS) / 3.0 +
4170 VAR(Tape) * sinB * sinB + var_comp * dy * dy;
4171 vy = var(Q_POS) / 3.0 +
4172 VAR(Tape) * cosB * cosB + var_comp * dx * dx;
4173 /* FIXME: Should use FrDepth sometimes... */
4174 vz = var(Q_POS) / 3.0 + 2 * VAR(ToDepth);
4176 #ifndef NO_COVARIANCES
4177 cxy = (VAR(Tape) - var_comp * tape * tape) * sinB * cosB;
4178 #endif
4180 addlegbyname(fr, to, fToFirst, dx, dy, dz, vx, vy, vz
4181 #ifndef NO_COVARIANCES
4182 , cxy, 0, 0
4183 #endif
4185 return 1;
4188 /* Process tape/compass/clino, diving, and cylpolar styles of survey data
4189 * Also handles topofil (fromcount/tocount or count) in place of tape */
4190 static void
4191 data_normal(void)
4193 prefix *fr = NULL, *to = NULL;
4194 reading first_stn = End;
4196 bool fTopofil = false, fMulti = false;
4197 bool fRev;
4198 clino_type ctype, backctype;
4199 bool fDepthChange;
4200 unsigned long compass_dat_flags = 0;
4202 const reading *ordering;
4204 VAL(Tape) = VAL(BackTape) = HUGE_REAL;
4205 VAL(Comp) = VAL(BackComp) = HUGE_REAL;
4206 VAL(FrCount) = VAL(ToCount) = 0;
4207 VAL(FrDepth) = VAL(ToDepth) = 0;
4208 VAL(Left) = VAL(Right) = VAL(Up) = VAL(Down) = HUGE_REAL;
4210 fRev = false;
4211 ctype = backctype = CTYPE_OMIT;
4212 fDepthChange = false;
4214 /* ordering may omit clino reading, so set up default here */
4215 /* this is also used if clino reading is the omit character */
4216 VAL(Clino) = VAL(BackClino) = 0;
4218 again:
4220 /* We clear these flags in the normal course of events, but if there's an
4221 * error in a reading, we might not, so make sure it has been cleared here.
4223 pcs->flags &= ~(BIT(FLAGS_ANON_ONE_END) | BIT(FLAGS_IMPLICIT_SPLAY));
4224 for (ordering = pcs->ordering; ; ordering++) {
4225 skipblanks();
4226 switch (*ordering) {
4227 case Fr:
4228 fr = read_prefix(PFX_STATION|PFX_ALLOW_ROOT|PFX_ANON);
4229 if (first_stn == End) first_stn = Fr;
4230 break;
4231 case To:
4232 to = read_prefix(PFX_STATION|PFX_ALLOW_ROOT|PFX_ANON);
4233 if (first_stn == End) first_stn = To;
4234 break;
4235 case CompassDATFr:
4236 // Compass DAT is always From then To.
4237 first_stn = Fr;
4238 fr = read_prefix(PFX_STATION);
4239 scan_compass_station_name(fr);
4240 break;
4241 case CompassDATTo:
4242 to = read_prefix(PFX_STATION);
4243 scan_compass_station_name(to);
4244 break;
4245 case Station:
4246 fr = to;
4247 to = read_prefix(PFX_STATION);
4248 first_stn = To;
4249 break;
4250 case Dir: {
4251 typedef enum {
4252 DIR_NULL=-1, DIR_FORE, DIR_BACK
4253 } dir_tok;
4254 static const sztok dir_tab[] = {
4255 {"B", DIR_BACK},
4256 {"F", DIR_FORE},
4258 dir_tok tok;
4259 get_token();
4260 tok = match_tok(dir_tab, TABSIZE(dir_tab));
4261 switch (tok) {
4262 case DIR_FORE:
4263 break;
4264 case DIR_BACK:
4265 fRev = true;
4266 break;
4267 default:
4268 compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Found ā€œ%sā€, expecting ā€œFā€ or ā€œBā€*/131, s_str(&token));
4269 process_eol();
4270 return;
4272 break;
4274 case Tape: case BackTape: {
4275 reading r = *ordering;
4276 read_reading(r, true);
4277 if (VAL(r) == HUGE_REAL) {
4278 if (!isOmit(ch)) {
4279 compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found ā€œ%sā€*/9);
4280 /* Avoid also warning about omitted tape reading. */
4281 VAL(r) = 0;
4282 } else {
4283 nextch();
4285 } else if (VAL(r) < (real)0.0) {
4286 compile_diagnostic_reading(DIAG_WARN, r, /*Negative tape reading*/60);
4288 break;
4290 case Count:
4291 VAL(FrCount) = VAL(ToCount);
4292 LOC(FrCount) = LOC(ToCount);
4293 WID(FrCount) = WID(ToCount);
4294 read_reading(ToCount, false);
4295 fTopofil = true;
4296 break;
4297 case FrCount:
4298 read_reading(FrCount, false);
4299 break;
4300 case ToCount:
4301 read_reading(ToCount, false);
4302 fTopofil = true;
4303 break;
4304 case Comp: case BackComp:
4305 read_bearing_or_omit(*ordering);
4306 break;
4307 case Clino: case BackClino: {
4308 reading r = *ordering;
4309 clino_type * p_ctype = (r == Clino ? &ctype : &backctype);
4310 read_reading(r, true);
4311 if (VAL(r) == HUGE_REAL) {
4312 VAL(r) = handle_plumb(p_ctype);
4313 if (VAL(r) != HUGE_REAL) {
4314 WID(r) = ftell(file.fh) - LOC(r);
4315 break;
4317 compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found ā€œ%sā€*/9);
4318 skipline();
4319 process_eol();
4320 return;
4322 *p_ctype = CTYPE_READING;
4323 break;
4325 case FrDepth: case ToDepth:
4326 read_reading(*ordering, false);
4327 break;
4328 case Depth:
4329 VAL(FrDepth) = VAL(ToDepth);
4330 LOC(FrDepth) = LOC(ToDepth);
4331 WID(FrDepth) = WID(ToDepth);
4332 read_reading(ToDepth, false);
4333 break;
4334 case DepthChange:
4335 fDepthChange = true;
4336 VAL(FrDepth) = 0;
4337 read_reading(ToDepth, false);
4338 break;
4339 case CompassDATComp:
4340 read_bearing_or_omit(Comp);
4341 if (is_compass_NaN(VAL(Comp))) VAL(Comp) = HUGE_REAL;
4342 break;
4343 case CompassDATBackComp:
4344 read_bearing_or_omit(BackComp);
4345 if (is_compass_NaN(VAL(BackComp))) VAL(BackComp) = HUGE_REAL;
4346 break;
4347 case CompassDATClino: case CompassDATBackClino: {
4348 reading r;
4349 clino_type * p_ctype;
4350 if (*ordering == CompassDATClino) {
4351 r = Clino;
4352 p_ctype = &ctype;
4353 } else {
4354 r = BackClino;
4355 p_ctype = &backctype;
4357 read_reading(r, false);
4358 if (is_compass_NaN(VAL(r))) {
4359 VAL(r) = 0;
4360 *p_ctype = CTYPE_OMIT;
4361 } else {
4362 *p_ctype = CTYPE_READING;
4364 break;
4366 case CompassDATLeft: case CompassDATRight:
4367 case CompassDATUp: case CompassDATDown: {
4368 /* FIXME: need to actually make use of these entries! */
4369 reading actual = Left + (*ordering - CompassDATLeft);
4370 read_reading(actual, false);
4371 if (VAL(actual) < 0) VAL(actual) = HUGE_REAL;
4372 break;
4374 case CompassDATFlags:
4375 if (ch == '#') {
4376 filepos fp;
4377 get_pos(&fp);
4378 nextch();
4379 if (ch == '|') {
4380 nextch();
4381 while (ch >= 'A' && ch <= 'Z') {
4382 compass_dat_flags |= BIT(ch - 'A');
4383 /* Flags we handle:
4384 * L (exclude from length)
4385 * S (splay)
4386 * P (no plot) (mapped to FLAG_SURFACE)
4387 * X (exclude data)
4388 * FIXME: Defined flags we currently ignore:
4389 * C (no adjustment) (set all (co)variances to 0? Then
4390 * we need to handle a loop of such legs or a traverse
4391 * of such legs between two fixed points...)
4393 nextch();
4395 if (ch == '#') {
4396 nextch();
4397 } else {
4398 compass_dat_flags = 0;
4399 set_pos(&fp);
4401 } else {
4402 set_pos(&fp);
4405 break;
4406 case WallsSRVFr:
4407 // Walls SRV is always From then To.
4408 first_stn = Fr;
4409 fr = read_walls_station(p_walls_options->prefix, true);
4410 skipblanks();
4411 if (ch == '*' || ch == '<') {
4412 // Isolated LRUD. Ignore for now.
4413 skipline();
4414 process_eol();
4415 return;
4417 break;
4418 case WallsSRVTo:
4419 to = read_walls_station(p_walls_options->prefix, true);
4420 skipblanks();
4421 if (ch == '*' || ch == '<') {
4422 // Odd apparently undocumented variant of isolated LRUD. Ignore
4423 // for now.
4424 skipline();
4425 process_eol();
4426 return;
4428 break;
4429 case WallsSRVTape:
4430 LOC(Tape) = ftell(file.fh);
4431 VAL(Tape) = read_numeric(true);
4432 if (VAL(Tape) == HUGE_REAL) {
4433 if (ch == 'i' || ch == 'I') {
4434 // Length specified in inches only, e.g. `i6` is 6 inches.
4435 VAL(Tape) = 0.0;
4436 goto inches_only;
4438 // Walls expects 2 or more - for an omitted value.
4439 if (ch != '-' || nextch() != '-') {
4440 compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found ā€œ%sā€*/9);
4441 /* Avoid also warning about omitted tape reading. */
4442 VAL(Tape) = 0;
4443 } else {
4444 while (nextch() == '-') { }
4446 } else {
4447 if (VAL(Tape) < (real)0.0)
4448 compile_diagnostic_reading(DIAG_WARN, Tape, /*Negative tape reading*/60);
4449 switch (ch) {
4450 case 'I': case 'i':
4451 inches_only:
4452 nextch();
4453 if (isdigit(ch)) {
4454 real inches = read_numeric(false);
4455 VAL(Tape) += inches / 12.0;
4457 /* FALLTHRU */
4458 case 'F': case 'f':
4459 VAL(Tape) *= METRES_PER_FOOT;
4460 /* FALLTHRU */
4461 case 'M': case 'm':
4462 VAL(Tape) /= pcs->units[Q_LENGTH];
4463 nextch();
4466 WID(Tape) = ftell(file.fh) - LOC(Tape);
4467 VAR(Tape) = var(Q_LENGTH);
4468 break;
4469 case WallsSRVComp: {
4470 skipblanks();
4471 LOC(Comp) = ftell(file.fh);
4472 if (ch != '/') {
4473 if (isalpha(ch)) {
4474 VAL(Comp) = read_quadrant(false);
4475 } else {
4476 VAL(Comp) = read_number(true, false);
4477 if (VAL(Comp) == HUGE_REAL) {
4478 if (ch != '-') {
4479 compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found ā€œ%sā€*/9);
4480 skipline();
4481 process_eol();
4482 return;
4484 // Walls documents two or more `-` for an omitted
4485 // reading, but actually just one works too!
4486 while (nextch() == '-') { }
4487 } else {
4488 switch (ch) {
4489 case 'D': case 'd':
4490 // Degrees.
4491 VAL(Comp) *= M_PI / 180.0 / pcs->units[Q_BEARING];
4492 nextch();
4493 break;
4494 case 'G': case 'g':
4495 // Grads.
4496 VAL(Comp) *= M_PI / 200.0 / pcs->units[Q_BEARING];
4497 nextch();
4498 break;
4499 case 'M': case 'm':
4500 // Mils.
4501 VAL(Comp) *= M_PI / 3200.0 / pcs->units[Q_BEARING];
4502 nextch();
4503 break;
4507 WID(Comp) = ftell(file.fh) - LOC(Comp);
4508 VAR(Comp) = var(Q_BEARING);
4509 } else {
4510 // Omitted foresight, e.g. `/123` or `/` (both omitted).
4511 WID(Comp) = 0;
4512 VAL(Comp) = HUGE_REAL;
4514 if (ch == '/' && !isBlank(nextch())) {
4515 LOC(BackComp) = ftell(file.fh);
4516 if (isalpha(ch)) {
4517 VAL(BackComp) = read_quadrant(false);
4518 } else {
4519 VAL(BackComp) = read_number(true, false);
4520 if (VAL(BackComp) == HUGE_REAL) {
4521 if (ch != '-') {
4522 compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found ā€œ%sā€*/9);
4523 skipline();
4524 process_eol();
4525 return;
4527 // Walls documents two or more `-` for an omitted
4528 // reading, but actually just one works too!
4529 while (nextch() == '-') { }
4530 } else {
4531 switch (ch) {
4532 case 'D': case 'd':
4533 // Degrees.
4534 VAL(BackComp) *= M_PI / 180.0 / pcs->units[Q_BACKBEARING];
4535 nextch();
4536 break;
4537 case 'G': case 'g':
4538 // Grads.
4539 VAL(BackComp) *= M_PI / 200.0 / pcs->units[Q_BACKBEARING];
4540 nextch();
4541 break;
4542 case 'M': case 'm':
4543 // Mils.
4544 VAL(BackComp) *= M_PI / 3200.0 / pcs->units[Q_BACKBEARING];
4545 nextch();
4546 break;
4550 WID(BackComp) = ftell(file.fh) - LOC(BackComp);
4551 VAR(BackComp) = var(Q_BACKBEARING);
4552 } else {
4553 // Omitted backsight, e.g. `123/` or `/` (both omitted).
4554 LOC(BackComp) = ftell(file.fh);
4555 WID(BackComp) = 0;
4556 VAL(BackComp) = HUGE_REAL;
4558 break;
4560 case WallsSRVClino: {
4561 skipblanks();
4562 LOC(Clino) = ftell(file.fh);
4563 if (ch != '/') {
4564 real clin = read_number(true, false);
4565 if (clin == HUGE_REAL) {
4566 if (ch != '-') {
4567 // Undocumented, but the clino can be omitted with order=dav or order=adv.
4568 if (p_walls_options->data_order_ct[4] != WallsSRVClino) {
4569 compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found ā€œ%sā€*/9);
4570 skipline();
4571 process_eol();
4572 return;
4574 } else {
4575 // Walls documents two or more `-` for an omitted
4576 // reading, but actually just one works too!
4577 while (nextch() == '-') { }
4579 } else {
4580 switch (ch) {
4581 case 'D': case 'd':
4582 // Degrees.
4583 clin *= M_PI / 180.0 / pcs->units[Q_GRADIENT];
4584 nextch();
4585 break;
4586 case 'G': case 'g':
4587 // Grads.
4588 clin *= M_PI / 200.0 / pcs->units[Q_GRADIENT];
4589 nextch();
4590 break;
4591 case 'M': case 'm':
4592 // Mils.
4593 clin *= M_PI / 3200.0 / pcs->units[Q_GRADIENT];
4594 nextch();
4595 break;
4597 VAL(Clino) = clin;
4598 ctype = CTYPE_READING;
4600 WID(Clino) = ftell(file.fh) - LOC(Clino);
4601 VAR(Clino) = var(Q_GRADIENT);
4602 } else {
4603 // Omitted foresight, e.g. `/12` or `/` (both omitted).
4604 WID(Clino) = 0;
4606 if (ch == '/' && !isBlank(nextch())) {
4607 LOC(BackClino) = ftell(file.fh);
4608 real backclin = read_number(true, false);
4609 if (backclin == HUGE_REAL) {
4610 if (ch != '-') {
4611 compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found ā€œ%sā€*/9);
4612 skipline();
4613 process_eol();
4614 return;
4616 // Walls documents two or more `-` for an omitted
4617 // reading, but actually just one works too!
4618 while (nextch() == '-') { }
4619 } else {
4620 switch (ch) {
4621 case 'D': case 'd':
4622 // Degrees.
4623 backclin *= M_PI / 180.0 / pcs->units[Q_BACKGRADIENT];
4624 nextch();
4625 break;
4626 case 'G': case 'g':
4627 // Grads.
4628 backclin *= M_PI / 200.0 / pcs->units[Q_BACKGRADIENT];
4629 nextch();
4630 break;
4631 case 'M': case 'm':
4632 // Mils.
4633 backclin *= M_PI / 3200.0 / pcs->units[Q_BACKGRADIENT];
4634 nextch();
4635 break;
4637 VAL(BackClino) = backclin;
4638 backctype = CTYPE_READING;
4640 WID(BackClino) = ftell(file.fh) - LOC(BackClino);
4641 VAR(BackClino) = var(Q_BACKGRADIENT);
4642 } else {
4643 // Omitted backsight, e.g. `12/` or `/` (both omitted).
4644 LOC(BackClino) = ftell(file.fh);
4645 WID(BackClino) = 0;
4647 break;
4649 case Ignore:
4650 skipword(); break;
4651 case IgnoreAllAndNewLine:
4652 skipline();
4653 /* fall through */
4654 case Newline:
4655 if (fr != NULL) {
4656 int r;
4657 int save_flags;
4658 int implicit_splay;
4659 if (fTopofil) {
4660 VAL(Tape) = VAL(ToCount) - VAL(FrCount);
4661 LOC(Tape) = LOC(ToCount);
4662 WID(Tape) = WID(ToCount);
4664 /* Note: frdepth == todepth test works regardless of fDepthChange
4665 * (frdepth always zero, todepth is change of depth) and also
4666 * works for STYLE_NORMAL (both remain 0) */
4667 if (TSTBIT(pcs->infer, INFER_EQUATES) &&
4668 (VAL(Tape) == (real)0.0 || VAL(Tape) == HUGE_REAL) &&
4669 (VAL(BackTape) == (real)0.0 || VAL(BackTape) == HUGE_REAL) &&
4670 VAL(FrDepth) == VAL(ToDepth)) {
4671 if (!TSTBIT(pcs->infer, INFER_EQUATES_SELF_OK) || fr != to)
4672 process_equate(fr, to);
4673 goto inferred_equate;
4675 if (fRev) {
4676 prefix *t = fr;
4677 fr = to;
4678 to = t;
4680 if (fTopofil) {
4681 VAL(Tape) *= pcs->units[Q_COUNT] * pcs->sc[Q_COUNT];
4682 } else if (VAL(Tape) != HUGE_REAL) {
4683 VAL(Tape) *= pcs->units[Q_LENGTH];
4684 VAL(Tape) -= pcs->z[Q_LENGTH];
4685 VAL(Tape) *= pcs->sc[Q_LENGTH];
4687 if (VAL(BackTape) != HUGE_REAL) {
4688 VAL(BackTape) *= pcs->units[Q_BACKLENGTH];
4689 VAL(BackTape) -= pcs->z[Q_BACKLENGTH];
4690 VAL(BackTape) *= pcs->sc[Q_BACKLENGTH];
4691 if (VAL(Tape) != HUGE_REAL) {
4692 real diff = VAL(Tape) - VAL(BackTape);
4693 if (sqrd(diff / 3.0) > VAR(Tape) + VAR(BackTape)) {
4694 /* fore and back readings differ by more than 3 sds */
4695 /* TRANSLATORS: %s is replaced by the amount the readings disagree
4696 * by, e.g. "0.12m" or "0.2ft". */
4697 warn_readings_differ(/*TAPE reading and BACKTAPE reading disagree by %s*/97,
4698 diff, get_length_units(Q_LENGTH), Tape, BackTape);
4700 VAL(Tape) = VAL(Tape) / VAR(Tape) + VAL(BackTape) / VAR(BackTape);
4701 VAR(Tape) = (VAR(Tape) + VAR(BackTape)) / 4;
4702 VAL(Tape) *= VAR(Tape);
4703 } else {
4704 VAL(Tape) = VAL(BackTape);
4705 VAR(Tape) = VAR(BackTape);
4707 } else if (VAL(Tape) == HUGE_REAL) {
4708 compile_diagnostic_reading(DIAG_ERR, Tape, /*Tape reading may not be omitted*/94);
4709 goto inferred_equate;
4711 implicit_splay = TSTBIT(pcs->flags, FLAGS_IMPLICIT_SPLAY);
4712 pcs->flags &= ~(BIT(FLAGS_ANON_ONE_END) | BIT(FLAGS_IMPLICIT_SPLAY));
4713 save_flags = pcs->flags;
4714 if (implicit_splay) {
4715 pcs->flags |= BIT(FLAGS_SPLAY);
4717 switch (pcs->style) {
4718 case STYLE_NORMAL:
4719 r = process_normal(fr, to, (first_stn == To) ^ fRev,
4720 ctype, backctype);
4721 break;
4722 case STYLE_DIVING:
4723 /* FIXME: Handle any clino readings */
4724 r = process_diving(fr, to, (first_stn == To) ^ fRev,
4725 fDepthChange);
4726 break;
4727 case STYLE_CYLPOLAR:
4728 r = process_cylpolar(fr, to, (first_stn == To) ^ fRev,
4729 fDepthChange);
4730 break;
4731 default:
4732 r = 0; /* avoid warning */
4733 BUG("bad style");
4735 pcs->flags = save_flags;
4736 if (!r) skipline();
4738 /* Swap fr and to back to how they were for next line */
4739 if (fRev) {
4740 prefix *t = fr;
4741 fr = to;
4742 to = t;
4746 fRev = false;
4747 ctype = backctype = CTYPE_OMIT;
4748 fDepthChange = false;
4750 /* ordering may omit clino reading, so set up default here */
4751 /* this is also used if clino reading is the omit character */
4752 VAL(Clino) = VAL(BackClino) = 0;
4753 LOC(Clino) = LOC(BackClino) = -1;
4754 WID(Clino) = WID(BackClino) = 0;
4756 inferred_equate:
4758 fMulti = true;
4759 while (1) {
4760 process_eol();
4761 skipblanks();
4762 if (isData(ch)) break;
4763 if (!isComm(ch)) {
4764 return;
4767 break;
4768 case IgnoreAll:
4769 skipline();
4770 /* fall through */
4771 case End:
4772 if (!fMulti) {
4773 int save_flags;
4774 int implicit_splay;
4775 /* Compass ignore flag is 'X' */
4776 if ((compass_dat_flags & BIT('X' - 'A'))) {
4777 process_eol();
4778 return;
4780 if (fRev) {
4781 prefix *t = fr;
4782 fr = to;
4783 to = t;
4785 if (fTopofil) {
4786 VAL(Tape) = VAL(ToCount) - VAL(FrCount);
4787 LOC(Tape) = LOC(ToCount);
4788 WID(Tape) = WID(ToCount);
4790 /* Note: frdepth == todepth test works regardless of fDepthChange
4791 * (frdepth always zero, todepth is change of depth) and also
4792 * works for STYLE_NORMAL (both remain 0) */
4793 if (TSTBIT(pcs->infer, INFER_EQUATES) &&
4794 (VAL(Tape) == (real)0.0 || VAL(Tape) == HUGE_REAL) &&
4795 (VAL(BackTape) == (real)0.0 || VAL(BackTape) == HUGE_REAL) &&
4796 VAL(FrDepth) == VAL(ToDepth)) {
4797 if (!TSTBIT(pcs->infer, INFER_EQUATES_SELF_OK) || fr != to)
4798 process_equate(fr, to);
4799 process_eol();
4800 return;
4802 if (fTopofil) {
4803 VAL(Tape) *= pcs->units[Q_COUNT] * pcs->sc[Q_COUNT];
4804 } else if (VAL(Tape) != HUGE_REAL) {
4805 VAL(Tape) *= pcs->units[Q_LENGTH];
4806 VAL(Tape) -= pcs->z[Q_LENGTH];
4807 VAL(Tape) *= pcs->sc[Q_LENGTH];
4809 if (VAL(BackTape) != HUGE_REAL) {
4810 VAL(BackTape) *= pcs->units[Q_BACKLENGTH];
4811 VAL(BackTape) -= pcs->z[Q_BACKLENGTH];
4812 VAL(BackTape) *= pcs->sc[Q_BACKLENGTH];
4813 if (VAL(Tape) != HUGE_REAL) {
4814 real diff = VAL(Tape) - VAL(BackTape);
4815 if (sqrd(diff / 3.0) > VAR(Tape) + VAR(BackTape)) {
4816 /* fore and back readings differ by more than 3 sds */
4817 /* TRANSLATORS: %s is replaced by the amount the readings disagree
4818 * by, e.g. "0.12m" or "0.2ft". */
4819 warn_readings_differ(/*TAPE reading and BACKTAPE reading disagree by %s*/97,
4820 diff, get_length_units(Q_LENGTH), Tape, BackTape);
4822 VAL(Tape) = VAL(Tape) / VAR(Tape) + VAL(BackTape) / VAR(BackTape);
4823 VAR(Tape) = (VAR(Tape) + VAR(BackTape)) / 4;
4824 VAL(Tape) *= VAR(Tape);
4825 } else {
4826 VAL(Tape) = VAL(BackTape);
4827 VAR(Tape) = VAR(BackTape);
4829 } else if (VAL(Tape) == HUGE_REAL) {
4830 compile_diagnostic_reading(DIAG_ERR, Tape, /*Tape reading may not be omitted*/94);
4831 process_eol();
4832 return;
4834 implicit_splay = TSTBIT(pcs->flags, FLAGS_IMPLICIT_SPLAY);
4835 pcs->flags &= ~(BIT(FLAGS_ANON_ONE_END) | BIT(FLAGS_IMPLICIT_SPLAY));
4836 save_flags = pcs->flags;
4837 if (implicit_splay) {
4838 pcs->flags |= BIT(FLAGS_SPLAY);
4840 if ((compass_dat_flags & BIT('S' - 'A'))) {
4841 /* 'S' means "splay". */
4842 pcs->flags |= BIT(FLAGS_SPLAY);
4844 if ((compass_dat_flags & BIT('P' - 'A'))) {
4845 /* 'P' means "Exclude this shot from plotting", but the use
4846 * suggested in the Compass docs is for surface data, and legs
4847 * with this flag "[do] not support passage modeling".
4849 * Even if it's actually being used for a different
4850 * purpose, Survex programs don't show surface legs
4851 * by default so FLAGS_SURFACE matches fairly well.
4853 pcs->flags |= BIT(FLAGS_SURFACE);
4855 if ((compass_dat_flags & BIT('L' - 'A'))) {
4856 /* 'L' means "exclude from length" - map this to Survex's
4857 * FLAGS_DUPLICATE. */
4858 pcs->flags |= BIT(FLAGS_DUPLICATE);
4860 switch (pcs->style) {
4861 case STYLE_NORMAL:
4862 process_normal(fr, to, (first_stn == To) ^ fRev,
4863 ctype, backctype);
4864 break;
4865 case STYLE_DIVING:
4866 /* FIXME: Handle any clino readings */
4867 process_diving(fr, to, (first_stn == To) ^ fRev,
4868 fDepthChange);
4869 break;
4870 case STYLE_CYLPOLAR:
4871 process_cylpolar(fr, to, (first_stn == To) ^ fRev,
4872 fDepthChange);
4873 break;
4874 default:
4875 BUG("bad style");
4877 pcs->flags = save_flags;
4879 process_eol();
4880 return;
4882 do {
4883 process_eol();
4884 skipblanks();
4885 } while (isComm(ch));
4886 goto again;
4887 default:
4888 BUG("Unknown reading in ordering");
4893 static int
4894 process_lrud(prefix *stn)
4896 SVX_ASSERT(next_lrud);
4897 lrud * xsect = osnew(lrud);
4898 xsect->stn = stn;
4899 xsect->l = (VAL(Left) * pcs->units[Q_LEFT] - pcs->z[Q_LEFT]) * pcs->sc[Q_LEFT];
4900 xsect->r = (VAL(Right) * pcs->units[Q_RIGHT] - pcs->z[Q_RIGHT]) * pcs->sc[Q_RIGHT];
4901 xsect->u = (VAL(Up) * pcs->units[Q_UP] - pcs->z[Q_UP]) * pcs->sc[Q_UP];
4902 xsect->d = (VAL(Down) * pcs->units[Q_DOWN] - pcs->z[Q_DOWN]) * pcs->sc[Q_DOWN];
4903 xsect->meta = pcs->meta;
4904 if (pcs->meta) ++pcs->meta->ref_count;
4905 xsect->next = NULL;
4906 *next_lrud = xsect;
4907 next_lrud = &(xsect->next);
4909 return 1;
4912 static void
4913 data_passage(void)
4915 prefix *stn = NULL;
4916 const reading *ordering;
4918 for (ordering = pcs->ordering ; ; ordering++) {
4919 skipblanks();
4920 switch (*ordering) {
4921 case Station:
4922 stn = read_prefix(PFX_STATION);
4923 break;
4924 case Left: case Right: case Up: case Down: {
4925 reading r = *ordering;
4926 read_reading(r, true);
4927 if (VAL(r) == HUGE_REAL) {
4928 if (!isOmit(ch)) {
4929 compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found ā€œ%sā€*/9);
4930 } else {
4931 nextch();
4933 VAL(r) = -1;
4935 break;
4937 case Ignore:
4938 skipword(); break;
4939 case IgnoreAll:
4940 skipline();
4941 /* fall through */
4942 case End: {
4943 process_lrud(stn);
4944 process_eol();
4945 return;
4947 default: BUG("Unknown reading in ordering");
4952 static int
4953 process_nosurvey(prefix *fr, prefix *to, bool fToFirst)
4955 nosurveylink *link;
4957 /* Suppress "unused fixed point" warnings for these stations */
4958 fr->sflags |= BIT(SFLAGS_USED);
4959 to->sflags |= BIT(SFLAGS_USED);
4961 /* add to linked list which is dealt with after network is solved */
4962 link = osnew(nosurveylink);
4963 if (fToFirst) {
4964 link->to = StnFromPfx(to);
4965 link->fr = StnFromPfx(fr);
4966 } else {
4967 link->fr = StnFromPfx(fr);
4968 link->to = StnFromPfx(to);
4970 link->flags = pcs->flags | (STYLE_NOSURVEY << FLAGS_STYLE_BIT0);
4971 link->meta = pcs->meta;
4972 if (pcs->meta) ++pcs->meta->ref_count;
4973 link->next = nosurveyhead;
4974 nosurveyhead = link;
4975 return 1;
4978 static void
4979 data_nosurvey(void)
4981 prefix *fr = NULL, *to = NULL;
4983 bool fMulti = false;
4985 reading first_stn = End;
4987 const reading *ordering;
4989 again:
4991 for (ordering = pcs->ordering ; ; ordering++) {
4992 skipblanks();
4993 switch (*ordering) {
4994 case Fr:
4995 fr = read_prefix(PFX_STATION|PFX_ALLOW_ROOT);
4996 if (first_stn == End) first_stn = Fr;
4997 break;
4998 case To:
4999 to = read_prefix(PFX_STATION|PFX_ALLOW_ROOT);
5000 if (first_stn == End) first_stn = To;
5001 break;
5002 case Station:
5003 fr = to;
5004 to = read_prefix(PFX_STATION);
5005 first_stn = To;
5006 break;
5007 case Ignore:
5008 skipword(); break;
5009 case IgnoreAllAndNewLine:
5010 skipline();
5011 /* fall through */
5012 case Newline:
5013 if (fr != NULL) {
5014 if (!process_nosurvey(fr, to, first_stn == To))
5015 skipline();
5017 if (ordering[1] == End) {
5018 do {
5019 process_eol();
5020 skipblanks();
5021 } while (isComm(ch));
5022 if (!isData(ch)) {
5023 return;
5025 goto again;
5027 fMulti = true;
5028 while (1) {
5029 process_eol();
5030 skipblanks();
5031 if (isData(ch)) break;
5032 if (!isComm(ch)) {
5033 return;
5036 break;
5037 case IgnoreAll:
5038 skipline();
5039 /* fall through */
5040 case End:
5041 if (!fMulti) {
5042 (void)process_nosurvey(fr, to, first_stn == To);
5043 process_eol();
5044 return;
5046 do {
5047 process_eol();
5048 skipblanks();
5049 } while (isComm(ch));
5050 goto again;
5051 default: BUG("Unknown reading in ordering");
5056 /* totally ignore a line of survey data */
5057 static void
5058 data_ignore(void)
5060 skipline();
5061 process_eol();