Invalid display lists on hidpi change
[survex.git] / src / commands.c
blob9fc25f7df4d4d4eea78239b3f4d7afed0ab73442
1 /* commands.c
2 * Code for directives
3 * Copyright (C) 1991-2022 Olly Betts
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
24 #include <assert.h>
25 #include <limits.h>
26 #include <stddef.h> /* for offsetof */
27 #include <string.h>
29 #include <proj.h>
30 #if PROJ_VERSION_MAJOR < 8 || \
31 (PROJ_VERSION_MAJOR == 8 && PROJ_VERSION_MINOR < 2)
32 /* Needed for proj_factors workaround */
33 # include <proj_experimental.h>
34 #endif
36 #include "cavern.h"
37 #include "commands.h"
38 #include "datain.h"
39 #include "date.h"
40 #include "debug.h"
41 #include "filename.h"
42 #include "message.h"
43 #include "netbits.h"
44 #include "netskel.h"
45 #include "out.h"
46 #include "readval.h"
47 #include "str.h"
49 #define WGS84_DATUM_STRING "EPSG:4326"
51 static void
52 default_grade(settings *s)
54 /* Values correspond to those in bcra5.svx */
55 s->Var[Q_POS] = (real)sqrd(0.05);
56 s->Var[Q_LENGTH] = (real)sqrd(0.05);
57 s->Var[Q_BACKLENGTH] = (real)sqrd(0.05);
58 s->Var[Q_COUNT] = (real)sqrd(0.05);
59 s->Var[Q_DX] = s->Var[Q_DY] = s->Var[Q_DZ] = (real)sqrd(0.05);
60 s->Var[Q_BEARING] = (real)sqrd(rad(0.5));
61 s->Var[Q_GRADIENT] = (real)sqrd(rad(0.5));
62 s->Var[Q_BACKBEARING] = (real)sqrd(rad(0.5));
63 s->Var[Q_BACKGRADIENT] = (real)sqrd(rad(0.5));
64 /* SD of plumbed legs (0.25 degrees?) */
65 s->Var[Q_PLUMB] = (real)sqrd(rad(0.25));
66 /* SD of level legs (0.25 degrees?) */
67 s->Var[Q_LEVEL] = (real)sqrd(rad(0.25));
68 s->Var[Q_DEPTH] = (real)sqrd(0.05);
71 static void
72 default_truncate(settings *s)
74 s->Truncate = INT_MAX;
77 static void
78 default_case(settings *s)
80 s->Case = LOWER;
83 static reading default_order[] = { Fr, To, Tape, Comp, Clino, End };
85 static void
86 default_style(settings *s)
88 s->style = STYLE_NORMAL;
89 s->ordering = default_order;
90 s->dash_for_anon_wall_station = fFalse;
93 static void
94 default_prefix(settings *s)
96 s->Prefix = root;
99 static void
100 default_translate(settings *s)
102 int i;
103 short *t;
104 if (s->next && s->next->Translate == s->Translate) {
105 t = ((short*)osmalloc(ossizeof(short) * 257)) + 1;
106 memcpy(t - 1, s->Translate - 1, sizeof(short) * 257);
107 s->Translate = t;
109 /* SVX_ASSERT(EOF==-1);*/ /* important, since we rely on this */
110 t = s->Translate;
111 memset(t - 1, 0, sizeof(short) * 257);
112 for (i = '0'; i <= '9'; i++) t[i] = SPECIAL_NAMES;
113 for (i = 'A'; i <= 'Z'; i++) t[i] = SPECIAL_NAMES;
114 for (i = 'a'; i <= 'z'; i++) t[i] = SPECIAL_NAMES;
116 t['\t'] |= SPECIAL_BLANK;
117 t[' '] |= SPECIAL_BLANK;
118 t[','] |= SPECIAL_BLANK;
119 t[';'] |= SPECIAL_COMMENT;
120 t['\032'] |= SPECIAL_EOL; /* Ctrl-Z, so olde DOS text files are handled ok */
121 t[EOF] |= SPECIAL_EOL;
122 t['\n'] |= SPECIAL_EOL;
123 t['\r'] |= SPECIAL_EOL;
124 t['*'] |= SPECIAL_KEYWORD;
125 t['-'] |= SPECIAL_OMIT;
126 t['\\'] |= SPECIAL_ROOT;
127 t['.'] |= SPECIAL_SEPARATOR;
128 t['_'] |= SPECIAL_NAMES;
129 t['-'] |= SPECIAL_NAMES; /* Added in 0.97 prerelease 4 */
130 t['.'] |= SPECIAL_DECIMAL;
131 t['-'] |= SPECIAL_MINUS;
132 t['+'] |= SPECIAL_PLUS;
133 #if 0 /* FIXME */
134 t['{'] |= SPECIAL_OPEN;
135 t['}'] |= SPECIAL_CLOSE;
136 #endif
139 void
140 default_units(settings *s)
142 int quantity;
143 for (quantity = 0; quantity < Q_MAC; quantity++) {
144 if (TSTBIT(ANG_QMASK, quantity))
145 s->units[quantity] = (real)(M_PI / 180.0); /* degrees */
146 else
147 s->units[quantity] = (real)1.0; /* metres */
149 s->f_clino_percent = s->f_backclino_percent = fFalse;
150 s->f_bearing_quadrants = s->f_backbearing_quadrants = fFalse;
153 void
154 default_calib(settings *s)
156 int quantity;
157 for (quantity = 0; quantity < Q_MAC; quantity++) {
158 s->z[quantity] = (real)0.0;
159 s->sc[quantity] = (real)1.0;
163 static void
164 default_flags(settings *s)
166 s->flags = 0;
169 extern void
170 default_all(settings *s)
172 default_truncate(s);
173 s->infer = 0;
174 default_case(s);
175 default_style(s);
176 default_prefix(s);
177 default_translate(s);
178 default_grade(s);
179 default_units(s);
180 default_calib(s);
181 default_flags(s);
184 char *buffer = NULL;
185 static int buf_len;
187 static char *ucbuffer = NULL;
189 /* read token */
190 extern void
191 get_token(void)
193 skipblanks();
194 get_token_no_blanks();
197 extern void
198 get_token_no_blanks(void)
200 int i = -1;
202 s_zero(&buffer);
203 osfree(ucbuffer);
204 while (isalpha(ch)) {
205 s_catchar(&buffer, &buf_len, (char)ch);
206 nextch();
209 if (!buffer) s_catchar(&buffer, &buf_len, '\0');
211 ucbuffer = osmalloc(buf_len);
212 do {
213 i++;
214 ucbuffer[i] = toupper(buffer[i]);
215 } while (buffer[i]);
216 #if 0
217 printf("get_token_no_blanks() got “%s”\n", buffer);
218 #endif
221 /* read word */
222 static void
223 get_word(void)
225 s_zero(&buffer);
226 skipblanks();
227 while (!isBlank(ch) && !isEol(ch)) {
228 s_catchar(&buffer, &buf_len, (char)ch);
229 nextch();
232 if (!buffer) s_catchar(&buffer, &buf_len, '\0');
233 #if 0
234 printf("get_word() got “%s”\n", buffer);
235 #endif
238 /* match_tok() now uses binary chop
239 * tab argument should be alphabetically sorted (ascending)
241 extern int
242 match_tok(const sztok *tab, int tab_size)
244 int a = 0, b = tab_size - 1, c;
245 int r;
246 assert(tab_size > 0); /* catch empty table */
247 /* printf("[%d,%d]",a,b); */
248 while (a <= b) {
249 c = (unsigned)(a + b) / 2;
250 /* printf(" %d",c); */
251 r = strcmp(tab[c].sz, ucbuffer);
252 if (r == 0) return tab[c].tok; /* match */
253 if (r < 0)
254 a = c + 1;
255 else
256 b = c - 1;
258 return tab[tab_size].tok; /* no match */
261 typedef enum {
262 CMD_NULL = -1, CMD_ALIAS, CMD_BEGIN, CMD_CALIBRATE, CMD_CASE, CMD_COPYRIGHT,
263 CMD_CS, CMD_DATA, CMD_DATE, CMD_DECLINATION, CMD_DEFAULT, CMD_END,
264 CMD_ENTRANCE, CMD_EQUATE, CMD_EXPORT, CMD_FIX, CMD_FLAGS, CMD_INCLUDE,
265 CMD_INFER, CMD_INSTRUMENT, CMD_PREFIX, CMD_REF, CMD_REQUIRE, CMD_SD,
266 CMD_SET, CMD_SOLVE, CMD_TEAM, CMD_TITLE, CMD_TRUNCATE, CMD_UNITS
267 } cmds;
269 static const sztok cmd_tab[] = {
270 {"ALIAS", CMD_ALIAS},
271 {"BEGIN", CMD_BEGIN},
272 {"CALIBRATE", CMD_CALIBRATE},
273 {"CASE", CMD_CASE},
274 {"COPYRIGHT", CMD_COPYRIGHT},
275 {"CS", CMD_CS},
276 {"DATA", CMD_DATA},
277 {"DATE", CMD_DATE},
278 {"DECLINATION", CMD_DECLINATION},
279 #ifndef NO_DEPRECATED
280 {"DEFAULT", CMD_DEFAULT},
281 #endif
282 {"END", CMD_END},
283 {"ENTRANCE", CMD_ENTRANCE},
284 {"EQUATE", CMD_EQUATE},
285 {"EXPORT", CMD_EXPORT},
286 {"FIX", CMD_FIX},
287 {"FLAGS", CMD_FLAGS},
288 {"INCLUDE", CMD_INCLUDE},
289 {"INFER", CMD_INFER},
290 {"INSTRUMENT",CMD_INSTRUMENT},
291 #ifndef NO_DEPRECATED
292 {"PREFIX", CMD_PREFIX},
293 #endif
294 {"REF", CMD_REF},
295 {"REQUIRE", CMD_REQUIRE},
296 {"SD", CMD_SD},
297 {"SET", CMD_SET},
298 {"SOLVE", CMD_SOLVE},
299 {"TEAM", CMD_TEAM},
300 {"TITLE", CMD_TITLE},
301 {"TRUNCATE", CMD_TRUNCATE},
302 {"UNITS", CMD_UNITS},
303 {NULL, CMD_NULL}
306 /* masks for units which are length and angles respectively */
307 #define LEN_UMASK (BIT(UNITS_METRES) | BIT(UNITS_FEET) | BIT(UNITS_YARDS))
308 #define ANG_UMASK (BIT(UNITS_DEGS) | BIT(UNITS_GRADS) | BIT(UNITS_MINUTES))
310 /* ordering must be the same as the units enum */
311 const real factor_tab[] = {
312 1.0, METRES_PER_FOOT, (METRES_PER_FOOT*3.0),
313 (M_PI/180.0), (M_PI/180.0), (M_PI/200.0), 0.01, (M_PI/180.0/60.0)
316 const int units_to_msgno[] = {
317 /*m*/424,
318 /*ft*/428,
319 -1, /* yards */
320 /*°*/344, /* quadrants */
321 /*°*/344,
322 /*ᵍ*/345,
323 /*%*/96,
324 -1 /* minutes */
327 int get_length_units(int quantity) {
328 double factor = pcs->units[quantity];
329 if (fabs(factor - METRES_PER_FOOT) <= REAL_EPSILON ||
330 fabs(factor - METRES_PER_FOOT * 3.0) <= REAL_EPSILON) {
331 return UNITS_FEET;
333 return UNITS_METRES;
336 int get_angle_units(int quantity) {
337 double factor = pcs->units[quantity];
338 if (fabs(factor - M_PI / 200.0) <= REAL_EPSILON) {
339 return UNITS_GRADS;
341 return UNITS_DEGS;
344 static int
345 get_units(unsigned long qmask, bool percent_ok)
347 static const sztok utab[] = {
348 {"DEGREES", UNITS_DEGS },
349 {"DEGS", UNITS_DEGS },
350 {"FEET", UNITS_FEET },
351 {"GRADS", UNITS_GRADS },
352 {"METERS", UNITS_METRES },
353 {"METRES", UNITS_METRES },
354 {"METRIC", UNITS_METRES },
355 {"MILS", UNITS_DEPRECATED_ALIAS_FOR_GRADS },
356 {"MINUTES", UNITS_MINUTES },
357 {"PERCENT", UNITS_PERCENT },
358 {"PERCENTAGE", UNITS_PERCENT },
359 {"QUADRANTS", UNITS_QUADRANTS },
360 {"QUADS", UNITS_QUADRANTS },
361 {"YARDS", UNITS_YARDS },
362 {NULL, UNITS_NULL }
364 int units;
365 get_token();
366 units = match_tok(utab, TABSIZE(utab));
367 if (units == UNITS_NULL) {
368 compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*Unknown units “%s”*/35, buffer);
369 return UNITS_NULL;
371 /* Survex has long misdefined "mils" as an alias for "grads", of which
372 * there are 400 in a circle. There are several definitions of "mils"
373 * with a circle containing 2000π SI milliradians, 6400 NATO mils, 6000
374 * Warsaw Pact mils, and 6300 Swedish streck, and they aren't in common
375 * use by cave surveyors, so we now just warn if mils are used.
377 if (units == UNITS_DEPRECATED_ALIAS_FOR_GRADS) {
378 compile_diagnostic(DIAG_WARN|DIAG_BUF|DIAG_SKIP,
379 /*Units “%s” are deprecated, assuming “grads” - see manual for details*/479,
380 buffer);
381 units = UNITS_GRADS;
383 if (units == UNITS_PERCENT && percent_ok &&
384 !(qmask & ~(BIT(Q_GRADIENT)|BIT(Q_BACKGRADIENT)))) {
385 return units;
387 if (units == UNITS_QUADRANTS &&
388 !(qmask & ~(BIT(Q_BEARING)|BIT(Q_BACKBEARING)))) {
389 return units;
391 if (((qmask & LEN_QMASK) && !TSTBIT(LEN_UMASK, units)) ||
392 ((qmask & ANG_QMASK) && !TSTBIT(ANG_UMASK, units))) {
393 /* TRANSLATORS: Note: In English you talk about the *units* of a single
394 * measurement, but the correct term in other languages may be singular.
396 compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*Invalid units “%s” for quantity*/37, buffer);
397 return UNITS_NULL;
399 return units;
402 /* returns mask with bit x set to indicate quantity x specified */
403 static unsigned long
404 get_qlist(unsigned long mask_bad)
406 static const sztok qtab[] = {
407 {"ALTITUDE", Q_DZ },
408 {"BACKBEARING", Q_BACKBEARING },
409 {"BACKCLINO", Q_BACKGRADIENT }, /* alternative name */
410 {"BACKCOMPASS", Q_BACKBEARING }, /* alternative name */
411 {"BACKGRADIENT", Q_BACKGRADIENT },
412 {"BACKLENGTH", Q_BACKLENGTH },
413 {"BACKTAPE", Q_BACKLENGTH }, /* alternative name */
414 {"BEARING", Q_BEARING },
415 {"CEILING", Q_UP }, /* alternative name */
416 {"CLINO", Q_GRADIENT }, /* alternative name */
417 {"COMPASS", Q_BEARING }, /* alternative name */
418 {"COUNT", Q_COUNT },
419 {"COUNTER", Q_COUNT }, /* alternative name */
420 {"DECLINATION", Q_DECLINATION },
421 {"DEFAULT", Q_DEFAULT }, /* not a real quantity... */
422 {"DEPTH", Q_DEPTH },
423 {"DOWN", Q_DOWN },
424 {"DX", Q_DX }, /* alternative name */
425 {"DY", Q_DY }, /* alternative name */
426 {"DZ", Q_DZ }, /* alternative name */
427 {"EASTING", Q_DX },
428 {"FLOOR", Q_DOWN }, /* alternative name */
429 {"GRADIENT", Q_GRADIENT },
430 {"LEFT", Q_LEFT },
431 {"LENGTH", Q_LENGTH },
432 {"LEVEL", Q_LEVEL},
433 {"NORTHING", Q_DY },
434 {"PLUMB", Q_PLUMB},
435 {"POSITION", Q_POS },
436 {"RIGHT", Q_RIGHT },
437 {"TAPE", Q_LENGTH }, /* alternative name */
438 {"UP", Q_UP },
439 {NULL, Q_NULL }
441 unsigned long qmask = 0;
442 int tok;
443 filepos fp;
445 while (1) {
446 get_pos(&fp);
447 get_token();
448 tok = match_tok(qtab, TABSIZE(qtab));
449 if (tok == Q_DEFAULT && !(mask_bad & BIT(Q_DEFAULT))) {
450 /* Only recognise DEFAULT if it is the first quantity, and then don't
451 * look for any more. */
452 if (qmask == 0)
453 return BIT(Q_DEFAULT);
454 break;
456 /* bail out if we reach the table end with no match */
457 if (tok == Q_NULL) break;
458 qmask |= BIT(tok);
459 if (qmask & mask_bad) {
460 compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*Unknown instrument “%s”*/39, buffer);
461 return 0;
465 if (qmask == 0) {
466 /* TRANSLATORS: A "quantity" is something measured like "LENGTH",
467 * "BEARING", "ALTITUDE", etc. */
468 compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*Unknown quantity “%s”*/34, buffer);
469 } else {
470 set_pos(&fp);
473 return qmask;
476 #define SPECIAL_UNKNOWN 0
477 static void
478 cmd_set(void)
480 static const sztok chartab[] = {
481 {"BLANK", SPECIAL_BLANK },
482 /*FIXME {"CLOSE", SPECIAL_CLOSE }, */
483 {"COMMENT", SPECIAL_COMMENT },
484 {"DECIMAL", SPECIAL_DECIMAL },
485 {"EOL", SPECIAL_EOL }, /* EOL won't work well */
486 {"KEYWORD", SPECIAL_KEYWORD },
487 {"MINUS", SPECIAL_MINUS },
488 {"NAMES", SPECIAL_NAMES },
489 {"OMIT", SPECIAL_OMIT },
490 /*FIXME {"OPEN", SPECIAL_OPEN }, */
491 {"PLUS", SPECIAL_PLUS },
492 #ifndef NO_DEPRECATED
493 {"ROOT", SPECIAL_ROOT },
494 #endif
495 {"SEPARATOR", SPECIAL_SEPARATOR },
496 {NULL, SPECIAL_UNKNOWN }
498 int mask;
499 int i;
501 get_token();
502 mask = match_tok(chartab, TABSIZE(chartab));
504 if (mask == SPECIAL_UNKNOWN) {
505 compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*Unknown character class “%s”*/42, buffer);
506 return;
509 #ifndef NO_DEPRECATED
510 if (mask == SPECIAL_ROOT) {
511 if (root_depr_count < 5) {
512 /* TRANSLATORS: Use of the ROOT character (which is "\" by default) is
513 * deprecated, so this error would be generated by:
515 * *equate \foo.7 1
517 * If you're unsure what "deprecated" means, see:
518 * https://en.wikipedia.org/wiki/Deprecation */
519 compile_diagnostic(DIAG_WARN|DIAG_BUF, /*ROOT is deprecated*/25);
520 if (++root_depr_count == 5)
521 /* TRANSLATORS: If you're unsure what "deprecated" means, see:
522 * https://en.wikipedia.org/wiki/Deprecation */
523 compile_diagnostic(DIAG_WARN, /*Further uses of this deprecated feature will not be reported*/95);
526 #endif
528 /* if we're currently using an inherited translation table, allocate a new
529 * table, and copy old one into it */
530 if (pcs->next && pcs->next->Translate == pcs->Translate) {
531 short *p;
532 p = ((short*)osmalloc(ossizeof(short) * 257)) + 1;
533 memcpy(p - 1, pcs->Translate - 1, sizeof(short) * 257);
534 pcs->Translate = p;
537 skipblanks();
539 /* clear this flag for all non-alphanums */
540 for (i = 0; i < 256; i++)
541 if (!isalnum(i)) pcs->Translate[i] &= ~mask;
543 /* now set this flag for all specified chars */
544 while (!isEol(ch)) {
545 if (!isalnum(ch)) {
546 pcs->Translate[ch] |= mask;
547 } else if (tolower(ch) == 'x') {
548 int hex;
549 filepos fp;
550 get_pos(&fp);
551 nextch();
552 if (!isxdigit(ch)) {
553 set_pos(&fp);
554 break;
556 hex = isdigit(ch) ? ch - '0' : tolower(ch) - 'a';
557 nextch();
558 if (!isxdigit(ch)) {
559 set_pos(&fp);
560 break;
562 hex = hex << 4 | (isdigit(ch) ? ch - '0' : tolower(ch) - 'a');
563 pcs->Translate[hex] |= mask;
564 } else {
565 break;
567 nextch();
571 static void
572 check_reentry(prefix *survey, const filepos* fpos_ptr)
574 /* Don't try to check "*prefix \" or "*begin \" */
575 if (!survey->up) return;
576 if (TSTBIT(survey->sflags, SFLAGS_PREFIX_ENTERED)) {
577 static int reenter_depr_count = 0;
578 filepos fp_tmp;
580 if (reenter_depr_count >= 5)
581 return;
583 get_pos(&fp_tmp);
584 set_pos(fpos_ptr);
585 /* TRANSLATORS: The first of two warnings given when a survey which has
586 * already been completed is reentered. This example file crawl.svx:
588 * *begin crawl ; <- second warning here
589 * 1 2 9.45 234 -01
590 * *end crawl
591 * *begin crawl ; <- first warning here
592 * 2 3 7.67 223 -03
593 * *end crawl
595 * Would lead to:
597 * crawl.svx:4: Reentering an existing survey is deprecated
598 * crawl.svx:1: Originally entered here
600 * If you're unsure what "deprecated" means, see:
601 * https://en.wikipedia.org/wiki/Deprecation */
602 compile_diagnostic(DIAG_WARN|DIAG_TOKEN, /*Reentering an existing survey is deprecated*/29);
603 set_pos(&fp_tmp);
604 /* TRANSLATORS: The second of two warnings given when a survey which has
605 * already been completed is reentered. This example file crawl.svx:
607 * *begin crawl
608 * 1 2 9.45 234 -01 # <- second warning here
609 * *end crawl
610 * *begin crawl # <- first warning here
611 * 2 3 7.67 223 -03
612 * *end crawl
614 * Would lead to:
616 * crawl.svx:3: Reentering an existing survey is deprecated
617 * crawl.svx:1: Originally entered here
619 * If you're unsure what "deprecated" means, see:
620 * https://en.wikipedia.org/wiki/Deprecation */
621 compile_diagnostic_pfx(DIAG_WARN, survey, /*Originally entered here*/30);
622 if (++reenter_depr_count == 5) {
623 /* After we've warned about 5 uses of the same deprecated feature, we
624 * give up for the rest of the current processing run.
626 * If you're unsure what "deprecated" means, see:
627 * https://en.wikipedia.org/wiki/Deprecation */
628 compile_diagnostic(DIAG_WARN, /*Further uses of this deprecated feature will not be reported*/95);
630 } else {
631 survey->sflags |= BIT(SFLAGS_PREFIX_ENTERED);
632 survey->filename = file.filename;
633 survey->line = file.line;
637 #ifndef NO_DEPRECATED
638 static void
639 cmd_prefix(void)
641 static int prefix_depr_count = 0;
642 prefix *survey;
643 filepos fp;
644 /* Issue warning first, so "*prefix \" warns first that *prefix is
645 * deprecated and then that ROOT is...
647 if (prefix_depr_count < 5) {
648 /* TRANSLATORS: If you're unsure what "deprecated" means, see:
649 * https://en.wikipedia.org/wiki/Deprecation */
650 compile_diagnostic(DIAG_WARN|DIAG_BUF, /**prefix is deprecated - use *begin and *end instead*/6);
651 if (++prefix_depr_count == 5)
652 compile_diagnostic(DIAG_WARN, /*Further uses of this deprecated feature will not be reported*/95);
654 get_pos(&fp);
655 survey = read_prefix(PFX_SURVEY|PFX_ALLOW_ROOT);
656 pcs->Prefix = survey;
657 check_reentry(survey, &fp);
659 #endif
661 static void
662 cmd_alias(void)
664 /* Currently only two forms are supported:
665 * *alias station - ..
666 * *alias station -
668 get_token();
669 if (strcmp(ucbuffer, "STATION") != 0)
670 goto bad;
671 get_word();
672 if (strcmp(buffer, "-") != 0)
673 goto bad;
674 get_word();
675 if (*buffer && strcmp(buffer, "..") != 0)
676 goto bad;
677 pcs->dash_for_anon_wall_station = (*buffer != '\0');
678 return;
679 bad:
680 compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*Bad *alias command*/397);
683 static void
684 cmd_begin(void)
686 settings *pcsNew;
688 pcsNew = osnew(settings);
689 *pcsNew = *pcs; /* copy contents */
690 pcsNew->begin_lineno = file.line;
691 pcsNew->next = pcs;
692 pcs = pcsNew;
694 skipblanks();
695 pcs->begin_survey = NULL;
696 if (!isEol(ch) && !isComm(ch)) {
697 filepos fp;
698 prefix *survey;
699 get_pos(&fp);
700 survey = read_prefix(PFX_SURVEY|PFX_ALLOW_ROOT|PFX_WARN_SEPARATOR);
701 pcs->begin_survey = survey;
702 pcs->Prefix = survey;
703 check_reentry(survey, &fp);
704 f_export_ok = fTrue;
708 static void
709 invalidate_pj_cached(void)
711 /* Invalidate the cached PJ. */
712 if (pj_cached) {
713 proj_destroy(pj_cached);
714 pj_cached = NULL;
718 void
719 report_declination(settings *p)
721 if (p->min_declination <= p->max_declination) {
722 int y, m, d;
723 char range[128];
724 const char* deg_sign = msg(/*°*/344);
725 ymd_from_days_since_1900(p->min_declination_days, &y, &m, &d);
726 snprintf(range, sizeof(range),
727 "%.1f%s @ %04d-%02d-%02d",
728 deg(p->min_declination), deg_sign, y, m, d);
729 if (p->min_declination_days != p->max_declination_days) {
730 size_t len = strlen(range);
731 ymd_from_days_since_1900(p->max_declination_days, &y, &m, &d);
732 snprintf(range + len, sizeof(range) - len,
733 " / %.1f%s @ %04d-%02d-%02d",
734 deg(p->max_declination), deg_sign, y, m, d);
736 /* TRANSLATORS: This message gives information about the range of
737 * declination values and the grid convergence value calculated for
738 * each "*declination auto ..." command.
740 * The first %s will be replaced by the declination range (or single
741 * value), and %.1f%s by the grid convergence angle.
743 compile_diagnostic_at(DIAG_INFO|DIAG_COL, p->dec_filename, p->dec_line,
744 /*Declination: %s, grid convergence: %.1f%s*/484,
745 range,
746 deg(p->convergence), deg_sign);
747 PUTC(' ', STDERR);
748 fputs(p->dec_context, STDERR);
749 fputnl(STDERR);
750 free(p->dec_context);
751 p->dec_context = NULL;
755 void
756 pop_settings(void)
758 settings * p = pcs;
759 pcs = pcs->next;
761 SVX_ASSERT(pcs);
763 if (pcs->dec_lat != p->dec_lat ||
764 pcs->dec_lon != p->dec_lon ||
765 pcs->dec_alt != p->dec_alt) {
766 report_declination(p);
767 } else {
768 pcs->min_declination_days = p->min_declination_days;
769 pcs->max_declination_days = p->max_declination_days;
770 pcs->min_declination = p->min_declination;
771 pcs->max_declination = p->max_declination;
774 if (p->proj_str != pcs->proj_str) {
775 if (!p->proj_str || !pcs->proj_str ||
776 strcmp(p->proj_str, pcs->proj_str) != 0) {
777 invalidate_pj_cached();
779 /* free proj_str if not used by parent */
780 osfree(p->proj_str);
783 /* don't free default ordering or ordering used by parent */
784 if (p->ordering != default_order && p->ordering != pcs->ordering)
785 osfree((reading*)p->ordering);
787 /* free Translate if not used by parent */
788 if (p->Translate != pcs->Translate)
789 osfree(p->Translate - 1);
791 /* free meta if not used by parent, or in this block */
792 if (p->meta && p->meta != pcs->meta && p->meta->ref_count == 0)
793 osfree(p->meta);
795 osfree(p);
798 static void
799 cmd_end(void)
801 prefix *survey, *begin_survey;
802 filepos fp;
804 if (pcs->begin_lineno == 0) {
805 if (pcs->next == NULL) {
806 /* more ENDs than BEGINs */
807 compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*No matching BEGIN*/192);
808 } else {
809 compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*END with no matching BEGIN in this file*/22);
811 return;
814 begin_survey = pcs->begin_survey;
816 pop_settings();
818 /* note need to read using root *before* BEGIN */
819 skipblanks();
820 if (isEol(ch) || isComm(ch)) {
821 survey = NULL;
822 } else {
823 get_pos(&fp);
824 survey = read_prefix(PFX_SURVEY|PFX_ALLOW_ROOT);
827 if (survey != begin_survey) {
828 if (survey) {
829 set_pos(&fp);
830 if (!begin_survey) {
831 /* TRANSLATORS: Used when a BEGIN command has no survey, but the
832 * END command does, e.g.:
834 * *begin
835 * 1 2 10.00 178 -01
836 * *end entrance <--[Message given here] */
837 compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Matching BEGIN command has no survey name*/36);
838 } else {
839 /* TRANSLATORS: *BEGIN <survey> and *END <survey> should have the
840 * same <survey> if it’s given at all */
841 compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Survey name doesn’t match BEGIN*/193);
843 skipline();
844 } else {
845 /* TRANSLATORS: Used when a BEGIN command has a survey name, but the
846 * END command omits it, e.g.:
848 * *begin entrance
849 * 1 2 10.00 178 -01
850 * *end <--[Message given here] */
851 compile_diagnostic(DIAG_WARN|DIAG_COL, /*Survey name omitted from END*/194);
856 static void
857 cmd_entrance(void)
859 prefix *pfx = read_prefix(PFX_STATION);
860 pfx->sflags |= BIT(SFLAGS_ENTRANCE);
863 static const prefix * first_fix_name = NULL;
864 static const char * first_fix_filename;
865 static unsigned first_fix_line;
867 static void
868 cmd_fix(void)
870 prefix *fix_name;
871 node *stn = NULL;
872 static prefix *name_omit_already = NULL;
873 static const char * name_omit_already_filename = NULL;
874 static unsigned int name_omit_already_line;
875 real x, y, z;
876 filepos fp;
878 fix_name = read_prefix(PFX_STATION|PFX_ALLOW_ROOT);
879 fix_name->sflags |= BIT(SFLAGS_FIXED);
881 get_pos(&fp);
882 get_token();
883 if (strcmp(ucbuffer, "REFERENCE") == 0) {
884 /* suppress "unused fixed point" warnings for this station */
885 fix_name->sflags |= BIT(SFLAGS_USED);
886 } else {
887 if (*ucbuffer) set_pos(&fp);
890 x = read_numeric(fTrue);
891 if (x == HUGE_REAL) {
892 /* If the end of the line isn't blank, read a number after all to
893 * get a more helpful error message */
894 if (!isEol(ch) && !isComm(ch)) x = read_numeric(fFalse);
896 if (x == HUGE_REAL) {
897 if (pcs->proj_str || proj_str_out) {
898 compile_diagnostic(DIAG_ERR|DIAG_COL|DIAG_SKIP, /*Coordinates can't be omitted when coordinate system has been specified*/439);
899 return;
902 if (fix_name == name_omit_already) {
903 compile_diagnostic(DIAG_WARN|DIAG_COL, /*Same station fixed twice with no coordinates*/61);
904 return;
907 if (name_omit_already) {
908 /* TRANSLATORS: Emitted after second and subsequent "FIX" command
909 * with no coordinates.
911 compile_diagnostic_at(DIAG_ERR|DIAG_COL,
912 name_omit_already_filename,
913 name_omit_already_line,
914 /*Already had FIX command with no coordinates for station “%s”*/441,
915 sprint_prefix(name_omit_already));
916 } else {
917 /* TRANSLATORS: " *fix a " gives this message: */
918 compile_diagnostic(DIAG_INFO|DIAG_COL, /*FIX command with no coordinates - fixing at (0,0,0)*/54);
920 name_omit_already = fix_name;
921 name_omit_already_filename = file.filename;
922 name_omit_already_line = file.line;
925 x = y = z = (real)0.0;
926 } else {
927 real sdx;
928 y = read_numeric(fFalse);
929 z = read_numeric(fFalse);
931 if (pcs->proj_str && proj_str_out) {
932 PJ *transform = pj_cached;
933 if (!transform) {
934 transform = proj_create_crs_to_crs(PJ_DEFAULT_CTX,
935 pcs->proj_str,
936 proj_str_out,
937 NULL);
938 if (transform) {
939 /* Normalise the output order so x is longitude and y latitude - by
940 * default new PROJ has them switched for EPSG:4326 which just seems
941 * confusing.
943 PJ* pj_norm = proj_normalize_for_visualization(PJ_DEFAULT_CTX,
944 transform);
945 proj_destroy(transform);
946 transform = pj_norm;
949 pj_cached = transform;
952 if (proj_angular_input(transform, PJ_FWD)) {
953 /* Input coordinate system expects radians. */
954 x = rad(x);
955 y = rad(y);
958 PJ_COORD coord = {{x, y, z, HUGE_VAL}};
959 coord = proj_trans(transform, PJ_FWD, coord);
960 x = coord.xyzt.x;
961 y = coord.xyzt.y;
962 z = coord.xyzt.z;
964 if (x == HUGE_VAL || y == HUGE_VAL || z == HUGE_VAL) {
965 compile_diagnostic(DIAG_ERR, /*Failed to convert coordinates: %s*/436,
966 proj_errno_string(proj_errno(transform)));
967 /* Set dummy values which are finite. */
968 x = y = z = 0;
970 } else if (pcs->proj_str) {
971 compile_diagnostic(DIAG_ERR, /*The input projection is set but the output projection isn't*/437);
972 } else if (proj_str_out) {
973 compile_diagnostic(DIAG_ERR, /*The output projection is set but the input projection isn't*/438);
976 get_pos(&fp);
977 sdx = read_numeric(fTrue);
978 if (sdx <= 0) {
979 set_pos(&fp);
980 compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_NUM, /*Standard deviation must be positive*/48);
981 return;
983 if (sdx != HUGE_REAL) {
984 real sdy, sdz;
985 real cxy = 0, cyz = 0, czx = 0;
986 get_pos(&fp);
987 sdy = read_numeric(fTrue);
988 if (sdy == HUGE_REAL) {
989 /* only one variance given */
990 sdy = sdz = sdx;
991 } else {
992 if (sdy <= 0) {
993 set_pos(&fp);
994 compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_NUM, /*Standard deviation must be positive*/48);
995 return;
997 get_pos(&fp);
998 sdz = read_numeric(fTrue);
999 if (sdz == HUGE_REAL) {
1000 /* two variances given - horizontal & vertical */
1001 sdz = sdy;
1002 sdy = sdx;
1003 } else {
1004 if (sdz <= 0) {
1005 set_pos(&fp);
1006 compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_NUM, /*Standard deviation must be positive*/48);
1007 return;
1009 cxy = read_numeric(fTrue);
1010 if (cxy != HUGE_REAL) {
1011 /* covariances given */
1012 cyz = read_numeric(fFalse);
1013 czx = read_numeric(fFalse);
1014 } else {
1015 cxy = 0;
1019 stn = StnFromPfx(fix_name);
1020 if (!fixed(stn)) {
1021 node *fixpt = osnew(node);
1022 prefix *name;
1023 name = osnew(prefix);
1024 name->pos = osnew(pos);
1025 name->ident = NULL;
1026 name->shape = 0;
1027 fixpt->name = name;
1028 name->stn = fixpt;
1029 name->up = NULL;
1030 if (TSTBIT(pcs->infer, INFER_EXPORTS)) {
1031 name->min_export = USHRT_MAX;
1032 } else {
1033 name->min_export = 0;
1035 name->max_export = 0;
1036 name->sflags = 0;
1037 add_stn_to_list(&stnlist, fixpt);
1038 POS(fixpt, 0) = x;
1039 POS(fixpt, 1) = y;
1040 POS(fixpt, 2) = z;
1041 fix(fixpt);
1042 fixpt->leg[0] = fixpt->leg[1] = fixpt->leg[2] = NULL;
1043 addfakeleg(fixpt, stn, 0, 0, 0,
1044 sdx * sdx, sdy * sdy, sdz * sdz
1045 #ifndef NO_COVARIANCES
1046 , cxy, cyz, czx
1047 #endif
1051 if (!first_fix_name) {
1052 /* We track if we've fixed a station yet, and if so what the name
1053 * of the first fix was, so that we can issue an error if the
1054 * output coordinate system is set after fixing a station. */
1055 first_fix_name = fix_name;
1056 first_fix_filename = file.filename;
1057 first_fix_line = file.line;
1060 return;
1064 if (!first_fix_name) {
1065 /* We track if we've fixed a station yet, and if so what the name of the
1066 * first fix was, so that we can issue an error if the output coordinate
1067 * system is set after fixing a station. */
1068 first_fix_name = fix_name;
1069 first_fix_filename = file.filename;
1070 first_fix_line = file.line;
1073 stn = StnFromPfx(fix_name);
1074 if (!fixed(stn)) {
1075 POS(stn, 0) = x;
1076 POS(stn, 1) = y;
1077 POS(stn, 2) = z;
1078 fix(stn);
1079 return;
1082 if (x != POS(stn, 0) || y != POS(stn, 1) || z != POS(stn, 2)) {
1083 compile_diagnostic(DIAG_ERR, /*Station already fixed or equated to a fixed point*/46);
1084 return;
1086 /* TRANSLATORS: *fix a 1 2 3 / *fix a 1 2 3 */
1087 compile_diagnostic(DIAG_WARN, /*Station already fixed at the same coordinates*/55);
1090 static void
1091 cmd_flags(void)
1093 static const sztok flagtab[] = {
1094 {"DUPLICATE", FLAGS_DUPLICATE },
1095 {"NOT", FLAGS_NOT },
1096 {"SPLAY", FLAGS_SPLAY },
1097 {"SURFACE", FLAGS_SURFACE },
1098 {NULL, FLAGS_UNKNOWN }
1100 bool fNot = fFalse;
1101 bool fEmpty = fTrue;
1102 while (1) {
1103 int flag;
1104 get_token();
1105 /* If buffer is empty, it could mean end of line, or maybe
1106 * some non-letter junk which is better reported later */
1107 if (!buffer[0]) break;
1109 fEmpty = fFalse;
1110 flag = match_tok(flagtab, TABSIZE(flagtab));
1111 /* treat the second NOT in "NOT NOT" as an unknown flag */
1112 if (flag == FLAGS_UNKNOWN || (fNot && flag == FLAGS_NOT)) {
1113 compile_diagnostic(DIAG_ERR|DIAG_BUF, /*FLAG “%s” unknown*/68, buffer);
1114 /* Recover from “*FLAGS NOT BOGUS SURFACE” by ignoring "NOT BOGUS" */
1115 fNot = fFalse;
1116 } else if (flag == FLAGS_NOT) {
1117 fNot = fTrue;
1118 } else if (fNot) {
1119 pcs->flags &= ~BIT(flag);
1120 fNot = fFalse;
1121 } else {
1122 pcs->flags |= BIT(flag);
1126 if (fNot) {
1127 compile_diagnostic(DIAG_ERR|DIAG_BUF, /*Expecting “DUPLICATE”, “SPLAY”, or “SURFACE”*/188);
1128 } else if (fEmpty) {
1129 compile_diagnostic(DIAG_ERR|DIAG_BUF, /*Expecting “NOT”, “DUPLICATE”, “SPLAY”, or “SURFACE”*/189);
1133 static void
1134 cmd_equate(void)
1136 prefix *name1, *name2;
1137 bool fOnlyOneStn = fTrue; /* to trap eg *equate entrance.6 */
1138 filepos fp;
1140 get_pos(&fp);
1141 name1 = read_prefix(PFX_STATION|PFX_ALLOW_ROOT|PFX_SUSPECT_TYPO);
1142 while (fTrue) {
1143 name2 = name1;
1144 skipblanks();
1145 if (isEol(ch) || isComm(ch)) {
1146 if (fOnlyOneStn) {
1147 set_pos(&fp);
1148 /* TRANSLATORS: EQUATE is a command name, so shouldn’t be
1149 * translated.
1151 * Here "station" is a survey station, not a train station.
1153 compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_TOKEN, /*Only one station in EQUATE command*/33);
1155 return;
1158 name1 = read_prefix(PFX_STATION|PFX_ALLOW_ROOT|PFX_SUSPECT_TYPO);
1159 process_equate(name1, name2);
1160 fOnlyOneStn = fFalse;
1164 static void
1165 report_missing_export(prefix *pfx, int depth)
1167 char *s;
1168 const char *p;
1169 prefix *survey = pfx;
1170 int i;
1171 for (i = depth + 1; i; i--) {
1172 survey = survey->up;
1173 SVX_ASSERT(survey);
1175 s = osstrdup(sprint_prefix(survey));
1176 p = sprint_prefix(pfx);
1177 if (survey->filename) {
1178 /* TRANSLATORS: A station must be exported out of each level it is in, so
1179 * this would give "Station “\outer.inner.1” not exported from survey
1180 * “\outer”)":
1182 * *equate entrance outer.inner.1
1183 * *begin outer
1184 * *begin inner
1185 * *export 1
1186 * 1 2 1.23 045 -6
1187 * *end inner
1188 * *end outer
1190 * Here "survey" is a "cave map" rather than list of questions - it should be
1191 * translated to the terminology that cavers using the language would use.
1193 compile_diagnostic_pfx(DIAG_ERR, survey,
1194 /*Station “%s” not exported from survey “%s”*/26, p, s);
1195 } else {
1196 compile_diagnostic(DIAG_ERR, /*Station “%s” not exported from survey “%s”*/26, p, s);
1198 osfree(s);
1201 static void
1202 cmd_export(void)
1204 prefix *pfx;
1206 fExportUsed = fTrue;
1207 do {
1208 int depth = 0;
1209 pfx = read_prefix(PFX_STATION|PFX_NEW);
1210 if (pfx == NULL) {
1211 /* The argument was an existing station. */
1212 /* FIXME */
1213 } else {
1214 prefix *p = pfx;
1215 while (p != NULL && p != pcs->Prefix) {
1216 depth++;
1217 p = p->up;
1219 /* Something like: *export \foo, but we've excluded use of root */
1220 SVX_ASSERT(p);
1222 /* *export \ or similar bogus stuff */
1223 SVX_ASSERT(depth);
1224 #if 0
1225 printf("C min %d max %d depth %d pfx %s\n",
1226 pfx->min_export, pfx->max_export, depth, sprint_prefix(pfx));
1227 #endif
1228 if (pfx->min_export == 0) {
1229 /* not encountered *export for this name before */
1230 if (pfx->max_export > depth) report_missing_export(pfx, depth);
1231 pfx->min_export = pfx->max_export = depth;
1232 } else if (pfx->min_export != USHRT_MAX) {
1233 /* FIXME: what to do if a station is marked for inferred exports
1234 * but is then explicitly exported? Currently we just ignore the
1235 * explicit export... */
1236 if (pfx->min_export - 1 > depth) {
1237 report_missing_export(pfx, depth);
1238 } else if (pfx->min_export - 1 < depth) {
1239 /* TRANSLATORS: Here "station" is a survey station, not a train station.
1241 * Exporting a station twice gives this error:
1243 * *begin example
1244 * *export 1
1245 * *export 1
1246 * 1 2 1.24 045 -6
1247 * *end example */
1248 compile_diagnostic(DIAG_ERR, /*Station “%s” already exported*/66,
1249 sprint_prefix(pfx));
1251 pfx->min_export = depth;
1253 skipblanks();
1254 } while (!isEol(ch) && !isComm(ch));
1257 static void
1258 cmd_data(void)
1260 static const sztok dtab[] = {
1261 {"ALTITUDE", Dz },
1262 {"BACKBEARING", BackComp },
1263 {"BACKCLINO", BackClino }, /* alternative name */
1264 {"BACKCOMPASS", BackComp }, /* alternative name */
1265 {"BACKGRADIENT", BackClino },
1266 {"BACKLENGTH", BackTape },
1267 {"BACKTAPE", BackTape }, /* alternative name */
1268 {"BEARING", Comp },
1269 {"CEILING", Up }, /* alternative name */
1270 {"CLINO", Clino }, /* alternative name */
1271 {"COMPASS", Comp }, /* alternative name */
1272 {"COUNT", Count }, /* FrCount&ToCount in multiline */
1273 {"DEPTH", Depth }, /* FrDepth&ToDepth in multiline */
1274 {"DEPTHCHANGE", DepthChange },
1275 {"DIRECTION", Dir },
1276 {"DOWN", Down },
1277 {"DX", Dx },
1278 {"DY", Dy },
1279 {"DZ", Dz },
1280 {"EASTING", Dx },
1281 {"FLOOR", Down }, /* alternative name */
1282 {"FROM", Fr },
1283 {"FROMCOUNT", FrCount },
1284 {"FROMDEPTH", FrDepth },
1285 {"GRADIENT", Clino },
1286 {"IGNORE", Ignore },
1287 {"IGNOREALL", IgnoreAll },
1288 {"LEFT", Left },
1289 {"LENGTH", Tape },
1290 {"NEWLINE", Newline },
1291 {"NORTHING", Dy },
1292 {"RIGHT", Right },
1293 {"STATION", Station }, /* Fr&To in multiline */
1294 {"TAPE", Tape }, /* alternative name */
1295 {"TO", To },
1296 {"TOCOUNT", ToCount },
1297 {"TODEPTH", ToDepth },
1298 {"UP", Up },
1299 {NULL, End }
1302 #define MASK_stns BIT(Fr) | BIT(To) | BIT(Station)
1303 #define MASK_tape BIT(Tape) | BIT(BackTape) | BIT(FrCount) | BIT(ToCount) | BIT(Count)
1304 #define MASK_dpth BIT(FrDepth) | BIT(ToDepth) | BIT(Depth) | BIT(DepthChange)
1305 #define MASK_comp BIT(Comp) | BIT(BackComp)
1306 #define MASK_clin BIT(Clino) | BIT(BackClino)
1308 #define MASK_NORMAL MASK_stns | BIT(Dir) | MASK_tape | MASK_comp | MASK_clin
1309 #define MASK_DIVING MASK_NORMAL | MASK_dpth
1310 #define MASK_CARTESIAN MASK_stns | BIT(Dx) | BIT(Dy) | BIT(Dz)
1311 #define MASK_CYLPOLAR MASK_stns | BIT(Dir) | MASK_tape | MASK_comp | MASK_dpth
1312 #define MASK_NOSURVEY MASK_stns
1313 #define MASK_PASSAGE BIT(Station) | BIT(Left) | BIT(Right) | BIT(Up) | BIT(Down)
1315 /* readings which may be given for each style */
1316 static const unsigned long mask[] = {
1317 MASK_NORMAL, MASK_DIVING, MASK_CARTESIAN, MASK_CYLPOLAR, MASK_NOSURVEY,
1318 MASK_PASSAGE
1321 /* readings which may be omitted for each style */
1322 static const unsigned long mask_optional[] = {
1323 BIT(Dir) | BIT(Clino) | BIT(BackClino),
1324 BIT(Dir) | BIT(Clino) | BIT(BackClino),
1326 BIT(Dir),
1328 0 /* BIT(Left) | BIT(Right) | BIT(Up) | BIT(Down), */
1331 /* all valid readings */
1332 static const unsigned long mask_all[] = {
1333 MASK_NORMAL | BIT(Newline) | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1334 MASK_DIVING | BIT(Newline) | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1335 MASK_CARTESIAN | BIT(Newline) | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1336 MASK_CYLPOLAR | BIT(Newline) | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1337 MASK_NOSURVEY | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1338 MASK_PASSAGE | BIT(Ignore) | BIT(IgnoreAll) | BIT(End)
1340 #define STYLE_DEFAULT -2
1341 #define STYLE_UNKNOWN -1
1343 static const sztok styletab[] = {
1344 {"CARTESIAN", STYLE_CARTESIAN },
1345 {"CYLPOLAR", STYLE_CYLPOLAR },
1346 {"DEFAULT", STYLE_DEFAULT },
1347 {"DIVING", STYLE_DIVING },
1348 {"NORMAL", STYLE_NORMAL },
1349 {"NOSURVEY", STYLE_NOSURVEY },
1350 {"PASSAGE", STYLE_PASSAGE },
1351 {"TOPOFIL", STYLE_NORMAL },
1352 {NULL, STYLE_UNKNOWN }
1355 #define m_multi (BIT(Station) | BIT(Count) | BIT(Depth))
1357 int style, k = 0;
1358 reading d;
1359 unsigned long mUsed = 0;
1360 int old_style = pcs->style;
1362 /* after a bad *data command ignore survey data until the next
1363 * *data command to avoid an avalanche of errors */
1364 pcs->style = STYLE_IGNORE;
1366 get_token();
1367 style = match_tok(styletab, TABSIZE(styletab));
1369 if (style == STYLE_DEFAULT) {
1370 default_style(pcs);
1371 return;
1374 if (style == STYLE_UNKNOWN) {
1375 if (!buffer[0]) {
1376 /* "*data" reinitialises the current style - for *data passage that
1377 * breaks the passage.
1379 pcs->style = style = old_style;
1380 goto reinit_style;
1382 /* TRANSLATORS: e.g. trying to refer to an invalid FNORD data style */
1383 compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*Data style “%s” unknown*/65, buffer);
1384 return;
1387 skipblanks();
1388 #ifndef NO_DEPRECATED
1389 /* Olde syntax had optional field for survey grade, so allow an omit
1390 * but issue a warning about it */
1391 if (isOmit(ch)) {
1392 static int data_depr_count = 0;
1393 if (data_depr_count < 5) {
1394 compile_diagnostic(DIAG_WARN|DIAG_BUF, /*“*data %s %c …” is deprecated - use “*data %s …” instead*/104,
1395 buffer, ch, buffer);
1396 if (++data_depr_count == 5)
1397 compile_diagnostic(DIAG_WARN, /*Further uses of this deprecated feature will not be reported*/95);
1399 nextch();
1401 #endif
1403 int kMac = 6; /* minimum for NORMAL style */
1404 reading *new_order = osmalloc(kMac * sizeof(reading));
1405 char *style_name = osstrdup(buffer);
1406 do {
1407 filepos fp;
1408 get_pos(&fp);
1409 get_token();
1410 d = match_tok(dtab, TABSIZE(dtab));
1411 /* only token allowed after IGNOREALL is NEWLINE */
1412 if (k && new_order[k - 1] == IgnoreAll && d != Newline) {
1413 set_pos(&fp);
1414 break;
1416 /* Note: an unknown token is reported as trailing garbage */
1417 if (!TSTBIT(mask_all[style], d)) {
1418 /* TRANSLATORS: a data "style" is something like NORMAL, DIVING, etc.
1419 * a "reading" is one of FROM, TO, TAPE, COMPASS, CLINO for NORMAL
1420 * style. Neither "style" nor "reading" is a keyword in the program.
1422 * This error complains about a "DEPTH" gauge reading in "NORMAL"
1423 * style, for example.
1425 compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP,
1426 /*Reading “%s” not allowed in data style “%s”*/63,
1427 buffer, style_name);
1428 osfree(style_name);
1429 osfree(new_order);
1430 return;
1432 if (TSTBIT(mUsed, Newline) && TSTBIT(m_multi, d)) {
1433 /* TRANSLATORS: caused by e.g.
1435 * *data diving station newline depth tape compass
1437 * ("depth" needs to occur before "newline"). */
1438 compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP,
1439 /*Reading “%s” must occur before NEWLINE*/225, buffer);
1440 osfree(style_name);
1441 osfree(new_order);
1442 return;
1444 /* Check for duplicates unless it's a special reading:
1445 * IGNOREALL,IGNORE (duplicates allowed) ; END (not possible)
1447 if (!((BIT(Ignore) | BIT(End) | BIT(IgnoreAll)) & BIT(d))) {
1448 if (TSTBIT(mUsed, d)) {
1449 /* TRANSLATORS: complains about a situation like trying to define
1450 * two from stations per leg */
1451 compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*Duplicate reading “%s”*/67, buffer);
1452 osfree(style_name);
1453 osfree(new_order);
1454 return;
1455 } else {
1456 /* Check for previously listed readings which are incompatible
1457 * with this one - e.g. Count vs FrCount */
1458 bool fBad = fFalse;
1459 switch (d) {
1460 case Station:
1461 if (mUsed & (BIT(Fr) | BIT(To))) fBad = fTrue;
1462 break;
1463 case Fr: case To:
1464 if (TSTBIT(mUsed, Station)) fBad = fTrue;
1465 break;
1466 case Count:
1467 if (mUsed & (BIT(FrCount) | BIT(ToCount) | BIT(Tape)))
1468 fBad = fTrue;
1469 break;
1470 case FrCount: case ToCount:
1471 if (mUsed & (BIT(Count) | BIT(Tape)))
1472 fBad = fTrue;
1473 break;
1474 case Depth:
1475 if (mUsed & (BIT(FrDepth) | BIT(ToDepth) | BIT(DepthChange)))
1476 fBad = fTrue;
1477 break;
1478 case FrDepth: case ToDepth:
1479 if (mUsed & (BIT(Depth) | BIT(DepthChange))) fBad = fTrue;
1480 break;
1481 case DepthChange:
1482 if (mUsed & (BIT(FrDepth) | BIT(ToDepth) | BIT(Depth)))
1483 fBad = fTrue;
1484 break;
1485 case Newline:
1486 if (mUsed & ~m_multi) {
1487 /* TRANSLATORS: e.g.
1489 * *data normal from to tape newline compass clino */
1490 compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*NEWLINE can only be preceded by STATION, DEPTH, and COUNT*/226);
1491 osfree(style_name);
1492 osfree(new_order);
1493 return;
1495 if (k == 0) {
1496 /* TRANSLATORS: error from:
1498 * *data normal newline from to tape compass clino */
1499 compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*NEWLINE can’t be the first reading*/222);
1500 osfree(style_name);
1501 osfree(new_order);
1502 return;
1504 break;
1505 default: /* avoid compiler warnings about unhandled enums */
1506 break;
1508 if (fBad) {
1509 /* Not entirely happy with phrasing this... */
1510 /* TRANSLATORS: This is an error from the *DATA command. It
1511 * means that a reading (which will appear where %s is isn't
1512 * valid as the list of readings has already included the same
1513 * reading, or an equivalent one (e.g. you can't have both
1514 * DEPTH and DEPTHCHANGE together). */
1515 compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*Reading “%s” duplicates previous reading(s)*/77,
1516 buffer);
1517 osfree(style_name);
1518 osfree(new_order);
1519 return;
1521 mUsed |= BIT(d); /* used to catch duplicates */
1524 if (k && new_order[k - 1] == IgnoreAll) {
1525 SVX_ASSERT(d == Newline);
1526 k--;
1527 d = IgnoreAllAndNewLine;
1529 if (k >= kMac) {
1530 kMac = kMac * 2;
1531 new_order = osrealloc(new_order, kMac * sizeof(reading));
1533 new_order[k++] = d;
1534 } while (d != End);
1536 if (k >= 2 && new_order[k - 2] == Newline) {
1537 /* TRANSLATORS: error from:
1539 * *data normal from to tape compass clino newline */
1540 compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*NEWLINE can’t be the last reading*/223);
1541 osfree(style_name);
1542 osfree(new_order);
1543 return;
1546 if (style == STYLE_NOSURVEY) {
1547 if (TSTBIT(mUsed, Station)) {
1548 if (k >= kMac) {
1549 kMac = kMac * 2;
1550 new_order = osrealloc(new_order, kMac * sizeof(reading));
1552 new_order[k - 1] = Newline;
1553 new_order[k++] = End;
1555 } else if (style == STYLE_PASSAGE) {
1556 /* Station doesn't mean "multiline" for STYLE_PASSAGE. */
1557 } else if (!TSTBIT(mUsed, Newline) && (m_multi & mUsed)) {
1558 /* TRANSLATORS: Error given by something like:
1560 * *data normal station tape compass clino
1562 * ("station" signifies interleaved data). */
1563 compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*Interleaved readings, but no NEWLINE*/224);
1564 osfree(style_name);
1565 osfree(new_order);
1566 return;
1569 #if 0
1570 printf("mUsed = 0x%x\n", mUsed);
1571 #endif
1573 /* Check the supplied readings form a sufficient set. */
1574 if (style != STYLE_PASSAGE) {
1575 if ((mUsed & (BIT(Fr) | BIT(To))) == (BIT(Fr) | BIT(To)))
1576 mUsed |= BIT(Station);
1577 else if (TSTBIT(mUsed, Station))
1578 mUsed |= BIT(Fr) | BIT(To);
1581 if (mUsed & (BIT(Comp) | BIT(BackComp)))
1582 mUsed |= BIT(Comp) | BIT(BackComp);
1584 if (mUsed & (BIT(Clino) | BIT(BackClino)))
1585 mUsed |= BIT(Clino) | BIT(BackClino);
1587 if ((mUsed & (BIT(FrDepth) | BIT(ToDepth))) == (BIT(FrDepth) | BIT(ToDepth)))
1588 mUsed |= BIT(Depth) | BIT(DepthChange);
1589 else if (mUsed & (BIT(Depth) | BIT(DepthChange)))
1590 mUsed |= BIT(FrDepth) | BIT(ToDepth) | BIT(Depth) | BIT(DepthChange);
1592 if ((mUsed & (BIT(FrCount) | BIT(ToCount))) == (BIT(FrCount) | BIT(ToCount)))
1593 mUsed |= BIT(Count) | BIT(Tape) | BIT(BackTape);
1594 else if (mUsed & (BIT(Count) | BIT(Tape) | BIT(BackTape)))
1595 mUsed |= BIT(FrCount) | BIT(ToCount) | BIT(Count) | BIT(Tape) | BIT(BackTape);
1597 #if 0
1598 printf("mUsed = 0x%x, opt = 0x%x, mask = 0x%x\n", mUsed,
1599 mask_optional[style], mask[style]);
1600 #endif
1602 if (((mUsed &~ BIT(Newline)) | mask_optional[style]) != mask[style]) {
1603 /* Test should only fail with too few bits set, not too many */
1604 SVX_ASSERT((((mUsed &~ BIT(Newline)) | mask_optional[style])
1605 &~ mask[style]) == 0);
1606 /* TRANSLATORS: i.e. not enough readings for the style. */
1607 compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*Too few readings for data style “%s”*/64, style_name);
1608 osfree(style_name);
1609 osfree(new_order);
1610 return;
1613 /* don't free default ordering or ordering used by parent */
1614 if (pcs->ordering != default_order &&
1615 !(pcs->next && pcs->next->ordering == pcs->ordering))
1616 osfree((reading*)pcs->ordering);
1618 pcs->style = style;
1619 pcs->ordering = new_order;
1621 osfree(style_name);
1623 reinit_style:
1624 if (style == STYLE_PASSAGE) {
1625 lrudlist * new_psg = osnew(lrudlist);
1626 new_psg->tube = NULL;
1627 new_psg->next = model;
1628 model = new_psg;
1629 next_lrud = &(new_psg->tube);
1633 static void
1634 cmd_units(void)
1636 int units, quantity;
1637 unsigned long qmask;
1638 unsigned long m; /* mask with bit x set to indicate quantity x specified */
1639 real factor;
1640 filepos fp;
1642 qmask = get_qlist(BIT(Q_POS)|BIT(Q_PLUMB)|BIT(Q_LEVEL));
1644 if (!qmask) return;
1645 if (qmask == BIT(Q_DEFAULT)) {
1646 default_units(pcs);
1647 return;
1650 get_pos(&fp);
1651 factor = read_numeric(fTrue);
1652 if (factor == 0.0) {
1653 set_pos(&fp);
1654 /* TRANSLATORS: error message given by "*units tape 0 feet" - it’s
1655 * meaningless to say your tape is marked in "0 feet" (but you might
1656 * measure distance by counting knots on a diving line, and tie them
1657 * every "2 feet"). */
1658 compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /**UNITS factor must be non-zero*/200);
1659 skipline();
1660 return;
1663 units = get_units(qmask, fTrue);
1664 if (units == UNITS_NULL) return;
1665 if (TSTBIT(qmask, Q_GRADIENT))
1666 pcs->f_clino_percent = (units == UNITS_PERCENT);
1667 if (TSTBIT(qmask, Q_BACKGRADIENT))
1668 pcs->f_backclino_percent = (units == UNITS_PERCENT);
1670 if (TSTBIT(qmask, Q_BEARING)) {
1671 pcs->f_bearing_quadrants = (units == UNITS_QUADRANTS);
1673 if (TSTBIT(qmask, Q_BACKBEARING)) {
1674 pcs->f_backbearing_quadrants = (units == UNITS_QUADRANTS);
1677 if (factor == HUGE_REAL) {
1678 factor = factor_tab[units];
1679 } else {
1680 factor *= factor_tab[units];
1683 for (quantity = 0, m = BIT(quantity); m <= qmask; quantity++, m <<= 1)
1684 if (qmask & m) pcs->units[quantity] = factor;
1687 static void
1688 cmd_calibrate(void)
1690 real sc, z;
1691 unsigned long qmask, m;
1692 int quantity;
1693 filepos fp;
1695 qmask = get_qlist(BIT(Q_POS)|BIT(Q_PLUMB)|BIT(Q_LEVEL));
1696 if (!qmask) return; /* error already reported */
1698 if (qmask == BIT(Q_DEFAULT)) {
1699 default_calib(pcs);
1700 return;
1703 if (((qmask & LEN_QMASK)) && ((qmask & ANG_QMASK))) {
1704 /* TRANSLATORS: e.g.
1706 * *calibrate tape compass 1 1
1708 compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*Can’t calibrate angular and length quantities together*/227);
1709 return;
1712 z = read_numeric(fFalse);
1713 get_pos(&fp);
1714 sc = read_numeric(fTrue);
1715 if (sc == HUGE_REAL) {
1716 if (isalpha(ch)) {
1717 int units = get_units(qmask, fFalse);
1718 if (units == UNITS_NULL) {
1719 return;
1721 z *= factor_tab[units];
1722 sc = read_numeric(fTrue);
1723 if (sc == HUGE_REAL) {
1724 sc = (real)1.0;
1725 } else {
1726 /* Adjustment applied is: (reading - z) * sc
1727 * We want: reading * sc - z
1728 * So divide z by sc so the applied adjustment does what we want.
1730 z /= sc;
1732 } else {
1733 sc = (real)1.0;
1737 if (sc == HUGE_REAL) sc = (real)1.0;
1738 /* check for declination scale */
1739 if (TSTBIT(qmask, Q_DECLINATION) && sc != 1.0) {
1740 set_pos(&fp);
1741 /* TRANSLATORS: DECLINATION is a built-in keyword, so best not to
1742 * translate */
1743 compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Scale factor must be 1.0 for DECLINATION*/40);
1744 skipline();
1745 return;
1747 if (sc == 0.0) {
1748 set_pos(&fp);
1749 /* TRANSLATORS: If the scale factor for an instrument is zero, then any
1750 * reading would be mapped to zero, which doesn't make sense. */
1751 compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Scale factor must be non-zero*/391);
1752 skipline();
1753 return;
1755 for (quantity = 0, m = BIT(quantity); m <= qmask; quantity++, m <<= 1) {
1756 if (qmask & m) {
1757 pcs->z[quantity] = pcs->units[quantity] * z;
1758 pcs->sc[quantity] = sc;
1763 static void
1764 cmd_declination(void)
1766 real v = read_numeric(fTrue);
1767 if (v == HUGE_REAL) {
1768 get_token_no_blanks();
1769 if (strcmp(ucbuffer, "AUTO") != 0) {
1770 compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_COL, /*Expected number or “AUTO”*/309);
1771 return;
1773 /* *declination auto X Y Z */
1774 real x = read_numeric(fFalse);
1775 real y = read_numeric(fFalse);
1776 real z = read_numeric(fFalse);
1777 if (!pcs->proj_str) {
1778 compile_diagnostic(DIAG_ERR, /*Input coordinate system must be specified for “*DECLINATION AUTO”*/301);
1779 return;
1781 /* Convert to WGS84 lat long. */
1782 PJ *transform = proj_create_crs_to_crs(PJ_DEFAULT_CTX,
1783 pcs->proj_str,
1784 WGS84_DATUM_STRING,
1785 NULL);
1786 if (transform) {
1787 /* Normalise the output order so x is longitude and y latitude - by
1788 * default new PROJ has them switched for EPSG:4326 which just seems
1789 * confusing.
1791 PJ* pj_norm = proj_normalize_for_visualization(PJ_DEFAULT_CTX,
1792 transform);
1793 proj_destroy(transform);
1794 transform = pj_norm;
1797 if (proj_angular_input(transform, PJ_FWD)) {
1798 /* Input coordinate system expects radians. */
1799 x = rad(x);
1800 y = rad(y);
1803 PJ_COORD coord = {{x, y, z, HUGE_VAL}};
1804 coord = proj_trans(transform, PJ_FWD, coord);
1805 x = coord.xyzt.x;
1806 y = coord.xyzt.y;
1807 z = coord.xyzt.z;
1809 if (x == HUGE_VAL || y == HUGE_VAL || z == HUGE_VAL) {
1810 compile_diagnostic(DIAG_ERR, /*Failed to convert coordinates: %s*/436,
1811 proj_errno_string(proj_errno(transform)));
1812 /* Set dummy values which are finite. */
1813 x = y = z = 0;
1815 proj_destroy(transform);
1817 report_declination(pcs);
1819 double lon = rad(x);
1820 double lat = rad(y);
1821 pcs->z[Q_DECLINATION] = HUGE_REAL;
1822 pcs->dec_lat = lat;
1823 pcs->dec_lon = lon;
1824 pcs->dec_alt = z;
1825 pcs->dec_filename = file.filename;
1826 pcs->dec_line = file.line;
1827 pcs->dec_context = grab_line();
1828 /* Invalidate cached declination. */
1829 pcs->declination = HUGE_REAL;
1831 // PJ_DEFAULT_CTX is really just NULL, but PROJ < 8.1.0
1832 // dereferences the context without a NULL check inside
1833 // proj_create_ellipsoidal_2D_cs() so create a context
1834 // temporarily to avoid a segmentation fault.
1835 PJ_CONTEXT * ctx = PJ_DEFAULT_CTX;
1836 #if PROJ_VERSION_MAJOR < 8 || \
1837 (PROJ_VERSION_MAJOR == 8 && PROJ_VERSION_MINOR < 1)
1838 ctx = proj_context_create();
1839 #endif
1841 PJ *pj = proj_create(ctx, proj_str_out);
1842 PJ_COORD lp;
1843 lp.lp.lam = lon;
1844 lp.lp.phi = lat;
1845 #if PROJ_VERSION_MAJOR < 8 || \
1846 (PROJ_VERSION_MAJOR == 8 && PROJ_VERSION_MINOR < 2)
1847 /* Code adapted from fix in PROJ 8.2.0 to make proj_factors() work in
1848 * cases we need (e.g. a CRS specified as "EPSG:<number>").
1850 switch (proj_get_type(pj)) {
1851 case PJ_TYPE_PROJECTED_CRS: {
1852 /* If it is a projected CRS, then compute the factors on the conversion
1853 * associated to it. We need to start from a temporary geographic CRS
1854 * using the same datum as the one of the projected CRS, and with
1855 * input coordinates being in longitude, latitude order in radian,
1856 * to be consistent with the expectations of the lp input parameter.
1859 PJ * geodetic_crs = proj_get_source_crs(ctx, pj);
1860 if (!geodetic_crs)
1861 break;
1862 PJ * datum = proj_crs_get_datum(ctx, geodetic_crs);
1863 #if PROJ_VERSION_MAJOR == 8 || \
1864 (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
1865 /* PROJ 7.2.0 upgraded to EPSG 10.x which added the concept
1866 * of a datum ensemble, and this version of PROJ also added
1867 * an API to deal with these.
1869 * If we're using PROJ < 7.2.0 then its EPSG database won't
1870 * have datum ensembles, so we don't need any code to handle
1871 * them.
1873 if (!datum) {
1874 datum = proj_crs_get_datum_ensemble(ctx, geodetic_crs);
1876 #endif
1877 PJ * cs = proj_create_ellipsoidal_2D_cs(
1878 ctx, PJ_ELLPS2D_LONGITUDE_LATITUDE, "Radian", 1.0);
1879 PJ * temp = proj_create_geographic_crs_from_datum(
1880 ctx, "unnamed crs", datum, cs);
1881 proj_destroy(datum);
1882 proj_destroy(cs);
1883 proj_destroy(geodetic_crs);
1884 PJ * newOp = proj_create_crs_to_crs_from_pj(ctx, temp, pj, NULL, NULL);
1885 proj_destroy(temp);
1886 if (newOp) {
1887 proj_destroy(pj);
1888 pj = newOp;
1890 break;
1892 default:
1893 break;
1895 #endif
1896 PJ_FACTORS factors = proj_factors(pj, lp);
1897 pcs->convergence = factors.meridian_convergence;
1898 proj_destroy(pj);
1899 #if PROJ_VERSION_MAJOR < 8 || \
1900 (PROJ_VERSION_MAJOR == 8 && PROJ_VERSION_MINOR < 1)
1901 proj_context_destroy(ctx);
1902 #endif
1904 } else {
1905 /* *declination D UNITS */
1906 int units = get_units(BIT(Q_DECLINATION), fFalse);
1907 if (units == UNITS_NULL) {
1908 return;
1910 pcs->z[Q_DECLINATION] = -v * factor_tab[units];
1911 pcs->convergence = 0;
1915 #ifndef NO_DEPRECATED
1916 static void
1917 cmd_default(void)
1919 static const sztok defaulttab[] = {
1920 { "CALIBRATE", CMD_CALIBRATE },
1921 { "DATA", CMD_DATA },
1922 { "UNITS", CMD_UNITS },
1923 { NULL, CMD_NULL }
1925 static int default_depr_count = 0;
1927 if (default_depr_count < 5) {
1928 /* TRANSLATORS: If you're unsure what "deprecated" means, see:
1929 * https://en.wikipedia.org/wiki/Deprecation */
1930 compile_diagnostic(DIAG_WARN|DIAG_COL, /**DEFAULT is deprecated - use *CALIBRATE/DATA/SD/UNITS with argument DEFAULT instead*/20);
1931 if (++default_depr_count == 5)
1932 compile_diagnostic(DIAG_WARN, /*Further uses of this deprecated feature will not be reported*/95);
1935 get_token();
1936 switch (match_tok(defaulttab, TABSIZE(defaulttab))) {
1937 case CMD_CALIBRATE:
1938 default_calib(pcs);
1939 break;
1940 case CMD_DATA:
1941 default_style(pcs);
1942 default_grade(pcs);
1943 break;
1944 case CMD_UNITS:
1945 default_units(pcs);
1946 break;
1947 default:
1948 compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*Unknown setting “%s”*/41, buffer);
1951 #endif
1953 static void
1954 cmd_include(void)
1956 char *pth, *fnm = NULL;
1957 int fnm_len;
1958 #ifndef NO_DEPRECATED
1959 prefix *root_store;
1960 #endif
1961 int ch_store;
1963 pth = path_from_fnm(file.filename);
1965 read_string(&fnm, &fnm_len);
1967 #ifndef NO_DEPRECATED
1968 /* Since *begin / *end nesting cannot cross file boundaries we only
1969 * need to preserve the prefix if the deprecated *prefix command
1970 * can be used */
1971 root_store = root;
1972 root = pcs->Prefix; /* Root for include file is current prefix */
1973 #endif
1974 ch_store = ch;
1976 data_file(pth, fnm);
1978 #ifndef NO_DEPRECATED
1979 root = root_store; /* and restore root */
1980 #endif
1981 ch = ch_store;
1983 s_free(&fnm);
1984 osfree(pth);
1987 static void
1988 cmd_sd(void)
1990 real sd, variance;
1991 int units;
1992 unsigned long qmask, m;
1993 int quantity;
1994 qmask = get_qlist(BIT(Q_DECLINATION));
1995 if (!qmask) return; /* no quantities found - error already reported */
1997 if (qmask == BIT(Q_DEFAULT)) {
1998 default_grade(pcs);
1999 return;
2001 sd = read_numeric(fFalse);
2002 if (sd <= (real)0.0) {
2003 compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_COL, /*Standard deviation must be positive*/48);
2004 return;
2006 units = get_units(qmask, fFalse);
2007 if (units == UNITS_NULL) return;
2009 sd *= factor_tab[units];
2010 variance = sqrd(sd);
2012 for (quantity = 0, m = BIT(quantity); m <= qmask; quantity++, m <<= 1)
2013 if (qmask & m) pcs->Var[quantity] = variance;
2016 static void
2017 cmd_title(void)
2019 if (!fExplicitTitle && pcs->Prefix == root) {
2020 /* If we don't have an explicit title yet, and we're currently in the
2021 * root prefix, use this title explicitly. */
2022 fExplicitTitle = fTrue;
2023 read_string(&survey_title, &survey_title_len);
2024 } else {
2025 /* parse and throw away this title (but still check rest of line) */
2026 char *s = NULL;
2027 int len;
2028 read_string(&s, &len);
2029 s_free(&s);
2033 static const sztok case_tab[] = {
2034 {"PRESERVE", OFF},
2035 {"TOLOWER", LOWER},
2036 {"TOUPPER", UPPER},
2037 {NULL, -1}
2040 static void
2041 cmd_case(void)
2043 int setting;
2044 get_token();
2045 setting = match_tok(case_tab, TABSIZE(case_tab));
2046 if (setting != -1) {
2047 pcs->Case = setting;
2048 } else {
2049 compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*Found “%s”, expecting “PRESERVE”, “TOUPPER”, or “TOLOWER”*/10, buffer);
2053 typedef enum {
2054 CS_NONE = -1,
2055 CS_CUSTOM,
2056 CS_EPSG,
2057 CS_ESRI,
2058 CS_EUR,
2059 CS_IJTSK,
2060 CS_JTSK,
2061 CS_LAT,
2062 CS_LOCAL,
2063 CS_LONG,
2064 CS_OSGB,
2065 CS_S_MERC,
2066 CS_UTM
2067 } cs_class;
2069 static const sztok cs_tab[] = {
2070 {"CUSTOM", CS_CUSTOM},
2071 {"EPSG", CS_EPSG}, /* EPSG:<number> */
2072 {"ESRI", CS_ESRI}, /* ESRI:<number> */
2073 {"EUR", CS_EUR}, /* EUR79Z30 */
2074 {"IJTSK", CS_IJTSK}, /* IJTSK or IJTSK03 */
2075 {"JTSK", CS_JTSK}, /* JTSK or JTSK03 */
2076 {"LAT", CS_LAT}, /* LAT-LONG */
2077 {"LOCAL", CS_LOCAL},
2078 {"LONG", CS_LONG}, /* LONG-LAT */
2079 {"OSGB", CS_OSGB}, /* OSGB:<H, N, O, S or T><A-Z except I> */
2080 {"S", CS_S_MERC}, /* S-MERC */
2081 {"UTM", CS_UTM}, /* UTM<zone><N or S or nothing> */
2082 {NULL, CS_NONE}
2085 static void
2086 cmd_cs(void)
2088 char * proj_str = NULL;
2089 int proj_str_len;
2090 cs_class cs;
2091 int cs_sub = INT_MIN;
2092 filepos fp;
2093 bool output = fFalse;
2094 enum { YES, NO, MAYBE } ok_for_output = YES;
2095 static bool had_cs = fFalse;
2097 if (!had_cs) {
2098 had_cs = fTrue;
2099 if (first_fix_name) {
2100 compile_diagnostic_at(DIAG_ERR,
2101 first_fix_filename, first_fix_line,
2102 /*Station “%s” fixed before CS command first used*/442,
2103 sprint_prefix(first_fix_name));
2107 get_pos(&fp);
2108 /* Note get_token() only accepts letters - it'll stop at digits so "UTM12"
2109 * will give token "UTM". */
2110 get_token();
2111 if (strcmp(ucbuffer, "OUT") == 0) {
2112 output = fTrue;
2113 get_pos(&fp);
2114 get_token();
2116 cs = match_tok(cs_tab, TABSIZE(cs_tab));
2117 switch (cs) {
2118 case CS_NONE:
2119 break;
2120 case CS_CUSTOM:
2121 ok_for_output = MAYBE;
2122 get_pos(&fp);
2123 read_string(&proj_str, &proj_str_len);
2124 cs_sub = 0;
2125 break;
2126 case CS_EPSG: case CS_ESRI:
2127 ok_for_output = MAYBE;
2128 if (ch == ':' && isdigit(nextch())) {
2129 unsigned n = read_uint();
2130 if (n < 1000000) {
2131 cs_sub = (int)n;
2134 break;
2135 case CS_EUR:
2136 if (isdigit(ch) &&
2137 read_uint() == 79 &&
2138 (ch == 'Z' || ch == 'z') &&
2139 isdigit(nextch()) &&
2140 read_uint() == 30) {
2141 cs_sub = 7930;
2143 break;
2144 case CS_JTSK:
2145 ok_for_output = NO;
2146 /* FALLTHRU */
2147 case CS_IJTSK:
2148 if (ch == '0') {
2149 if (nextch() == '3') {
2150 nextch();
2151 cs_sub = 3;
2153 } else {
2154 cs_sub = 0;
2156 break;
2157 case CS_LAT: case CS_LONG:
2158 ok_for_output = NO;
2159 if (ch == '-') {
2160 nextch();
2161 get_token_no_blanks();
2162 cs_class cs2 = match_tok(cs_tab, TABSIZE(cs_tab));
2163 if ((cs ^ cs2) == (CS_LAT ^ CS_LONG)) {
2164 cs_sub = 0;
2167 break;
2168 case CS_LOCAL:
2169 cs_sub = 0;
2170 break;
2171 case CS_OSGB:
2172 if (ch == ':') {
2173 int uch1 = toupper(nextch());
2174 if (strchr("HNOST", uch1)) {
2175 int uch2 = toupper(nextch());
2176 if (uch2 >= 'A' && uch2 <= 'Z' && uch2 != 'I') {
2177 int x, y;
2178 nextch();
2179 if (uch1 > 'I') --uch1;
2180 uch1 -= 'A';
2181 if (uch2 > 'I') --uch2;
2182 uch2 -= 'A';
2183 x = uch1 % 5;
2184 y = uch1 / 5;
2185 x = (x * 5) + uch2 % 5;
2186 y = (y * 5) + uch2 / 5;
2187 cs_sub = y * 25 + x;
2191 break;
2192 case CS_S_MERC:
2193 if (ch == '-') {
2194 nextch();
2195 get_token_no_blanks();
2196 if (strcmp(ucbuffer, "MERC") == 0) {
2197 cs_sub = 0;
2200 break;
2201 case CS_UTM:
2202 if (isdigit(ch)) {
2203 unsigned n = read_uint();
2204 if (n >= 1 && n <= 60) {
2205 int uch = toupper(ch);
2206 cs_sub = (int)n;
2207 if (uch == 'S') {
2208 nextch();
2209 cs_sub = -cs_sub;
2210 } else if (uch == 'N') {
2211 nextch();
2215 break;
2217 if (cs_sub == INT_MIN || isalnum(ch)) {
2218 set_pos(&fp);
2219 compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Unknown coordinate system*/434);
2220 skipline();
2221 return;
2223 /* Actually handle the cs */
2224 switch (cs) {
2225 case CS_NONE:
2226 break;
2227 case CS_CUSTOM:
2228 /* proj_str already set */
2229 break;
2230 case CS_EPSG:
2231 proj_str = osmalloc(32);
2232 sprintf(proj_str, "EPSG:%d", cs_sub);
2233 break;
2234 case CS_ESRI:
2235 proj_str = osmalloc(32);
2236 sprintf(proj_str, "ESRI:%d", cs_sub);
2237 break;
2238 case CS_EUR:
2239 proj_str = osstrdup("+proj=utm +zone=30 +ellps=intl +towgs84=-86,-98,-119,0,0,0,0 +no_defs");
2240 break;
2241 case CS_IJTSK:
2242 if (cs_sub == 0)
2243 proj_str = osstrdup("+proj=krovak +ellps=bessel +towgs84=570.8285,85.6769,462.842,4.9984,1.5867,5.2611,3.5623 +no_defs");
2244 else
2245 proj_str = osstrdup("+proj=krovak +ellps=bessel +towgs84=485.021,169.465,483.839,7.786342,4.397554,4.102655,0 +no_defs");
2246 break;
2247 case CS_JTSK:
2248 if (cs_sub == 0)
2249 proj_str = osstrdup("+proj=krovak +czech +ellps=bessel +towgs84=570.8285,85.6769,462.842,4.9984,1.5867,5.2611,3.5623 +no_defs");
2250 else
2251 proj_str = osstrdup("+proj=krovak +czech +ellps=bessel +towgs84=485.021,169.465,483.839,7.786342,4.397554,4.102655,0 +no_defs");
2252 break;
2253 case CS_LAT:
2254 /* FIXME: Requires PROJ >= 4.8.0 for +axis, and the SDs will be
2255 * misapplied, so we may want to swap ourselves. Also, while
2256 * therion supports lat-long, I'm not totally convinced that it is
2257 * sensible to do so - people often say "lat-long", but probably
2258 * don't think that that's actually "Northing, Easting". This
2259 * seems like it'll result in people accidentally getting X and Y
2260 * swapped in their fixed points...
2262 #if 0
2263 proj_str = osstrdup("+proj=longlat +ellps=WGS84 +datum=WGS84 +axis=neu +no_defs");
2264 #endif
2265 break;
2266 case CS_LOCAL:
2267 /* FIXME: Is it useful to be able to explicitly specify this? */
2268 break;
2269 case CS_LONG:
2270 proj_str = osstrdup("EPSG:4326");
2271 break;
2272 case CS_OSGB: {
2273 int x = 14 - (cs_sub % 25);
2274 int y = (cs_sub / 25) - 20;
2275 proj_str = osmalloc(160);
2276 sprintf(proj_str, "+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=%d +y_0=%d +ellps=airy +datum=OSGB36 +units=m +no_defs", x * 100000, y * 100000);
2277 break;
2279 case CS_S_MERC:
2280 proj_str = osstrdup("EPSG:3857");
2281 break;
2282 case CS_UTM:
2283 proj_str = osmalloc(32);
2284 if (cs_sub > 0) {
2285 sprintf(proj_str, "EPSG:%d", 32600 + cs_sub);
2286 } else {
2287 sprintf(proj_str, "EPSG:%d", 32700 - cs_sub);
2289 break;
2292 if (!proj_str) {
2293 /* printf("CS %d:%d\n", (int)cs, cs_sub); */
2294 set_pos(&fp);
2295 compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Unknown coordinate system*/434);
2296 skipline();
2297 return;
2300 if (output) {
2301 if (ok_for_output == NO) {
2302 set_pos(&fp);
2303 compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Coordinate system unsuitable for output*/435);
2304 skipline();
2305 return;
2308 if (proj_str_out && strcmp(proj_str, proj_str_out) == 0) {
2309 /* Same as the output cs that's already set, so nothing to do. */
2310 osfree(proj_str);
2311 return;
2314 if (ok_for_output == MAYBE) {
2315 /* We only actually create the transformation from input to output when
2316 * we need it, but for a custom proj string or EPSG/ESRI code we need
2317 * to check that the specified coordinate system is valid and also if
2318 * it's suitable for output so we need to test creating it here.
2320 PJ* pj = proj_create(PJ_DEFAULT_CTX, proj_str);
2321 if (!pj) {
2322 set_pos(&fp);
2323 compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Invalid coordinate system: %s*/443,
2324 proj_errno_string(proj_context_errno(PJ_DEFAULT_CTX)));
2325 skipline();
2326 osfree(proj_str);
2327 return;
2329 int type = proj_get_type(pj);
2330 if (type == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
2331 type == PJ_TYPE_GEOGRAPHIC_3D_CRS) {
2332 set_pos(&fp);
2333 compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Coordinate system unsuitable for output*/435);
2334 skipline();
2335 osfree(proj_str);
2336 return;
2340 if (proj_str_out) {
2341 /* If the output cs is already set, subsequent attempts to set it
2342 * are silently ignored (so you can combine two datasets and set
2343 * the output cs to use before you include either).
2345 osfree(proj_str);
2346 } else {
2347 proj_str_out = proj_str;
2349 } else {
2350 if (proj_str_out && strcmp(proj_str, proj_str_out) == 0) {
2351 /* Same as the current output projection, so valid for input. */
2352 } else if (pcs->proj_str && strcmp(proj_str, pcs->proj_str) == 0) {
2353 /* Same as the current input projection, so nothing to do! */
2354 return;
2355 } else if (ok_for_output == MAYBE) {
2356 /* (ok_for_output == MAYBE) also happens to indicate whether we need
2357 * to check that the coordinate system is valid for input.
2359 PJ* pj = proj_create(PJ_DEFAULT_CTX, proj_str);
2360 if (!pj) {
2361 set_pos(&fp);
2362 compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Invalid coordinate system: %s*/443,
2363 proj_errno_string(proj_context_errno(PJ_DEFAULT_CTX)));
2364 skipline();
2365 return;
2367 proj_destroy(pj);
2370 /* Free current input proj_str if not used by parent. */
2371 settings * p = pcs;
2372 if (!p->next || p->proj_str != p->next->proj_str)
2373 osfree(p->proj_str);
2374 p->proj_str = proj_str;
2375 invalidate_pj_cached();
2379 static const sztok infer_tab[] = {
2380 { "EQUATES", INFER_EQUATES },
2381 { "EXPORTS", INFER_EXPORTS },
2382 { "PLUMBS", INFER_PLUMBS },
2383 #if 0 /* FIXME */
2384 { "SUBSURVEYS", INFER_SUBSURVEYS },
2385 #endif
2386 { NULL, INFER_NULL }
2389 static const sztok onoff_tab[] = {
2390 { "OFF", 0 },
2391 { "ON", 1 },
2392 { NULL, -1 }
2395 static void
2396 cmd_infer(void)
2398 infer_what setting;
2399 int on;
2400 get_token();
2401 setting = match_tok(infer_tab, TABSIZE(infer_tab));
2402 if (setting == INFER_NULL) {
2403 compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*Found “%s”, expecting “EQUATES”, “EXPORTS”, or “PLUMBS”*/31, buffer);
2404 return;
2406 get_token();
2407 on = match_tok(onoff_tab, TABSIZE(onoff_tab));
2408 if (on == -1) {
2409 compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*Found “%s”, expecting “ON” or “OFF”*/32, buffer);
2410 return;
2413 if (on) {
2414 pcs->infer |= BIT(setting);
2415 if (setting == INFER_EXPORTS) fExportUsed = fTrue;
2416 } else {
2417 pcs->infer &= ~BIT(setting);
2421 static void
2422 cmd_truncate(void)
2424 unsigned int truncate_at = 0; /* default is no truncation */
2425 filepos fp;
2427 get_pos(&fp);
2429 get_token();
2430 if (strcmp(ucbuffer, "OFF") != 0) {
2431 if (*ucbuffer) set_pos(&fp);
2432 truncate_at = read_uint();
2434 /* for backward compatibility, "*truncate 0" means "*truncate off" */
2435 pcs->Truncate = (truncate_at == 0) ? INT_MAX : truncate_at;
2438 static void
2439 cmd_ref(void)
2441 /* Just syntax check for now. */
2442 char *ref = NULL;
2443 int ref_len;
2444 read_string(&ref, &ref_len);
2445 s_free(&ref);
2448 static void
2449 cmd_require(void)
2451 const unsigned int version[] = {COMMAVERSION};
2452 const unsigned int *ver = version;
2453 filepos fp;
2455 skipblanks();
2456 get_pos(&fp);
2457 while (1) {
2458 int diff = *ver++ - read_uint();
2459 if (diff > 0) break;
2460 if (diff < 0) {
2461 size_t i, len;
2462 char *v;
2463 filepos fp_tmp;
2465 /* find end of version number */
2466 while (isdigit(ch) || ch == '.') nextch();
2467 get_pos(&fp_tmp);
2468 len = (size_t)(fp_tmp.offset - fp.offset);
2469 v = osmalloc(len + 1);
2470 set_pos(&fp);
2471 for (i = 0; i < len; i++) {
2472 v[i] = ch;
2473 nextch();
2475 v[i] = '\0';
2476 /* TRANSLATORS: Feel free to translate as "or newer" instead of "or
2477 * greater" if that gives a more natural translation. It's
2478 * technically not quite right when there are parallel active release
2479 * series (e.g. Survex 1.0.40 was released *after* 1.2.0), but this
2480 * seems unlikely to confuse users. "Survex" is the name of the
2481 * software, so should not be translated.
2483 * Here "survey" is a "cave map" rather than list of questions - it should be
2484 * translated to the terminology that cavers using the language would use.
2486 fatalerror_in_file(file.filename, file.line, /*Survex version %s or greater required to process this survey data.*/2, v);
2488 if (ch != '.') break;
2489 nextch();
2490 if (!isdigit(ch) || ver == version + sizeof(version) / sizeof(*version))
2491 break;
2493 /* skip rest of version number */
2494 while (isdigit(ch) || ch == '.') nextch();
2497 /* allocate new meta_data if need be */
2498 void
2499 copy_on_write_meta(settings *s)
2501 if (!s->meta || s->meta->ref_count != 0) {
2502 meta_data * meta_new = osnew(meta_data);
2503 if (!s->meta) {
2504 meta_new->days1 = meta_new->days2 = -1;
2505 } else {
2506 *meta_new = *(s->meta);
2508 meta_new->ref_count = 0;
2509 s->meta = meta_new;
2513 static void
2514 cmd_date(void)
2516 int year, month, day;
2517 int days1, days2;
2518 bool implicit_range = fFalse;
2519 filepos fp, fp2;
2521 get_pos(&fp);
2522 read_date(&year, &month, &day);
2523 days1 = days_since_1900(year, month ? month : 1, day ? day : 1);
2525 if (days1 > current_days_since_1900) {
2526 set_pos(&fp);
2527 compile_diagnostic(DIAG_WARN|DIAG_DATE, /*Date is in the future!*/80);
2530 skipblanks();
2531 if (ch == '-') {
2532 nextch();
2533 get_pos(&fp2);
2534 read_date(&year, &month, &day);
2535 } else {
2536 if (month && day) {
2537 days2 = days1;
2538 goto read;
2540 implicit_range = fTrue;
2543 if (month == 0) month = 12;
2544 if (day == 0) day = last_day(year, month);
2545 days2 = days_since_1900(year, month, day);
2547 if (!implicit_range && days2 > current_days_since_1900) {
2548 set_pos(&fp2);
2549 compile_diagnostic(DIAG_WARN|DIAG_DATE, /*Date is in the future!*/80);
2552 if (days2 < days1) {
2553 set_pos(&fp);
2554 compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*End of date range is before the start*/81);
2555 int tmp = days1;
2556 days1 = days2;
2557 days2 = tmp;
2560 read:
2561 if (!pcs->meta || pcs->meta->days1 != days1 || pcs->meta->days2 != days2) {
2562 copy_on_write_meta(pcs);
2563 pcs->meta->days1 = days1;
2564 pcs->meta->days2 = days2;
2565 /* Invalidate cached declination. */
2566 pcs->declination = HUGE_REAL;
2570 typedef void (*cmd_fn)(void);
2572 static const cmd_fn cmd_funcs[] = {
2573 cmd_alias,
2574 cmd_begin,
2575 cmd_calibrate,
2576 cmd_case,
2577 skipline, /*cmd_copyright,*/
2578 cmd_cs,
2579 cmd_data,
2580 cmd_date,
2581 cmd_declination,
2582 #ifndef NO_DEPRECATED
2583 cmd_default,
2584 #endif
2585 cmd_end,
2586 cmd_entrance,
2587 cmd_equate,
2588 cmd_export,
2589 cmd_fix,
2590 cmd_flags,
2591 cmd_include,
2592 cmd_infer,
2593 skipline, /*cmd_instrument,*/
2594 #ifndef NO_DEPRECATED
2595 cmd_prefix,
2596 #endif
2597 cmd_ref,
2598 cmd_require,
2599 cmd_sd,
2600 cmd_set,
2601 solve_network,
2602 skipline, /*cmd_team,*/
2603 cmd_title,
2604 cmd_truncate,
2605 cmd_units
2608 extern void
2609 handle_command(void)
2611 int cmdtok;
2612 get_token();
2613 cmdtok = match_tok(cmd_tab, TABSIZE(cmd_tab));
2615 if (cmdtok < 0 || cmdtok >= (int)(sizeof(cmd_funcs) / sizeof(cmd_fn))) {
2616 compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*Unknown command “%s”*/12, buffer);
2617 return;
2620 switch (cmdtok) {
2621 case CMD_EXPORT:
2622 if (!f_export_ok)
2623 /* TRANSLATORS: The *EXPORT command is only valid just after *BEGIN
2624 * <SURVEY>, so this would generate this error:
2626 * *begin fred
2627 * 1 2 1.23 045 -6
2628 * *export 2
2629 * *end fred */
2630 compile_diagnostic(DIAG_ERR, /**EXPORT must immediately follow “*BEGIN <SURVEY>”*/57);
2631 break;
2632 case CMD_ALIAS:
2633 case CMD_CALIBRATE:
2634 case CMD_CASE:
2635 case CMD_COPYRIGHT:
2636 case CMD_CS:
2637 case CMD_DATA:
2638 case CMD_DATE:
2639 case CMD_DECLINATION:
2640 case CMD_DEFAULT:
2641 case CMD_FLAGS:
2642 case CMD_INFER:
2643 case CMD_INSTRUMENT:
2644 case CMD_REF:
2645 case CMD_REQUIRE:
2646 case CMD_SD:
2647 case CMD_SET:
2648 case CMD_TEAM:
2649 case CMD_TITLE:
2650 case CMD_TRUNCATE:
2651 case CMD_UNITS:
2652 /* These can occur between *begin and *export */
2653 break;
2654 default:
2655 /* NB: additional handling for "*begin <survey>" in cmd_begin */
2656 f_export_ok = fFalse;
2657 break;
2660 cmd_funcs[cmdtok]();