img: Return -1 for missing Compass DAT LRUD readings
[survex.git] / src / img.c
blobcb556b90162ad2c6eedb3a93fc19fac9c0246601
1 /* img.c
2 * Routines for reading and writing processed survey data files
4 * Copyright (C) 1993-2024 Olly Betts
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
25 #include <ctype.h>
26 #include <errno.h>
27 #include <limits.h>
28 #include <locale.h>
29 #include <stddef.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <time.h>
35 #include "img.h"
37 #define TIMENA "?"
38 #ifdef IMG_HOSTED
39 # define INT32_T int32_t
40 # define UINT32_T uint32_t
41 # include "debug.h"
42 # include "filelist.h"
43 # include "filename.h"
44 # include "message.h"
45 # include "useful.h"
46 # define TIMEFMT msg(/*%a,%Y.%m.%d %H:%M:%S %Z*/107)
47 #else
48 # if defined HAVE_STDINT_H || \
49 (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) || \
50 (defined __cplusplus && __cplusplus >= 201103L)
51 # include <stdint.h>
52 # define INT32_T int32_t
53 # define UINT32_T uint32_t
54 # else
55 # include <limits.h>
56 # if INT_MAX >= 2147483647
57 # define INT32_T int
58 # define UINT32_T unsigned
59 # else
60 # define INT32_T long
61 # define UINT32_T unsigned long
62 # endif
63 # endif
64 # define TIMEFMT "%a,%Y.%m.%d %H:%M:%S %Z"
65 # define EXT_SVX_3D "3d"
66 # define EXT_SVX_POS "pos"
67 # define FNM_SEP_EXT '.'
68 # define METRES_PER_FOOT 0.3048 /* exact value */
69 # define xosmalloc(L) malloc((L))
70 # define xosrealloc(L,S) realloc((L),(S))
71 # define osfree(P) free((P))
72 # define osnew(T) (T*)malloc(sizeof(T))
74 /* in IMG_HOSTED mode, this tests if a filename refers to a directory */
75 # define fDirectory(X) 0
76 /* open file FNM with mode MODE, maybe using path PTH and/or extension EXT */
77 /* path isn't used in img.c, but EXT is */
78 # define fopenWithPthAndExt(PTH,FNM,EXT,MODE,X) \
79 ((*(X) = NULL), fopen(FNM,MODE))
80 # ifndef PUTC
81 # define PUTC(C, FH) putc(C, FH)
82 # endif
83 # ifndef GETC
84 # define GETC(FH) getc(FH)
85 # endif
86 # define fputsnl(S, FH) (fputs((S), (FH)) == EOF ? EOF : putc('\n', (FH)))
87 # define SVX_ASSERT(X)
89 #ifdef __cplusplus
90 # include <algorithm>
91 using std::max;
92 using std::min;
93 #else
94 /* Return max/min of two numbers. */
95 /* May be defined already (e.g. by Borland C in stdlib.h) */
96 /* NB Bad news if X or Y has side-effects... */
97 # ifndef max
98 # define max(X, Y) ((X) > (Y) ? (X) : (Y))
99 # endif
100 # ifndef min
101 # define min(X, Y) ((X) < (Y) ? (X) : (Y))
102 # endif
103 #endif
105 static INT32_T
106 get32(FILE *fh)
108 UINT32_T w = GETC(fh);
109 w |= (UINT32_T)GETC(fh) << 8l;
110 w |= (UINT32_T)GETC(fh) << 16l;
111 w |= (UINT32_T)GETC(fh) << 24l;
112 return (INT32_T)w;
115 static void
116 put32(UINT32_T w, FILE *fh)
118 PUTC((char)(w), fh);
119 PUTC((char)(w >> 8l), fh);
120 PUTC((char)(w >> 16l), fh);
121 PUTC((char)(w >> 24l), fh);
124 static short
125 get16(FILE *fh)
127 UINT32_T w = GETC(fh);
128 w |= (UINT32_T)GETC(fh) << 8l;
129 return (short)w;
132 static void
133 put16(short word, FILE *fh)
135 unsigned short w = (unsigned short)word;
136 PUTC((char)(w), fh);
137 PUTC((char)(w >> 8l), fh);
140 static char *
141 baseleaf_from_fnm(const char *fnm)
143 const char *p;
144 const char *q;
145 char * res;
146 size_t len;
148 p = fnm;
149 q = strrchr(p, '/');
150 if (q) p = q + 1;
151 q = strrchr(p, '\\');
152 if (q) p = q + 1;
154 q = strrchr(p, FNM_SEP_EXT);
155 if (q) len = (const char *)q - p; else len = strlen(p);
157 res = (char *)xosmalloc(len + 1);
158 if (!res) return NULL;
159 memcpy(res, p, len);
160 res[len] = '\0';
161 return res;
163 #endif
165 static char * my_strdup(const char *str);
167 static time_t
168 mktime_with_tz(struct tm * tm, const char * tz)
170 time_t r;
171 char * old_tz = getenv("TZ");
172 #ifdef _MSC_VER
173 if (old_tz) {
174 old_tz = my_strdup(old_tz);
175 if (!old_tz)
176 return (time_t)-1;
178 if (_putenv_s("TZ", tz) != 0) {
179 osfree(old_tz);
180 return (time_t)-1;
182 #elif defined HAVE_SETENV
183 if (old_tz) {
184 old_tz = my_strdup(old_tz);
185 if (!old_tz)
186 return (time_t)-1;
188 if (setenv("TZ", tz, 1) < 0) {
189 osfree(old_tz);
190 return (time_t)-1;
192 #else
193 char * p;
194 if (old_tz) {
195 size_t len = strlen(old_tz) + 1;
196 p = (char *)xosmalloc(len + 3);
197 if (!p)
198 return (time_t)-1;
199 memcpy(p, "TZ=", 3);
200 memcpy(p + 3, tz, len);
201 old_tz = p;
203 p = (char *)xosmalloc(strlen(tz) + 4);
204 if (!p) {
205 osfree(old_tz);
206 return (time_t)-1;
208 memcpy(p, "TZ=", 3);
209 strcpy(p + 3, tz);
210 if (putenv(p) != 0) {
211 osfree(p);
212 osfree(old_tz);
213 return (time_t)-1;
215 #define CLEANUP() osfree(p)
216 #endif
217 tzset();
218 r = mktime(tm);
219 if (old_tz) {
220 #ifdef _MSC_VER
221 _putenv_s("TZ", old_tz);
222 #elif !defined HAVE_SETENV
223 putenv(old_tz);
224 #else
225 setenv("TZ", old_tz, 1);
226 #endif
227 osfree(old_tz);
228 } else {
229 #ifdef _MSC_VER
230 _putenv_s("TZ", "");
231 #elif !defined HAVE_UNSETENV
232 putenv((char*)"TZ");
233 #else
234 unsetenv("TZ");
235 #endif
237 #ifdef CLEANUP
238 CLEANUP();
239 #undef CLEANUP
240 #endif
241 return r;
244 static unsigned short
245 getu16(FILE *fh)
247 return (unsigned short)get16(fh);
250 #include <math.h>
252 #if !defined HAVE_LROUND && !defined HAVE_DECL_LROUND
253 /* The autoconf tests are not in use, but C99 and C++11 both added lround(),
254 * so set HAVE_LROUND and HAVE_DECL_LROUND conservatively based on the language
255 * standard version the compiler claims to support. */
256 # if (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) || \
257 (defined __cplusplus && __cplusplus >= 201103L)
258 # define HAVE_LROUND 1
259 # define HAVE_DECL_LROUND 1
260 # endif
261 #endif
263 #ifdef HAVE_LROUND
264 # if defined HAVE_DECL_LROUND && !HAVE_DECL_LROUND
265 /* On older systems, the prototype may be missing. */
266 extern long lround(double);
267 # endif
268 # define my_lround lround
269 #else
270 static long
271 my_lround(double x) {
272 return (x >= 0.0) ? (long)(x + 0.5) : -(long)(0.5 - x);
274 #endif
276 /* portable case insensitive string compare */
277 #if defined(strcasecmp) || defined(HAVE_STRCASECMP)
278 # define my_strcasecmp strcasecmp
279 #else
280 static int my_strcasecmp(const char *s1, const char *s2) {
281 unsigned char c1, c2;
282 do {
283 c1 = *s1++;
284 c2 = *s2++;
285 } while (c1 && toupper(c1) == toupper(c2));
286 /* now calculate real difference */
287 return c1 - c2;
289 #endif
291 unsigned int img_output_version = IMG_VERSION_MAX;
293 static img_errcode img_errno = IMG_NONE;
295 #define FILEID "Survex 3D Image File"
297 #define EXT_PLT "plt"
298 #define EXT_PLF "plf"
300 /* Attempt to string paste to ensure we are passed a literal string */
301 #define LITLEN(S) (sizeof(S"") - 1)
303 /* Fake "version numbers" for non-3d formats we can read. */
304 #define VERSION_CMAP_SHOT -4
305 #define VERSION_CMAP_STATION -3
306 #define VERSION_COMPASS_PLT -2
307 #define VERSION_SURVEX_POS -1
309 /* Flags bitwise-or-ed into pending to track XSECTs. */
310 #define PENDING_XSECT_END 0x100
311 #define PENDING_HAD_XSECT 0x001 /* Only for VERSION_COMPASS_PLT */
312 #define PENDING_MOVE 0x002 /* Only for VERSION_COMPASS_PLT */
313 #define PENDING_LINE 0x004 /* Only for VERSION_COMPASS_PLT */
314 #define PENDING_XSECT 0x008 /* Only for VERSION_COMPASS_PLT */
315 #define PENDING_FLAGS_SHIFT 9 /* Only for VERSION_COMPASS_PLT */
317 /* Days from start of 1900 to start of 1970. */
318 #define DAYS_1900 25567
320 /* Start of 1900 in time_t with standard Unix epoch of start of 1970. */
321 #define TIME_T_1900 -2208988800L
323 /* Seconds in a day. */
324 #define SECS_PER_DAY 86400L
326 static unsigned
327 hash_data(const char *s, unsigned len)
329 /* djb2 hash but with an initial value of zero. */
330 unsigned h = 0;
331 while (len) {
332 unsigned char c = (unsigned char)*s++;
333 h = ((h << 5) + h) + c;
334 --len;
336 return h;
339 struct compass_station {
340 struct compass_station *next;
341 unsigned char flags;
342 unsigned char len;
343 char name[1];
346 /* On the first pass, at the start of each survey we run through all the
347 * hash table entries that exist and set this flag.
349 * If this flag is set when we add flags to an existing station we
350 * know it appears in multiple surveys and can infer img_SFLAG_EXPORTED.
352 #define COMPASS_SFLAG_DIFFERENT_SURVEY 0x80
354 #define COMPASS_SFLAG_MASK 0x7f
356 /* How many hash buckets to use (must be a power of 2).
358 * Each bucket is a linked list so this doesn't limit how many entries we can
359 * store, but should be sized based on a plausible estimate of how many
360 * different stations we're likely to see in a single PLT file.
362 #define HASH_BUCKETS 0x2000U
364 static void*
365 compass_plt_allocate_hash(void)
367 struct compass_station_name** htab = xosmalloc(HASH_BUCKETS * sizeof(struct compass_station_name*));
368 if (htab) {
369 unsigned i;
370 for (i = 0; i < HASH_BUCKETS; ++i)
371 htab[i] = NULL;
373 return htab;
376 static int
377 compass_plt_update_station(img *pimg, const char *name, int name_len,
378 unsigned flags)
380 struct compass_station *p;
381 struct compass_station **htab = (struct compass_station**)pimg->data;
382 htab += hash_data(name, name_len) & (HASH_BUCKETS - 1U);
383 for (p = *htab; p; p = p->next) {
384 if (p->len == name_len) {
385 if (memcmp(name, p->name, name_len) == 0) {
386 p->flags |= flags;
387 if (p->flags & COMPASS_SFLAG_DIFFERENT_SURVEY)
388 p->flags |= img_SFLAG_EXPORTED;
389 return 0;
393 p = malloc(offsetof(struct compass_station, name) + name_len);
394 if (!p) return -1;
395 p->flags = flags;
396 p->len = name_len;
397 memcpy(p->name, name, name_len);
398 p->next = *htab;
399 *htab = p;
400 return 0;
403 static void
404 compass_plt_new_survey(img *pimg)
406 struct compass_station **htab = (struct compass_station**)pimg->data;
407 int i = HASH_BUCKETS;
408 while (--i) {
409 struct compass_station *p;
410 for (p = *htab; p; p = p->next) {
411 p->flags |= COMPASS_SFLAG_DIFFERENT_SURVEY;
413 ++htab;
417 static void
418 compass_plt_free_data(img *pimg)
420 struct compass_station **htab = (struct compass_station**)pimg->data;
421 int i = HASH_BUCKETS;
422 while (--i) {
423 struct compass_station *p = *htab;
424 while (p) {
425 struct compass_station *next = p->next;
426 osfree(p);
427 p = next;
429 ++htab;
431 osfree(pimg->data);
432 pimg->data = NULL;
435 static int
436 compass_plt_get_station_flags(img *pimg, const char *name, int name_len)
438 struct compass_station *p;
439 struct compass_station **htab = (struct compass_station**)pimg->data;
440 htab += hash_data(name, name_len) & (HASH_BUCKETS - 1U);
441 for (p = *htab; p; p = p->next) {
442 if (p->len == name_len) {
443 if (memcmp(name, p->name, name_len) == 0) {
444 if (p->flags & COMPASS_SFLAG_DIFFERENT_SURVEY) {
445 p->flags &= ~COMPASS_SFLAG_DIFFERENT_SURVEY;
446 return p->flags;
448 return p->flags | INT_MIN;
452 return -1;
455 static char *
456 my_strdup(const char *str)
458 char *p;
459 size_t len = strlen(str) + 1;
460 p = (char *)xosmalloc(len);
461 if (p) memcpy(p, str, len);
462 return p;
465 #define getline_alloc(FH) getline_alloc_len(FH, NULL)
467 static char *
468 getline_alloc_len(FILE *fh, size_t * p_len)
470 int ch;
471 size_t i = 0;
472 size_t len = 16;
473 char *buf = (char *)xosmalloc(len);
474 if (!buf) return NULL;
476 ch = GETC(fh);
477 while (ch != '\n' && ch != '\r' && ch != EOF) {
478 buf[i++] = ch;
479 if (i == len - 1) {
480 char *p;
481 len += len;
482 p = (char *)xosrealloc(buf, len);
483 if (!p) {
484 osfree(buf);
485 return NULL;
487 buf = p;
489 ch = GETC(fh);
491 if (ch == '\n' || ch == '\r') {
492 int otherone = ch ^ ('\n' ^ '\r');
493 ch = GETC(fh);
494 /* if it's not the other eol character, put it back */
495 if (ch != otherone) ungetc(ch, fh);
497 buf[i] = '\0';
498 if (p_len) *p_len = i;
499 return buf;
502 img_errcode
503 img_error(void)
505 return img_errno;
508 static int
509 check_label_space(img *pimg, size_t len)
511 if (len > pimg->buf_len) {
512 char *b = (char *)xosrealloc(pimg->label_buf, len);
513 if (!b) return 0;
514 pimg->label = (pimg->label - pimg->label_buf) + b;
515 pimg->label_buf = b;
516 pimg->buf_len = len;
518 return 1;
521 /* Check if a station name should be included. */
522 static int
523 stn_included(img *pimg)
525 if (!pimg->survey_len) return 1;
526 size_t l = pimg->survey_len;
527 const char *s = pimg->label_buf;
528 if (strncmp(pimg->survey, s, l + 1) != 0) {
529 return 0;
531 pimg->label += l + 1;
532 return 1;
535 /* Check if a survey name should be included. */
536 static int
537 survey_included(img *pimg)
539 if (!pimg->survey_len) return 1;
540 size_t l = pimg->survey_len;
541 const char *s = pimg->label_buf;
542 if (strncmp(pimg->survey, s, l) != 0 ||
543 !(s[l] == '.' || s[l] == '\0')) {
544 return 0;
546 pimg->label += l;
547 /* skip the dot if there */
548 if (*pimg->label) pimg->label++;
549 return 1;
552 /* Check if a survey name in a buffer should be included.
554 * For "foreign" formats which just have one level of surveys.
556 static int
557 buf_included(img *pimg, const char *buf, size_t len)
559 return pimg->survey_len == len && memcmp(buf, pimg->survey, len) == 0;
562 #define has_ext(F,L,E) ((L) > LITLEN(E) + 1 &&\
563 (F)[(L) - LITLEN(E) - 1] == FNM_SEP_EXT &&\
564 my_strcasecmp((F) + (L) - LITLEN(E), E) == 0)
566 img *
567 img_open_survey(const char *fnm, const char *survey)
569 img *pimg;
570 FILE *fh;
571 char* filename_opened = NULL;
573 if (fDirectory(fnm)) {
574 img_errno = IMG_DIRECTORY;
575 return NULL;
578 fh = fopenWithPthAndExt("", fnm, EXT_SVX_3D, "rb", &filename_opened);
579 pimg = img_read_stream_survey(fh, fclose,
580 filename_opened ? filename_opened : fnm,
581 survey);
582 if (pimg) {
583 pimg->filename_opened = filename_opened;
584 } else {
585 osfree(filename_opened);
587 return pimg;
590 static int
591 compass_plt_open(img *pimg)
593 int utm_zone = 0;
594 int datum = img_DATUM_UNKNOWN;
595 long fpos;
596 char *from = NULL;
597 int from_len = 0;
599 pimg->version = VERSION_COMPASS_PLT;
600 /* Spaces aren't legal in Compass station names, but dots are, so
601 * use space as the level separator */
602 pimg->separator = ' ';
603 pimg->start = -1;
604 pimg->datestamp = my_strdup(TIMENA);
605 if (!pimg->datestamp) {
606 return IMG_OUTOFMEMORY;
608 pimg->data = compass_plt_allocate_hash();
609 if (!pimg->data) {
610 return IMG_OUTOFMEMORY;
613 /* Read through the whole file first, recording any station flags
614 * (pimg->data), finding where to start reading data from (pimg->start),
615 * and deciding what to report for "title".
617 while (1) {
618 int ch = GETC(pimg->fh);
619 switch (ch) {
620 case '\x1a':
621 fseek(pimg->fh, -1, SEEK_CUR);
622 /* FALL THRU */
623 case EOF:
624 if (pimg->start < 0) {
625 pimg->start = ftell(pimg->fh);
626 } else {
627 fseek(pimg->fh, pimg->start, SEEK_SET);
630 if (datum && utm_zone && abs(utm_zone) <= 60) {
631 /* Map to an EPSG code where we can. */
632 const char* template = "EPSG:%d";
633 int value = 0;
634 switch (datum) {
635 case img_DATUM_NAD27:
636 if (utm_zone < 0) {
637 template = "+proj=utm +zone=%d +datum=NAD27 +south +units=m +no_defs +type=crs";
638 value = -utm_zone;
639 } else if (utm_zone <= 23) {
640 value = 26700 + utm_zone;
641 } else if (utm_zone < 59) {
642 template = "+proj=utm +zone=%d +datum=NAD27 +units=m +no_defs +type=crs";
643 value = utm_zone;
644 } else {
645 value = 3311 + utm_zone;
647 break;
648 case img_DATUM_NAD83:
649 if (utm_zone < 0) {
650 template = "+proj=utm +zone=%d +datum=NAD83 +south +units=m +no_defs +type=crs";
651 value = -utm_zone;
652 } else if (utm_zone <= 23) {
653 value = 26900 + utm_zone;
654 } else if (utm_zone == 24) {
655 value = 9712;
656 } else if (utm_zone < 59) {
657 template = "+proj=utm +zone=%d +datum=NAD83 +units=m +no_defs +type=crs";
658 value = utm_zone;
659 } else {
660 value = 3313 + utm_zone;
662 break;
663 case img_DATUM_WGS84:
664 if (utm_zone > 0) {
665 value = 32600 + utm_zone;
666 } else {
667 value = 32700 - utm_zone;
669 break;
671 if (value) {
672 pimg->cs = (char*)xosmalloc(strlen(template) + 4);
673 if (!pimg->cs) {
674 goto out_of_memory_error;
676 sprintf(pimg->cs, template, value);
680 osfree(from);
681 return 0;
682 case 'S':
683 /* "Section" - in the case where we aren't filtering by survey
684 * (i.e. pimg->survey == NULL): if there's only one non-empty
685 * section name specified, we use it as the title.
687 if (pimg->survey == NULL && (!pimg->title || pimg->title[0])) {
688 char *line = getline_alloc(pimg->fh);
689 if (!line) {
690 goto out_of_memory_error;
692 if (line[0]) {
693 if (pimg->title) {
694 if (strcmp(pimg->title, line) != 0) {
695 /* Two different non-empty section names found. */
696 pimg->title[0] = '\0';
698 osfree(line);
699 } else {
700 pimg->title = line;
702 } else {
703 osfree(line);
705 continue;
707 break;
708 case 'N': {
709 char *line, *q;
710 size_t len;
711 compass_plt_new_survey(pimg);
712 if (pimg->start >= 0) break;
713 fpos = ftell(pimg->fh) - 1;
714 if (!pimg->survey) {
715 /* We're not filtering by survey so just note down the file
716 * offset for the first N command. */
717 pimg->start = fpos;
718 break;
720 line = getline_alloc(pimg->fh);
721 if (!line) {
722 goto out_of_memory_error;
724 len = 0;
725 while (line[len] > 32) ++len;
726 if (!buf_included(pimg, line, len)) {
727 /* Not the survey we are looking for. */
728 osfree(line);
729 continue;
731 q = strchr(line + len, 'C');
732 if (q && q[1]) {
733 osfree(pimg->title);
734 pimg->title = my_strdup(q + 1);
735 } else if (!pimg->title) {
736 pimg->title = my_strdup(pimg->label);
738 osfree(line);
739 if (!pimg->title) {
740 goto out_of_memory_error;
742 pimg->start = fpos;
743 continue;
745 case 'M':
746 case 'D':
747 case 'd': {
748 /* Move or Draw */
749 int command = ch;
750 char *q, *name;
751 unsigned station_flags = 0;
752 int name_len;
753 int not_plotted = (command == 'd');
755 /* Find station name. */
756 do { ch = GETC(pimg->fh); } while (ch >= ' ' && ch != 'S');
758 if (ch != 'S') {
759 /* Leave reporting error to second pass for consistency. */
760 break;
763 name = getline_alloc(pimg->fh);
764 if (!name) {
765 goto out_of_memory_error;
768 name_len = 0;
769 while (name[name_len] > ' ') ++name_len;
770 if (name_len > 255) {
771 /* The spec says "up to 12 characters", we allow up to 255. */
772 osfree(name);
773 osfree(from);
774 return IMG_BADFORMAT;
777 /* Check for the "distance from entrance" field. */
778 q = strchr(name + name_len, 'I');
779 if (q) {
780 double distance_from_entrance;
781 int bytes_used = 0;
782 ++q;
783 if (sscanf(q, "%lf%n",
784 &distance_from_entrance, &bytes_used) == 1 &&
785 distance_from_entrance == 0.0) {
786 /* Infer an entrance. */
787 station_flags |= img_SFLAG_ENTRANCE;
789 q += bytes_used;
790 while (*q && *q <= ' ') q++;
791 } else {
792 q = strchr(name + name_len, 'F');
795 if (q && *q == 'F') {
796 /* "Shot Flags". */
797 while (isalpha((unsigned char)*++q)) {
798 switch (*q) {
799 case 'S':
800 /* The format specification says «The shot is a "splay"
801 * shot, which is a shot from a station to the wall to
802 * define the passage shape.» so we set the wall flag
803 * for the to station.
805 station_flags |= img_SFLAG_WALL;
806 break;
807 case 'P':
808 not_plotted = 1;
809 break;
814 /* Shot flag P (which is also implied by command d) is "Exclude
815 * this shot from plotting", but the use suggested in the Compass
816 * docs is for surface data, and they "[do] not support passage
817 * modeling".
819 * Even if it's actually being used for a different purpose,
820 * Survex programs don't show surface legs by default so the end
821 * effect is at least to not plot as intended.
823 if (command != 'M') {
824 int surface_or_not = not_plotted ? img_SFLAG_SURFACE
825 : img_SFLAG_UNDERGROUND;
826 station_flags |= surface_or_not;
827 if (compass_plt_update_station(pimg, from, from_len,
828 surface_or_not) < 0) {
829 goto out_of_memory_error;
833 if (compass_plt_update_station(pimg, name, name_len,
834 station_flags) < 0) {
835 goto out_of_memory_error;
837 osfree(from);
838 from = name;
839 from_len = name_len;
840 continue;
842 case 'P': {
843 /* Fixed point. */
844 char *line, *q, *name;
845 int name_len;
847 line = getline_alloc(pimg->fh);
848 if (!line) {
849 goto out_of_memory_error;
851 q = line;
852 while (*q && *q <= ' ') q++;
853 name = q;
854 name_len = 0;
855 while (name[name_len] > ' ') ++name_len;
857 if (name_len > 255) {
858 /* The spec says "up to 12 characters", we allow up to 255. */
859 osfree(line);
860 osfree(from);
861 return IMG_BADFORMAT;
864 if (compass_plt_update_station(pimg, name, name_len,
865 img_SFLAG_FIXED) < 0) {
866 goto out_of_memory_error;
869 osfree(line);
870 continue;
872 case 'G': {
873 /* UTM Zone - 1 to 60 for North, -1 to -60 for South. */
874 char *line = getline_alloc(pimg->fh);
875 char *p = line;
876 long v = strtol(p, &p, 10);
877 if (v < -60 || v > 60 || v == 0 || *p > ' ') {
878 osfree(line);
879 continue;
881 if (utm_zone && utm_zone != v) {
882 /* More than one UTM zone specified. */
883 /* FIXME: We could handle this by reprojecting, but then we'd
884 * need access to PROJ from img.
886 utm_zone = 99;
887 } else {
888 utm_zone = v;
890 osfree(line);
891 continue;
893 case 'O': {
894 /* Datum. */
895 int new_datum;
896 char *line = getline_alloc(pimg->fh);
897 if (!line) {
898 goto out_of_memory_error;
900 if (utm_zone == 99) {
901 osfree(line);
902 continue;
905 new_datum = img_parse_compass_datum_string(line, strlen(line));
906 if (new_datum == img_DATUM_UNKNOWN) {
907 utm_zone = 99;
908 } else if (datum == img_DATUM_UNKNOWN) {
909 datum = new_datum;
910 } else if (datum != new_datum) {
911 utm_zone = 99;
914 osfree(line);
915 continue;
918 while (ch != '\n' && ch != '\r') {
919 ch = GETC(pimg->fh);
922 out_of_memory_error:
923 osfree(from);
924 return IMG_OUTOFMEMORY;
927 static int
928 cmap_xyz_open(img *pimg)
930 size_t len;
931 char *line = getline_alloc(pimg->fh);
932 if (!line) {
933 return IMG_OUTOFMEMORY;
936 /* Spaces aren't legal in CMAP station names, but dots are, so
937 * use space as the level separator. */
938 pimg->separator = ' ';
940 /* There doesn't seem to be a spec for what happens after 1999 with cmap
941 * files, so this code allows for:
942 * * 21xx -> xx (up to 2150)
943 * * 21xx -> 1xx (up to 2199)
944 * * full year being specified instead of 2 digits
946 len = strlen(line);
947 if (len > 59) {
948 /* Don't just truncate at column 59, allow for a > 2 digit year. */
949 char * p = strstr(line + len, "Page");
950 if (p) {
951 while (p > line && p[-1] == ' ')
952 --p;
953 *p = '\0';
954 len = p - line;
955 } else {
956 line[59] = '\0';
959 if (len > 45) {
960 /* YY/MM/DD HH:MM */
961 struct tm tm;
962 unsigned long v;
963 char * p;
964 pimg->datestamp = my_strdup(line + 45);
965 p = pimg->datestamp;
966 v = strtoul(p, &p, 10);
967 if (v <= 50) {
968 /* In the absence of a spec for cmap files, assume <= 50 means 21st
969 * century. */
970 v += 2000;
971 } else if (v < 200) {
972 /* Map 100-199 to 21st century. */
973 v += 1900;
975 if (v == ULONG_MAX || *p++ != '/')
976 goto bad_cmap_date;
977 tm.tm_year = v - 1900;
978 v = strtoul(p, &p, 10);
979 if (v < 1 || v > 12 || *p++ != '/')
980 goto bad_cmap_date;
981 tm.tm_mon = v - 1;
982 v = strtoul(p, &p, 10);
983 if (v < 1 || v > 31 || *p++ != ' ')
984 goto bad_cmap_date;
985 tm.tm_mday = v;
986 v = strtoul(p, &p, 10);
987 if (v >= 24 || *p++ != ':')
988 goto bad_cmap_date;
989 tm.tm_hour = v;
990 v = strtoul(p, &p, 10);
991 if (v >= 60)
992 goto bad_cmap_date;
993 tm.tm_min = v;
994 if (*p == ':') {
995 v = strtoul(p + 1, &p, 10);
996 if (v > 60)
997 goto bad_cmap_date;
998 tm.tm_sec = v;
999 } else {
1000 tm.tm_sec = 0;
1002 tm.tm_isdst = 0;
1003 /* We have no indication of what timezone this timestamp is in. It's
1004 * probably local time for whoever processed the data, so just assume
1005 * UTC, which is at least fairly central in the possibilities.
1007 pimg->datestamp_numeric = mktime_with_tz(&tm, "");
1008 } else {
1009 pimg->datestamp = my_strdup(TIMENA);
1011 bad_cmap_date:
1012 if (strncmp(line, " Cave Survey Data Processed by CMAP ",
1013 LITLEN(" Cave Survey Data Processed by CMAP ")) != 0) {
1014 if (len > 45) {
1015 line[45] = '\0';
1016 len = 45;
1018 while (len > 2 && line[len - 1] == ' ') --len;
1019 if (len > 2) {
1020 line[len] = '\0';
1021 pimg->title = my_strdup(line + 2);
1024 osfree(line);
1025 if (!pimg->datestamp || !pimg->title) {
1026 return IMG_OUTOFMEMORY;
1028 line = getline_alloc(pimg->fh);
1029 if (!line) {
1030 return IMG_OUTOFMEMORY;
1032 if (line[0] != ' ' || (line[1] != 'S' && line[1] != 'O')) {
1033 return IMG_BADFORMAT;
1035 if (line[1] == 'S') {
1036 pimg->version = VERSION_CMAP_STATION;
1037 } else {
1038 pimg->version = VERSION_CMAP_SHOT;
1040 osfree(line);
1041 line = getline_alloc(pimg->fh);
1042 if (!line) {
1043 return IMG_OUTOFMEMORY;
1045 if (line[0] != ' ' || line[1] != '-') {
1046 return IMG_BADFORMAT;
1048 osfree(line);
1049 pimg->start = ftell(pimg->fh);
1050 return 0;
1053 img *
1054 img_read_stream_survey(FILE *stream, int (*close_func)(FILE*),
1055 const char *fnm,
1056 const char *survey)
1058 img *pimg;
1059 size_t len;
1060 char buf[LITLEN(FILEID) + 9];
1061 int ch;
1063 if (stream == NULL) {
1064 img_errno = IMG_FILENOTFOUND;
1065 return NULL;
1068 pimg = osnew(img);
1069 if (pimg == NULL) {
1070 img_errno = IMG_OUTOFMEMORY;
1071 if (close_func) close_func(stream);
1072 return NULL;
1075 pimg->fh = stream;
1076 pimg->close_func = close_func;
1078 pimg->buf_len = 257;
1079 pimg->label_buf = (char *)xosmalloc(pimg->buf_len);
1080 if (!pimg->label_buf) {
1081 if (pimg->close_func) pimg->close_func(pimg->fh);
1082 osfree(pimg);
1083 img_errno = IMG_OUTOFMEMORY;
1084 return NULL;
1087 pimg->fRead = 1; /* reading from this file */
1088 img_errno = IMG_NONE;
1090 pimg->flags = 0;
1091 pimg->filename_opened = NULL;
1092 pimg->data = NULL;
1094 /* for version >= 3 we use label_buf to store the prefix for reuse */
1095 /* for VERSION_COMPASS_PLT, 0 value indicates we haven't
1096 * entered a survey yet */
1097 /* for VERSION_CMAP_SHOT, we store the last station here
1098 * to detect whether we MOVE or LINE */
1099 pimg->label_len = 0;
1100 pimg->label_buf[0] = '\0';
1102 pimg->survey = NULL;
1103 pimg->survey_len = 0;
1104 pimg->separator = '.';
1105 #if IMG_API_VERSION == 0
1106 pimg->date1 = pimg->date2 = 0;
1107 #else /* IMG_API_VERSION == 1 */
1108 pimg->days1 = pimg->days2 = -1;
1109 #endif
1110 pimg->is_extended_elevation = 0;
1112 pimg->style = pimg->oldstyle = img_STYLE_UNKNOWN;
1114 pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
1116 pimg->title = pimg->datestamp = pimg->cs = NULL;
1117 pimg->datestamp_numeric = (time_t)-1;
1119 if (survey) {
1120 len = strlen(survey);
1121 if (len) {
1122 if (survey[len - 1] == '.') len--;
1123 if (len) {
1124 char *p;
1125 pimg->survey = (char *)xosmalloc(len + 2);
1126 if (!pimg->survey) {
1127 goto out_of_memory_error;
1129 memcpy(pimg->survey, survey, len);
1130 /* Set title to leaf survey name */
1131 pimg->survey[len] = '\0';
1132 p = strrchr(pimg->survey, '.');
1133 if (p) p++; else p = pimg->survey;
1134 pimg->title = my_strdup(p);
1135 if (!pimg->title) {
1136 goto out_of_memory_error;
1138 pimg->survey[len] = '.';
1139 pimg->survey[len + 1] = '\0';
1142 pimg->survey_len = len;
1145 /* [VERSION_COMPASS_PLT] bitwise-or of PENDING_* values, or -1.
1146 * [VERSION_CMAP_STATION, VERSION_CMAP_SHOT] pending IMG_LINE or IMG_MOVE -
1147 * both have 4 added.
1148 * [VERSION_SURVEX_POS] already skipped heading line, or there wasn't one
1149 * [version 0] not in the middle of a 'LINE' command
1150 * [version >= 3] not in the middle of turning a LINE into a MOVE
1152 pimg->pending = 0;
1154 len = strlen(fnm);
1155 if (has_ext(fnm, len, EXT_SVX_POS)) {
1156 pos_file:
1157 pimg->version = VERSION_SURVEX_POS;
1158 pimg->datestamp = my_strdup(TIMENA);
1159 if (!pimg->datestamp) {
1160 goto out_of_memory_error;
1162 pimg->start = 0;
1163 goto successful_return;
1166 if (has_ext(fnm, len, EXT_PLT) || has_ext(fnm, len, EXT_PLF)) {
1167 int result;
1168 plt_file:
1169 result = compass_plt_open(pimg);
1170 if (result) {
1171 img_errno = result;
1172 goto error;
1174 goto successful_return;
1177 /* Although these are often referred to as "CMAP .XYZ files", it seems
1178 * that actually, the extension .XYZ isn't used, rather .SHT (shot
1179 * variant, produced by CMAP v16 and later), .UNA (unadjusted) and
1180 * .ADJ (adjusted) extensions are. Since img has long checked for
1181 * .XYZ, we continue to do so in case anyone is relying on it.
1183 if (has_ext(fnm, len, "sht") ||
1184 has_ext(fnm, len, "adj") ||
1185 has_ext(fnm, len, "una") ||
1186 has_ext(fnm, len, "xyz")) {
1187 int result;
1188 xyz_file:
1189 result = cmap_xyz_open(pimg);
1190 if (result) {
1191 img_errno = result;
1192 goto error;
1194 goto successful_return;
1197 if (fread(buf, LITLEN(FILEID) + 1, 1, pimg->fh) != 1 ||
1198 memcmp(buf, FILEID"\n", LITLEN(FILEID) + 1) != 0) {
1199 if (fread(buf + LITLEN(FILEID) + 1, 8, 1, pimg->fh) == 1 &&
1200 memcmp(buf, FILEID"\r\nv0.01\r\n", LITLEN(FILEID) + 9) == 0) {
1201 /* v0 3d file with DOS EOLs */
1202 pimg->version = 0;
1203 goto v03d;
1205 rewind(pimg->fh);
1206 if (buf[1] == ' ') {
1207 if (buf[0] == ' ') {
1208 /* Looks like a CMAP .xyz file ... */
1209 goto xyz_file;
1210 } else if (strchr("ZSNF", buf[0])) {
1211 /* Looks like a Compass .plt file ... */
1212 /* Almost certainly it'll start "Z " */
1213 goto plt_file;
1216 if (buf[0] == '(') {
1217 /* Looks like a Survex .pos file ... */
1218 goto pos_file;
1220 img_errno = IMG_BADFORMAT;
1221 goto error;
1224 /* check file format version */
1225 ch = GETC(pimg->fh);
1226 pimg->version = 0;
1227 if (tolower(ch) == 'b') {
1228 /* binary file iff B/b prefix */
1229 pimg->version = 1;
1230 ch = GETC(pimg->fh);
1232 if (ch != 'v') {
1233 img_errno = IMG_BADFORMAT;
1234 goto error;
1236 ch = GETC(pimg->fh);
1237 if (ch == '0') {
1238 if (fread(buf, 4, 1, pimg->fh) != 1 || memcmp(buf, ".01\n", 4) != 0) {
1239 img_errno = IMG_BADFORMAT;
1240 goto error;
1242 /* nothing special to do */
1243 } else if (pimg->version == 0) {
1244 if (ch < '2' || ch > '0' + IMG_VERSION_MAX || GETC(pimg->fh) != '\n') {
1245 img_errno = IMG_TOONEW;
1246 goto error;
1248 pimg->version = ch - '0';
1249 } else {
1250 img_errno = IMG_BADFORMAT;
1251 goto error;
1254 v03d:
1256 size_t title_len;
1257 char * title = getline_alloc_len(pimg->fh, &title_len);
1258 if (!title) goto out_of_memory_error;
1259 if (pimg->version == 8) {
1260 /* We sneak in an extra field after a zero byte here, containing the
1261 * specified coordinate system (if any). Older readers will just
1262 * not see it (which is fine), and this trick avoids us having to
1263 * bump the 3d format version.
1265 size_t real_len = strlen(title);
1266 if (real_len != title_len) {
1267 char * cs = title + real_len + 1;
1268 if (memcmp(cs, "+init=", 6) == 0) {
1269 /* PROJ 5 and later don't handle +init=esri:<number> but
1270 * that's what cavern used to put in .3d files for
1271 * coordinate systems specified using ESRI codes. We parse
1272 * and convert the strings cavern used to generate and
1273 * convert to the form ESRI:<number> which is still
1274 * understood.
1276 * PROJ 6 and later don't recognise +init=epsg:<number>
1277 * by default and don't apply datum shift terms in some
1278 * cases, so we also convert these to the form
1279 * EPSG:<number>.
1281 char * p = cs + 6;
1282 if (p[4] == ':' && isdigit((unsigned char)p[5]) &&
1283 ((memcmp(p, "epsg", 4) == 0 || memcmp(p, "esri", 4) == 0))) {
1284 p = p + 6;
1285 while (isdigit((unsigned char)*p)) {
1286 ++p;
1288 /* Allow +no_defs to be omitted as it seems to not
1289 * actually do anything with recent PROJ - cavern always
1290 * included it, but other software generating 3d files
1291 * may not.
1293 if (*p == '\0' || strcmp(p, " +no_defs") == 0) {
1294 int i;
1295 cs = cs + 6;
1296 for (i = 0; i < 4; ++i) {
1297 cs[i] = toupper(cs[i]);
1299 *p = '\0';
1302 } else if (memcmp(cs, "+proj=", 6) == 0) {
1303 /* Convert S_MERC and UTM proj strings which cavern used
1304 * to generate to their corresponding EPSG:<number> codes.
1306 char * p = cs + 6;
1307 if (memcmp(p, "utm +ellps=WGS84 +datum=WGS84 +units=m +zone=", 45) == 0) {
1308 int n = 0;
1309 p += 45;
1310 while (isdigit((unsigned char)*p)) {
1311 n = n * 10 + (*p - '0');
1312 ++p;
1314 if (memcmp(p, " +south", 7) == 0) {
1315 p += 7;
1316 n += 32700;
1317 } else {
1318 n += 32600;
1320 /* Allow +no_defs to be omitted as it seems to not
1321 * actually do anything with recent PROJ - cavern always
1322 * included it, but other software generating 3d files
1323 * may not have.
1325 if (*p == '\0' || strcmp(p, " +no_defs") == 0) {
1326 sprintf(cs, "EPSG:%d", n);
1328 } else if (memcmp(p, "merc +lat_ts=0 +lon_0=0 +k=1 +x_0=0 +y_0=0 +a=6378137 +b=6378137 +units=m +nadgrids=@null", 89) == 0) {
1329 p = p + 89;
1330 /* Allow +no_defs to be omitted as it seems to not
1331 * actually do anything with recent PROJ - cavern always
1332 * included it, but other software generating 3d files
1333 * may not have.
1335 if (*p == '\0' || strcmp(p, " +no_defs") == 0) {
1336 strcpy(cs, "EPSG:3857");
1340 pimg->cs = my_strdup(cs);
1343 if (!pimg->title) {
1344 pimg->title = title;
1345 } else {
1346 osfree(title);
1349 pimg->datestamp = getline_alloc(pimg->fh);
1350 if (!pimg->datestamp) {
1351 out_of_memory_error:
1352 img_errno = IMG_OUTOFMEMORY;
1353 error:
1354 osfree(pimg->title);
1355 osfree(pimg->cs);
1356 osfree(pimg->datestamp);
1357 osfree(pimg->filename_opened);
1358 if (pimg->close_func) pimg->close_func(pimg->fh);
1359 osfree(pimg);
1360 return NULL;
1363 if (pimg->version >= 8) {
1364 int flags = GETC(pimg->fh);
1365 if (flags & img_FFLAG_EXTENDED) pimg->is_extended_elevation = 1;
1366 } else if (pimg->title) {
1367 len = strlen(pimg->title);
1368 if (len > 11 && strcmp(pimg->title + len - 11, " (extended)") == 0) {
1369 pimg->title[len - 11] = '\0';
1370 pimg->is_extended_elevation = 1;
1374 if (pimg->datestamp[0] == '@') {
1375 unsigned long v;
1376 char * p;
1377 errno = 0;
1378 v = strtoul(pimg->datestamp + 1, &p, 10);
1379 if (errno == 0 && *p == '\0')
1380 pimg->datestamp_numeric = v;
1381 /* FIXME: We're assuming here that the C time_t epoch is 1970, which is
1382 * true for Unix-like systems, macOS and Windows, but isn't guaranteed
1383 * by ISO C.
1385 } else {
1386 /* %a,%Y.%m.%d %H:%M:%S %Z */
1387 struct tm tm;
1388 unsigned long v;
1389 char * p = pimg->datestamp;
1390 while (isalpha((unsigned char)*p)) ++p;
1391 if (*p == ',') ++p;
1392 while (isspace((unsigned char)*p)) ++p;
1393 v = strtoul(p, &p, 10);
1394 if (v == ULONG_MAX || *p++ != '.')
1395 goto bad_3d_date;
1396 tm.tm_year = v - 1900;
1397 v = strtoul(p, &p, 10);
1398 if (v < 1 || v > 12 || *p++ != '.')
1399 goto bad_3d_date;
1400 tm.tm_mon = v - 1;
1401 v = strtoul(p, &p, 10);
1402 if (v < 1 || v > 31 || *p++ != ' ')
1403 goto bad_3d_date;
1404 tm.tm_mday = v;
1405 v = strtoul(p, &p, 10);
1406 if (v >= 24 || *p++ != ':')
1407 goto bad_3d_date;
1408 tm.tm_hour = v;
1409 v = strtoul(p, &p, 10);
1410 if (v >= 60 || *p++ != ':')
1411 goto bad_3d_date;
1412 tm.tm_min = v;
1413 v = strtoul(p, &p, 10);
1414 if (v > 60)
1415 goto bad_3d_date;
1416 tm.tm_sec = v;
1417 tm.tm_isdst = 0;
1418 while (isspace((unsigned char)*p)) ++p;
1419 /* p now points to the timezone string.
1421 * However, it's likely to be a string like "BST", and such strings can
1422 * be ambiguous (BST could be UTC+1 or UTC+6), so it is impossible to
1423 * reliably convert in all cases. Just pass what we have to tzset() - if
1424 * it doesn't handle it, UTC will be used.
1426 pimg->datestamp_numeric = mktime_with_tz(&tm, p);
1428 bad_3d_date:
1430 pimg->start = ftell(pimg->fh);
1432 successful_return:
1433 /* If no title from another source, default to the base leafname. */
1434 if (!pimg->title || !pimg->title[0]) {
1435 osfree(pimg->title);
1436 pimg->title = baseleaf_from_fnm(fnm);
1438 return pimg;
1442 img_rewind(img *pimg)
1444 if (!pimg->fRead) {
1445 img_errno = IMG_WRITEERROR;
1446 return 0;
1448 if (fseek(pimg->fh, pimg->start, SEEK_SET) != 0) {
1449 img_errno = IMG_READERROR;
1450 return 0;
1452 clearerr(pimg->fh);
1453 /* [VERSION_SURVEX_POS] already skipped heading line, or there wasn't one
1454 * [version 0] not in the middle of a 'LINE' command
1455 * [version >= 3] not in the middle of turning a LINE into a MOVE */
1456 pimg->pending = 0;
1458 img_errno = IMG_NONE;
1460 /* for version >= 3 we use label_buf to store the prefix for reuse */
1461 /* for VERSION_COMPASS_PLT, 0 value indicates we haven't entered a survey
1462 * yet */
1463 /* for VERSION_CMAP_SHOT, we store the last station here to detect whether
1464 * we MOVE or LINE */
1465 pimg->label_len = 0;
1466 pimg->style = img_STYLE_UNKNOWN;
1467 return 1;
1470 img *
1471 img_open_write_cs(const char *fnm, const char *title, const char *cs, int flags)
1473 if (fDirectory(fnm)) {
1474 img_errno = IMG_DIRECTORY;
1475 return NULL;
1478 return img_write_stream(fopen(fnm, "wb"), fclose, title, cs, flags);
1481 img *
1482 img_write_stream(FILE *stream, int (*close_func)(FILE*),
1483 const char *title, const char *cs, int flags)
1485 time_t tm;
1486 img *pimg;
1488 if (stream == NULL) {
1489 img_errno = IMG_FILENOTFOUND;
1490 return NULL;
1493 pimg = osnew(img);
1494 if (pimg == NULL) {
1495 img_errno = IMG_OUTOFMEMORY;
1496 if (close_func) close_func(stream);
1497 return NULL;
1500 pimg->fh = stream;
1501 pimg->close_func = close_func;
1502 pimg->buf_len = 257;
1503 pimg->label_buf = (char *)xosmalloc(pimg->buf_len);
1504 if (!pimg->label_buf) {
1505 if (pimg->close_func) pimg->close_func(pimg->fh);
1506 osfree(pimg);
1507 img_errno = IMG_OUTOFMEMORY;
1508 return NULL;
1511 pimg->filename_opened = NULL;
1512 pimg->data = NULL;
1514 /* Output image file header */
1515 fputs("Survex 3D Image File\n", pimg->fh); /* file identifier string */
1516 if (img_output_version < 2) {
1517 pimg->version = 1;
1518 fputs("Bv0.01\n", pimg->fh); /* binary file format version number */
1519 } else {
1520 pimg->version = (img_output_version > IMG_VERSION_MAX) ? IMG_VERSION_MAX : img_output_version;
1521 fprintf(pimg->fh, "v%d\n", pimg->version); /* file format version no. */
1524 fputs(title, pimg->fh);
1525 if (pimg->version < 8 && (flags & img_FFLAG_EXTENDED)) {
1526 /* Older format versions append " (extended)" to the title to mark
1527 * extended elevations. */
1528 size_t len = strlen(title);
1529 if (len < 11 || strcmp(title + len - 11, " (extended)") != 0)
1530 fputs(" (extended)", pimg->fh);
1532 if (pimg->version == 8 && cs && *cs) {
1533 /* We sneak in an extra field after a zero byte here, containing the
1534 * specified coordinate system (if any). Older readers will just not
1535 * see it (which is fine), and this trick avoids us having to bump the
1536 * 3d format version.
1538 PUTC('\0', pimg->fh);
1539 fputs(cs, pimg->fh);
1541 PUTC('\n', pimg->fh);
1543 if (getenv("SOURCE_DATE_EPOCH")) {
1544 /* Support reproducible builds which create .3d files by not embedding a
1545 * timestamp if SOURCE_DATE_EPOCH is set. We don't bother trying to
1546 * parse the timestamp as it is simpler and seems cleaner to just not
1547 * embed a timestamp at all given the 3d file format already provides
1548 * a way not to.
1550 * See https://reproducible-builds.org/docs/source-date-epoch/
1552 tm = (time_t)-1;
1553 } else {
1554 tm = time(NULL);
1557 if (tm == (time_t)-1) {
1558 fputsnl(TIMENA, pimg->fh);
1559 } else if (pimg->version <= 7) {
1560 char date[256];
1561 /* output current date and time in format specified */
1562 strftime(date, 256, TIMEFMT, localtime(&tm));
1563 fputsnl(date, pimg->fh);
1564 } else {
1565 fprintf(pimg->fh, "@%ld\n", (long)tm);
1568 if (pimg->version >= 8) {
1569 /* Clear bit one in case anyone has been passing true for fBinary. */
1570 flags &=~ 1;
1571 PUTC(flags, pimg->fh);
1574 #if 0
1575 if (img_output_version >= 5) {
1576 static const unsigned char codelengths[32] = {
1577 4, 8, 8, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1578 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1580 fwrite(codelengths, 32, 1, pimg->fh);
1582 #endif
1583 pimg->fRead = 0; /* writing to this file */
1584 img_errno = IMG_NONE;
1586 /* for version >= 3 we use label_buf to store the prefix for reuse */
1587 pimg->label_buf[0] = '\0';
1588 pimg->label_len = 0;
1590 #if IMG_API_VERSION == 0
1591 pimg->date1 = pimg->date2 = 0;
1592 pimg->olddate1 = pimg->olddate2 = 0;
1593 #else /* IMG_API_VERSION == 1 */
1594 pimg->days1 = pimg->days2 = -1;
1595 pimg->olddays1 = pimg->olddays2 = -1;
1596 #endif
1597 pimg->style = pimg->oldstyle = img_STYLE_UNKNOWN;
1599 pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
1601 pimg->n_legs = 0;
1602 pimg->length = 0.0;
1603 pimg->E = pimg->H = pimg->V = 0.0;
1605 /* Don't check for write errors now - let img_close() report them... */
1606 return pimg;
1609 static void
1610 read_xyz_station_coords(img_point *pt, const char *line)
1612 char num[12];
1613 memcpy(num, line + 6, 9);
1614 num[9] = '\0';
1615 pt->x = atof(num) / METRES_PER_FOOT;
1616 memcpy(num, line + 15, 9);
1617 pt->y = atof(num) / METRES_PER_FOOT;
1618 memcpy(num, line + 24, 8);
1619 num[8] = '\0';
1620 pt->z = atof(num) / METRES_PER_FOOT;
1623 static void
1624 read_xyz_shot_coords(img_point *pt, const char *line)
1626 char num[12];
1627 memcpy(num, line + 40, 10);
1628 num[10] = '\0';
1629 pt->x = atof(num) / METRES_PER_FOOT;
1630 memcpy(num, line + 50, 10);
1631 pt->y = atof(num) / METRES_PER_FOOT;
1632 memcpy(num, line + 60, 9);
1633 num[9] = '\0';
1634 pt->z = atof(num) / METRES_PER_FOOT;
1637 static void
1638 subtract_xyz_shot_deltas(img_point *pt, const char *line)
1640 char num[12];
1641 memcpy(num, line + 15, 9);
1642 num[9] = '\0';
1643 pt->x -= atof(num) / METRES_PER_FOOT;
1644 memcpy(num, line + 24, 8);
1645 num[8] = '\0';
1646 pt->y -= atof(num) / METRES_PER_FOOT;
1647 memcpy(num, line + 32, 8);
1648 pt->z -= atof(num) / METRES_PER_FOOT;
1651 static int
1652 read_coord(FILE *fh, img_point *pt)
1654 SVX_ASSERT(fh);
1655 SVX_ASSERT(pt);
1656 pt->x = get32(fh) / 100.0;
1657 pt->y = get32(fh) / 100.0;
1658 pt->z = get32(fh) / 100.0;
1659 if (ferror(fh) || feof(fh)) {
1660 img_errno = feof(fh) ? IMG_BADFORMAT : IMG_READERROR;
1661 return 0;
1663 return 1;
1666 static int
1667 skip_coord(FILE *fh)
1669 return (fseek(fh, 12, SEEK_CUR) == 0);
1672 static int
1673 read_v3label(img *pimg)
1675 char *q;
1676 long len = GETC(pimg->fh);
1677 if (len == EOF) {
1678 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1679 return img_BAD;
1681 if (len == 0xfe) {
1682 len += get16(pimg->fh);
1683 if (feof(pimg->fh)) {
1684 img_errno = IMG_BADFORMAT;
1685 return img_BAD;
1687 if (ferror(pimg->fh)) {
1688 img_errno = IMG_READERROR;
1689 return img_BAD;
1691 } else if (len == 0xff) {
1692 len = get32(pimg->fh);
1693 if (ferror(pimg->fh)) {
1694 img_errno = IMG_READERROR;
1695 return img_BAD;
1697 if (feof(pimg->fh) || len < 0xfe + 0xffff) {
1698 img_errno = IMG_BADFORMAT;
1699 return img_BAD;
1703 if (!check_label_space(pimg, pimg->label_len + len + 1)) {
1704 img_errno = IMG_OUTOFMEMORY;
1705 return img_BAD;
1707 q = pimg->label_buf + pimg->label_len;
1708 pimg->label_len += len;
1709 if (len && fread(q, len, 1, pimg->fh) != 1) {
1710 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1711 return img_BAD;
1713 q[len] = '\0';
1714 return 0;
1717 static int
1718 read_v8label(img *pimg, int common_flag, size_t common_val)
1720 char *q;
1721 size_t del, add;
1722 if (common_flag) {
1723 if (common_val == 0) return 0;
1724 add = del = common_val;
1725 } else {
1726 int ch = GETC(pimg->fh);
1727 if (ch == EOF) {
1728 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1729 return img_BAD;
1731 if (ch != 0x00) {
1732 del = ch >> 4;
1733 add = ch & 0x0f;
1734 } else {
1735 ch = GETC(pimg->fh);
1736 if (ch == EOF) {
1737 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1738 return img_BAD;
1740 if (ch != 0xff) {
1741 del = ch;
1742 } else {
1743 del = get32(pimg->fh);
1744 if (ferror(pimg->fh)) {
1745 img_errno = IMG_READERROR;
1746 return img_BAD;
1749 ch = GETC(pimg->fh);
1750 if (ch == EOF) {
1751 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1752 return img_BAD;
1754 if (ch != 0xff) {
1755 add = ch;
1756 } else {
1757 add = get32(pimg->fh);
1758 if (ferror(pimg->fh)) {
1759 img_errno = IMG_READERROR;
1760 return img_BAD;
1765 if (add > del && !check_label_space(pimg, pimg->label_len + add - del + 1)) {
1766 img_errno = IMG_OUTOFMEMORY;
1767 return img_BAD;
1770 if (del > pimg->label_len) {
1771 img_errno = IMG_BADFORMAT;
1772 return img_BAD;
1774 pimg->label_len -= del;
1775 q = pimg->label_buf + pimg->label_len;
1776 pimg->label_len += add;
1777 if (add && fread(q, add, 1, pimg->fh) != 1) {
1778 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1779 return img_BAD;
1781 q[add] = '\0';
1782 return 0;
1785 static int img_read_item_new(img *pimg, img_point *p);
1786 static int img_read_item_v3to7(img *pimg, img_point *p);
1787 static int img_read_item_ancient(img *pimg, img_point *p);
1788 static int img_read_item_ascii_wrapper(img *pimg, img_point *p);
1789 static int img_read_item_ascii(img *pimg, img_point *p);
1792 img_read_item(img *pimg, img_point *p)
1794 pimg->flags = 0;
1796 if (pimg->version >= 8) {
1797 return img_read_item_new(pimg, p);
1798 } else if (pimg->version >= 3) {
1799 return img_read_item_v3to7(pimg, p);
1800 } else if (pimg->version >= 1) {
1801 return img_read_item_ancient(pimg, p);
1802 } else {
1803 return img_read_item_ascii_wrapper(pimg, p);
1807 static int
1808 img_read_item_new(img *pimg, img_point *p)
1810 int result;
1811 int opt;
1812 pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
1813 if (pimg->pending >= 0x40) {
1814 if (pimg->pending == PENDING_XSECT_END) {
1815 pimg->pending = 0;
1816 return img_XSECT_END;
1818 *p = pimg->mv;
1819 pimg->flags = (int)(pimg->pending) & 0x3f;
1820 pimg->pending = 0;
1821 return img_LINE;
1823 again3: /* label to goto if we get a prefix, date, or lrud */
1824 pimg->label = pimg->label_buf;
1825 opt = GETC(pimg->fh);
1826 if (opt == EOF) {
1827 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1828 return img_BAD;
1830 if (opt >> 6 == 0) {
1831 if (opt <= 4) {
1832 if (opt == 0 && pimg->style == 0)
1833 return img_STOP; /* end of data marker */
1834 /* STYLE */
1835 pimg->style = opt;
1836 goto again3;
1838 if (opt >= 0x10) {
1839 switch (opt) {
1840 case 0x10: { /* No date info */
1841 #if IMG_API_VERSION == 0
1842 pimg->date1 = pimg->date2 = 0;
1843 #else /* IMG_API_VERSION == 1 */
1844 pimg->days1 = pimg->days2 = -1;
1845 #endif
1846 break;
1848 case 0x11: { /* Single date */
1849 int days1 = (int)getu16(pimg->fh);
1850 #if IMG_API_VERSION == 0
1851 pimg->date2 = pimg->date1 = (days1 - DAYS_1900) * SECS_PER_DAY;
1852 #else /* IMG_API_VERSION == 1 */
1853 pimg->days2 = pimg->days1 = days1;
1854 #endif
1855 break;
1857 case 0x12: { /* Date range (short) */
1858 int days1 = (int)getu16(pimg->fh);
1859 int days2 = days1 + GETC(pimg->fh) + 1;
1860 #if IMG_API_VERSION == 0
1861 pimg->date1 = (days1 - DAYS_1900) * SECS_PER_DAY;
1862 pimg->date2 = (days2 - DAYS_1900) * SECS_PER_DAY;
1863 #else /* IMG_API_VERSION == 1 */
1864 pimg->days1 = days1;
1865 pimg->days2 = days2;
1866 #endif
1867 break;
1869 case 0x13: { /* Date range (long) */
1870 int days1 = (int)getu16(pimg->fh);
1871 int days2 = (int)getu16(pimg->fh);
1872 #if IMG_API_VERSION == 0
1873 pimg->date1 = (days1 - DAYS_1900) * SECS_PER_DAY;
1874 pimg->date2 = (days2 - DAYS_1900) * SECS_PER_DAY;
1875 #else /* IMG_API_VERSION == 1 */
1876 pimg->days1 = days1;
1877 pimg->days2 = days2;
1878 #endif
1879 break;
1881 case 0x1f: /* Error info */
1882 pimg->n_legs = get32(pimg->fh);
1883 pimg->length = get32(pimg->fh) / 100.0;
1884 pimg->E = get32(pimg->fh) / 100.0;
1885 pimg->H = get32(pimg->fh) / 100.0;
1886 pimg->V = get32(pimg->fh) / 100.0;
1887 return img_ERROR_INFO;
1888 case 0x30: case 0x31: /* LRUD */
1889 case 0x32: case 0x33: /* Big LRUD! */
1890 if (read_v8label(pimg, 0, 0) == img_BAD) return img_BAD;
1891 pimg->flags = (int)opt & 0x01;
1892 if (opt < 0x32) {
1893 pimg->l = get16(pimg->fh) / 100.0;
1894 pimg->r = get16(pimg->fh) / 100.0;
1895 pimg->u = get16(pimg->fh) / 100.0;
1896 pimg->d = get16(pimg->fh) / 100.0;
1897 } else {
1898 pimg->l = get32(pimg->fh) / 100.0;
1899 pimg->r = get32(pimg->fh) / 100.0;
1900 pimg->u = get32(pimg->fh) / 100.0;
1901 pimg->d = get32(pimg->fh) / 100.0;
1903 if (!stn_included(pimg)) {
1904 return img_XSECT_END;
1906 /* If this is the last cross-section in this passage, set
1907 * pending so we return img_XSECT_END next time. */
1908 if (pimg->flags & 0x01) {
1909 pimg->pending = PENDING_XSECT_END;
1910 pimg->flags &= ~0x01;
1912 return img_XSECT;
1913 default: /* 0x25 - 0x2f and 0x34 - 0x3f are currently unallocated. */
1914 img_errno = IMG_BADFORMAT;
1915 return img_BAD;
1917 goto again3;
1919 if (opt != 15) {
1920 /* 1-14 and 16-31 reserved */
1921 img_errno = IMG_BADFORMAT;
1922 return img_BAD;
1924 result = img_MOVE;
1925 } else if (opt >= 0x80) {
1926 if (read_v8label(pimg, 0, 0) == img_BAD) return img_BAD;
1928 result = img_LABEL;
1930 if (!stn_included(pimg)) {
1931 if (!skip_coord(pimg->fh)) return img_BAD;
1932 pimg->pending = 0;
1933 goto again3;
1936 pimg->flags = (int)opt & 0x7f;
1937 } else if ((opt >> 6) == 1) {
1938 if (read_v8label(pimg, opt & 0x20, 0) == img_BAD) return img_BAD;
1940 result = img_LINE;
1942 if (!survey_included(pimg)) {
1943 if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1944 pimg->pending = 15;
1945 goto again3;
1948 if (pimg->pending) {
1949 *p = pimg->mv;
1950 if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1951 pimg->pending = opt;
1952 return img_MOVE;
1954 pimg->flags = (int)opt & 0x1f;
1955 } else {
1956 img_errno = IMG_BADFORMAT;
1957 return img_BAD;
1959 if (!read_coord(pimg->fh, p)) return img_BAD;
1960 pimg->pending = 0;
1961 return result;
1964 static int
1965 img_read_item_v3to7(img *pimg, img_point *p)
1967 int result;
1968 int opt;
1969 pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
1970 if (pimg->pending == PENDING_XSECT_END) {
1971 pimg->pending = 0;
1972 return img_XSECT_END;
1974 if (pimg->pending >= 0x80) {
1975 *p = pimg->mv;
1976 pimg->flags = (int)(pimg->pending) & 0x3f;
1977 pimg->pending = 0;
1978 return img_LINE;
1980 again3: /* label to goto if we get a prefix, date, or lrud */
1981 pimg->label = pimg->label_buf;
1982 opt = GETC(pimg->fh);
1983 if (opt == EOF) {
1984 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1985 return img_BAD;
1987 switch (opt >> 6) {
1988 case 0:
1989 if (opt == 0) {
1990 if (!pimg->label_len) return img_STOP; /* end of data marker */
1991 pimg->label_len = 0;
1992 goto again3;
1994 if (opt < 15) {
1995 /* 1-14 mean trim that many levels from current prefix */
1996 int c;
1997 if (pimg->label_len <= 17) {
1998 /* zero prefix using "0" */
1999 img_errno = IMG_BADFORMAT;
2000 return img_BAD;
2002 /* extra - 1 because label_len points to one past the end */
2003 c = pimg->label_len - 17 - 1;
2004 while (pimg->label_buf[c] != '.' || --opt > 0) {
2005 if (--c < 0) {
2006 /* zero prefix using "0" */
2007 img_errno = IMG_BADFORMAT;
2008 return img_BAD;
2011 c++;
2012 pimg->label_len = c;
2013 goto again3;
2015 if (opt == 15) {
2016 result = img_MOVE;
2017 break;
2019 if (opt >= 0x20) {
2020 switch (opt) {
2021 case 0x20: /* Single date */
2022 if (pimg->version < 7) {
2023 int date1 = get32(pimg->fh);
2024 #if IMG_API_VERSION == 0
2025 pimg->date2 = pimg->date1 = date1;
2026 #else /* IMG_API_VERSION == 1 */
2027 if (date1 != 0) {
2028 pimg->days1 = (date1 - TIME_T_1900) / SECS_PER_DAY;
2029 pimg->days2 = pimg->days1;
2030 } else {
2031 pimg->days2 = pimg->days1 = -1;
2033 #endif
2034 } else {
2035 int days1 = (int)getu16(pimg->fh);
2036 #if IMG_API_VERSION == 0
2037 pimg->date1 = (days1 - DAYS_1900) * SECS_PER_DAY;
2038 pimg->date2 = pimg->date1;
2039 #else /* IMG_API_VERSION == 1 */
2040 pimg->days2 = pimg->days1 = days1;
2041 #endif
2043 break;
2044 case 0x21: /* Date range (short for v7+) */
2045 if (pimg->version < 7) {
2046 INT32_T date1 = get32(pimg->fh);
2047 INT32_T date2 = get32(pimg->fh);
2048 #if IMG_API_VERSION == 0
2049 pimg->date1 = date1;
2050 pimg->date2 = date2;
2051 #else /* IMG_API_VERSION == 1 */
2052 pimg->days1 = (date1 - TIME_T_1900) / SECS_PER_DAY;
2053 pimg->days2 = (date2 - TIME_T_1900) / SECS_PER_DAY;
2054 #endif
2055 } else {
2056 int days1 = (int)getu16(pimg->fh);
2057 int days2 = days1 + GETC(pimg->fh) + 1;
2058 #if IMG_API_VERSION == 0
2059 pimg->date1 = (days1 - DAYS_1900) * SECS_PER_DAY;
2060 pimg->date2 = (days2 - DAYS_1900) * SECS_PER_DAY;
2061 #else /* IMG_API_VERSION == 1 */
2062 pimg->days1 = days1;
2063 pimg->days2 = days2;
2064 #endif
2066 break;
2067 case 0x22: /* Error info */
2068 pimg->n_legs = get32(pimg->fh);
2069 pimg->length = get32(pimg->fh) / 100.0;
2070 pimg->E = get32(pimg->fh) / 100.0;
2071 pimg->H = get32(pimg->fh) / 100.0;
2072 pimg->V = get32(pimg->fh) / 100.0;
2073 if (feof(pimg->fh)) {
2074 img_errno = IMG_BADFORMAT;
2075 return img_BAD;
2077 if (ferror(pimg->fh)) {
2078 img_errno = IMG_READERROR;
2079 return img_BAD;
2081 return img_ERROR_INFO;
2082 case 0x23: { /* v7+: Date range (long) */
2083 if (pimg->version < 7) {
2084 img_errno = IMG_BADFORMAT;
2085 return img_BAD;
2087 int days1 = (int)getu16(pimg->fh);
2088 int days2 = (int)getu16(pimg->fh);
2089 if (feof(pimg->fh)) {
2090 img_errno = IMG_BADFORMAT;
2091 return img_BAD;
2093 if (ferror(pimg->fh)) {
2094 img_errno = IMG_READERROR;
2095 return img_BAD;
2097 #if IMG_API_VERSION == 0
2098 pimg->date1 = (days1 - DAYS_1900) * SECS_PER_DAY;
2099 pimg->date2 = (days2 - DAYS_1900) * SECS_PER_DAY;
2100 #else /* IMG_API_VERSION == 1 */
2101 pimg->days1 = days1;
2102 pimg->days2 = days2;
2103 #endif
2104 break;
2106 case 0x24: { /* v7+: No date info */
2107 #if IMG_API_VERSION == 0
2108 pimg->date1 = pimg->date2 = 0;
2109 #else /* IMG_API_VERSION == 1 */
2110 pimg->days1 = pimg->days2 = -1;
2111 #endif
2112 break;
2114 case 0x30: case 0x31: /* LRUD */
2115 case 0x32: case 0x33: /* Big LRUD! */
2116 if (read_v3label(pimg) == img_BAD) return img_BAD;
2117 pimg->flags = (int)opt & 0x01;
2118 if (opt < 0x32) {
2119 pimg->l = get16(pimg->fh) / 100.0;
2120 pimg->r = get16(pimg->fh) / 100.0;
2121 pimg->u = get16(pimg->fh) / 100.0;
2122 pimg->d = get16(pimg->fh) / 100.0;
2123 } else {
2124 pimg->l = get32(pimg->fh) / 100.0;
2125 pimg->r = get32(pimg->fh) / 100.0;
2126 pimg->u = get32(pimg->fh) / 100.0;
2127 pimg->d = get32(pimg->fh) / 100.0;
2129 if (feof(pimg->fh)) {
2130 img_errno = IMG_BADFORMAT;
2131 return img_BAD;
2133 if (ferror(pimg->fh)) {
2134 img_errno = IMG_READERROR;
2135 return img_BAD;
2137 if (!stn_included(pimg)) {
2138 return img_XSECT_END;
2140 /* If this is the last cross-section in this passage, set
2141 * pending so we return img_XSECT_END next time. */
2142 if (pimg->flags & 0x01) {
2143 pimg->pending = PENDING_XSECT_END;
2144 pimg->flags &= ~0x01;
2146 return img_XSECT;
2147 default: /* 0x25 - 0x2f and 0x34 - 0x3f are currently unallocated. */
2148 img_errno = IMG_BADFORMAT;
2149 return img_BAD;
2151 if (feof(pimg->fh)) {
2152 img_errno = IMG_BADFORMAT;
2153 return img_BAD;
2155 if (ferror(pimg->fh)) {
2156 img_errno = IMG_READERROR;
2157 return img_BAD;
2159 goto again3;
2161 /* 16-31 mean remove (n - 15) characters from the prefix */
2162 /* zero prefix using 0 */
2163 if (pimg->label_len <= (size_t)(opt - 15)) {
2164 img_errno = IMG_BADFORMAT;
2165 return img_BAD;
2167 pimg->label_len -= (opt - 15);
2168 goto again3;
2169 case 1:
2170 if (read_v3label(pimg) == img_BAD) return img_BAD;
2172 result = img_LABEL;
2174 if (!stn_included(pimg)) {
2175 if (!skip_coord(pimg->fh)) return img_BAD;
2176 pimg->pending = 0;
2177 goto again3;
2180 pimg->flags = (int)opt & 0x3f;
2181 break;
2182 case 2:
2183 if (read_v3label(pimg) == img_BAD) return img_BAD;
2185 result = img_LINE;
2187 if (!survey_included(pimg)) {
2188 if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
2189 pimg->pending = 15;
2190 goto again3;
2193 if (pimg->pending) {
2194 *p = pimg->mv;
2195 if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
2196 pimg->pending = opt;
2197 return img_MOVE;
2199 pimg->flags = (int)opt & 0x3f;
2200 break;
2201 default:
2202 img_errno = IMG_BADFORMAT;
2203 return img_BAD;
2205 if (!read_coord(pimg->fh, p)) return img_BAD;
2206 pimg->pending = 0;
2207 return result;
2210 static int
2211 img_read_item_ancient(img *pimg, img_point *p)
2213 int result;
2214 static long opt_lookahead = 0;
2215 static img_point pt = { 0.0, 0.0, 0.0 };
2216 long opt;
2218 again: /* label to goto if we get a cross */
2219 pimg->label = pimg->label_buf;
2220 pimg->label[0] = '\0';
2222 if (pimg->version == 1) {
2223 if (opt_lookahead) {
2224 opt = opt_lookahead;
2225 opt_lookahead = 0;
2226 } else {
2227 opt = get32(pimg->fh);
2229 } else {
2230 opt = GETC(pimg->fh);
2233 if (feof(pimg->fh)) {
2234 img_errno = IMG_BADFORMAT;
2235 return img_BAD;
2237 if (ferror(pimg->fh)) {
2238 img_errno = IMG_READERROR;
2239 return img_BAD;
2242 switch (opt) {
2243 case -1: case 0:
2244 return img_STOP; /* end of data marker */
2245 case 1:
2246 /* skip coordinates */
2247 if (!skip_coord(pimg->fh)) {
2248 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
2249 return img_BAD;
2251 goto again;
2252 case 2: case 3: {
2253 size_t len;
2254 result = img_LABEL;
2255 if (!fgets(pimg->label_buf, pimg->buf_len, pimg->fh)) {
2256 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
2257 return img_BAD;
2259 if (pimg->label[0] == '\\') pimg->label++;
2260 len = strlen(pimg->label);
2261 if (len == 0 || pimg->label[len - 1] != '\n') {
2262 img_errno = IMG_BADFORMAT;
2263 return img_BAD;
2265 /* Ignore empty labels in some .3d files (caused by a bug) */
2266 if (len == 1) goto again;
2267 pimg->label[len - 1] = '\0';
2268 pimg->flags = img_SFLAG_UNDERGROUND; /* no flags given... */
2269 if (opt == 2) goto done;
2270 break;
2272 case 6: case 7: {
2273 long len;
2274 result = img_LABEL;
2276 if (opt == 7)
2277 pimg->flags = GETC(pimg->fh);
2278 else
2279 pimg->flags = img_SFLAG_UNDERGROUND; /* no flags given... */
2281 len = get32(pimg->fh);
2283 if (feof(pimg->fh)) {
2284 img_errno = IMG_BADFORMAT;
2285 return img_BAD;
2287 if (ferror(pimg->fh)) {
2288 img_errno = IMG_READERROR;
2289 return img_BAD;
2292 /* Ignore empty labels in some .3d files (caused by a bug) */
2293 if (len == 0) goto again;
2294 if (!check_label_space(pimg, len + 1)) {
2295 img_errno = IMG_OUTOFMEMORY;
2296 return img_BAD;
2298 if (fread(pimg->label_buf, len, 1, pimg->fh) != 1) {
2299 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
2300 return img_BAD;
2302 pimg->label_buf[len] = '\0';
2303 break;
2305 case 4:
2306 result = img_MOVE;
2307 break;
2308 case 5:
2309 result = img_LINE;
2310 break;
2311 default:
2312 switch ((int)opt & 0xc0) {
2313 case 0x80:
2314 pimg->flags = (int)opt & 0x3f;
2315 result = img_LINE;
2316 break;
2317 case 0x40: {
2318 char *q;
2319 pimg->flags = (int)opt & 0x3f;
2320 result = img_LABEL;
2321 if (!fgets(pimg->label_buf, pimg->buf_len, pimg->fh)) {
2322 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
2323 return img_BAD;
2325 q = pimg->label_buf + strlen(pimg->label_buf) - 1;
2326 /* Ignore empty-labels in some .3d files (caused by a bug) */
2327 if (q == pimg->label_buf) goto again;
2328 if (*q != '\n') {
2329 img_errno = IMG_BADFORMAT;
2330 return img_BAD;
2332 *q = '\0';
2333 break;
2335 default:
2336 img_errno = IMG_BADFORMAT;
2337 return img_BAD;
2339 break;
2342 if (!read_coord(pimg->fh, &pt)) return img_BAD;
2344 if (result == img_LABEL && !stn_included(pimg)) {
2345 goto again;
2348 done:
2349 *p = pt;
2351 if (result == img_MOVE && pimg->version == 1) {
2352 /* peek at next code and see if it's an old-style label */
2353 opt_lookahead = get32(pimg->fh);
2355 if (feof(pimg->fh)) {
2356 img_errno = IMG_BADFORMAT;
2357 return img_BAD;
2359 if (ferror(pimg->fh)) {
2360 img_errno = IMG_READERROR;
2361 return img_BAD;
2364 if (opt_lookahead == 2) return img_read_item_ancient(pimg, p);
2367 return result;
2370 static int
2371 img_read_item_ascii_wrapper(img *pimg, img_point *p)
2373 /* We need to set the default locale for fscanf() to work on
2374 * numbers with "." as decimal point. */
2375 int result;
2376 char * current_locale = my_strdup(setlocale(LC_NUMERIC, NULL));
2377 setlocale(LC_NUMERIC, "C");
2378 result = img_read_item_ascii(pimg, p);
2379 setlocale(LC_NUMERIC, current_locale);
2380 free(current_locale);
2381 return result;
2384 /* Handle all ASCII formats. */
2385 static int
2386 img_read_item_ascii(img *pimg, img_point *p)
2388 int result;
2389 pimg->label = pimg->label_buf;
2390 if (pimg->version == 0) {
2391 ascii_again:
2392 pimg->label[0] = '\0';
2393 if (feof(pimg->fh)) return img_STOP;
2394 if (pimg->pending) {
2395 pimg->pending = 0;
2396 result = img_LINE;
2397 } else {
2398 char cmd[7];
2399 /* Stop if nothing found */
2400 if (fscanf(pimg->fh, "%6s", cmd) < 1) return img_STOP;
2401 if (strcmp(cmd, "move") == 0)
2402 result = img_MOVE;
2403 else if (strcmp(cmd, "draw") == 0)
2404 result = img_LINE;
2405 else if (strcmp(cmd, "line") == 0) {
2406 /* set flag to indicate to process second triplet as LINE */
2407 pimg->pending = 1;
2408 result = img_MOVE;
2409 } else if (strcmp(cmd, "cross") == 0) {
2410 if (fscanf(pimg->fh, "%lf%lf%lf", &p->x, &p->y, &p->z) < 3) {
2411 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
2412 return img_BAD;
2414 goto ascii_again;
2415 } else if (strcmp(cmd, "name") == 0) {
2416 size_t off = 0;
2417 int ch = GETC(pimg->fh);
2418 if (ch == ' ') ch = GETC(pimg->fh);
2419 while (ch != ' ') {
2420 if (ch == '\n' || ch == EOF) {
2421 img_errno = ferror(pimg->fh) ? IMG_READERROR : IMG_BADFORMAT;
2422 return img_BAD;
2424 if (off == pimg->buf_len) {
2425 if (!check_label_space(pimg, pimg->buf_len * 2)) {
2426 img_errno = IMG_OUTOFMEMORY;
2427 return img_BAD;
2430 pimg->label_buf[off++] = ch;
2431 ch = GETC(pimg->fh);
2433 pimg->label_buf[off] = '\0';
2435 pimg->label = pimg->label_buf;
2436 if (pimg->label[0] == '\\') pimg->label++;
2438 pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
2440 result = img_LABEL;
2441 } else {
2442 img_errno = IMG_BADFORMAT;
2443 return img_BAD; /* unknown keyword */
2447 if (fscanf(pimg->fh, "%lf%lf%lf", &p->x, &p->y, &p->z) < 3) {
2448 img_errno = ferror(pimg->fh) ? IMG_READERROR : IMG_BADFORMAT;
2449 return img_BAD;
2452 if (result == img_LABEL && !stn_included(pimg)) {
2453 goto ascii_again;
2456 return result;
2457 } else if (pimg->version == VERSION_SURVEX_POS) {
2458 /* Survex .pos file */
2459 int ch;
2460 size_t off;
2461 pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
2462 againpos:
2463 while (fscanf(pimg->fh, "(%lf,%lf,%lf )", &p->x, &p->y, &p->z) != 3) {
2464 if (ferror(pimg->fh)) {
2465 img_errno = IMG_READERROR;
2466 return img_BAD;
2468 if (feof(pimg->fh)) return img_STOP;
2469 if (pimg->pending) {
2470 img_errno = IMG_BADFORMAT;
2471 return img_BAD;
2473 pimg->pending = 1;
2474 /* ignore rest of line */
2475 do {
2476 ch = GETC(pimg->fh);
2477 } while (ch != '\n' && ch != '\r' && ch != EOF);
2480 pimg->label_buf[0] = '\0';
2481 do {
2482 ch = GETC(pimg->fh);
2483 } while (ch == ' ' || ch == '\t');
2484 if (ch == '\n' || ch == EOF) {
2485 /* If there's no label, set img_SFLAG_ANON. */
2486 pimg->flags |= img_SFLAG_ANON;
2487 return img_LABEL;
2489 pimg->label_buf[0] = ch;
2490 off = 1;
2491 while (!feof(pimg->fh)) {
2492 if (!fgets(pimg->label_buf + off, pimg->buf_len - off, pimg->fh)) {
2493 img_errno = IMG_READERROR;
2494 return img_BAD;
2497 off += strlen(pimg->label_buf + off);
2498 if (off && pimg->label_buf[off - 1] == '\n') {
2499 pimg->label_buf[off - 1] = '\0';
2500 break;
2502 if (!check_label_space(pimg, pimg->buf_len * 2)) {
2503 img_errno = IMG_OUTOFMEMORY;
2504 return img_BAD;
2508 pimg->label = pimg->label_buf;
2510 if (pimg->label[0] == '\\') pimg->label++;
2512 if (!stn_included(pimg)) goto againpos;
2514 return img_LABEL;
2515 } else if (pimg->version == VERSION_COMPASS_PLT) {
2516 /* Compass .plt file */
2517 if ((pimg->pending & ~PENDING_HAD_XSECT) > 0) {
2518 /* -1 signals we've entered the first survey we want to read, and
2519 * need to fudge lots if the first action is 'D' or 'd'...
2521 pimg->flags = 0;
2522 if (pimg->pending & PENDING_XSECT_END) {
2523 /* pending XSECT_END */
2524 pimg->pending &= ~PENDING_XSECT_END;
2525 return img_XSECT_END;
2527 if (pimg->pending & PENDING_XSECT) {
2528 /* pending XSECT */
2529 pimg->pending &= ~PENDING_XSECT;
2530 return img_XSECT;
2532 pimg->label[pimg->label_len] = '\0';
2533 if (pimg->pending & PENDING_LINE) {
2534 pimg->flags = (pimg->pending >> PENDING_FLAGS_SHIFT);
2535 pimg->pending &= ((1 << PENDING_FLAGS_SHIFT) - 1) & ~PENDING_LINE;
2536 return img_LINE;
2538 pimg->pending &= ~PENDING_MOVE;
2539 return img_MOVE;
2542 while (1) {
2543 char *line;
2544 char *q;
2545 size_t len = 0;
2546 int ch = GETC(pimg->fh);
2548 switch (ch) {
2549 case '\x1a': case EOF: /* Don't insist on ^Z at end of file */
2550 if (pimg->pending == PENDING_HAD_XSECT) {
2551 ungetc('\x1a', pimg->fh);
2552 pimg->pending = 0;
2553 return img_XSECT_END;
2555 return img_STOP;
2556 case 'X': case 'F': case 'S':
2557 /* bounding boX (marks end of survey), Feature survey, or
2558 * new Section - skip to next survey */
2559 if (pimg->pending == PENDING_HAD_XSECT) {
2560 ungetc(ch, pimg->fh);
2561 pimg->pending = 0;
2562 return img_XSECT_END;
2564 if (pimg->survey) return img_STOP;
2565 skip_to_N:
2566 while (1) {
2567 do {
2568 ch = GETC(pimg->fh);
2569 } while (ch != '\n' && ch != '\r' && ch != EOF);
2570 while (ch == '\n' || ch == '\r') ch = GETC(pimg->fh);
2571 if (ch == 'N') break;
2572 if (ch == '\x1a' || ch == EOF) return img_STOP;
2574 /* FALLTHRU */
2575 case 'N':
2576 compass_plt_new_survey(pimg);
2577 line = getline_alloc(pimg->fh);
2578 if (!line) {
2579 img_errno = IMG_OUTOFMEMORY;
2580 return img_BAD;
2582 while (line[len] > 32) ++len;
2583 if (pimg->label_len == 0) pimg->pending = -1;
2584 if (!check_label_space(pimg, len + 1)) {
2585 osfree(line);
2586 img_errno = IMG_OUTOFMEMORY;
2587 return img_BAD;
2589 pimg->label_len = len;
2590 pimg->label = pimg->label_buf;
2591 memcpy(pimg->label, line, len);
2592 pimg->label[len] = '\0';
2593 /* Handle the survey date. */
2594 while (line[len] && line[len] <= 32) ++len;
2595 if (line[len] == 'D') {
2596 struct tm tm;
2597 memset(&tm, 0, sizeof(tm));
2598 unsigned long v;
2599 q = line + len + 1;
2600 /* NB Order is Month Day Year order. */
2601 v = strtoul(q, &q, 10);
2602 if (v < 1 || v > 12)
2603 goto bad_plt_date;
2604 tm.tm_mon = v - 1;
2606 v = strtoul(q, &q, 10);
2607 if (v < 1 || v > 31)
2608 goto bad_plt_date;
2609 tm.tm_mday = v;
2611 v = strtoul(q, &q, 10);
2612 if (v == ULONG_MAX)
2613 goto bad_plt_date;
2614 if (v < 1900) {
2615 /* "The Year is expected to be the full year like 1994 not
2616 * 94", but "expected to" != "must" so treat a two digit
2617 * year as 19xx.
2619 v += 1900;
2621 if (v == 1901 && tm.tm_mday == 1 && tm.tm_mon == 0) {
2622 /* Compass uses 1/1/1 or 1/1/1901 for "date unknown". */
2623 goto bad_plt_date;
2625 tm.tm_year = v - 1900;
2626 /* We have no indication of what timezone this date is
2627 * in. It's probably local time for whoever processed the
2628 * data, so just assume noon in UTC, which is at least fairly
2629 * central in the possibilities.
2631 tm.tm_hour = 12;
2633 time_t datestamp = mktime_with_tz(&tm, "");
2634 #if IMG_API_VERSION == 0
2635 pimg->date1 = pimg->date2 = datestamp;
2636 #else /* IMG_API_VERSION == 1 */
2637 pimg->days1 = (datestamp - TIME_T_1900) / SECS_PER_DAY;
2638 pimg->days2 = pimg->days1;
2639 #endif
2641 } else {
2642 bad_plt_date:
2643 #if IMG_API_VERSION == 0
2644 pimg->date1 = pimg->date2 = 0;
2645 #else /* IMG_API_VERSION == 1 */
2646 pimg->days1 = pimg->days2 = -1;
2647 #endif
2649 osfree(line);
2650 break;
2651 case 'M':
2652 if (pimg->pending == PENDING_HAD_XSECT) {
2653 pimg->pending = PENDING_XSECT_END;
2655 /* FALLTHRU */
2656 case 'D':
2657 case 'd': {
2658 /* Move or Draw */
2659 unsigned shot_flags = (ch == 'd' ? img_FLAG_SURFACE : 0);
2660 long fpos = -1;
2661 if (pimg->survey && pimg->label_len == 0) {
2662 /* We're only holding onto this line in case the first line
2663 * of the 'N' is a 'D', so skip it for now...
2665 goto skip_to_N;
2667 if (pimg->pending == -1) {
2668 pimg->pending = 0;
2669 if (ch != 'M') {
2670 if (pimg->survey) {
2671 fpos = ftell(pimg->fh) - 1;
2672 fseek(pimg->fh, pimg->start, SEEK_SET);
2673 ch = GETC(pimg->fh);
2674 } else {
2675 /* If a file actually has a 'D' or 'd' before any
2676 * 'M', then pretend the action is 'M' - one of the
2677 * examples in the docs was like this!
2679 ch = 'M';
2683 line = getline_alloc(pimg->fh);
2684 if (!line) {
2685 img_errno = IMG_OUTOFMEMORY;
2686 return img_BAD;
2688 /* Compass stores coordinates as North, East, Up = (y,x,z)! */
2689 if (sscanf(line, "%lf%lf%lf", &p->y, &p->x, &p->z) != 3) {
2690 osfree(line);
2691 if (ferror(pimg->fh)) {
2692 img_errno = IMG_READERROR;
2693 } else {
2694 img_errno = IMG_BADFORMAT;
2696 return img_BAD;
2698 p->x *= METRES_PER_FOOT;
2699 p->y *= METRES_PER_FOOT;
2700 p->z *= METRES_PER_FOOT;
2701 q = strchr(line, 'S');
2702 if (!q) {
2703 osfree(line);
2704 img_errno = IMG_BADFORMAT;
2705 return img_BAD;
2707 ++q;
2708 len = 0;
2709 while (q[len] > ' ') ++len;
2710 /* Add 2 for ' ' before and terminating '\0'. */
2711 if (!check_label_space(pimg, pimg->label_len + len + 2)) {
2712 img_errno = IMG_OUTOFMEMORY;
2713 return img_BAD;
2715 pimg->flags = compass_plt_get_station_flags(pimg, q, len);
2716 pimg->label = pimg->label_buf;
2717 if (pimg->label_len) {
2718 pimg->label[pimg->label_len] = ' ';
2719 memcpy(pimg->label + pimg->label_len + 1, q, len);
2720 pimg->label[pimg->label_len + 1 + len] = '\0';
2721 } else {
2722 memcpy(pimg->label, q, len);
2723 pimg->label[len] = '\0';
2725 q += len;
2727 /* Now read LRUD. Technically, this is optional but virtually
2728 * all PLT files have it (with dummy negative values if no LRUD
2729 * was recorded) and some versions of Compass can't read PLT
2730 * files without it!
2732 while (*q && *q <= ' ') q++;
2733 if (*q == 'P') {
2734 int bytes_used;
2735 ++q;
2736 if (sscanf(q, "%lf%lf%lf%lf%n",
2737 &pimg->l, &pimg->r, &pimg->u, &pimg->d,
2738 &bytes_used) != 4) {
2739 osfree(line);
2740 if (ferror(pimg->fh)) {
2741 img_errno = IMG_READERROR;
2742 } else {
2743 img_errno = IMG_BADFORMAT;
2745 return img_BAD;
2747 if ((pimg->flags & img_SFLAG_UNDERGROUND) &&
2748 (pimg->l >= 0 || pimg->r >= 0 || pimg->u >= 0 || pimg->d >= 0)) {
2749 if (pimg->l >= 0) pimg->l *= METRES_PER_FOOT; else pimg->l = -1;
2750 if (pimg->r >= 0) pimg->r *= METRES_PER_FOOT; else pimg->r = -1;
2751 if (pimg->u >= 0) pimg->u *= METRES_PER_FOOT; else pimg->u = -1;
2752 if (pimg->d >= 0) pimg->d *= METRES_PER_FOOT; else pimg->d = -1;
2753 pimg->pending |= PENDING_XSECT | PENDING_HAD_XSECT;
2754 } else if (pimg->pending == PENDING_HAD_XSECT) {
2755 pimg->pending = PENDING_XSECT_END;
2757 q += bytes_used;
2758 } else {
2759 pimg->l = pimg->r = pimg->u = pimg->d = -1;
2760 if (pimg->pending == PENDING_HAD_XSECT) {
2761 pimg->pending = PENDING_XSECT_END;
2764 while (*q && *q <= ' ') q++;
2765 if (*q == 'I') {
2766 /* Skip distance from entrance. */
2767 do ++q; while (*q && *q <= ' ');
2768 while (*q > ' ') q++;
2769 while (*q && *q <= ' ') q++;
2771 if (*q == 'F') {
2772 /* "Shot Flags". Defined flags we currently ignore here:
2773 * C: "Do not adjust this shot when closing loops."
2774 * X: "you will never see this flag in a plot file."
2776 while (isalpha((unsigned char)*++q)) {
2777 switch (*q) {
2778 case 'L':
2779 shot_flags |= img_FLAG_DUPLICATE;
2780 break;
2781 case 'S':
2782 shot_flags |= img_FLAG_SPLAY;
2783 break;
2784 case 'P':
2785 /* P is "Exclude this shot from plotting", but the
2786 * use suggested in the Compass docs is for surface
2787 * data, and they "[do] not support passage
2788 * modeling".
2790 * Even if it's actually being used for a different
2791 * purpose, Survex programs don't show surface legs
2792 * by default so img_FLAG_SURFACE matches fairly
2793 * well.
2795 shot_flags |= img_FLAG_SURFACE;
2796 break;
2800 if (shot_flags & img_FLAG_SURFACE) {
2801 /* Suppress passage? */
2803 osfree(line);
2804 if (fpos != -1) {
2805 fseek(pimg->fh, fpos, SEEK_SET);
2808 if (pimg->flags < 0) {
2809 pimg->flags = shot_flags;
2810 /* We've already emitted img_LABEL for this station. */
2811 if (ch == 'M') {
2812 return img_MOVE;
2814 return img_LINE;
2816 if (fpos == -1) {
2817 if (ch == 'M') {
2818 pimg->pending |= PENDING_MOVE;
2819 } else {
2820 pimg->pending |= PENDING_LINE | (shot_flags << PENDING_FLAGS_SHIFT);
2824 return img_LABEL;
2826 default:
2827 img_errno = IMG_BADFORMAT;
2828 return img_BAD;
2831 } else {
2832 /* CMAP .xyz file */
2833 char *line = NULL;
2834 char *q;
2835 size_t len;
2837 if (pimg->pending) {
2838 /* pending MOVE or LINE or LABEL or STOP */
2839 int r = pimg->pending - 4;
2840 /* Set label to empty - don't use "" as we adjust label relative
2841 * to label_buf when label_buf is reallocated. */
2842 pimg->label = pimg->label_buf + strlen(pimg->label_buf);
2843 pimg->flags = 0;
2844 if (r == img_LABEL) {
2845 /* nasty magic */
2846 read_xyz_shot_coords(p, pimg->label_buf + 16);
2847 subtract_xyz_shot_deltas(p, pimg->label_buf + 16);
2848 pimg->pending = img_STOP + 4;
2849 return img_MOVE;
2852 pimg->pending = 0;
2854 if (r == img_STOP) {
2855 /* nasty magic */
2856 read_xyz_shot_coords(p, pimg->label_buf + 16);
2857 return img_LINE;
2860 return r;
2863 pimg->label = pimg->label_buf;
2864 do {
2865 osfree(line);
2866 if (feof(pimg->fh)) return img_STOP;
2867 line = getline_alloc(pimg->fh);
2868 if (!line) {
2869 img_errno = IMG_OUTOFMEMORY;
2870 return img_BAD;
2872 } while (line[0] == ' ' || line[0] == '\0');
2873 if (line[0] == '\x1a') return img_STOP;
2875 len = strlen(line);
2876 if (pimg->version == VERSION_CMAP_STATION) {
2877 /* station variant */
2878 if (len < 37) {
2879 osfree(line);
2880 img_errno = IMG_BADFORMAT;
2881 return img_BAD;
2883 memcpy(pimg->label, line, 6);
2884 q = (char *)memchr(pimg->label, ' ', 6);
2885 if (!q) q = pimg->label + 6;
2886 *q = '\0';
2888 read_xyz_station_coords(p, line);
2890 /* FIXME: look at prev for lines (line + 32, 5) */
2891 /* FIXME: duplicate stations... */
2892 return img_LABEL;
2893 } else {
2894 /* Shot variant (VERSION_CMAP_SHOT) */
2895 char old[8], new_[8];
2896 if (len < 61) {
2897 osfree(line);
2898 img_errno = IMG_BADFORMAT;
2899 return img_BAD;
2902 memcpy(old, line, 7);
2903 q = (char *)memchr(old, ' ', 7);
2904 if (!q) q = old + 7;
2905 *q = '\0';
2907 memcpy(new_, line + 7, 7);
2908 q = (char *)memchr(new_, ' ', 7);
2909 if (!q) q = new_ + 7;
2910 *q = '\0';
2912 pimg->flags = img_SFLAG_UNDERGROUND;
2914 if (strcmp(old, new_) == 0) {
2915 pimg->pending = img_MOVE + 4;
2916 read_xyz_shot_coords(p, line);
2917 strcpy(pimg->label, new_);
2918 osfree(line);
2919 return img_LABEL;
2922 if (strcmp(old, pimg->label) == 0) {
2923 pimg->pending = img_LINE + 4;
2924 read_xyz_shot_coords(p, line);
2925 strcpy(pimg->label, new_);
2926 osfree(line);
2927 return img_LABEL;
2930 pimg->pending = img_LABEL + 4;
2931 read_xyz_shot_coords(p, line);
2932 strcpy(pimg->label, new_);
2933 memcpy(pimg->label + 16, line, 70);
2935 osfree(line);
2936 return img_LABEL;
2941 static void
2942 write_coord(FILE *fh, double x, double y, double z)
2944 SVX_ASSERT(fh);
2945 /* Output in cm */
2946 INT32_T X = my_lround(x * 100.0);
2947 INT32_T Y = my_lround(y * 100.0);
2948 INT32_T Z = my_lround(z * 100.0);
2950 put32(X, fh);
2951 put32(Y, fh);
2952 put32(Z, fh);
2955 static int
2956 write_v3label(img *pimg, int opt, const char *s)
2958 size_t len, n, dot;
2960 /* find length of common prefix */
2961 dot = 0;
2962 for (len = 0; s[len] == pimg->label_buf[len] && s[len] != '\0'; len++) {
2963 if (s[len] == '.') dot = len + 1;
2966 SVX_ASSERT(len <= pimg->label_len);
2967 n = pimg->label_len - len;
2968 if (len == 0) {
2969 if (pimg->label_len) PUTC(0, pimg->fh);
2970 } else if (n <= 16) {
2971 if (n) PUTC(n + 15, pimg->fh);
2972 } else if (dot == 0) {
2973 if (pimg->label_len) PUTC(0, pimg->fh);
2974 len = 0;
2975 } else {
2976 const char *p = pimg->label_buf + dot;
2977 n = 1;
2978 for (len = pimg->label_len - dot - 17; len; len--) {
2979 if (*p++ == '.') n++;
2981 if (n <= 14) {
2982 PUTC(n, pimg->fh);
2983 len = dot;
2984 } else {
2985 if (pimg->label_len) PUTC(0, pimg->fh);
2986 len = 0;
2990 n = strlen(s + len);
2991 PUTC(opt, pimg->fh);
2992 if (n < 0xfe) {
2993 PUTC(n, pimg->fh);
2994 } else if (n < 0xffff + 0xfe) {
2995 PUTC(0xfe, pimg->fh);
2996 put16((short)(n - 0xfe), pimg->fh);
2997 } else {
2998 PUTC(0xff, pimg->fh);
2999 put32(n, pimg->fh);
3001 fwrite(s + len, n, 1, pimg->fh);
3003 n += len;
3004 pimg->label_len = n;
3005 if (!check_label_space(pimg, n + 1))
3006 return 0; /* FIXME: distinguish out of memory... */
3007 memcpy(pimg->label_buf + len, s + len, n - len + 1);
3009 return !ferror(pimg->fh);
3012 static int
3013 write_v8label(img *pimg, int opt, int common_flag, size_t common_val,
3014 const char *s)
3016 size_t len, del, add;
3018 /* find length of common prefix */
3019 for (len = 0; s[len] == pimg->label_buf[len] && s[len] != '\0'; len++) {
3022 SVX_ASSERT(len <= pimg->label_len);
3023 del = pimg->label_len - len;
3024 add = strlen(s + len);
3026 if (add == common_val && del == common_val) {
3027 PUTC(opt | common_flag, pimg->fh);
3028 } else {
3029 PUTC(opt, pimg->fh);
3030 if (del <= 15 && add <= 15 && (del || add)) {
3031 PUTC((del << 4) | add, pimg->fh);
3032 } else {
3033 PUTC(0x00, pimg->fh);
3034 if (del < 0xff) {
3035 PUTC(del, pimg->fh);
3036 } else {
3037 PUTC(0xff, pimg->fh);
3038 put32(del, pimg->fh);
3040 if (add < 0xff) {
3041 PUTC(add, pimg->fh);
3042 } else {
3043 PUTC(0xff, pimg->fh);
3044 put32(add, pimg->fh);
3049 if (add)
3050 fwrite(s + len, add, 1, pimg->fh);
3052 pimg->label_len = len + add;
3053 if (add > del && !check_label_space(pimg, pimg->label_len + 1))
3054 return 0; /* FIXME: distinguish out of memory... */
3056 memcpy(pimg->label_buf + len, s + len, add + 1);
3058 return !ferror(pimg->fh);
3061 static void
3062 img_write_item_date_new(img *pimg)
3064 int same, unset;
3065 /* Only write dates when they've changed. */
3066 #if IMG_API_VERSION == 0
3067 if (pimg->date1 == pimg->olddate1 && pimg->date2 == pimg->olddate2)
3068 return;
3070 same = (pimg->date1 == pimg->date2);
3071 unset = (pimg->date1 == 0);
3072 #else /* IMG_API_VERSION == 1 */
3073 if (pimg->days1 == pimg->olddays1 && pimg->days2 == pimg->olddays2)
3074 return;
3076 same = (pimg->days1 == pimg->days2);
3077 unset = (pimg->days1 == -1);
3078 #endif
3080 if (same) {
3081 if (unset) {
3082 PUTC(0x10, pimg->fh);
3083 } else {
3084 PUTC(0x11, pimg->fh);
3085 #if IMG_API_VERSION == 0
3086 put16((pimg->date1 - TIME_T_1900) / SECS_PER_DAY, pimg->fh);
3087 #else /* IMG_API_VERSION == 1 */
3088 put16(pimg->days1, pimg->fh);
3089 #endif
3091 } else {
3092 #if IMG_API_VERSION == 0
3093 int diff = (pimg->date2 - pimg->date1) / SECS_PER_DAY;
3094 if (diff > 0 && diff <= 256) {
3095 PUTC(0x12, pimg->fh);
3096 put16((pimg->date1 - TIME_T_1900) / SECS_PER_DAY, pimg->fh);
3097 PUTC(diff - 1, pimg->fh);
3098 } else {
3099 PUTC(0x13, pimg->fh);
3100 put16((pimg->date1 - TIME_T_1900) / SECS_PER_DAY, pimg->fh);
3101 put16((pimg->date2 - TIME_T_1900) / SECS_PER_DAY, pimg->fh);
3103 #else /* IMG_API_VERSION == 1 */
3104 int diff = pimg->days2 - pimg->days1;
3105 if (diff > 0 && diff <= 256) {
3106 PUTC(0x12, pimg->fh);
3107 put16(pimg->days1, pimg->fh);
3108 PUTC(diff - 1, pimg->fh);
3109 } else {
3110 PUTC(0x13, pimg->fh);
3111 put16(pimg->days1, pimg->fh);
3112 put16(pimg->days2, pimg->fh);
3114 #endif
3116 #if IMG_API_VERSION == 0
3117 pimg->olddate1 = pimg->date1;
3118 pimg->olddate2 = pimg->date2;
3119 #else /* IMG_API_VERSION == 1 */
3120 pimg->olddays1 = pimg->days1;
3121 pimg->olddays2 = pimg->days2;
3122 #endif
3125 static void
3126 img_write_item_date(img *pimg)
3128 int same, unset;
3129 /* Only write dates when they've changed. */
3130 #if IMG_API_VERSION == 0
3131 if (pimg->date1 == pimg->olddate1 && pimg->date2 == pimg->olddate2)
3132 return;
3134 same = (pimg->date1 == pimg->date2);
3135 unset = (pimg->date1 == 0);
3136 #else /* IMG_API_VERSION == 1 */
3137 if (pimg->days1 == pimg->olddays1 && pimg->days2 == pimg->olddays2)
3138 return;
3140 same = (pimg->days1 == pimg->days2);
3141 unset = (pimg->days1 == -1);
3142 #endif
3144 if (same) {
3145 if (img_output_version < 7) {
3146 PUTC(0x20, pimg->fh);
3147 #if IMG_API_VERSION == 0
3148 put32(pimg->date1, pimg->fh);
3149 #else /* IMG_API_VERSION == 1 */
3150 put32((pimg->days1 - DAYS_1900) * SECS_PER_DAY, pimg->fh);
3151 #endif
3152 } else {
3153 if (unset) {
3154 PUTC(0x24, pimg->fh);
3155 } else {
3156 PUTC(0x20, pimg->fh);
3157 #if IMG_API_VERSION == 0
3158 put16((pimg->date1 - TIME_T_1900) / SECS_PER_DAY, pimg->fh);
3159 #else /* IMG_API_VERSION == 1 */
3160 put16(pimg->days1, pimg->fh);
3161 #endif
3164 } else {
3165 if (img_output_version < 7) {
3166 PUTC(0x21, pimg->fh);
3167 #if IMG_API_VERSION == 0
3168 put32(pimg->date1, pimg->fh);
3169 put32(pimg->date2, pimg->fh);
3170 #else /* IMG_API_VERSION == 1 */
3171 put32((pimg->days1 - DAYS_1900) * SECS_PER_DAY, pimg->fh);
3172 put32((pimg->days2 - DAYS_1900) * SECS_PER_DAY, pimg->fh);
3173 #endif
3174 } else {
3175 #if IMG_API_VERSION == 0
3176 int diff = (pimg->date2 - pimg->date1) / SECS_PER_DAY;
3177 if (diff > 0 && diff <= 256) {
3178 PUTC(0x21, pimg->fh);
3179 put16((pimg->date1 - TIME_T_1900) / SECS_PER_DAY, pimg->fh);
3180 PUTC(diff - 1, pimg->fh);
3181 } else {
3182 PUTC(0x23, pimg->fh);
3183 put16((pimg->date1 - TIME_T_1900) / SECS_PER_DAY, pimg->fh);
3184 put16((pimg->date2 - TIME_T_1900) / SECS_PER_DAY, pimg->fh);
3186 #else /* IMG_API_VERSION == 1 */
3187 int diff = pimg->days2 - pimg->days1;
3188 if (diff > 0 && diff <= 256) {
3189 PUTC(0x21, pimg->fh);
3190 put16(pimg->days1, pimg->fh);
3191 PUTC(diff - 1, pimg->fh);
3192 } else {
3193 PUTC(0x23, pimg->fh);
3194 put16(pimg->days1, pimg->fh);
3195 put16(pimg->days2, pimg->fh);
3197 #endif
3200 #if IMG_API_VERSION == 0
3201 pimg->olddate1 = pimg->date1;
3202 pimg->olddate2 = pimg->date2;
3203 #else /* IMG_API_VERSION == 1 */
3204 pimg->olddays1 = pimg->days1;
3205 pimg->olddays2 = pimg->days2;
3206 #endif
3209 static void
3210 img_write_item_new(img *pimg, int code, int flags, const char *s,
3211 double x, double y, double z);
3212 static void
3213 img_write_item_v3to7(img *pimg, int code, int flags, const char *s,
3214 double x, double y, double z);
3215 static void
3216 img_write_item_ancient(img *pimg, int code, int flags, const char *s,
3217 double x, double y, double z);
3219 void
3220 img_write_item(img *pimg, int code, int flags, const char *s,
3221 double x, double y, double z)
3223 if (!pimg) return;
3224 if (pimg->version >= 8) {
3225 img_write_item_new(pimg, code, flags, s, x, y, z);
3226 } else if (pimg->version >= 3) {
3227 img_write_item_v3to7(pimg, code, flags, s, x, y, z);
3228 } else {
3229 img_write_item_ancient(pimg, code, flags, s, x, y, z);
3233 static void
3234 img_write_item_new(img *pimg, int code, int flags, const char *s,
3235 double x, double y, double z)
3237 switch (code) {
3238 case img_LABEL:
3239 write_v8label(pimg, 0x80 | flags, 0, -1, s);
3240 break;
3241 case img_XSECT: {
3242 INT32_T l, r, u, d, max_dim;
3243 img_write_item_date_new(pimg);
3244 l = (INT32_T)my_lround(pimg->l * 100.0);
3245 r = (INT32_T)my_lround(pimg->r * 100.0);
3246 u = (INT32_T)my_lround(pimg->u * 100.0);
3247 d = (INT32_T)my_lround(pimg->d * 100.0);
3248 if (l < 0) l = -1;
3249 if (r < 0) r = -1;
3250 if (u < 0) u = -1;
3251 if (d < 0) d = -1;
3252 max_dim = max(max(l, r), max(u, d));
3253 flags = (flags & img_XFLAG_END) ? 1 : 0;
3254 if (max_dim >= 32768) flags |= 2;
3255 write_v8label(pimg, 0x30 | flags, 0, -1, s);
3256 if (flags & 2) {
3257 /* Big passage! Need to use 4 bytes. */
3258 put32(l, pimg->fh);
3259 put32(r, pimg->fh);
3260 put32(u, pimg->fh);
3261 put32(d, pimg->fh);
3262 } else {
3263 put16(l, pimg->fh);
3264 put16(r, pimg->fh);
3265 put16(u, pimg->fh);
3266 put16(d, pimg->fh);
3268 return;
3270 case img_MOVE:
3271 PUTC(15, pimg->fh);
3272 break;
3273 case img_LINE:
3274 img_write_item_date_new(pimg);
3275 if (pimg->style != pimg->oldstyle) {
3276 switch (pimg->style) {
3277 case img_STYLE_NORMAL:
3278 case img_STYLE_DIVING:
3279 case img_STYLE_CARTESIAN:
3280 case img_STYLE_CYLPOLAR:
3281 case img_STYLE_NOSURVEY:
3282 PUTC(pimg->style, pimg->fh);
3283 break;
3285 pimg->oldstyle = pimg->style;
3287 write_v8label(pimg, 0x40 | flags, 0x20, 0x00, s ? s : "");
3288 break;
3289 default: /* ignore for now */
3290 return;
3292 write_coord(pimg->fh, x, y, z);
3295 static void
3296 img_write_item_v3to7(img *pimg, int code, int flags, const char *s,
3297 double x, double y, double z)
3299 switch (code) {
3300 case img_LABEL:
3301 write_v3label(pimg, 0x40 | flags, s);
3302 break;
3303 case img_XSECT: {
3304 INT32_T l, r, u, d, max_dim;
3305 /* Need at least version 5 for img_XSECT. */
3306 if (pimg->version < 5) return;
3307 img_write_item_date(pimg);
3308 l = (INT32_T)my_lround(pimg->l * 100.0);
3309 r = (INT32_T)my_lround(pimg->r * 100.0);
3310 u = (INT32_T)my_lround(pimg->u * 100.0);
3311 d = (INT32_T)my_lround(pimg->d * 100.0);
3312 if (l < 0) l = -1;
3313 if (r < 0) r = -1;
3314 if (u < 0) u = -1;
3315 if (d < 0) d = -1;
3316 max_dim = max(max(l, r), max(u, d));
3317 flags = (flags & img_XFLAG_END) ? 1 : 0;
3318 if (max_dim >= 32768) flags |= 2;
3319 write_v3label(pimg, 0x30 | flags, s);
3320 if (flags & 2) {
3321 /* Big passage! Need to use 4 bytes. */
3322 put32(l, pimg->fh);
3323 put32(r, pimg->fh);
3324 put32(u, pimg->fh);
3325 put32(d, pimg->fh);
3326 } else {
3327 put16(l, pimg->fh);
3328 put16(r, pimg->fh);
3329 put16(u, pimg->fh);
3330 put16(d, pimg->fh);
3332 return;
3334 case img_MOVE:
3335 PUTC(15, pimg->fh);
3336 break;
3337 case img_LINE:
3338 if (pimg->version >= 4) {
3339 img_write_item_date(pimg);
3341 write_v3label(pimg, 0x80 | flags, s ? s : "");
3342 break;
3343 default: /* ignore for now */
3344 return;
3346 write_coord(pimg->fh, x, y, z);
3349 static void
3350 img_write_item_ancient(img *pimg, int code, int flags, const char *s,
3351 double x, double y, double z)
3353 size_t len;
3354 INT32_T opt = 0;
3355 SVX_ASSERT(pimg->version > 0);
3356 switch (code) {
3357 case img_LABEL:
3358 if (pimg->version == 1) {
3359 /* put a move before each label */
3360 img_write_item_ancient(pimg, img_MOVE, 0, NULL, x, y, z);
3361 put32(2, pimg->fh);
3362 fputsnl(s, pimg->fh);
3363 return;
3365 len = strlen(s);
3366 if (len > 255 || strchr(s, '\n')) {
3367 /* long label - not in early incarnations of v2 format, but few
3368 * 3d files will need these, so better not to force incompatibility
3369 * with a new version I think... */
3370 PUTC(7, pimg->fh);
3371 PUTC(flags, pimg->fh);
3372 put32(len, pimg->fh);
3373 fputs(s, pimg->fh);
3374 } else {
3375 PUTC(0x40 | (flags & 0x3f), pimg->fh);
3376 fputsnl(s, pimg->fh);
3378 opt = 0;
3379 break;
3380 case img_MOVE:
3381 opt = 4;
3382 break;
3383 case img_LINE:
3384 if (pimg->version > 1) {
3385 opt = 0x80 | (flags & 0x3f);
3386 break;
3388 opt = 5;
3389 break;
3390 default: /* ignore for now */
3391 return;
3393 if (pimg->version == 1) {
3394 put32(opt, pimg->fh);
3395 } else {
3396 if (opt) PUTC(opt, pimg->fh);
3398 write_coord(pimg->fh, x, y, z);
3401 /* Write error information for the current traverse
3402 * n_legs is the number of legs in the traverse
3403 * length is the traverse length (in m)
3404 * E is the ratio of the observed misclosure to the theoretical one
3405 * H is the ratio of the observed horizontal misclosure to the theoretical one
3406 * V is the ratio of the observed vertical misclosure to the theoretical one
3408 void
3409 img_write_errors(img *pimg, int n_legs, double length,
3410 double E, double H, double V)
3412 PUTC((pimg->version >= 8 ? 0x1f : 0x22), pimg->fh);
3413 put32(n_legs, pimg->fh);
3414 put32((INT32_T)my_lround(length * 100.0), pimg->fh);
3415 put32((INT32_T)my_lround(E * 100.0), pimg->fh);
3416 put32((INT32_T)my_lround(H * 100.0), pimg->fh);
3417 put32((INT32_T)my_lround(V * 100.0), pimg->fh);
3421 img_close(img *pimg)
3423 int result = 1;
3424 if (pimg) {
3425 if (pimg->fh) {
3426 if (pimg->fRead) {
3427 osfree(pimg->survey);
3428 osfree(pimg->title);
3429 osfree(pimg->cs);
3430 osfree(pimg->datestamp);
3431 } else {
3432 /* write end of data marker */
3433 switch (pimg->version) {
3434 case 1:
3435 put32((INT32_T)-1, pimg->fh);
3436 break;
3437 default:
3438 if (pimg->version <= 7 ?
3439 (pimg->label_len != 0) :
3440 (pimg->style != img_STYLE_NORMAL)) {
3441 PUTC(0, pimg->fh);
3443 /* FALL THROUGH */
3444 case 2:
3445 PUTC(0, pimg->fh);
3446 break;
3449 if (ferror(pimg->fh)) result = 0;
3450 if (pimg->close_func && pimg->close_func(pimg->fh))
3451 result = 0;
3452 if (!result) img_errno = pimg->fRead ? IMG_READERROR : IMG_WRITEERROR;
3454 if (pimg->data) {
3455 switch (pimg->version) {
3456 case VERSION_COMPASS_PLT:
3457 compass_plt_free_data(pimg);
3458 break;
3459 default:
3460 osfree(pimg->data);
3463 osfree(pimg->label_buf);
3464 osfree(pimg->filename_opened);
3465 osfree(pimg);
3467 return result;
3470 img_datum
3471 img_parse_compass_datum_string(const char *s, size_t len)
3473 #define EQ(S) len == LITLEN(S) && memcmp(s, S, LITLEN(S)) == 0
3474 /* First check the three which seem to be commonly used in Compass data. */
3475 if (EQ("WGS 1984"))
3476 return img_DATUM_WGS84;
3477 if (EQ("North American 1927"))
3478 return img_DATUM_NAD27;
3479 if (EQ("North American 1983"))
3480 return img_DATUM_NAD83;
3482 if (EQ("Adindan"))
3483 return img_DATUM_ADINDAN;
3484 if (EQ("Arc 1950"))
3485 return img_DATUM_ARC1950;
3486 if (EQ("Arc 1960"))
3487 return img_DATUM_ARC1960;
3488 if (EQ("Cape"))
3489 return img_DATUM_CAPE;
3490 if (EQ("European 1950"))
3491 return img_DATUM_EUROPEAN1950;
3492 if (EQ("Geodetic 1949"))
3493 return img_DATUM_NZGD49;
3494 if (EQ("Hu Tzu Shan"))
3495 return img_DATUM_HUTZUSHAN1950;
3496 if (EQ("Indian"))
3497 return img_DATUM_INDIAN1960;
3498 if (EQ("Tokyo"))
3499 return img_DATUM_TOKYO;
3500 if (EQ("WGS 1972"))
3501 return img_DATUM_WGS72;
3503 return img_DATUM_UNKNOWN;
3506 char *
3507 img_compass_utm_proj_str(img_datum datum, int utm_zone)
3509 int epsg_code = 0;
3510 const char* proj4_datum = NULL;
3512 switch (datum) {
3513 case img_DATUM_UNKNOWN:
3514 break;
3515 case img_DATUM_ADINDAN:
3516 if (utm_zone >= 35 && utm_zone <= 38)
3517 epsg_code = 20100 + utm_zone;
3518 break;
3519 case img_DATUM_ARC1950:
3520 if (utm_zone >= -36 && utm_zone <= -34)
3521 epsg_code = 20900 - utm_zone;
3522 break;
3523 case img_DATUM_ARC1960:
3524 if (utm_zone >= -37 && utm_zone <= -35)
3525 epsg_code = 21000 - utm_zone;
3526 break;
3527 case img_DATUM_CAPE:
3528 if (utm_zone >= -36 && utm_zone <= -34)
3529 epsg_code = 22200 - utm_zone;
3530 break;
3531 case img_DATUM_EUROPEAN1950:
3532 if (utm_zone >= 28 && utm_zone <= 38)
3533 epsg_code = 23000 + utm_zone;
3534 break;
3535 case img_DATUM_NZGD49:
3536 if (utm_zone >= 58)
3537 epsg_code = 27200 + utm_zone;
3538 break;
3539 case img_DATUM_HUTZUSHAN1950:
3540 if (utm_zone == 51)
3541 epsg_code = 3829;
3542 break;
3543 case img_DATUM_INDIAN1960:
3544 if (utm_zone >= 48 && utm_zone <= 49)
3545 epsg_code = 3100 + utm_zone;
3546 break;
3547 case img_DATUM_NAD27:
3548 if (utm_zone > 0 && utm_zone <= 23)
3549 epsg_code = 26700 + utm_zone;
3550 else if (utm_zone >= 59)
3551 epsg_code = 3311 + utm_zone;
3552 else
3553 proj4_datum = "NAD27";
3554 break;
3555 case img_DATUM_NAD83:
3556 if (utm_zone > 0 && utm_zone <= 23)
3557 epsg_code = 26900 + utm_zone;
3558 else if (utm_zone == 24)
3559 epsg_code = 9712;
3560 else if (utm_zone >= 59)
3561 epsg_code = 3313 + utm_zone;
3562 else
3563 proj4_datum = "NAD83";
3564 break;
3565 case img_DATUM_TOKYO:
3566 if (utm_zone >= 51 && utm_zone <= 55)
3567 epsg_code = 3041 + utm_zone;
3568 break;
3569 case img_DATUM_WGS72:
3570 if (utm_zone > 0)
3571 epsg_code = 32200 + utm_zone;
3572 else
3573 epsg_code = 32300 - utm_zone;
3574 break;
3575 case img_DATUM_WGS84:
3576 if (utm_zone > 0)
3577 epsg_code = 32600 + utm_zone;
3578 else
3579 epsg_code = 32700 - utm_zone;
3580 break;
3583 if (epsg_code) {
3584 char *proj_str = xosmalloc(11);
3585 if (!proj_str) {
3586 img_errno = IMG_OUTOFMEMORY;
3587 return NULL;
3589 sprintf(proj_str, "EPSG:%d", epsg_code);
3590 return proj_str;
3593 if (proj4_datum) {
3594 char *proj_str;
3595 size_t len = strlen(proj4_datum) + 52 + 2 + 1;
3596 const char *south = "";
3597 if (utm_zone < 0) {
3598 utm_zone = -utm_zone;
3599 south = "+south ";
3600 len += 7;
3602 proj_str = xosmalloc(len);
3603 if (!proj_str) {
3604 img_errno = IMG_OUTOFMEMORY;
3605 return NULL;
3607 sprintf(proj_str,
3608 "+proj=utm +zone=%d %s+datum=%s +units=m +no_defs +type=crs",
3609 utm_zone, south, proj4_datum);
3610 return proj_str;
3613 return NULL;