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
44 #if PROJ_VERSION_MAJOR < 8
45 # define proj_context_errno_string(CTX, ERR) proj_errno_string(ERR)
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>
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
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))
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
);
94 // Clino omitted. VAL() should be set to 0.0.
96 // An actual clino reading.
98 // An explicit plumb (U/D/UP/DOWN/+V/-V for reading).
100 // An inferred plumb (+90 or -90 and *infer plumbs).
102 // An explicit horizontal leg (H/LEVEL for reading).
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);
132 fp
->offset
= ftell(file
.fh
);
133 if (fp
->offset
== -1)
134 fatalerror_in_file(file
.filename
, 0, /*Error reading file*/18);
138 set_pos(const filepos
*fp
)
141 if (fseek(file
.fh
, fp
->offset
, SEEK_SET
) == -1)
142 fatalerror_in_file(file
.filename
, 0, /*Error reading file*/18);
146 report_parent(parse
* p
) {
148 report_parent(p
->parent
);
149 /* Force re-report of include tree for further errors in
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
);
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
166 file
.reported_where
= true;
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. */
181 int c
= GETC(file
.fh
);
182 /* Note: isEol() is true for EOF */
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
= ' ';
191 /* If we have a location in the line for the error, indicate it. */
194 while (--col
) PUTC(' ', 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);
211 /* Rewind to beginning of line. */
212 long cur_pos
= ftell(file
.fh
);
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. */
219 int c
= GETC(file
.fh
);
220 /* Note: isEol() is true for EOF */
222 // Change tabs to spaces for consistency with how we show context
223 // lines for other diagnostics.
224 if (c
== '\t') c
= ' ';
228 /* Revert to where we were. */
229 if (fseek(file
.fh
, cur_pos
, SEEK_SET
) == -1) {
231 fatalerror_in_file(file
.filename
, 0, /*Error reading file*/18);
237 static int caret_width
= 0;
240 compile_v_report_fpos(int diag_flags
, long fpos
, int en
, va_list ap
)
242 int severity
= (diag_flags
& DIAG_SEVERITY_MASK
);
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
;
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
) {
260 if (line
!= file
.line
) {
261 file
.lpos
+= prev_line_len
;
263 if (diag_flags
& DIAG_SKIP
) skipline();
268 compile_diagnostic(int diag_flags
, int en
, ...)
273 int diag_context_code
= (diag_flags
& DIAG_CONTEXT_MASK
);
274 if (diag_context_code
) {
276 if (diag_context_code
!= DIAG_COL
&& diag_context_code
!= DIAG_TOKEN
) {
279 switch (diag_context_code
) {
286 while (!isBlank(ch
) && !isComm(ch
) && !isEol(ch
)) {
292 while (isdigit(ch
)) {
298 while (isdigit(ch
) || ch
== '.') {
305 len
= ftell(file
.fh
);
308 /* We want to include any quotes, so can't use s_len(&p). */
309 len
= ftell(file
.fh
) - len
;
313 filepos fp_last_nonblank
= {0}; // Initialise to avoid warning.
314 int len_last_nonblank
= len
;
315 while (!isComm(ch
) && !isEol(ch
)) {
317 bool non_blank
= !isBlank(ch
);
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
);
331 if (isMinus(ch
) || isPlus(ch
)) {
335 while (isdigit(ch
)) {
343 while (isdigit(ch
)) {
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
);
360 compile_diagnostic_reading(int diag_flags
, reading r
, int en
, ...)
363 int severity
= (diag_flags
& DIAG_SEVERITY_MASK
);
365 caret_width
= WID(r
);
366 compile_v_report_fpos(severity
, LOC(r
) + caret_width
, en
, ap
);
371 compile_error_reading_skip(reading r
, int en
, ...)
375 caret_width
= WID(r
);
376 compile_v_report_fpos(DIAG_ERR
, LOC(r
) + caret_width
, en
, ap
);
382 compile_diagnostic_at(int diag_flags
, const char * filename
, unsigned line
, int en
, ...)
385 int severity
= (diag_flags
& DIAG_SEVERITY_MASK
);
387 v_report(severity
, filename
, line
, 0, en
, ap
);
393 compile_diagnostic_pfx(int diag_flags
, const prefix
* pfx
, int en
, ...)
396 int severity
= (diag_flags
& DIAG_SEVERITY_MASK
);
398 v_report(severity
, pfx
->filename
, pfx
->line
, 0, en
, ap
);
404 compile_diagnostic_token_show(int diag_flags
, int en
)
408 while (!isBlank(ch
) && !isComm(ch
) && !isEol(ch
)) {
409 s_appendch(&p
, (char)ch
);
413 compile_diagnostic(diag_flags
|DIAG_WIDTH(s_len(&p
)), en
, s_str(&p
));
416 compile_diagnostic(DIAG_ERR
|DIAG_COL
, en
, "");
421 compile_error_string(const char * s
, int en
, ...)
427 caret_width
= strlen(s
);
428 fpos
= ftell(file
.fh
);
430 compile_v_report_fpos(DIAG_ERR
, fpos
, en
, ap
);
434 /* This function makes a note where to put output files */
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 */
444 lf
= baseleaf_from_fnm(fnm
);
445 p
= use_path(fnm_output_base
, lf
);
447 osfree(fnm_output_base
);
449 fnm_output_base_is_dir
= 0;
456 while (!isBlank(ch
) && !isComm(ch
) && !isEol(ch
)) nextch();
462 while (isBlank(ch
)) nextch();
468 while (!isEol(ch
)) nextch();
480 compile_diagnostic(DIAG_ERR
|DIAG_TAIL
, /*End of line not blank*/15);
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
491 if (ch
== eolchar
|| !isEol(ch
)) {
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
;
502 process_non_data_line(void)
506 if (isData(ch
)) return false;
519 read_reading(reading r
, bool f_optional
)
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;
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
);
548 if (n_readings
> 1) VAR(r
) /= sqrt(n_readings
);
552 read_bearing_or_omit(reading r
)
555 bool quadrants
= false;
556 q_quantity q
= Q_NULL
;
560 if (pcs
->f_bearing_quadrants
)
565 if (pcs
->f_backbearing_quadrants
)
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
);
576 if (n_readings
> 1) VAR(r
) /= sqrt(n_readings
);
579 // Set up settings for reading Compass DAT or MAK.
581 initialise_common_compass_settings(void)
583 short *t
= ((short*)osmalloc(ossizeof(short) * 257)) + 1;
585 t
[EOF
] = SPECIAL_EOL
;
586 memset(t
, 0, sizeof(short) * 33);
587 for (i
= 33; i
< 127; i
++) t
[i
] = SPECIAL_NAMES
;
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
;
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;
611 update_output_separator();
614 /* For reading Compass MAK files which have a freeform syntax */
616 nextch_handling_eol(void)
619 while (ch
!= EOF
&& isEol(ch
)) {
625 get_token_and_check_len(const char *expect
, size_t len
)
628 if (s_eqlen(&token
, expect
, len
))
630 compile_diagnostic(DIAG_ERR
|DIAG_TOKEN
, /*Expecting ā%sā*/497, expect
);
638 compile_diagnostic(DIAG_ERR
|DIAG_COL
, /*Expecting ā%sā*/497, ":");
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)
658 data_file_compass_dat_or_clp(bool is_clp
)
660 initialise_common_compass_settings();
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
) |
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.
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
;
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.
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.
728 if (GET_TOKEN_AND_CHECK("SURVEY") &&
729 GET_TOKEN_AND_CHECK_COLON("TEAM")) {
730 // Value is on the next line.
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
];
742 (void)read_numeric(false);
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.
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
;
772 // CORRECTIONS and CORRECTIONS2 have already been applied to data in
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);
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));
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();
816 pcs
->ordering
= NULL
; /* Avoid free() of static array. */
821 data_file_compass_dat(void)
823 data_file_compass_dat_or_clp(false);
827 data_file_compass_clp(void)
829 data_file_compass_dat_or_clp(true);
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.
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;
853 string path
= S_INIT
;
854 s_donate(&path
, path_from_fnm(file
.filename
));
856 struct mak_folder
*next
;
858 } *folder_stack
= NULL
;
860 while (ch
!= EOF
&& !ferror(file
.fh
)) {
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
)) {
874 // Process the previous @ command using the datum from &.
875 char *proj_str
= img_compass_utm_proj_str(datum
,
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
,
887 file
.line
= saved_line
;
888 file
.lpos
= saved_lpos
;
889 if (!pcs
->proj_str
) {
890 pcs
->proj_str
= proj_str
;
892 proj_str_out
= osstrdup(proj_str
);
894 pcs
->input_convergence
= HUGE_REAL
;
901 data_file(s_str(&path
), s_str(&dat_fnm
));
905 while (ch
!= ';' && ch
!= EOF
) {
906 nextch_handling_eol();
909 prefix
*name
= read_prefix(PFX_STATION
|PFX_OPT
);
911 scan_compass_station_name(name
);
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') {
925 nextch_handling_eol();
926 } else if (ch
== 'M' || ch
== 'm') {
927 nextch_handling_eol();
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);
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
);
956 if (fix_result
< 0) {
957 compile_diagnostic(DIAG_ERR
|DIAG_WORD
, /*Station already fixed or equated to a fixed point*/46);
959 compile_diagnostic(DIAG_WARN
|DIAG_WORD
, /*Station already fixed at the same coordinates*/55);
962 compile_diagnostic_pfx(DIAG_INFO
, name
, /*Previously fixed or equated here*/493);
964 while (ch
!= ']' && ch
!= EOF
) nextch_handling_eol();
966 nextch_handling_eol();
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();
984 utm_zone
= read_int(-60, 60);
986 if (ch
== ';') nextch_handling_eol();
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
);
997 pcs
->proj_str
= proj_str
;
999 proj_str_out
= osstrdup(proj_str
);
1003 invalidate_pj_cached();
1012 while (ch
!= ';' && !isEol(ch
)) {
1013 s_appendch(&p
, (char)ch
);
1015 /* Ignore trailing blanks. */
1016 if (!isBlank(ch
)) datum_len
= c
;
1019 if (ch
== ';') nextch_handling_eol();
1020 datum
= img_parse_compass_datum_string(s_str(&p
), datum_len
);
1022 goto update_proj_str
;
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
);
1033 while (ch
!= ';' && !isEol(ch
)) {
1037 s_appendch(&path
, (char)ch
);
1040 if (ch
== ';') nextch_handling_eol();
1044 // Leave subdirectory.
1045 struct mak_folder
*p
= folder_stack
;
1046 if (folder_stack
== NULL
) {
1047 // FIXME: Error? Check what Compass does.
1050 s_truncate(&path
, folder_stack
->len
);
1051 folder_stack
= folder_stack
->next
;
1055 if (ch
== ';') nextch_handling_eol();
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.
1064 real easting
= read_numeric(false);
1066 if (ch
!= ',') break;
1068 real northing
= read_numeric(false);
1070 if (ch
!= ',') break;
1072 real elevation
= read_numeric(false);
1074 if (ch
!= ',') break;
1076 int zone
= read_int(-60, 60);
1078 if (ch
!= ',') break;
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.
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
1093 (void)convergence_angle
;
1094 if (ch
== ';') nextch_handling_eol();
1098 nextch_handling_eol();
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
;
1114 // The current Walls reference point and CRS details.
1118 // img_DATUM_* code from src/img.h or -1 if no .REF in effect.
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
;
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
1143 static walls_macro
**walls_macros_wpj
= NULL
;
1144 static walls_macro
**walls_macros
= NULL
;
1147 walls_swap_macro_tables()
1149 walls_macro
**tmp
= walls_macros_wpj
;
1150 walls_macros_wpj
= walls_macros
;
1154 // Takes ownership of the contents of p_name and of value.
1155 // Passing NULL for value sets empty string.
1157 walls_set_macro(walls_macro
***table
, string
*p_name
, char *val
)
1159 //printf("MACRO: $|%s|=\"%s\":\n", name, val);
1161 *table
= osmalloc(WALLS_MACRO_HASH_SIZE
* ossizeof(walls_macro
*));
1162 for (size_t i
= 0; i
< WALLS_MACRO_HASH_SIZE
; i
++)
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
];
1170 if (s_eqlen(p_name
, p
->name
, p
->name_len
)) {
1171 // Update existing definition of macro.
1180 walls_macro
*entry
= osnew(walls_macro
);
1181 entry
->name_len
= s_len(p_name
);
1182 entry
->name
= s_steal(p_name
);
1184 entry
->next
= (*table
)[h
];
1185 (*table
)[h
] = entry
;
1188 // Returns NULL if not set.
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
];
1197 if (name_len
== p
->name_len
&& memcmp(name
, p
->name
, name_len
) == 0) {
1198 return p
->value
? p
->value
: "";
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
}
1245 WALLS_UNITS_OPT_CASE
,
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
,
1268 WALLS_UNITS_OPT_SAVE
,
1269 WALLS_UNITS_OPT_TAPE
,
1270 WALLS_UNITS_OPT_TYPEAB
,
1271 WALLS_UNITS_OPT_TYPEVB
,
1273 WALLS_UNITS_OPT_UVH
,
1274 WALLS_UNITS_OPT_UVV
,
1277 WALLS_UNITS_OPT_NULL
= -1
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
)},
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).
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?
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.
1393 struct walls_options
*next
;
1396 static walls_options
* p_walls_options
;
1398 static const walls_options walls_options_default
= {
1400 { NULL
, NULL
, NULL
},
1404 WallsSRVFr
, WallsSRVTo
, WallsSRVTape
, WallsSRVComp
, WallsSRVClino
,
1405 WallsSRVHeights
, WallsSRVExtras
, End
1408 // data_order_rect[7]
1410 WallsSRVFr
, WallsSRVTo
, Dx
, Dy
, Dz
,
1417 // fix_station_flags
1420 // compass_dat_flags
1431 push_walls_options(void)
1433 settings
*pcsNew
= osnew(settings
);
1434 *pcsNew
= *pcs
; /* copy contents */
1435 pcsNew
->begin_lineno
= file
.line
;
1439 // Walls-specific settings.
1440 walls_options
*new_options
= osnew(walls_options
);
1441 if (!p_walls_options
) {
1442 *new_options
= walls_options_default
;
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
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
;
1463 pop_walls_options(void)
1465 pcs
->ordering
= NULL
; /* Avoid free() of static array. */
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
]);
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
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
;
1500 for (int i
= 128; i
< 256; i
++) t
[i
] = SPECIAL_NAMES
;
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
;
1519 pcs
->begin_lineno
= 0;
1520 // Spec says "maximum of eight characters" - we currently allow arbitrarily
1522 pcs
->Truncate
= INT_MAX
;
1523 pcs
->infer
= BIT(INFER_EQUATES
) |
1525 // Walls cartesian data is aligned to True North.
1526 pcs
->cartesian_north
= TRUE_NORTH
;
1527 pcs
->cartesian_rotation
= 0.0;
1533 // "[S]et all parameters (including the current name prefix) to their
1534 // defaults" but not the segment. We currently ignore the segment anyway.
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
;
1551 read_walls_angle(real default_units
)
1553 real angle
= read_numeric(false);
1554 if (isalpha((unsigned char)ch
)) {
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') {
1560 angle
*= M_PI
/ 180.0;
1561 } else if (s_str(&uctoken
)[0] == 'G') {
1563 angle
*= M_PI
/ 200.0;
1564 } else if (s_str(&uctoken
)[0] == 'M') {
1566 angle
*= M_PI
/ 3200.0;
1569 compile_diagnostic(DIAG_ERR
|DIAG_COL
,
1570 /*Expecting ā%sā, ā%sā, or ā%sā*/188,
1574 angle
*= default_units
;
1580 read_walls_distance(bool f_optional
, real default_units
)
1583 if (ch
== 'i' || ch
== 'I') {
1584 // Length specified in inches only, e.g. `i6` is 6 inches.
1588 distance
= read_numeric(f_optional
);
1589 if (distance
!= HUGE_REAL
) {
1590 if (isalpha((unsigned char)ch
)) {
1593 // Only one letter is allowed here.
1594 if (s_str(&uctoken
)[1] != '\0') goto bad_distance_units
;
1595 switch (s_str(&uctoken
)[0]) {
1601 distance
*= METRES_PER_FOOT
;
1605 real inches
= read_numeric(false);
1606 distance
+= inches
/ 12.0;
1608 distance
*= METRES_PER_FOOT
;
1612 compile_diagnostic(DIAG_ERR
|DIAG_COL
,
1613 /*Expecting ā%sā or ā%sā*/103, "F", "M");
1616 distance
*= default_units
;
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
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
1639 if (ch
== ',' || ch
== ')') {
1640 // Use default variance, which is exact for a fixed point.
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.
1651 if (ch
== 'R' || ch
== 'r') {
1656 val_h
= read_walls_distance(false, true);
1657 if (ch
== 'F' || ch
== 'f') {
1658 val_h
*= METRES_PER_FOOT
;
1660 } else if (ch
== 'M' || ch
== 'm') {
1663 val_h
*= pcs
->units
[Q_LENGTH
];
1672 // Use default variance, which is exact for a fixed point.
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.
1683 if (ch
== 'R' || ch
== 'r') {
1688 val_v
= read_number(false, true);
1689 if (ch
== 'F' || ch
== 'f') {
1690 val_v
*= METRES_PER_FOOT
;
1692 } else if (ch
== 'M' || ch
== 'm') {
1695 val_v
*= pcs
->units
[Q_LENGTH
];
1702 compile_diagnostic(DIAG_ERR
|DIAG_COL
,
1703 /*Expecting ā%sā*/497, ")");
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
;
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;
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
1729 *p_var_xy
= var(Q_POS
) / 3.0 + var(Q_LENGTH
) / 2.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
;
1737 *p_var_z
= val_v
* val_v
;
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
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.
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
) {
1764 #ifdef DEBUG_WALLS_FLAGS
1765 bool printed
= false;
1769 if (isComm(ch
) || isEol(ch
)) break;
1770 if (quoted
&& ch
== '"') {
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
)) {
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.
1806 printf("*** Unknown flags:");
1808 printf(" %s", s_str(&token
));
1811 if (check_for_quote
) break;
1813 #ifdef DEBUG_WALLS_FLAGS
1814 if (printed
) printf("\n");
1816 return station_flags
;
1820 parse_walls_segment(unsigned long* p_compass_dat_flags
)
1822 *p_compass_dat_flags
= 0;
1823 const unsigned long valid_compass_dat_flags
=
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".
1837 if (ch
== '/' || ch
== '\\') {
1838 // Treat absolute path the same as a relative path.
1841 while (ch
>= 'A' && ch
<= 'Z') {
1842 possible_compass_dat_flags
|= BIT(ch
- 'A');
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
;
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
;
1890 while (!isEol(ch
)) {
1892 if (s_empty(&token
) && isComm(ch
)) {
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
));
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
1930 compile_diagnostic(DIAG_ERR
|DIAG_COL
,
1931 /*Expecting ā%sā*/497, "=");
1940 case WALLS_UNITS_OPT_METERS
:
1941 pcs
->units
[Q_LENGTH
] =
1944 pcs
->units
[Q_DZ
] = 1.0;
1946 case WALLS_UNITS_OPT_FEET
:
1947 pcs
->units
[Q_LENGTH
] =
1950 pcs
->units
[Q_DZ
] = METRES_PER_FOOT
;
1952 case WALLS_UNITS_OPT_D
:
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
;
1964 set_pos(&fp_option
);
1965 (void)nextch(); // Skip the `=`.
1966 compile_diagnostic(DIAG_ERR
|DIAG_COL
, /*Expecting ā%sā or ā%sā*/103, "F", "M");
1970 case WALLS_UNITS_OPT_A
:
1972 // It seems Walls only checks the initial letter.
1973 if (s_str(&uctoken
)[0] == 'D') {
1975 pcs
->units
[Q_BEARING
] = M_PI
/ 180.0;
1976 } else if (s_str(&uctoken
)[0] == 'G') {
1978 pcs
->units
[Q_BEARING
] = M_PI
/ 200.0;
1979 } else if (s_str(&uctoken
)[0] == 'M') {
1981 pcs
->units
[Q_BEARING
] = M_PI
/ 3200.0;
1985 set_pos(&fp_option
);
1986 (void)nextch(); // Skip the `=`.
1987 compile_diagnostic(DIAG_ERR
|DIAG_COL
,
1988 /*Expecting ā%sā, ā%sā, or ā%sā*/188,
1993 case WALLS_UNITS_OPT_AB
:
1995 // It seems Walls only checks the initial letter.
1996 if (s_str(&uctoken
)[0] == 'D') {
1998 pcs
->units
[Q_BACKBEARING
] = M_PI
/ 180.0;
1999 } else if (s_str(&uctoken
)[0] == 'G') {
2001 pcs
->units
[Q_BACKBEARING
] = M_PI
/ 200.0;
2002 } else if (s_str(&uctoken
)[0] == 'M') {
2004 pcs
->units
[Q_BACKBEARING
] = M_PI
/ 3200.0;
2008 set_pos(&fp_option
);
2009 (void)nextch(); // Skip the `=`.
2010 compile_diagnostic(DIAG_ERR
|DIAG_COL
,
2011 /*Expecting ā%sā, ā%sā, or ā%sā*/188,
2016 case WALLS_UNITS_OPT_V
:
2018 pcs
->f_clino_percent
= false;
2019 // It seems Walls only checks the initial letter.
2020 if (s_str(&uctoken
)[0] == 'D') {
2022 pcs
->units
[Q_GRADIENT
] = M_PI
/ 180.0;
2023 } else if (s_str(&uctoken
)[0] == 'G') {
2025 pcs
->units
[Q_GRADIENT
] = M_PI
/ 200.0;
2026 } else if (s_str(&uctoken
)[0] == 'M') {
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;
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");
2043 case WALLS_UNITS_OPT_VB
:
2045 pcs
->f_backclino_percent
= false;
2046 // It seems Walls only checks the initial letter.
2047 if (s_str(&uctoken
)[0] == 'D') {
2049 pcs
->units
[Q_BACKGRADIENT
] = M_PI
/ 180.0;
2050 } else if (s_str(&uctoken
)[0] == 'G') {
2052 pcs
->units
[Q_BACKGRADIENT
] = M_PI
/ 200.0;
2053 } else if (s_str(&uctoken
)[0] == 'M') {
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;
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");
2070 case WALLS_UNITS_OPT_S
:
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') {
2078 pcs
->units
[Q_DZ
] = 1.0;
2079 } else if (s_str(&uctoken
)[0] == 'F') {
2082 pcs
->units
[Q_DZ
] = METRES_PER_FOOT
;
2086 set_pos(&fp_option
);
2087 (void)nextch(); // Skip the `=`.
2088 compile_diagnostic(DIAG_ERR
|DIAG_COL
, /*Expecting ā%sā or ā%sā*/103, "F", "M");
2092 case WALLS_UNITS_OPT_ORDER
:
2094 int order
= match_tok(walls_order_tab
,
2095 TABSIZE(walls_order_tab
));
2097 compile_diagnostic(DIAG_ERR
|DIAG_TOKEN
, /*Data style ā%sā unknown*/65, s_str(&token
));
2101 bool rect
= (order
& (1 << 24));
2103 order
&= ((1 << 24) - 1);
2105 p
= p_walls_options
->data_order_rect
+ 2;
2108 p
= p_walls_options
->data_order_ct
+ 2;
2111 *p
++ = (order
& 0xff);
2114 if (!rect
) *p
++ = WallsSRVHeights
;
2115 *p
++ = WallsSRVExtras
;
2118 case WALLS_UNITS_OPT_DECL
:
2119 pcs
->z
[Q_DECLINATION
] = -read_walls_angle(M_PI
/ 180.0);
2121 case WALLS_UNITS_OPT_INCA
:
2122 pcs
->z
[Q_BEARING
] = -read_walls_angle(pcs
->units
[Q_BEARING
]);
2124 case WALLS_UNITS_OPT_INCAB
:
2125 pcs
->z
[Q_BACKBEARING
] = -read_walls_angle(pcs
->units
[Q_BACKBEARING
]);
2127 case WALLS_UNITS_OPT_INCD
:
2128 pcs
->z
[Q_LENGTH
] = -read_walls_distance(false, pcs
->units
[Q_LENGTH
]);
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) {
2135 set_pos(&fp_option
);
2136 compile_diagnostic(DIAG_WARN
|DIAG_TOKEN
, /*Unknown command ā%sā*/12, s_str(&token
));
2140 case WALLS_UNITS_OPT_INCV
:
2141 pcs
->z
[Q_GRADIENT
] = -read_walls_angle(pcs
->units
[Q_GRADIENT
]);
2143 case WALLS_UNITS_OPT_INCVB
:
2144 pcs
->z
[Q_BACKGRADIENT
] = -read_walls_angle(pcs
->units
[Q_BACKGRADIENT
]);
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);
2151 case WALLS_UNITS_OPT_RECT
:
2152 // There are two different RECT options, one with a
2153 // parameter and one without!
2157 // A bearing to rotate cartesian data by, with 0 meaning true
2159 pcs
->cartesian_rotation
= read_walls_angle(M_PI
/ 180.0);
2161 pcs
->recorded_style
= pcs
->style
= STYLE_CARTESIAN
;
2162 pcs
->ordering
= p_walls_options
->data_order_rect
;
2165 case WALLS_UNITS_OPT_CASE
:
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]) {
2182 case WALLS_UNITS_OPT_CT
:
2183 pcs
->recorded_style
= pcs
->style
= STYLE_NORMAL
;
2184 pcs
->ordering
= p_walls_options
->data_order_ct
;
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
;
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
;
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
2207 string val
= S_INIT
;
2212 case WALLS_UNITS_OPT_TAPE
:
2214 /* FIXME: Implement different taping methods? */
2215 /* IT, SS, IS, ST (default is IT). */
2217 case WALLS_UNITS_OPT_TYPEAB
:
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
;
2226 set_pos(&fp_option
);
2227 (void)nextch(); // Skip the `=`.
2228 compile_diagnostic(DIAG_ERR
|DIAG_COL
, /*Expecting ā%sā or ā%sā*/103, "C", "N");
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.
2238 compile_diagnostic(DIAG_WARN
|DIAG_TOKEN
, /*Ignoring ā%sā*/506, s_str(&token
));
2242 if (toupper(ch
) == 'X') {
2244 // FIXME: Only use foresight (but check backsight).
2249 case WALLS_UNITS_OPT_TYPEVB
:
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;
2258 set_pos(&fp_option
);
2259 (void)nextch(); // Skip the `=`.
2260 compile_diagnostic(DIAG_ERR
|DIAG_COL
, /*Expecting ā%sā or ā%sā*/103, "C", "N");
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.
2270 compile_diagnostic(DIAG_WARN
|DIAG_TOKEN
, /*Ignoring ā%sā*/506, s_str(&token
));
2274 if (toupper(ch
) == 'X') {
2276 // FIXME: Only use foresight (but check backsight).
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.
2290 compile_diagnostic(DIAG_WARN
|DIAG_TOKEN
, /*Ignoring ā%sā*/506, s_str(&token
));
2293 case WALLS_UNITS_OPT_FLAG
:
2294 // Default flag to apply to stations in #FIX.
2298 p_walls_options
->fix_station_flags
= parse_walls_flags(true);
2300 p_walls_options
->fix_station_flags
= 0;
2303 case WALLS_UNITS_OPT_RESET
:
2304 // FIXME: This should be processed before other arguments!
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");
2314 pop_walls_options();
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;
2323 case WALLS_UNITS_OPT_NULL
:
2324 if (s_str(&uctoken
)[0] == '\0' && ch
== '$') {
2325 // Macro definition.
2329 string name
= S_INIT
;
2330 while (!isBlank(ch
) && !isComm(ch
) && !isEol(ch
) && ch
!= '=') {
2331 s_appendch(&name
, ch
);
2334 if (!s_empty(&name
)) {
2337 // Set an empty value.
2338 walls_set_macro(&walls_macros
, &name
, NULL
);
2341 string val
= S_INIT
;
2343 walls_set_macro(&walls_macros
, &name
, s_steal(&val
));
2351 compile_diagnostic(DIAG_ERR
|DIAG_TOKEN
, /*Unknown command ā%sā*/12, s_str(&token
));
2353 // Skip over `=` and the rest of the argument so we handle a
2354 // typo-ed option name nicely.
2357 } while (!isBlank(ch
) && !isComm(ch
) && !isEol(ch
));
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;
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
);
2380 // We're being included standalone rather than from a WPJ file.
2381 walls_initialise_settings();
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
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.
2403 if (pcs
->style
== STYLE_NORMAL
)
2404 pcs
->ordering
= p_walls_options
->data_order_ct
;
2406 pcs
->ordering
= p_walls_options
->data_order_rect
;
2408 while (ch
!= EOF
&& !ferror(file
.fh
)) {
2412 if (ch
== ';' || isEol(ch
)) {
2415 } else if (pcs
->style
== STYLE_NORMAL
) {
2418 // Set up Dz in case it's omitted.
2427 int leading_blanks
= ftell(file
.fh
) - file
.lpos
- 1;
2430 // "Commented out" data.
2431 int start_lineno
= file
.line
;
2436 while (depth
&& ch
!= EOF
) {
2440 depth
+= (ch
== '[') - (ch
== ']');
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,
2455 /* TRANSLATORS: %s is replaced with e.g. BEGIN or .BOOK or #[ */
2456 compile_diagnostic(DIAG_ERR
|DIAG_SKIP
, /*No matching %s*/192, "#[");
2459 int blanks_after_hash
= ftell(file
.fh
) - file
.lpos
- leading_blanks
- 2;
2461 walls_cmd directive
= match_tok(walls_cmd_tab
, TABSIZE(walls_cmd_tab
));
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
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
);
2480 // Expand macros such as $(foo) in rest of line.
2481 while (!isEol(ch
)) {
2483 s_appendch(&line
, ch
);
2489 s_appendch(&line
, '$');
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
);
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
,
2506 macro
= walls_get_macro(&walls_macros_wpj
, name
, name_len
);
2509 compile_diagnostic(DIAG_ERR
, /*Macro ā%sā not defined*/499,
2510 s_str(&line
) + macro_start
);
2512 s_truncate(&line
, macro_start
);
2514 s_append(&line
, macro
);
2519 //printf("MACRO EXPANSION <%s>\n", line);
2520 // Read from the buffered macro-expanded line instead of the
2524 #ifdef HAVE_FMEMOPEN
2525 file
.fh
= fmemopen((char*)s_str(&line
), s_len(&line
), "rb");
2527 fatalerror(/*Failed to create temporary file*/498);
2530 file
.fh
= tmpfile();
2532 fatalerror(/*Failed to create temporary file*/498);
2534 fwrite(s_str(&line
), s_len(&line
), 1, file
.fh
);
2536 fseek(file
.fh
, fp_args
.offset
- file
.lpos
, SEEK_SET
);
2537 ch
= (unsigned char)s_str(&line
)[fp_args
.offset
- file
.lpos
- 1];
2540 //printf("no macros seen in <%s>\n", line);
2541 // No macros seen so rewind and read directly from the file.
2547 switch (directive
) {
2548 case WALLS_CMD_UNITS
:
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
;
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
2574 compile_diagnostic(DIAG_WARN
|DIAG_SKIP
|DIAG_TAIL
,
2575 /*End of line not blank*/15);
2579 case WALLS_CMD_FIX
: {
2583 prefix
*name
= read_walls_station(p_walls_options
->prefix
,
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
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
;
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");
2619 coord
= read_number(false, true);
2621 // FIXME: This accepts decimals on any component e.g `N40.1:1:1`.
2623 real minutes
= read_number(false, true);
2624 coord
+= minutes
/ 60.0;
2627 real seconds
= read_number(false, true);
2628 coord
+= seconds
/ 3600.0;
2631 if (negate
) coord
= -coord
;
2636 coords
[dim
] = coord
;
2639 real var_xy
= 0.0, var_z
= 0.0;
2642 read_walls_variance_overrides(&var_xy
, &var_z
);
2646 // Station note - ignore for now. Note: Must be '/'.
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
) {
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
,
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
,
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
);
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.
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) {
2711 int fix_result
= fix_station(name
, coords
);
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);
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);
2730 if (var_xy
== HUGE_REAL
&& var_z
== HUGE_REAL
) {
2731 // We've been asked to not fix horizontally or vertically!
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
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.
2757 if (isEol(ch
) || isComm(ch
)) {
2758 // Just "#FLAG" with no arguments clears the default flag.
2759 fix_station_flags
= 0;
2762 bool setting_default_flag
= isWallsSlash(ch
);
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
2788 int station_flags
= parse_walls_flags(false);
2790 if (setting_default_flag
) {
2791 fix_station_flags
= station_flags
;
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.
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
,
2811 name
->sflags
|= station_flags
;
2814 pcs
->Translate
['/'] = save_translate_slash
;
2815 pcs
->Translate
['\\'] = save_translate_bslash
;
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
;
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);
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
2841 prefix
*name
= read_walls_station(p_walls_options
->prefix
,
2843 name
->sflags
|= BIT(SFLAGS_USED
);
2847 case WALLS_CMD_SEGMENT
:
2848 parse_walls_segment(&p_walls_options
->compass_dat_flags
);
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.
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
));
2861 if (!s_empty(&line
)) {
2862 // Revert to reading from the file.
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
];
2879 walls_macro
*to_free
= p
;
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();
2897 WALLS_WPJ_CMD_ENDBOOK
,
2899 WALLS_WPJ_CMD_OPTIONS
,
2902 WALLS_WPJ_CMD_STATUS
,
2903 WALLS_WPJ_CMD_SURVEY
,
2904 WALLS_WPJ_CMD_NULL
= -1
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
}
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:
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:
2952 // - 0b00 Inherit value from parent
2953 WALLS_WPJ_TRISTATE_INHERIT
= 0,
2955 WALLS_WPJ_TRISTATE_OFF
= 1,
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.
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();
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.
3014 long name_lpos
= -1;
3015 unsigned name_lineno
= 0;
3017 string name
= S_INIT
;
3019 walls_ref
.x
= walls_ref
.y
= walls_ref
.z
= HUGE_VAL
;
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
;
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.
3037 // If the line isn't blank or a comment then process_eol() will
3038 // issue a suitable error.
3044 get_token_no_blanks();
3045 tok
= match_tok(walls_wpj_cmd_tab
, TABSIZE(walls_wpj_cmd_tab
));
3046 if (detached_nest_level
) {
3048 case WALLS_WPJ_CMD_BOOK
:
3049 ++detached_nest_level
;
3052 case WALLS_WPJ_CMD_ENDBOOK
:
3053 --detached_nest_level
;
3056 // Ignore everything else.
3064 (tok
== WALLS_WPJ_CMD_SURVEY
||
3065 tok
== WALLS_WPJ_CMD_BOOK
||
3066 tok
== WALLS_WPJ_CMD_ENDBOOK
)) {
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) {
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.
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] : "");
3095 FILE *fh
= fopen_portable(s_str(&p_walls_options
->path
),
3096 s_str(&name
), "srv", "rb", &filename
);
3098 fh
= fopen_portable(s_str(&p_walls_options
->path
),
3099 s_str(&name
), "SRV", "rb", &filename
);
3102 // Report the diagnostic at the location of the ".NAME".
3103 unsigned save_line
= file
.line
;
3104 long save_lpos
= file
.lpos
;
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
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
));
3127 compile_diagnostic(DIAG_ERR
|DIAG_TAIL
, /*Couldnāt open file ā%sā*/24, s_str(&full_file
));
3131 file
.line
= save_line
;
3132 file
.lpos
= save_lpos
;
3137 parse file_store
= file
;
3139 if (file
.fh
) file
.parent
= &file_store
;
3141 file
.filename
= filename
;
3144 file
.reported_where
= false;
3145 file
.prev_line_len
= 0;
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); */
3172 walls_ref
.x
= walls_ref
.y
= walls_ref
.z
= HUGE_VAL
;
3174 detached_or_not_srv
:
3175 pop_walls_options();
3178 if (tok
== WALLS_WPJ_CMD_NULL
) break;
3182 case WALLS_WPJ_CMD_BOOK
:
3184 push_walls_options();
3188 case WALLS_WPJ_CMD_SURVEY
:
3189 push_walls_options(); // FIXME: Eliminate redundant push and pop inside data_file_walls_srv()
3193 case WALLS_WPJ_CMD_ENDBOOK
:
3195 compile_diagnostic(DIAG_ERR
|DIAG_SKIP
, /*No matching %s*/192, ".BOOK");
3198 pop_walls_options();
3201 case WALLS_WPJ_CMD_OPTIONS
:
3204 case WALLS_WPJ_CMD_PATH
: {
3207 while (!isEol(ch
)) {
3211 s_appendch(&p_walls_options
->path
, ch
);
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));
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
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.
3263 string datum_str
= S_INIT
;
3264 read_string(&datum_str
);
3265 int datum
= img_parse_compass_datum_string(s_str(&datum_str
),
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
;
3280 compile_diagnostic(DIAG_ERR
|DIAG_FROM(fp
),
3281 /*Datum ā%sā not supported*/503,
3283 // Set a datum to avoid causing problems converting
3285 datum
= img_DATUM_WGS84
;
3290 if (datum
&& walls_ref
.zone
&& abs(walls_ref
.zone
) <= 60) {
3291 char *proj_str
= img_compass_utm_proj_str(datum
,
3293 set_declination_location(walls_ref
.x
, walls_ref
.y
, walls_ref
.z
,
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
;
3304 } else if (datum
== img_DATUM_WGS84
&& abs(walls_ref
.zone
) == 61) {
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
,
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
;
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.
3330 pop_walls_options();
3331 detached_nest_level
= 1;
3334 case WALLS_WPJ_CMD_NAME
:
3335 if (!s_empty(&name
)) {
3336 // FIXME: NAME already set
3341 name_lpos
= file
.lpos
;
3342 name_lineno
= file
.line
;
3343 while (!isEol(ch
)) {
3344 s_appendch(&name
, ch
);
3348 case WALLS_WPJ_CMD_NULL
:
3349 compile_diagnostic(DIAG_ERR
|DIAG_TOKEN
|DIAG_SKIP
, /*Unknown command ā%sā*/12, s_str(&token
));
3356 pop_walls_options();
3360 data_file_survex(void)
3362 int begin_lineno_store
= pcs
->begin_lineno
;
3363 pcs
->begin_lineno
= 0;
3366 /* Maybe a UTF-8 "BOM" - skip if so. */
3367 if (nextch() == 0xbb && nextch() == 0xbf) {
3376 if (setjmp(jbSkipLine
)) {
3377 // Recover from errors in nested functions by longjmp() to here.
3382 while (ch
!= EOF
&& !ferror(file
.fh
)) {
3383 if (!process_non_data_line()) {
3384 f_export_ok
= false;
3385 switch (pcs
->style
) {
3388 case STYLE_CYLPOLAR
:
3391 case STYLE_CARTESIAN
:
3397 case STYLE_NOSURVEY
:
3410 /* don't allow *BEGIN at the end of a file, then *EXPORT in the
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,
3420 /* Implicitly close any unclosed BEGINs from this file */
3423 } while (pcs
->begin_lineno
);
3426 pcs
->begin_lineno
= begin_lineno_store
;
3429 #define EXT3(C1, C2, C3) (((C3) << 16) | ((C2) << 8) | (C1))
3432 data_file(const char *pth
, const char *fnm
)
3443 /* file specified on command line - don't do special translation */
3444 fh
= fopenWithPthAndExt(pth
, fnm
, EXT_SVX_DATA
, "rb", &filename
);
3446 fh
= fopen_portable(pth
, fnm
, EXT_SVX_DATA
, "rb", &filename
);
3450 compile_error_string(fnm
, /*Couldnāt open file ā%sā*/24, fnm
);
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
);
3464 if (file
.fh
) file
.parent
= &file_store
;
3466 file
.filename
= filename
;
3469 file
.reported_where
= false;
3470 file
.prev_line_len
= 0;
3474 using_data_file(file
.filename
);
3477 case EXT3('d', 'a', 't'):
3478 // Compass survey data.
3479 data_file_compass_dat();
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();
3490 case EXT3('m', 'a', 'k'):
3491 // Compass project file.
3492 data_file_compass_mak();
3494 case EXT3('s', 'r', 'v'):
3495 // Walls survey data.
3496 data_file_walls_srv();
3498 case EXT3('w', 'p', 'j'):
3499 // Walls project file.
3500 data_file_walls_wpj();
3503 // Native Survex data.
3508 if (ferror(file
.fh
))
3509 fatalerror_in_file(file
.filename
, 0, /*Error reading file*/18);
3511 (void)fclose(file
.fh
);
3515 /* don't free this - it may be pointed to by prefix.file */
3516 /* osfree(file.filename); */
3522 return a
- floor(a
/ (2 * M_PI
)) * (2 * M_PI
);
3526 handle_plumb(clino_type
*p_ctype
)
3529 CLINO_NULL
=-1, CLINO_UP
, CLINO_DOWN
, CLINO_LEVEL
3531 static const sztok clino_tab
[] = {
3533 {"DOWN", CLINO_DOWN
},
3535 {"LEVEL", CLINO_LEVEL
},
3540 static const real clinos
[] = {(real
)M_PI_2
, (real
)(-M_PI_2
), (real
)0.0};
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
);
3555 } else if (isSign(ch
)) {
3558 if (toupper(ch
) == 'V') {
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 */
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... */
3574 *p_ctype
= CTYPE_OMIT
;
3575 /* no clino reading, so assume 0 with large sd */
3582 warn_readings_differ(int msgno
, real diff
, int units
,
3583 reading r_fore
, reading r_back
)
3587 diff
/= get_units_factor(units
);
3588 snprintf(buf
, sizeof(buf
), "%.2f", fabs(diff
));
3589 for (p
= buf
; *p
; ++p
) {
3593 if (*p
!= '0') z
= p
+ 1;
3599 strcpy(p
, get_units_string(units
));
3600 // FIXME: Highlight r_fore too.
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,
3609 handle_comp_units(void)
3611 reading which_comp
= End
;
3612 if (VAL(Comp
) != HUGE_REAL
) {
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
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
));
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();
3646 PJ
* pj
= proj_create(ctx
, proj_str
);
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
);
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
3680 datum
= proj_crs_get_datum_ensemble(ctx
, geodetic_crs
);
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
);
3689 proj_destroy(geodetic_crs
);
3690 PJ
* newOp
= proj_create_crs_to_crs_from_pj(ctx
, temp
, pj
, NULL
, NULL
);
3702 #if PROJ_VERSION_MAJOR < 9 || \
3703 (PROJ_VERSION_MAJOR == 9 && PROJ_VERSION_MINOR < 3)
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
);
3716 PJ_FACTORS factors
= proj_factors(pj
, lp
);
3718 #if PROJ_VERSION_MAJOR < 8 || \
3719 (PROJ_VERSION_MAJOR == 8 && PROJ_VERSION_MINOR < 1)
3720 proj_context_destroy(ctx
);
3722 return factors
.meridian_convergence
;
3726 calculate_convergence(const char *proj_str
)
3728 return calculate_convergence_lonlat(proj_str
, pcs
->dec_lon
, pcs
->dec_lat
);
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
,
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
3744 PJ
* pj_norm
= proj_normalize_for_visualization(PJ_DEFAULT_CTX
,
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
);
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. */
3763 proj_destroy(transform
);
3765 return calculate_convergence_lonlat(proj_str
, rad(x
), rad(y
));
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
;
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,
3797 return 0.0;// FIXME cache
3799 pcs
->input_convergence
= calculate_convergence(pcs
->proj_str
);
3801 return pcs
->input_convergence
;
3805 get_declination(void)
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
3814 declination
= pcs
->declination
;
3816 if (!pcs
->meta
|| pcs
->meta
->days1
== -1) {
3817 compile_diagnostic(DIAG_WARN
, /*No survey date specified - using 0 for magnetic declination*/304);
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
,
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
3839 pcs
->declination
= declination
;
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
;
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;
3877 compvar
= VAR(BackComp
);
3885 handle_clino(q_quantity q
, reading r
, real val
, bool percent
, clino_type
*p_ctype
)
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.
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
) {
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
);
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
);
3946 #ifndef NO_COVARIANCES
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);
3981 if (ctype
== CTYPE_PLUMB
|| ctype
== CTYPE_INFERPLUMB
||
3982 backctype
== CTYPE_PLUMB
|| backctype
== CTYPE_INFERPLUMB
) {
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);
4006 dz
= (clin
> (real
)0.0) ? tape
: -tape
;
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;
4017 /* Each of ctype and backctype are either CTYPE_READING/CTYPE_HORIZ
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);
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;
4034 printf("Zero length leg: vx = %f, vy = %f, vz = %f\n", vx
, vy
, vz
);
4038 /* take into account variance in LEVEL case */
4039 real var_clin
= var(Q_LEVEL
);
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;
4063 var_clin
= VAR(BackClino
);
4068 printf(" %4.2f %4.2f %4.2f\n", tape
, comp
, clin
);
4071 LcosG
= tape
* cosG
;
4075 printf("sinB = %f, cosG = %f, LcosG = %f\n", sinB
, cosG
, LcosG
);
4079 dz
= tape
* sin(clin
);
4080 /* printf("%.2f\n",clin); */
4082 printf("dx = %f\ndy = %f\ndz = %f\n", dx
, dy
, dz
);
4088 cosG2
= cosG
* cosG
;
4089 sinGcosG
= sin(clin
) * cosG
;
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;
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; */
4105 vx
= var(Q_POS
) / 3.0 + dx2
* V
+ dy2
* var_comp
+
4107 vy
= var(Q_POS
) / 3.0 + dy2
* V
+ dx2
* var_comp
+
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;
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
;
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
);
4127 printf("In DATAIN.C, vx = %f, vy = %f, vz = %f\n", vx
, vy
, vz
);
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
);
4138 printf("Just before addleg, vx = %f\n", vx
);
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
4150 process_diving(prefix
*fr
, prefix
*to
, bool fToFirst
, bool fDepthChange
)
4152 real tape
= VAL(Tape
);
4156 #ifndef NO_COVARIANCES
4157 real cxy
= 0, cyz
= 0, czx
= 0;
4160 handle_comp_units();
4162 /* depth gauge readings increase upwards with default calibration */
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
];
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
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
) {
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
));
4204 real L2
, sinB
, cosB
, dz2
, D2
;
4206 real comp
= handle_compass(&var_comp
);
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
;
4220 /* FIXME: Should use FrDepth sometimes... */
4221 dz
= (dz
* VAR(Tape
) + tape
* 2 * VAR(ToDepth
)) / vsum
;
4223 dz
= (dz
* VAR(Tape
) - tape
* 2 * VAR(ToDepth
)) / vsum
;
4227 /* FIXME: Should use FrDepth sometimes... */
4228 real F
= VAR(Tape
) * L2
+ 2 * VAR(ToDepth
) * D2
;
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
;
4246 /* FIXME: If there's a clino reading, check it against the depth reading,
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
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
) {
4269 if (!proj_str_out
&& !pcs
->proj_str
) {
4270 // The default unspecified coordinate system has no grid
4274 rotation
+= get_input_convergence();
4275 rotation
-= get_convergence();
4278 if (!proj_str_out
&& !pcs
->proj_str
) {
4279 // True north is grid north in the default unspecified coordinate
4283 rotation
-= get_convergence();
4285 case MAGNETIC_NORTH
:
4286 rotation
+= get_declination();
4291 real sinB
= sin(rotation
);
4292 real cosB
= cos(rotation
);
4294 real new_dx
= dx
* cosB
+ dy
* sinB
;
4295 dy
= dy
* cosB
- dx
* sinB
;
4298 real vx
= VAR(Dx
) * cosB
* cosB
+ VAR(Dy
) * sinB
* sinB
;
4299 real vy
= VAR(Dy
) * cosB
* cosB
- VAR(Dx
) * sinB
* sinB
;
4301 #ifndef NO_COVARIANCES
4302 real cxy
= (VAR(Dx
) + VAR(Dy
)) * sinB
* cosB
;
4304 addlegbyname(fr
, to
, fToFirst
, dx
, dy
, dz
, vx
, vy
, vz
4305 #ifndef NO_COVARIANCES
4313 read_walls_lrud(void)
4315 int end
= (ch
== '*' ? ch
: '>');
4316 while (nextch() != end
&& !isEol(ch
)) {
4317 // FIXME: Process LRUD.
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.
4326 compile_diagnostic(DIAG_WARN
|DIAG_TOKEN
, /*Ignoring ā%sā*/506,
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.
4337 read_walls_extras(unsigned long* p_compass_dat_flags
)
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
) {
4353 } else if (var_z
== HUGE_REAL
&& var_xy
!= HUGE_REAL
) {
4366 // Allow for `#SEG` after a data leg.
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
);
4373 compile_diagnostic(DIAG_ERR
|DIAG_SKIP
|DIAG_TOKEN
,
4374 /*Expecting ā%sā, ā%sā, or ā%sā*/188,
4375 "S", "SEG", "SEGMENT");
4385 read_walls_srv_to(prefix
**p_to
, unsigned long* p_compass_dat_flags
)
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`
4395 int end
= (ch
== '*' ? ch
: '>');
4398 } while (ch
!= end
&& !isComm(ch
) && !isEol(ch
));
4399 bool parse_as_lrud
= (ch
== end
);
4401 if (parse_as_lrud
) {
4402 handle_isolated_lrud
:
4403 read_walls_extras(p_compass_dat_flags
);
4405 if (!isEol(ch
) && !isComm(ch
)) {
4406 compile_diagnostic(DIAG_WARN
|DIAG_SKIP
|DIAG_TAIL
,
4407 /*End of line not blank*/15);
4414 *p_to
= read_walls_station(p_walls_options
->prefix
, true, &new);
4415 if (might_be_lrud
&& new) {
4419 // TRANSLATORS: Warning issued about a dubious case in survey data in
4420 // Walls format (.srv). Real world example:
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);
4434 if (ch
== '*' || ch
== '<') {
4435 // Odd apparently undocumented variant of isolated LRUD.
4436 goto handle_isolated_lrud
;
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
;
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
++) {
4461 switch (*ordering
) {
4463 fr
= read_prefix(PFX_STATION
|PFX_ALLOW_ROOT
|PFX_ANON
);
4464 if (first_stn
== End
) first_stn
= Fr
;
4467 to
= read_prefix(PFX_STATION
|PFX_ALLOW_ROOT
|PFX_ANON
);
4468 if (first_stn
== End
) first_stn
= To
;
4472 to
= read_prefix(PFX_STATION
);
4475 case Dx
: case Dy
: case Dz
:
4476 read_reading(*ordering
, false);
4479 // Walls SRV is always From then To.
4481 fr
= read_walls_station(p_walls_options
->prefix
, true, NULL
);
4484 if (!read_walls_srv_to(&to
, &compass_dat_flags
)) {
4485 // Isolated LRUD so don't try to parse line as a survey leg.
4489 case WallsSRVExtras
:
4490 read_walls_extras(&compass_dat_flags
);
4494 case IgnoreAllAndNewLine
:
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
))
4507 pcs
->flags
= save_flags
;
4513 if (isData(ch
)) break;
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
));
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
;
4550 } while (isComm(ch
));
4552 default: BUG("Unknown reading in ordering");
4558 process_cylpolar(prefix
*fr
, prefix
*to
, bool fToFirst
, bool fDepthChange
)
4560 real tape
= VAL(Tape
);
4564 #ifndef NO_COVARIANCES
4568 handle_comp_units();
4570 /* depth gauge readings increase upwards with default calibration */
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
];
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
) {
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
);
4594 real comp
= handle_compass(&var_comp
);
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
;
4612 addlegbyname(fr
, to
, fToFirst
, dx
, dy
, dz
, vx
, vy
, vz
4613 #ifndef NO_COVARIANCES
4620 /* Process tape/compass/clino, diving, and cylpolar styles of survey data
4621 * Also handles topofil (fromcount/tocount or count) in place of tape */
4625 prefix
*fr
= NULL
, *to
= NULL
;
4626 reading first_stn
= End
;
4628 bool fTopofil
= false, fMulti
= false;
4630 clino_type ctype
, backctype
;
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.
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;
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
++) {
4662 switch (*ordering
) {
4664 fr
= read_prefix(PFX_STATION
|PFX_ALLOW_ROOT
|PFX_ANON
);
4665 if (first_stn
== End
) first_stn
= Fr
;
4668 to
= read_prefix(PFX_STATION
|PFX_ALLOW_ROOT
|PFX_ANON
);
4669 if (first_stn
== End
) first_stn
= To
;
4672 // Compass DAT is always From then To.
4674 fr
= read_prefix(PFX_STATION
);
4675 scan_compass_station_name(fr
);
4678 to
= read_prefix(PFX_STATION
);
4679 scan_compass_station_name(to
);
4683 to
= read_prefix(PFX_STATION
);
4688 DIR_NULL
=-1, DIR_FORE
, DIR_BACK
4690 static const sztok dir_tab
[] = {
4696 tok
= match_tok(dir_tab
, TABSIZE(dir_tab
));
4704 compile_diagnostic(DIAG_ERR
|DIAG_TOKEN
|DIAG_SKIP
, /*Found ā%sā, expecting āFā or āBā*/131, s_str(&token
));
4708 do_legacy_token_warning();
4711 case Tape
: case BackTape
: {
4712 reading r
= *ordering
;
4713 read_reading(r
, true);
4714 if (VAL(r
) == HUGE_REAL
) {
4716 compile_diagnostic_token_show(DIAG_ERR
, /*Expecting numeric field, found ā%sā*/9);
4717 /* Avoid also warning about omitted tape reading. */
4722 } else if (VAL(r
) < (real
)0.0) {
4723 compile_diagnostic_reading(DIAG_WARN
, r
, /*Negative tape reading*/60);
4728 VAL(FrCount
) = VAL(ToCount
);
4729 LOC(FrCount
) = LOC(ToCount
);
4730 WID(FrCount
) = WID(ToCount
);
4731 read_reading(ToCount
, false);
4735 read_reading(FrCount
, false);
4738 read_reading(ToCount
, false);
4741 case Comp
: case BackComp
:
4742 read_bearing_or_omit(*ordering
);
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
);
4754 compile_diagnostic_token_show(DIAG_ERR
, /*Expecting numeric field, found ā%sā*/9);
4759 *p_ctype
= CTYPE_READING
;
4762 case FrDepth
: case ToDepth
:
4763 read_reading(*ordering
, false);
4766 VAL(FrDepth
) = VAL(ToDepth
);
4767 LOC(FrDepth
) = LOC(ToDepth
);
4768 WID(FrDepth
) = WID(ToDepth
);
4769 read_reading(ToDepth
, false);
4772 fDepthChange
= true;
4774 read_reading(ToDepth
, false);
4776 case CompassDATComp
:
4777 read_bearing_or_omit(Comp
);
4778 if (is_compass_NaN(VAL(Comp
))) VAL(Comp
) = HUGE_REAL
;
4780 case CompassDATBackComp
:
4781 read_bearing_or_omit(BackComp
);
4782 if (is_compass_NaN(VAL(BackComp
))) VAL(BackComp
) = HUGE_REAL
;
4784 case CompassDATClino
: case CompassDATBackClino
: {
4786 clino_type
* p_ctype
;
4787 if (*ordering
== CompassDATClino
) {
4792 p_ctype
= &backctype
;
4794 read_reading(r
, false);
4795 if (is_compass_NaN(VAL(r
))) {
4797 *p_ctype
= CTYPE_OMIT
;
4799 *p_ctype
= CTYPE_READING
;
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
;
4811 case CompassDATFlags
:
4818 while (ch
>= 'A' && ch
<= 'Z') {
4819 compass_dat_flags
|= BIT(ch
- 'A');
4821 * L (exclude from length)
4823 * P (no plot) (mapped to FLAG_SURFACE)
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...)
4835 compass_dat_flags
= 0;
4844 // Walls SRV is always From then To.
4846 fr
= read_walls_station(p_walls_options
->prefix
, true, NULL
);
4849 if (!read_walls_srv_to(&to
, &compass_dat_flags
)) {
4850 // Isolated LRUD so don't try to parse line as a survey leg.
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.
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. */
4869 while (nextch() == '-') { }
4872 if (VAL(Tape
) < (real
)0.0)
4873 compile_diagnostic_reading(DIAG_WARN
, Tape
, /*Negative tape reading*/60);
4879 real inches
= read_numeric(false);
4880 VAL(Tape
) += inches
/ 12.0;
4884 VAL(Tape
) *= METRES_PER_FOOT
;
4887 VAL(Tape
) /= pcs
->units
[Q_LENGTH
];
4891 WID(Tape
) = ftell(file
.fh
) - LOC(Tape
);
4892 VAR(Tape
) = var(Q_LENGTH
);
4894 case WallsSRVComp
: {
4896 LOC(Comp
) = ftell(file
.fh
);
4899 VAL(Comp
) = read_quadrant(false);
4901 VAL(Comp
) = read_number(true, false);
4902 if (VAL(Comp
) == HUGE_REAL
) {
4904 compile_diagnostic_token_show(DIAG_ERR
, /*Expecting numeric field, found ā%sā*/9);
4909 // Walls documents two or more `-` for an omitted
4910 // reading, but actually just one works too!
4911 while (nextch() == '-') { }
4916 VAL(Comp
) *= M_PI
/ 180.0 / pcs
->units
[Q_BEARING
];
4921 VAL(Comp
) *= M_PI
/ 200.0 / pcs
->units
[Q_BEARING
];
4926 VAL(Comp
) *= M_PI
/ 3200.0 / pcs
->units
[Q_BEARING
];
4932 WID(Comp
) = ftell(file
.fh
) - LOC(Comp
);
4933 VAR(Comp
) = var(Q_BEARING
);
4935 // Omitted foresight, e.g. `/123` or `/` (both omitted).
4937 VAL(Comp
) = HUGE_REAL
;
4939 if (ch
== '/' && !isBlank(nextch())) {
4940 LOC(BackComp
) = ftell(file
.fh
);
4942 VAL(BackComp
) = read_quadrant(false);
4944 VAL(BackComp
) = read_number(true, false);
4945 if (VAL(BackComp
) == HUGE_REAL
) {
4947 compile_diagnostic_token_show(DIAG_ERR
, /*Expecting numeric field, found ā%sā*/9);
4952 // Walls documents two or more `-` for an omitted
4953 // reading, but actually just one works too!
4954 while (nextch() == '-') { }
4959 VAL(BackComp
) *= M_PI
/ 180.0 / pcs
->units
[Q_BACKBEARING
];
4964 VAL(BackComp
) *= M_PI
/ 200.0 / pcs
->units
[Q_BACKBEARING
];
4969 VAL(BackComp
) *= M_PI
/ 3200.0 / pcs
->units
[Q_BACKBEARING
];
4975 WID(BackComp
) = ftell(file
.fh
) - LOC(BackComp
);
4976 VAR(BackComp
) = var(Q_BACKBEARING
);
4978 // Omitted backsight, e.g. `123/` or `/` (both omitted).
4979 LOC(BackComp
) = ftell(file
.fh
);
4981 VAL(BackComp
) = HUGE_REAL
;
4985 case WallsSRVClino
: {
4987 LOC(Clino
) = ftell(file
.fh
);
4989 real clin
= read_number(true, false);
4990 if (clin
== HUGE_REAL
) {
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.
4997 compile_diagnostic_token_show(DIAG_ERR
, /*Expecting numeric field, found ā%sā*/9);
5003 // Walls documents two or more `-` for an omitted
5004 // reading, but actually just one works too!
5005 while (nextch() == '-') { }
5011 clin
*= M_PI
/ 180.0 / pcs
->units
[Q_GRADIENT
];
5016 clin
*= M_PI
/ 200.0 / pcs
->units
[Q_GRADIENT
];
5021 clin
*= M_PI
/ 3200.0 / pcs
->units
[Q_GRADIENT
];
5026 ctype
= CTYPE_READING
;
5028 WID(Clino
) = ftell(file
.fh
) - LOC(Clino
);
5029 VAR(Clino
) = var(Q_GRADIENT
);
5031 // Omitted foresight, e.g. `/12` or `/` (both omitted).
5034 if (ch
== '/' && !isBlank(nextch())) {
5035 LOC(BackClino
) = ftell(file
.fh
);
5036 real backclin
= read_number(true, false);
5037 if (backclin
== HUGE_REAL
) {
5039 compile_diagnostic_token_show(DIAG_ERR
, /*Expecting numeric field, found ā%sā*/9);
5044 // Walls documents two or more `-` for an omitted
5045 // reading, but actually just one works too!
5046 while (nextch() == '-') { }
5051 backclin
*= M_PI
/ 180.0 / pcs
->units
[Q_BACKGRADIENT
];
5056 backclin
*= M_PI
/ 200.0 / pcs
->units
[Q_BACKGRADIENT
];
5061 backclin
*= M_PI
/ 3200.0 / pcs
->units
[Q_BACKGRADIENT
];
5065 VAL(BackClino
) = backclin
;
5066 backctype
= CTYPE_READING
;
5068 WID(BackClino
) = ftell(file
.fh
) - LOC(BackClino
);
5069 VAR(BackClino
) = var(Q_BACKGRADIENT
);
5071 // Omitted backsight, e.g. `12/` or `/` (both omitted).
5072 LOC(BackClino
) = ftell(file
.fh
);
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;
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);
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;
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);
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
;
5112 case WallsSRVExtras
:
5113 read_walls_extras(&compass_dat_flags
);
5117 case IgnoreAllAndNewLine
:
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
;
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
);
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
) {
5183 r
= process_normal(fr
, to
, (first_stn
== To
) ^ fRev
,
5187 /* FIXME: Handle any clino readings */
5188 r
= process_diving(fr
, to
, (first_stn
== To
) ^ fRev
,
5191 case STYLE_CYLPOLAR
:
5192 r
= process_cylpolar(fr
, to
, (first_stn
== To
) ^ fRev
,
5196 r
= 0; /* avoid warning */
5199 pcs
->flags
= save_flags
;
5202 /* Swap fr and to back to how they were for next line */
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;
5226 if (isData(ch
)) break;
5237 /* Compass ignore flag is 'X' */
5238 if ((compass_dat_flags
& BIT('X' - 'A'))) {
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
));
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
);
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
);
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);
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
) {
5315 process_normal(fr
, to
, (first_stn
== To
) ^ fRev
,
5319 /* FIXME: Handle any clino readings */
5320 process_diving(fr
, to
, (first_stn
== To
) ^ fRev
,
5323 case STYLE_CYLPOLAR
:
5324 process_cylpolar(fr
, to
, (first_stn
== To
) ^ fRev
,
5330 pcs
->flags
= save_flags
;
5338 } while (isComm(ch
));
5341 BUG("Unknown reading in ordering");
5347 process_lrud(prefix
*stn
)
5349 SVX_ASSERT(next_lrud
);
5350 lrud
* xsect
= osnew(lrud
);
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
;
5360 next_lrud
= &(xsect
->next
);
5369 const reading
*ordering
;
5371 for (ordering
= pcs
->ordering
; ; ordering
++) {
5373 switch (*ordering
) {
5375 stn
= read_prefix(PFX_STATION
);
5377 case Left
: case Right
: case Up
: case Down
: {
5378 reading r
= *ordering
;
5379 read_reading(r
, true);
5380 if (VAL(r
) == HUGE_REAL
) {
5382 compile_diagnostic_token_show(DIAG_ERR
, /*Expecting numeric field, found ā%sā*/9);
5400 default: BUG("Unknown reading in ordering");
5406 process_nosurvey(prefix
*fr
, prefix
*to
, bool fToFirst
)
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
);
5417 link
->to
= StnFromPfx(to
);
5418 link
->fr
= StnFromPfx(fr
);
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
;
5434 prefix
*fr
= NULL
, *to
= NULL
;
5436 bool fMulti
= false;
5438 reading first_stn
= End
;
5440 const reading
*ordering
;
5444 for (ordering
= pcs
->ordering
; ; ordering
++) {
5446 switch (*ordering
) {
5448 fr
= read_prefix(PFX_STATION
|PFX_ALLOW_ROOT
);
5449 if (first_stn
== End
) first_stn
= Fr
;
5452 to
= read_prefix(PFX_STATION
|PFX_ALLOW_ROOT
);
5453 if (first_stn
== End
) first_stn
= To
;
5457 to
= read_prefix(PFX_STATION
);
5462 case IgnoreAllAndNewLine
:
5467 if (!process_nosurvey(fr
, to
, first_stn
== To
))
5470 if (ordering
[1] == End
) {
5474 } while (isComm(ch
));
5484 if (isData(ch
)) break;
5495 (void)process_nosurvey(fr
, to
, first_stn
== To
);
5502 } while (isComm(ch
));
5504 default: BUG("Unknown reading in ordering");
5509 /* totally ignore a line of survey data */