Fix message references for inches and feet
[survex.git] / src / datain.c
blobf785a4fc5c85e89a1a39b95691b533bf97a2b6ad
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 static int process_nosurvey(prefix *fr, prefix *to, bool fToFirst);
55 #define EPSILON (REAL_EPSILON * 1000)
57 #define var(I) (pcs->Var[(I)])
59 /* Test for a not-a-number value in Compass data (999.0 or -999.0).
61 * Compass itself uses -999.0 but reportedly understands Karst data which used
62 * 999.0 (information from Larry Fish via Simeon Warner in 2004). However
63 * testing with Compass in early 2024 it seems 999.0 is treated like any other
64 * reading.
66 * When "corrected" backsights are specified in FORMAT, Compass seems to write
67 * out -999 with the correction applied to the CLP file.
69 * Valid readings should be 0 to 360 for the compass and -90 to 90 for the
70 * clino, and the correction should have absolute value < 360, so we test for
71 * any reading with an absolute value greater than 999 - 360 = 639, which is
72 * well outside the valid range.
74 #define is_compass_NaN(x) (fabs(x) > (999.0 - 360.0))
76 static int
77 read_compass_date_as_days_since_1900(void)
79 /* NB order is *month* *day* year */
80 int month = read_uint();
81 int day = read_uint();
82 int year = read_uint();
83 /* Note: Larry says a 2 digit year is always 19XX */
84 if (year < 100) year += 1900;
86 /* Compass uses 1901-01-01 when no date was specified. */
87 if (year == 1901 && day == 1 && month == 1) return -1;
89 return days_since_1900(year, month, day);
91 int ch;
93 typedef enum {
94 // Clino omitted. VAL() should be set to 0.0.
95 CTYPE_OMIT,
96 // An actual clino reading.
97 CTYPE_READING,
98 // An explicit plumb (U/D/UP/DOWN/+V/-V for reading).
99 CTYPE_PLUMB,
100 // An inferred plumb (+90 or -90 and *infer plumbs).
101 CTYPE_INFERPLUMB,
102 // An explicit horizontal leg (H/LEVEL for reading).
103 CTYPE_HORIZ
104 } clino_type;
106 parse file;
108 jmp_buf jbSkipLine;
110 bool f_export_ok;
112 static real value[Fr - 1];
113 #define VAL(N) value[(N)-1]
114 static real variance[Fr - 1];
115 #define VAR(N) variance[(N)-1]
116 static long location[Fr - 1];
117 #define LOC(N) location[(N)-1]
118 static int location_width[Fr - 1];
119 #define WID(N) location_width[(N)-1]
121 /* style functions */
122 static void data_normal(void);
123 static void data_cartesian(void);
124 static void data_passage(void);
125 static void data_nosurvey(void);
126 static void data_ignore(void);
128 void
129 get_pos(filepos *fp)
131 fp->ch = ch;
132 fp->offset = ftell(file.fh);
133 if (fp->offset == -1)
134 fatalerror_in_file(file.filename, 0, /*Error reading file*/18);
137 void
138 set_pos(const filepos *fp)
140 ch = fp->ch;
141 if (fseek(file.fh, fp->offset, SEEK_SET) == -1)
142 fatalerror_in_file(file.filename, 0, /*Error reading file*/18);
145 static void
146 report_parent(parse * p) {
147 if (p->parent)
148 report_parent(p->parent);
149 /* Force re-report of include tree for further errors in
150 * parent files */
151 p->reported_where = false;
152 /* TRANSLATORS: %s is replaced by the filename of the parent file, and %u
153 * by the line number in that file. Your translation should also contain
154 * %s:%u so that automatic parsing of error messages to determine the file
155 * and line number still works. */
156 fprintf(STDERR, msg(/*In file included from %s:%u:\n*/5), p->filename, p->line);
159 static void
160 error_list_parent_files(void)
162 if (!file.reported_where && file.parent) {
163 report_parent(file.parent);
164 /* Suppress reporting of full include tree for further errors
165 * in this file */
166 file.reported_where = true;
170 static void
171 show_line(int col, int width)
173 /* Rewind to beginning of line. */
174 long cur_pos = ftell(file.fh);
175 if (cur_pos < 0 || fseek(file.fh, file.lpos, SEEK_SET) == -1)
176 fatalerror_in_file(file.filename, 0, /*Error reading file*/18);
178 /* Read the whole line and write it out. */
179 PUTC(' ', STDERR);
180 while (1) {
181 int c = GETC(file.fh);
182 /* Note: isEol() is true for EOF */
183 if (isEol(c)) break;
184 // Replace tabs with spaces so alignment and length of the `^~~~`
185 // highlight works regardless of the terminal's tab rendering.
186 if (c == '\t') c = ' ';
187 PUTC(c, STDERR);
189 fputnl(STDERR);
191 /* If we have a location in the line for the error, indicate it. */
192 if (col > 0) {
193 PUTC(' ', STDERR);
194 while (--col) PUTC(' ', STDERR);
195 PUTC('^', STDERR);
196 while (width > 1) {
197 PUTC('~', STDERR);
198 --width;
200 fputnl(STDERR);
203 /* Revert to where we were. */
204 if (fseek(file.fh, cur_pos, SEEK_SET) == -1)
205 fatalerror_in_file(file.filename, 0, /*Error reading file*/18);
208 char*
209 grab_line(void)
211 /* Rewind to beginning of line. */
212 long cur_pos = ftell(file.fh);
213 string p = S_INIT;
214 if (cur_pos < 0 || fseek(file.fh, file.lpos, SEEK_SET) == -1)
215 fatalerror_in_file(file.filename, 0, /*Error reading file*/18);
217 /* Read the whole line into a string. */
218 while (1) {
219 int c = GETC(file.fh);
220 /* Note: isEol() is true for EOF */
221 if (isEol(c)) break;
222 // Change tabs to spaces for consistency with how we show context
223 // lines for other diagnostics.
224 if (c == '\t') c = ' ';
225 s_appendch(&p, c);
228 /* Revert to where we were. */
229 if (fseek(file.fh, cur_pos, SEEK_SET) == -1) {
230 s_free(&p);
231 fatalerror_in_file(file.filename, 0, /*Error reading file*/18);
234 return s_steal(&p);
237 static int caret_width = 0;
239 static void
240 compile_v_report_fpos(int diag_flags, long fpos, int en, va_list ap)
242 int severity = (diag_flags & DIAG_SEVERITY_MASK);
243 int col = 0;
244 error_list_parent_files();
245 unsigned line = file.line;
246 long prev_line_len = (long)file.prev_line_len;
247 if (fpos >= file.lpos) {
248 col = fpos - file.lpos - caret_width;
249 } else if (fpos >= file.lpos - prev_line_len) {
250 file.lpos -= prev_line_len;
251 --line;
252 col = fpos - file.lpos - caret_width;
254 v_report(severity == DIAG_FATAL ? DIAG_ERR : severity,
255 file.filename, line, col, en, ap);
256 if (file.fh) show_line(col, caret_width);
257 if (severity == DIAG_FATAL) {
258 exit(EXIT_FAILURE);
260 if (line != file.line) {
261 file.lpos += prev_line_len;
263 if (diag_flags & DIAG_SKIP) skipline();
264 caret_width = 0;
267 void
268 compile_diagnostic(int diag_flags, int en, ...)
270 va_list ap;
271 va_start(ap, en);
272 long fpos = 0;
273 int diag_context_code = (diag_flags & DIAG_CONTEXT_MASK);
274 if (diag_context_code) {
275 int len = 0;
276 if (diag_context_code != DIAG_COL && diag_context_code != DIAG_TOKEN) {
277 skipblanks();
279 switch (diag_context_code) {
280 case DIAG_COL:
281 break;
282 case DIAG_TOKEN:
283 len = s_len(&token);
284 break;
285 case DIAG_WORD:
286 while (!isBlank(ch) && !isComm(ch) && !isEol(ch)) {
287 ++len;
288 nextch();
290 break;
291 case DIAG_UINT:
292 while (isdigit(ch)) {
293 ++len;
294 nextch();
296 break;
297 case DIAG_DATE:
298 while (isdigit(ch) || ch == '.') {
299 ++len;
300 nextch();
302 break;
303 case DIAG_STRING: {
304 string p = S_INIT;
305 len = ftell(file.fh);
306 read_string(&p);
307 s_free(&p);
308 /* We want to include any quotes, so can't use s_len(&p). */
309 len = ftell(file.fh) - len;
310 break;
312 case DIAG_TAIL: {
313 filepos fp_last_nonblank = {0}; // Initialise to avoid warning.
314 int len_last_nonblank = len;
315 while (!isComm(ch) && !isEol(ch)) {
316 ++len;
317 bool non_blank = !isBlank(ch);
318 nextch();
319 if (non_blank) {
320 len_last_nonblank = len;
321 get_pos(&fp_last_nonblank);
324 if (len_last_nonblank != len) {
325 len = len_last_nonblank;
326 set_pos(&fp_last_nonblank);
328 break;
330 case DIAG_NUM:
331 if (isMinus(ch) || isPlus(ch)) {
332 ++len;
333 nextch();
335 while (isdigit(ch)) {
336 ++len;
337 nextch();
339 if (isDecimal(ch)) {
340 ++len;
341 nextch();
343 while (isdigit(ch)) {
344 ++len;
345 nextch();
347 break;
349 caret_width = len;
350 fpos = ftell(file.fh);
351 } else if (diag_flags & DIAG_FROM_MASK) {
352 caret_width = diag_flags >> DIAG_FROM_SHIFT;
353 fpos = ftell(file.fh);
355 compile_v_report_fpos(diag_flags, fpos, en, ap);
356 va_end(ap);
359 static void
360 compile_diagnostic_reading(int diag_flags, reading r, int en, ...)
362 va_list ap;
363 int severity = (diag_flags & DIAG_SEVERITY_MASK);
364 va_start(ap, en);
365 caret_width = WID(r);
366 compile_v_report_fpos(severity, LOC(r) + caret_width, en, ap);
367 va_end(ap);
370 static void
371 compile_error_reading_skip(reading r, int en, ...)
373 va_list ap;
374 va_start(ap, en);
375 caret_width = WID(r);
376 compile_v_report_fpos(DIAG_ERR, LOC(r) + caret_width, en, ap);
377 va_end(ap);
378 skipline();
381 void
382 compile_diagnostic_at(int diag_flags, const char * filename, unsigned line, int en, ...)
384 va_list ap;
385 int severity = (diag_flags & DIAG_SEVERITY_MASK);
386 va_start(ap, en);
387 v_report(severity, filename, line, 0, en, ap);
388 va_end(ap);
389 caret_width = 0;
392 void
393 compile_diagnostic_pfx(int diag_flags, const prefix * pfx, int en, ...)
395 va_list ap;
396 int severity = (diag_flags & DIAG_SEVERITY_MASK);
397 va_start(ap, en);
398 v_report(severity, pfx->filename, pfx->line, 0, en, ap);
399 va_end(ap);
400 caret_width = 0;
403 void
404 compile_diagnostic_token_show(int diag_flags, int en)
406 string p = S_INIT;
407 skipblanks();
408 while (!isBlank(ch) && !isComm(ch) && !isEol(ch)) {
409 s_appendch(&p, (char)ch);
410 nextch();
412 if (!s_empty(&p)) {
413 compile_diagnostic(diag_flags|DIAG_WIDTH(s_len(&p)), en, s_str(&p));
414 s_free(&p);
415 } else {
416 compile_diagnostic(DIAG_ERR|DIAG_COL, en, "");
420 static void
421 compile_error_string(const char * s, int en, ...)
423 va_list ap;
424 va_start(ap, en);
425 long fpos = 0;
426 if (file.fh) {
427 caret_width = strlen(s);
428 fpos = ftell(file.fh);
430 compile_v_report_fpos(DIAG_ERR, fpos, en, ap);
431 va_end(ap);
434 /* This function makes a note where to put output files */
435 static void
436 using_data_file(const char *fnm)
438 if (!fnm_output_base) {
439 /* was: fnm_output_base = base_from_fnm(fnm); */
440 fnm_output_base = baseleaf_from_fnm(fnm);
441 } else if (fnm_output_base_is_dir) {
442 /* --output pointed to directory so use the leaf basename in that dir */
443 char *lf, *p;
444 lf = baseleaf_from_fnm(fnm);
445 p = use_path(fnm_output_base, lf);
446 osfree(lf);
447 osfree(fnm_output_base);
448 fnm_output_base = p;
449 fnm_output_base_is_dir = 0;
453 static void
454 skipword(void)
456 while (!isBlank(ch) && !isComm(ch) && !isEol(ch)) nextch();
459 extern void
460 skipblanks(void)
462 while (isBlank(ch)) nextch();
465 extern void
466 skipline(void)
468 while (!isEol(ch)) nextch();
471 static void
472 process_eol(void)
474 int eolchar;
476 skipblanks();
478 if (!isEol(ch)) {
479 if (!isComm(ch))
480 compile_diagnostic(DIAG_ERR|DIAG_TAIL, /*End of line not blank*/15);
481 skipline();
484 eolchar = ch;
485 file.line++;
486 /* skip any different eol characters so we get line counts correct on
487 * DOS text files and similar, but don't count several adjacent blank
488 * lines as one */
489 while (ch != EOF) {
490 nextch();
491 if (ch == eolchar || !isEol(ch)) {
492 break;
494 if (ch == '\n') eolchar = ch;
496 long old_lpos = file.lpos;
497 file.lpos = ftell(file.fh) - 1;
498 file.prev_line_len = file.lpos - old_lpos;
501 static bool
502 process_non_data_line(void)
504 skipblanks();
506 if (isData(ch)) return false;
508 if (isKeywd(ch)) {
509 nextch();
510 handle_command();
513 process_eol();
515 return true;
518 static void
519 read_reading(reading r, bool f_optional)
521 int n_readings;
522 q_quantity q;
523 switch (r) {
524 case Tape: q = Q_LENGTH; break;
525 case BackTape: q = Q_BACKLENGTH; break;
526 case Comp: q = Q_BEARING; break;
527 case BackComp: q = Q_BACKBEARING; break;
528 case Clino: q = Q_GRADIENT; break;
529 case BackClino: q = Q_BACKGRADIENT; break;
530 case FrDepth: case ToDepth: q = Q_DEPTH; break;
531 case Dx: q = Q_DX; break;
532 case Dy: q = Q_DY; break;
533 case Dz: q = Q_DZ; break;
534 case FrCount: case ToCount: q = Q_COUNT; break;
535 case Left: q = Q_LEFT; break;
536 case Right: q = Q_RIGHT; break;
537 case Up: q = Q_UP; break;
538 case Down: q = Q_DOWN; break;
539 default:
540 q = Q_NULL; /* Suppress compiler warning */;
541 BUG("Unexpected case");
543 LOC(r) = ftell(file.fh);
544 /* since we don't handle bearings in read_readings, it's never quadrant */
545 VAL(r) = read_numeric_multi(f_optional, false, &n_readings);
546 WID(r) = ftell(file.fh) - LOC(r);
547 VAR(r) = var(q);
548 if (n_readings > 1) VAR(r) /= sqrt(n_readings);
551 static void
552 read_bearing_or_omit(reading r)
554 int n_readings;
555 bool quadrants = false;
556 q_quantity q = Q_NULL;
557 switch (r) {
558 case Comp:
559 q = Q_BEARING;
560 if (pcs->f_bearing_quadrants)
561 quadrants = true;
562 break;
563 case BackComp:
564 q = Q_BACKBEARING;
565 if (pcs->f_backbearing_quadrants)
566 quadrants = true;
567 break;
568 default:
569 q = Q_NULL; /* Suppress compiler warning */;
570 BUG("Unexpected case");
572 LOC(r) = ftell(file.fh);
573 VAL(r) = read_bearing_multi_or_omit(quadrants, &n_readings);
574 WID(r) = ftell(file.fh) - LOC(r);
575 VAR(r) = var(q);
576 if (n_readings > 1) VAR(r) /= sqrt(n_readings);
579 // Set up settings for reading Compass DAT or MAK.
580 static void
581 initialise_common_compass_settings(void)
583 short *t = ((short*)osmalloc(ossizeof(short) * 257)) + 1;
584 int i;
585 t[EOF] = SPECIAL_EOL;
586 memset(t, 0, sizeof(short) * 33);
587 for (i = 33; i < 127; i++) t[i] = SPECIAL_NAMES;
588 t[127] = 0;
589 for (i = 128; i < 256; i++) t[i] = SPECIAL_NAMES;
590 t['\t'] |= SPECIAL_BLANK;
591 t[' '] |= SPECIAL_BLANK;
592 t['\032'] |= SPECIAL_EOL; /* Ctrl-Z, so olde DOS text files are handled ok */
593 t['\n'] |= SPECIAL_EOL;
594 t['\r'] |= SPECIAL_EOL;
595 t['.'] |= SPECIAL_DECIMAL;
596 t['-'] |= SPECIAL_MINUS;
597 t['+'] |= SPECIAL_PLUS;
599 settings *pcsNew = osnew(settings);
600 *pcsNew = *pcs; /* copy contents */
601 pcsNew->begin_lineno = 0;
602 pcsNew->Translate = t;
603 pcsNew->Case = OFF;
604 pcsNew->Truncate = INT_MAX;
605 // Compass itself appears to quietly ignore legs with the same station as
606 // `from` and `to`, but it seems like something to warn about.
607 pcsNew->from_equals_to_is_only_a_warning = true;
608 pcsNew->next = pcs;
609 pcs = pcsNew;
611 update_output_separator();
614 /* For reading Compass MAK files which have a freeform syntax */
615 static void
616 nextch_handling_eol(void)
618 nextch();
619 while (ch != EOF && isEol(ch)) {
620 process_eol();
624 static bool
625 get_token_and_check_len(const char *expect, size_t len)
627 get_token();
628 if (s_eqlen(&token, expect, len))
629 return true;
630 compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Expecting ā€œ%sā€*/497, expect);
631 return false;
634 static bool
635 check_colon(void)
637 if (ch != ':') {
638 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting ā€œ%sā€*/497, ":");
639 return false;
641 nextch();
642 return true;
645 static bool
646 get_token_and_check_colon_len(const char *expect, size_t len)
648 return get_token_and_check_len(expect, len) && check_colon();
651 #define GET_TOKEN_AND_CHECK(LITERAL) \
652 get_token_and_check_len(LITERAL, sizeof(LITERAL "") - 1)
654 #define GET_TOKEN_AND_CHECK_COLON(LITERAL) \
655 get_token_and_check_colon_len(LITERAL, sizeof(LITERAL "") - 1)
657 static void
658 data_file_compass_dat_or_clp(bool is_clp)
660 initialise_common_compass_settings();
661 default_units(pcs);
662 default_calib(pcs);
663 pcs->z[Q_DECLINATION] = HUGE_REAL;
665 pcs->style = STYLE_NORMAL;
666 pcs->units[Q_LENGTH] = METRES_PER_FOOT;
667 pcs->infer = BIT(INFER_EQUATES) |
668 BIT(INFER_EQUATES_SELF_OK) |
669 BIT(INFER_EXPORTS) |
670 BIT(INFER_PLUMBS);
671 /* We need to update separator_map so we don't pick a separator character
672 * which occurs in a station name. However Compass DAT allows everything
673 * >= ASCII char 33 except 127 in station names so if we just added all
674 * the valid station name characters we'd always pick space as the
675 * separator for any dataset which included a DAT file, yet in practice
676 * '.' is never used in any of the sample DAT files I've seen. So
677 * instead we scan the characters actually used in station names when we
678 * process CompassDATFr and CompassDATTo fields.
681 if (setjmp(jbSkipLine)) {
682 // Recover from errors in nested functions by longjmp() to here.
683 skipline();
684 process_eol();
687 while (ch != EOF && !ferror(file.fh)) {
688 static const reading compass_order[] = {
689 CompassDATFr, CompassDATTo, Tape, CompassDATComp, CompassDATClino,
690 CompassDATLeft, CompassDATUp, CompassDATDown, CompassDATRight,
691 CompassDATFlags, IgnoreAll
693 static const reading compass_order_backsights[] = {
694 CompassDATFr, CompassDATTo, Tape, CompassDATComp, CompassDATClino,
695 CompassDATLeft, CompassDATUp, CompassDATDown, CompassDATRight,
696 CompassDATBackComp, CompassDATBackClino,
697 CompassDATFlags, IgnoreAll
700 copy_on_write_meta(pcs);
701 pcs->meta->days1 = pcs->meta->days2 = -1;
702 pcs->declination = HUGE_REAL;
703 pcs->ordering = compass_order;
704 pcs->recorded_style = STYLE_NORMAL;
706 /* <Cave name> */
707 skipline();
708 process_eol();
709 /* SURVEY NAME: <Short name> */
710 if (GET_TOKEN_AND_CHECK("SURVEY") &&
711 GET_TOKEN_AND_CHECK_COLON("NAME")) {
712 // Survey short name currently ignored.
713 get_token();
715 skipline();
716 process_eol();
718 /* SURVEY DATE: 7 10 79 COMMENT:<Long name> */
719 if (GET_TOKEN_AND_CHECK("SURVEY") &&
720 GET_TOKEN_AND_CHECK_COLON("DATE")) {
721 int days = read_compass_date_as_days_since_1900();
722 pcs->meta->days1 = pcs->meta->days2 = days;
723 // Ignore "COMMENT:<Long name>" part for now.
725 skipline();
726 process_eol();
727 /* SURVEY TEAM: */
728 if (GET_TOKEN_AND_CHECK("SURVEY") &&
729 GET_TOKEN_AND_CHECK_COLON("TEAM")) {
730 // Value is on the next line.
732 process_eol();
733 /* <Survey team> */
734 skipline();
735 process_eol();
736 /* DECLINATION: 1.00 FORMAT: DDDDLUDRADLN CORRECTIONS: 2.00 3.00 4.00 */
737 if (GET_TOKEN_AND_CHECK_COLON("DECLINATION")) {
738 if (pcs->dec_filename == NULL) {
739 pcs->z[Q_DECLINATION] = -read_numeric(false);
740 pcs->z[Q_DECLINATION] *= pcs->units[Q_DECLINATION];
741 } else {
742 (void)read_numeric(false);
745 get_token();
746 pcs->ordering = compass_order;
747 // "FORMAT" is optional.
748 if (S_EQ(&token, "FORMAT") && check_colon()) {
749 /* This documents the format in the original survey notebook - we
750 * don't need to fully parse it to be able to parse the survey data
751 * in the file, which gets converted to a fixed order and units.
753 get_token();
754 size_t token_len = s_len(&token);
755 if (token_len >= 4 && s_str(&token)[3] == 'W') {
756 /* Original "Inclination Units" were "Depth Gauge". */
757 pcs->recorded_style = STYLE_DIVING;
759 if (token_len >= 12) {
760 char backsight_type = s_str(&token)[token_len >= 15 ? 13 : 11];
761 // B means redundant backsight; C means redundant backsights
762 // but displayed "corrected" (i.e. reversed to make visually
763 // comparing easier).
764 if (backsight_type == 'B' || backsight_type == 'C') {
765 /* We have backsights for compass and clino */
766 pcs->ordering = compass_order_backsights;
769 get_token();
772 // CORRECTIONS and CORRECTIONS2 have already been applied to data in
773 // the CLP file.
774 if (!is_clp) {
775 if (S_EQ(&token, "CORRECTIONS") && check_colon()) {
776 pcs->z[Q_BACKBEARING] = pcs->z[Q_BEARING] = -rad(read_numeric(false));
777 pcs->z[Q_BACKGRADIENT] = pcs->z[Q_GRADIENT] = -rad(read_numeric(false));
778 pcs->z[Q_LENGTH] = -METRES_PER_FOOT * read_numeric(false);
779 get_token();
782 if (S_EQ(&token, "CORRECTIONS2") && check_colon()) {
783 pcs->z[Q_BACKBEARING] = -rad(read_numeric(false));
784 pcs->z[Q_BACKGRADIENT] = -rad(read_numeric(false));
785 get_token();
789 #if 0
790 // FIXME Parse once we handle discovery dates...
791 // NB: Need to skip unread CORRECTIONS* for the `is_clp` case.
792 if (S_EQ(&token, "DISCOVERY") && check_colon()) {
793 // Discovery date, e.g. DISCOVERY: 2 28 2024
794 int days = read_compass_date_as_days_since_1900();
796 #endif
797 skipline();
798 process_eol();
799 /* BLANK LINE */
800 process_eol();
801 /* heading line */
802 skipline();
803 process_eol();
804 /* BLANK LINE */
805 process_eol();
806 while (ch != EOF) {
807 if (ch == '\x0c') {
808 nextch();
809 process_eol();
810 break;
812 data_normal();
814 clear_last_leg();
816 pcs->ordering = NULL; /* Avoid free() of static array. */
817 pop_settings();
820 static void
821 data_file_compass_dat(void)
823 data_file_compass_dat_or_clp(false);
826 static void
827 data_file_compass_clp(void)
829 data_file_compass_dat_or_clp(true);
832 static void
833 data_file_compass_mak(void)
835 initialise_common_compass_settings();
836 short *t = pcs->Translate;
837 // In a Compass MAK file a station name can't contain these three
838 // characters due to how the syntax works.
839 t['['] = t[','] = t[';'] = 0;
841 if (setjmp(jbSkipLine)) {
842 // Recover from errors in nested functions by longjmp() to here.
843 skipline();
844 process_eol();
847 int datum = 0;
848 int utm_zone = 0;
849 real base_x = 0.0, base_y = 0.0, base_z = 0.0;
850 int base_utm_zone = 0;
851 unsigned int base_line = 0;
852 long base_lpos = 0;
853 string path = S_INIT;
854 s_donate(&path, path_from_fnm(file.filename));
855 struct mak_folder {
856 struct mak_folder *next;
857 int len;
858 } *folder_stack = NULL;
860 while (ch != EOF && !ferror(file.fh)) {
861 switch (ch) {
862 case '#': {
863 /* include a file */
864 int ch_store;
865 string dat_fnm = S_INIT;
866 nextch_handling_eol();
867 while (ch != ',' && ch != ';' && ch != EOF) {
868 while (isEol(ch)) process_eol();
869 s_appendch(&dat_fnm, (char)ch);
870 nextch_handling_eol();
872 if (!s_empty(&dat_fnm)) {
873 if (base_utm_zone) {
874 // Process the previous @ command using the datum from &.
875 char *proj_str = img_compass_utm_proj_str(datum,
876 base_utm_zone);
877 if (proj_str) {
878 // Temporarily reset line and lpos so dec_context and
879 // dec_line refer to the @ command.
880 unsigned saved_line = file.line;
881 file.line = base_line;
882 long saved_lpos = file.lpos;
883 file.lpos = base_lpos;
884 file.prev_line_len = 0; // Not used for Compass MAK.
885 set_declination_location(base_x, base_y, base_z,
886 proj_str);
887 file.line = saved_line;
888 file.lpos = saved_lpos;
889 if (!pcs->proj_str) {
890 pcs->proj_str = proj_str;
891 if (!proj_str_out) {
892 proj_str_out = osstrdup(proj_str);
894 pcs->input_convergence = HUGE_REAL;
895 } else {
896 osfree(proj_str);
900 ch_store = ch;
901 data_file(s_str(&path), s_str(&dat_fnm));
902 ch = ch_store;
903 s_free(&dat_fnm);
905 while (ch != ';' && ch != EOF) {
906 nextch_handling_eol();
907 filepos fp_name;
908 get_pos(&fp_name);
909 prefix *name = read_prefix(PFX_STATION|PFX_OPT);
910 if (name) {
911 scan_compass_station_name(name);
912 skipblanks();
913 if (ch == '[') {
914 /* fixed pt */
915 real coords[3];
916 bool in_feet = false;
917 // Compass treats these fixed points as entrances
918 // ("distance from entrance" in a .DAT file counts
919 // from 0.0 at these points) so we do too.
920 name->sflags |= BIT(SFLAGS_FIXED) |
921 BIT(SFLAGS_ENTRANCE);
922 nextch_handling_eol();
923 if (ch == 'F' || ch == 'f') {
924 in_feet = true;
925 nextch_handling_eol();
926 } else if (ch == 'M' || ch == 'm') {
927 nextch_handling_eol();
928 } else {
929 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting ā€œ%sā€ or ā€œ%sā€*/103, "F", "M");
931 while (!isdigit(ch) && ch != '+' && ch != '-' &&
932 ch != '.' && ch != ']' && ch != EOF) {
933 nextch_handling_eol();
935 coords[0] = read_numeric(false);
936 while (!isdigit(ch) && ch != '+' && ch != '-' &&
937 ch != '.' && ch != ']' && ch != EOF) {
938 nextch_handling_eol();
940 coords[1] = read_numeric(false);
941 while (!isdigit(ch) && ch != '+' && ch != '-' &&
942 ch != '.' && ch != ']' && ch != EOF) {
943 nextch_handling_eol();
945 coords[2] = read_numeric(false);
946 if (in_feet) {
947 coords[0] *= METRES_PER_FOOT;
948 coords[1] *= METRES_PER_FOOT;
949 coords[2] *= METRES_PER_FOOT;
951 int fix_result = fix_station(name, coords);
952 if (fix_result) {
953 filepos fp;
954 get_pos(&fp);
955 set_pos(&fp_name);
956 if (fix_result < 0) {
957 compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Station already fixed or equated to a fixed point*/46);
958 } else {
959 compile_diagnostic(DIAG_WARN|DIAG_WORD, /*Station already fixed at the same coordinates*/55);
961 set_pos(&fp);
962 compile_diagnostic_pfx(DIAG_INFO, name, /*Previously fixed or equated here*/493);
964 while (ch != ']' && ch != EOF) nextch_handling_eol();
965 if (ch == ']') {
966 nextch_handling_eol();
967 skipblanks();
969 } else {
970 /* FIXME: link station - ignore for now */
971 /* FIXME: perhaps issue warning? Other station names
972 * can be "reused", which is problematic... */
974 while (ch != ',' && ch != ';' && ch != EOF)
975 nextch_handling_eol();
978 break;
980 case '$':
981 /* UTM zone */
982 nextch();
983 skipblanks();
984 utm_zone = read_int(-60, 60);
985 skipblanks();
986 if (ch == ';') nextch_handling_eol();
988 update_proj_str:
989 if (!pcs->next || pcs->proj_str != pcs->next->proj_str)
990 osfree(pcs->proj_str);
991 pcs->proj_str = NULL;
992 pcs->input_convergence = HUGE_REAL;
993 if (datum && utm_zone && abs(utm_zone) <= 60) {
994 /* Set up coordinate system. */
995 char *proj_str = img_compass_utm_proj_str(datum, utm_zone);
996 if (proj_str) {
997 pcs->proj_str = proj_str;
998 if (!proj_str_out) {
999 proj_str_out = osstrdup(proj_str);
1003 invalidate_pj_cached();
1004 break;
1005 case '&': {
1006 /* Datum */
1007 string p = S_INIT;
1008 int datum_len = 0;
1009 int c = 0;
1010 nextch();
1011 skipblanks();
1012 while (ch != ';' && !isEol(ch)) {
1013 s_appendch(&p, (char)ch);
1014 ++c;
1015 /* Ignore trailing blanks. */
1016 if (!isBlank(ch)) datum_len = c;
1017 nextch();
1019 if (ch == ';') nextch_handling_eol();
1020 datum = img_parse_compass_datum_string(s_str(&p), datum_len);
1021 s_free(&p);
1022 goto update_proj_str;
1024 case '[': {
1025 // Enter subdirectory.
1026 struct mak_folder *p = folder_stack;
1027 folder_stack = osnew(struct mak_folder);
1028 folder_stack->next = p;
1029 folder_stack->len = s_len(&path);
1030 if (!s_empty(&path))
1031 s_appendch(&path, FNM_SEP_LEV);
1032 nextch();
1033 while (ch != ';' && !isEol(ch)) {
1034 if (ch == '\\') {
1035 ch = FNM_SEP_LEV;
1037 s_appendch(&path, (char)ch);
1038 nextch();
1040 if (ch == ';') nextch_handling_eol();
1041 break;
1043 case ']': {
1044 // Leave subdirectory.
1045 struct mak_folder *p = folder_stack;
1046 if (folder_stack == NULL) {
1047 // FIXME: Error? Check what Compass does.
1048 break;
1050 s_truncate(&path, folder_stack->len);
1051 folder_stack = folder_stack->next;
1052 osfree(p);
1053 nextch();
1054 skipblanks();
1055 if (ch == ';') nextch_handling_eol();
1056 break;
1058 case '@': {
1059 /* "Base Location" to calculate magnetic declination at:
1060 * UTM East, UTM North, Elevation, UTM Zone, Convergence Angle
1061 * The first three are in metres.
1063 nextch();
1064 real easting = read_numeric(false);
1065 skipblanks();
1066 if (ch != ',') break;
1067 nextch();
1068 real northing = read_numeric(false);
1069 skipblanks();
1070 if (ch != ',') break;
1071 nextch();
1072 real elevation = read_numeric(false);
1073 skipblanks();
1074 if (ch != ',') break;
1075 nextch();
1076 int zone = read_int(-60, 60);
1077 skipblanks();
1078 if (ch != ',') break;
1079 nextch();
1080 real convergence_angle = read_numeric(false);
1081 /* We've now read them all successfully so store them. The
1082 * Compass documentation gives an example which specifies the
1083 * datum *AFTER* the base location, so we need to convert lazily.
1085 base_x = easting;
1086 base_y = northing;
1087 base_z = elevation;
1088 base_utm_zone = zone;
1089 base_line = file.line;
1090 base_lpos = file.lpos;
1091 // We ignore the stored UTM grid convergence angle since we get
1092 // this from PROJ.
1093 (void)convergence_angle;
1094 if (ch == ';') nextch_handling_eol();
1095 break;
1097 default:
1098 nextch_handling_eol();
1099 break;
1103 while (folder_stack) {
1104 // FIXME: Error? Check what Compass does.
1105 struct mak_folder *next = folder_stack->next;
1106 osfree(folder_stack);
1107 folder_stack = next;
1110 pop_settings();
1111 s_free(&path);
1114 // The current Walls reference point and CRS details.
1115 static struct {
1116 real x, y, z;
1117 int zone;
1118 // img_DATUM_* code from src/img.h or -1 if no .REF in effect.
1119 int img_datum_code;
1120 } walls_ref = { 0.0, 0.0, 0.0, 0, -1 };
1122 // We don't expect a huge number of macros, and this doesn't limit how many we
1123 // can handle, only at what point access time stops being O(1).
1124 #define WALLS_MACRO_HASH_SIZE 0x100
1126 typedef struct walls_macro {
1127 struct walls_macro *next;
1128 char *name;
1129 char *value;
1130 int name_len;
1131 } walls_macro;
1133 // Macros set in the WPJ persist, but those set in an SRV only apply for that
1134 // SRV so we keep a table for each and when expanding them in the SRV we use
1135 // a definition from the SRV file in preference to one from the WPJ file.
1137 // Definitions actually also get added to walls_macros, but we swap the tables
1138 // around both before and after processing an SRV file (and we clear the
1139 // table from the SRV file at the end of the file).
1141 // Testing with Walls, macro definitions are NOT affected by SAVE, RESTORE or
1142 // RESET.
1143 static walls_macro **walls_macros_wpj = NULL;
1144 static walls_macro **walls_macros = NULL;
1146 static void
1147 walls_swap_macro_tables()
1149 walls_macro **tmp = walls_macros_wpj;
1150 walls_macros_wpj = walls_macros;
1151 walls_macros = tmp;
1154 // Takes ownership of the contents of p_name and of value.
1155 // Passing NULL for value sets empty string.
1156 static void
1157 walls_set_macro(walls_macro ***table, string *p_name, char *val)
1159 //printf("MACRO: $|%s|=\"%s\":\n", name, val);
1160 if (!*table) {
1161 *table = osmalloc(WALLS_MACRO_HASH_SIZE * ossizeof(walls_macro*));
1162 for (size_t i = 0; i < WALLS_MACRO_HASH_SIZE; i++)
1163 (*table)[i] = NULL;
1166 unsigned h = hash_data(s_str(p_name), s_len(p_name)) &
1167 (WALLS_MACRO_HASH_SIZE - 1);
1168 walls_macro *p = (*table)[h];
1169 while (p) {
1170 if (s_eqlen(p_name, p->name, p->name_len)) {
1171 // Update existing definition of macro.
1172 s_free(p_name);
1173 osfree(p->value);
1174 p->value = val;
1175 return;
1177 p = p->next;
1180 walls_macro *entry = osnew(walls_macro);
1181 entry->name_len = s_len(p_name);
1182 entry->name = s_steal(p_name);
1183 entry->value = val;
1184 entry->next = (*table)[h];
1185 (*table)[h] = entry;
1188 // Returns NULL if not set.
1189 static const char*
1190 walls_get_macro(walls_macro ***table, const char *name, int name_len)
1192 if (!*table) return NULL;
1194 unsigned h = hash_data(name, name_len) & (WALLS_MACRO_HASH_SIZE - 1);
1195 walls_macro *p = (*table)[h];
1196 while (p) {
1197 if (name_len == p->name_len && memcmp(name, p->name, name_len) == 0) {
1198 return p->value ? p->value : "";
1200 p = p->next;
1203 return NULL;
1206 typedef enum {
1207 WALLS_CMD_DATE,
1208 WALLS_CMD_FLAG,
1209 WALLS_CMD_FIX,
1210 WALLS_CMD_NOTE,
1211 WALLS_CMD_PREFIX,
1212 WALLS_CMD_PREFIX2,
1213 WALLS_CMD_PREFIX3,
1214 WALLS_CMD_SEGMENT,
1215 WALLS_CMD_SYMBOL,
1216 WALLS_CMD_UNITS,
1217 WALLS_CMD_NULL = -1
1218 } walls_cmd;
1220 static const sztok walls_cmd_tab[] = {
1221 {"DATE", WALLS_CMD_DATE},
1222 {"F", WALLS_CMD_FLAG}, // Abbreviated form.
1223 {"FIX", WALLS_CMD_FIX},
1224 {"FLAG", WALLS_CMD_FLAG},
1225 {"N", WALLS_CMD_NOTE}, // Abbreviated form.
1226 {"NOTE", WALLS_CMD_NOTE},
1227 {"P", WALLS_CMD_PREFIX}, // Abbreviated form.
1228 {"PREFIX", WALLS_CMD_PREFIX},
1229 {"PREFIX1", WALLS_CMD_PREFIX}, // Alias.
1230 {"PREFIX2", WALLS_CMD_PREFIX2},
1231 {"PREFIX3", WALLS_CMD_PREFIX3},
1232 {"S", WALLS_CMD_SEGMENT}, // Abbreviated form.
1233 {"SEG", WALLS_CMD_SEGMENT}, // Abbreviated form.
1234 {"SEGMENT", WALLS_CMD_SEGMENT},
1235 {"SYM", WALLS_CMD_SYMBOL},
1236 {"SYMBOL", WALLS_CMD_SYMBOL},
1237 {"U", WALLS_CMD_UNITS}, // Abbreviated form.
1238 {"UNITS", WALLS_CMD_UNITS},
1239 {NULL, WALLS_CMD_NULL}
1242 typedef enum {
1243 WALLS_UNITS_OPT_A,
1244 WALLS_UNITS_OPT_AB,
1245 WALLS_UNITS_OPT_CASE,
1246 WALLS_UNITS_OPT_CT,
1247 WALLS_UNITS_OPT_D,
1248 WALLS_UNITS_OPT_DECL,
1249 WALLS_UNITS_OPT_FEET,
1250 WALLS_UNITS_OPT_FLAG,
1251 WALLS_UNITS_OPT_GRID,
1252 WALLS_UNITS_OPT_INCA,
1253 WALLS_UNITS_OPT_INCAB,
1254 WALLS_UNITS_OPT_INCD,
1255 WALLS_UNITS_OPT_INCH,
1256 WALLS_UNITS_OPT_INCV,
1257 WALLS_UNITS_OPT_INCVB,
1258 WALLS_UNITS_OPT_LRUD,
1259 WALLS_UNITS_OPT_METERS,
1260 WALLS_UNITS_OPT_ORDER,
1261 WALLS_UNITS_OPT_PREFIX,
1262 WALLS_UNITS_OPT_PREFIX2,
1263 WALLS_UNITS_OPT_PREFIX3,
1264 WALLS_UNITS_OPT_RECT,
1265 WALLS_UNITS_OPT_RESET,
1266 WALLS_UNITS_OPT_RESTORE,
1267 WALLS_UNITS_OPT_S,
1268 WALLS_UNITS_OPT_SAVE,
1269 WALLS_UNITS_OPT_TAPE,
1270 WALLS_UNITS_OPT_TYPEAB,
1271 WALLS_UNITS_OPT_TYPEVB,
1272 WALLS_UNITS_OPT_UV,
1273 WALLS_UNITS_OPT_UVH,
1274 WALLS_UNITS_OPT_UVV,
1275 WALLS_UNITS_OPT_V,
1276 WALLS_UNITS_OPT_VB,
1277 WALLS_UNITS_OPT_NULL = -1
1278 } walls_units_opt;
1280 // The aliases AZIMUTH, DISTANCE and VERTICAL don't seem to be documented.
1281 // They were found from `strings Walls32.exe` and then testing.
1282 static const sztok walls_units_opt_tab[] = {
1283 {"A", WALLS_UNITS_OPT_A},
1284 {"AB", WALLS_UNITS_OPT_AB},
1285 {"AZIMUTH", WALLS_UNITS_OPT_A}, // Alias (undocumented).
1286 {"CASE", WALLS_UNITS_OPT_CASE},
1287 {"CT", WALLS_UNITS_OPT_CT},
1288 {"D", WALLS_UNITS_OPT_D},
1289 {"DECL", WALLS_UNITS_OPT_DECL},
1290 {"DISTANCE", WALLS_UNITS_OPT_D}, // Alias (undocumented).
1291 {"F", WALLS_UNITS_OPT_FEET}, // Abbreviated form.
1292 {"FEET", WALLS_UNITS_OPT_FEET},
1293 {"FLAG", WALLS_UNITS_OPT_FLAG},
1294 {"GRID", WALLS_UNITS_OPT_GRID},
1295 {"INCA", WALLS_UNITS_OPT_INCA},
1296 {"INCAB", WALLS_UNITS_OPT_INCAB},
1297 {"INCD", WALLS_UNITS_OPT_INCD},
1298 {"INCH", WALLS_UNITS_OPT_INCH},
1299 {"INCV", WALLS_UNITS_OPT_INCV},
1300 {"INCVB", WALLS_UNITS_OPT_INCVB},
1301 {"LRUD", WALLS_UNITS_OPT_LRUD},
1302 {"M", WALLS_UNITS_OPT_METERS}, // Abbreviated form.
1303 {"METERS", WALLS_UNITS_OPT_METERS},
1304 // The Walls documentation mentions a NOTE option here:
1306 // Advanced or Seldom Used Parameters (LRUD=, CASE=, PREFIX=, TAPE=,
1307 // UV=, FLAG=, NOTE=, $name=)
1309 // However the documentation doesn't define it anywhere I can see, and
1310 // testing Walls32.exe it does not seem to be implemented either!
1311 // {"NOTE", WALLS_UNITS_OPT_NOTE},
1312 {"O", WALLS_UNITS_OPT_ORDER}, // Abbreviated form.
1313 {"ORDER", WALLS_UNITS_OPT_ORDER},
1314 {"P", WALLS_UNITS_OPT_PREFIX}, // Abbreviated form.
1315 {"PREFIX", WALLS_UNITS_OPT_PREFIX},
1316 {"PREFIX1", WALLS_UNITS_OPT_PREFIX}, // Alias.
1317 {"PREFIX2", WALLS_UNITS_OPT_PREFIX2},
1318 {"PREFIX3", WALLS_UNITS_OPT_PREFIX3},
1319 {"RECT", WALLS_UNITS_OPT_RECT},
1320 {"RESET", WALLS_UNITS_OPT_RESET},
1321 {"RESTORE", WALLS_UNITS_OPT_RESTORE},
1322 {"S", WALLS_UNITS_OPT_S},
1323 {"SAVE", WALLS_UNITS_OPT_SAVE},
1324 {"TAPE", WALLS_UNITS_OPT_TAPE},
1325 {"TYPEAB", WALLS_UNITS_OPT_TYPEAB},
1326 {"TYPEVB", WALLS_UNITS_OPT_TYPEVB},
1327 {"UV", WALLS_UNITS_OPT_UV},
1328 {"UVH", WALLS_UNITS_OPT_UVH},
1329 {"UVV", WALLS_UNITS_OPT_UVV},
1330 {"V", WALLS_UNITS_OPT_V},
1331 {"VB", WALLS_UNITS_OPT_VB},
1332 {"VERTICAL", WALLS_UNITS_OPT_V}, // Alias (undocumented).
1333 {NULL, WALLS_UNITS_OPT_NULL}
1336 #define WALLS_ORDER_CT(R1,R2,R3) ((R1) | ((R2) << 8) | ((R3) << 16))
1337 #define WALLS_ORDER_RECT(R1,R2,R3) ((R1) | ((R2) << 8) | ((R3) << 16) | (1 << 24))
1339 // Here we rely on the integer values of the reading codes used fitting in
1340 // a byte so assert that is the case.
1341 typedef int compiletimeassert_order_byte_encoding_ok[
1342 (WallsSRVTape|WallsSRVComp|WallsSRVClino|Dx|Dy|Dz) < 0x100 ? 1 : -1];
1344 static const sztok walls_order_tab[] = {
1345 {"AD", WALLS_ORDER_CT(WallsSRVComp, WallsSRVTape, 0)},
1346 {"ADV", WALLS_ORDER_CT(WallsSRVComp, WallsSRVTape, WallsSRVClino)},
1347 {"AVD", WALLS_ORDER_CT(WallsSRVComp, WallsSRVClino, WallsSRVTape)},
1348 {"DA", WALLS_ORDER_CT(WallsSRVTape, WallsSRVComp, 0)},
1349 {"DAV", WALLS_ORDER_CT(WallsSRVTape, WallsSRVComp, WallsSRVClino)},
1350 {"DVA", WALLS_ORDER_CT(WallsSRVTape, WallsSRVClino, WallsSRVComp)},
1351 {"EN", WALLS_ORDER_RECT(Dx, Dy, 0)},
1352 {"ENU", WALLS_ORDER_RECT(Dx, Dy, Dz)},
1353 {"EUN", WALLS_ORDER_RECT(Dx, Dz, Dy)},
1354 {"NE", WALLS_ORDER_RECT(Dy, Dx, 0)},
1355 {"NEU", WALLS_ORDER_RECT(Dy, Dx, Dz)},
1356 {"NUE", WALLS_ORDER_RECT(Dy, Dz, Dx)},
1357 {"UEN", WALLS_ORDER_RECT(Dz, Dx, Dy)},
1358 {"UNE", WALLS_ORDER_RECT(Dz, Dy, Dx)},
1359 {"VAD", WALLS_ORDER_CT(WallsSRVClino, WallsSRVComp, WallsSRVTape)},
1360 {"VDA", WALLS_ORDER_CT(WallsSRVClino, WallsSRVTape, WallsSRVComp)},
1361 {NULL, -1}
1364 // In #FLAG Walls seems to only document `/` but based on real-world use also
1365 // allows `\`. FIXME: Are there other places that allow `\`?
1366 static inline bool isWallsSlash(int c) { return c == '/' || c == '\\'; }
1368 // Walls-specific options. Options which Survex has a direct equivalent of
1369 // are stored in the `settings` struct instead.
1370 typedef struct walls_options {
1371 // The current values of the three prefix levels Walls supports.
1372 // NULL for any level not currently set (all NULL by default).
1373 char* prefix[3];
1375 // Data order for CT legs.
1376 reading data_order_ct[8];
1378 // Data order for RECT legs (also used for #Fix coordinate order).
1379 reading data_order_rect[7];
1381 // Is this from SAVE in .OPTIONS / #Units?
1382 bool explicit;
1384 // Flags to apply to stations in #FIX.
1385 int fix_station_flags;
1387 // Default Compass-compatible flags to apply to legs.
1388 unsigned long compass_dat_flags;
1390 // Current path including trailing directory separator if one is needed.
1391 string path;
1393 struct walls_options *next;
1394 } walls_options;
1396 static walls_options* p_walls_options;
1398 static const walls_options walls_options_default = {
1399 // prefix[3]
1400 { NULL, NULL, NULL },
1402 // data_order_ct[8]
1404 WallsSRVFr, WallsSRVTo, WallsSRVTape, WallsSRVComp, WallsSRVClino,
1405 WallsSRVHeights, WallsSRVExtras, End
1408 // data_order_rect[7]
1410 WallsSRVFr, WallsSRVTo, Dx, Dy, Dz,
1411 WallsSRVExtras, End
1414 // explicit
1415 false,
1417 // fix_station_flags
1420 // compass_dat_flags
1423 // path
1424 S_INIT,
1426 // next
1427 NULL
1430 static void
1431 push_walls_options(void)
1433 settings *pcsNew = osnew(settings);
1434 *pcsNew = *pcs; /* copy contents */
1435 pcsNew->begin_lineno = file.line;
1436 pcsNew->next = pcs;
1437 pcs = pcsNew;
1439 // Walls-specific settings.
1440 walls_options *new_options = osnew(walls_options);
1441 if (!p_walls_options) {
1442 *new_options = walls_options_default;
1443 } else {
1444 *new_options = *p_walls_options; /* copy contents */
1445 // There's only 3 prefix levels and typically only one seems to be
1446 // actually set so just copy the strings rather than trying to do
1447 // copy-on-write.
1448 for (int i = 0; i < 3; ++i) {
1449 if (new_options->prefix[i])
1450 new_options->prefix[i] = osstrdup(new_options->prefix[i]);
1452 // Actually copy path. FIXME: Maybe copy on write?
1453 string empty_string = S_INIT;
1454 new_options->path = empty_string;
1455 s_appends(&new_options->path, &p_walls_options->path);
1458 new_options->next = p_walls_options;
1459 p_walls_options = new_options;
1462 static void
1463 pop_walls_options(void)
1465 pcs->ordering = NULL; /* Avoid free() of static array. */
1466 pop_settings();
1467 walls_options *p = p_walls_options;
1468 p_walls_options = p_walls_options->next;
1469 for (int i = 0; i < 3; ++i) {
1470 osfree(p->prefix[i]);
1472 s_free(&p->path);
1473 osfree(p);
1476 static void
1477 walls_initialise_settings(void)
1479 push_walls_options();
1481 // Generic settings.
1482 short *t = ((short*)osmalloc(ossizeof(short) * 257)) + 1;
1483 // "Unprefixed names can have a maximum of eight characters and must not
1484 // contain any colons, semicolons, commas, pound signs (#), or embedded
1485 // tabs or spaces. In order to avoid possible problems when printing or
1486 // when exporting data to other programs, you are encouraged to restrict
1487 // names in new surveys to numbers with alphabetic prefixes or suffixes
1488 // (e.g., BR123)."
1490 // However in practice, `#` actually seems to be allowed so we allow it.
1491 // It still can't be used as the first character of the from station as
1492 // there it will be interpreted as introducing a command.
1494 // We assume other control characters aren't allowed either (so nothing
1495 // < 32 and not 127), but allow all top-bit-set characters.
1496 t[EOF] = SPECIAL_EOL;
1497 memset(t, 0, sizeof(short) * 33);
1498 for (int i = 33; i < 127; i++) t[i] = SPECIAL_NAMES;
1499 t[127] = 0;
1500 for (int i = 128; i < 256; i++) t[i] = SPECIAL_NAMES;
1501 t[':'] = 0;
1502 t[';'] = SPECIAL_COMMENT;
1503 // FIXME: `,` seems to be treated like a space almost everywhere, but right
1504 // after a directive name a comma instead gives a warning suggesting a
1505 // parse error in a different directive.
1506 t[','] = SPECIAL_BLANK;
1507 t['\t'] |= SPECIAL_BLANK;
1508 t[' '] |= SPECIAL_BLANK;
1509 t['\032'] |= SPECIAL_EOL; /* Ctrl-Z, so olde DOS text files are handled ok */
1510 t['\n'] |= SPECIAL_EOL;
1511 t['\r'] |= SPECIAL_EOL;
1512 t['-'] |= SPECIAL_OMIT;
1513 t[':'] |= SPECIAL_SEPARATOR;
1514 t['.'] |= SPECIAL_DECIMAL;
1515 t['-'] |= SPECIAL_MINUS;
1516 t['+'] |= SPECIAL_PLUS;
1517 pcs->Translate = t;
1519 pcs->begin_lineno = 0;
1520 // Spec says "maximum of eight characters" - we currently allow arbitrarily
1521 // many.
1522 pcs->Truncate = INT_MAX;
1523 pcs->infer = BIT(INFER_EQUATES) |
1524 BIT(INFER_PLUMBS);
1525 // Walls cartesian data is aligned to True North.
1526 pcs->cartesian_north = TRUE_NORTH;
1527 pcs->cartesian_rotation = 0.0;
1530 static void
1531 walls_reset(void)
1533 // "[S]et all parameters (including the current name prefix) to their
1534 // defaults" but not the segment. We currently ignore the segment anyway.
1535 pcs->Case = OFF;
1537 default_units(pcs);
1538 default_calib(pcs);
1539 // FIXME: pcs->z[Q_DECLINATION] = HUGE_REAL;
1541 pcs->recorded_style = pcs->style = STYLE_NORMAL;
1542 pcs->ordering = p_walls_options->data_order_ct;
1544 for (int i = 0; i < 3; ++i) {
1545 osfree(p_walls_options->prefix[i]);
1547 *p_walls_options = walls_options_default;
1550 static real
1551 read_walls_angle(real default_units)
1553 real angle = read_numeric(false);
1554 if (isalpha((unsigned char)ch)) {
1555 get_token();
1556 // Only one letter is allowed here.
1557 if (s_str(&uctoken)[1] != '\0') goto bad_angle_units;
1558 if (s_str(&uctoken)[0] == 'D') {
1559 // Degrees.
1560 angle *= M_PI / 180.0;
1561 } else if (s_str(&uctoken)[0] == 'G') {
1562 // Grads.
1563 angle *= M_PI / 200.0;
1564 } else if (s_str(&uctoken)[0] == 'M') {
1565 // Mils.
1566 angle *= M_PI / 3200.0;
1567 } else {
1568 bad_angle_units:
1569 compile_diagnostic(DIAG_ERR|DIAG_COL,
1570 /*Expecting ā€œ%sā€, ā€œ%sā€, or ā€œ%sā€*/188,
1571 "D", "G", "M");
1573 } else {
1574 angle *= default_units;
1576 return angle;
1579 static real
1580 read_walls_distance(bool f_optional, real default_units)
1582 real distance;
1583 if (ch == 'i' || ch == 'I') {
1584 // Length specified in inches only, e.g. `i6` is 6 inches.
1585 distance = 0.0;
1586 goto inches_only;
1588 distance = read_numeric(f_optional);
1589 if (distance != HUGE_REAL) {
1590 if (isalpha((unsigned char)ch)) {
1591 inches_only:
1592 get_token_legacy();
1593 // Only one letter is allowed here.
1594 if (s_str(&uctoken)[1] != '\0') goto bad_distance_units;
1595 switch (s_str(&uctoken)[0]) {
1596 case 'M':
1597 // Metres.
1598 break;
1599 case 'F':
1600 // Feet.
1601 distance *= METRES_PER_FOOT;
1602 break;
1603 case 'I':
1604 if (isdigit(ch)) {
1605 real inches = read_numeric(false);
1606 distance += inches / 12.0;
1608 distance *= METRES_PER_FOOT;
1609 break;
1610 default:
1611 bad_distance_units:
1612 compile_diagnostic(DIAG_ERR|DIAG_COL,
1613 /*Expecting ā€œ%sā€ or ā€œ%sā€*/103, "F", "M");
1615 } else {
1616 distance *= default_units;
1619 return distance;
1622 static void
1623 read_walls_variance_overrides(real* p_var_xy, real* p_var_z)
1625 // Optional variance override "with no embedded tabs or spaces".
1626 // E.g. `(R5,?)` specifies horizontal and vertical so:
1627 // `R5` means 5m RMS horizontal error
1628 // `?` specifies no elevations were obtained - infinite variance.
1629 // `*` seems to mean the same as `?` for a fixed point, but for
1630 // a leg it is meant to mean "all vectors in the containing traverse are
1631 // floated".
1632 // <non-negative number> means treat as compass and tape vector
1633 // of that length (in length units from #units)
1634 // `` (empty) means don't override that one
1635 // no comma e.g. `(R5)` means apply to both h and v
1636 nextch();
1637 bool rms_h = false;
1638 real val_h;
1639 if (ch == ',' || ch == ')') {
1640 // Use default variance, which is exact for a fixed point.
1641 val_h = 0.0;
1642 } else if (ch == '?' || ch == '*') {
1643 // Infinite variance. It seems `?` and `*` effectively
1644 // mean the same for a fixed point. We don't really
1645 // support this in general but if it's set both
1646 // horizontally and vertically we can ignore it, and if
1647 // it's set for one we can bodge it with a large value.
1648 nextch();
1649 val_h = HUGE_REAL;
1650 } else {
1651 if (ch == 'R' || ch == 'r') {
1652 rms_h = true;
1653 nextch();
1656 val_h = read_walls_distance(false, true);
1657 if (ch == 'F' || ch == 'f') {
1658 val_h *= METRES_PER_FOOT;
1659 nextch();
1660 } else if (ch == 'M' || ch == 'm') {
1661 nextch();
1662 } else {
1663 val_h *= pcs->units[Q_LENGTH];
1666 bool rms_v = rms_h;
1667 real val_v = val_h;
1668 if (ch == ',') {
1669 rms_v = false;
1670 nextch();
1671 if (ch == ')') {
1672 // Use default variance, which is exact for a fixed point.
1673 val_v = 0.0;
1674 } else if (ch == '?' || ch == '*') {
1675 // Infinite variance. It seems `?` and `*` effectively
1676 // mean the same for a fixed point. We don't really
1677 // support this in general but if it's set both
1678 // horizontally and vertically we can ignore it, and if
1679 // it's set for one we can bodge it with a large value.
1680 nextch();
1681 val_v = HUGE_REAL;
1682 } else {
1683 if (ch == 'R' || ch == 'r') {
1684 rms_v = true;
1685 nextch();
1688 val_v = read_number(false, true);
1689 if (ch == 'F' || ch == 'f') {
1690 val_v *= METRES_PER_FOOT;
1691 nextch();
1692 } else if (ch == 'M' || ch == 'm') {
1693 nextch();
1694 } else {
1695 val_v *= pcs->units[Q_LENGTH];
1699 if (ch == ')') {
1700 nextch();
1701 } else {
1702 compile_diagnostic(DIAG_ERR|DIAG_COL,
1703 /*Expecting ā€œ%sā€*/497, ")");
1706 if (val_h == 0) {
1707 // Use default variance, which is exact for a fixed point.
1708 } else if (val_h == HUGE_REAL) {
1709 // Infinite variance. It seems `?` and `*` effectively
1710 // mean the same for a fixed point. We don't really
1711 // support this in general but if it's set both
1712 // horizontally and vertically we can ignore it, and if
1713 // it's set for one we can bodge it with a large value.
1714 *p_var_xy = HUGE_REAL;
1715 } else if (rms_h) {
1716 // "Note that if you make an assignment like (R10), which
1717 // is the same as (R10,R10), you won't be giving all three
1718 // error components identical variances. The vertical
1719 // component in this case would be given variance 10Ā² =
1720 // 100, while each horizontal component would be given half
1721 // that variance, or 50".
1722 *p_var_xy = val_h * val_h / 2.0;
1723 } else {
1724 // The value is to be treated as the length of a leg to use
1725 // the variances of, so this is based on the leg variance
1726 // calculations except we don't have any angles here. Note
1727 // that for Survex the leg length does not affect the
1728 // variances.
1729 *p_var_xy = var(Q_POS) / 3.0 + var(Q_LENGTH) / 2.0;
1731 if (val_v < 0.0) {
1732 // Use default variance, which is exact for a fixed point.
1733 } else if (val_v == HUGE_REAL) {
1734 // Infinite variance.
1735 *p_var_z = HUGE_REAL;
1736 } else if (rms_v) {
1737 *p_var_z = val_v * val_v;
1738 } else {
1739 // The value is to be treated as the length of a leg to use
1740 // the variances of, so this is based on the leg variance
1741 // calculations except we don't have any angles here. Note
1742 // that for Survex the leg length does not affect the
1743 // variances.
1744 *p_var_z = var(Q_POS) / 3.0 + var(Q_LENGTH) / 2.0;
1748 // Walls #FLAG values seem to be arbitrary strings - we attempt to infer
1749 // suitable Survex station flags from a few key words.
1750 static int
1751 parse_walls_flags(bool check_for_quote)
1753 //#define DEBUG_WALLS_FLAGS
1754 int station_flags = 0;
1755 bool quoted = false;
1756 if (check_for_quote) {
1757 skipblanks();
1758 if (ch == '"') {
1759 quoted = true;
1760 nextch();
1764 #ifdef DEBUG_WALLS_FLAGS
1765 bool printed = false;
1766 #endif
1767 while (1) {
1768 skipblanks();
1769 if (isComm(ch) || isEol(ch)) break;
1770 if (quoted && ch == '"') {
1771 nextch();
1772 break;
1774 get_token();
1775 if (S_EQ(&uctoken, "ENTRANCE")) {
1776 station_flags |= BIT(SFLAGS_ENTRANCE);
1777 } else if (S_EQ(&uctoken, "FIX")) {
1778 station_flags |= BIT(SFLAGS_FIXED);
1779 } else if (s_empty(&token)) {
1780 nextch();
1781 #ifdef DEBUG_WALLS_FLAGS
1782 } else if (S_EQ(&uctoken, "LOWER") ||
1783 S_EQ(&uctoken, "LEAD") ||
1784 S_EQ(&uctoken, "SPRING") ||
1785 S_EQ(&uctoken, "RESURGENCE") ||
1786 S_EQ(&uctoken, "RISING") ||
1787 S_EQ(&uctoken, "SINK") ||
1788 S_EQ(&uctoken, "SWALLET") ||
1789 S_EQ(&uctoken, "SUMP") ||
1790 S_EQ(&uctoken, "SHAFT") ||
1791 S_EQ(&uctoken, "UPPER") ||
1792 S_EQ(&uctoken, "CAVE") ||
1793 S_EQ(&uctoken, "GPS") || // -> FIXED flag?
1794 S_EQ(&uctoken, "SURVEYED") ||
1795 S_EQ(&uctoken, "BATS") ||
1796 S_EQ(&uctoken, "MYOTIS") ||
1797 S_EQ(&uctoken, "VELIFER") ||
1798 S_EQ(&uctoken, "GATED") ||
1799 S_EQ(&uctoken, "LOCATION")) {
1800 // With DEBUG_WALLS_FLAGS defined, run on real-world
1801 // data to capture more words which might also be usefully
1802 // mapped to Survex station flags.
1803 } else {
1804 if (!printed) {
1805 printed = true;
1806 printf("*** Unknown flags:");
1808 printf(" %s", s_str(&token));
1809 #endif
1811 if (check_for_quote) break;
1813 #ifdef DEBUG_WALLS_FLAGS
1814 if (printed) printf("\n");
1815 #endif
1816 return station_flags;
1819 static void
1820 parse_walls_segment(unsigned long* p_compass_dat_flags)
1822 *p_compass_dat_flags = 0;
1823 const unsigned long valid_compass_dat_flags =
1824 BIT('L' - 'A') |
1825 BIT('S' - 'A') |
1826 BIT('P' - 'A') |
1827 BIT('X' - 'A') |
1828 BIT('C' - 'A');
1829 unsigned long possible_compass_dat_flags = 0;
1830 // There's no defined format for #segment as such, but apparently it's
1831 // common to set it to Compass DAT flags so if the value looks like a
1832 // set of Compass DAT flags we map them to the Survex leg flags.
1834 // Otherwise we ignore the value since "Segments are optional and have no
1835 // affect on the compilation of survey data".
1836 nextch();
1837 if (ch == '/' || ch == '\\') {
1838 // Treat absolute path the same as a relative path.
1839 nextch();
1841 while (ch >= 'A' && ch <= 'Z') {
1842 possible_compass_dat_flags |= BIT(ch - 'A');
1843 nextch();
1845 if (isBlank(ch) || isEol(ch) || isComm(ch)) {
1846 if ((possible_compass_dat_flags &~ valid_compass_dat_flags) == 0) {
1847 // Compass DAT `X` means exclude the data, but it seems to be used
1848 // in Walls data to mark duplicate data, so we map it to `L`.
1849 if (possible_compass_dat_flags & BIT('X' - 'A')) {
1850 possible_compass_dat_flags &= ~BIT('X' - 'A');
1851 possible_compass_dat_flags |= BIT('L' - 'A');
1853 *p_compass_dat_flags = possible_compass_dat_flags;
1856 skipline();
1859 static int
1860 convert_compass_dat_flags(unsigned long compass_dat_flags)
1862 int survex_flags = 0;
1863 if ((compass_dat_flags & BIT('S' - 'A'))) {
1864 /* 'S' means "splay". */
1865 survex_flags |= BIT(FLAGS_SPLAY);
1867 if ((compass_dat_flags & BIT('P' - 'A'))) {
1868 /* 'P' means "Exclude this shot from plotting", but the use
1869 * suggested in the Compass docs is for surface data, and legs
1870 * with this flag "[do] not support passage modeling".
1872 * Even if it's actually being used for a different
1873 * purpose, Survex programs don't show surface legs
1874 * by default so FLAGS_SURFACE matches fairly well.
1876 survex_flags |= BIT(FLAGS_SURFACE);
1878 if ((compass_dat_flags & BIT('L' - 'A'))) {
1879 /* 'L' means "exclude from length" - map this to Survex's
1880 * FLAGS_DUPLICATE. */
1881 survex_flags |= BIT(FLAGS_DUPLICATE);
1883 return survex_flags;
1886 static void
1887 parse_options(void)
1889 skipblanks();
1890 while (!isEol(ch)) {
1891 get_token();
1892 if (s_empty(&token) && isComm(ch)) {
1893 break;
1895 filepos fp_option;
1896 get_pos(&fp_option);
1898 // Assign to typed variable so we get a warning if we are
1899 // missing a case below.
1900 walls_units_opt opt = match_tok(walls_units_opt_tab,
1901 TABSIZE(walls_units_opt_tab));
1902 switch (opt) {
1903 case WALLS_UNITS_OPT_D:
1904 case WALLS_UNITS_OPT_A:
1905 case WALLS_UNITS_OPT_AB:
1906 case WALLS_UNITS_OPT_V:
1907 case WALLS_UNITS_OPT_VB:
1908 case WALLS_UNITS_OPT_S:
1909 case WALLS_UNITS_OPT_ORDER:
1910 case WALLS_UNITS_OPT_DECL:
1911 case WALLS_UNITS_OPT_INCA:
1912 case WALLS_UNITS_OPT_INCAB:
1913 case WALLS_UNITS_OPT_INCD:
1914 case WALLS_UNITS_OPT_INCH:
1915 case WALLS_UNITS_OPT_INCV:
1916 case WALLS_UNITS_OPT_INCVB:
1917 case WALLS_UNITS_OPT_GRID:
1918 case WALLS_UNITS_OPT_CASE:
1919 case WALLS_UNITS_OPT_LRUD:
1920 case WALLS_UNITS_OPT_TAPE:
1921 case WALLS_UNITS_OPT_TYPEAB:
1922 case WALLS_UNITS_OPT_TYPEVB:
1923 case WALLS_UNITS_OPT_UV:
1924 case WALLS_UNITS_OPT_UVH:
1925 case WALLS_UNITS_OPT_UVV:
1926 // These options all require an argument, so check for `=` and
1927 // advance past it.
1928 skipblanks();
1929 if (ch != '=') {
1930 compile_diagnostic(DIAG_ERR|DIAG_COL,
1931 /*Expecting ā€œ%sā€*/497, "=");
1932 break;
1934 nextch();
1935 break;
1936 default:
1937 break;
1939 switch (opt) {
1940 case WALLS_UNITS_OPT_METERS:
1941 pcs->units[Q_LENGTH] =
1942 pcs->units[Q_DX] =
1943 pcs->units[Q_DY] =
1944 pcs->units[Q_DZ] = 1.0;
1945 break;
1946 case WALLS_UNITS_OPT_FEET:
1947 pcs->units[Q_LENGTH] =
1948 pcs->units[Q_DX] =
1949 pcs->units[Q_DY] =
1950 pcs->units[Q_DZ] = METRES_PER_FOOT;
1951 break;
1952 case WALLS_UNITS_OPT_D:
1953 get_token();
1954 // From testing it seems Walls only checks the initial letter - e.g.
1955 // "M", "METERS", "METRES", "F", "FEET" and even "FISH" are accepted,
1956 // but "X" gives an error.
1957 if (s_str(&uctoken)[0] == 'M') {
1958 pcs->units[Q_LENGTH] = 1.0;
1959 } else if (s_str(&uctoken)[0] == 'F') {
1960 pcs->units[Q_LENGTH] = METRES_PER_FOOT;
1961 } else {
1962 filepos fp;
1963 get_pos(&fp);
1964 set_pos(&fp_option);
1965 (void)nextch(); // Skip the `=`.
1966 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting ā€œ%sā€ or ā€œ%sā€*/103, "F", "M");
1967 set_pos(&fp);
1969 break;
1970 case WALLS_UNITS_OPT_A:
1971 get_token();
1972 // It seems Walls only checks the initial letter.
1973 if (s_str(&uctoken)[0] == 'D') {
1974 // Degrees.
1975 pcs->units[Q_BEARING] = M_PI / 180.0;
1976 } else if (s_str(&uctoken)[0] == 'G') {
1977 // Grads.
1978 pcs->units[Q_BEARING] = M_PI / 200.0;
1979 } else if (s_str(&uctoken)[0] == 'M') {
1980 // Mils.
1981 pcs->units[Q_BEARING] = M_PI / 3200.0;
1982 } else {
1983 filepos fp;
1984 get_pos(&fp);
1985 set_pos(&fp_option);
1986 (void)nextch(); // Skip the `=`.
1987 compile_diagnostic(DIAG_ERR|DIAG_COL,
1988 /*Expecting ā€œ%sā€, ā€œ%sā€, or ā€œ%sā€*/188,
1989 "D", "G", "M");
1990 set_pos(&fp);
1992 break;
1993 case WALLS_UNITS_OPT_AB:
1994 get_token();
1995 // It seems Walls only checks the initial letter.
1996 if (s_str(&uctoken)[0] == 'D') {
1997 // Degrees.
1998 pcs->units[Q_BACKBEARING] = M_PI / 180.0;
1999 } else if (s_str(&uctoken)[0] == 'G') {
2000 // Grads.
2001 pcs->units[Q_BACKBEARING] = M_PI / 200.0;
2002 } else if (s_str(&uctoken)[0] == 'M') {
2003 // Mils.
2004 pcs->units[Q_BACKBEARING] = M_PI / 3200.0;
2005 } else {
2006 filepos fp;
2007 get_pos(&fp);
2008 set_pos(&fp_option);
2009 (void)nextch(); // Skip the `=`.
2010 compile_diagnostic(DIAG_ERR|DIAG_COL,
2011 /*Expecting ā€œ%sā€, ā€œ%sā€, or ā€œ%sā€*/188,
2012 "D", "G", "M");
2013 set_pos(&fp);
2015 break;
2016 case WALLS_UNITS_OPT_V:
2017 get_token();
2018 pcs->f_clino_percent = false;
2019 // It seems Walls only checks the initial letter.
2020 if (s_str(&uctoken)[0] == 'D') {
2021 // Degrees.
2022 pcs->units[Q_GRADIENT] = M_PI / 180.0;
2023 } else if (s_str(&uctoken)[0] == 'G') {
2024 // Grads.
2025 pcs->units[Q_GRADIENT] = M_PI / 200.0;
2026 } else if (s_str(&uctoken)[0] == 'M') {
2027 // Mils.
2028 pcs->units[Q_GRADIENT] = M_PI / 3200.0;
2029 } else if (s_str(&uctoken)[0] == 'P') {
2030 pcs->units[Q_GRADIENT] = 0.01;
2031 pcs->f_clino_percent = true;
2032 } else {
2033 filepos fp;
2034 get_pos(&fp);
2035 set_pos(&fp_option);
2036 (void)nextch(); // Skip the `=`.
2037 compile_diagnostic(DIAG_ERR|DIAG_COL,
2038 /*Expecting ā€œ%sā€, ā€œ%sā€, ā€œ%sā€, or ā€œ%sā€*/189,
2039 "D", "G", "M", "P");
2040 set_pos(&fp);
2042 break;
2043 case WALLS_UNITS_OPT_VB:
2044 get_token();
2045 pcs->f_backclino_percent = false;
2046 // It seems Walls only checks the initial letter.
2047 if (s_str(&uctoken)[0] == 'D') {
2048 // Degrees.
2049 pcs->units[Q_BACKGRADIENT] = M_PI / 180.0;
2050 } else if (s_str(&uctoken)[0] == 'G') {
2051 // Grads.
2052 pcs->units[Q_BACKGRADIENT] = M_PI / 200.0;
2053 } else if (s_str(&uctoken)[0] == 'M') {
2054 // Mils.
2055 pcs->units[Q_BACKGRADIENT] = M_PI / 3200.0;
2056 } else if (s_str(&uctoken)[0] == 'P') {
2057 pcs->units[Q_BACKGRADIENT] = 0.01;
2058 pcs->f_backclino_percent = true;
2059 } else {
2060 filepos fp;
2061 get_pos(&fp);
2062 set_pos(&fp_option);
2063 (void)nextch(); // Skip the `=`.
2064 compile_diagnostic(DIAG_ERR|DIAG_COL,
2065 /*Expecting ā€œ%sā€, ā€œ%sā€, ā€œ%sā€, or ā€œ%sā€*/189,
2066 "D", "G", "M", "P");
2067 set_pos(&fp);
2069 break;
2070 case WALLS_UNITS_OPT_S:
2071 get_token();
2072 // From testing it seems Walls only checks the initial letter - e.g.
2073 // "M", "METERS", "METRES", "F", "FEET" and even "FISH" are accepted,
2074 // but "X" gives an error.
2075 if (s_str(&uctoken)[0] == 'M') {
2076 pcs->units[Q_DX] =
2077 pcs->units[Q_DY] =
2078 pcs->units[Q_DZ] = 1.0;
2079 } else if (s_str(&uctoken)[0] == 'F') {
2080 pcs->units[Q_DX] =
2081 pcs->units[Q_DY] =
2082 pcs->units[Q_DZ] = METRES_PER_FOOT;
2083 } else {
2084 filepos fp;
2085 get_pos(&fp);
2086 set_pos(&fp_option);
2087 (void)nextch(); // Skip the `=`.
2088 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting ā€œ%sā€ or ā€œ%sā€*/103, "F", "M");
2089 set_pos(&fp);
2091 break;
2092 case WALLS_UNITS_OPT_ORDER:
2093 get_token();
2094 int order = match_tok(walls_order_tab,
2095 TABSIZE(walls_order_tab));
2096 if (order < 0) {
2097 compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Data style ā€œ%sā€ unknown*/65, s_str(&token));
2098 break;
2100 reading* p;
2101 bool rect = (order & (1 << 24));
2102 if (rect) {
2103 order &= ((1 << 24) - 1);
2104 // "RECT" order.
2105 p = p_walls_options->data_order_rect + 2;
2106 } else {
2107 // "CT" order.
2108 p = p_walls_options->data_order_ct + 2;
2110 while (order) {
2111 *p++ = (order & 0xff);
2112 order >>= 8;
2114 if (!rect) *p++ = WallsSRVHeights;
2115 *p++ = WallsSRVExtras;
2116 *p = End;
2117 break;
2118 case WALLS_UNITS_OPT_DECL:
2119 pcs->z[Q_DECLINATION] = -read_walls_angle(M_PI / 180.0);
2120 break;
2121 case WALLS_UNITS_OPT_INCA:
2122 pcs->z[Q_BEARING] = -read_walls_angle(pcs->units[Q_BEARING]);
2123 break;
2124 case WALLS_UNITS_OPT_INCAB:
2125 pcs->z[Q_BACKBEARING] = -read_walls_angle(pcs->units[Q_BACKBEARING]);
2126 break;
2127 case WALLS_UNITS_OPT_INCD:
2128 pcs->z[Q_LENGTH] = -read_walls_distance(false, pcs->units[Q_LENGTH]);
2129 break;
2130 case WALLS_UNITS_OPT_INCH:
2131 // INCH=0 is what we do anyway, so only warn about non-zero values.
2132 if (read_walls_distance(false, pcs->units[Q_LENGTH]) != 0.0) {
2133 filepos fp;
2134 get_pos(&fp);
2135 set_pos(&fp_option);
2136 compile_diagnostic(DIAG_WARN|DIAG_TOKEN, /*Unknown command ā€œ%sā€*/12, s_str(&token));
2137 set_pos(&fp);
2139 break;
2140 case WALLS_UNITS_OPT_INCV:
2141 pcs->z[Q_GRADIENT] = -read_walls_angle(pcs->units[Q_GRADIENT]);
2142 break;
2143 case WALLS_UNITS_OPT_INCVB:
2144 pcs->z[Q_BACKGRADIENT] = -read_walls_angle(pcs->units[Q_BACKGRADIENT]);
2145 break;
2146 case WALLS_UNITS_OPT_GRID:
2147 // FIXME: GRID= not useful with geo-referenced data?
2148 compile_diagnostic(DIAG_WARN|DIAG_TOKEN, /*Unknown command ā€œ%sā€*/12, s_str(&token));
2149 (void)read_walls_angle(M_PI / 180.0);
2150 break;
2151 case WALLS_UNITS_OPT_RECT:
2152 // There are two different RECT options, one with a
2153 // parameter and one without!
2154 skipblanks();
2155 if (ch == '=') {
2156 nextch();
2157 // A bearing to rotate cartesian data by, with 0 meaning true
2158 // North.
2159 pcs->cartesian_rotation = read_walls_angle(M_PI / 180.0);
2160 } else {
2161 pcs->recorded_style = pcs->style = STYLE_CARTESIAN;
2162 pcs->ordering = p_walls_options->data_order_rect;
2164 break;
2165 case WALLS_UNITS_OPT_CASE:
2166 get_token();
2167 // Walls documents `CASE = Upper / Lower / Mixed` which hints that
2168 // it only actually tests the first character. It also seems that
2169 // any other character is treated as `Mixed` too.
2170 switch (s_str(&uctoken)[0]) {
2171 case 'L':
2172 pcs->Case = LOWER;
2173 break;
2174 case 'U':
2175 pcs->Case = UPPER;
2176 break;
2177 default:
2178 pcs->Case = OFF;
2179 break;
2181 break;
2182 case WALLS_UNITS_OPT_CT:
2183 pcs->recorded_style = pcs->style = STYLE_NORMAL;
2184 pcs->ordering = p_walls_options->data_order_ct;
2185 break;
2186 case WALLS_UNITS_OPT_PREFIX:
2187 case WALLS_UNITS_OPT_PREFIX2:
2188 case WALLS_UNITS_OPT_PREFIX3: {
2189 // PREFIX, etc without a value clear that level of the prefix.
2190 char *new_prefix = NULL;
2191 skipblanks();
2192 if (ch == '=') {
2193 nextch();
2194 new_prefix = read_walls_prefix();
2196 int i = (int)WALLS_UNITS_OPT_PREFIX3 - (int)opt;
2197 osfree(p_walls_options->prefix[i]);
2198 p_walls_options->prefix[i] = new_prefix;
2199 break;
2201 case WALLS_UNITS_OPT_LRUD: {
2202 // Value is F, T, FB, TB or one of those followed by eg. :UDRL (no
2203 // spaces around :). Default is F:LRUD.
2205 // We currently ignore LRUD, so can also ignore the settings for
2206 // it.
2207 string val = S_INIT;
2208 read_string(&val);
2209 s_free(&val);
2210 break;
2212 case WALLS_UNITS_OPT_TAPE:
2213 get_token();
2214 /* FIXME: Implement different taping methods? */
2215 /* IT, SS, IS, ST (default is IT). */
2216 break;
2217 case WALLS_UNITS_OPT_TYPEAB:
2218 get_token();
2219 if (s_str(&uctoken)[0] == 'N') {
2220 pcs->z[Q_BACKBEARING] = 0.0;
2221 } else if (s_str(&uctoken)[0] == 'C') {
2222 pcs->z[Q_BACKBEARING] = M_PI;
2223 } else {
2224 filepos fp;
2225 get_pos(&fp);
2226 set_pos(&fp_option);
2227 (void)nextch(); // Skip the `=`.
2228 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting ā€œ%sā€ or ā€œ%sā€*/103, "C", "N");
2229 set_pos(&fp);
2231 if (ch == ',') {
2232 nextch();
2233 // FIXME: Use threshold value.
2234 (void)read_numeric(false);
2235 if (!isBlank(ch) && !isComm(ch) && !isEol(ch)) {
2236 // Walls quietly ignores junk after a valid number here.
2237 get_word();
2238 compile_diagnostic(DIAG_WARN|DIAG_TOKEN, /*Ignoring ā€œ%sā€*/506, s_str(&token));
2240 if (ch == ',') {
2241 nextch();
2242 if (toupper(ch) == 'X') {
2243 nextch();
2244 // FIXME: Only use foresight (but check backsight).
2248 break;
2249 case WALLS_UNITS_OPT_TYPEVB:
2250 get_token();
2251 if (s_str(&uctoken)[0] == 'N') {
2252 pcs->sc[Q_BACKGRADIENT] = 1.0;
2253 } else if (s_str(&uctoken)[0] == 'C') {
2254 pcs->sc[Q_BACKGRADIENT] = -1.0;
2255 } else {
2256 filepos fp;
2257 get_pos(&fp);
2258 set_pos(&fp_option);
2259 (void)nextch(); // Skip the `=`.
2260 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting ā€œ%sā€ or ā€œ%sā€*/103, "C", "N");
2261 set_pos(&fp);
2263 if (ch == ',') {
2264 nextch();
2265 // FIXME: Use threshold value.
2266 (void)read_numeric(false);
2267 if (!isBlank(ch) && !isComm(ch) && !isEol(ch)) {
2268 // Walls quietly ignores junk after a valid number here.
2269 get_word();
2270 compile_diagnostic(DIAG_WARN|DIAG_TOKEN, /*Ignoring ā€œ%sā€*/506, s_str(&token));
2272 if (ch == ',') {
2273 nextch();
2274 if (toupper(ch) == 'X') {
2275 nextch();
2276 // FIXME: Only use foresight (but check backsight).
2280 break;
2281 case WALLS_UNITS_OPT_UV:
2282 case WALLS_UNITS_OPT_UVH:
2283 case WALLS_UNITS_OPT_UVV:
2284 // Scale factors for variances (with horizontal-only and
2285 // vertical-only variants). FIXME: Actually apply these!
2286 (void)read_numeric(false);
2287 if (!isBlank(ch) && !isComm(ch) && !isEol(ch)) {
2288 // Walls quietly ignores junk after a valid number here.
2289 get_word();
2290 compile_diagnostic(DIAG_WARN|DIAG_TOKEN, /*Ignoring ā€œ%sā€*/506, s_str(&token));
2292 break;
2293 case WALLS_UNITS_OPT_FLAG:
2294 // Default flag to apply to stations in #FIX.
2295 skipblanks();
2296 if (ch == '=') {
2297 nextch();
2298 p_walls_options->fix_station_flags = parse_walls_flags(true);
2299 } else {
2300 p_walls_options->fix_station_flags = 0;
2302 break;
2303 case WALLS_UNITS_OPT_RESET:
2304 // FIXME: This should be processed before other arguments!
2305 walls_reset();
2306 break;
2307 case WALLS_UNITS_OPT_RESTORE: {
2308 // FIXME: Should this be processed before other arguments?
2309 if (!p_walls_options->explicit) {
2310 /* TRANSLATORS: %s is replaced with e.g. BEGIN or .BOOK or #[ */
2311 compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*No matching %s*/192, "SAVE");
2312 break;
2314 pop_walls_options();
2315 break;
2317 case WALLS_UNITS_OPT_SAVE: {
2318 // FIXME: This should be processed before other arguments!
2319 push_walls_options();
2320 p_walls_options->explicit = true;
2321 break;
2323 case WALLS_UNITS_OPT_NULL:
2324 if (s_str(&uctoken)[0] == '\0' && ch == '$') {
2325 // Macro definition.
2326 filepos fp;
2327 get_pos(&fp);
2328 nextch();
2329 string name = S_INIT;
2330 while (!isBlank(ch) && !isComm(ch) && !isEol(ch) && ch != '=') {
2331 s_appendch(&name, ch);
2332 nextch();
2334 if (!s_empty(&name)) {
2335 skipblanks();
2336 if (ch != '=') {
2337 // Set an empty value.
2338 walls_set_macro(&walls_macros, &name, NULL);
2339 } else {
2340 nextch();
2341 string val = S_INIT;
2342 read_string(&val);
2343 walls_set_macro(&walls_macros, &name, s_steal(&val));
2345 break;
2347 s_free(&name);
2348 set_pos(&fp);
2349 s_clear(&token);
2351 compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Unknown command ā€œ%sā€*/12, s_str(&token));
2352 if (ch == '=') {
2353 // Skip over `=` and the rest of the argument so we handle a
2354 // typo-ed option name nicely.
2355 do {
2356 nextch();
2357 } while (!isBlank(ch) && !isComm(ch) && !isEol(ch));
2359 break;
2361 // pcs->z[Q_BACKBEARING] = pcs->z[Q_BEARING] = -rad(read_numeric(false));
2362 // pcs->z[Q_BACKGRADIENT] = pcs->z[Q_GRADIENT] = -rad(read_numeric(false));
2363 // pcs->z[Q_LENGTH] = -METRES_PER_FOOT * read_numeric(false);
2365 /* Original "Inclination Units" were "Depth Gauge". */
2366 //pcs->recorded_style = STYLE_DIVING;
2367 //skipline();
2368 skipblanks();
2372 static void
2373 data_file_walls_srv(void)
2375 // FIXME: Do any of these variables need to be volatile to protect them
2376 // from longjmp()? GCC isn't warning about them...
2378 bool standalone = (p_walls_options == NULL);
2379 if (standalone) {
2380 // We're being included standalone rather than from a WPJ file.
2381 walls_initialise_settings();
2382 walls_reset();
2385 // Default flags assigned to stations in #FIX.
2386 int fix_station_flags = p_walls_options->fix_station_flags;
2388 // FIXME: We need to update the separator_map to reflect what can be
2389 // SPECIAL_NAMES. Or should we use the Compass approach and base this
2390 // on what's actually used? The first approach would pick the separator
2391 // from {':', ';', ',', '#', space}; the latter would pick '.' if
2392 // the station naming recommendations in the Walls documentation are
2393 // followed.
2394 update_output_separator();
2396 /* errors in nested functions can longjmp here */
2397 if (setjmp(jbSkipLine)) {
2398 // Recover from errors in nested functions by longjmp() to here.
2399 skipline();
2400 process_eol();
2403 if (pcs->style == STYLE_NORMAL)
2404 pcs->ordering = p_walls_options->data_order_ct;
2405 else
2406 pcs->ordering = p_walls_options->data_order_rect;
2408 while (ch != EOF && !ferror(file.fh)) {
2409 next_line:
2410 skipblanks();
2411 if (ch != '#') {
2412 if (ch == ';' || isEol(ch)) {
2413 skipline();
2414 process_eol();
2415 } else if (pcs->style == STYLE_NORMAL) {
2416 data_normal();
2417 } else {
2418 // Set up Dz in case it's omitted.
2419 VAL(Dz) = 0.0;
2420 VAR(Dz) = 1e6;
2421 data_cartesian();
2423 continue;
2426 // Directive:
2427 int leading_blanks = ftell(file.fh) - file.lpos - 1;
2428 nextch();
2429 if (ch == '[') {
2430 // "Commented out" data.
2431 int start_lineno = file.line;
2432 skipline();
2433 process_eol();
2435 int depth = 1;
2436 while (depth && ch != EOF) {
2437 skipblanks();
2438 if (ch == '#') {
2439 nextch();
2440 depth += (ch == '[') - (ch == ']');
2442 skipline();
2443 process_eol();
2445 if (depth) {
2446 /* TRANSLATORS: %s and %s are replaced with e.g. BEGIN and END
2447 * or END and BEGIN or #[ and #] */
2448 error_in_file(file.filename, start_lineno,
2449 /*%s with no matching %s in this file*/23,
2450 "#[", "#]");
2452 goto next_line;
2454 if (ch == ']') {
2455 /* TRANSLATORS: %s is replaced with e.g. BEGIN or .BOOK or #[ */
2456 compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*No matching %s*/192, "#[");
2458 skipblanks();
2459 int blanks_after_hash = ftell(file.fh) - file.lpos - leading_blanks - 2;
2460 get_token();
2461 walls_cmd directive = match_tok(walls_cmd_tab, TABSIZE(walls_cmd_tab));
2462 parse file_store;
2463 volatile int ch_store;
2464 string line = S_INIT;
2465 if (directive != WALLS_CMD_FIX && directive != WALLS_CMD_NULL) {
2466 bool seen_macros = false;
2467 // Recreate the start of the line for error reporting. This will
2468 // change each tab to a single space, but we do that anyway in
2469 // show_line().
2470 if (leading_blanks)
2471 s_appendn(&line, leading_blanks, ' ');
2472 s_appendch(&line, '#');
2473 if (blanks_after_hash)
2474 s_appendn(&line, blanks_after_hash, ' ');
2475 s_appends(&line, &token);
2477 filepos fp_args;
2478 get_pos(&fp_args);
2480 // Expand macros such as $(foo) in rest of line.
2481 while (!isEol(ch)) {
2482 if (ch != '$') {
2483 s_appendch(&line, ch);
2484 nextch();
2485 continue;
2487 nextch();
2488 if (ch != '(') {
2489 s_appendch(&line, '$');
2490 continue;
2492 nextch();
2493 // Read name of macro onto the end of line, then replace it
2494 // with the value of the macro.
2495 int macro_start = s_len(&line);
2496 while (!isEol(ch) && ch != ')') {
2497 s_appendch(&line, ch);
2498 nextch();
2500 nextch();
2501 const char *name = s_str(&line) + macro_start;
2502 int name_len = s_len(&line) - macro_start;
2503 const char *macro = walls_get_macro(&walls_macros,
2504 name, name_len);
2505 if (!macro) {
2506 macro = walls_get_macro(&walls_macros_wpj, name, name_len);
2508 if (!macro) {
2509 compile_diagnostic(DIAG_ERR, /*Macro ā€œ%sā€ not defined*/499,
2510 s_str(&line) + macro_start);
2512 s_truncate(&line, macro_start);
2513 if (macro)
2514 s_append(&line, macro);
2515 seen_macros = true;
2518 if (seen_macros) {
2519 //printf("MACRO EXPANSION <%s>\n", line);
2520 // Read from the buffered macro-expanded line instead of the
2521 // file.
2522 file_store = file;
2523 ch_store = ch;
2524 #ifdef HAVE_FMEMOPEN
2525 file.fh = fmemopen((char*)s_str(&line), s_len(&line), "rb");
2526 if (!file.fh) {
2527 fatalerror(/*Failed to create temporary file*/498);
2529 #else
2530 file.fh = tmpfile();
2531 if (!file.fh) {
2532 fatalerror(/*Failed to create temporary file*/498);
2534 fwrite(s_str(&line), s_len(&line), 1, file.fh);
2535 #endif
2536 fseek(file.fh, fp_args.offset - file.lpos, SEEK_SET);
2537 ch = (unsigned char)s_str(&line)[fp_args.offset - file.lpos - 1];
2538 file.lpos = 0;
2539 } else {
2540 //printf("no macros seen in <%s>\n", line);
2541 // No macros seen so rewind and read directly from the file.
2542 s_free(&line);
2543 set_pos(&fp_args);
2547 switch (directive) {
2548 case WALLS_CMD_UNITS:
2549 parse_options();
2550 break;
2551 case WALLS_CMD_DATE: {
2552 int year, month, day;
2553 read_walls_srv_date(&year, &month, &day);
2554 copy_on_write_meta(pcs);
2555 int days = days_since_1900(year, month, day);
2556 pcs->meta->days1 = pcs->meta->days2 = days;
2557 // [If there's a .REF] "A #Date directive then becomes equivalent
2558 // to a #Units directive with the single argument DECL=x, where x
2559 // is the declination derived from a model of the Earth's magnetic
2560 // field. The use of this option, however, will not deactivate all
2561 // DECL specifications that might actually appear on #Units
2562 // directive lines. The two methods of specifying declination will
2563 // simply override each other depending on the ordering of
2564 // directives in your files."
2565 if (walls_ref.img_datum_code >= 0) {
2566 pcs->z[Q_DECLINATION] = HUGE_REAL;
2568 skipblanks();
2569 if (!isEol(ch) && !isComm(ch)) {
2570 // Walls seems to ignore anything after the date so make this a
2571 // warning not an error so we can process existing Walls
2572 // datasets (e.g. the Mammoth dataset reportedly has `#Date
2573 // 1978-07-01\`.
2574 compile_diagnostic(DIAG_WARN|DIAG_SKIP|DIAG_TAIL,
2575 /*End of line not blank*/15);
2577 break;
2579 case WALLS_CMD_FIX: {
2580 real coords[3];
2581 filepos fp_stn;
2582 get_pos(&fp_stn);
2583 prefix *name = read_walls_station(p_walls_options->prefix,
2584 false, NULL);
2585 // FIXME: can be e.g. `W97:43:52.5 N31:16:45 323f`
2586 // Or E/S instead of W/N.
2588 enum { UNKNOWN, LATLONG, UTM } format = UNKNOWN;
2589 for (int i = 0; i < 3; ++i) {
2590 // The order of the coordinates is specified by data_order_rect.
2591 int compiletimeassert_dxdydz[Dy - Dx == 1 && Dz - Dy == 1 ? 1 : -1];
2592 (void)compiletimeassert_dxdydz;
2593 int dim = p_walls_options->data_order_rect[i + 2] - Dx;
2594 if ((unsigned)dim > 2) {
2595 // FIXME: Survex doesn't currently support horizontal-only
2596 // fixes.
2597 coords[2] = 0.0;
2598 break;
2601 real coord;
2602 skipblanks();
2603 int upper_ch = toupper(ch);
2604 if (dim == 2 || format == UTM || strchr("NWES", upper_ch) == NULL) {
2605 // Read as a distance if this is the altitude, or we've
2606 // already seen a distance for x or y, or if the coordinate
2607 // doesn't start with a compass point letter.
2608 coord = read_walls_distance(false, pcs->units[Q_LENGTH]);
2609 if (dim != 2) format = UTM;
2610 } else {
2611 // Set negate if S or W.
2612 bool negate = ((upper_ch & 3) == 3);
2613 bool e_or_w = ((upper_ch & 5) == 5);
2614 if (dim == e_or_w) {
2615 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting ā€œ%sā€ or ā€œ%sā€*/103,
2616 e_or_w ? "N" : "E", e_or_w ? "S" : "W");
2618 nextch();
2619 coord = read_number(false, true);
2620 if (ch == ':') {
2621 // FIXME: This accepts decimals on any component e.g `N40.1:1:1`.
2622 nextch();
2623 real minutes = read_number(false, true);
2624 coord += minutes / 60.0;
2625 if (ch == ':') {
2626 nextch();
2627 real seconds = read_number(false, true);
2628 coord += seconds / 3600.0;
2631 if (negate) coord = -coord;
2633 format = LATLONG;
2636 coords[dim] = coord;
2639 real var_xy = 0.0, var_z = 0.0;
2640 skipblanks();
2641 if (ch == '(') {
2642 read_walls_variance_overrides(&var_xy, &var_z);
2644 skipblanks();
2645 if (ch == '/') {
2646 // Station note - ignore for now. Note: Must be '/'.
2647 skipline();
2650 if (format == LATLONG) {
2651 // Convert coordinates based on the current coordinate system
2652 // set by .REF in the wpj.
2653 if (walls_ref.img_datum_code > 0 && proj_str_out) {
2654 int epsg_code =
2655 img_compass_longlat_epsg_code(walls_ref.img_datum_code);
2656 char proj_longlat[32];
2657 snprintf(proj_longlat, sizeof(proj_longlat),
2658 "EPSG:%d", epsg_code);
2659 PJ *transform = proj_create_crs_to_crs(PJ_DEFAULT_CTX,
2660 proj_longlat,
2661 proj_str_out,
2662 NULL);
2663 if (transform) {
2664 /* Normalise the output order so x is longitude and y
2665 * latitude - by default new PROJ has them switched for
2666 * EPSG:4326 which just seems confusing.
2668 PJ* pj_norm = proj_normalize_for_visualization(PJ_DEFAULT_CTX,
2669 transform);
2670 proj_destroy(transform);
2671 transform = pj_norm;
2674 if (proj_angular_input(transform, PJ_FWD)) {
2675 /* Input coordinate system expects radians. */
2676 coords[0] = rad(coords[0]);
2677 coords[1] = rad(coords[1]);
2680 PJ_COORD coord = {{coords[0], coords[1], coords[2], HUGE_VAL}};
2681 coord = proj_trans(transform, PJ_FWD, coord);
2682 coords[0] = coord.xyzt.x;
2683 coords[1] = coord.xyzt.y;
2684 coords[2] = coord.xyzt.z;
2686 if (coords[0] == HUGE_VAL || coords[1] == HUGE_VAL || coords[2] == HUGE_VAL) {
2687 compile_diagnostic(DIAG_ERR, /*Failed to convert coordinates: %s*/436,
2688 proj_context_errno_string(PJ_DEFAULT_CTX,
2689 proj_errno(transform)));
2690 /* Set dummy values which are finite. */
2691 coords[0] = coords[1] = coords[2] = 0;
2693 proj_destroy(transform);
2694 } else {
2695 if (walls_ref.img_datum_code == 0) {
2696 // We already emitted an error that this datum is not
2697 // supported so an error here doesn't seem helpful.
2698 } else {
2699 compile_diagnostic(DIAG_ERR, /*Output coordinate system not set*/488);
2701 /* Set dummy values which are finite. */
2702 coords[0] = coords[1] = coords[2] = 0;
2706 // Apply station flags inferred from Walls "default flag".
2707 name->sflags |= fix_station_flags;
2709 if (var_xy == 0.0 && var_z == 0.0) {
2710 // Exact fix.
2711 int fix_result = fix_station(name, coords);
2712 if (fix_result) {
2713 filepos fp;
2714 get_pos(&fp);
2715 set_pos(&fp_stn);
2716 if (fix_result < 0) {
2717 // The equivalent of this is an error for .svx files,
2718 // but Walls explicitly documents that a repeated exact
2719 // fix warns and uses the coordinates of the first fix.
2720 compile_diagnostic(DIAG_WARN|DIAG_WORD, /*Station already fixed or equated to a fixed point*/46);
2721 } else {
2722 compile_diagnostic(DIAG_WARN|DIAG_WORD, /*Station already fixed at the same coordinates*/55);
2724 compile_diagnostic_pfx(DIAG_INFO, name, /*Previously fixed or equated here*/493);
2725 set_pos(&fp);
2727 break;
2730 if (var_xy == HUGE_REAL && var_z == HUGE_REAL) {
2731 // We've been asked to not fix horizontally or vertically!
2732 break;
2735 // If only one variance is exact, make it 1mm instead. FIXME
2736 if (var_xy == 0.0) var_xy = 1e-6;
2737 if (var_z == 0.0) var_z = 1e-6;
2739 // We don't currently handle fixing only horizontally or only
2740 // vertically so for now just assign a large variance. FIXME
2741 if (var_xy == HUGE_REAL) var_xy = 1e6;
2742 if (var_z == HUGE_REAL) var_z = 1e6;
2744 fix_station_with_variance(name, coords, var_xy, var_xy, var_z
2745 #ifndef NO_COVARIANCES
2746 , 0.0, 0.0, 0.0
2747 #endif
2749 break;
2751 case WALLS_CMD_FLAG: {
2752 // The flag comes after a list of stations, so to avoid having to
2753 // store a list of the stations we note the position, scan ahead
2754 // and parse the flag, then come back and actually parse the
2755 // stations and apply the flag.
2756 skipblanks();
2757 if (isEol(ch) || isComm(ch)) {
2758 // Just "#FLAG" with no arguments clears the default flag.
2759 fix_station_flags = 0;
2760 break;
2762 bool setting_default_flag = isWallsSlash(ch);
2764 filepos fp;
2765 get_pos(&fp);
2766 while (!isWallsSlash(ch)) {
2767 if (isComm(ch) || isEol(ch)) {
2768 // The flag name is not required (it "can *optionally*
2769 // follow the list of station names" (my emphasis).
2770 // Elsewhere the docs say:
2772 // Another automatically assigned list item is
2773 // "{Unnamed Flag}", which is present only if
2774 // stations appear on a #Flag directive without a
2775 // name parameter -- for example, "#FLAG A1 A2 A3".
2776 // There's really no reason to have any of those,
2777 // although Walls has always allowed them.
2779 // These seem to occur in real data, but we ignore
2780 // unknown flag names, so it seems reasonable to just
2781 // ignore these too. Or maybe we should warn? FIXME
2782 process_eol();
2783 goto next_line;
2785 nextch();
2787 nextch();
2788 int station_flags = parse_walls_flags(false);
2790 if (setting_default_flag) {
2791 fix_station_flags = station_flags;
2792 break;
2795 // Suppress "unused fixed point" warnings for stations in #flag.
2796 station_flags |= BIT(SFLAGS_USED);
2798 // Go back and read stations and apply the flags.
2799 filepos fp_end;
2800 get_pos(&fp_end);
2801 set_pos(&fp);
2802 // It seems / and \ can't be used in #flag station names?
2803 // FIXME: Need to actually test this with Walls.
2804 int save_translate_slash = pcs->Translate['/'];
2805 int save_translate_bslash = pcs->Translate['\\'];
2806 pcs->Translate['/'] = 0;
2807 pcs->Translate['\\'] = 0;
2808 while (!isWallsSlash(ch)) {
2809 prefix *name = read_walls_station(p_walls_options->prefix,
2810 false, NULL);
2811 name->sflags |= station_flags;
2812 skipblanks();
2814 pcs->Translate['/'] = save_translate_slash;
2815 pcs->Translate['\\'] = save_translate_bslash;
2816 set_pos(&fp_end);
2817 break;
2819 case WALLS_CMD_PREFIX:
2820 case WALLS_CMD_PREFIX2:
2821 case WALLS_CMD_PREFIX3: {
2822 char *new_prefix = read_walls_prefix();
2823 int i = (int)WALLS_CMD_PREFIX3 - (int)directive;
2824 osfree(p_walls_options->prefix[i]);
2825 p_walls_options->prefix[i] = new_prefix;
2826 skipblanks();
2827 if (!isEol(ch) && !isComm(ch)) {
2828 // Walls seems to ignore anything after the prefix so make this a
2829 // warning not an error so we can process existing Walls
2830 // datasets (e.g. the Mammoth dataset reportedly has `#prefix
2831 // 4136 Lucys domes`).
2832 compile_diagnostic(DIAG_WARN|DIAG_SKIP|DIAG_TAIL,
2833 /*End of line not blank*/15);
2835 break;
2837 case WALLS_CMD_NOTE: {
2838 // A text note attached to a station - ignore for now except we
2839 // read the station name and flag it to avoid an "unused fixed
2840 // point" warning.
2841 prefix *name = read_walls_station(p_walls_options->prefix,
2842 false, NULL);
2843 name->sflags |= BIT(SFLAGS_USED);
2844 skipline();
2845 break;
2847 case WALLS_CMD_SEGMENT:
2848 parse_walls_segment(&p_walls_options->compass_dat_flags);
2849 break;
2850 case WALLS_CMD_SYMBOL:
2851 // Now to draw symbols. Not really appropriate here as this is
2852 // presentation information, so we just ignore it.
2853 skipline();
2854 break;
2855 case WALLS_CMD_NULL:
2856 // FIXME it's a "directive" in Walls-speak.
2857 compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Unknown command ā€œ%sā€*/12, s_str(&token));
2858 break;
2861 if (!s_empty(&line)) {
2862 // Revert to reading from the file.
2863 fclose(file.fh);
2864 s_free(&line);
2865 file = file_store;
2866 ch = ch_store;
2869 process_eol();
2872 clear_last_leg();
2874 if (walls_macros) {
2875 // Clear all macros set in this SRV file.
2876 for (unsigned i = 0; i < WALLS_MACRO_HASH_SIZE; ++i) {
2877 walls_macro *p = walls_macros[i];
2878 while (p) {
2879 walls_macro *to_free = p;
2880 p = p->next;
2881 osfree(to_free);
2883 walls_macros[i] = NULL;
2887 while (p_walls_options->explicit) {
2888 // FIXME: Walls quietly allows SAVE without a corresponding RESTORE, but
2889 // probably worth at least a warning here.
2890 pop_walls_options();
2892 if (standalone) pop_walls_options();
2895 typedef enum {
2896 WALLS_WPJ_CMD_BOOK,
2897 WALLS_WPJ_CMD_ENDBOOK,
2898 WALLS_WPJ_CMD_NAME,
2899 WALLS_WPJ_CMD_OPTIONS,
2900 WALLS_WPJ_CMD_PATH,
2901 WALLS_WPJ_CMD_REF,
2902 WALLS_WPJ_CMD_STATUS,
2903 WALLS_WPJ_CMD_SURVEY,
2904 WALLS_WPJ_CMD_NULL = -1
2905 } walls_wpj_cmd;
2907 static const sztok walls_wpj_cmd_tab[] = {
2908 {"BOOK", WALLS_WPJ_CMD_BOOK},
2909 {"ENDBOOK", WALLS_WPJ_CMD_ENDBOOK},
2910 {"NAME", WALLS_WPJ_CMD_NAME},
2911 {"OPTIONS", WALLS_WPJ_CMD_OPTIONS},
2912 {"PATH", WALLS_WPJ_CMD_PATH},
2913 {"REF", WALLS_WPJ_CMD_REF},
2914 {"STATUS", WALLS_WPJ_CMD_STATUS},
2915 {"SURVEY", WALLS_WPJ_CMD_SURVEY},
2916 {NULL, WALLS_WPJ_CMD_NULL}
2919 static void
2920 data_file_walls_wpj(void)
2922 // .STATUS is a decimal integer which is a bitmap of flags. Meanings
2923 // cribbed from dewalls and from trying Walls32.exe on simple testcases:
2924 enum {
2925 // Set if a branch is expanded in the UI - we can ignore.
2926 WALLS_WPJ_STATUS_BOOK_OPEN = 0x000001,
2927 // Detached items are not processed as part of higher level items.
2928 WALLS_WPJ_STATUS_DETACHED = 0x000002,
2929 // This bit appears to be unused/no longer used. Setting it by
2930 // modifying a WPJ file in a text editor and then loading it into Walls
2931 // and forcing saving clears this bit.
2932 WALLS_WPJ_STATUS_UNUSED_BIT2 = 0x000004,
2933 // We ignore segments currently so ignore this too.
2934 WALLS_WPJ_STATUS_NAME_DEFINES_SEGMENT = 0x000008,
2935 // Controls the units used in reporting data - we can ignore.
2936 WALLS_WPJ_STATUS_REVIEW_UNITS_FEET = 0x000010,
2937 // Comments in dewalls-java suggests this is no longer used.
2938 WALLS_WPJ_STATUS_UNUSED_BIT5 = 0x000020,
2939 // These bits are handled via WALLS_WPF_STATUS_*_SHIFT defined below.
2940 WALLS_WPJ_STATUS_TRISTATES_MASK = 0x00ffc0,
2941 // Attached file of arbitrary type (we can just ignore):
2942 WALLS_WPJ_STATUS_TYPE_OTHER = 0x010000,
2943 // We can ignore these:
2944 WALLS_WPJ_STATUS_EDIT_ON_LAUNCH = 0x020000,
2945 WALLS_WPJ_STATUS_OPEN_ON_LAUNCH = 0x040000,
2946 WALLS_WPJ_STATUS_DEFAULT_VIEW_MASK = 0x380000,
2947 WALLS_WPJ_STATUS_PROCESS_SVG = 0x400000,
2950 // The values of the shifted bits for the tristate values:
2951 enum {
2952 // - 0b00 Inherit value from parent
2953 WALLS_WPJ_TRISTATE_INHERIT = 0,
2954 // - 0b01 Off
2955 WALLS_WPJ_TRISTATE_OFF = 1,
2956 // - 0b10 On
2957 WALLS_WPJ_TRISTATE_ON = 2,
2958 // - 0b11 <not used>
2961 // Shifts to extract these values:
2963 // ((status >> WALLS_WPJ_STATUS_*_SHIFT) & 3)
2965 // should be one of the WALLS_WPJ_TRISTATE_* values above.
2966 enum {
2967 // Controls whether to use/inherit .REF.
2968 WALLS_WPJ_STATUS_USE_REFERENCE_SHIFT = 6,
2969 // Whether to calculate declination from .REF locations and #DATE.
2970 WALLS_WPJ_STATUS_DECLINATION_AUTO_SHIFT = 8,
2971 // Whether to apply grid convergence. We ignore this as we always
2972 // correct for grid convergence when calculating declinations, and
2973 // don't if we aren't.
2974 WALLS_WPJ_STATUS_UTM_GPS_RELATIVE_SHIFT = 10,
2975 // AIUI these just control distributing loop misclosure:
2976 WALLS_WPJ_STATUS_PRESERVE_PLUMB_ORIENTATION_SHIFT = 12,
2977 WALLS_WPJ_STATUS_PRESERVE_PLUMB_LENGTH_SHIFT = 14,
2980 // FIXME: Do any of these variables need to be volatile to protect them
2981 // from longjmp()? GCC isn't warning about them...
2982 char *pth = path_from_fnm(file.filename);
2984 walls_initialise_settings();
2985 walls_reset();
2987 // FIXME: We need to update the separator_map to reflect what can be
2988 // SPECIAL_NAMES. Or should we use the Compass approach and base this
2989 // on what's actually used? The first approach would pick the separator
2990 // from {':', ';', ',', '#', space}; the latter would pick '.' if
2991 // the documentation station naming recommendations were followed.
2992 update_output_separator();
2994 /* We need to update separator_map so we don't pick a separator character
2995 * which occurs in a station name. However Compass DAT allows everything
2996 * >= ASCII char 33 except 127 in station names so if we just added all
2997 * the valid station name characters we'd always pick space as the
2998 * separator for any dataset which included a DAT file, yet in practice
2999 * '.' is never used in any of the sample DAT files I've seen. So
3000 * instead we scan the characters actually used in station names when we
3001 * process CompassDATFr and CompassDATTo fields. (FIXME)
3004 // Start from the location of this WPJ.
3005 s_append(&p_walls_options->path, pth);
3007 if (setjmp(jbSkipLine)) {
3008 // Recover from errors in nested functions by longjmp() to here.
3009 skipline();
3010 process_eol();
3013 int status = -1;
3014 long name_lpos = -1;
3015 unsigned name_lineno = 0;
3016 filepos fp_name;
3017 string name = S_INIT;
3019 walls_ref.x = walls_ref.y = walls_ref.z = HUGE_VAL;
3020 walls_ref.zone = 0;
3022 int depth = 0;
3023 int detached_nest_level = 0;
3024 bool in_survey = false;
3025 while (!ferror(file.fh)) {
3026 walls_wpj_cmd tok = WALLS_WPJ_CMD_NULL;
3027 skipblanks();
3028 if (ch != '.') {
3029 if (ch == EOF) {
3030 if (!in_survey) break;
3031 // Ensure that a survey at the end of the WPJ file is processed
3032 // even if not contained in a book. Not seen in example data, so
3033 // not sure if this is actually valid but this code will just be
3034 // unused if it isn't.
3035 goto process_entry;
3037 // If the line isn't blank or a comment then process_eol() will
3038 // issue a suitable error.
3039 process_eol();
3040 continue;
3043 nextch();
3044 get_token_no_blanks();
3045 tok = match_tok(walls_wpj_cmd_tab, TABSIZE(walls_wpj_cmd_tab));
3046 if (detached_nest_level) {
3047 switch (tok) {
3048 case WALLS_WPJ_CMD_BOOK:
3049 ++detached_nest_level;
3050 skipline();
3051 break;
3052 case WALLS_WPJ_CMD_ENDBOOK:
3053 --detached_nest_level;
3054 break;
3055 default:
3056 // Ignore everything else.
3057 skipline();
3058 break;
3060 process_eol();
3061 continue;
3063 if (in_survey &&
3064 (tok == WALLS_WPJ_CMD_SURVEY ||
3065 tok == WALLS_WPJ_CMD_BOOK ||
3066 tok == WALLS_WPJ_CMD_ENDBOOK)) {
3067 process_entry:
3068 // Process the current entry.
3070 // A quirk is that the root item is flagged with
3071 // WALLS_WPJ_STATUS_DETACHED (seems the flag might be more like
3072 // "don't draw a connecting line left from here").
3073 if ((status & WALLS_WPJ_STATUS_DETACHED) && depth > 0) {
3074 // Detached survey.
3075 //printf("*** Detached survey\n");
3076 goto detached_or_not_srv;
3078 if ((status & WALLS_WPJ_STATUS_TYPE_OTHER)) {
3079 // Attached file of arbitrary type.
3080 goto detached_or_not_srv;
3082 if (s_empty(&name)) {
3083 printf("*** in_survey but no/empty NAME\n");
3084 goto detached_or_not_srv;
3087 // Include SRV file.
3088 #if 0
3089 printf("+++ %s %s .SRV :%s:%s:%s\n", s_str(&p_walls_options->path), s_str(&name),
3090 p_walls_options->prefix[0] ? p_walls_options->prefix[0] : "",
3091 p_walls_options->prefix[1] ? p_walls_options->prefix[1] : "",
3092 p_walls_options->prefix[2] ? p_walls_options->prefix[2] : "");
3093 #endif
3094 char *filename;
3095 FILE *fh = fopen_portable(s_str(&p_walls_options->path),
3096 s_str(&name), "srv", "rb", &filename);
3097 if (fh == NULL)
3098 fh = fopen_portable(s_str(&p_walls_options->path),
3099 s_str(&name), "SRV", "rb", &filename);
3101 if (fh == NULL) {
3102 // Report the diagnostic at the location of the ".NAME".
3103 unsigned save_line = file.line;
3104 long save_lpos = file.lpos;
3105 filepos fp;
3106 get_pos(&fp);
3107 set_pos(&fp_name);
3108 file.lpos = name_lpos;
3109 file.line = name_lineno;
3110 file.prev_line_len = 0; // Not used for Walls WPJ.
3111 // Report the resolved path. FIXME: Maybe we should use
3112 // full_file in the fopen_portable() call above so things
3113 // align better?
3114 string full_file = S_INIT;
3115 s_appends(&full_file, &p_walls_options->path);
3116 s_appends(&full_file, &name);
3117 s_append(&full_file, ".SRV");
3118 if (!fDirectory(s_str(&p_walls_options->path))) {
3119 // Walls appears to quietly ignore file if the
3120 // directory does not exist, but it seems worth
3121 // warning about at least.
3123 // FIXME: This should take case into account like
3124 // opening the file does.
3125 compile_diagnostic(DIAG_WARN|DIAG_TAIL, /*Couldnā€™t open file ā€œ%sā€*/24, s_str(&full_file));
3126 } else {
3127 compile_diagnostic(DIAG_ERR|DIAG_TAIL, /*Couldnā€™t open file ā€œ%sā€*/24, s_str(&full_file));
3129 s_free(&full_file);
3130 set_pos(&fp);
3131 file.line = save_line;
3132 file.lpos = save_lpos;
3133 goto srv_not_found;
3137 parse file_store = file;
3138 int ch_store = ch;
3139 if (file.fh) file.parent = &file_store;
3140 file.fh = fh;
3141 file.filename = filename;
3142 file.line = 1;
3143 file.lpos = 0;
3144 file.reported_where = false;
3145 file.prev_line_len = 0;
3146 nextch();
3148 using_data_file(file.filename);
3150 push_walls_options();
3151 walls_swap_macro_tables();
3152 data_file_walls_srv();
3153 walls_swap_macro_tables();
3154 pop_walls_options();
3156 if (ferror(file.fh))
3157 fatalerror_in_file(file.filename, 0, /*Error reading file*/18);
3159 (void)fclose(file.fh);
3161 /* don't free this - it may be pointed to by prefix.file */
3162 /* osfree(file.filename); */
3164 file = file_store;
3165 ch = ch_store;
3168 srv_not_found:
3169 status = -1;
3170 s_clear(&name);
3171 //s_clear(&path);
3172 walls_ref.x = walls_ref.y = walls_ref.z = HUGE_VAL;
3173 walls_ref.zone = 0;
3174 detached_or_not_srv:
3175 pop_walls_options();
3176 in_survey = false;
3177 // Exit if at EOF.
3178 if (tok == WALLS_WPJ_CMD_NULL) break;
3181 switch (tok) {
3182 case WALLS_WPJ_CMD_BOOK:
3183 ++depth;
3184 push_walls_options();
3185 in_survey = false;
3186 skipline();
3187 break;
3188 case WALLS_WPJ_CMD_SURVEY:
3189 push_walls_options(); // FIXME: Eliminate redundant push and pop inside data_file_walls_srv()
3190 in_survey = true;
3191 skipline();
3192 break;
3193 case WALLS_WPJ_CMD_ENDBOOK:
3194 if (depth == 0) {
3195 compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*No matching %s*/192, ".BOOK");
3197 --depth;
3198 pop_walls_options();
3199 in_survey = false;
3200 break;
3201 case WALLS_WPJ_CMD_OPTIONS:
3202 parse_options();
3203 break;
3204 case WALLS_WPJ_CMD_PATH: {
3205 skipblanks();
3206 if (!isEol(ch)) {
3207 while (!isEol(ch)) {
3208 if (ch == '\\') {
3209 ch = FNM_SEP_LEV;
3211 s_appendch(&p_walls_options->path, ch);
3212 nextch();
3214 // Ensure path ends with a directory separator.
3215 if (s_back(&p_walls_options->path) != FNM_SEP_LEV) {
3216 s_appendch(&p_walls_options->path, FNM_SEP_LEV);
3219 //printf("PATH: %s\n", s_str(&p_walls_options->path));
3220 break;
3222 case WALLS_WPJ_CMD_REF:
3223 walls_ref.y = read_numeric(false);
3224 walls_ref.x = read_numeric(false);
3225 // Walls supports UPS zones and uses -61 and 61 to specify them.
3226 walls_ref.zone = read_int(-61, 61);
3228 // Ignore pre-computed convergence as we compute that for ourselves.
3229 (void)read_numeric(false);
3231 walls_ref.z = read_numeric(false);
3233 // Ignore field which seems to be a bitmask.
3235 // From experimenting with Walls32.exe and looking at the saved
3236 // .wpj file contents, the bottom two bits (mask 0x03) seem to
3237 // encode the format (d/dm/dms) that the Walls32.exe UI uses to
3238 // display/enter lat/long.
3240 // Bit 0x04 seems to be "West of meridian?" and bit 0x08 seems
3241 // to be "South of equator?" (and the lat and long seem to be
3242 // stored without signs).
3244 // Other bits seem to be unused so it seems we can safely ignore
3245 // this field.
3246 (void)read_uint();
3248 // Ignore same location in lat/long.
3249 (void)read_numeric(false);
3250 (void)read_numeric(false);
3251 (void)read_numeric(false);
3252 (void)read_numeric(false);
3253 (void)read_numeric(false);
3254 (void)read_numeric(false);
3256 // Ignore integer index for the datum (e.g. 27 for "WGS 1984").
3257 // The string names seem more likely to have not changed over time.
3258 (void)read_uint();
3260 skipblanks();
3261 filepos fp;
3262 get_pos(&fp);
3263 string datum_str = S_INIT;
3264 read_string(&datum_str);
3265 int datum = img_parse_compass_datum_string(s_str(&datum_str),
3266 s_len(&datum_str));
3267 if (datum == 0) {
3268 // Walls has different name strings to Compass for some datums.
3269 if (S_EQ(&datum_str, "NAD27 CONUS")) {
3270 // FIXME Assuming this is right. Walls seems to have 6
3271 // variant NAD27 datums, not sure what's going on.
3272 datum = img_DATUM_NAD27;
3273 } else if (S_EQ(&datum_str, "NAD83")) {
3274 datum = img_DATUM_NAD83;
3275 } else if (S_EQ(&datum_str, "Geodetic Datum `49")) {
3276 datum = img_DATUM_NZGD49;
3277 } else if (S_EQ(&datum_str, "Hu-Tzu-Shan")) {
3278 datum = img_DATUM_HUTZUSHAN1950;
3279 } else {
3280 compile_diagnostic(DIAG_ERR|DIAG_FROM(fp),
3281 /*Datum ā€œ%sā€ not supported*/503,
3282 s_str(&datum_str));
3283 // Set a datum to avoid causing problems converting
3284 // coordinates.
3285 datum = img_DATUM_WGS84;
3288 s_free(&datum_str);
3290 if (datum && walls_ref.zone && abs(walls_ref.zone) <= 60) {
3291 char *proj_str = img_compass_utm_proj_str(datum,
3292 walls_ref.zone);
3293 set_declination_location(walls_ref.x, walls_ref.y, walls_ref.z,
3294 proj_str);
3295 if (!pcs->proj_str) {
3296 pcs->proj_str = proj_str;
3297 if (!proj_str_out) {
3298 proj_str_out = osstrdup(proj_str);
3300 pcs->input_convergence = HUGE_REAL;
3301 } else {
3302 osfree(proj_str);
3304 } else if (datum == img_DATUM_WGS84 && abs(walls_ref.zone) == 61) {
3305 // Polar UPS zones.
3306 const char *proj_str =
3307 (walls_ref.zone > 0 ? "EPSG:5041" : "EPSG:5042");
3308 set_declination_location(walls_ref.x, walls_ref.y, walls_ref.z,
3309 proj_str);
3310 if (!pcs->proj_str) {
3311 pcs->proj_str = osstrdup(proj_str);
3312 if (!proj_str_out) {
3313 proj_str_out = osstrdup(proj_str);
3315 pcs->input_convergence = HUGE_REAL;
3319 walls_ref.img_datum_code = datum;
3320 break;
3321 case WALLS_WPJ_CMD_STATUS:
3322 status = read_uint();
3323 // A quirk is that the root item is flagged with
3324 // WALLS_WPJ_STATUS_DETACHED (seems the flag might be more like
3325 // "don't draw a connecting line left from here").
3326 if ((status & WALLS_WPJ_STATUS_DETACHED) && !in_survey && depth > 1) {
3327 //printf("Detached book (status %d = 0x%06x)\n", status, status);
3328 // Detached BOOK - resume at the corresponding ENDBOOK.
3329 s_clear(&name);
3330 pop_walls_options();
3331 detached_nest_level = 1;
3333 break;
3334 case WALLS_WPJ_CMD_NAME:
3335 if (!s_empty(&name)) {
3336 // FIXME: NAME already set
3337 s_clear(&name);
3339 skipblanks();
3340 get_pos(&fp_name);
3341 name_lpos = file.lpos;
3342 name_lineno = file.line;
3343 while (!isEol(ch)) {
3344 s_appendch(&name, ch);
3345 nextch();
3347 break;
3348 case WALLS_WPJ_CMD_NULL:
3349 compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Unknown command ā€œ%sā€*/12, s_str(&token));
3351 process_eol();
3354 osfree(pth);
3356 pop_walls_options();
3359 static void
3360 data_file_survex(void)
3362 int begin_lineno_store = pcs->begin_lineno;
3363 pcs->begin_lineno = 0;
3365 if (ch == 0xef) {
3366 /* Maybe a UTF-8 "BOM" - skip if so. */
3367 if (nextch() == 0xbb && nextch() == 0xbf) {
3368 nextch();
3369 file.lpos = 3;
3370 } else {
3371 rewind(file.fh);
3372 ch = 0xef;
3376 if (setjmp(jbSkipLine)) {
3377 // Recover from errors in nested functions by longjmp() to here.
3378 skipline();
3379 process_eol();
3382 while (ch != EOF && !ferror(file.fh)) {
3383 if (!process_non_data_line()) {
3384 f_export_ok = false;
3385 switch (pcs->style) {
3386 case STYLE_NORMAL:
3387 case STYLE_DIVING:
3388 case STYLE_CYLPOLAR:
3389 data_normal();
3390 break;
3391 case STYLE_CARTESIAN:
3392 data_cartesian();
3393 break;
3394 case STYLE_PASSAGE:
3395 data_passage();
3396 break;
3397 case STYLE_NOSURVEY:
3398 data_nosurvey();
3399 break;
3400 case STYLE_IGNORE:
3401 data_ignore();
3402 break;
3403 default:
3404 BUG("bad style");
3408 clear_last_leg();
3410 /* don't allow *BEGIN at the end of a file, then *EXPORT in the
3411 * including file */
3412 f_export_ok = false;
3414 if (pcs->begin_lineno) {
3415 /* TRANSLATORS: %s and %s are replaced with e.g. BEGIN and END
3416 * or END and BEGIN or #[ and #] */
3417 error_in_file(file.filename, pcs->begin_lineno,
3418 /*%s with no matching %s in this file*/23,
3419 "BEGIN", "END");
3420 /* Implicitly close any unclosed BEGINs from this file */
3421 do {
3422 pop_settings();
3423 } while (pcs->begin_lineno);
3426 pcs->begin_lineno = begin_lineno_store;
3429 #define EXT3(C1, C2, C3) (((C3) << 16) | ((C2) << 8) | (C1))
3431 extern void
3432 data_file(const char *pth, const char *fnm)
3434 parse file_store;
3435 unsigned ext = 0;
3438 char *filename;
3439 FILE *fh;
3440 size_t len;
3442 if (!pth) {
3443 /* file specified on command line - don't do special translation */
3444 fh = fopenWithPthAndExt(pth, fnm, EXT_SVX_DATA, "rb", &filename);
3445 } else {
3446 fh = fopen_portable(pth, fnm, EXT_SVX_DATA, "rb", &filename);
3449 if (fh == NULL) {
3450 compile_error_string(fnm, /*Couldnā€™t open file ā€œ%sā€*/24, fnm);
3451 return;
3454 len = strlen(filename);
3455 if (len > 4 && filename[len - 4] == FNM_SEP_EXT) {
3456 /* Read extension and pack into ext. */
3457 for (int i = 1; i < 4; ++i) {
3458 unsigned char ext_ch = filename[len - i];
3459 ext = (ext << 8) | tolower(ext_ch);
3463 file_store = file;
3464 if (file.fh) file.parent = &file_store;
3465 file.fh = fh;
3466 file.filename = filename;
3467 file.line = 1;
3468 file.lpos = 0;
3469 file.reported_where = false;
3470 file.prev_line_len = 0;
3471 nextch();
3474 using_data_file(file.filename);
3476 switch (ext) {
3477 case EXT3('d', 'a', 't'):
3478 // Compass survey data.
3479 data_file_compass_dat();
3480 break;
3481 case EXT3('c', 'l', 'p'):
3482 // Compass closed data. The format of .clp is the same as .dat,
3483 // but it contains loop-closed data. This might be useful to
3484 // read if you want to keep existing stations at the same
3485 // adjusted positions, for example to be able to draw extensions
3486 // on an existing drawn-up survey. Or if you managed to lose the
3487 // original .dat but still have the .clp.
3488 data_file_compass_clp();
3489 break;
3490 case EXT3('m', 'a', 'k'):
3491 // Compass project file.
3492 data_file_compass_mak();
3493 break;
3494 case EXT3('s', 'r', 'v'):
3495 // Walls survey data.
3496 data_file_walls_srv();
3497 break;
3498 case EXT3('w', 'p', 'j'):
3499 // Walls project file.
3500 data_file_walls_wpj();
3501 break;
3502 default:
3503 // Native Survex data.
3504 data_file_survex();
3505 break;
3508 if (ferror(file.fh))
3509 fatalerror_in_file(file.filename, 0, /*Error reading file*/18);
3511 (void)fclose(file.fh);
3513 file = file_store;
3515 /* don't free this - it may be pointed to by prefix.file */
3516 /* osfree(file.filename); */
3519 static real
3520 mod2pi(real a)
3522 return a - floor(a / (2 * M_PI)) * (2 * M_PI);
3525 static real
3526 handle_plumb(clino_type *p_ctype)
3528 typedef enum {
3529 CLINO_NULL=-1, CLINO_UP, CLINO_DOWN, CLINO_LEVEL
3530 } clino_tok;
3531 static const sztok clino_tab[] = {
3532 {"D", CLINO_DOWN},
3533 {"DOWN", CLINO_DOWN},
3534 {"H", CLINO_LEVEL},
3535 {"LEVEL", CLINO_LEVEL},
3536 {"U", CLINO_UP},
3537 {"UP", CLINO_UP},
3538 {NULL, CLINO_NULL}
3540 static const real clinos[] = {(real)M_PI_2, (real)(-M_PI_2), (real)0.0};
3541 clino_tok tok;
3543 skipblanks();
3544 if (isalpha(ch)) {
3545 filepos fp;
3546 get_pos(&fp);
3547 get_token_legacy();
3548 tok = match_tok(clino_tab, TABSIZE(clino_tab));
3549 if (tok != CLINO_NULL) {
3550 do_legacy_token_warning();
3551 *p_ctype = (tok == CLINO_LEVEL ? CTYPE_HORIZ : CTYPE_PLUMB);
3552 return clinos[tok];
3554 set_pos(&fp);
3555 } else if (isSign(ch)) {
3556 int chOld = ch;
3557 nextch();
3558 if (toupper(ch) == 'V') {
3559 nextch();
3560 do_legacy_token_warning();
3561 *p_ctype = CTYPE_PLUMB;
3562 return (!isMinus(chOld) ? M_PI_2 : -M_PI_2);
3565 if (isOmit(chOld)) {
3566 *p_ctype = CTYPE_OMIT;
3567 /* no clino reading, so assume 0 with large sd */
3568 return (real)0.0;
3570 } else if (isOmit(ch)) {
3571 /* OMIT char may not be a SIGN char too so we need to check here as
3572 * well as above... */
3573 nextch();
3574 *p_ctype = CTYPE_OMIT;
3575 /* no clino reading, so assume 0 with large sd */
3576 return (real)0.0;
3578 return HUGE_REAL;
3581 static void
3582 warn_readings_differ(int msgno, real diff, int units,
3583 reading r_fore, reading r_back)
3585 char buf[64];
3586 char *p;
3587 diff /= get_units_factor(units);
3588 snprintf(buf, sizeof(buf), "%.2f", fabs(diff));
3589 for (p = buf; *p; ++p) {
3590 if (*p == '.') {
3591 char *z = p;
3592 while (*++p) {
3593 if (*p != '0') z = p + 1;
3595 p = z;
3596 break;
3599 strcpy(p, get_units_string(units));
3600 // FIXME: Highlight r_fore too.
3601 (void)r_fore;
3602 compile_diagnostic_reading(DIAG_WARN, r_back, msgno, buf);
3605 // If one (or both) compass readings are given, return Comp or BackComp
3606 // so we can report plumb legs with compass readings. If neither are,
3607 // return End.
3608 static reading
3609 handle_comp_units(void)
3611 reading which_comp = End;
3612 if (VAL(Comp) != HUGE_REAL) {
3613 which_comp = Comp;
3614 VAL(Comp) *= pcs->units[Q_BEARING];
3615 if (VAL(Comp) < (real)0.0 || VAL(Comp) - M_PI * 2.0 > EPSILON) {
3616 /* TRANSLATORS: Suspicious means something like 410 degrees or -20
3617 * degrees */
3618 compile_diagnostic_reading(DIAG_WARN, Comp, /*Suspicious compass reading*/59);
3619 VAL(Comp) = mod2pi(VAL(Comp));
3622 if (VAL(BackComp) != HUGE_REAL) {
3623 if (which_comp == End) which_comp = BackComp;
3624 VAL(BackComp) *= pcs->units[Q_BACKBEARING];
3625 if (VAL(BackComp) < (real)0.0 || VAL(BackComp) - M_PI * 2.0 > EPSILON) {
3626 /* FIXME: different message for BackComp? */
3627 compile_diagnostic_reading(DIAG_WARN, BackComp, /*Suspicious compass reading*/59);
3628 VAL(BackComp) = mod2pi(VAL(BackComp));
3631 return which_comp;
3634 static real
3635 calculate_convergence_lonlat(const char *proj_str, double lon, double lat)
3637 // PROJ < 8.1.0 dereferences the context without a NULL check inside
3638 // proj_create_ellipsoidal_2D_cs() but PJ_DEFAULT_CTX is really just
3639 // NULL so for affected PROJ versions we create a context temporarily to
3640 // avoid a segmentation fault.
3641 PJ_CONTEXT * ctx = PJ_DEFAULT_CTX;
3642 #if PROJ_VERSION_MAJOR < 8 || \
3643 (PROJ_VERSION_MAJOR == 8 && PROJ_VERSION_MINOR < 1)
3644 ctx = proj_context_create();
3645 #endif
3646 PJ * pj = proj_create(ctx, proj_str);
3647 PJ_COORD lp;
3648 lp.lp.lam = lon;
3649 lp.lp.phi = lat;
3650 #if PROJ_VERSION_MAJOR < 8 || \
3651 (PROJ_VERSION_MAJOR == 8 && PROJ_VERSION_MINOR < 2)
3652 /* Code adapted from fix in PROJ 8.2.0 to make proj_factors() work in
3653 * cases we need (e.g. a CRS specified as "EPSG:<number>").
3655 switch (proj_get_type(pj)) {
3656 case PJ_TYPE_PROJECTED_CRS: {
3657 /* If it is a projected CRS, then compute the factors on the
3658 * conversion associated to it. We need to start from a temporary
3659 * geographic CRS using the same datum as the one of the projected
3660 * CRS, and with input coordinates being in longitude, latitude
3661 * order in radian, to be consistent with the expectations of the
3662 * lp input parameter.
3665 PJ * geodetic_crs = proj_get_source_crs(ctx, pj);
3666 if (!geodetic_crs)
3667 break;
3668 PJ * datum = proj_crs_get_datum(ctx, geodetic_crs);
3669 #if PROJ_VERSION_MAJOR == 8 || \
3670 (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
3671 /* PROJ 7.2.0 upgraded to EPSG 10.x which added the concept
3672 * of a datum ensemble, and this version of PROJ also added
3673 * an API to deal with these.
3675 * If we're using PROJ < 7.2.0 then its EPSG database won't
3676 * have datum ensembles, so we don't need any code to handle
3677 * them.
3679 if (!datum) {
3680 datum = proj_crs_get_datum_ensemble(ctx, geodetic_crs);
3682 #endif
3683 PJ * cs = proj_create_ellipsoidal_2D_cs(
3684 ctx, PJ_ELLPS2D_LONGITUDE_LATITUDE, "Radian", 1.0);
3685 PJ * temp = proj_create_geographic_crs_from_datum(
3686 ctx, "unnamed crs", datum, cs);
3687 proj_destroy(datum);
3688 proj_destroy(cs);
3689 proj_destroy(geodetic_crs);
3690 PJ * newOp = proj_create_crs_to_crs_from_pj(ctx, temp, pj, NULL, NULL);
3691 proj_destroy(temp);
3692 if (newOp) {
3693 proj_destroy(pj);
3694 pj = newOp;
3696 break;
3698 default:
3699 break;
3701 #endif
3702 #if PROJ_VERSION_MAJOR < 9 || \
3703 (PROJ_VERSION_MAJOR == 9 && PROJ_VERSION_MINOR < 3)
3704 if (pj) {
3705 /* In PROJ < 9.3.0 proj_factors() returns a grid convergence which is
3706 * off by 90Ā° for a projected coordinate system with northing/easting
3707 * axis order. We can't copy over the fix for this in PROJ 9.3.0's
3708 * proj_factors() since it uses non-public PROJ functions, but
3709 * normalising the output order here works too.
3711 PJ* pj_norm = proj_normalize_for_visualization(PJ_DEFAULT_CTX, pj);
3712 proj_destroy(pj);
3713 pj = pj_norm;
3715 #endif
3716 PJ_FACTORS factors = proj_factors(pj, lp);
3717 proj_destroy(pj);
3718 #if PROJ_VERSION_MAJOR < 8 || \
3719 (PROJ_VERSION_MAJOR == 8 && PROJ_VERSION_MINOR < 1)
3720 proj_context_destroy(ctx);
3721 #endif
3722 return factors.meridian_convergence;
3725 static real
3726 calculate_convergence(const char *proj_str)
3728 return calculate_convergence_lonlat(proj_str, pcs->dec_lon, pcs->dec_lat);
3731 real
3732 calculate_convergence_xy(const char *proj_str, double x, double y, double z)
3734 /* Convert to WGS84 lat long. */
3735 PJ *transform = proj_create_crs_to_crs(PJ_DEFAULT_CTX,
3736 proj_str,
3737 WGS84_DATUM_STRING,
3738 NULL);
3739 if (transform) {
3740 /* Normalise the output order so x is longitude and y latitude - by
3741 * default new PROJ has them switched for EPSG:4326 which just seems
3742 * confusing.
3744 PJ* pj_norm = proj_normalize_for_visualization(PJ_DEFAULT_CTX,
3745 transform);
3746 proj_destroy(transform);
3747 transform = pj_norm;
3750 PJ_COORD coord = {{x, y, z, HUGE_VAL}};
3751 coord = proj_trans(transform, PJ_FWD, coord);
3752 x = coord.xyzt.x;
3753 y = coord.xyzt.y;
3754 z = coord.xyzt.z;
3756 if (x == HUGE_VAL || y == HUGE_VAL || z == HUGE_VAL) {
3757 compile_diagnostic(DIAG_ERR, /*Failed to convert coordinates: %s*/436,
3758 proj_context_errno_string(PJ_DEFAULT_CTX,
3759 proj_errno(transform)));
3760 /* Set dummy values which are finite. */
3761 x = y = z = 0;
3763 proj_destroy(transform);
3765 return calculate_convergence_lonlat(proj_str, rad(x), rad(y));
3768 static real
3769 get_convergence(void)
3771 if (pcs->convergence == HUGE_REAL) {
3772 /* Compute the convergence lazily. It only depends on the output
3773 * coordinate system so we can cache it for reuse to apply to
3774 * a declination value for a different date.
3776 if (!proj_str_out) {
3777 compile_diagnostic(DIAG_ERR, /*Output coordinate system not set*/488);
3778 return 0.0;// FIXME cache
3780 pcs->convergence = calculate_convergence(proj_str_out);
3782 return pcs->convergence;
3785 static real
3786 get_input_convergence(void)
3788 if (pcs->input_convergence == HUGE_REAL) {
3789 /* Compute the convergence lazily. It only depends on the input
3790 * coordinate system so we can cache it for reuse.
3792 if (!pcs->proj_str) {
3793 // TRANSLATORS: %s is replaced by the command that requires it, e.g.
3794 // *DECLINATION AUTO
3795 compile_diagnostic(DIAG_ERR, /*Input coordinate system must be specified for ā€œ%sā€*/301,
3796 "*CARTESIAN GRID");
3797 return 0.0;// FIXME cache
3799 pcs->input_convergence = calculate_convergence(pcs->proj_str);
3801 return pcs->input_convergence;
3804 static real
3805 get_declination(void)
3807 real declination;
3808 if (pcs->z[Q_DECLINATION] != HUGE_REAL) {
3809 declination = -pcs->z[Q_DECLINATION];
3810 } else if (pcs->declination != HUGE_REAL) {
3811 /* Cached value calculated for a previous compass reading taken on the
3812 * same date.
3814 declination = pcs->declination;
3815 } else {
3816 if (!pcs->meta || pcs->meta->days1 == -1) {
3817 compile_diagnostic(DIAG_WARN, /*No survey date specified - using 0 for magnetic declination*/304);
3818 declination = 0;
3819 } else {
3820 int avg_days = (pcs->meta->days1 + pcs->meta->days2) / 2;
3821 double dat = julian_date_from_days_since_1900(avg_days);
3822 /* thgeomag() takes (lat, lon, h, dat) - i.e. (y, x, z, date). */
3823 declination = thgeomag(pcs->dec_lat, pcs->dec_lon, pcs->dec_alt,
3824 dat);
3825 if (declination < pcs->min_declination) {
3826 pcs->min_declination = declination;
3827 pcs->min_declination_days = avg_days;
3829 if (declination > pcs->max_declination) {
3830 pcs->max_declination = declination;
3831 pcs->max_declination_days = avg_days;
3834 declination -= get_convergence();
3835 /* We cache the calculated declination as the calculation is relatively
3836 * expensive. We also cache an "assumed 0" answer so that we only
3837 * warn once per such survey rather than for every line with a compass
3838 * reading. */
3839 pcs->declination = declination;
3841 return declination;
3844 static real
3845 handle_compass(real *p_var)
3847 real compvar = VAR(Comp);
3848 real comp = VAL(Comp);
3849 real backcomp = VAL(BackComp);
3850 real declination = get_declination();
3851 if (comp != HUGE_REAL) {
3852 comp = (comp - pcs->z[Q_BEARING]) * pcs->sc[Q_BEARING];
3853 comp += declination;
3855 if (backcomp != HUGE_REAL) {
3856 backcomp = (backcomp - pcs->z[Q_BACKBEARING])
3857 * pcs->sc[Q_BACKBEARING];
3858 backcomp += declination;
3859 backcomp -= M_PI;
3860 if (comp != HUGE_REAL) {
3861 real diff = comp - backcomp;
3862 real adj = fabs(diff) > M_PI ? M_PI : 0;
3863 diff -= floor((diff + M_PI) / (2 * M_PI)) * 2 * M_PI;
3864 if (sqrd(diff / 3.0) > compvar + VAR(BackComp)) {
3865 /* fore and back readings differ by more than 3 sds */
3866 /* TRANSLATORS: %s is replaced by the amount the readings disagree
3867 * by, e.g. "2.5Ā°" or "3įµ". */
3868 warn_readings_differ(/*COMPASS reading and BACKCOMPASS reading disagree by %s*/98,
3869 diff, get_angle_units(Q_BEARING), Comp, BackComp);
3871 comp = (comp / compvar + backcomp / VAR(BackComp));
3872 compvar = (compvar + VAR(BackComp)) / 4;
3873 comp *= compvar;
3874 comp += adj;
3875 } else {
3876 comp = backcomp;
3877 compvar = VAR(BackComp);
3880 *p_var = compvar;
3881 return comp;
3884 static real
3885 handle_clino(q_quantity q, reading r, real val, bool percent, clino_type *p_ctype)
3887 bool range_0_180;
3888 real z;
3889 real diff_from_abs90;
3890 val *= pcs->units[q];
3891 /* percentage scale */
3892 if (percent) val = atan(val);
3893 /* We want to warn if there's a reading which it would be impossible
3894 * to have read from the instrument (e.g. on a -90 to 90 degree scale
3895 * you can't read "96" (it's probably a typo for "69"). However, the
3896 * gradient reading from a topofil is typically in the range 0 to 180,
3897 * with 90 being horizontal.
3899 * Really we should allow the valid range to be specified, but for now
3900 * we infer it from the zero error - if this is within 45 degrees of
3901 * 90 then we assume the range is 0 to 180.
3903 z = pcs->z[q];
3904 range_0_180 = (z > M_PI_4 && z < 3*M_PI_4);
3905 diff_from_abs90 = fabs(val) - M_PI_2;
3906 if (diff_from_abs90 > EPSILON) {
3907 if (!range_0_180) {
3908 int clino_units = get_angle_units(q);
3909 const char * units = get_units_string(clino_units);
3910 real right_angle = M_PI_2 / get_units_factor(clino_units);
3911 /* FIXME: different message for BackClino? */
3912 /* TRANSLATORS: %.f%s will be replaced with a right angle in the
3913 * units currently in use, e.g. "90Ā°" or "100įµ". And "absolute
3914 * value" means the reading ignoring the sign (so it might be
3915 * < -90Ā° or > 90Ā°. */
3916 compile_diagnostic_reading(DIAG_WARN, r, /*Clino reading over %.f%s (absolute value)*/51,
3917 right_angle, units);
3919 } else if (TSTBIT(pcs->infer, INFER_PLUMBS) &&
3920 diff_from_abs90 >= -EPSILON) {
3921 *p_ctype = CTYPE_INFERPLUMB;
3923 if (range_0_180 && *p_ctype != CTYPE_INFERPLUMB) {
3924 /* FIXME: Warning message not ideal... */
3925 if (val < 0.0 || val - M_PI > EPSILON) {
3926 int clino_units = get_angle_units(q);
3927 const char * units = get_units_string(clino_units);
3928 real right_angle = M_PI_2 / get_units_factor(clino_units);
3929 compile_diagnostic_reading(DIAG_WARN, r, /*Clino reading over %.f%s (absolute value)*/51,
3930 right_angle, units);
3933 return val;
3936 static int
3937 process_normal(prefix *fr, prefix *to, bool fToFirst,
3938 clino_type ctype, clino_type backctype)
3940 real tape = VAL(Tape);
3941 real clin = VAL(Clino);
3942 real backclin = VAL(BackClino);
3944 real dx, dy, dz;
3945 real vx, vy, vz;
3946 #ifndef NO_COVARIANCES
3947 real cxy, cyz, czx;
3948 #endif
3950 /* adjusted tape is negative -- probably the calibration is wrong */
3951 if (tape < (real)0.0) {
3952 /* TRANSLATE different message for topofil? */
3953 compile_diagnostic_reading(DIAG_WARN, Tape, /*Negative adjusted tape reading*/79);
3956 reading comp_given = handle_comp_units();
3958 if (ctype == CTYPE_READING) {
3959 clin = handle_clino(Q_GRADIENT, Clino, clin,
3960 pcs->f_clino_percent, &ctype);
3963 if (backctype == CTYPE_READING) {
3964 backclin = handle_clino(Q_BACKGRADIENT, BackClino, backclin,
3965 pcs->f_backclino_percent, &backctype);
3968 /* un-infer the plumb if the backsight was just a reading */
3969 if (ctype == CTYPE_INFERPLUMB && backctype == CTYPE_READING) {
3970 ctype = CTYPE_READING;
3973 if (ctype != CTYPE_OMIT && backctype != CTYPE_OMIT && ctype != backctype) {
3974 /* TRANSLATORS: In data with backsights, the user has tried to give a
3975 * plumb for the foresight and a clino reading for the backsight, or
3976 * something similar. */
3977 compile_error_reading_skip(Clino, /*CLINO and BACKCLINO readings must be of the same type*/84);
3978 return 0;
3981 if (ctype == CTYPE_PLUMB || ctype == CTYPE_INFERPLUMB ||
3982 backctype == CTYPE_PLUMB || backctype == CTYPE_INFERPLUMB) {
3983 /* plumbed */
3984 if (comp_given != End) {
3985 if (ctype == CTYPE_PLUMB ||
3986 (ctype == CTYPE_INFERPLUMB && VAL(Comp) != 0.0) ||
3987 backctype == CTYPE_PLUMB ||
3988 (backctype == CTYPE_INFERPLUMB &&
3989 (VAL(BackComp) != 0.0 &&
3990 fabs(VAL(BackComp) - M_PI) > EPSILON))) {
3991 /* TRANSLATORS: A "plumbed leg" is one measured using a plumbline
3992 * (a weight on a string). So the problem here is that the leg is
3993 * vertical, so a compass reading has no meaning! */
3994 compile_diagnostic_reading(DIAG_WARN, comp_given, /*Compass reading given on plumbed leg*/21);
3998 dx = dy = (real)0.0;
3999 if (ctype != CTYPE_OMIT) {
4000 if (backctype != CTYPE_OMIT && (clin > 0) == (backclin > 0)) {
4001 /* TRANSLATORS: We've been told the foresight and backsight are
4002 * both "UP", or that they're both "DOWN". */
4003 compile_error_reading_skip(Clino, /*Plumbed CLINO and BACKCLINO readings can't be in the same direction*/92);
4004 return 0;
4006 dz = (clin > (real)0.0) ? tape : -tape;
4007 } else {
4008 dz = (backclin < (real)0.0) ? tape : -tape;
4010 vx = vy = var(Q_POS) / 3.0 + dz * dz * var(Q_PLUMB);
4011 vz = var(Q_POS) / 3.0 + VAR(Tape);
4012 #ifndef NO_COVARIANCES
4013 /* Correct values - no covariances in this case! */
4014 cxy = cyz = czx = (real)0.0;
4015 #endif
4016 } else {
4017 /* Each of ctype and backctype are either CTYPE_READING/CTYPE_HORIZ
4018 * or CTYPE_OMIT */
4019 /* clino */
4020 real L2, cosG, LcosG, cosG2, sinB, cosB, dx2, dy2, dz2, v, V;
4021 if (comp_given == End) {
4022 /* TRANSLATORS: Here "legs" are survey legs, i.e. measurements between
4023 * survey stations. */
4024 compile_error_reading_skip(Comp, /*Compass reading may not be omitted except on plumbed legs*/14);
4025 return 0;
4027 if (tape == (real)0.0) {
4028 dx = dy = dz = (real)0.0;
4029 vx = vy = vz = (real)(var(Q_POS) / 3.0); /* Position error only */
4030 #ifndef NO_COVARIANCES
4031 cxy = cyz = czx = (real)0.0;
4032 #endif
4033 #if DEBUG_DATAIN_1
4034 printf("Zero length leg: vx = %f, vy = %f, vz = %f\n", vx, vy, vz);
4035 #endif
4036 } else {
4037 real sinGcosG;
4038 /* take into account variance in LEVEL case */
4039 real var_clin = var(Q_LEVEL);
4040 real var_comp;
4041 real comp = handle_compass(&var_comp);
4042 /* ctype != CTYPE_READING is LEVEL case */
4043 if (ctype == CTYPE_READING) {
4044 clin = (clin - pcs->z[Q_GRADIENT]) * pcs->sc[Q_GRADIENT];
4045 var_clin = VAR(Clino);
4047 if (backctype == CTYPE_READING) {
4048 backclin = (backclin - pcs->z[Q_BACKGRADIENT])
4049 * pcs->sc[Q_BACKGRADIENT];
4050 if (ctype == CTYPE_READING) {
4051 if (sqrd((clin + backclin) / 3.0) > var_clin + VAR(BackClino)) {
4052 /* fore and back readings differ by more than 3 sds */
4053 /* TRANSLATORS: %s is replaced by the amount the readings disagree
4054 * by, e.g. "2.5Ā°" or "3įµ". */
4055 warn_readings_differ(/*CLINO reading and BACKCLINO reading disagree by %s*/99,
4056 clin + backclin, get_angle_units(Q_GRADIENT), Clino, BackClino);
4058 clin = (clin / var_clin - backclin / VAR(BackClino));
4059 var_clin = (var_clin + VAR(BackClino)) / 4;
4060 clin *= var_clin;
4061 } else {
4062 clin = -backclin;
4063 var_clin = VAR(BackClino);
4067 #if DEBUG_DATAIN
4068 printf(" %4.2f %4.2f %4.2f\n", tape, comp, clin);
4069 #endif
4070 cosG = cos(clin);
4071 LcosG = tape * cosG;
4072 sinB = sin(comp);
4073 cosB = cos(comp);
4074 #if DEBUG_DATAIN_1
4075 printf("sinB = %f, cosG = %f, LcosG = %f\n", sinB, cosG, LcosG);
4076 #endif
4077 dx = LcosG * sinB;
4078 dy = LcosG * cosB;
4079 dz = tape * sin(clin);
4080 /* printf("%.2f\n",clin); */
4081 #if DEBUG_DATAIN_1
4082 printf("dx = %f\ndy = %f\ndz = %f\n", dx, dy, dz);
4083 #endif
4084 dx2 = dx * dx;
4085 L2 = tape * tape;
4086 V = VAR(Tape) / L2;
4087 dy2 = dy * dy;
4088 cosG2 = cosG * cosG;
4089 sinGcosG = sin(clin) * cosG;
4090 dz2 = dz * dz;
4091 v = dz2 * var_clin;
4092 #ifdef NO_COVARIANCES
4093 vx = (var(Q_POS) / 3.0 + dx2 * V + dy2 * var_comp +
4094 (.5 + sinB * sinB * cosG2) * v);
4095 vy = (var(Q_POS) / 3.0 + dy2 * V + dx2 * var_comp +
4096 (.5 + cosB * cosB * cosG2) * v);
4097 if (ctype == CTYPE_OMIT && backctype == CTYPE_OMIT) {
4098 /* if no clino, assume sd=tape/sqrt(10) so 3sds = .95*tape */
4099 vz = var(Q_POS) / 3.0 + L2 * (real)0.1;
4100 } else {
4101 vz = var(Q_POS) / 3.0 + dz2 * V + L2 * cosG2 * var_clin;
4103 /* for Surveyor87 errors: vx=vy=vz=var(Q_POS)/3.0; */
4104 #else
4105 vx = var(Q_POS) / 3.0 + dx2 * V + dy2 * var_comp +
4106 (sinB * sinB * v);
4107 vy = var(Q_POS) / 3.0 + dy2 * V + dx2 * var_comp +
4108 (cosB * cosB * v);
4109 if (ctype == CTYPE_OMIT && backctype == CTYPE_OMIT) {
4110 /* if no clino, assume sd=tape/sqrt(10) so 3sds = .95*tape */
4111 vz = var(Q_POS) / 3.0 + L2 * (real)0.1;
4112 } else {
4113 vz = var(Q_POS) / 3.0 + dz2 * V + L2 * cosG2 * var_clin;
4115 /* usual covariance formulae are fine in no clino case since
4116 * dz = 0 so value of var_clin is ignored */
4117 cxy = sinB * cosB * (VAR(Tape) * cosG2 + var_clin * dz2)
4118 - var_comp * dx * dy;
4119 czx = VAR(Tape) * sinB * sinGcosG - var_clin * dx * dz;
4120 cyz = VAR(Tape) * cosB * sinGcosG - var_clin * dy * dz;
4121 #if 0
4122 printf("vx = %6.3f, vy = %6.3f, vz = %6.3f\n", vx, vy, vz);
4123 printf("cxy = %6.3f, cyz = %6.3f, czx = %6.3f\n", cxy, cyz, czx);
4124 #endif
4125 #endif
4126 #if DEBUG_DATAIN_1
4127 printf("In DATAIN.C, vx = %f, vy = %f, vz = %f\n", vx, vy, vz);
4128 #endif
4132 // Apply any Walls variance overrides.
4133 if (VAR(Dx) >= 0) vx = VAR(Dx);
4134 if (VAR(Dy) >= 0) vy = VAR(Dy);
4135 if (VAR(Dz) >= 0) vz = VAR(Dz);
4137 #if DEBUG_DATAIN_1
4138 printf("Just before addleg, vx = %f\n", vx);
4139 #endif
4140 /*printf("dx,dy,dz = %.2f %.2f %.2f\n\n", dx, dy, dz);*/
4141 addlegbyname(fr, to, fToFirst, dx, dy, dz, vx, vy, vz
4142 #ifndef NO_COVARIANCES
4143 , cyz, czx, cxy
4144 #endif
4146 return 1;
4149 static int
4150 process_diving(prefix *fr, prefix *to, bool fToFirst, bool fDepthChange)
4152 real tape = VAL(Tape);
4154 real dx, dy, dz;
4155 real vx, vy, vz;
4156 #ifndef NO_COVARIANCES
4157 real cxy = 0, cyz = 0, czx = 0;
4158 #endif
4160 handle_comp_units();
4162 /* depth gauge readings increase upwards with default calibration */
4163 if (fDepthChange) {
4164 SVX_ASSERT(VAL(FrDepth) == 0.0);
4165 dz = VAL(ToDepth) * pcs->units[Q_DEPTH] - pcs->z[Q_DEPTH];
4166 dz *= pcs->sc[Q_DEPTH];
4167 } else {
4168 dz = VAL(ToDepth) - VAL(FrDepth);
4169 dz *= pcs->units[Q_DEPTH] * pcs->sc[Q_DEPTH];
4172 /* adjusted tape is negative -- probably the calibration is wrong */
4173 if (tape < (real)0.0) {
4174 compile_diagnostic_reading(DIAG_WARN, Tape, /*Negative adjusted tape reading*/79);
4177 /* check if tape is less than depth change */
4178 if (tape < fabs(dz)) {
4179 /* FIXME: allow margin of error based on variances? */
4180 /* TRANSLATORS: This means that the data fed in said this.
4182 * It could be a gross error (e.g. the decimal point is missing from the
4183 * depth gauge reading) or it could just be due to random error on a near
4184 * vertical leg */
4185 compile_diagnostic_reading(DIAG_WARN, Tape, /*Tape reading is less than change in depth*/62);
4188 if (tape == (real)0.0 && dz == 0.0) {
4189 dx = dy = dz = (real)0.0;
4190 vx = vy = vz = (real)(var(Q_POS) / 3.0); /* Position error only */
4191 } else if (VAL(Comp) == HUGE_REAL &&
4192 VAL(BackComp) == HUGE_REAL) {
4193 /* plumb */
4194 dx = dy = (real)0.0;
4195 if (dz < 0) tape = -tape;
4196 /* FIXME: Should use FrDepth sometimes... */
4197 dz = (dz * VAR(Tape) + tape * 2 * VAR(ToDepth))
4198 / (VAR(Tape) * 2 * VAR(ToDepth));
4199 vx = vy = var(Q_POS) / 3.0 + dz * dz * var(Q_PLUMB);
4200 /* FIXME: Should use FrDepth sometimes... */
4201 vz = var(Q_POS) / 3.0 + VAR(Tape) * 2 * VAR(ToDepth)
4202 / (VAR(Tape) + VAR(ToDepth));
4203 } else {
4204 real L2, sinB, cosB, dz2, D2;
4205 real var_comp;
4206 real comp = handle_compass(&var_comp);
4207 sinB = sin(comp);
4208 cosB = cos(comp);
4209 L2 = tape * tape;
4210 dz2 = dz * dz;
4211 D2 = L2 - dz2;
4212 if (D2 <= (real)0.0) {
4213 /* FIXME: Should use FrDepth sometimes... */
4214 real vsum = VAR(Tape) + 2 * VAR(ToDepth);
4215 dx = dy = (real)0.0;
4216 vx = vy = var(Q_POS) / 3.0;
4217 /* FIXME: Should use FrDepth sometimes... */
4218 vz = var(Q_POS) / 3.0 + VAR(Tape) * 2 * VAR(ToDepth) / vsum;
4219 if (dz > 0) {
4220 /* FIXME: Should use FrDepth sometimes... */
4221 dz = (dz * VAR(Tape) + tape * 2 * VAR(ToDepth)) / vsum;
4222 } else {
4223 dz = (dz * VAR(Tape) - tape * 2 * VAR(ToDepth)) / vsum;
4225 } else {
4226 real D = sqrt(D2);
4227 /* FIXME: Should use FrDepth sometimes... */
4228 real F = VAR(Tape) * L2 + 2 * VAR(ToDepth) * D2;
4229 dx = D * sinB;
4230 dy = D * cosB;
4232 vx = var(Q_POS) / 3.0 +
4233 sinB * sinB * F / D2 + var_comp * dy * dy;
4234 vy = var(Q_POS) / 3.0 +
4235 cosB * cosB * F / D2 + var_comp * dx * dx;
4236 /* FIXME: Should use FrDepth sometimes... */
4237 vz = var(Q_POS) / 3.0 + 2 * VAR(ToDepth);
4239 #ifndef NO_COVARIANCES
4240 cxy = sinB * cosB * (F / D2 + var_comp * D2);
4241 /* FIXME: Should use FrDepth sometimes... */
4242 cyz = -2 * VAR(ToDepth) * dy / D;
4243 czx = -2 * VAR(ToDepth) * dx / D;
4244 #endif
4246 /* FIXME: If there's a clino reading, check it against the depth reading,
4247 * and average.
4248 * if (VAL(Clino) != HUGE_REAL || VAL(BackClino) != HUGE_REAL) { ... }
4251 addlegbyname(fr, to, fToFirst, dx, dy, dz, vx, vy, vz
4252 #ifndef NO_COVARIANCES
4253 , cxy, cyz, czx
4254 #endif
4256 return 1;
4259 static int
4260 process_cartesian(prefix *fr, prefix *to, bool fToFirst)
4262 real dx = (VAL(Dx) * pcs->units[Q_DX] - pcs->z[Q_DX]) * pcs->sc[Q_DX];
4263 real dy = (VAL(Dy) * pcs->units[Q_DY] - pcs->z[Q_DY]) * pcs->sc[Q_DY];
4264 real dz = (VAL(Dz) * pcs->units[Q_DZ] - pcs->z[Q_DZ]) * pcs->sc[Q_DZ];
4266 real rotation = pcs->cartesian_rotation;
4267 switch (pcs->cartesian_north) {
4268 case GRID_NORTH:
4269 if (!proj_str_out && !pcs->proj_str) {
4270 // The default unspecified coordinate system has no grid
4271 // convergence.
4272 break;
4274 rotation += get_input_convergence();
4275 rotation -= get_convergence();
4276 break;
4277 case TRUE_NORTH:
4278 if (!proj_str_out && !pcs->proj_str) {
4279 // True north is grid north in the default unspecified coordinate
4280 // system.
4281 break;
4283 rotation -= get_convergence();
4284 break;
4285 case MAGNETIC_NORTH:
4286 rotation += get_declination();
4287 break;
4290 // Apply rotation.
4291 real sinB = sin(rotation);
4292 real cosB = cos(rotation);
4294 real new_dx = dx * cosB + dy * sinB;
4295 dy = dy * cosB - dx * sinB;
4296 dx = new_dx;
4298 real vx = VAR(Dx) * cosB * cosB + VAR(Dy) * sinB * sinB;
4299 real vy = VAR(Dy) * cosB * cosB - VAR(Dx) * sinB * sinB;
4300 real vz = VAR(Dz);
4301 #ifndef NO_COVARIANCES
4302 real cxy = (VAR(Dx) + VAR(Dy)) * sinB * cosB;
4303 #endif
4304 addlegbyname(fr, to, fToFirst, dx, dy, dz, vx, vy, vz
4305 #ifndef NO_COVARIANCES
4306 , cxy, 0, 0
4307 #endif
4309 return 1;
4312 static void
4313 read_walls_lrud(void)
4315 int end = (ch == '*' ? ch : '>');
4316 while (nextch() != end && !isEol(ch)) {
4317 // FIXME: Process LRUD.
4319 if (ch == end) {
4320 nextch();
4321 if (!isBlank(ch) && !isComm(ch) && !isEol(ch) && ch != '#' && ch != '(') {
4322 // Walls seems to quietly ignore junk after LRUD before the next
4323 // blank, but allow for a command (#SEG) or variance overrides
4324 // following with no blanks in between.
4325 get_word();
4326 compile_diagnostic(DIAG_WARN|DIAG_TOKEN, /*Ignoring ā€œ%sā€*/506,
4327 s_str(&token));
4329 } else {
4330 char as_string[2] = { end, '\0' };
4331 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting ā€œ%sā€*/497, as_string);
4335 // Read optional LRUD and/or variance overrides.
4336 static void
4337 read_walls_extras(unsigned long* p_compass_dat_flags)
4339 while (true) {
4340 skipblanks();
4341 switch (ch) {
4342 case '(': {
4343 real var_xy = HUGE_REAL, var_z = HUGE_REAL;
4344 read_walls_variance_overrides(&var_xy, &var_z);
4345 // For now don't allow 0 variance, make it 1mm instead. FIXME We
4346 // really should check connectivity before allowing 0.
4347 if (var_xy == 0.0) var_xy = 1e-6;
4348 if (var_z == 0.0) var_z = 1e-6;
4349 // FIXME: We don't currently support legs which only connect in
4350 // some dimensions so give them a high variance for now.
4351 if (var_xy == HUGE_REAL && var_z != HUGE_REAL) {
4352 var_xy = 1e6;
4353 } else if (var_z == HUGE_REAL && var_xy != HUGE_REAL) {
4354 var_z = 1e6;
4356 VAR(Dx) = var_xy;
4357 VAR(Dy) = var_xy;
4358 VAR(Dz) = var_z;
4359 break;
4361 case '*':
4362 case '<':
4363 read_walls_lrud();
4364 break;
4365 case '#':
4366 // Allow for `#SEG` after a data leg.
4367 nextch();
4368 get_token();
4369 walls_cmd directive = match_tok(walls_cmd_tab, TABSIZE(walls_cmd_tab));
4370 if (directive == WALLS_CMD_SEGMENT) {
4371 parse_walls_segment(p_compass_dat_flags);
4372 } else {
4373 compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_TOKEN,
4374 /*Expecting ā€œ%sā€, ā€œ%sā€, or ā€œ%sā€*/188,
4375 "S", "SEG", "SEGMENT");
4377 return;
4378 default:
4379 return;
4384 static bool
4385 read_walls_srv_to(prefix **p_to, unsigned long* p_compass_dat_flags)
4387 skipblanks();
4388 filepos fp;
4389 get_pos(&fp);
4390 bool might_be_lrud = (ch == '*' || ch == '<');
4391 if (might_be_lrud) {
4392 // Isolated LRUD if there's a closing delimiter. If not then
4393 // Walls parses the `*` or `<` as the first character of the `To`
4394 // station name.
4395 int end = (ch == '*' ? ch : '>');
4396 do {
4397 nextch();
4398 } while (ch != end && !isComm(ch) && !isEol(ch));
4399 bool parse_as_lrud = (ch == end);
4400 set_pos(&fp);
4401 if (parse_as_lrud) {
4402 handle_isolated_lrud:
4403 read_walls_extras(p_compass_dat_flags);
4404 skipblanks();
4405 if (!isEol(ch) && !isComm(ch)) {
4406 compile_diagnostic(DIAG_WARN|DIAG_SKIP|DIAG_TAIL,
4407 /*End of line not blank*/15);
4409 process_eol();
4410 return false;
4413 bool new;
4414 *p_to = read_walls_station(p_walls_options->prefix, true, &new);
4415 if (might_be_lrud && new) {
4416 filepos fp_save;
4417 get_pos(&fp_save);
4418 set_pos(&fp);
4419 // TRANSLATORS: Warning issued about a dubious case in survey data in
4420 // Walls format (.srv). Real world example:
4422 // P25 *8 5 15 3.58
4424 // This is treated by Walls as a leg from P25 to *8 but seems likely
4425 // to be intended to be an isolated LRUD reading (one not on a survey
4426 // leg, useful for the start or end of a traverse) but the closing *
4427 // was missed (or perhaps in this particular case, mistyped as an 8
4428 // by failing to press shift), so Survex issues a warning about it.
4429 compile_diagnostic(DIAG_WARN|DIAG_WORD,
4430 /*Parsing as ā€œtoā€ station but may be isolated LRUD with missing closing delimiter*/508);
4431 set_pos(&fp_save);
4433 skipblanks();
4434 if (ch == '*' || ch == '<') {
4435 // Odd apparently undocumented variant of isolated LRUD.
4436 goto handle_isolated_lrud;
4438 return true;
4441 static void
4442 data_cartesian(void)
4444 prefix *fr = NULL, *to = NULL;
4446 bool fMulti = false;
4448 reading first_stn = End;
4450 unsigned long compass_dat_flags = 0;
4451 if (p_walls_options) compass_dat_flags = p_walls_options->compass_dat_flags;
4453 again:
4455 /* We clear these flags in the normal course of events, but if there's an
4456 * error in a reading, we might not, so make sure it has been cleared here.
4458 pcs->flags &= ~(BIT(FLAGS_ANON_ONE_END) | BIT(FLAGS_IMPLICIT_SPLAY));
4459 for (const reading *ordering = pcs->ordering ; ; ordering++) {
4460 skipblanks();
4461 switch (*ordering) {
4462 case Fr:
4463 fr = read_prefix(PFX_STATION|PFX_ALLOW_ROOT|PFX_ANON);
4464 if (first_stn == End) first_stn = Fr;
4465 break;
4466 case To:
4467 to = read_prefix(PFX_STATION|PFX_ALLOW_ROOT|PFX_ANON);
4468 if (first_stn == End) first_stn = To;
4469 break;
4470 case Station:
4471 fr = to;
4472 to = read_prefix(PFX_STATION);
4473 first_stn = To;
4474 break;
4475 case Dx: case Dy: case Dz:
4476 read_reading(*ordering, false);
4477 break;
4478 case WallsSRVFr:
4479 // Walls SRV is always From then To.
4480 first_stn = Fr;
4481 fr = read_walls_station(p_walls_options->prefix, true, NULL);
4482 break;
4483 case WallsSRVTo:
4484 if (!read_walls_srv_to(&to, &compass_dat_flags)) {
4485 // Isolated LRUD so don't try to parse line as a survey leg.
4486 return;
4488 break;
4489 case WallsSRVExtras:
4490 read_walls_extras(&compass_dat_flags);
4491 break;
4492 case Ignore:
4493 skipword(); break;
4494 case IgnoreAllAndNewLine:
4495 skipline();
4496 /* fall through */
4497 case Newline:
4498 if (fr != NULL) {
4499 int implicit_splay = TSTBIT(pcs->flags, FLAGS_IMPLICIT_SPLAY);
4500 pcs->flags &= ~(BIT(FLAGS_ANON_ONE_END) | BIT(FLAGS_IMPLICIT_SPLAY));
4501 int save_flags = pcs->flags;
4502 if (implicit_splay) {
4503 pcs->flags |= BIT(FLAGS_SPLAY);
4505 if (!process_cartesian(fr, to, first_stn == To))
4506 skipline();
4507 pcs->flags = save_flags;
4509 fMulti = true;
4510 while (1) {
4511 process_eol();
4512 skipblanks();
4513 if (isData(ch)) break;
4514 if (!isComm(ch)) {
4515 return;
4518 break;
4519 case IgnoreAll:
4520 skipline();
4521 /* fall through */
4522 case End:
4523 if (!fMulti) {
4524 if (VAR(Dx) == HUGE_REAL &&
4525 VAR(Dy) == HUGE_REAL &&
4526 VAR(Dz) == HUGE_REAL) {
4527 // Walls variance override of `(?)` or equivalent turns the leg
4528 // into a "nosurvey" leg.
4529 (void)process_nosurvey(fr, to, (first_stn == To));
4530 return;
4532 int implicit_splay = TSTBIT(pcs->flags, FLAGS_IMPLICIT_SPLAY);
4533 pcs->flags &= ~(BIT(FLAGS_ANON_ONE_END) | BIT(FLAGS_IMPLICIT_SPLAY));
4534 int save_flags = pcs->flags;
4535 if (implicit_splay) {
4536 pcs->flags |= BIT(FLAGS_SPLAY);
4538 if (compass_dat_flags) {
4539 pcs->flags |= convert_compass_dat_flags(compass_dat_flags);
4541 process_cartesian(fr, to, first_stn == To);
4542 pcs->flags = save_flags;
4544 process_eol();
4545 return;
4547 do {
4548 process_eol();
4549 skipblanks();
4550 } while (isComm(ch));
4551 goto again;
4552 default: BUG("Unknown reading in ordering");
4557 static int
4558 process_cylpolar(prefix *fr, prefix *to, bool fToFirst, bool fDepthChange)
4560 real tape = VAL(Tape);
4562 real dx, dy, dz;
4563 real vx, vy, vz;
4564 #ifndef NO_COVARIANCES
4565 real cxy = 0;
4566 #endif
4568 handle_comp_units();
4570 /* depth gauge readings increase upwards with default calibration */
4571 if (fDepthChange) {
4572 SVX_ASSERT(VAL(FrDepth) == 0.0);
4573 dz = VAL(ToDepth) * pcs->units[Q_DEPTH] - pcs->z[Q_DEPTH];
4574 dz *= pcs->sc[Q_DEPTH];
4575 } else {
4576 dz = VAL(ToDepth) - VAL(FrDepth);
4577 dz *= pcs->units[Q_DEPTH] * pcs->sc[Q_DEPTH];
4580 /* adjusted tape is negative -- probably the calibration is wrong */
4581 if (tape < (real)0.0) {
4582 compile_diagnostic_reading(DIAG_WARN, Tape, /*Negative adjusted tape reading*/79);
4585 if (VAL(Comp) == HUGE_REAL && VAL(BackComp) == HUGE_REAL) {
4586 /* plumb */
4587 dx = dy = (real)0.0;
4588 vx = vy = var(Q_POS) / 3.0 + dz * dz * var(Q_PLUMB);
4589 /* FIXME: Should use FrDepth sometimes... */
4590 vz = var(Q_POS) / 3.0 + 2 * VAR(ToDepth);
4591 } else {
4592 real sinB, cosB;
4593 real var_comp;
4594 real comp = handle_compass(&var_comp);
4595 sinB = sin(comp);
4596 cosB = cos(comp);
4598 dx = tape * sinB;
4599 dy = tape * cosB;
4601 vx = var(Q_POS) / 3.0 +
4602 VAR(Tape) * sinB * sinB + var_comp * dy * dy;
4603 vy = var(Q_POS) / 3.0 +
4604 VAR(Tape) * cosB * cosB + var_comp * dx * dx;
4605 /* FIXME: Should use FrDepth sometimes... */
4606 vz = var(Q_POS) / 3.0 + 2 * VAR(ToDepth);
4608 #ifndef NO_COVARIANCES
4609 cxy = (VAR(Tape) - var_comp * tape * tape) * sinB * cosB;
4610 #endif
4612 addlegbyname(fr, to, fToFirst, dx, dy, dz, vx, vy, vz
4613 #ifndef NO_COVARIANCES
4614 , cxy, 0, 0
4615 #endif
4617 return 1;
4620 /* Process tape/compass/clino, diving, and cylpolar styles of survey data
4621 * Also handles topofil (fromcount/tocount or count) in place of tape */
4622 static void
4623 data_normal(void)
4625 prefix *fr = NULL, *to = NULL;
4626 reading first_stn = End;
4628 bool fTopofil = false, fMulti = false;
4629 bool fRev;
4630 clino_type ctype, backctype;
4631 bool fDepthChange;
4632 unsigned long compass_dat_flags = 0;
4633 if (p_walls_options) compass_dat_flags = p_walls_options->compass_dat_flags;
4635 VAL(Tape) = VAL(BackTape) = HUGE_REAL;
4636 VAL(Comp) = VAL(BackComp) = HUGE_REAL;
4637 VAL(FrCount) = VAL(ToCount) = 0;
4638 VAL(FrDepth) = VAL(ToDepth) = 0;
4639 VAL(Left) = VAL(Right) = VAL(Up) = VAL(Down) = HUGE_REAL;
4641 // Initialise variance slots where we store variance overrides.
4642 VAR(Dx) = -1.0;
4643 VAR(Dy) = -1.0;
4644 VAR(Dz) = -1.0;
4646 fRev = false;
4647 ctype = backctype = CTYPE_OMIT;
4648 fDepthChange = false;
4650 /* ordering may omit clino reading, so set up default here */
4651 /* this is also used if clino reading is the omit character */
4652 VAL(Clino) = VAL(BackClino) = 0;
4654 again:
4656 /* We clear these flags in the normal course of events, but if there's an
4657 * error in a reading, we might not, so make sure it has been cleared here.
4659 pcs->flags &= ~(BIT(FLAGS_ANON_ONE_END) | BIT(FLAGS_IMPLICIT_SPLAY));
4660 for (const reading *ordering = pcs->ordering; ; ordering++) {
4661 skipblanks();
4662 switch (*ordering) {
4663 case Fr:
4664 fr = read_prefix(PFX_STATION|PFX_ALLOW_ROOT|PFX_ANON);
4665 if (first_stn == End) first_stn = Fr;
4666 break;
4667 case To:
4668 to = read_prefix(PFX_STATION|PFX_ALLOW_ROOT|PFX_ANON);
4669 if (first_stn == End) first_stn = To;
4670 break;
4671 case CompassDATFr:
4672 // Compass DAT is always From then To.
4673 first_stn = Fr;
4674 fr = read_prefix(PFX_STATION);
4675 scan_compass_station_name(fr);
4676 break;
4677 case CompassDATTo:
4678 to = read_prefix(PFX_STATION);
4679 scan_compass_station_name(to);
4680 break;
4681 case Station:
4682 fr = to;
4683 to = read_prefix(PFX_STATION);
4684 first_stn = To;
4685 break;
4686 case Dir: {
4687 typedef enum {
4688 DIR_NULL=-1, DIR_FORE, DIR_BACK
4689 } dir_tok;
4690 static const sztok dir_tab[] = {
4691 {"B", DIR_BACK},
4692 {"F", DIR_FORE},
4694 dir_tok tok;
4695 get_token_legacy();
4696 tok = match_tok(dir_tab, TABSIZE(dir_tab));
4697 switch (tok) {
4698 case DIR_FORE:
4699 break;
4700 case DIR_BACK:
4701 fRev = true;
4702 break;
4703 default:
4704 compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Found ā€œ%sā€, expecting ā€œFā€ or ā€œBā€*/131, s_str(&token));
4705 process_eol();
4706 return;
4708 do_legacy_token_warning();
4709 break;
4711 case Tape: case BackTape: {
4712 reading r = *ordering;
4713 read_reading(r, true);
4714 if (VAL(r) == HUGE_REAL) {
4715 if (!isOmit(ch)) {
4716 compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found ā€œ%sā€*/9);
4717 /* Avoid also warning about omitted tape reading. */
4718 VAL(r) = 0;
4719 } else {
4720 nextch();
4722 } else if (VAL(r) < (real)0.0) {
4723 compile_diagnostic_reading(DIAG_WARN, r, /*Negative tape reading*/60);
4725 break;
4727 case Count:
4728 VAL(FrCount) = VAL(ToCount);
4729 LOC(FrCount) = LOC(ToCount);
4730 WID(FrCount) = WID(ToCount);
4731 read_reading(ToCount, false);
4732 fTopofil = true;
4733 break;
4734 case FrCount:
4735 read_reading(FrCount, false);
4736 break;
4737 case ToCount:
4738 read_reading(ToCount, false);
4739 fTopofil = true;
4740 break;
4741 case Comp: case BackComp:
4742 read_bearing_or_omit(*ordering);
4743 break;
4744 case Clino: case BackClino: {
4745 reading r = *ordering;
4746 clino_type * p_ctype = (r == Clino ? &ctype : &backctype);
4747 read_reading(r, true);
4748 if (VAL(r) == HUGE_REAL) {
4749 VAL(r) = handle_plumb(p_ctype);
4750 if (VAL(r) != HUGE_REAL) {
4751 WID(r) = ftell(file.fh) - LOC(r);
4752 break;
4754 compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found ā€œ%sā€*/9);
4755 skipline();
4756 process_eol();
4757 return;
4759 *p_ctype = CTYPE_READING;
4760 break;
4762 case FrDepth: case ToDepth:
4763 read_reading(*ordering, false);
4764 break;
4765 case Depth:
4766 VAL(FrDepth) = VAL(ToDepth);
4767 LOC(FrDepth) = LOC(ToDepth);
4768 WID(FrDepth) = WID(ToDepth);
4769 read_reading(ToDepth, false);
4770 break;
4771 case DepthChange:
4772 fDepthChange = true;
4773 VAL(FrDepth) = 0;
4774 read_reading(ToDepth, false);
4775 break;
4776 case CompassDATComp:
4777 read_bearing_or_omit(Comp);
4778 if (is_compass_NaN(VAL(Comp))) VAL(Comp) = HUGE_REAL;
4779 break;
4780 case CompassDATBackComp:
4781 read_bearing_or_omit(BackComp);
4782 if (is_compass_NaN(VAL(BackComp))) VAL(BackComp) = HUGE_REAL;
4783 break;
4784 case CompassDATClino: case CompassDATBackClino: {
4785 reading r;
4786 clino_type * p_ctype;
4787 if (*ordering == CompassDATClino) {
4788 r = Clino;
4789 p_ctype = &ctype;
4790 } else {
4791 r = BackClino;
4792 p_ctype = &backctype;
4794 read_reading(r, false);
4795 if (is_compass_NaN(VAL(r))) {
4796 VAL(r) = 0;
4797 *p_ctype = CTYPE_OMIT;
4798 } else {
4799 *p_ctype = CTYPE_READING;
4801 break;
4803 case CompassDATLeft: case CompassDATRight:
4804 case CompassDATUp: case CompassDATDown: {
4805 /* FIXME: need to actually make use of these entries! */
4806 reading actual = Left + (*ordering - CompassDATLeft);
4807 read_reading(actual, false);
4808 if (VAL(actual) < 0) VAL(actual) = HUGE_REAL;
4809 break;
4811 case CompassDATFlags:
4812 if (ch == '#') {
4813 filepos fp;
4814 get_pos(&fp);
4815 nextch();
4816 if (ch == '|') {
4817 nextch();
4818 while (ch >= 'A' && ch <= 'Z') {
4819 compass_dat_flags |= BIT(ch - 'A');
4820 /* Flags we handle:
4821 * L (exclude from length)
4822 * S (splay)
4823 * P (no plot) (mapped to FLAG_SURFACE)
4824 * X (exclude data)
4825 * FIXME: Defined flags we currently ignore:
4826 * C (no adjustment) (set all (co)variances to 0? Then
4827 * we need to handle a loop of such legs or a traverse
4828 * of such legs between two fixed points...)
4830 nextch();
4832 if (ch == '#') {
4833 nextch();
4834 } else {
4835 compass_dat_flags = 0;
4836 set_pos(&fp);
4838 } else {
4839 set_pos(&fp);
4842 break;
4843 case WallsSRVFr:
4844 // Walls SRV is always From then To.
4845 first_stn = Fr;
4846 fr = read_walls_station(p_walls_options->prefix, true, NULL);
4847 break;
4848 case WallsSRVTo:
4849 if (!read_walls_srv_to(&to, &compass_dat_flags)) {
4850 // Isolated LRUD so don't try to parse line as a survey leg.
4851 return;
4853 break;
4854 case WallsSRVTape:
4855 LOC(Tape) = ftell(file.fh);
4856 VAL(Tape) = read_numeric(true);
4857 if (VAL(Tape) == HUGE_REAL) {
4858 if (ch == 'i' || ch == 'I') {
4859 // Length specified in inches only, e.g. `i6` is 6 inches.
4860 VAL(Tape) = 0.0;
4861 goto inches_only;
4863 // Walls expects 2 or more - for an omitted value.
4864 if (ch != '-' || nextch() != '-') {
4865 compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found ā€œ%sā€*/9);
4866 /* Avoid also warning about omitted tape reading. */
4867 VAL(Tape) = 0;
4868 } else {
4869 while (nextch() == '-') { }
4871 } else {
4872 if (VAL(Tape) < (real)0.0)
4873 compile_diagnostic_reading(DIAG_WARN, Tape, /*Negative tape reading*/60);
4874 switch (ch) {
4875 case 'I': case 'i':
4876 inches_only:
4877 nextch();
4878 if (isdigit(ch)) {
4879 real inches = read_numeric(false);
4880 VAL(Tape) += inches / 12.0;
4882 /* FALLTHRU */
4883 case 'F': case 'f':
4884 VAL(Tape) *= METRES_PER_FOOT;
4885 /* FALLTHRU */
4886 case 'M': case 'm':
4887 VAL(Tape) /= pcs->units[Q_LENGTH];
4888 nextch();
4891 WID(Tape) = ftell(file.fh) - LOC(Tape);
4892 VAR(Tape) = var(Q_LENGTH);
4893 break;
4894 case WallsSRVComp: {
4895 skipblanks();
4896 LOC(Comp) = ftell(file.fh);
4897 if (ch != '/') {
4898 if (isalpha(ch)) {
4899 VAL(Comp) = read_quadrant(false);
4900 } else {
4901 VAL(Comp) = read_number(true, false);
4902 if (VAL(Comp) == HUGE_REAL) {
4903 if (ch != '-') {
4904 compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found ā€œ%sā€*/9);
4905 skipline();
4906 process_eol();
4907 return;
4909 // Walls documents two or more `-` for an omitted
4910 // reading, but actually just one works too!
4911 while (nextch() == '-') { }
4912 } else {
4913 switch (ch) {
4914 case 'D': case 'd':
4915 // Degrees.
4916 VAL(Comp) *= M_PI / 180.0 / pcs->units[Q_BEARING];
4917 nextch();
4918 break;
4919 case 'G': case 'g':
4920 // Grads.
4921 VAL(Comp) *= M_PI / 200.0 / pcs->units[Q_BEARING];
4922 nextch();
4923 break;
4924 case 'M': case 'm':
4925 // Mils.
4926 VAL(Comp) *= M_PI / 3200.0 / pcs->units[Q_BEARING];
4927 nextch();
4928 break;
4932 WID(Comp) = ftell(file.fh) - LOC(Comp);
4933 VAR(Comp) = var(Q_BEARING);
4934 } else {
4935 // Omitted foresight, e.g. `/123` or `/` (both omitted).
4936 WID(Comp) = 0;
4937 VAL(Comp) = HUGE_REAL;
4939 if (ch == '/' && !isBlank(nextch())) {
4940 LOC(BackComp) = ftell(file.fh);
4941 if (isalpha(ch)) {
4942 VAL(BackComp) = read_quadrant(false);
4943 } else {
4944 VAL(BackComp) = read_number(true, false);
4945 if (VAL(BackComp) == HUGE_REAL) {
4946 if (ch != '-') {
4947 compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found ā€œ%sā€*/9);
4948 skipline();
4949 process_eol();
4950 return;
4952 // Walls documents two or more `-` for an omitted
4953 // reading, but actually just one works too!
4954 while (nextch() == '-') { }
4955 } else {
4956 switch (ch) {
4957 case 'D': case 'd':
4958 // Degrees.
4959 VAL(BackComp) *= M_PI / 180.0 / pcs->units[Q_BACKBEARING];
4960 nextch();
4961 break;
4962 case 'G': case 'g':
4963 // Grads.
4964 VAL(BackComp) *= M_PI / 200.0 / pcs->units[Q_BACKBEARING];
4965 nextch();
4966 break;
4967 case 'M': case 'm':
4968 // Mils.
4969 VAL(BackComp) *= M_PI / 3200.0 / pcs->units[Q_BACKBEARING];
4970 nextch();
4971 break;
4975 WID(BackComp) = ftell(file.fh) - LOC(BackComp);
4976 VAR(BackComp) = var(Q_BACKBEARING);
4977 } else {
4978 // Omitted backsight, e.g. `123/` or `/` (both omitted).
4979 LOC(BackComp) = ftell(file.fh);
4980 WID(BackComp) = 0;
4981 VAL(BackComp) = HUGE_REAL;
4983 break;
4985 case WallsSRVClino: {
4986 skipblanks();
4987 LOC(Clino) = ftell(file.fh);
4988 if (ch != '/') {
4989 real clin = read_number(true, false);
4990 if (clin == HUGE_REAL) {
4991 if (ch != '-') {
4992 if (TSTBIT(pcs->flags, FLAGS_ANON_ONE_END) &&
4993 p_walls_options->data_order_ct[4] == WallsSRVClino) {
4994 // The clino can be completely omitted with order=dav
4995 // or order=adv on a leg to/from an anonymous station.
4996 } else {
4997 compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found ā€œ%sā€*/9);
4998 skipline();
4999 process_eol();
5000 return;
5002 } else {
5003 // Walls documents two or more `-` for an omitted
5004 // reading, but actually just one works too!
5005 while (nextch() == '-') { }
5007 } else {
5008 switch (ch) {
5009 case 'D': case 'd':
5010 // Degrees.
5011 clin *= M_PI / 180.0 / pcs->units[Q_GRADIENT];
5012 nextch();
5013 break;
5014 case 'G': case 'g':
5015 // Grads.
5016 clin *= M_PI / 200.0 / pcs->units[Q_GRADIENT];
5017 nextch();
5018 break;
5019 case 'M': case 'm':
5020 // Mils.
5021 clin *= M_PI / 3200.0 / pcs->units[Q_GRADIENT];
5022 nextch();
5023 break;
5025 VAL(Clino) = clin;
5026 ctype = CTYPE_READING;
5028 WID(Clino) = ftell(file.fh) - LOC(Clino);
5029 VAR(Clino) = var(Q_GRADIENT);
5030 } else {
5031 // Omitted foresight, e.g. `/12` or `/` (both omitted).
5032 WID(Clino) = 0;
5034 if (ch == '/' && !isBlank(nextch())) {
5035 LOC(BackClino) = ftell(file.fh);
5036 real backclin = read_number(true, false);
5037 if (backclin == HUGE_REAL) {
5038 if (ch != '-') {
5039 compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found ā€œ%sā€*/9);
5040 skipline();
5041 process_eol();
5042 return;
5044 // Walls documents two or more `-` for an omitted
5045 // reading, but actually just one works too!
5046 while (nextch() == '-') { }
5047 } else {
5048 switch (ch) {
5049 case 'D': case 'd':
5050 // Degrees.
5051 backclin *= M_PI / 180.0 / pcs->units[Q_BACKGRADIENT];
5052 nextch();
5053 break;
5054 case 'G': case 'g':
5055 // Grads.
5056 backclin *= M_PI / 200.0 / pcs->units[Q_BACKGRADIENT];
5057 nextch();
5058 break;
5059 case 'M': case 'm':
5060 // Mils.
5061 backclin *= M_PI / 3200.0 / pcs->units[Q_BACKGRADIENT];
5062 nextch();
5063 break;
5065 VAL(BackClino) = backclin;
5066 backctype = CTYPE_READING;
5068 WID(BackClino) = ftell(file.fh) - LOC(BackClino);
5069 VAR(BackClino) = var(Q_BACKGRADIENT);
5070 } else {
5071 // Omitted backsight, e.g. `12/` or `/` (both omitted).
5072 LOC(BackClino) = ftell(file.fh);
5073 WID(BackClino) = 0;
5075 break;
5077 case WallsSRVHeights: {
5078 real instrument_height = read_walls_distance(true,
5079 pcs->units[Q_LENGTH]);
5080 if (instrument_height == HUGE_REAL) {
5081 instrument_height = 0.0;
5082 if (ch == '-') {
5083 // Walls expects 2 or more - for an omitted value.
5084 if (nextch() != '-') {
5085 compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found ā€œ%sā€*/9);
5086 } else {
5087 while (nextch() == '-') { }
5091 if (instrument_height != HUGE_REAL) {
5092 real target_height = read_walls_distance(true,
5093 pcs->units[Q_LENGTH]);
5094 if (target_height == HUGE_REAL) {
5095 target_height = 0.0;
5096 if (ch == '-') {
5097 // Walls expects 2 or more - for an omitted value.
5098 if (nextch() != '-') {
5099 compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found ā€œ%sā€*/9);
5100 } else {
5101 while (nextch() == '-') { }
5105 // FIXME: Ideally we'd make use of these, or at least warn if
5106 // they aren't equal...
5107 (void)instrument_height;
5108 (void)target_height;
5110 break;
5112 case WallsSRVExtras:
5113 read_walls_extras(&compass_dat_flags);
5114 break;
5115 case Ignore:
5116 skipword(); break;
5117 case IgnoreAllAndNewLine:
5118 skipline();
5119 /* fall through */
5120 case Newline:
5121 if (fr != NULL) {
5122 int r;
5123 if (fTopofil) {
5124 VAL(Tape) = VAL(ToCount) - VAL(FrCount);
5125 LOC(Tape) = LOC(ToCount);
5126 WID(Tape) = WID(ToCount);
5128 /* Note: frdepth == todepth test works regardless of fDepthChange
5129 * (frdepth always zero, todepth is change of depth) and also
5130 * works for STYLE_NORMAL (both remain 0) */
5131 if (TSTBIT(pcs->infer, INFER_EQUATES) &&
5132 (VAL(Tape) == (real)0.0 || VAL(Tape) == HUGE_REAL) &&
5133 (VAL(BackTape) == (real)0.0 || VAL(BackTape) == HUGE_REAL) &&
5134 VAL(FrDepth) == VAL(ToDepth)) {
5135 if (!TSTBIT(pcs->infer, INFER_EQUATES_SELF_OK) || fr != to)
5136 process_equate(fr, to);
5137 goto inferred_equate;
5139 if (fRev) {
5140 prefix *t = fr;
5141 fr = to;
5142 to = t;
5144 if (fTopofil) {
5145 VAL(Tape) *= pcs->units[Q_COUNT] * pcs->sc[Q_COUNT];
5146 } else if (VAL(Tape) != HUGE_REAL) {
5147 VAL(Tape) *= pcs->units[Q_LENGTH];
5148 VAL(Tape) -= pcs->z[Q_LENGTH];
5149 VAL(Tape) *= pcs->sc[Q_LENGTH];
5151 if (VAL(BackTape) != HUGE_REAL) {
5152 VAL(BackTape) *= pcs->units[Q_BACKLENGTH];
5153 VAL(BackTape) -= pcs->z[Q_BACKLENGTH];
5154 VAL(BackTape) *= pcs->sc[Q_BACKLENGTH];
5155 if (VAL(Tape) != HUGE_REAL) {
5156 real diff = VAL(Tape) - VAL(BackTape);
5157 if (sqrd(diff / 3.0) > VAR(Tape) + VAR(BackTape)) {
5158 /* fore and back readings differ by more than 3 sds */
5159 /* TRANSLATORS: %s is replaced by the amount the readings disagree
5160 * by, e.g. "0.12m" or "0.2ft". */
5161 warn_readings_differ(/*TAPE reading and BACKTAPE reading disagree by %s*/97,
5162 diff, get_length_units(Q_LENGTH), Tape, BackTape);
5164 VAL(Tape) = VAL(Tape) / VAR(Tape) + VAL(BackTape) / VAR(BackTape);
5165 VAR(Tape) = (VAR(Tape) + VAR(BackTape)) / 4;
5166 VAL(Tape) *= VAR(Tape);
5167 } else {
5168 VAL(Tape) = VAL(BackTape);
5169 VAR(Tape) = VAR(BackTape);
5171 } else if (VAL(Tape) == HUGE_REAL) {
5172 compile_diagnostic_reading(DIAG_ERR, Tape, /*Tape reading may not be omitted*/94);
5173 goto inferred_equate;
5175 int implicit_splay = TSTBIT(pcs->flags, FLAGS_IMPLICIT_SPLAY);
5176 pcs->flags &= ~(BIT(FLAGS_ANON_ONE_END) | BIT(FLAGS_IMPLICIT_SPLAY));
5177 int save_flags = pcs->flags;
5178 if (implicit_splay) {
5179 pcs->flags |= BIT(FLAGS_SPLAY);
5181 switch (pcs->style) {
5182 case STYLE_NORMAL:
5183 r = process_normal(fr, to, (first_stn == To) ^ fRev,
5184 ctype, backctype);
5185 break;
5186 case STYLE_DIVING:
5187 /* FIXME: Handle any clino readings */
5188 r = process_diving(fr, to, (first_stn == To) ^ fRev,
5189 fDepthChange);
5190 break;
5191 case STYLE_CYLPOLAR:
5192 r = process_cylpolar(fr, to, (first_stn == To) ^ fRev,
5193 fDepthChange);
5194 break;
5195 default:
5196 r = 0; /* avoid warning */
5197 BUG("bad style");
5199 pcs->flags = save_flags;
5200 if (!r) skipline();
5202 /* Swap fr and to back to how they were for next line */
5203 if (fRev) {
5204 prefix *t = fr;
5205 fr = to;
5206 to = t;
5210 fRev = false;
5211 ctype = backctype = CTYPE_OMIT;
5212 fDepthChange = false;
5214 /* ordering may omit clino reading, so set up default here */
5215 /* this is also used if clino reading is the omit character */
5216 VAL(Clino) = VAL(BackClino) = 0;
5217 LOC(Clino) = LOC(BackClino) = -1;
5218 WID(Clino) = WID(BackClino) = 0;
5220 inferred_equate:
5222 fMulti = true;
5223 while (1) {
5224 process_eol();
5225 skipblanks();
5226 if (isData(ch)) break;
5227 if (!isComm(ch)) {
5228 return;
5231 break;
5232 case IgnoreAll:
5233 skipline();
5234 /* fall through */
5235 case End:
5236 if (!fMulti) {
5237 /* Compass ignore flag is 'X' */
5238 if ((compass_dat_flags & BIT('X' - 'A'))) {
5239 process_eol();
5240 return;
5242 if (VAR(Dx) == HUGE_REAL &&
5243 VAR(Dy) == HUGE_REAL &&
5244 VAR(Dz) == HUGE_REAL) {
5245 // Walls variance override of `(?)` or equivalent turns the leg
5246 // into a "nosurvey" leg.
5247 (void)process_nosurvey(fr, to, (first_stn == To));
5248 return;
5250 if (fRev) {
5251 prefix *t = fr;
5252 fr = to;
5253 to = t;
5255 if (fTopofil) {
5256 VAL(Tape) = VAL(ToCount) - VAL(FrCount);
5257 LOC(Tape) = LOC(ToCount);
5258 WID(Tape) = WID(ToCount);
5260 /* Note: frdepth == todepth test works regardless of fDepthChange
5261 * (frdepth always zero, todepth is change of depth) and also
5262 * works for STYLE_NORMAL (both remain 0) */
5263 if (TSTBIT(pcs->infer, INFER_EQUATES) &&
5264 (VAL(Tape) == (real)0.0 || VAL(Tape) == HUGE_REAL) &&
5265 (VAL(BackTape) == (real)0.0 || VAL(BackTape) == HUGE_REAL) &&
5266 VAL(FrDepth) == VAL(ToDepth)) {
5267 if (!TSTBIT(pcs->infer, INFER_EQUATES_SELF_OK) || fr != to)
5268 process_equate(fr, to);
5269 process_eol();
5270 return;
5272 if (fTopofil) {
5273 VAL(Tape) *= pcs->units[Q_COUNT] * pcs->sc[Q_COUNT];
5274 } else if (VAL(Tape) != HUGE_REAL) {
5275 VAL(Tape) *= pcs->units[Q_LENGTH];
5276 VAL(Tape) -= pcs->z[Q_LENGTH];
5277 VAL(Tape) *= pcs->sc[Q_LENGTH];
5279 if (VAL(BackTape) != HUGE_REAL) {
5280 VAL(BackTape) *= pcs->units[Q_BACKLENGTH];
5281 VAL(BackTape) -= pcs->z[Q_BACKLENGTH];
5282 VAL(BackTape) *= pcs->sc[Q_BACKLENGTH];
5283 if (VAL(Tape) != HUGE_REAL) {
5284 real diff = VAL(Tape) - VAL(BackTape);
5285 if (sqrd(diff / 3.0) > VAR(Tape) + VAR(BackTape)) {
5286 /* fore and back readings differ by more than 3 sds */
5287 /* TRANSLATORS: %s is replaced by the amount the readings disagree
5288 * by, e.g. "0.12m" or "0.2ft". */
5289 warn_readings_differ(/*TAPE reading and BACKTAPE reading disagree by %s*/97,
5290 diff, get_length_units(Q_LENGTH), Tape, BackTape);
5292 VAL(Tape) = VAL(Tape) / VAR(Tape) + VAL(BackTape) / VAR(BackTape);
5293 VAR(Tape) = (VAR(Tape) + VAR(BackTape)) / 4;
5294 VAL(Tape) *= VAR(Tape);
5295 } else {
5296 VAL(Tape) = VAL(BackTape);
5297 VAR(Tape) = VAR(BackTape);
5299 } else if (VAL(Tape) == HUGE_REAL) {
5300 compile_diagnostic_reading(DIAG_ERR, Tape, /*Tape reading may not be omitted*/94);
5301 process_eol();
5302 return;
5304 int implicit_splay = TSTBIT(pcs->flags, FLAGS_IMPLICIT_SPLAY);
5305 pcs->flags &= ~(BIT(FLAGS_ANON_ONE_END) | BIT(FLAGS_IMPLICIT_SPLAY));
5306 int save_flags = pcs->flags;
5307 if (implicit_splay) {
5308 pcs->flags |= BIT(FLAGS_SPLAY);
5310 if (compass_dat_flags) {
5311 pcs->flags |= convert_compass_dat_flags(compass_dat_flags);
5313 switch (pcs->style) {
5314 case STYLE_NORMAL:
5315 process_normal(fr, to, (first_stn == To) ^ fRev,
5316 ctype, backctype);
5317 break;
5318 case STYLE_DIVING:
5319 /* FIXME: Handle any clino readings */
5320 process_diving(fr, to, (first_stn == To) ^ fRev,
5321 fDepthChange);
5322 break;
5323 case STYLE_CYLPOLAR:
5324 process_cylpolar(fr, to, (first_stn == To) ^ fRev,
5325 fDepthChange);
5326 break;
5327 default:
5328 BUG("bad style");
5330 pcs->flags = save_flags;
5332 process_eol();
5333 return;
5335 do {
5336 process_eol();
5337 skipblanks();
5338 } while (isComm(ch));
5339 goto again;
5340 default:
5341 BUG("Unknown reading in ordering");
5346 static int
5347 process_lrud(prefix *stn)
5349 SVX_ASSERT(next_lrud);
5350 lrud * xsect = osnew(lrud);
5351 xsect->stn = stn;
5352 xsect->l = (VAL(Left) * pcs->units[Q_LEFT] - pcs->z[Q_LEFT]) * pcs->sc[Q_LEFT];
5353 xsect->r = (VAL(Right) * pcs->units[Q_RIGHT] - pcs->z[Q_RIGHT]) * pcs->sc[Q_RIGHT];
5354 xsect->u = (VAL(Up) * pcs->units[Q_UP] - pcs->z[Q_UP]) * pcs->sc[Q_UP];
5355 xsect->d = (VAL(Down) * pcs->units[Q_DOWN] - pcs->z[Q_DOWN]) * pcs->sc[Q_DOWN];
5356 xsect->meta = pcs->meta;
5357 if (pcs->meta) ++pcs->meta->ref_count;
5358 xsect->next = NULL;
5359 *next_lrud = xsect;
5360 next_lrud = &(xsect->next);
5362 return 1;
5365 static void
5366 data_passage(void)
5368 prefix *stn = NULL;
5369 const reading *ordering;
5371 for (ordering = pcs->ordering ; ; ordering++) {
5372 skipblanks();
5373 switch (*ordering) {
5374 case Station:
5375 stn = read_prefix(PFX_STATION);
5376 break;
5377 case Left: case Right: case Up: case Down: {
5378 reading r = *ordering;
5379 read_reading(r, true);
5380 if (VAL(r) == HUGE_REAL) {
5381 if (!isOmit(ch)) {
5382 compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found ā€œ%sā€*/9);
5383 } else {
5384 nextch();
5386 VAL(r) = -1;
5388 break;
5390 case Ignore:
5391 skipword(); break;
5392 case IgnoreAll:
5393 skipline();
5394 /* fall through */
5395 case End: {
5396 process_lrud(stn);
5397 process_eol();
5398 return;
5400 default: BUG("Unknown reading in ordering");
5405 static int
5406 process_nosurvey(prefix *fr, prefix *to, bool fToFirst)
5408 nosurveylink *link;
5410 /* Suppress "unused fixed point" warnings for these stations */
5411 fr->sflags |= BIT(SFLAGS_USED);
5412 to->sflags |= BIT(SFLAGS_USED);
5414 /* add to linked list which is dealt with after network is solved */
5415 link = osnew(nosurveylink);
5416 if (fToFirst) {
5417 link->to = StnFromPfx(to);
5418 link->fr = StnFromPfx(fr);
5419 } else {
5420 link->fr = StnFromPfx(fr);
5421 link->to = StnFromPfx(to);
5423 link->flags = pcs->flags | (STYLE_NOSURVEY << FLAGS_STYLE_BIT0);
5424 link->meta = pcs->meta;
5425 if (pcs->meta) ++pcs->meta->ref_count;
5426 link->next = nosurveyhead;
5427 nosurveyhead = link;
5428 return 1;
5431 static void
5432 data_nosurvey(void)
5434 prefix *fr = NULL, *to = NULL;
5436 bool fMulti = false;
5438 reading first_stn = End;
5440 const reading *ordering;
5442 again:
5444 for (ordering = pcs->ordering ; ; ordering++) {
5445 skipblanks();
5446 switch (*ordering) {
5447 case Fr:
5448 fr = read_prefix(PFX_STATION|PFX_ALLOW_ROOT);
5449 if (first_stn == End) first_stn = Fr;
5450 break;
5451 case To:
5452 to = read_prefix(PFX_STATION|PFX_ALLOW_ROOT);
5453 if (first_stn == End) first_stn = To;
5454 break;
5455 case Station:
5456 fr = to;
5457 to = read_prefix(PFX_STATION);
5458 first_stn = To;
5459 break;
5460 case Ignore:
5461 skipword(); break;
5462 case IgnoreAllAndNewLine:
5463 skipline();
5464 /* fall through */
5465 case Newline:
5466 if (fr != NULL) {
5467 if (!process_nosurvey(fr, to, first_stn == To))
5468 skipline();
5470 if (ordering[1] == End) {
5471 do {
5472 process_eol();
5473 skipblanks();
5474 } while (isComm(ch));
5475 if (!isData(ch)) {
5476 return;
5478 goto again;
5480 fMulti = true;
5481 while (1) {
5482 process_eol();
5483 skipblanks();
5484 if (isData(ch)) break;
5485 if (!isComm(ch)) {
5486 return;
5489 break;
5490 case IgnoreAll:
5491 skipline();
5492 /* fall through */
5493 case End:
5494 if (!fMulti) {
5495 (void)process_nosurvey(fr, to, first_stn == To);
5496 process_eol();
5497 return;
5499 do {
5500 process_eol();
5501 skipblanks();
5502 } while (isComm(ch));
5503 goto again;
5504 default: BUG("Unknown reading in ordering");
5509 /* totally ignore a line of survey data */
5510 static void
5511 data_ignore(void)
5513 skipline();
5514 process_eol();