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 #define EPSILON (REAL_EPSILON * 1000)
55 #define var(I) (pcs->Var[(I)])
57 /* Test for a not-a-number value in Compass data (999.0 or -999.0).
59 * Compass itself uses -999.0 but reportedly understands Karst data which used
60 * 999.0 (information from Larry Fish via Simeon Warner in 2004). However
61 * testing with Compass in early 2024 it seems 999.0 is treated like any other
64 * When "corrected" backsights are specified in FORMAT, Compass seems to write
65 * out -999 with the correction applied to the CLP file.
67 * Valid readings should be 0 to 360 for the compass and -90 to 90 for the
68 * clino, and the correction should have absolute value < 360, so we test for
69 * any reading with an absolute value greater than 999 - 360 = 639, which is
70 * well outside the valid range.
72 #define is_compass_NaN(x) (fabs(x) > (999.0 - 360.0))
75 read_compass_date_as_days_since_1900(void)
77 /* NB order is *month* *day* year */
78 int month
= read_uint();
79 int day
= read_uint();
80 int year
= read_uint();
81 /* Note: Larry says a 2 digit year is always 19XX */
82 if (year
< 100) year
+= 1900;
84 /* Compass uses 1901-01-01 when no date was specified. */
85 if (year
== 1901 && day
== 1 && month
== 1) return -1;
87 return days_since_1900(year
, month
, day
);
92 // Clino omitted. VAL() should be set to 0.0.
94 // An actual clino reading.
96 // An explicit plumb (U/D/UP/DOWN/+V/-V for reading).
98 // An inferred plumb (+90 or -90 and *infer plumbs).
100 // An explicit horizontal leg (H/LEVEL for reading).
104 /* Don't explicitly initialise as we can't set the jmp_buf - this has
105 * static scope so will be initialised like this anyway */
106 parse file
/* = { NULL, NULL, 0, false, NULL } */ ;
110 static real value
[Fr
- 1];
111 #define VAL(N) value[(N)-1]
112 static real variance
[Fr
- 1];
113 #define VAR(N) variance[(N)-1]
114 static long location
[Fr
- 1];
115 #define LOC(N) location[(N)-1]
116 static int location_width
[Fr
- 1];
117 #define WID(N) location_width[(N)-1]
119 /* style functions */
120 static void data_normal(void);
121 static void data_cartesian(void);
122 static void data_passage(void);
123 static void data_nosurvey(void);
124 static void data_ignore(void);
130 fp
->offset
= ftell(file
.fh
);
131 if (fp
->offset
== -1)
132 fatalerror_in_file(file
.filename
, 0, /*Error reading file*/18);
136 set_pos(const filepos
*fp
)
139 if (fseek(file
.fh
, fp
->offset
, SEEK_SET
) == -1)
140 fatalerror_in_file(file
.filename
, 0, /*Error reading file*/18);
144 report_parent(parse
* p
) {
146 report_parent(p
->parent
);
147 /* Force re-report of include tree for further errors in
149 p
->reported_where
= false;
150 /* TRANSLATORS: %s is replaced by the filename of the parent file, and %u
151 * by the line number in that file. Your translation should also contain
152 * %s:%u so that automatic parsing of error messages to determine the file
153 * and line number still works. */
154 fprintf(STDERR
, msg(/*In file included from %s:%u:\n*/5), p
->filename
, p
->line
);
158 error_list_parent_files(void)
160 if (!file
.reported_where
&& file
.parent
) {
161 report_parent(file
.parent
);
162 /* Suppress reporting of full include tree for further errors
164 file
.reported_where
= true;
169 show_line(int col
, int width
)
171 /* Rewind to beginning of line. */
172 long cur_pos
= ftell(file
.fh
);
174 if (cur_pos
< 0 || fseek(file
.fh
, file
.lpos
, SEEK_SET
) == -1)
175 fatalerror_in_file(file
.filename
, 0, /*Error reading file*/18);
177 /* Read the whole line and write it out. */
180 int c
= GETC(file
.fh
);
181 /* Note: isEol() is true for EOF */
183 if (c
== '\t') ++tabs
;
188 /* If we have a location in the line for the error, indicate it. */
192 while (--col
) PUTC(' ', STDERR
);
194 /* Copy tabs from line, replacing other characters with spaces - this
195 * means that the caret should line up correctly. */
196 if (fseek(file
.fh
, file
.lpos
, SEEK_SET
) == -1)
197 fatalerror_in_file(file
.filename
, 0, /*Error reading file*/18);
199 int c
= GETC(file
.fh
);
200 if (c
!= '\t') c
= ' ';
212 /* Revert to where we were. */
213 if (fseek(file
.fh
, cur_pos
, SEEK_SET
) == -1)
214 fatalerror_in_file(file
.filename
, 0, /*Error reading file*/18);
220 /* Rewind to beginning of line. */
221 long cur_pos
= ftell(file
.fh
);
223 if (cur_pos
< 0 || fseek(file
.fh
, file
.lpos
, SEEK_SET
) == -1)
224 fatalerror_in_file(file
.filename
, 0, /*Error reading file*/18);
226 /* Read the whole line into a string. */
228 int c
= GETC(file
.fh
);
229 /* Note: isEol() is true for EOF */
234 /* Revert to where we were. */
235 if (fseek(file
.fh
, cur_pos
, SEEK_SET
) == -1) {
237 fatalerror_in_file(file
.filename
, 0, /*Error reading file*/18);
243 static int caret_width
= 0;
246 compile_v_report_fpos(int severity
, long fpos
, int en
, va_list ap
)
249 error_list_parent_files();
250 if (fpos
>= file
.lpos
)
251 col
= fpos
- file
.lpos
- caret_width
;
252 v_report(severity
, file
.filename
, file
.line
, col
, en
, ap
);
253 if (file
.fh
) show_line(col
, caret_width
);
257 compile_v_report(int diag_flags
, int en
, va_list ap
)
259 int severity
= (diag_flags
& DIAG_SEVERITY_MASK
);
260 if (diag_flags
& (DIAG_COL
|DIAG_TOKEN
)) {
262 if (diag_flags
& DIAG_TOKEN
) caret_width
= s_len(&token
);
263 compile_v_report_fpos(severity
, ftell(file
.fh
), en
, ap
);
264 if (diag_flags
& DIAG_TOKEN
) caret_width
= 0;
265 if (diag_flags
& DIAG_SKIP
) skipline();
269 error_list_parent_files();
270 v_report(severity
, file
.filename
, file
.line
, 0, en
, ap
);
272 if (diag_flags
& DIAG_TOKEN
) {
273 show_line(0, s_len(&token
));
275 show_line(0, caret_width
);
278 if (diag_flags
& DIAG_SKIP
) skipline();
282 compile_diagnostic(int diag_flags
, int en
, ...)
286 if (diag_flags
& (DIAG_DATE
|DIAG_NUM
|DIAG_UINT
|DIAG_WORD
|DIAG_TAIL
|DIAG_FROM_
)) {
289 if (diag_flags
& DIAG_WORD
) {
290 while (!isBlank(ch
) && !isComm(ch
) && !isEol(ch
)) {
294 } else if (diag_flags
& DIAG_UINT
) {
295 while (isdigit(ch
)) {
299 } else if (diag_flags
& DIAG_DATE
) {
300 while (isdigit(ch
) || ch
== '.') {
304 } else if (diag_flags
& DIAG_TAIL
) {
305 int len_last_nonblank
= len
;
306 while (!isComm(ch
) && !isEol(ch
)) {
308 if (!isBlank(ch
)) len_last_nonblank
= len
;
311 len
= len_last_nonblank
;
312 } else if (diag_flags
& DIAG_FROM_
) {
313 len
= diag_flags
>> DIAG_FROM_SHIFT
;
315 if (isMinus(ch
) || isPlus(ch
)) {
319 while (isdigit(ch
)) {
327 while (isdigit(ch
)) {
333 compile_v_report(diag_flags
|DIAG_COL
, en
, ap
);
335 } else if (diag_flags
& DIAG_STRING
) {
338 caret_width
= ftell(file
.fh
);
341 /* We want to include any quotes, so can't use s_len(&p). */
342 caret_width
= ftell(file
.fh
) - caret_width
;
343 compile_v_report(diag_flags
|DIAG_COL
, en
, ap
);
346 compile_v_report(diag_flags
, en
, ap
);
352 compile_diagnostic_reading(int diag_flags
, reading r
, int en
, ...)
355 int severity
= (diag_flags
& DIAG_SEVERITY_MASK
);
357 caret_width
= WID(r
);
358 compile_v_report_fpos(severity
, LOC(r
) + caret_width
, en
, ap
);
364 compile_error_reading_skip(reading r
, int en
, ...)
368 caret_width
= WID(r
);
369 compile_v_report_fpos(DIAG_ERR
, LOC(r
) + caret_width
, en
, ap
);
376 compile_diagnostic_at(int diag_flags
, const char * filename
, unsigned line
, int en
, ...)
379 int severity
= (diag_flags
& DIAG_SEVERITY_MASK
);
381 v_report(severity
, filename
, line
, 0, en
, ap
);
386 compile_diagnostic_pfx(int diag_flags
, const prefix
* pfx
, int en
, ...)
389 int severity
= (diag_flags
& DIAG_SEVERITY_MASK
);
391 v_report(severity
, pfx
->filename
, pfx
->line
, 0, en
, ap
);
396 compile_diagnostic_token_show(int diag_flags
, int en
)
400 while (!isBlank(ch
) && !isComm(ch
) && !isEol(ch
)) {
401 s_catchar(&p
, (char)ch
);
405 caret_width
= s_len(&p
);
406 compile_diagnostic(diag_flags
|DIAG_COL
, en
, s_str(&p
));
410 compile_diagnostic(DIAG_ERR
|DIAG_COL
, en
, "");
415 compile_error_string(const char * s
, int en
, ...)
419 caret_width
= strlen(s
);
420 compile_v_report(DIAG_ERR
|DIAG_COL
, en
, ap
);
425 /* This function makes a note where to put output files */
427 using_data_file(const char *fnm
)
429 if (!fnm_output_base
) {
430 /* was: fnm_output_base = base_from_fnm(fnm); */
431 fnm_output_base
= baseleaf_from_fnm(fnm
);
432 } else if (fnm_output_base_is_dir
) {
433 /* --output pointed to directory so use the leaf basename in that dir */
435 lf
= baseleaf_from_fnm(fnm
);
436 p
= use_path(fnm_output_base
, lf
);
438 osfree(fnm_output_base
);
440 fnm_output_base_is_dir
= 0;
447 while (!isBlank(ch
) && !isComm(ch
) && !isEol(ch
)) nextch();
453 while (isBlank(ch
)) nextch();
459 while (!isEol(ch
)) nextch();
471 compile_diagnostic(DIAG_ERR
|DIAG_TAIL
, /*End of line not blank*/15);
477 /* skip any different eol characters so we get line counts correct on
478 * DOS text files and similar, but don't count several adjacent blank
482 if (ch
== eolchar
|| !isEol(ch
)) {
485 if (ch
== '\n') eolchar
= ch
;
487 file
.lpos
= ftell(file
.fh
) - 1;
491 process_non_data_line(void)
495 if (isData(ch
)) return false;
508 read_reading(reading r
, bool f_optional
)
513 case Tape
: q
= Q_LENGTH
; break;
514 case BackTape
: q
= Q_BACKLENGTH
; break;
515 case Comp
: q
= Q_BEARING
; break;
516 case BackComp
: q
= Q_BACKBEARING
; break;
517 case Clino
: q
= Q_GRADIENT
; break;
518 case BackClino
: q
= Q_BACKGRADIENT
; break;
519 case FrDepth
: case ToDepth
: q
= Q_DEPTH
; break;
520 case Dx
: q
= Q_DX
; break;
521 case Dy
: q
= Q_DY
; break;
522 case Dz
: q
= Q_DZ
; break;
523 case FrCount
: case ToCount
: q
= Q_COUNT
; break;
524 case Left
: q
= Q_LEFT
; break;
525 case Right
: q
= Q_RIGHT
; break;
526 case Up
: q
= Q_UP
; break;
527 case Down
: q
= Q_DOWN
; break;
529 q
= Q_NULL
; /* Suppress compiler warning */;
530 BUG("Unexpected case");
532 LOC(r
) = ftell(file
.fh
);
533 /* since we don't handle bearings in read_readings, it's never quadrant */
534 VAL(r
) = read_numeric_multi(f_optional
, false, &n_readings
);
535 WID(r
) = ftell(file
.fh
) - LOC(r
);
537 if (n_readings
> 1) VAR(r
) /= sqrt(n_readings
);
541 read_bearing_or_omit(reading r
)
544 bool quadrants
= false;
545 q_quantity q
= Q_NULL
;
549 if (pcs
->f_bearing_quadrants
)
554 if (pcs
->f_backbearing_quadrants
)
558 q
= Q_NULL
; /* Suppress compiler warning */;
559 BUG("Unexpected case");
561 LOC(r
) = ftell(file
.fh
);
562 VAL(r
) = read_bearing_multi_or_omit(quadrants
, &n_readings
);
563 WID(r
) = ftell(file
.fh
) - LOC(r
);
565 if (n_readings
> 1) VAR(r
) /= sqrt(n_readings
);
568 // Set up settings for reading Compass DAT or MAK.
570 initialise_common_compass_settings(void)
572 short *t
= ((short*)osmalloc(ossizeof(short) * 257)) + 1;
574 t
[EOF
] = SPECIAL_EOL
;
575 memset(t
, 0, sizeof(short) * 33);
576 for (i
= 33; i
< 127; i
++) t
[i
] = SPECIAL_NAMES
;
578 for (i
= 128; i
< 256; i
++) t
[i
] = SPECIAL_NAMES
;
579 t
['\t'] |= SPECIAL_BLANK
;
580 t
[' '] |= SPECIAL_BLANK
;
581 t
['\032'] |= SPECIAL_EOL
; /* Ctrl-Z, so olde DOS text files are handled ok */
582 t
['\n'] |= SPECIAL_EOL
;
583 t
['\r'] |= SPECIAL_EOL
;
584 t
['.'] |= SPECIAL_DECIMAL
;
585 t
['-'] |= SPECIAL_MINUS
;
586 t
['+'] |= SPECIAL_PLUS
;
588 settings
*pcsNew
= osnew(settings
);
589 *pcsNew
= *pcs
; /* copy contents */
590 pcsNew
->begin_lineno
= 0;
591 pcsNew
->Translate
= t
;
593 pcsNew
->Truncate
= INT_MAX
;
597 update_output_separator();
600 /* For reading Compass MAK files which have a freeform syntax */
602 nextch_handling_eol(void)
605 while (ch
!= EOF
&& isEol(ch
)) {
611 get_token_and_check(const char *expect
)
614 if (s_eq(&token
, expect
))
616 compile_diagnostic(DIAG_ERR
|DIAG_TOKEN
, /*Expecting ā%sā*/497, expect
);
624 compile_diagnostic(DIAG_ERR
|DIAG_COL
, /*Expecting ā%sā*/497, ":");
632 get_token_and_check_colon(const char *expect
)
634 return get_token_and_check(expect
) && check_colon();
638 data_file_compass_dat_or_clp(bool is_clp
)
640 initialise_common_compass_settings();
643 pcs
->z
[Q_DECLINATION
] = HUGE_REAL
;
645 pcs
->style
= STYLE_NORMAL
;
646 pcs
->units
[Q_LENGTH
] = METRES_PER_FOOT
;
647 pcs
->infer
= BIT(INFER_EQUATES
) |
648 BIT(INFER_EQUATES_SELF_OK
) |
651 /* We need to update separator_map so we don't pick a separator character
652 * which occurs in a station name. However Compass DAT allows everything
653 * >= ASCII char 33 except 127 in station names so if we just added all
654 * the valid station name characters we'd always pick space as the
655 * separator for any dataset which included a DAT file, yet in practice
656 * '.' is never used in any of the sample DAT files I've seen. So
657 * instead we scan the characters actually used in station names when we
658 * process CompassDATFr and CompassDATTo fields.
662 /* errors in nested functions can longjmp here */
663 if (setjmp(file
.jbSkipLine
)) {
669 while (ch
!= EOF
&& !ferror(file
.fh
)) {
670 static const reading compass_order
[] = {
671 CompassDATFr
, CompassDATTo
, Tape
, CompassDATComp
, CompassDATClino
,
672 CompassDATLeft
, CompassDATUp
, CompassDATDown
, CompassDATRight
,
673 CompassDATFlags
, IgnoreAll
675 static const reading compass_order_backsights
[] = {
676 CompassDATFr
, CompassDATTo
, Tape
, CompassDATComp
, CompassDATClino
,
677 CompassDATLeft
, CompassDATUp
, CompassDATDown
, CompassDATRight
,
678 CompassDATBackComp
, CompassDATBackClino
,
679 CompassDATFlags
, IgnoreAll
682 copy_on_write_meta(pcs
);
683 pcs
->meta
->days1
= pcs
->meta
->days2
= -1;
684 pcs
->declination
= HUGE_REAL
;
685 pcs
->ordering
= compass_order
;
686 pcs
->recorded_style
= STYLE_NORMAL
;
691 /* SURVEY NAME: <Short name> */
692 if (get_token_and_check("SURVEY") &&
693 get_token_and_check_colon("NAME")) {
694 // Survey short name currently ignored.
700 /* SURVEY DATE: 7 10 79 COMMENT:<Long name> */
701 if (get_token_and_check("SURVEY") &&
702 get_token_and_check_colon("DATE")) {
703 int days
= read_compass_date_as_days_since_1900();
704 pcs
->meta
->days1
= pcs
->meta
->days2
= days
;
705 // Ignore "COMMENT:<Long name>" part for now.
710 if (get_token_and_check("SURVEY") &&
711 get_token_and_check_colon("TEAM")) {
712 // Value is on the next line.
718 /* DECLINATION: 1.00 FORMAT: DDDDLUDRADLN CORRECTIONS: 2.00 3.00 4.00 */
719 if (get_token_and_check_colon("DECLINATION")) {
720 if (pcs
->dec_filename
== NULL
) {
721 pcs
->z
[Q_DECLINATION
] = -read_numeric(false);
722 pcs
->z
[Q_DECLINATION
] *= pcs
->units
[Q_DECLINATION
];
724 (void)read_numeric(false);
728 pcs
->ordering
= compass_order
;
729 // "FORMAT" is optional.
730 if (S_EQ(&token
, "FORMAT") && check_colon()) {
731 /* This documents the format in the original survey notebook - we
732 * don't need to fully parse it to be able to parse the survey data
733 * in the file, which gets converted to a fixed order and units.
736 size_t token_len
= s_len(&token
);
737 if (token_len
>= 4 && s_str(&token
)[3] == 'W') {
738 /* Original "Inclination Units" were "Depth Gauge". */
739 pcs
->recorded_style
= STYLE_DIVING
;
741 if (token_len
>= 12) {
742 char backsight_type
= s_str(&token
)[token_len
>= 15 ? 13 : 11];
743 // B means redundant backsight; C means redundant backsights
744 // but displayed "corrected" (i.e. reversed to make visually
745 // comparing easier).
746 if (backsight_type
== 'B' || backsight_type
== 'C') {
747 /* We have backsights for compass and clino */
748 pcs
->ordering
= compass_order_backsights
;
754 // CORRECTIONS and CORRECTIONS2 have already been applied to data in
757 if (S_EQ(&token
, "CORRECTIONS") && check_colon()) {
758 pcs
->z
[Q_BACKBEARING
] = pcs
->z
[Q_BEARING
] = -rad(read_numeric(false));
759 pcs
->z
[Q_BACKGRADIENT
] = pcs
->z
[Q_GRADIENT
] = -rad(read_numeric(false));
760 pcs
->z
[Q_LENGTH
] = -METRES_PER_FOOT
* read_numeric(false);
764 /* get_token() only reads alphas so we need special handling for
768 s_catchar(&token
, ch
);
771 if (S_EQ(&token
, "CORRECTIONS2") && check_colon()) {
772 pcs
->z
[Q_BACKBEARING
] = -rad(read_numeric(false));
773 pcs
->z
[Q_BACKGRADIENT
] = -rad(read_numeric(false));
779 // FIXME Parse once we handle discovery dates...
780 // NB: Need to skip unread CORRECTIONS* for the `is_clp` case.
781 if (S_EQ(&token
, "DISCOVERY") && check_colon()) {
782 // Discovery date, e.g. DISCOVERY: 2 28 2024
783 int days
= read_compass_date_as_days_since_1900();
805 pcs
->ordering
= NULL
; /* Avoid free() of static array. */
810 data_file_compass_dat(void)
812 data_file_compass_dat_or_clp(false);
816 data_file_compass_clp(void)
818 data_file_compass_dat_or_clp(true);
822 data_file_compass_mak(void)
824 initialise_common_compass_settings();
825 short *t
= pcs
->Translate
;
826 // In a Compass MAK file a station name can't contain these three
827 // characters due to how the syntax works.
828 t
['['] = t
[','] = t
[';'] = 0;
831 /* errors in nested functions can longjmp here */
832 if (setjmp(file
.jbSkipLine
)) {
840 real base_x
= 0.0, base_y
= 0.0, base_z
= 0.0;
841 int base_utm_zone
= 0;
842 unsigned int base_line
= 0;
844 string path
= S_INIT
;
845 s_donate(&path
, path_from_fnm(file
.filename
));
847 struct mak_folder
*next
;
849 } *folder_stack
= NULL
;
851 while (ch
!= EOF
&& !ferror(file
.fh
)) {
856 string dat_fnm
= S_INIT
;
857 nextch_handling_eol();
858 while (ch
!= ',' && ch
!= ';' && ch
!= EOF
) {
859 while (isEol(ch
)) process_eol();
860 s_catchar(&dat_fnm
, (char)ch
);
861 nextch_handling_eol();
863 if (!s_empty(&dat_fnm
)) {
865 // Process the previous @ command using the datum from &.
866 char *proj_str
= img_compass_utm_proj_str(datum
,
869 // Temporarily reset line and lpos so dec_context and
870 // dec_line refer to the @ command.
871 unsigned saved_line
= file
.line
;
872 file
.line
= base_line
;
873 long saved_lpos
= file
.lpos
;
874 file
.lpos
= base_lpos
;
875 set_declination_location(base_x
, base_y
, base_z
,
877 file
.line
= saved_line
;
878 file
.lpos
= saved_lpos
;
879 if (!pcs
->proj_str
) {
880 pcs
->proj_str
= proj_str
;
882 proj_str_out
= osstrdup(proj_str
);
890 data_file(s_str(&path
), s_str(&dat_fnm
));
894 while (ch
!= ';' && ch
!= EOF
) {
895 nextch_handling_eol();
898 prefix
*name
= read_prefix(PFX_STATION
|PFX_OPT
);
900 scan_compass_station_name(name
);
905 bool in_feet
= false;
906 // Compass treats these fixed points as entrances
907 // ("distance from entrance" in a .DAT file counts
908 // from 0.0 at these points) so we do too.
909 name
->sflags
|= BIT(SFLAGS_FIXED
) |
910 BIT(SFLAGS_ENTRANCE
);
911 nextch_handling_eol();
912 if (ch
== 'F' || ch
== 'f') {
914 nextch_handling_eol();
915 } else if (ch
== 'M' || ch
== 'm') {
916 nextch_handling_eol();
918 compile_diagnostic(DIAG_ERR
|DIAG_COL
, /*Expecting ā%sā or ā%sā*/103, "F", "M");
920 while (!isdigit(ch
) && ch
!= '+' && ch
!= '-' &&
921 ch
!= '.' && ch
!= ']' && ch
!= EOF
) {
922 nextch_handling_eol();
924 coords
[0] = read_numeric(false);
925 while (!isdigit(ch
) && ch
!= '+' && ch
!= '-' &&
926 ch
!= '.' && ch
!= ']' && ch
!= EOF
) {
927 nextch_handling_eol();
929 coords
[1] = read_numeric(false);
930 while (!isdigit(ch
) && ch
!= '+' && ch
!= '-' &&
931 ch
!= '.' && ch
!= ']' && ch
!= EOF
) {
932 nextch_handling_eol();
934 coords
[2] = read_numeric(false);
936 coords
[0] *= METRES_PER_FOOT
;
937 coords
[1] *= METRES_PER_FOOT
;
938 coords
[2] *= METRES_PER_FOOT
;
940 int fix_result
= fix_station(name
, coords
);
945 if (fix_result
< 0) {
946 compile_diagnostic(DIAG_ERR
|DIAG_WORD
, /*Station already fixed or equated to a fixed point*/46);
948 compile_diagnostic(DIAG_WARN
|DIAG_WORD
, /*Station already fixed at the same coordinates*/55);
951 compile_diagnostic_pfx(DIAG_INFO
, name
, /*Previously fixed or equated here*/493);
953 while (ch
!= ']' && ch
!= EOF
) nextch_handling_eol();
955 nextch_handling_eol();
959 /* FIXME: link station - ignore for now */
960 /* FIXME: perhaps issue warning? Other station names
961 * can be "reused", which is problematic... */
963 while (ch
!= ',' && ch
!= ';' && ch
!= EOF
)
964 nextch_handling_eol();
973 utm_zone
= read_int(-60, 60);
975 if (ch
== ';') nextch_handling_eol();
978 if (!pcs
->next
|| pcs
->proj_str
!= pcs
->next
->proj_str
)
979 osfree(pcs
->proj_str
);
980 pcs
->proj_str
= NULL
;
981 if (datum
&& utm_zone
&& abs(utm_zone
) <= 60) {
982 /* Set up coordinate system. */
983 char *proj_str
= img_compass_utm_proj_str(datum
, utm_zone
);
985 pcs
->proj_str
= proj_str
;
987 proj_str_out
= osstrdup(proj_str
);
991 invalidate_pj_cached();
1000 while (ch
!= ';' && !isEol(ch
)) {
1001 s_catchar(&p
, (char)ch
);
1003 /* Ignore trailing blanks. */
1004 if (!isBlank(ch
)) datum_len
= c
;
1007 if (ch
== ';') nextch_handling_eol();
1008 datum
= img_parse_compass_datum_string(s_str(&p
), datum_len
);
1010 goto update_proj_str
;
1013 // Enter subdirectory.
1014 struct mak_folder
*p
= folder_stack
;
1015 folder_stack
= osnew(struct mak_folder
);
1016 folder_stack
->next
= p
;
1017 folder_stack
->len
= s_len(&path
);
1018 if (!s_empty(&path
))
1019 s_catchar(&path
, FNM_SEP_LEV
);
1021 while (ch
!= ';' && !isEol(ch
)) {
1025 s_catchar(&path
, (char)ch
);
1028 if (ch
== ';') nextch_handling_eol();
1032 // Leave subdirectory.
1033 struct mak_folder
*p
= folder_stack
;
1034 if (folder_stack
== NULL
) {
1035 // FIXME: Error? Check what Compass does.
1038 s_truncate(&path
, folder_stack
->len
);
1039 folder_stack
= folder_stack
->next
;
1043 if (ch
== ';') nextch_handling_eol();
1047 /* "Base Location" to calculate magnetic declination at:
1048 * UTM East, UTM North, Elevation, UTM Zone, Convergence Angle
1049 * The first three are in metres.
1052 real easting
= read_numeric(false);
1054 if (ch
!= ',') break;
1056 real northing
= read_numeric(false);
1058 if (ch
!= ',') break;
1060 real elevation
= read_numeric(false);
1062 if (ch
!= ',') break;
1064 int zone
= read_int(-60, 60);
1066 if (ch
!= ',') break;
1068 real convergence_angle
= read_numeric(false);
1069 /* We've now read them all successfully so store them. The
1070 * Compass documentation gives an example which specifies the
1071 * datum *AFTER* the base location, so we need to convert lazily.
1076 base_utm_zone
= zone
;
1077 base_line
= file
.line
;
1078 base_lpos
= file
.lpos
;
1079 // We ignore the stored UTM grid convergence angle since we get
1081 (void)convergence_angle
;
1082 if (ch
== ';') nextch_handling_eol();
1086 nextch_handling_eol();
1091 while (folder_stack
) {
1092 // FIXME: Error? Check what Compass does.
1093 struct mak_folder
*next
= folder_stack
->next
;
1094 osfree(folder_stack
);
1095 folder_stack
= next
;
1102 // The current Walls reference point and CRS details.
1106 // img_DATUM_* code from src/img.h or -1 if no .REF in effect.
1108 } walls_ref
= { 0.0, 0.0, 0.0, 0, -1 };
1110 // We don't expect a huge number of macros, and this doesn't limit how many we
1111 // can handle, only at what point access time stops being O(1).
1112 #define WALLS_MACRO_HASH_SIZE 0x100
1114 typedef struct walls_macro
{
1115 struct walls_macro
*next
;
1120 // Macros set in the WPJ persist, but those set in an SRV only apply for that
1121 // SRV so we keep a table for each and when expanding them in the SRV we use
1122 // a definition from the SRV file in preference to one from the WPJ file.
1124 // Definitions actually also get added to walls_macros, but we swap the tables
1125 // around both before and after processing an SRV file (and we clear the
1126 // table from the SRV file at the end of the file).
1128 // Testing with Walls, macro definitions are NOT affected by SAVE, RESTORE or
1130 static walls_macro
**walls_macros_wpj
= NULL
;
1131 static walls_macro
**walls_macros
= NULL
;
1134 walls_swap_macro_tables()
1136 walls_macro
**tmp
= walls_macros_wpj
;
1137 walls_macros_wpj
= walls_macros
;
1141 // Takes ownership of the contents of p_name and of value.
1142 // Passing NULL for value sets empty string.
1144 walls_set_macro(walls_macro
***table
, string
*p_name
, char *val
)
1146 //printf("MACRO: $|%s|=\"%s\":\n", name, val);
1148 *table
= osmalloc(WALLS_MACRO_HASH_SIZE
* ossizeof(walls_macro
*));
1149 for (size_t i
= 0; i
< WALLS_MACRO_HASH_SIZE
; i
++)
1153 unsigned h
= hash_data(s_str(p_name
), s_len(p_name
)) &
1154 (WALLS_MACRO_HASH_SIZE
- 1);
1155 walls_macro
*p
= (*table
)[h
];
1157 if (s_eq(p_name
, p
->name
)) {
1158 // Update existing definition of macro.
1167 walls_macro
*entry
= osnew(walls_macro
);
1168 entry
->name
= s_steal(p_name
);
1170 entry
->next
= (*table
)[h
];
1171 (*table
)[h
] = entry
;
1174 // Returns NULL if not set.
1176 walls_get_macro(walls_macro
***table
, const char *name
, int name_len
)
1178 if (!*table
) return NULL
;
1180 unsigned h
= hash_data(name
, name_len
) & (WALLS_MACRO_HASH_SIZE
- 1);
1181 walls_macro
*p
= (*table
)[h
];
1183 if (strcmp(p
->name
, name
) == 0) {
1184 return p
->value
? p
->value
: "";
1206 static const sztok walls_cmd_tab
[] = {
1207 {"DATE", WALLS_CMD_DATE
},
1208 {"F", WALLS_CMD_FLAG
}, // Abbreviated form.
1209 {"FIX", WALLS_CMD_FIX
},
1210 {"FLAG", WALLS_CMD_FLAG
},
1211 {"N", WALLS_CMD_NOTE
}, // Abbreviated form.
1212 {"NOTE", WALLS_CMD_NOTE
},
1213 {"P", WALLS_CMD_PREFIX
}, // Abbreviated form.
1214 {"PREFIX", WALLS_CMD_PREFIX
},
1215 {"PREFIX1", WALLS_CMD_PREFIX
}, // Alias.
1216 {"PREFIX2", WALLS_CMD_PREFIX2
},
1217 {"PREFIX3", WALLS_CMD_PREFIX3
},
1218 {"S", WALLS_CMD_SEGMENT
}, // Abbreviated form.
1219 {"SEG", WALLS_CMD_SEGMENT
}, // Abbreviated form.
1220 {"SEGMENT", WALLS_CMD_SEGMENT
},
1221 {"SYM", WALLS_CMD_SYMBOL
},
1222 {"SYMBOL", WALLS_CMD_SYMBOL
},
1223 {"U", WALLS_CMD_UNITS
}, // Abbreviated form.
1224 {"UNITS", WALLS_CMD_UNITS
},
1225 {NULL
, WALLS_CMD_NULL
}
1231 WALLS_UNITS_OPT_CASE
,
1234 WALLS_UNITS_OPT_DECL
,
1235 WALLS_UNITS_OPT_FEET
,
1236 WALLS_UNITS_OPT_FLAG
,
1237 WALLS_UNITS_OPT_GRID
,
1238 WALLS_UNITS_OPT_INCA
,
1239 WALLS_UNITS_OPT_INCAB
,
1240 WALLS_UNITS_OPT_INCD
,
1241 WALLS_UNITS_OPT_INCH
,
1242 WALLS_UNITS_OPT_INCV
,
1243 WALLS_UNITS_OPT_INCVB
,
1244 WALLS_UNITS_OPT_LRUD
,
1245 WALLS_UNITS_OPT_METERS
,
1246 WALLS_UNITS_OPT_ORDER
,
1247 WALLS_UNITS_OPT_PREFIX
,
1248 WALLS_UNITS_OPT_PREFIX2
,
1249 WALLS_UNITS_OPT_PREFIX3
,
1250 WALLS_UNITS_OPT_RECT
,
1251 WALLS_UNITS_OPT_RESET
,
1252 WALLS_UNITS_OPT_RESTORE
,
1254 WALLS_UNITS_OPT_SAVE
,
1255 WALLS_UNITS_OPT_TAPE
,
1256 WALLS_UNITS_OPT_TYPEAB
,
1257 WALLS_UNITS_OPT_TYPEVB
,
1259 WALLS_UNITS_OPT_UVH
,
1260 WALLS_UNITS_OPT_UVV
,
1263 WALLS_UNITS_OPT_NULL
= -1
1266 // The aliases AZIMUTH, DISTANCE and VERTICAL don't seem to be documented.
1267 // They were found from `strings Walls32.exe` and then testing.
1268 static const sztok walls_units_opt_tab
[] = {
1269 {"A", WALLS_UNITS_OPT_A
},
1270 {"AB", WALLS_UNITS_OPT_AB
},
1271 {"AZIMUTH", WALLS_UNITS_OPT_A
}, // Alias (undocumented).
1272 {"CASE", WALLS_UNITS_OPT_CASE
},
1273 {"CT", WALLS_UNITS_OPT_CT
},
1274 {"D", WALLS_UNITS_OPT_D
},
1275 {"DECL", WALLS_UNITS_OPT_DECL
},
1276 {"DISTANCE", WALLS_UNITS_OPT_D
}, // Alias (undocumented).
1277 {"F", WALLS_UNITS_OPT_FEET
}, // Abbreviated form.
1278 {"FEET", WALLS_UNITS_OPT_FEET
},
1279 {"FLAG", WALLS_UNITS_OPT_FLAG
},
1280 {"GRID", WALLS_UNITS_OPT_GRID
},
1281 {"INCA", WALLS_UNITS_OPT_INCA
},
1282 {"INCAB", WALLS_UNITS_OPT_INCAB
},
1283 {"INCD", WALLS_UNITS_OPT_INCD
},
1284 {"INCH", WALLS_UNITS_OPT_INCH
},
1285 {"INCV", WALLS_UNITS_OPT_INCV
},
1286 {"INCVB", WALLS_UNITS_OPT_INCVB
},
1287 {"LRUD", WALLS_UNITS_OPT_LRUD
},
1288 {"M", WALLS_UNITS_OPT_METERS
}, // Abbreviated form.
1289 {"METERS", WALLS_UNITS_OPT_METERS
},
1290 // The Walls documentation mentions a NOTE option here:
1292 // Advanced or Seldom Used Parameters (LRUD=, CASE=, PREFIX=, TAPE=,
1293 // UV=, FLAG=, NOTE=, $name=)
1295 // However the documentation doesn't define it anywhere I can see, and
1296 // testing Walls32.exe it does not seem to be implemented either!
1297 // {"NOTE", WALLS_UNITS_OPT_NOTE},
1298 {"O", WALLS_UNITS_OPT_ORDER
}, // Abbreviated form.
1299 {"ORDER", WALLS_UNITS_OPT_ORDER
},
1300 {"P", WALLS_UNITS_OPT_PREFIX
}, // Abbreviated form.
1301 {"PREFIX", WALLS_UNITS_OPT_PREFIX
},
1302 {"PREFIX1", WALLS_UNITS_OPT_PREFIX
}, // Alias.
1303 {"PREFIX2", WALLS_UNITS_OPT_PREFIX2
},
1304 {"PREFIX3", WALLS_UNITS_OPT_PREFIX3
},
1305 {"RECT", WALLS_UNITS_OPT_RECT
},
1306 {"RESET", WALLS_UNITS_OPT_RESET
},
1307 {"RESTORE", WALLS_UNITS_OPT_RESTORE
},
1308 {"S", WALLS_UNITS_OPT_S
},
1309 {"SAVE", WALLS_UNITS_OPT_SAVE
},
1310 {"TAPE", WALLS_UNITS_OPT_TAPE
},
1311 {"TYPEAB", WALLS_UNITS_OPT_TYPEAB
},
1312 {"TYPEVB", WALLS_UNITS_OPT_TYPEVB
},
1313 {"UV", WALLS_UNITS_OPT_UV
},
1314 {"UVH", WALLS_UNITS_OPT_UVH
},
1315 {"UVV", WALLS_UNITS_OPT_UVV
},
1316 {"V", WALLS_UNITS_OPT_V
},
1317 {"VB", WALLS_UNITS_OPT_VB
},
1318 {"VERTICAL", WALLS_UNITS_OPT_V
}, // Alias (undocumented).
1319 {NULL
, WALLS_UNITS_OPT_NULL
}
1322 #define WALLS_ORDER_CT(R1,R2,R3) ((R1) | ((R2) << 8) | ((R3) << 16))
1323 #define WALLS_ORDER_RECT(R1,R2,R3) ((R1) | ((R2) << 8) | ((R3) << 16) | (1 << 24))
1325 // Here we rely on the integer values of the reading codes used fitting in
1326 // a byte so assert that is the case.
1327 typedef int compiletimeassert_order_byte_encoding_ok
[
1328 (WallsSRVTape
|WallsSRVComp
|WallsSRVClino
|Dx
|Dy
|Dz
) < 0x100 ? 1 : -1];
1330 static const sztok walls_order_tab
[] = {
1331 {"AD", WALLS_ORDER_CT(WallsSRVComp
, WallsSRVTape
, 0)},
1332 {"ADV", WALLS_ORDER_CT(WallsSRVComp
, WallsSRVTape
, WallsSRVClino
)},
1333 {"AVD", WALLS_ORDER_CT(WallsSRVComp
, WallsSRVClino
, WallsSRVTape
)},
1334 {"DA", WALLS_ORDER_CT(WallsSRVTape
, WallsSRVComp
, 0)},
1335 {"DAV", WALLS_ORDER_CT(WallsSRVTape
, WallsSRVComp
, WallsSRVClino
)},
1336 {"DVA", WALLS_ORDER_CT(WallsSRVTape
, WallsSRVClino
, WallsSRVComp
)},
1337 {"EN", WALLS_ORDER_RECT(Dx
, Dy
, 0)},
1338 {"ENU", WALLS_ORDER_RECT(Dx
, Dy
, Dz
)},
1339 {"EUN", WALLS_ORDER_RECT(Dx
, Dz
, Dy
)},
1340 {"NE", WALLS_ORDER_RECT(Dy
, Dx
, 0)},
1341 {"NEU", WALLS_ORDER_RECT(Dy
, Dx
, Dz
)},
1342 {"NUE", WALLS_ORDER_RECT(Dy
, Dz
, Dx
)},
1343 {"UEN", WALLS_ORDER_RECT(Dz
, Dx
, Dy
)},
1344 {"UNE", WALLS_ORDER_RECT(Dz
, Dy
, Dx
)},
1345 {"VAD", WALLS_ORDER_CT(WallsSRVClino
, WallsSRVComp
, WallsSRVTape
)},
1346 {"VDA", WALLS_ORDER_CT(WallsSRVClino
, WallsSRVTape
, WallsSRVComp
)},
1350 // In #FLAG Walls seems to only document `/` but based on real-world use also
1351 // allows `\`. FIXME: Are there other places that allow `\`?
1352 static inline bool isWallsSlash(int c
) { return c
== '/' || c
== '\\'; }
1354 // Walls-specific options. Options which Survex has a direct equivalent of
1355 // are stored in the `settings` struct instead.
1356 typedef struct walls_options
{
1357 // The current values of the three prefix levels Walls supports.
1358 // NULL for any level not currently set (all NULL by default).
1361 // Data order for CT legs.
1362 reading data_order_ct
[6];
1364 // Data order for RECT legs (also used for #Fix coordinate order).
1365 reading data_order_rect
[6];
1367 // Is this from SAVE in .OPTIONS / #Units?
1370 // Flags to apply to stations in #FIX.
1371 int fix_station_flags
;
1375 struct walls_options
*next
;
1378 static walls_options
* p_walls_options
;
1380 static const walls_options walls_options_default
= {
1382 { NULL
, NULL
, NULL
},
1386 WallsSRVFr
, WallsSRVTo
, WallsSRVTape
, WallsSRVComp
, WallsSRVClino
,
1387 // FIXME CompassDATLeft, CompassDATUp, CompassDATDown, CompassDATRight,
1391 // data_order_rect[6]
1393 WallsSRVFr
, WallsSRVTo
, Dx
, Dy
, Dz
,
1394 // FIXME CompassDATLeft, CompassDATUp, CompassDATDown, CompassDATRight,
1401 // fix_station_flags
1412 push_walls_options(void)
1414 settings
*pcsNew
= osnew(settings
);
1415 *pcsNew
= *pcs
; /* copy contents */
1416 pcsNew
->begin_lineno
= file
.line
;
1420 // Walls-specific settings.
1421 walls_options
*new_options
= osnew(walls_options
);
1422 if (!p_walls_options
) {
1423 *new_options
= walls_options_default
;
1425 *new_options
= *p_walls_options
; /* copy contents */
1426 // There's only 3 prefix levels and typically only one seems to be
1427 // actually set so just copy the strings rather than trying to do
1429 for (int i
= 0; i
< 3; ++i
) {
1430 if (new_options
->prefix
[i
])
1431 new_options
->prefix
[i
] = osstrdup(new_options
->prefix
[i
]);
1433 // Actually copy path. FIXME: Maybe copy on write?
1434 string empty_string
= S_INIT
;
1435 new_options
->path
= empty_string
;
1436 s_cats(&new_options
->path
, &p_walls_options
->path
);
1439 new_options
->next
= p_walls_options
;
1440 p_walls_options
= new_options
;
1444 pop_walls_options(void)
1446 pcs
->ordering
= NULL
; /* Avoid free() of static array. */
1448 walls_options
*p
= p_walls_options
;
1449 p_walls_options
= p_walls_options
->next
;
1450 for (int i
= 0; i
< 3; ++i
) {
1451 osfree(p
->prefix
[i
]);
1458 walls_initialise_settings(void)
1460 push_walls_options();
1462 // Generic settings.
1463 short *t
= ((short*)osmalloc(ossizeof(short) * 257)) + 1;
1464 // "Unprefixed names can have a maximum of eight characters and must not
1465 // contain any colons, semicolons, commas, pound signs (#), or embedded
1466 // tabs or spaces. In order to avoid possible problems when printing or
1467 // when exporting data to other programs, you are encouraged to restrict
1468 // names in new surveys to numbers with alphabetic prefixes or suffixes
1471 // However in practice, `#` actually seems to be allowed so we allow it.
1472 // It still can't be used as the first character of the from station as
1473 // there it will be interpreted as introducing a command.
1475 // We assume other control characters aren't allowed either (so nothing
1476 // < 32 and not 127), but allow all top-bit-set characters.
1477 t
[EOF
] = SPECIAL_EOL
;
1478 memset(t
, 0, sizeof(short) * 33);
1479 for (int i
= 33; i
< 127; i
++) t
[i
] = SPECIAL_NAMES
;
1481 for (int i
= 128; i
< 256; i
++) t
[i
] = SPECIAL_NAMES
;
1483 t
[';'] = SPECIAL_COMMENT
;
1484 // FIXME: `,` seems to be treated like a space almost everywhere, but right
1485 // after a directive name a comma instead gives a warning suggesting a
1486 // parse error in a different directive.
1487 t
[','] = SPECIAL_BLANK
;
1488 t
['\t'] |= SPECIAL_BLANK
;
1489 t
[' '] |= SPECIAL_BLANK
;
1490 t
['\032'] |= SPECIAL_EOL
; /* Ctrl-Z, so olde DOS text files are handled ok */
1491 t
['\n'] |= SPECIAL_EOL
;
1492 t
['\r'] |= SPECIAL_EOL
;
1493 t
['-'] |= SPECIAL_OMIT
;
1494 t
[':'] |= SPECIAL_SEPARATOR
;
1495 t
['.'] |= SPECIAL_DECIMAL
;
1496 t
['-'] |= SPECIAL_MINUS
;
1497 t
['+'] |= SPECIAL_PLUS
;
1500 pcs
->begin_lineno
= 0;
1501 // Spec says "maximum of eight characters" - we currently allow arbitrarily
1503 pcs
->Truncate
= INT_MAX
;
1504 pcs
->infer
= BIT(INFER_EXPORTS
) | // FIXME?
1511 // "[S]et all parameters (including the current name prefix) to their
1512 // defaults" but not the segment. We currently ignore the segment anyway.
1517 // FIXME: pcs->z[Q_DECLINATION] = HUGE_REAL;
1519 pcs
->recorded_style
= pcs
->style
= STYLE_NORMAL
;
1520 pcs
->ordering
= p_walls_options
->data_order_ct
;
1522 for (int i
= 0; i
< 3; ++i
) {
1523 osfree(p_walls_options
->prefix
[i
]);
1525 *p_walls_options
= walls_options_default
;
1529 read_walls_angle(real default_units
)
1531 real angle
= read_numeric(false);
1532 if (isalpha((unsigned char)ch
)) {
1534 // Only one letter is allowed here.
1535 if (s_str(&uctoken
)[1] != '\0') goto bad_angle_units
;
1536 if (s_str(&uctoken
)[0] == 'D') {
1538 angle
*= M_PI
/ 180.0;
1539 } else if (s_str(&uctoken
)[0] == 'G') {
1541 angle
*= M_PI
/ 200.0;
1542 } else if (s_str(&uctoken
)[0] == 'M') {
1544 angle
*= M_PI
/ 3200.0;
1547 compile_diagnostic(DIAG_ERR
|DIAG_COL
,
1548 /*Expecting ā%sā, ā%sā, or ā%sā*/188,
1552 angle
*= default_units
;
1558 read_walls_distance(real default_units
)
1560 real distance
= read_numeric(false);
1561 if (isalpha((unsigned char)ch
)) {
1563 // Only one letter is allowed here.
1564 if (s_str(&uctoken
)[1] != '\0') goto bad_distance_units
;
1565 if (s_str(&uctoken
)[0] == 'M') {
1567 } else if (s_str(&uctoken
)[0] == 'F') {
1569 distance
*= METRES_PER_FOOT
;
1572 compile_diagnostic(DIAG_ERR
|DIAG_COL
, /*Expecting ā%sā or ā%sā*/103, "F", "M");
1575 distance
*= default_units
;
1580 // Walls #FLAG values seem to be arbitrary strings - we attempt to infer
1581 // suitable Survex station flags from a few key words.
1582 static int parse_walls_flags(bool check_for_quote
)
1584 //#define DEBUG_WALLS_FLAGS
1585 int station_flags
= 0;
1586 bool quoted
= false;
1587 if (check_for_quote
) {
1595 #ifdef DEBUG_WALLS_FLAGS
1596 bool printed
= false;
1600 if (isComm(ch
) || isEol(ch
)) break;
1601 if (quoted
&& ch
== '"') {
1606 if (S_EQ(&uctoken
, "ENTRANCE")) {
1607 station_flags
|= BIT(SFLAGS_ENTRANCE
);
1608 } else if (S_EQ(&uctoken
, "FIX")) {
1609 station_flags
|= BIT(SFLAGS_FIXED
);
1610 } else if (s_empty(&token
)) {
1612 #ifdef DEBUG_WALLS_FLAGS
1613 } else if (S_EQ(&uctoken
, "LOWER") ||
1614 S_EQ(&uctoken
, "LEAD") ||
1615 S_EQ(&uctoken
, "SPRING") ||
1616 S_EQ(&uctoken
, "RESURGENCE") ||
1617 S_EQ(&uctoken
, "RISING") ||
1618 S_EQ(&uctoken
, "SINK") ||
1619 S_EQ(&uctoken
, "SWALLET") ||
1620 S_EQ(&uctoken
, "SUMP") ||
1621 S_EQ(&uctoken
, "SHAFT") ||
1622 S_EQ(&uctoken
, "UPPER") ||
1623 S_EQ(&uctoken
, "CAVE") ||
1624 S_EQ(&uctoken
, "GPS") || // -> FIXED flag?
1625 S_EQ(&uctoken
, "SURVEYED") ||
1626 S_EQ(&uctoken
, "BATS") ||
1627 S_EQ(&uctoken
, "MYOTIS") ||
1628 S_EQ(&uctoken
, "VELIFER") ||
1629 S_EQ(&uctoken
, "GATED") ||
1630 S_EQ(&uctoken
, "0") ||
1631 S_EQ(&uctoken
, "LOCATION")) {
1632 // With DEBUG_WALLS_FLAGS defined, run on real-world
1633 // data to capture more words which might also be usefully
1634 // mapped to Survex station flags.
1638 printf("*** Unknown flags:");
1640 printf(" %s", s_str(&token
));
1643 if (check_for_quote
) break;
1645 #ifdef DEBUG_WALLS_FLAGS
1646 if (printed
) printf("\n");
1648 return station_flags
;
1655 while (!isEol(ch
)) {
1657 if (s_empty(&token
) && isComm(ch
)) {
1661 get_pos(&fp_option
);
1663 // Assign to typed variable so we get a warning if we are
1664 // missing a case below.
1665 walls_units_opt opt
= match_tok(walls_units_opt_tab
,
1666 TABSIZE(walls_units_opt_tab
));
1668 case WALLS_UNITS_OPT_D
:
1669 case WALLS_UNITS_OPT_A
:
1670 case WALLS_UNITS_OPT_AB
:
1671 case WALLS_UNITS_OPT_V
:
1672 case WALLS_UNITS_OPT_VB
:
1673 case WALLS_UNITS_OPT_S
:
1674 case WALLS_UNITS_OPT_ORDER
:
1675 case WALLS_UNITS_OPT_DECL
:
1676 case WALLS_UNITS_OPT_INCA
:
1677 case WALLS_UNITS_OPT_INCAB
:
1678 case WALLS_UNITS_OPT_INCD
:
1679 case WALLS_UNITS_OPT_INCH
:
1680 case WALLS_UNITS_OPT_INCV
:
1681 case WALLS_UNITS_OPT_INCVB
:
1682 case WALLS_UNITS_OPT_GRID
:
1683 case WALLS_UNITS_OPT_CASE
:
1684 case WALLS_UNITS_OPT_LRUD
:
1685 case WALLS_UNITS_OPT_TAPE
:
1686 case WALLS_UNITS_OPT_TYPEAB
:
1687 case WALLS_UNITS_OPT_TYPEVB
:
1688 case WALLS_UNITS_OPT_UV
:
1689 case WALLS_UNITS_OPT_UVH
:
1690 case WALLS_UNITS_OPT_UVV
:
1691 // These options all require an argument, so check for `=` and
1695 compile_diagnostic(DIAG_ERR
|DIAG_COL
,
1696 /*Expecting ā%sā*/497, "=");
1705 case WALLS_UNITS_OPT_METERS
:
1706 pcs
->units
[Q_LENGTH
] =
1709 pcs
->units
[Q_DZ
] = 1.0;
1711 case WALLS_UNITS_OPT_FEET
:
1712 pcs
->units
[Q_LENGTH
] =
1715 pcs
->units
[Q_DZ
] = METRES_PER_FOOT
;
1717 case WALLS_UNITS_OPT_D
:
1719 // From testing it seems Walls only checks the initial letter - e.g.
1720 // "M", "METERS", "METRES", "F", "FEET" and even "FISH" are accepted,
1721 // but "X" gives an error.
1722 if (s_str(&uctoken
)[0] == 'M') {
1723 pcs
->units
[Q_LENGTH
] = 1.0;
1724 } else if (s_str(&uctoken
)[0] == 'F') {
1725 pcs
->units
[Q_LENGTH
] = METRES_PER_FOOT
;
1729 set_pos(&fp_option
);
1730 (void)nextch(); // Skip the `=`.
1731 compile_diagnostic(DIAG_ERR
|DIAG_COL
, /*Expecting ā%sā or ā%sā*/103, "F", "M");
1735 case WALLS_UNITS_OPT_A
:
1737 // It seems Walls only checks the initial letter.
1738 if (s_str(&uctoken
)[0] == 'D') {
1740 pcs
->units
[Q_BEARING
] = M_PI
/ 180.0;
1741 } else if (s_str(&uctoken
)[0] == 'G') {
1743 pcs
->units
[Q_BEARING
] = M_PI
/ 200.0;
1744 } else if (s_str(&uctoken
)[0] == 'M') {
1746 pcs
->units
[Q_BEARING
] = M_PI
/ 3200.0;
1750 set_pos(&fp_option
);
1751 (void)nextch(); // Skip the `=`.
1752 compile_diagnostic(DIAG_ERR
|DIAG_COL
,
1753 /*Expecting ā%sā, ā%sā, or ā%sā*/188,
1758 case WALLS_UNITS_OPT_AB
:
1760 // It seems Walls only checks the initial letter.
1761 if (s_str(&uctoken
)[0] == 'D') {
1763 pcs
->units
[Q_BACKBEARING
] = M_PI
/ 180.0;
1764 } else if (s_str(&uctoken
)[0] == 'G') {
1766 pcs
->units
[Q_BACKBEARING
] = M_PI
/ 200.0;
1767 } else if (s_str(&uctoken
)[0] == 'M') {
1769 pcs
->units
[Q_BACKBEARING
] = M_PI
/ 3200.0;
1773 set_pos(&fp_option
);
1774 (void)nextch(); // Skip the `=`.
1775 compile_diagnostic(DIAG_ERR
|DIAG_COL
,
1776 /*Expecting ā%sā, ā%sā, or ā%sā*/188,
1781 case WALLS_UNITS_OPT_V
:
1783 pcs
->f_clino_percent
= false;
1784 // It seems Walls only checks the initial letter.
1785 if (s_str(&uctoken
)[0] == 'D') {
1787 pcs
->units
[Q_GRADIENT
] = M_PI
/ 180.0;
1788 } else if (s_str(&uctoken
)[0] == 'G') {
1790 pcs
->units
[Q_GRADIENT
] = M_PI
/ 200.0;
1791 } else if (s_str(&uctoken
)[0] == 'M') {
1793 pcs
->units
[Q_GRADIENT
] = M_PI
/ 3200.0;
1794 } else if (s_str(&uctoken
)[0] == 'P') {
1795 pcs
->units
[Q_GRADIENT
] = 0.01;
1796 pcs
->f_clino_percent
= true;
1800 set_pos(&fp_option
);
1801 (void)nextch(); // Skip the `=`.
1802 compile_diagnostic(DIAG_ERR
|DIAG_COL
,
1803 /*Expecting ā%sā, ā%sā, ā%sā, or ā%sā*/189,
1804 "D", "G", "M", "P");
1808 case WALLS_UNITS_OPT_VB
:
1810 pcs
->f_backclino_percent
= false;
1811 // It seems Walls only checks the initial letter.
1812 if (s_str(&uctoken
)[0] == 'D') {
1814 pcs
->units
[Q_BACKGRADIENT
] = M_PI
/ 180.0;
1815 } else if (s_str(&uctoken
)[0] == 'G') {
1817 pcs
->units
[Q_BACKGRADIENT
] = M_PI
/ 200.0;
1818 } else if (s_str(&uctoken
)[0] == 'M') {
1820 pcs
->units
[Q_BACKGRADIENT
] = M_PI
/ 3200.0;
1821 } else if (s_str(&uctoken
)[0] == 'P') {
1822 pcs
->units
[Q_BACKGRADIENT
] = 0.01;
1823 pcs
->f_backclino_percent
= true;
1827 set_pos(&fp_option
);
1828 (void)nextch(); // Skip the `=`.
1829 compile_diagnostic(DIAG_ERR
|DIAG_COL
,
1830 /*Expecting ā%sā, ā%sā, ā%sā, or ā%sā*/189,
1831 "D", "G", "M", "P");
1835 case WALLS_UNITS_OPT_S
:
1837 // From testing it seems Walls only checks the initial letter - e.g.
1838 // "M", "METERS", "METRES", "F", "FEET" and even "FISH" are accepted,
1839 // but "X" gives an error.
1840 if (s_str(&uctoken
)[0] == 'M') {
1843 pcs
->units
[Q_DZ
] = 1.0;
1844 } else if (s_str(&uctoken
)[0] == 'F') {
1847 pcs
->units
[Q_DZ
] = METRES_PER_FOOT
;
1851 set_pos(&fp_option
);
1852 (void)nextch(); // Skip the `=`.
1853 compile_diagnostic(DIAG_ERR
|DIAG_COL
, /*Expecting ā%sā or ā%sā*/103, "F", "M");
1857 case WALLS_UNITS_OPT_ORDER
:
1859 int order
= match_tok(walls_order_tab
,
1860 TABSIZE(walls_order_tab
));
1862 compile_diagnostic(DIAG_ERR
|DIAG_TOKEN
, /*Data style ā%sā unknown*/65, s_str(&token
));
1866 if (order
& (1 << 24)) {
1867 order
&= ((1 << 24) - 1);
1869 p
= p_walls_options
->data_order_rect
+ 2;
1872 p
= p_walls_options
->data_order_ct
+ 2;
1875 *p
++ = (order
& 0xff);
1880 case WALLS_UNITS_OPT_DECL
:
1881 //pcs->declination = HUGE_REAL;
1882 // if (pcs->dec_filename == NULL) {
1883 pcs
->z
[Q_DECLINATION
] = -read_walls_angle(M_PI
/ 180.0);
1885 // (void)read_numeric(false);
1888 case WALLS_UNITS_OPT_INCA
:
1889 pcs
->z
[Q_BEARING
] = -read_walls_angle(pcs
->units
[Q_BEARING
]);
1891 case WALLS_UNITS_OPT_INCAB
:
1892 pcs
->z
[Q_BACKBEARING
] = -read_walls_angle(pcs
->units
[Q_BACKBEARING
]);
1894 case WALLS_UNITS_OPT_INCD
:
1895 pcs
->z
[Q_LENGTH
] = -read_walls_distance(pcs
->units
[Q_LENGTH
]);
1897 case WALLS_UNITS_OPT_INCH
:
1898 // INCH=0 is what we do anyway, so only warn about non-zero values.
1899 if (read_walls_distance(pcs
->units
[Q_LENGTH
]) != 0.0) {
1902 set_pos(&fp_option
);
1903 compile_diagnostic(DIAG_WARN
|DIAG_TOKEN
, /*Unknown command ā%sā*/12, s_str(&token
));
1907 case WALLS_UNITS_OPT_INCV
:
1908 pcs
->z
[Q_GRADIENT
] = -read_walls_angle(pcs
->units
[Q_GRADIENT
]);
1910 case WALLS_UNITS_OPT_INCVB
:
1911 pcs
->z
[Q_BACKGRADIENT
] = -read_walls_angle(pcs
->units
[Q_BACKGRADIENT
]);
1913 case WALLS_UNITS_OPT_GRID
:
1914 // FIXME: GRID= not useful with geo-referenced data?
1915 compile_diagnostic(DIAG_WARN
|DIAG_TOKEN
, /*Unknown command ā%sā*/12, s_str(&token
));
1916 (void)read_walls_angle(M_PI
/ 180.0);
1918 case WALLS_UNITS_OPT_RECT
:
1919 // There are two different RECT options, one with a
1920 // parameter and one without!
1923 // FIXME: A bearing to rotate cartesian data by, which we don't
1924 // currently support. 0 means true North (Survex always uses
1925 // grid North currently).
1926 compile_diagnostic(DIAG_WARN
|DIAG_TOKEN
, /*Unknown command ā%sā*/12, s_str(&token
));
1928 (void)read_walls_angle(M_PI
/ 180.0);
1930 pcs
->recorded_style
= pcs
->style
= STYLE_CARTESIAN
;
1931 pcs
->ordering
= p_walls_options
->data_order_rect
;
1934 case WALLS_UNITS_OPT_CASE
:
1936 // Walls documents `CASE = Upper / Lower / Mixed` which hints that
1937 // it only actually tests the first character. It also seems that
1938 // any other character is treated as `Mixed` too.
1939 switch (s_str(&uctoken
)[0]) {
1951 case WALLS_UNITS_OPT_CT
:
1952 pcs
->recorded_style
= pcs
->style
= STYLE_NORMAL
;
1953 pcs
->ordering
= p_walls_options
->data_order_ct
;
1955 case WALLS_UNITS_OPT_PREFIX
:
1956 case WALLS_UNITS_OPT_PREFIX2
:
1957 case WALLS_UNITS_OPT_PREFIX3
: {
1958 // PREFIX, etc without a value clear that level of the prefix.
1959 char *new_prefix
= NULL
;
1963 new_prefix
= read_walls_prefix();
1965 int i
= (int)WALLS_UNITS_OPT_PREFIX3
- (int)opt
;
1966 osfree(p_walls_options
->prefix
[i
]);
1967 p_walls_options
->prefix
[i
] = new_prefix
;
1970 case WALLS_UNITS_OPT_LRUD
: {
1971 // Value is F, T, FB, TB or one of those followed by eg. :UDRL (no
1972 // spaces around :). Default is F:LRUD.
1974 // We currently ignore LRUD, so can also ignore the settings for
1976 string val
= S_INIT
;
1981 case WALLS_UNITS_OPT_TAPE
:
1983 /* FIXME: Implement different taping methods? */
1984 /* IT, SS, IS, ST (default is IT). */
1986 case WALLS_UNITS_OPT_TYPEAB
:
1988 if (s_str(&uctoken
)[0] == 'N') {
1989 pcs
->z
[Q_BACKBEARING
] = 0.0;
1990 } else if (s_str(&uctoken
)[0] == 'C') {
1991 pcs
->z
[Q_BACKBEARING
] = M_PI
;
1995 set_pos(&fp_option
);
1996 (void)nextch(); // Skip the `=`.
1997 compile_diagnostic(DIAG_ERR
|DIAG_COL
, /*Expecting ā%sā or ā%sā*/103, "C", "N");
2002 // FIXME: Use threshold value.
2003 (void)read_numeric(false);
2004 if (!isBlank(ch
) && !isComm(ch
) && !isEol(ch
)) {
2005 // Walls quietly ignores junk after a valid number here.
2007 compile_diagnostic(DIAG_WARN
|DIAG_TOKEN
, /*Ignoring ā%sā*/506, s_str(&token
));
2011 if (toupper(ch
) == 'X') {
2013 // FIXME: Only use foresight (but check backsight).
2018 case WALLS_UNITS_OPT_TYPEVB
:
2020 if (s_str(&uctoken
)[0] == 'N') {
2021 pcs
->sc
[Q_BACKGRADIENT
] = 1.0;
2022 } else if (s_str(&uctoken
)[0] == 'C') {
2023 pcs
->sc
[Q_BACKGRADIENT
] = -1.0;
2027 set_pos(&fp_option
);
2028 (void)nextch(); // Skip the `=`.
2029 compile_diagnostic(DIAG_ERR
|DIAG_COL
, /*Expecting ā%sā or ā%sā*/103, "C", "N");
2034 // FIXME: Use threshold value.
2035 (void)read_numeric(false);
2036 if (!isBlank(ch
) && !isComm(ch
) && !isEol(ch
)) {
2037 // Walls quietly ignores junk after a valid number here.
2039 compile_diagnostic(DIAG_WARN
|DIAG_TOKEN
, /*Ignoring ā%sā*/506, s_str(&token
));
2043 if (toupper(ch
) == 'X') {
2045 // FIXME: Only use foresight (but check backsight).
2050 case WALLS_UNITS_OPT_UV
:
2051 case WALLS_UNITS_OPT_UVH
:
2052 case WALLS_UNITS_OPT_UVV
:
2053 // Scale factors for variances (with horizontal-only and
2054 // vertical-only variants). FIXME: Actually apply these!
2055 (void)read_numeric(false);
2056 if (!isBlank(ch
) && !isComm(ch
) && !isEol(ch
)) {
2057 // Walls quietly ignores junk after a valid number here.
2059 compile_diagnostic(DIAG_WARN
|DIAG_TOKEN
, /*Ignoring ā%sā*/506, s_str(&token
));
2062 case WALLS_UNITS_OPT_FLAG
:
2063 // Default flag to apply to stations in #FIX.
2067 p_walls_options
->fix_station_flags
= parse_walls_flags(true);
2069 p_walls_options
->fix_station_flags
= 0;
2072 case WALLS_UNITS_OPT_RESET
:
2073 // FIXME: This should be processed before other arguments!
2076 case WALLS_UNITS_OPT_RESTORE
: {
2077 // FIXME: Should this be processed before other arguments?
2078 if (!p_walls_options
->explicit) {
2079 /* TRANSLATORS: %s is replaced with e.g. BEGIN or .BOOK or #[ */
2080 compile_diagnostic(DIAG_ERR
|DIAG_TOKEN
, /*No matching %s*/192, "SAVE");
2083 pop_walls_options();
2086 case WALLS_UNITS_OPT_SAVE
: {
2087 // FIXME: This should be processed before other arguments!
2088 push_walls_options();
2089 p_walls_options
->explicit = true;
2092 case WALLS_UNITS_OPT_NULL
:
2093 if (s_str(&uctoken
)[0] == '\0' && ch
== '$') {
2094 // Macro definition.
2098 string name
= S_INIT
;
2099 while (!isBlank(ch
) && !isComm(ch
) && !isEol(ch
) && ch
!= '=') {
2100 s_catchar(&name
, ch
);
2103 if (!s_empty(&name
)) {
2106 // Set an empty value.
2107 walls_set_macro(&walls_macros
, &name
, NULL
);
2110 string val
= S_INIT
;
2112 walls_set_macro(&walls_macros
, &name
, s_steal(&val
));
2120 compile_diagnostic(DIAG_ERR
|DIAG_TOKEN
, /*Unknown command ā%sā*/12, s_str(&token
));
2122 // Skip over `=` and the rest of the argument so we handle a
2123 // typo-ed option name nicely.
2126 } while (!isBlank(ch
) && !isComm(ch
) && !isEol(ch
));
2130 // pcs->z[Q_BACKBEARING] = pcs->z[Q_BEARING] = -rad(read_numeric(false));
2131 // pcs->z[Q_BACKGRADIENT] = pcs->z[Q_GRADIENT] = -rad(read_numeric(false));
2132 // pcs->z[Q_LENGTH] = -METRES_PER_FOOT * read_numeric(false);
2134 /* Original "Inclination Units" were "Depth Gauge". */
2135 //pcs->recorded_style = STYLE_DIVING;
2142 data_file_walls_srv(void)
2144 // FIXME: Do any of these variables need to be volatile to protect them
2145 // from longjmp()? GCC isn't warning about them...
2147 bool standalone
= (p_walls_options
== NULL
);
2149 // We're being included standalone rather than from a WPJ file.
2150 walls_initialise_settings();
2154 // Default flags assigned to stations in #FIX.
2155 int fix_station_flags
= p_walls_options
->fix_station_flags
;
2157 // FIXME: We need to update the separator_map to reflect what can be
2158 // SPECIAL_NAMES. Or should we use the Compass approach and base this
2159 // on what's actually used? The first approach would pick the separator
2160 // from {':', ';', ',', '#', space}; the latter would pick '.' if
2161 // the station naming recommendations in the Walls documentation are
2163 update_output_separator();
2165 #ifdef HAVE_SETJMP_H
2166 /* errors in nested functions can longjmp here */
2167 if (setjmp(file
.jbSkipLine
)) {
2173 if (pcs
->style
== STYLE_NORMAL
)
2174 pcs
->ordering
= p_walls_options
->data_order_ct
;
2176 pcs
->ordering
= p_walls_options
->data_order_rect
;
2178 while (ch
!= EOF
&& !ferror(file
.fh
)) {
2182 if (ch
== ';' || isEol(ch
)) {
2185 } else if (pcs
->style
== STYLE_NORMAL
) {
2188 // Set up Dz in case it's omitted.
2197 int leading_blanks
= ftell(file
.fh
) - file
.lpos
- 1;
2200 // "Commented out" data.
2201 int start_lineno
= file
.line
;
2206 while (depth
&& ch
!= EOF
) {
2210 depth
+= (ch
== '[') - (ch
== ']');
2216 /* TRANSLATORS: %s and %s are replaced with e.g. BEGIN and END
2218 error_in_file(file
.filename
, start_lineno
,
2219 /*%s with no matching %s in this file*/23,
2225 /* TRANSLATORS: %s is replaced with e.g. BEGIN or .BOOK or #[ */
2226 compile_diagnostic(DIAG_ERR
|DIAG_SKIP
, /*No matching %s*/192, "#[");
2229 int blanks_after_hash
= ftell(file
.fh
) - file
.lpos
- leading_blanks
- 2;
2231 walls_cmd directive
= match_tok(walls_cmd_tab
, TABSIZE(walls_cmd_tab
));
2233 volatile int ch_store
;
2234 string line
= S_INIT
;
2235 if (directive
!= WALLS_CMD_FIX
&& directive
!= WALLS_CMD_NULL
) {
2236 bool seen_macros
= false;
2237 // Recreate the start of the line for error reporting.
2238 // FIXME: This will change tabs to a single space...
2240 s_catn(&line
, leading_blanks
, ' ');
2241 s_catchar(&line
, '#');
2242 if (blanks_after_hash
)
2243 s_catn(&line
, blanks_after_hash
, ' ');
2244 s_cats(&line
, &token
);
2249 // Expand macros such as $(foo) in rest of line.
2250 while (!isEol(ch
)) {
2252 s_catchar(&line
, ch
);
2258 s_catchar(&line
, '$');
2262 // Read name of macro onto the end of line, then replace it
2263 // with the value of the macro.
2264 int macro_start
= s_len(&line
);
2265 while (!isEol(ch
) && ch
!= ')') {
2266 s_catchar(&line
, ch
);
2270 const char *name
= s_str(&line
) + macro_start
;
2271 int name_len
= s_len(&line
) - macro_start
;
2272 const char *macro
= walls_get_macro(&walls_macros
,
2275 macro
= walls_get_macro(&walls_macros_wpj
, name
, name_len
);
2278 compile_diagnostic(DIAG_ERR
, /*Macro ā%sā not defined*/499,
2279 s_str(&line
) + macro_start
);
2281 s_truncate(&line
, macro_start
);
2283 s_cat(&line
, macro
);
2288 //printf("MACRO EXPANSION <%s>\n", line);
2289 // Read from the buffered macro-expanded line instead of the
2293 #ifdef HAVE_FMEMOPEN
2294 file
.fh
= fmemopen((char*)s_str(&line
), s_len(&line
), "rb");
2296 fatalerror(/*Failed to create temporary file*/498);
2299 file
.fh
= tmpfile();
2301 fatalerror(/*Failed to create temporary file*/498);
2303 fwrite(s_str(&line
), s_len(&line
), 1, file
.fh
);
2305 fseek(file
.fh
, fp_args
.offset
- file
.lpos
, SEEK_SET
);
2306 ch
= (unsigned char)s_str(&line
)[fp_args
.offset
- file
.lpos
- 1];
2309 //printf("no macros seen in <%s>\n", line);
2310 // No macros seen so rewind and read directly from the file.
2316 switch (directive
) {
2317 case WALLS_CMD_UNITS
:
2320 case WALLS_CMD_DATE
: {
2321 int year
, month
, day
;
2322 read_walls_srv_date(&year
, &month
, &day
);
2323 copy_on_write_meta(pcs
);
2324 int days
= days_since_1900(year
, month
, day
);
2325 pcs
->meta
->days1
= pcs
->meta
->days2
= days
;
2327 if (!isEol(ch
) && !isComm(ch
)) {
2328 // Walls seems to ignore anything after the date so make this a
2329 // warning not an error so we can process existing Walls
2330 // datasets (e.g. the Mammoth dataset reportedly has `#Date
2332 compile_diagnostic(DIAG_WARN
|DIAG_TAIL
, /*End of line not blank*/15);
2337 case WALLS_CMD_FIX
: {
2341 prefix
*name
= read_walls_station(p_walls_options
->prefix
, false);
2342 // FIXME: can be e.g. `W97:43:52.5 N31:16:45 323f`
2343 // Or E/S instead of W/N.
2345 enum { UNKNOWN
, LATLONG
, UTM
} format
= UNKNOWN
;
2346 for (int i
= 0; i
< 3; ++i
) {
2347 // The order of the coordinates is specified by data_order_rect.
2348 int compiletimeassert_dxdydz
[Dy
- Dx
== 1 && Dz
- Dy
== 1 ? 1 : -1];
2349 (void)compiletimeassert_dxdydz
;
2350 int dim
= p_walls_options
->data_order_rect
[i
+ 2] - Dx
;
2351 if ((unsigned)dim
> 2) {
2352 // FIXME: Survex doesn't currently support horizontal-only
2360 int upper_ch
= toupper(ch
);
2361 if (dim
== 2 || format
== UTM
|| strchr("NWES", upper_ch
) == NULL
) {
2362 // Read as a distance if this is the altitude, or we've
2363 // already seen a distance for x or y, or if the coordinate
2364 // doesn't start with a compass point letter.
2365 coord
= read_numeric(false);
2366 if (ch
== 'F' || ch
== 'f') {
2367 coord
*= METRES_PER_FOOT
;
2369 } else if (ch
== 'M' || ch
== 'm') {
2372 coord
*= pcs
->units
[Q_LENGTH
];
2374 if (dim
!= 2) format
= UTM
;
2376 // Set negate if S or W.
2377 bool negate
= ((upper_ch
& 3) == 3);
2378 bool e_or_w
= ((upper_ch
& 5) == 5);
2379 if (dim
== e_or_w
) {
2380 compile_diagnostic(DIAG_ERR
|DIAG_COL
, /*Expecting ā%sā or ā%sā*/103,
2381 e_or_w
? "N" : "E", e_or_w
? "S" : "W");
2384 coord
= read_number(false, true);
2385 if (negate
) coord
= -coord
;
2390 coords
[dim
] = coord
;
2393 real var_xy
= 0.0, var_z
= 0.0;
2396 // Optional variance override "with no embedded tabs or spaces".
2397 // E.g. `(R5,?)` specifies horizontal and vertical so:
2398 // `R5` means 5m RMS horizontal error
2399 // `?` specifies no elevations were obtained - infinite variance.
2400 // `*` means ... probably the same as `?` for a fixed point.
2401 // <non-negative number> means treat as compass and tape vector
2402 // of that length (in length units from #units)
2403 // `` (empty) means don't override that one
2404 // no comma e.g. `(R5)` means apply to both h and v
2408 if (ch
== ',' || ch
== ')') {
2409 // Use default variance, which is exact for a fixed point.
2411 } else if (ch
== '?' || ch
== '*') {
2412 // Infinite variance. It seems `?` and `*` effectively
2413 // mean the same for a fixed point. We don't really
2414 // support this in general but if it's set both
2415 // horizontally and vertically we can ignore it, and if
2416 // it's set for one we can bodge it with a large value.
2420 if (ch
== 'R' || ch
== 'r') {
2425 val_h
= read_number(false, true);
2426 if (ch
== 'F' || ch
== 'f') {
2427 val_h
*= METRES_PER_FOOT
;
2429 } else if (ch
== 'M' || ch
== 'm') {
2432 val_h
*= pcs
->units
[Q_LENGTH
];
2441 // Use default variance, which is exact for a fixed point.
2443 } else if (ch
== '?' || ch
== '*') {
2444 // Infinite variance. It seems `?` and `*` effectively
2445 // mean the same for a fixed point. We don't really
2446 // support this in general but if it's set both
2447 // horizontally and vertically we can ignore it, and if
2448 // it's set for one we can bodge it with a large value.
2452 if (ch
== 'R' || ch
== 'r') {
2457 val_v
= read_number(false, true);
2458 if (ch
== 'F' || ch
== 'f') {
2459 val_v
*= METRES_PER_FOOT
;
2461 } else if (ch
== 'M' || ch
== 'm') {
2464 val_v
*= pcs
->units
[Q_LENGTH
];
2472 compile_diagnostic(DIAG_ERR
|DIAG_COL
,
2473 /*Expecting ā%sā*/497, ")");
2477 // Use default variance, which is exact for a fixed point.
2478 } else if (val_h
== HUGE_REAL
) {
2479 // Infinite variance. It seems `?` and `*` effectively
2480 // mean the same for a fixed point. We don't really
2481 // support this in general but if it's set both
2482 // horizontally and vertically we can ignore it, and if
2483 // it's set for one we can bodge it with a large value.
2486 // "Note that if you make an assignment like (R10), which
2487 // is the same as (R10,R10), you won't be giving all three
2488 // error components identical variances. The vertical
2489 // component in this case would be given variance 10Ā² =
2490 // 100, while each horizontal component would be given half
2491 // that variance, or 50".
2492 var_xy
= val_h
* val_h
/ 2.0;
2494 // The value is to be treated as the length of a leg to use
2495 // the variances of, so this is based on the leg variance
2496 // calculations except we don't have any angles here. Note
2497 // that for Survex the leg length does not affect the
2499 var_xy
= var(Q_POS
) / 3.0 + var(Q_LENGTH
) / 2.0;
2502 // Use default variance, which is exact for a fixed point.
2503 } else if (val_v
== HUGE_REAL
) {
2504 // Infinite variance.
2507 var_z
= val_v
* val_v
;
2509 // The value is to be treated as the length of a leg to use
2510 // the variances of, so this is based on the leg variance
2511 // calculations except we don't have any angles here. Note
2512 // that for Survex the leg length does not affect the
2514 var_z
= var(Q_POS
) / 3.0 + var(Q_LENGTH
) / 2.0;
2519 // Station note - ignore for now. Note: Must be '/'.
2523 if (format
== LATLONG
) {
2524 // Convert coordinates based on the current coordinate system
2525 // set by .REF in the wpj.
2526 if (walls_ref
.img_datum_code
> 0 && proj_str_out
) {
2528 img_compass_longlat_epsg_code(walls_ref
.img_datum_code
);
2529 char proj_longlat
[32];
2530 snprintf(proj_longlat
, sizeof(proj_longlat
),
2531 "EPSG:%d", epsg_code
);
2532 PJ
*transform
= proj_create_crs_to_crs(PJ_DEFAULT_CTX
,
2537 /* Normalise the output order so x is longitude and y
2538 * latitude - by default new PROJ has them switched for
2539 * EPSG:4326 which just seems confusing.
2541 PJ
* pj_norm
= proj_normalize_for_visualization(PJ_DEFAULT_CTX
,
2543 proj_destroy(transform
);
2544 transform
= pj_norm
;
2547 if (proj_angular_input(transform
, PJ_FWD
)) {
2548 /* Input coordinate system expects radians. */
2549 coords
[0] = rad(coords
[0]);
2550 coords
[1] = rad(coords
[1]);
2553 PJ_COORD coord
= {{coords
[0], coords
[1], coords
[2], HUGE_VAL
}};
2554 coord
= proj_trans(transform
, PJ_FWD
, coord
);
2555 coords
[0] = coord
.xyzt
.x
;
2556 coords
[1] = coord
.xyzt
.y
;
2557 coords
[2] = coord
.xyzt
.z
;
2559 if (coords
[0] == HUGE_VAL
|| coords
[1] == HUGE_VAL
|| coords
[2] == HUGE_VAL
) {
2560 compile_diagnostic(DIAG_ERR
, /*Failed to convert coordinates: %s*/436,
2561 proj_context_errno_string(PJ_DEFAULT_CTX
,
2562 proj_errno(transform
)));
2563 /* Set dummy values which are finite. */
2564 coords
[0] = coords
[1] = coords
[2] = 0;
2566 proj_destroy(transform
);
2568 if (walls_ref
.img_datum_code
== 0) {
2569 // We already emitted an error that this datum is not
2570 // supported so an error here doesn't seem helpful.
2572 compile_diagnostic(DIAG_ERR
, /*Output coordinate system not set*/488);
2574 /* Set dummy values which are finite. */
2575 coords
[0] = coords
[1] = coords
[2] = 0;
2579 // Apply station flags inferred from Walls "default flag".
2580 name
->sflags
|= fix_station_flags
;
2582 if (var_xy
== 0.0 && var_z
== 0.0) {
2584 int fix_result
= fix_station(name
, coords
);
2588 if (fix_result
< 0) {
2589 // The equivalent of this is an error for .svx files,
2590 // but Walls explicitly documents that a repeated exact
2591 // fix warns and uses the coordinates of the first fix.
2592 compile_diagnostic(DIAG_WARN
|DIAG_WORD
, /*Station already fixed or equated to a fixed point*/46);
2594 compile_diagnostic(DIAG_WARN
|DIAG_WORD
, /*Station already fixed at the same coordinates*/55);
2596 compile_diagnostic_pfx(DIAG_INFO
, name
, /*Previously fixed or equated here*/493);
2602 if (var_xy
== HUGE_REAL
&& var_z
== HUGE_REAL
) {
2603 // We've been asked to not fix horizontally or vertically!
2607 // If only one variance is exact, make it 1mm instead. FIXME
2608 if (var_xy
== 0.0) var_xy
= 1e-6;
2609 if (var_z
== 0.0) var_z
= 1e-6;
2611 // We don't currently handle fixing only horizontally or only
2612 // vertically so for now just assign a large variance. FIXME
2613 if (var_xy
== HUGE_REAL
) var_xy
= 1e6
;
2614 if (var_z
== HUGE_REAL
) var_z
= 1e6
;
2616 fix_station_with_variance(name
, coords
, var_xy
, var_xy
, var_z
2617 #ifndef NO_COVARIANCES
2623 case WALLS_CMD_FLAG
: {
2624 // The flag comes after a list of stations, so to avoid having to
2625 // store a list of the stations we note the position, scan ahead
2626 // and parse the flag, then come back and actually parse the
2627 // stations and apply the flag.
2629 if (isEol(ch
) || isComm(ch
)) {
2630 // Just "#FLAG" with no arguments clears the default flag.
2631 fix_station_flags
= 0;
2634 bool setting_default_flag
= isWallsSlash(ch
);
2638 while (!isWallsSlash(ch
)) {
2639 if (isComm(ch
) || isEol(ch
)) {
2640 // The flag name is not required (it "can *optionally*
2641 // follow the list of station names" (my emphasis).
2642 // Elsewhere the docs say:
2644 // Another automatically assigned list item is
2645 // "{Unnamed Flag}", which is present only if
2646 // stations appear on a #Flag directive without a
2647 // name parameter -- for example, "#FLAG A1 A2 A3".
2648 // There's really no reason to have any of those,
2649 // although Walls has always allowed them.
2651 // These seem to occur in real data, but we ignore
2652 // unknown flag names, so it seems reasonable to just
2653 // ignore these too. Or maybe we should warn? FIXME
2660 int station_flags
= parse_walls_flags(false);
2662 if (setting_default_flag
) {
2663 fix_station_flags
= station_flags
;
2667 // Suppress "unused fixed point" warnings for stations in #flag.
2668 station_flags
|= BIT(SFLAGS_USED
);
2670 // Go back and read stations and apply the flags.
2674 // It seems / and \ can't be used in #flag station names?
2675 // FIXME: Need to actually test this with Walls.
2676 int save_translate_slash
= pcs
->Translate
['/'];
2677 int save_translate_bslash
= pcs
->Translate
['\\'];
2678 pcs
->Translate
['/'] = 0;
2679 pcs
->Translate
['\\'] = 0;
2680 while (!isWallsSlash(ch
)) {
2681 prefix
*name
= read_walls_station(p_walls_options
->prefix
, false);
2682 name
->sflags
|= station_flags
;
2685 pcs
->Translate
['/'] = save_translate_slash
;
2686 pcs
->Translate
['\\'] = save_translate_bslash
;
2690 case WALLS_CMD_PREFIX
:
2691 case WALLS_CMD_PREFIX2
:
2692 case WALLS_CMD_PREFIX3
: {
2693 char *new_prefix
= read_walls_prefix();
2694 int i
= (int)WALLS_CMD_PREFIX3
- (int)directive
;
2695 osfree(p_walls_options
->prefix
[i
]);
2696 p_walls_options
->prefix
[i
] = new_prefix
;
2698 if (!isEol(ch
) && !isComm(ch
)) {
2699 // Walls seems to ignore anything after the prefix so make this a
2700 // warning not an error so we can process existing Walls
2701 // datasets (e.g. the Mammoth dataset reportedly has `#prefix
2702 // 4136 Lucys domes`).
2703 compile_diagnostic(DIAG_WARN
|DIAG_TAIL
, /*End of line not blank*/15);
2708 case WALLS_CMD_NOTE
: {
2709 // A text note attached to a station - ignore for now except we
2710 // read the station name and flag it to avoid an "unused fixed
2712 prefix
*name
= read_walls_station(p_walls_options
->prefix
, false);
2713 name
->sflags
|= BIT(SFLAGS_USED
);
2717 case WALLS_CMD_SEGMENT
:
2718 // "Segments are optional and have no affect on the compilation of
2719 // survey data" so ignore for now.
2722 case WALLS_CMD_SYMBOL
:
2723 // Now to draw symbols. Not really appropriate here as this is
2724 // presentation information, so we just ignore it.
2727 case WALLS_CMD_NULL
:
2728 // FIXME it's a "directive" in Walls-speak.
2729 compile_diagnostic(DIAG_ERR
|DIAG_TOKEN
|DIAG_SKIP
, /*Unknown command ā%sā*/12, s_str(&token
));
2733 if (!s_empty(&line
)) {
2734 // Revert to reading from the file.
2747 // Clear all macros set in this SRV file.
2748 for (unsigned i
= 0; i
< WALLS_MACRO_HASH_SIZE
; ++i
) {
2749 walls_macro
*p
= walls_macros
[i
];
2751 walls_macro
*to_free
= p
;
2755 walls_macros
[i
] = NULL
;
2759 while (p_walls_options
->explicit) {
2760 // FIXME: Walls quietly allows SAVE without a corresponding RESTORE, but
2761 // probably worth at least a warning here.
2762 pop_walls_options();
2764 if (standalone
) pop_walls_options();
2769 WALLS_WPJ_CMD_ENDBOOK
,
2771 WALLS_WPJ_CMD_OPTIONS
,
2774 WALLS_WPJ_CMD_STATUS
,
2775 WALLS_WPJ_CMD_SURVEY
,
2776 WALLS_WPJ_CMD_NULL
= -1
2779 static const sztok walls_wpj_cmd_tab
[] = {
2780 {"BOOK", WALLS_WPJ_CMD_BOOK
},
2781 {"ENDBOOK", WALLS_WPJ_CMD_ENDBOOK
},
2782 {"NAME", WALLS_WPJ_CMD_NAME
},
2783 {"OPTIONS", WALLS_WPJ_CMD_OPTIONS
},
2784 {"PATH", WALLS_WPJ_CMD_PATH
},
2785 {"REF", WALLS_WPJ_CMD_REF
},
2786 {"STATUS", WALLS_WPJ_CMD_STATUS
},
2787 {"SURVEY", WALLS_WPJ_CMD_SURVEY
},
2788 {NULL
, WALLS_WPJ_CMD_NULL
}
2792 data_file_walls_wpj(void)
2794 // FIXME: Do any of these variables need to be volatile to protect them
2795 // from longjmp()? GCC isn't warning about them...
2796 char *pth
= path_from_fnm(file
.filename
);
2798 walls_initialise_settings();
2801 // FIXME: We need to update the separator_map to reflect what can be
2802 // SPECIAL_NAMES. Or should we use the Compass approach and base this
2803 // on what's actually used? The first approach would pick the separator
2804 // from {':', ';', ',', '#', space}; the latter would pick '.' if
2805 // the documentation station naming recommendations were followed.
2806 update_output_separator();
2808 /* We need to update separator_map so we don't pick a separator character
2809 * which occurs in a station name. However Compass DAT allows everything
2810 * >= ASCII char 33 except 127 in station names so if we just added all
2811 * the valid station name characters we'd always pick space as the
2812 * separator for any dataset which included a DAT file, yet in practice
2813 * '.' is never used in any of the sample DAT files I've seen. So
2814 * instead we scan the characters actually used in station names when we
2815 * process CompassDATFr and CompassDATTo fields. (FIXME)
2818 // Start from the location of this WPJ.
2819 s_cat(&p_walls_options
->path
, pth
);
2821 #ifdef HAVE_SETJMP_H
2822 /* errors in nested functions can longjmp here */
2823 if (setjmp(file
.jbSkipLine
)) {
2830 long name_lpos
= -1;
2831 unsigned name_lineno
= 0;
2833 string name
= S_INIT
;
2835 walls_ref
.x
= walls_ref
.y
= walls_ref
.z
= HUGE_VAL
;
2839 int detached_nest_level
= 0;
2840 bool in_survey
= false;
2841 while (!ferror(file
.fh
)) {
2842 walls_wpj_cmd tok
= WALLS_WPJ_CMD_NULL
;
2846 if (!in_survey
) break;
2847 // Ensure that a survey at the end of the WPJ file is processed
2848 // even if not contained in a book. Not seen in example data, so
2849 // not sure if this is actually valid but this code will just be
2850 // unused if it isn't.
2853 // If the line isn't blank or a comment then process_eol() will
2854 // issue a suitable error.
2860 get_token_no_blanks();
2861 tok
= match_tok(walls_wpj_cmd_tab
, TABSIZE(walls_wpj_cmd_tab
));
2862 if (detached_nest_level
) {
2864 case WALLS_WPJ_CMD_BOOK
:
2865 ++detached_nest_level
;
2868 case WALLS_WPJ_CMD_ENDBOOK
:
2869 --detached_nest_level
;
2872 // Ignore everything else.
2880 (tok
== WALLS_WPJ_CMD_SURVEY
||
2881 tok
== WALLS_WPJ_CMD_BOOK
||
2882 tok
== WALLS_WPJ_CMD_ENDBOOK
)) {
2884 // Process the current entry.
2886 // .STATUS is a decimal integer which is a bitmap of flags.
2887 // Meanings mostly cribbed from dewalls:
2889 // Set if a branch is expanded in the UI - we can ignore.
2890 #define WALLS_WPJ_STATUS_BOOK_OPEN 0x000001
2891 // Detached items are not processed as part of higher level
2893 #define WALLS_WPJ_STATUS_DETACHED 0x000002
2894 // 0x000004 appears to be unused/no longer used. Setting it
2895 // externally in a WPJ file and then loading it into Walls and
2896 // forcing saving clears it.
2897 #define WALLS_WPJ_STATUS_UNUSED_BIT2 0x000004
2898 // We ignore segments currently so ignore this too.
2899 #define WALLS_WPJ_STATUS_NAME_DEFINES_SEGMENT 0x000008
2900 // Controls the units used in reporting data - we can ignore.
2901 #define WALLS_WPJ_STATUS_REVIEW_UNITS_FEET 0x000010
2902 // Comments in dewalls-java suggests this is no longer used.
2903 #define WALLS_WPJ_STATUS_UNUSED_BIT5 0x000020
2904 // These WALLS_WPJ_STATUS_*_TRISTATE values are (shifted) binary:
2905 // 00 Inherit value from parent
2909 #define WALLS_WPJ_STATUS_USE_REFERENCE_TRISTATE 0x0000c0
2910 #define WALLS_WPJ_STATUS_DECLINATION_AUTO_TRISTATE 0x000300
2911 #define WALLS_WPJ_STATUS_UTM_GPS_RELATIVE_TRISTATE 0x000c00
2912 // AIUI these just control distributing loop misclosure:
2913 #define WALLS_WPJ_STATUS_PRESERVE_PLUMB_ORIENTATION_TRISTATE 0x003000
2914 #define WALLS_WPJ_STATUS_PRESERVE_PLUMB_LENGTH_TRISTATE 0x00c000
2915 // Attached file of arbitrary type (we can just ignore):
2916 #define WALLS_WPJ_STATUS_TYPE_OTHER 0x010000
2917 // We can ignore these:
2918 #define WALLS_WPJ_STATUS_EDIT_ON_LAUNCH 0x020000
2919 #define WALLS_WPJ_STATUS_OPEN_ON_LAUNCH 0x040000
2920 #define WALLS_WPJ_STATUS_DEFAULT_VIEW_MASK 0x380000
2921 #define WALLS_WPJ_STATUS_PROCESS_SVG 0x400000
2923 // A quirk is that the root item is flagged with
2924 // WALLS_WPJ_STATUS_DETACHED (seems the flag might be more like
2925 // "don't draw a connecting line left from here").
2926 if ((status
& WALLS_WPJ_STATUS_DETACHED
) && depth
> 0) {
2928 //printf("*** Detached survey\n");
2929 goto detached_or_not_srv
;
2931 if ((status
& WALLS_WPJ_STATUS_TYPE_OTHER
)) {
2932 // Attached file of arbitrary type.
2933 goto detached_or_not_srv
;
2935 if (s_empty(&name
)) {
2936 printf("*** in_survey but no/empty NAME\n");
2937 goto detached_or_not_srv
;
2940 // Include SRV file.
2942 printf("+++ %s %s .SRV :%s:%s:%s\n", s_str(&p_walls_options
->path
), s_str(&name
),
2943 p_walls_options
->prefix
[0] ? p_walls_options
->prefix
[0] : "",
2944 p_walls_options
->prefix
[1] ? p_walls_options
->prefix
[1] : "",
2945 p_walls_options
->prefix
[2] ? p_walls_options
->prefix
[2] : "");
2948 FILE *fh
= fopen_portable(s_str(&p_walls_options
->path
),
2949 s_str(&name
), "srv", "rb", &filename
);
2951 fh
= fopen_portable(s_str(&p_walls_options
->path
),
2952 s_str(&name
), "SRV", "rb", &filename
);
2955 // Report the diagnostic at the location of the ".NAME".
2956 unsigned save_line
= file
.line
;
2957 long save_lpos
= file
.lpos
;
2961 file
.lpos
= name_lpos
;
2962 file
.line
= name_lineno
;
2963 // Report the resolved path. FIXME: Maybe we should use
2964 // full_file in the fopen_portable() call above so things
2966 string full_file
= S_INIT
;
2967 s_cats(&full_file
, &p_walls_options
->path
);
2968 s_catchar(&full_file
, FNM_SEP_LEV
);
2969 s_cats(&full_file
, &name
);
2970 s_cat(&full_file
, ".SRV");
2971 if (!fDirectory(s_str(&p_walls_options
->path
))) {
2972 // Walls appears to quietly ignore file if the
2973 // directory does not exist, but it seems worth
2974 // warning about at least.
2976 // FIXME: This should take case into account like
2977 // opening the file does.
2978 compile_diagnostic(DIAG_WARN
|DIAG_TAIL
, /*Couldnāt open file ā%sā*/24, s_str(&full_file
));
2980 compile_diagnostic(DIAG_ERR
|DIAG_TAIL
, /*Couldnāt open file ā%sā*/24, s_str(&full_file
));
2984 file
.line
= save_line
;
2985 file
.lpos
= save_lpos
;
2990 parse file_store
= file
;
2992 if (file
.fh
) file
.parent
= &file_store
;
2994 file
.filename
= filename
;
2997 file
.reported_where
= false;
3000 using_data_file(file
.filename
);
3002 push_walls_options();
3003 walls_swap_macro_tables();
3004 data_file_walls_srv();
3005 walls_swap_macro_tables();
3006 pop_walls_options();
3008 if (ferror(file
.fh
))
3009 fatalerror_in_file(file
.filename
, 0, /*Error reading file*/18);
3011 (void)fclose(file
.fh
);
3013 /* don't free this - it may be pointed to by prefix.file */
3014 /* osfree(file.filename); */
3024 walls_ref
.x
= walls_ref
.y
= walls_ref
.z
= HUGE_VAL
;
3026 detached_or_not_srv
:
3027 pop_walls_options();
3030 if (tok
== WALLS_WPJ_CMD_NULL
) break;
3034 case WALLS_WPJ_CMD_BOOK
:
3036 push_walls_options();
3040 case WALLS_WPJ_CMD_SURVEY
:
3041 push_walls_options(); // FIXME: Eliminate redundant push and pop inside data_file_walls_srv()
3045 case WALLS_WPJ_CMD_ENDBOOK
:
3047 compile_diagnostic(DIAG_ERR
|DIAG_SKIP
, /*No matching %s*/192, ".BOOK");
3050 pop_walls_options();
3053 case WALLS_WPJ_CMD_OPTIONS
:
3056 case WALLS_WPJ_CMD_PATH
: {
3059 if (!s_empty(&p_walls_options
->path
)) {
3060 s_catchar(&p_walls_options
->path
, FNM_SEP_LEV
);
3062 while (!isEol(ch
)) {
3066 s_catchar(&p_walls_options
->path
, ch
);
3070 //printf("PATH: %s\n", s_str(&p_walls_options->path));
3073 case WALLS_WPJ_CMD_REF
:
3074 walls_ref
.y
= read_numeric(false);
3075 walls_ref
.x
= read_numeric(false);
3076 // Walls supports UPS zones and uses -61 and 61 to specify them.
3077 walls_ref
.zone
= read_int(-61, 61);
3079 // Ignore pre-computed convergence as we compute that for ourselves.
3080 (void)read_numeric(false);
3082 walls_ref
.z
= read_numeric(false);
3084 // Ignore field which seems to be a bitmask.
3086 // From experimenting with Walls32.exe and looking at the saved
3087 // .wpj file contents, the bottom two bits (mask 0x03) seem to
3088 // encode the format (d/dm/dms) that the Walls32.exe UI uses to
3089 // display/enter lat/long.
3091 // Bit 0x04 seems to be "West of meridian?" and bit 0x08 seems
3092 // to be "South of equator?" (and the lat and long seem to be
3093 // stored without signs).
3095 // Other bits seem to be unused so it seems we can safely ignore
3099 // Ignore same location in lat/long.
3100 (void)read_numeric(false);
3101 (void)read_numeric(false);
3102 (void)read_numeric(false);
3103 (void)read_numeric(false);
3104 (void)read_numeric(false);
3105 (void)read_numeric(false);
3107 // Ignore integer index for the datum (e.g. 27 for "WGS 1984").
3108 // The string names seem more likely to have not changed over time.
3111 string datum_str
= S_INIT
;
3112 read_string(&datum_str
);
3113 int datum
= img_parse_compass_datum_string(s_str(&datum_str
),
3116 // Walls has different name strings to Compass for some datums.
3117 if (S_EQ(&datum_str
, "NAD27 CONUS")) {
3118 // FIXME Assuming this is right. Walls seems to have 6
3119 // variant NAD27 datums, not sure what's going on.
3120 datum
= img_DATUM_NAD27
;
3121 } else if (S_EQ(&datum_str
, "NAD83")) {
3122 datum
= img_DATUM_NAD83
;
3123 } else if (S_EQ(&datum_str
, "Geodetic Datum `49")) {
3124 datum
= img_DATUM_NZGD49
;
3125 } else if (S_EQ(&datum_str
, "Hu-Tzu-Shan")) {
3126 datum
= img_DATUM_HUTZUSHAN1950
;
3128 compile_diagnostic(DIAG_ERR
|DIAG_WIDTH(s_len(&datum_str
)),
3129 /*Datum ā%sā not supported*/503,
3135 if (datum
&& walls_ref
.zone
&& abs(walls_ref
.zone
) <= 60) {
3136 char *proj_str
= img_compass_utm_proj_str(datum
,
3138 set_declination_location(walls_ref
.x
, walls_ref
.y
, walls_ref
.z
,
3140 if (!pcs
->proj_str
) {
3141 pcs
->proj_str
= proj_str
;
3142 if (!proj_str_out
) {
3143 proj_str_out
= osstrdup(proj_str
);
3148 } else if (datum
== img_DATUM_WGS84
&& abs(walls_ref
.zone
) == 61) {
3150 const char *proj_str
=
3151 (walls_ref
.zone
> 0 ? "EPSG:5041" : "EPSG:5042");
3152 set_declination_location(walls_ref
.x
, walls_ref
.y
, walls_ref
.z
,
3154 if (!pcs
->proj_str
) {
3155 pcs
->proj_str
= osstrdup(proj_str
);
3156 if (!proj_str_out
) {
3157 proj_str_out
= osstrdup(proj_str
);
3162 walls_ref
.img_datum_code
= datum
;
3164 case WALLS_WPJ_CMD_STATUS
:
3165 status
= read_uint();
3166 // A quirk is that the root item is flagged with
3167 // WALLS_WPJ_STATUS_DETACHED (seems the flag might be more like
3168 // "don't draw a connecting line left from here").
3169 if ((status
& WALLS_WPJ_STATUS_DETACHED
) && !in_survey
&& depth
> 1) {
3170 //printf("Detached book (status %d = 0x%06x)\n", status, status);
3171 // Detached BOOK - resume at the corresponding ENDBOOK.
3173 pop_walls_options();
3174 detached_nest_level
= 1;
3177 case WALLS_WPJ_CMD_NAME
:
3178 if (!s_empty(&name
)) {
3179 // FIXME: NAME already set
3184 name_lpos
= file
.lpos
;
3185 name_lineno
= file
.line
;
3186 while (!isEol(ch
)) {
3187 s_catchar(&name
, ch
);
3191 case WALLS_WPJ_CMD_NULL
:
3192 compile_diagnostic(DIAG_ERR
|DIAG_TOKEN
|DIAG_SKIP
, /*Unknown command ā%sā*/12, s_str(&token
));
3199 pop_walls_options();
3203 data_file_survex(void)
3205 int begin_lineno_store
= pcs
->begin_lineno
;
3206 pcs
->begin_lineno
= 0;
3209 /* Maybe a UTF-8 "BOM" - skip if so. */
3210 if (nextch() == 0xbb && nextch() == 0xbf) {
3219 #ifdef HAVE_SETJMP_H
3220 /* errors in nested functions can longjmp here */
3221 if (setjmp(file
.jbSkipLine
)) {
3227 while (ch
!= EOF
&& !ferror(file
.fh
)) {
3228 if (!process_non_data_line()) {
3229 f_export_ok
= false;
3230 switch (pcs
->style
) {
3233 case STYLE_CYLPOLAR
:
3236 case STYLE_CARTESIAN
:
3242 case STYLE_NOSURVEY
:
3255 /* don't allow *BEGIN at the end of a file, then *EXPORT in the
3257 f_export_ok
= false;
3259 if (pcs
->begin_lineno
) {
3260 /* TRANSLATORS: %s and %s are replaced with e.g. BEGIN and END
3262 error_in_file(file
.filename
, pcs
->begin_lineno
,
3263 /*%s with no matching %s in this file*/23,
3265 /* Implicitly close any unclosed BEGINs from this file */
3268 } while (pcs
->begin_lineno
);
3271 pcs
->begin_lineno
= begin_lineno_store
;
3274 #define EXT3(C1, C2, C3) (((C3) << 16) | ((C2) << 8) | (C1))
3277 data_file(const char *pth
, const char *fnm
)
3288 /* file specified on command line - don't do special translation */
3289 fh
= fopenWithPthAndExt(pth
, fnm
, EXT_SVX_DATA
, "rb", &filename
);
3291 fh
= fopen_portable(pth
, fnm
, EXT_SVX_DATA
, "rb", &filename
);
3295 compile_error_string(fnm
, /*Couldnāt open file ā%sā*/24, fnm
);
3299 len
= strlen(filename
);
3300 if (len
> 4 && filename
[len
- 4] == FNM_SEP_EXT
) {
3301 /* Read extension and pack into ext. */
3302 for (int i
= 1; i
< 4; ++i
) {
3303 unsigned char ext_ch
= filename
[len
- i
];
3304 ext
= (ext
<< 8) | tolower(ext_ch
);
3309 if (file
.fh
) file
.parent
= &file_store
;
3311 file
.filename
= filename
;
3314 file
.reported_where
= false;
3318 using_data_file(file
.filename
);
3321 case EXT3('d', 'a', 't'):
3322 // Compass survey data.
3323 data_file_compass_dat();
3325 case EXT3('c', 'l', 'p'):
3326 // Compass closed data. The format of .clp is the same as .dat,
3327 // but it contains loop-closed data. This might be useful to
3328 // read if you want to keep existing stations at the same
3329 // adjusted positions, for example to be able to draw extensions
3330 // on an existing drawn-up survey. Or if you managed to lose the
3331 // original .dat but still have the .clp.
3332 data_file_compass_clp();
3334 case EXT3('m', 'a', 'k'):
3335 // Compass project file.
3336 data_file_compass_mak();
3338 case EXT3('s', 'r', 'v'):
3339 // Walls survey data.
3340 data_file_walls_srv();
3342 case EXT3('w', 'p', 'j'):
3343 // Walls project file.
3344 data_file_walls_wpj();
3347 // Native Survex data.
3352 if (ferror(file
.fh
))
3353 fatalerror_in_file(file
.filename
, 0, /*Error reading file*/18);
3355 (void)fclose(file
.fh
);
3359 /* don't free this - it may be pointed to by prefix.file */
3360 /* osfree(file.filename); */
3366 return a
- floor(a
/ (2 * M_PI
)) * (2 * M_PI
);
3370 handle_plumb(clino_type
*p_ctype
)
3373 CLINO_NULL
=-1, CLINO_UP
, CLINO_DOWN
, CLINO_LEVEL
3375 static const sztok clino_tab
[] = {
3377 {"DOWN", CLINO_DOWN
},
3379 {"LEVEL", CLINO_LEVEL
},
3384 static const real clinos
[] = {(real
)M_PI_2
, (real
)(-M_PI_2
), (real
)0.0};
3392 tok
= match_tok(clino_tab
, TABSIZE(clino_tab
));
3393 if (tok
!= CLINO_NULL
) {
3394 *p_ctype
= (tok
== CLINO_LEVEL
? CTYPE_HORIZ
: CTYPE_PLUMB
);
3398 } else if (isSign(ch
)) {
3401 if (toupper(ch
) == 'V') {
3403 *p_ctype
= CTYPE_PLUMB
;
3404 return (!isMinus(chOld
) ? M_PI_2
: -M_PI_2
);
3407 if (isOmit(chOld
)) {
3408 *p_ctype
= CTYPE_OMIT
;
3409 /* no clino reading, so assume 0 with large sd */
3412 } else if (isOmit(ch
)) {
3413 /* OMIT char may not be a SIGN char too so we need to check here as
3414 * well as above... */
3416 *p_ctype
= CTYPE_OMIT
;
3417 /* no clino reading, so assume 0 with large sd */
3424 warn_readings_differ(int msgno
, real diff
, int units
,
3425 reading r_fore
, reading r_back
)
3429 diff
/= get_units_factor(units
);
3430 snprintf(buf
, sizeof(buf
), "%.2f", fabs(diff
));
3431 for (p
= buf
; *p
; ++p
) {
3435 if (*p
!= '0') z
= p
+ 1;
3441 strcpy(p
, get_units_string(units
));
3442 // FIXME: Highlight r_fore too.
3444 compile_diagnostic_reading(DIAG_WARN
, r_back
, msgno
, buf
);
3447 // If one (or both) compass readings are given, return Comp or BackComp
3448 // so we can report plumb legs with compass readings. If neither are,
3451 handle_comp_units(void)
3453 reading which_comp
= End
;
3454 if (VAL(Comp
) != HUGE_REAL
) {
3456 VAL(Comp
) *= pcs
->units
[Q_BEARING
];
3457 if (VAL(Comp
) < (real
)0.0 || VAL(Comp
) - M_PI
* 2.0 > EPSILON
) {
3458 /* TRANSLATORS: Suspicious means something like 410 degrees or -20
3460 compile_diagnostic_reading(DIAG_WARN
, Comp
, /*Suspicious compass reading*/59);
3461 VAL(Comp
) = mod2pi(VAL(Comp
));
3464 if (VAL(BackComp
) != HUGE_REAL
) {
3465 if (which_comp
== End
) which_comp
= BackComp
;
3466 VAL(BackComp
) *= pcs
->units
[Q_BACKBEARING
];
3467 if (VAL(BackComp
) < (real
)0.0 || VAL(BackComp
) - M_PI
* 2.0 > EPSILON
) {
3468 /* FIXME: different message for BackComp? */
3469 compile_diagnostic_reading(DIAG_WARN
, BackComp
, /*Suspicious compass reading*/59);
3470 VAL(BackComp
) = mod2pi(VAL(BackComp
));
3476 static real
compute_convergence(real lon
, real lat
) {
3477 // PROJ < 8.1.0 dereferences the context without a NULL check inside
3478 // proj_create_ellipsoidal_2D_cs() but PJ_DEFAULT_CTX is really just
3479 // NULL so for affected PROJ versions we create a context temporarily to
3480 // avoid a segmentation fault.
3481 PJ_CONTEXT
* ctx
= PJ_DEFAULT_CTX
;
3482 #if PROJ_VERSION_MAJOR < 8 || \
3483 (PROJ_VERSION_MAJOR == 8 && PROJ_VERSION_MINOR < 1)
3484 ctx
= proj_context_create();
3487 if (!proj_str_out
) {
3488 compile_diagnostic(DIAG_ERR
, /*Output coordinate system not set*/488);
3491 PJ
* pj
= proj_create(ctx
, proj_str_out
);
3495 #if PROJ_VERSION_MAJOR < 8 || \
3496 (PROJ_VERSION_MAJOR == 8 && PROJ_VERSION_MINOR < 2)
3497 /* Code adapted from fix in PROJ 8.2.0 to make proj_factors() work in
3498 * cases we need (e.g. a CRS specified as "EPSG:<number>").
3500 switch (proj_get_type(pj
)) {
3501 case PJ_TYPE_PROJECTED_CRS
: {
3502 /* If it is a projected CRS, then compute the factors on the conversion
3503 * associated to it. We need to start from a temporary geographic CRS
3504 * using the same datum as the one of the projected CRS, and with
3505 * input coordinates being in longitude, latitude order in radian,
3506 * to be consistent with the expectations of the lp input parameter.
3509 PJ
* geodetic_crs
= proj_get_source_crs(ctx
, pj
);
3512 PJ
* datum
= proj_crs_get_datum(ctx
, geodetic_crs
);
3513 #if PROJ_VERSION_MAJOR == 8 || \
3514 (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
3515 /* PROJ 7.2.0 upgraded to EPSG 10.x which added the concept
3516 * of a datum ensemble, and this version of PROJ also added
3517 * an API to deal with these.
3519 * If we're using PROJ < 7.2.0 then its EPSG database won't
3520 * have datum ensembles, so we don't need any code to handle
3524 datum
= proj_crs_get_datum_ensemble(ctx
, geodetic_crs
);
3527 PJ
* cs
= proj_create_ellipsoidal_2D_cs(
3528 ctx
, PJ_ELLPS2D_LONGITUDE_LATITUDE
, "Radian", 1.0);
3529 PJ
* temp
= proj_create_geographic_crs_from_datum(
3530 ctx
, "unnamed crs", datum
, cs
);
3531 proj_destroy(datum
);
3533 proj_destroy(geodetic_crs
);
3534 PJ
* newOp
= proj_create_crs_to_crs_from_pj(ctx
, temp
, pj
, NULL
, NULL
);
3546 #if PROJ_VERSION_MAJOR < 9 || \
3547 (PROJ_VERSION_MAJOR == 9 && PROJ_VERSION_MINOR < 3)
3549 /* In PROJ < 9.3.0 proj_factors() returns a grid convergence which is
3550 * off by 90Ā° for a projected coordinate system with northing/easting
3551 * axis order. We can't copy over the fix for this in PROJ 9.3.0's
3552 * proj_factors() since it uses non-public PROJ functions, but
3553 * normalising the output order here works too.
3555 PJ
* pj_norm
= proj_normalize_for_visualization(PJ_DEFAULT_CTX
, pj
);
3560 PJ_FACTORS factors
= proj_factors(pj
, lp
);
3562 #if PROJ_VERSION_MAJOR < 8 || \
3563 (PROJ_VERSION_MAJOR == 8 && PROJ_VERSION_MINOR < 1)
3564 proj_context_destroy(ctx
);
3566 return factors
.meridian_convergence
;
3570 handle_compass(real
*p_var
)
3572 real compvar
= VAR(Comp
);
3573 real comp
= VAL(Comp
);
3574 real backcomp
= VAL(BackComp
);
3576 if (pcs
->z
[Q_DECLINATION
] != HUGE_REAL
) {
3577 declination
= -pcs
->z
[Q_DECLINATION
];
3578 } else if (pcs
->declination
!= HUGE_REAL
) {
3579 /* Cached value calculated for a previous compass reading taken on the
3580 * same date (by the 'else' just below).
3582 declination
= pcs
->declination
;
3584 if (!pcs
->meta
|| pcs
->meta
->days1
== -1) {
3585 compile_diagnostic(DIAG_WARN
, /*No survey date specified - using 0 for magnetic declination*/304);
3588 int avg_days
= (pcs
->meta
->days1
+ pcs
->meta
->days2
) / 2;
3589 double dat
= julian_date_from_days_since_1900(avg_days
);
3590 /* thgeomag() takes (lat, lon, h, dat) - i.e. (y, x, z, date). */
3591 declination
= thgeomag(pcs
->dec_lat
, pcs
->dec_lon
, pcs
->dec_alt
, dat
);
3592 if (declination
< pcs
->min_declination
) {
3593 pcs
->min_declination
= declination
;
3594 pcs
->min_declination_days
= avg_days
;
3596 if (declination
> pcs
->max_declination
) {
3597 pcs
->max_declination
= declination
;
3598 pcs
->max_declination_days
= avg_days
;
3601 if (pcs
->convergence
== HUGE_REAL
) {
3602 /* Compute the convergence lazily. It only depends on the output
3603 * coordinate system so we can cache it for reuse to apply to
3604 * a declination value for a different date.
3606 pcs
->convergence
= compute_convergence(pcs
->dec_lon
, pcs
->dec_lat
);
3608 declination
-= pcs
->convergence
;
3609 /* We cache the calculated declination as the calculation is relatively
3610 * expensive. We also cache an "assumed 0" answer so that we only
3611 * warn once per such survey rather than for every line with a compass
3613 pcs
->declination
= declination
;
3615 if (comp
!= HUGE_REAL
) {
3616 comp
= (comp
- pcs
->z
[Q_BEARING
]) * pcs
->sc
[Q_BEARING
];
3617 comp
+= declination
;
3619 if (backcomp
!= HUGE_REAL
) {
3620 backcomp
= (backcomp
- pcs
->z
[Q_BACKBEARING
])
3621 * pcs
->sc
[Q_BACKBEARING
];
3622 backcomp
+= declination
;
3624 if (comp
!= HUGE_REAL
) {
3625 real diff
= comp
- backcomp
;
3626 real adj
= fabs(diff
) > M_PI
? M_PI
: 0;
3627 diff
-= floor((diff
+ M_PI
) / (2 * M_PI
)) * 2 * M_PI
;
3628 if (sqrd(diff
/ 3.0) > compvar
+ VAR(BackComp
)) {
3629 /* fore and back readings differ by more than 3 sds */
3630 /* TRANSLATORS: %s is replaced by the amount the readings disagree
3631 * by, e.g. "2.5Ā°" or "3įµ". */
3632 warn_readings_differ(/*COMPASS reading and BACKCOMPASS reading disagree by %s*/98,
3633 diff
, get_angle_units(Q_BEARING
), Comp
, BackComp
);
3635 comp
= (comp
/ compvar
+ backcomp
/ VAR(BackComp
));
3636 compvar
= (compvar
+ VAR(BackComp
)) / 4;
3641 compvar
= VAR(BackComp
);
3649 handle_clino(q_quantity q
, reading r
, real val
, bool percent
, clino_type
*p_ctype
)
3653 real diff_from_abs90
;
3654 val
*= pcs
->units
[q
];
3655 /* percentage scale */
3656 if (percent
) val
= atan(val
);
3657 /* We want to warn if there's a reading which it would be impossible
3658 * to have read from the instrument (e.g. on a -90 to 90 degree scale
3659 * you can't read "96" (it's probably a typo for "69"). However, the
3660 * gradient reading from a topofil is typically in the range 0 to 180,
3661 * with 90 being horizontal.
3663 * Really we should allow the valid range to be specified, but for now
3664 * we infer it from the zero error - if this is within 45 degrees of
3665 * 90 then we assume the range is 0 to 180.
3668 range_0_180
= (z
> M_PI_4
&& z
< 3*M_PI_4
);
3669 diff_from_abs90
= fabs(val
) - M_PI_2
;
3670 if (diff_from_abs90
> EPSILON
) {
3672 int clino_units
= get_angle_units(q
);
3673 const char * units
= get_units_string(clino_units
);
3674 real right_angle
= M_PI_2
/ get_units_factor(clino_units
);
3675 /* FIXME: different message for BackClino? */
3676 /* TRANSLATORS: %.f%s will be replaced with a right angle in the
3677 * units currently in use, e.g. "90Ā°" or "100įµ". And "absolute
3678 * value" means the reading ignoring the sign (so it might be
3679 * < -90Ā° or > 90Ā°. */
3680 compile_diagnostic_reading(DIAG_WARN
, r
, /*Clino reading over %.f%s (absolute value)*/51,
3681 right_angle
, units
);
3683 } else if (TSTBIT(pcs
->infer
, INFER_PLUMBS
) &&
3684 diff_from_abs90
>= -EPSILON
) {
3685 *p_ctype
= CTYPE_INFERPLUMB
;
3687 if (range_0_180
&& *p_ctype
!= CTYPE_INFERPLUMB
) {
3688 /* FIXME: Warning message not ideal... */
3689 if (val
< 0.0 || val
- M_PI
> EPSILON
) {
3690 int clino_units
= get_angle_units(q
);
3691 const char * units
= get_units_string(clino_units
);
3692 real right_angle
= M_PI_2
/ get_units_factor(clino_units
);
3693 compile_diagnostic_reading(DIAG_WARN
, r
, /*Clino reading over %.f%s (absolute value)*/51,
3694 right_angle
, units
);
3701 process_normal(prefix
*fr
, prefix
*to
, bool fToFirst
,
3702 clino_type ctype
, clino_type backctype
)
3704 real tape
= VAL(Tape
);
3705 real clin
= VAL(Clino
);
3706 real backclin
= VAL(BackClino
);
3710 #ifndef NO_COVARIANCES
3714 /* adjusted tape is negative -- probably the calibration is wrong */
3715 if (tape
< (real
)0.0) {
3716 /* TRANSLATE different message for topofil? */
3717 compile_diagnostic_reading(DIAG_WARN
, Tape
, /*Negative adjusted tape reading*/79);
3720 reading comp_given
= handle_comp_units();
3722 if (ctype
== CTYPE_READING
) {
3723 clin
= handle_clino(Q_GRADIENT
, Clino
, clin
,
3724 pcs
->f_clino_percent
, &ctype
);
3727 if (backctype
== CTYPE_READING
) {
3728 backclin
= handle_clino(Q_BACKGRADIENT
, BackClino
, backclin
,
3729 pcs
->f_backclino_percent
, &backctype
);
3732 /* un-infer the plumb if the backsight was just a reading */
3733 if (ctype
== CTYPE_INFERPLUMB
&& backctype
== CTYPE_READING
) {
3734 ctype
= CTYPE_READING
;
3737 if (ctype
!= CTYPE_OMIT
&& backctype
!= CTYPE_OMIT
&& ctype
!= backctype
) {
3738 /* TRANSLATORS: In data with backsights, the user has tried to give a
3739 * plumb for the foresight and a clino reading for the backsight, or
3740 * something similar. */
3741 compile_error_reading_skip(Clino
, /*CLINO and BACKCLINO readings must be of the same type*/84);
3745 if (ctype
== CTYPE_PLUMB
|| ctype
== CTYPE_INFERPLUMB
||
3746 backctype
== CTYPE_PLUMB
|| backctype
== CTYPE_INFERPLUMB
) {
3748 if (comp_given
!= End
) {
3749 if (ctype
== CTYPE_PLUMB
||
3750 (ctype
== CTYPE_INFERPLUMB
&& VAL(Comp
) != 0.0) ||
3751 backctype
== CTYPE_PLUMB
||
3752 (backctype
== CTYPE_INFERPLUMB
&&
3753 (VAL(BackComp
) != 0.0 && VAL(BackComp
) != M_PI
))) {
3754 /* TRANSLATORS: A "plumbed leg" is one measured using a plumbline
3755 * (a weight on a string). So the problem here is that the leg is
3756 * vertical, so a compass reading has no meaning! */
3757 compile_diagnostic_reading(DIAG_WARN
, comp_given
, /*Compass reading given on plumbed leg*/21);
3761 dx
= dy
= (real
)0.0;
3762 if (ctype
!= CTYPE_OMIT
) {
3763 if (backctype
!= CTYPE_OMIT
&& (clin
> 0) == (backclin
> 0)) {
3764 /* TRANSLATORS: We've been told the foresight and backsight are
3765 * both "UP", or that they're both "DOWN". */
3766 compile_error_reading_skip(Clino
, /*Plumbed CLINO and BACKCLINO readings can't be in the same direction*/92);
3769 dz
= (clin
> (real
)0.0) ? tape
: -tape
;
3771 dz
= (backclin
< (real
)0.0) ? tape
: -tape
;
3773 vx
= vy
= var(Q_POS
) / 3.0 + dz
* dz
* var(Q_PLUMB
);
3774 vz
= var(Q_POS
) / 3.0 + VAR(Tape
);
3775 #ifndef NO_COVARIANCES
3776 /* Correct values - no covariances in this case! */
3777 cxy
= cyz
= czx
= (real
)0.0;
3780 /* Each of ctype and backctype are either CTYPE_READING/CTYPE_HORIZ
3783 real L2
, cosG
, LcosG
, cosG2
, sinB
, cosB
, dx2
, dy2
, dz2
, v
, V
;
3784 if (comp_given
== End
) {
3785 /* TRANSLATORS: Here "legs" are survey legs, i.e. measurements between
3786 * survey stations. */
3787 compile_error_reading_skip(Comp
, /*Compass reading may not be omitted except on plumbed legs*/14);
3790 if (tape
== (real
)0.0) {
3791 dx
= dy
= dz
= (real
)0.0;
3792 vx
= vy
= vz
= (real
)(var(Q_POS
) / 3.0); /* Position error only */
3793 #ifndef NO_COVARIANCES
3794 cxy
= cyz
= czx
= (real
)0.0;
3797 printf("Zero length leg: vx = %f, vy = %f, vz = %f\n", vx
, vy
, vz
);
3801 /* take into account variance in LEVEL case */
3802 real var_clin
= var(Q_LEVEL
);
3804 real comp
= handle_compass(&var_comp
);
3805 /* ctype != CTYPE_READING is LEVEL case */
3806 if (ctype
== CTYPE_READING
) {
3807 clin
= (clin
- pcs
->z
[Q_GRADIENT
]) * pcs
->sc
[Q_GRADIENT
];
3808 var_clin
= VAR(Clino
);
3810 if (backctype
== CTYPE_READING
) {
3811 backclin
= (backclin
- pcs
->z
[Q_BACKGRADIENT
])
3812 * pcs
->sc
[Q_BACKGRADIENT
];
3813 if (ctype
== CTYPE_READING
) {
3814 if (sqrd((clin
+ backclin
) / 3.0) > var_clin
+ VAR(BackClino
)) {
3815 /* fore and back readings differ by more than 3 sds */
3816 /* TRANSLATORS: %s is replaced by the amount the readings disagree
3817 * by, e.g. "2.5Ā°" or "3įµ". */
3818 warn_readings_differ(/*CLINO reading and BACKCLINO reading disagree by %s*/99,
3819 clin
+ backclin
, get_angle_units(Q_GRADIENT
), Clino
, BackClino
);
3821 clin
= (clin
/ var_clin
- backclin
/ VAR(BackClino
));
3822 var_clin
= (var_clin
+ VAR(BackClino
)) / 4;
3826 var_clin
= VAR(BackClino
);
3831 printf(" %4.2f %4.2f %4.2f\n", tape
, comp
, clin
);
3834 LcosG
= tape
* cosG
;
3838 printf("sinB = %f, cosG = %f, LcosG = %f\n", sinB
, cosG
, LcosG
);
3842 dz
= tape
* sin(clin
);
3843 /* printf("%.2f\n",clin); */
3845 printf("dx = %f\ndy = %f\ndz = %f\n", dx
, dy
, dz
);
3851 cosG2
= cosG
* cosG
;
3852 sinGcosG
= sin(clin
) * cosG
;
3855 #ifdef NO_COVARIANCES
3856 vx
= (var(Q_POS
) / 3.0 + dx2
* V
+ dy2
* var_comp
+
3857 (.5 + sinB
* sinB
* cosG2
) * v
);
3858 vy
= (var(Q_POS
) / 3.0 + dy2
* V
+ dx2
* var_comp
+
3859 (.5 + cosB
* cosB
* cosG2
) * v
);
3860 if (ctype
== CTYPE_OMIT
&& backctype
== CTYPE_OMIT
) {
3861 /* if no clino, assume sd=tape/sqrt(10) so 3sds = .95*tape */
3862 vz
= var(Q_POS
) / 3.0 + L2
* (real
)0.1;
3864 vz
= var(Q_POS
) / 3.0 + dz2
* V
+ L2
* cosG2
* var_clin
;
3866 /* for Surveyor87 errors: vx=vy=vz=var(Q_POS)/3.0; */
3868 vx
= var(Q_POS
) / 3.0 + dx2
* V
+ dy2
* var_comp
+
3870 vy
= var(Q_POS
) / 3.0 + dy2
* V
+ dx2
* var_comp
+
3872 if (ctype
== CTYPE_OMIT
&& backctype
== CTYPE_OMIT
) {
3873 /* if no clino, assume sd=tape/sqrt(10) so 3sds = .95*tape */
3874 vz
= var(Q_POS
) / 3.0 + L2
* (real
)0.1;
3876 vz
= var(Q_POS
) / 3.0 + dz2
* V
+ L2
* cosG2
* var_clin
;
3878 /* usual covariance formulae are fine in no clino case since
3879 * dz = 0 so value of var_clin is ignored */
3880 cxy
= sinB
* cosB
* (VAR(Tape
) * cosG2
+ var_clin
* dz2
)
3881 - var_comp
* dx
* dy
;
3882 czx
= VAR(Tape
) * sinB
* sinGcosG
- var_clin
* dx
* dz
;
3883 cyz
= VAR(Tape
) * cosB
* sinGcosG
- var_clin
* dy
* dz
;
3885 printf("vx = %6.3f, vy = %6.3f, vz = %6.3f\n", vx
, vy
, vz
);
3886 printf("cxy = %6.3f, cyz = %6.3f, czx = %6.3f\n", cxy
, cyz
, czx
);
3890 printf("In DATAIN.C, vx = %f, vy = %f, vz = %f\n", vx
, vy
, vz
);
3895 printf("Just before addleg, vx = %f\n", vx
);
3897 /*printf("dx,dy,dz = %.2f %.2f %.2f\n\n", dx, dy, dz);*/
3898 addlegbyname(fr
, to
, fToFirst
, dx
, dy
, dz
, vx
, vy
, vz
3899 #ifndef NO_COVARIANCES
3907 process_diving(prefix
*fr
, prefix
*to
, bool fToFirst
, bool fDepthChange
)
3909 real tape
= VAL(Tape
);
3913 #ifndef NO_COVARIANCES
3914 real cxy
= 0, cyz
= 0, czx
= 0;
3917 handle_comp_units();
3919 /* depth gauge readings increase upwards with default calibration */
3921 SVX_ASSERT(VAL(FrDepth
) == 0.0);
3922 dz
= VAL(ToDepth
) * pcs
->units
[Q_DEPTH
] - pcs
->z
[Q_DEPTH
];
3923 dz
*= pcs
->sc
[Q_DEPTH
];
3925 dz
= VAL(ToDepth
) - VAL(FrDepth
);
3926 dz
*= pcs
->units
[Q_DEPTH
] * pcs
->sc
[Q_DEPTH
];
3929 /* adjusted tape is negative -- probably the calibration is wrong */
3930 if (tape
< (real
)0.0) {
3931 compile_diagnostic_reading(DIAG_WARN
, Tape
, /*Negative adjusted tape reading*/79);
3934 /* check if tape is less than depth change */
3935 if (tape
< fabs(dz
)) {
3936 /* FIXME: allow margin of error based on variances? */
3937 /* TRANSLATORS: This means that the data fed in said this.
3939 * It could be a gross error (e.g. the decimal point is missing from the
3940 * depth gauge reading) or it could just be due to random error on a near
3942 compile_diagnostic_reading(DIAG_WARN
, Tape
, /*Tape reading is less than change in depth*/62);
3945 if (tape
== (real
)0.0 && dz
== 0.0) {
3946 dx
= dy
= dz
= (real
)0.0;
3947 vx
= vy
= vz
= (real
)(var(Q_POS
) / 3.0); /* Position error only */
3948 } else if (VAL(Comp
) == HUGE_REAL
&&
3949 VAL(BackComp
) == HUGE_REAL
) {
3951 dx
= dy
= (real
)0.0;
3952 if (dz
< 0) tape
= -tape
;
3953 /* FIXME: Should use FrDepth sometimes... */
3954 dz
= (dz
* VAR(Tape
) + tape
* 2 * VAR(ToDepth
))
3955 / (VAR(Tape
) * 2 * VAR(ToDepth
));
3956 vx
= vy
= var(Q_POS
) / 3.0 + dz
* dz
* var(Q_PLUMB
);
3957 /* FIXME: Should use FrDepth sometimes... */
3958 vz
= var(Q_POS
) / 3.0 + VAR(Tape
) * 2 * VAR(ToDepth
)
3959 / (VAR(Tape
) + VAR(ToDepth
));
3961 real L2
, sinB
, cosB
, dz2
, D2
;
3963 real comp
= handle_compass(&var_comp
);
3969 if (D2
<= (real
)0.0) {
3970 /* FIXME: Should use FrDepth sometimes... */
3971 real vsum
= VAR(Tape
) + 2 * VAR(ToDepth
);
3972 dx
= dy
= (real
)0.0;
3973 vx
= vy
= var(Q_POS
) / 3.0;
3974 /* FIXME: Should use FrDepth sometimes... */
3975 vz
= var(Q_POS
) / 3.0 + VAR(Tape
) * 2 * VAR(ToDepth
) / vsum
;
3977 /* FIXME: Should use FrDepth sometimes... */
3978 dz
= (dz
* VAR(Tape
) + tape
* 2 * VAR(ToDepth
)) / vsum
;
3980 dz
= (dz
* VAR(Tape
) - tape
* 2 * VAR(ToDepth
)) / vsum
;
3984 /* FIXME: Should use FrDepth sometimes... */
3985 real F
= VAR(Tape
) * L2
+ 2 * VAR(ToDepth
) * D2
;
3989 vx
= var(Q_POS
) / 3.0 +
3990 sinB
* sinB
* F
/ D2
+ var_comp
* dy
* dy
;
3991 vy
= var(Q_POS
) / 3.0 +
3992 cosB
* cosB
* F
/ D2
+ var_comp
* dx
* dx
;
3993 /* FIXME: Should use FrDepth sometimes... */
3994 vz
= var(Q_POS
) / 3.0 + 2 * VAR(ToDepth
);
3996 #ifndef NO_COVARIANCES
3997 cxy
= sinB
* cosB
* (F
/ D2
+ var_comp
* D2
);
3998 /* FIXME: Should use FrDepth sometimes... */
3999 cyz
= -2 * VAR(ToDepth
) * dy
/ D
;
4000 czx
= -2 * VAR(ToDepth
) * dx
/ D
;
4003 /* FIXME: If there's a clino reading, check it against the depth reading,
4005 * if (VAL(Clino) != HUGE_REAL || VAL(BackClino) != HUGE_REAL) { ... }
4008 addlegbyname(fr
, to
, fToFirst
, dx
, dy
, dz
, vx
, vy
, vz
4009 #ifndef NO_COVARIANCES
4017 process_cartesian(prefix
*fr
, prefix
*to
, bool fToFirst
)
4019 real dx
= (VAL(Dx
) * pcs
->units
[Q_DX
] - pcs
->z
[Q_DX
]) * pcs
->sc
[Q_DX
];
4020 real dy
= (VAL(Dy
) * pcs
->units
[Q_DY
] - pcs
->z
[Q_DY
]) * pcs
->sc
[Q_DY
];
4021 real dz
= (VAL(Dz
) * pcs
->units
[Q_DZ
] - pcs
->z
[Q_DZ
]) * pcs
->sc
[Q_DZ
];
4023 addlegbyname(fr
, to
, fToFirst
, dx
, dy
, dz
, VAR(Dx
), VAR(Dy
), VAR(Dz
)
4024 #ifndef NO_COVARIANCES
4032 data_cartesian(void)
4034 prefix
*fr
= NULL
, *to
= NULL
;
4036 bool fMulti
= false;
4038 reading first_stn
= End
;
4040 const reading
*ordering
;
4044 for (ordering
= pcs
->ordering
; ; ordering
++) {
4046 switch (*ordering
) {
4048 fr
= read_prefix(PFX_STATION
|PFX_ALLOW_ROOT
);
4049 if (first_stn
== End
) first_stn
= Fr
;
4052 to
= read_prefix(PFX_STATION
|PFX_ALLOW_ROOT
);
4053 if (first_stn
== End
) first_stn
= To
;
4057 to
= read_prefix(PFX_STATION
);
4060 case Dx
: case Dy
: case Dz
:
4061 read_reading(*ordering
, false);
4064 // Walls SRV is always From then To.
4066 fr
= read_walls_station(p_walls_options
->prefix
, true);
4068 if (ch
== '*' || ch
== '<') {
4069 // Isolated LRUD. Ignore for now.
4076 to
= read_walls_station(p_walls_options
->prefix
, true);
4078 if (ch
== '*' || ch
== '<') {
4079 // Odd apparently undocumented variant of isolated LRUD. Ignore
4088 case IgnoreAllAndNewLine
:
4093 if (!process_cartesian(fr
, to
, first_stn
== To
))
4100 if (isData(ch
)) break;
4111 process_cartesian(fr
, to
, first_stn
== To
);
4118 } while (isComm(ch
));
4120 default: BUG("Unknown reading in ordering");
4126 process_cylpolar(prefix
*fr
, prefix
*to
, bool fToFirst
, bool fDepthChange
)
4128 real tape
= VAL(Tape
);
4132 #ifndef NO_COVARIANCES
4136 handle_comp_units();
4138 /* depth gauge readings increase upwards with default calibration */
4140 SVX_ASSERT(VAL(FrDepth
) == 0.0);
4141 dz
= VAL(ToDepth
) * pcs
->units
[Q_DEPTH
] - pcs
->z
[Q_DEPTH
];
4142 dz
*= pcs
->sc
[Q_DEPTH
];
4144 dz
= VAL(ToDepth
) - VAL(FrDepth
);
4145 dz
*= pcs
->units
[Q_DEPTH
] * pcs
->sc
[Q_DEPTH
];
4148 /* adjusted tape is negative -- probably the calibration is wrong */
4149 if (tape
< (real
)0.0) {
4150 compile_diagnostic_reading(DIAG_WARN
, Tape
, /*Negative adjusted tape reading*/79);
4153 if (VAL(Comp
) == HUGE_REAL
&& VAL(BackComp
) == HUGE_REAL
) {
4155 dx
= dy
= (real
)0.0;
4156 vx
= vy
= var(Q_POS
) / 3.0 + dz
* dz
* var(Q_PLUMB
);
4157 /* FIXME: Should use FrDepth sometimes... */
4158 vz
= var(Q_POS
) / 3.0 + 2 * VAR(ToDepth
);
4162 real comp
= handle_compass(&var_comp
);
4169 vx
= var(Q_POS
) / 3.0 +
4170 VAR(Tape
) * sinB
* sinB
+ var_comp
* dy
* dy
;
4171 vy
= var(Q_POS
) / 3.0 +
4172 VAR(Tape
) * cosB
* cosB
+ var_comp
* dx
* dx
;
4173 /* FIXME: Should use FrDepth sometimes... */
4174 vz
= var(Q_POS
) / 3.0 + 2 * VAR(ToDepth
);
4176 #ifndef NO_COVARIANCES
4177 cxy
= (VAR(Tape
) - var_comp
* tape
* tape
) * sinB
* cosB
;
4180 addlegbyname(fr
, to
, fToFirst
, dx
, dy
, dz
, vx
, vy
, vz
4181 #ifndef NO_COVARIANCES
4188 /* Process tape/compass/clino, diving, and cylpolar styles of survey data
4189 * Also handles topofil (fromcount/tocount or count) in place of tape */
4193 prefix
*fr
= NULL
, *to
= NULL
;
4194 reading first_stn
= End
;
4196 bool fTopofil
= false, fMulti
= false;
4198 clino_type ctype
, backctype
;
4200 unsigned long compass_dat_flags
= 0;
4202 const reading
*ordering
;
4204 VAL(Tape
) = VAL(BackTape
) = HUGE_REAL
;
4205 VAL(Comp
) = VAL(BackComp
) = HUGE_REAL
;
4206 VAL(FrCount
) = VAL(ToCount
) = 0;
4207 VAL(FrDepth
) = VAL(ToDepth
) = 0;
4208 VAL(Left
) = VAL(Right
) = VAL(Up
) = VAL(Down
) = HUGE_REAL
;
4211 ctype
= backctype
= CTYPE_OMIT
;
4212 fDepthChange
= false;
4214 /* ordering may omit clino reading, so set up default here */
4215 /* this is also used if clino reading is the omit character */
4216 VAL(Clino
) = VAL(BackClino
) = 0;
4220 /* We clear these flags in the normal course of events, but if there's an
4221 * error in a reading, we might not, so make sure it has been cleared here.
4223 pcs
->flags
&= ~(BIT(FLAGS_ANON_ONE_END
) | BIT(FLAGS_IMPLICIT_SPLAY
));
4224 for (ordering
= pcs
->ordering
; ; ordering
++) {
4226 switch (*ordering
) {
4228 fr
= read_prefix(PFX_STATION
|PFX_ALLOW_ROOT
|PFX_ANON
);
4229 if (first_stn
== End
) first_stn
= Fr
;
4232 to
= read_prefix(PFX_STATION
|PFX_ALLOW_ROOT
|PFX_ANON
);
4233 if (first_stn
== End
) first_stn
= To
;
4236 // Compass DAT is always From then To.
4238 fr
= read_prefix(PFX_STATION
);
4239 scan_compass_station_name(fr
);
4242 to
= read_prefix(PFX_STATION
);
4243 scan_compass_station_name(to
);
4247 to
= read_prefix(PFX_STATION
);
4252 DIR_NULL
=-1, DIR_FORE
, DIR_BACK
4254 static const sztok dir_tab
[] = {
4260 tok
= match_tok(dir_tab
, TABSIZE(dir_tab
));
4268 compile_diagnostic(DIAG_ERR
|DIAG_TOKEN
|DIAG_SKIP
, /*Found ā%sā, expecting āFā or āBā*/131, s_str(&token
));
4274 case Tape
: case BackTape
: {
4275 reading r
= *ordering
;
4276 read_reading(r
, true);
4277 if (VAL(r
) == HUGE_REAL
) {
4279 compile_diagnostic_token_show(DIAG_ERR
, /*Expecting numeric field, found ā%sā*/9);
4280 /* Avoid also warning about omitted tape reading. */
4285 } else if (VAL(r
) < (real
)0.0) {
4286 compile_diagnostic_reading(DIAG_WARN
, r
, /*Negative tape reading*/60);
4291 VAL(FrCount
) = VAL(ToCount
);
4292 LOC(FrCount
) = LOC(ToCount
);
4293 WID(FrCount
) = WID(ToCount
);
4294 read_reading(ToCount
, false);
4298 read_reading(FrCount
, false);
4301 read_reading(ToCount
, false);
4304 case Comp
: case BackComp
:
4305 read_bearing_or_omit(*ordering
);
4307 case Clino
: case BackClino
: {
4308 reading r
= *ordering
;
4309 clino_type
* p_ctype
= (r
== Clino
? &ctype
: &backctype
);
4310 read_reading(r
, true);
4311 if (VAL(r
) == HUGE_REAL
) {
4312 VAL(r
) = handle_plumb(p_ctype
);
4313 if (VAL(r
) != HUGE_REAL
) {
4314 WID(r
) = ftell(file
.fh
) - LOC(r
);
4317 compile_diagnostic_token_show(DIAG_ERR
, /*Expecting numeric field, found ā%sā*/9);
4322 *p_ctype
= CTYPE_READING
;
4325 case FrDepth
: case ToDepth
:
4326 read_reading(*ordering
, false);
4329 VAL(FrDepth
) = VAL(ToDepth
);
4330 LOC(FrDepth
) = LOC(ToDepth
);
4331 WID(FrDepth
) = WID(ToDepth
);
4332 read_reading(ToDepth
, false);
4335 fDepthChange
= true;
4337 read_reading(ToDepth
, false);
4339 case CompassDATComp
:
4340 read_bearing_or_omit(Comp
);
4341 if (is_compass_NaN(VAL(Comp
))) VAL(Comp
) = HUGE_REAL
;
4343 case CompassDATBackComp
:
4344 read_bearing_or_omit(BackComp
);
4345 if (is_compass_NaN(VAL(BackComp
))) VAL(BackComp
) = HUGE_REAL
;
4347 case CompassDATClino
: case CompassDATBackClino
: {
4349 clino_type
* p_ctype
;
4350 if (*ordering
== CompassDATClino
) {
4355 p_ctype
= &backctype
;
4357 read_reading(r
, false);
4358 if (is_compass_NaN(VAL(r
))) {
4360 *p_ctype
= CTYPE_OMIT
;
4362 *p_ctype
= CTYPE_READING
;
4366 case CompassDATLeft
: case CompassDATRight
:
4367 case CompassDATUp
: case CompassDATDown
: {
4368 /* FIXME: need to actually make use of these entries! */
4369 reading actual
= Left
+ (*ordering
- CompassDATLeft
);
4370 read_reading(actual
, false);
4371 if (VAL(actual
) < 0) VAL(actual
) = HUGE_REAL
;
4374 case CompassDATFlags
:
4381 while (ch
>= 'A' && ch
<= 'Z') {
4382 compass_dat_flags
|= BIT(ch
- 'A');
4384 * L (exclude from length)
4386 * P (no plot) (mapped to FLAG_SURFACE)
4388 * FIXME: Defined flags we currently ignore:
4389 * C (no adjustment) (set all (co)variances to 0? Then
4390 * we need to handle a loop of such legs or a traverse
4391 * of such legs between two fixed points...)
4398 compass_dat_flags
= 0;
4407 // Walls SRV is always From then To.
4409 fr
= read_walls_station(p_walls_options
->prefix
, true);
4411 if (ch
== '*' || ch
== '<') {
4412 // Isolated LRUD. Ignore for now.
4419 to
= read_walls_station(p_walls_options
->prefix
, true);
4421 if (ch
== '*' || ch
== '<') {
4422 // Odd apparently undocumented variant of isolated LRUD. Ignore
4430 LOC(Tape
) = ftell(file
.fh
);
4431 VAL(Tape
) = read_numeric(true);
4432 if (VAL(Tape
) == HUGE_REAL
) {
4433 if (ch
== 'i' || ch
== 'I') {
4434 // Length specified in inches only, e.g. `i6` is 6 inches.
4438 // Walls expects 2 or more - for an omitted value.
4439 if (ch
!= '-' || nextch() != '-') {
4440 compile_diagnostic_token_show(DIAG_ERR
, /*Expecting numeric field, found ā%sā*/9);
4441 /* Avoid also warning about omitted tape reading. */
4444 while (nextch() == '-') { }
4447 if (VAL(Tape
) < (real
)0.0)
4448 compile_diagnostic_reading(DIAG_WARN
, Tape
, /*Negative tape reading*/60);
4454 real inches
= read_numeric(false);
4455 VAL(Tape
) += inches
/ 12.0;
4459 VAL(Tape
) *= METRES_PER_FOOT
;
4462 VAL(Tape
) /= pcs
->units
[Q_LENGTH
];
4466 WID(Tape
) = ftell(file
.fh
) - LOC(Tape
);
4467 VAR(Tape
) = var(Q_LENGTH
);
4469 case WallsSRVComp
: {
4471 LOC(Comp
) = ftell(file
.fh
);
4474 VAL(Comp
) = read_quadrant(false);
4476 VAL(Comp
) = read_number(true, false);
4477 if (VAL(Comp
) == HUGE_REAL
) {
4479 compile_diagnostic_token_show(DIAG_ERR
, /*Expecting numeric field, found ā%sā*/9);
4484 // Walls documents two or more `-` for an omitted
4485 // reading, but actually just one works too!
4486 while (nextch() == '-') { }
4491 VAL(Comp
) *= M_PI
/ 180.0 / pcs
->units
[Q_BEARING
];
4496 VAL(Comp
) *= M_PI
/ 200.0 / pcs
->units
[Q_BEARING
];
4501 VAL(Comp
) *= M_PI
/ 3200.0 / pcs
->units
[Q_BEARING
];
4507 WID(Comp
) = ftell(file
.fh
) - LOC(Comp
);
4508 VAR(Comp
) = var(Q_BEARING
);
4510 // Omitted foresight, e.g. `/123` or `/` (both omitted).
4512 VAL(Comp
) = HUGE_REAL
;
4514 if (ch
== '/' && !isBlank(nextch())) {
4515 LOC(BackComp
) = ftell(file
.fh
);
4517 VAL(BackComp
) = read_quadrant(false);
4519 VAL(BackComp
) = read_number(true, false);
4520 if (VAL(BackComp
) == HUGE_REAL
) {
4522 compile_diagnostic_token_show(DIAG_ERR
, /*Expecting numeric field, found ā%sā*/9);
4527 // Walls documents two or more `-` for an omitted
4528 // reading, but actually just one works too!
4529 while (nextch() == '-') { }
4534 VAL(BackComp
) *= M_PI
/ 180.0 / pcs
->units
[Q_BACKBEARING
];
4539 VAL(BackComp
) *= M_PI
/ 200.0 / pcs
->units
[Q_BACKBEARING
];
4544 VAL(BackComp
) *= M_PI
/ 3200.0 / pcs
->units
[Q_BACKBEARING
];
4550 WID(BackComp
) = ftell(file
.fh
) - LOC(BackComp
);
4551 VAR(BackComp
) = var(Q_BACKBEARING
);
4553 // Omitted backsight, e.g. `123/` or `/` (both omitted).
4554 LOC(BackComp
) = ftell(file
.fh
);
4556 VAL(BackComp
) = HUGE_REAL
;
4560 case WallsSRVClino
: {
4562 LOC(Clino
) = ftell(file
.fh
);
4564 real clin
= read_number(true, false);
4565 if (clin
== HUGE_REAL
) {
4567 // Undocumented, but the clino can be omitted with order=dav or order=adv.
4568 if (p_walls_options
->data_order_ct
[4] != WallsSRVClino
) {
4569 compile_diagnostic_token_show(DIAG_ERR
, /*Expecting numeric field, found ā%sā*/9);
4575 // Walls documents two or more `-` for an omitted
4576 // reading, but actually just one works too!
4577 while (nextch() == '-') { }
4583 clin
*= M_PI
/ 180.0 / pcs
->units
[Q_GRADIENT
];
4588 clin
*= M_PI
/ 200.0 / pcs
->units
[Q_GRADIENT
];
4593 clin
*= M_PI
/ 3200.0 / pcs
->units
[Q_GRADIENT
];
4598 ctype
= CTYPE_READING
;
4600 WID(Clino
) = ftell(file
.fh
) - LOC(Clino
);
4601 VAR(Clino
) = var(Q_GRADIENT
);
4603 // Omitted foresight, e.g. `/12` or `/` (both omitted).
4606 if (ch
== '/' && !isBlank(nextch())) {
4607 LOC(BackClino
) = ftell(file
.fh
);
4608 real backclin
= read_number(true, false);
4609 if (backclin
== HUGE_REAL
) {
4611 compile_diagnostic_token_show(DIAG_ERR
, /*Expecting numeric field, found ā%sā*/9);
4616 // Walls documents two or more `-` for an omitted
4617 // reading, but actually just one works too!
4618 while (nextch() == '-') { }
4623 backclin
*= M_PI
/ 180.0 / pcs
->units
[Q_BACKGRADIENT
];
4628 backclin
*= M_PI
/ 200.0 / pcs
->units
[Q_BACKGRADIENT
];
4633 backclin
*= M_PI
/ 3200.0 / pcs
->units
[Q_BACKGRADIENT
];
4637 VAL(BackClino
) = backclin
;
4638 backctype
= CTYPE_READING
;
4640 WID(BackClino
) = ftell(file
.fh
) - LOC(BackClino
);
4641 VAR(BackClino
) = var(Q_BACKGRADIENT
);
4643 // Omitted backsight, e.g. `12/` or `/` (both omitted).
4644 LOC(BackClino
) = ftell(file
.fh
);
4651 case IgnoreAllAndNewLine
:
4660 VAL(Tape
) = VAL(ToCount
) - VAL(FrCount
);
4661 LOC(Tape
) = LOC(ToCount
);
4662 WID(Tape
) = WID(ToCount
);
4664 /* Note: frdepth == todepth test works regardless of fDepthChange
4665 * (frdepth always zero, todepth is change of depth) and also
4666 * works for STYLE_NORMAL (both remain 0) */
4667 if (TSTBIT(pcs
->infer
, INFER_EQUATES
) &&
4668 (VAL(Tape
) == (real
)0.0 || VAL(Tape
) == HUGE_REAL
) &&
4669 (VAL(BackTape
) == (real
)0.0 || VAL(BackTape
) == HUGE_REAL
) &&
4670 VAL(FrDepth
) == VAL(ToDepth
)) {
4671 if (!TSTBIT(pcs
->infer
, INFER_EQUATES_SELF_OK
) || fr
!= to
)
4672 process_equate(fr
, to
);
4673 goto inferred_equate
;
4681 VAL(Tape
) *= pcs
->units
[Q_COUNT
] * pcs
->sc
[Q_COUNT
];
4682 } else if (VAL(Tape
) != HUGE_REAL
) {
4683 VAL(Tape
) *= pcs
->units
[Q_LENGTH
];
4684 VAL(Tape
) -= pcs
->z
[Q_LENGTH
];
4685 VAL(Tape
) *= pcs
->sc
[Q_LENGTH
];
4687 if (VAL(BackTape
) != HUGE_REAL
) {
4688 VAL(BackTape
) *= pcs
->units
[Q_BACKLENGTH
];
4689 VAL(BackTape
) -= pcs
->z
[Q_BACKLENGTH
];
4690 VAL(BackTape
) *= pcs
->sc
[Q_BACKLENGTH
];
4691 if (VAL(Tape
) != HUGE_REAL
) {
4692 real diff
= VAL(Tape
) - VAL(BackTape
);
4693 if (sqrd(diff
/ 3.0) > VAR(Tape
) + VAR(BackTape
)) {
4694 /* fore and back readings differ by more than 3 sds */
4695 /* TRANSLATORS: %s is replaced by the amount the readings disagree
4696 * by, e.g. "0.12m" or "0.2ft". */
4697 warn_readings_differ(/*TAPE reading and BACKTAPE reading disagree by %s*/97,
4698 diff
, get_length_units(Q_LENGTH
), Tape
, BackTape
);
4700 VAL(Tape
) = VAL(Tape
) / VAR(Tape
) + VAL(BackTape
) / VAR(BackTape
);
4701 VAR(Tape
) = (VAR(Tape
) + VAR(BackTape
)) / 4;
4702 VAL(Tape
) *= VAR(Tape
);
4704 VAL(Tape
) = VAL(BackTape
);
4705 VAR(Tape
) = VAR(BackTape
);
4707 } else if (VAL(Tape
) == HUGE_REAL
) {
4708 compile_diagnostic_reading(DIAG_ERR
, Tape
, /*Tape reading may not be omitted*/94);
4709 goto inferred_equate
;
4711 implicit_splay
= TSTBIT(pcs
->flags
, FLAGS_IMPLICIT_SPLAY
);
4712 pcs
->flags
&= ~(BIT(FLAGS_ANON_ONE_END
) | BIT(FLAGS_IMPLICIT_SPLAY
));
4713 save_flags
= pcs
->flags
;
4714 if (implicit_splay
) {
4715 pcs
->flags
|= BIT(FLAGS_SPLAY
);
4717 switch (pcs
->style
) {
4719 r
= process_normal(fr
, to
, (first_stn
== To
) ^ fRev
,
4723 /* FIXME: Handle any clino readings */
4724 r
= process_diving(fr
, to
, (first_stn
== To
) ^ fRev
,
4727 case STYLE_CYLPOLAR
:
4728 r
= process_cylpolar(fr
, to
, (first_stn
== To
) ^ fRev
,
4732 r
= 0; /* avoid warning */
4735 pcs
->flags
= save_flags
;
4738 /* Swap fr and to back to how they were for next line */
4747 ctype
= backctype
= CTYPE_OMIT
;
4748 fDepthChange
= false;
4750 /* ordering may omit clino reading, so set up default here */
4751 /* this is also used if clino reading is the omit character */
4752 VAL(Clino
) = VAL(BackClino
) = 0;
4753 LOC(Clino
) = LOC(BackClino
) = -1;
4754 WID(Clino
) = WID(BackClino
) = 0;
4762 if (isData(ch
)) break;
4775 /* Compass ignore flag is 'X' */
4776 if ((compass_dat_flags
& BIT('X' - 'A'))) {
4786 VAL(Tape
) = VAL(ToCount
) - VAL(FrCount
);
4787 LOC(Tape
) = LOC(ToCount
);
4788 WID(Tape
) = WID(ToCount
);
4790 /* Note: frdepth == todepth test works regardless of fDepthChange
4791 * (frdepth always zero, todepth is change of depth) and also
4792 * works for STYLE_NORMAL (both remain 0) */
4793 if (TSTBIT(pcs
->infer
, INFER_EQUATES
) &&
4794 (VAL(Tape
) == (real
)0.0 || VAL(Tape
) == HUGE_REAL
) &&
4795 (VAL(BackTape
) == (real
)0.0 || VAL(BackTape
) == HUGE_REAL
) &&
4796 VAL(FrDepth
) == VAL(ToDepth
)) {
4797 if (!TSTBIT(pcs
->infer
, INFER_EQUATES_SELF_OK
) || fr
!= to
)
4798 process_equate(fr
, to
);
4803 VAL(Tape
) *= pcs
->units
[Q_COUNT
] * pcs
->sc
[Q_COUNT
];
4804 } else if (VAL(Tape
) != HUGE_REAL
) {
4805 VAL(Tape
) *= pcs
->units
[Q_LENGTH
];
4806 VAL(Tape
) -= pcs
->z
[Q_LENGTH
];
4807 VAL(Tape
) *= pcs
->sc
[Q_LENGTH
];
4809 if (VAL(BackTape
) != HUGE_REAL
) {
4810 VAL(BackTape
) *= pcs
->units
[Q_BACKLENGTH
];
4811 VAL(BackTape
) -= pcs
->z
[Q_BACKLENGTH
];
4812 VAL(BackTape
) *= pcs
->sc
[Q_BACKLENGTH
];
4813 if (VAL(Tape
) != HUGE_REAL
) {
4814 real diff
= VAL(Tape
) - VAL(BackTape
);
4815 if (sqrd(diff
/ 3.0) > VAR(Tape
) + VAR(BackTape
)) {
4816 /* fore and back readings differ by more than 3 sds */
4817 /* TRANSLATORS: %s is replaced by the amount the readings disagree
4818 * by, e.g. "0.12m" or "0.2ft". */
4819 warn_readings_differ(/*TAPE reading and BACKTAPE reading disagree by %s*/97,
4820 diff
, get_length_units(Q_LENGTH
), Tape
, BackTape
);
4822 VAL(Tape
) = VAL(Tape
) / VAR(Tape
) + VAL(BackTape
) / VAR(BackTape
);
4823 VAR(Tape
) = (VAR(Tape
) + VAR(BackTape
)) / 4;
4824 VAL(Tape
) *= VAR(Tape
);
4826 VAL(Tape
) = VAL(BackTape
);
4827 VAR(Tape
) = VAR(BackTape
);
4829 } else if (VAL(Tape
) == HUGE_REAL
) {
4830 compile_diagnostic_reading(DIAG_ERR
, Tape
, /*Tape reading may not be omitted*/94);
4834 implicit_splay
= TSTBIT(pcs
->flags
, FLAGS_IMPLICIT_SPLAY
);
4835 pcs
->flags
&= ~(BIT(FLAGS_ANON_ONE_END
) | BIT(FLAGS_IMPLICIT_SPLAY
));
4836 save_flags
= pcs
->flags
;
4837 if (implicit_splay
) {
4838 pcs
->flags
|= BIT(FLAGS_SPLAY
);
4840 if ((compass_dat_flags
& BIT('S' - 'A'))) {
4841 /* 'S' means "splay". */
4842 pcs
->flags
|= BIT(FLAGS_SPLAY
);
4844 if ((compass_dat_flags
& BIT('P' - 'A'))) {
4845 /* 'P' means "Exclude this shot from plotting", but the use
4846 * suggested in the Compass docs is for surface data, and legs
4847 * with this flag "[do] not support passage modeling".
4849 * Even if it's actually being used for a different
4850 * purpose, Survex programs don't show surface legs
4851 * by default so FLAGS_SURFACE matches fairly well.
4853 pcs
->flags
|= BIT(FLAGS_SURFACE
);
4855 if ((compass_dat_flags
& BIT('L' - 'A'))) {
4856 /* 'L' means "exclude from length" - map this to Survex's
4857 * FLAGS_DUPLICATE. */
4858 pcs
->flags
|= BIT(FLAGS_DUPLICATE
);
4860 switch (pcs
->style
) {
4862 process_normal(fr
, to
, (first_stn
== To
) ^ fRev
,
4866 /* FIXME: Handle any clino readings */
4867 process_diving(fr
, to
, (first_stn
== To
) ^ fRev
,
4870 case STYLE_CYLPOLAR
:
4871 process_cylpolar(fr
, to
, (first_stn
== To
) ^ fRev
,
4877 pcs
->flags
= save_flags
;
4885 } while (isComm(ch
));
4888 BUG("Unknown reading in ordering");
4894 process_lrud(prefix
*stn
)
4896 SVX_ASSERT(next_lrud
);
4897 lrud
* xsect
= osnew(lrud
);
4899 xsect
->l
= (VAL(Left
) * pcs
->units
[Q_LEFT
] - pcs
->z
[Q_LEFT
]) * pcs
->sc
[Q_LEFT
];
4900 xsect
->r
= (VAL(Right
) * pcs
->units
[Q_RIGHT
] - pcs
->z
[Q_RIGHT
]) * pcs
->sc
[Q_RIGHT
];
4901 xsect
->u
= (VAL(Up
) * pcs
->units
[Q_UP
] - pcs
->z
[Q_UP
]) * pcs
->sc
[Q_UP
];
4902 xsect
->d
= (VAL(Down
) * pcs
->units
[Q_DOWN
] - pcs
->z
[Q_DOWN
]) * pcs
->sc
[Q_DOWN
];
4903 xsect
->meta
= pcs
->meta
;
4904 if (pcs
->meta
) ++pcs
->meta
->ref_count
;
4907 next_lrud
= &(xsect
->next
);
4916 const reading
*ordering
;
4918 for (ordering
= pcs
->ordering
; ; ordering
++) {
4920 switch (*ordering
) {
4922 stn
= read_prefix(PFX_STATION
);
4924 case Left
: case Right
: case Up
: case Down
: {
4925 reading r
= *ordering
;
4926 read_reading(r
, true);
4927 if (VAL(r
) == HUGE_REAL
) {
4929 compile_diagnostic_token_show(DIAG_ERR
, /*Expecting numeric field, found ā%sā*/9);
4947 default: BUG("Unknown reading in ordering");
4953 process_nosurvey(prefix
*fr
, prefix
*to
, bool fToFirst
)
4957 /* Suppress "unused fixed point" warnings for these stations */
4958 fr
->sflags
|= BIT(SFLAGS_USED
);
4959 to
->sflags
|= BIT(SFLAGS_USED
);
4961 /* add to linked list which is dealt with after network is solved */
4962 link
= osnew(nosurveylink
);
4964 link
->to
= StnFromPfx(to
);
4965 link
->fr
= StnFromPfx(fr
);
4967 link
->fr
= StnFromPfx(fr
);
4968 link
->to
= StnFromPfx(to
);
4970 link
->flags
= pcs
->flags
| (STYLE_NOSURVEY
<< FLAGS_STYLE_BIT0
);
4971 link
->meta
= pcs
->meta
;
4972 if (pcs
->meta
) ++pcs
->meta
->ref_count
;
4973 link
->next
= nosurveyhead
;
4974 nosurveyhead
= link
;
4981 prefix
*fr
= NULL
, *to
= NULL
;
4983 bool fMulti
= false;
4985 reading first_stn
= End
;
4987 const reading
*ordering
;
4991 for (ordering
= pcs
->ordering
; ; ordering
++) {
4993 switch (*ordering
) {
4995 fr
= read_prefix(PFX_STATION
|PFX_ALLOW_ROOT
);
4996 if (first_stn
== End
) first_stn
= Fr
;
4999 to
= read_prefix(PFX_STATION
|PFX_ALLOW_ROOT
);
5000 if (first_stn
== End
) first_stn
= To
;
5004 to
= read_prefix(PFX_STATION
);
5009 case IgnoreAllAndNewLine
:
5014 if (!process_nosurvey(fr
, to
, first_stn
== To
))
5017 if (ordering
[1] == End
) {
5021 } while (isComm(ch
));
5031 if (isData(ch
)) break;
5042 (void)process_nosurvey(fr
, to
, first_stn
== To
);
5049 } while (isComm(ch
));
5051 default: BUG("Unknown reading in ordering");
5056 /* totally ignore a line of survey data */