Show location for backsights out of tolerance
[survex.git] / src / img.c
blob7a4b910a60726c1525fdf11d566f18ff4b35adda
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 <stdarg.h>
30 #include <stddef.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <time.h>
36 #include "img.h"
38 #define TIMENA "?"
39 #ifdef IMG_HOSTED
40 # define INT32_T int32_t
41 # define UINT32_T uint32_t
42 # define SNPRINTF snprintf
43 # include "debug.h"
44 # include "filelist.h"
45 # include "filename.h"
46 # include "message.h"
47 # include "useful.h"
48 # define TIMEFMT msg(/*%a,%Y.%m.%d %H:%M:%S %Z*/107)
49 #else
50 # if defined HAVE_STDINT_H || \
51 (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) || \
52 (defined __cplusplus && __cplusplus >= 201103L)
53 # include <stdint.h>
54 # define INT32_T int32_t
55 # define UINT32_T uint32_t
56 # else
57 # include <limits.h>
58 # if INT_MAX >= 2147483647
59 # define INT32_T int
60 # define UINT32_T unsigned
61 # else
62 # define INT32_T long
63 # define UINT32_T unsigned long
64 # endif
65 # endif
66 # if defined HAVE_SNPRINTF || \
67 (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) || \
68 (defined __cplusplus && __cplusplus >= 201103L)
69 # define SNPRINTF snprintf
70 # else
71 # define SNPRINTF my_snprintf
72 static int my_snprintf(char *s, size_t size, const char *format, ...) {
73 int result;
74 va_list ap;
75 va_start(ap, format);
76 (void)size; /* The buffer passed should always be large enough. */
77 result = vsprintf(s, format, ap);
78 va_end(ap);
79 return result;
81 # endif
82 # define TIMEFMT "%a,%Y.%m.%d %H:%M:%S %Z"
83 # define EXT_SVX_3D "3d"
84 # define FNM_SEP_EXT '.'
85 # define METRES_PER_FOOT 0.3048 /* exact value */
86 # define xosmalloc(L) malloc((L))
87 # define xosrealloc(L,S) realloc((L),(S))
88 # define osfree(P) free((P))
89 # define osnew(T) (T*)malloc(sizeof(T))
91 /* in IMG_HOSTED mode, this tests if a filename refers to a directory */
92 # define fDirectory(X) 0
93 /* open file FNM with mode MODE, maybe using path PTH and/or extension EXT */
94 /* path isn't used in img.c, but EXT is */
95 # define fopenWithPthAndExt(PTH,FNM,EXT,MODE,X) \
96 ((*(X) = NULL), fopen(FNM,MODE))
97 # ifndef PUTC
98 # define PUTC(C, FH) putc(C, FH)
99 # endif
100 # ifndef GETC
101 # define GETC(FH) getc(FH)
102 # endif
103 # define fputsnl(S, FH) (fputs((S), (FH)) == EOF ? EOF : putc('\n', (FH)))
104 # define SVX_ASSERT(X)
106 #ifdef __cplusplus
107 # include <algorithm>
108 using std::max;
109 using std::min;
110 #else
111 /* Return max/min of two numbers. */
112 /* May be defined already (e.g. by Borland C in stdlib.h) */
113 /* NB Bad news if X or Y has side-effects... */
114 # ifndef max
115 # define max(X, Y) ((X) > (Y) ? (X) : (Y))
116 # endif
117 # ifndef min
118 # define min(X, Y) ((X) < (Y) ? (X) : (Y))
119 # endif
120 #endif
122 static INT32_T
123 get32(FILE *fh)
125 UINT32_T w = GETC(fh);
126 w |= (UINT32_T)GETC(fh) << 8l;
127 w |= (UINT32_T)GETC(fh) << 16l;
128 w |= (UINT32_T)GETC(fh) << 24l;
129 return (INT32_T)w;
132 static void
133 put32(UINT32_T w, FILE *fh)
135 PUTC((char)(w), fh);
136 PUTC((char)(w >> 8l), fh);
137 PUTC((char)(w >> 16l), fh);
138 PUTC((char)(w >> 24l), fh);
141 static short
142 get16(FILE *fh)
144 UINT32_T w = GETC(fh);
145 w |= (UINT32_T)GETC(fh) << 8l;
146 return (short)w;
149 static void
150 put16(short word, FILE *fh)
152 unsigned short w = (unsigned short)word;
153 PUTC((char)(w), fh);
154 PUTC((char)(w >> 8l), fh);
157 static char *
158 baseleaf_from_fnm(const char *fnm)
160 const char *p;
161 const char *q;
162 char * res;
163 size_t len;
165 p = fnm;
166 q = strrchr(p, '/');
167 if (q) p = q + 1;
168 q = strrchr(p, '\\');
169 if (q) p = q + 1;
171 q = strrchr(p, FNM_SEP_EXT);
172 if (q) len = (const char *)q - p; else len = strlen(p);
174 res = (char *)xosmalloc(len + 1);
175 if (!res) return NULL;
176 memcpy(res, p, len);
177 res[len] = '\0';
178 return res;
180 #endif
182 static char * my_strdup(const char *str);
184 static time_t
185 mktime_with_tz(struct tm * tm, const char * tz)
187 time_t r;
188 char * old_tz = getenv("TZ");
189 #ifdef _MSC_VER
190 if (old_tz) {
191 old_tz = my_strdup(old_tz);
192 if (!old_tz)
193 return (time_t)-1;
195 if (_putenv_s("TZ", tz) != 0) {
196 osfree(old_tz);
197 return (time_t)-1;
199 #elif defined HAVE_SETENV
200 if (old_tz) {
201 old_tz = my_strdup(old_tz);
202 if (!old_tz)
203 return (time_t)-1;
205 if (setenv("TZ", tz, 1) < 0) {
206 osfree(old_tz);
207 return (time_t)-1;
209 #else
210 char * p;
211 if (old_tz) {
212 size_t len = strlen(old_tz) + 1;
213 p = (char *)xosmalloc(len + 3);
214 if (!p)
215 return (time_t)-1;
216 memcpy(p, "TZ=", 3);
217 memcpy(p + 3, tz, len);
218 old_tz = p;
220 p = (char *)xosmalloc(strlen(tz) + 4);
221 if (!p) {
222 osfree(old_tz);
223 return (time_t)-1;
225 memcpy(p, "TZ=", 3);
226 strcpy(p + 3, tz);
227 if (putenv(p) != 0) {
228 osfree(p);
229 osfree(old_tz);
230 return (time_t)-1;
232 #define CLEANUP() osfree(p)
233 #endif
234 tzset();
235 r = mktime(tm);
236 if (old_tz) {
237 #ifdef _MSC_VER
238 _putenv_s("TZ", old_tz);
239 #elif !defined HAVE_SETENV
240 putenv(old_tz);
241 #else
242 setenv("TZ", old_tz, 1);
243 #endif
244 osfree(old_tz);
245 } else {
246 #ifdef _MSC_VER
247 _putenv_s("TZ", "");
248 #elif !defined HAVE_UNSETENV
249 putenv((char*)"TZ");
250 #else
251 unsetenv("TZ");
252 #endif
254 #ifdef CLEANUP
255 CLEANUP();
256 #undef CLEANUP
257 #endif
258 return r;
261 static unsigned short
262 getu16(FILE *fh)
264 return (unsigned short)get16(fh);
267 #include <math.h>
269 #if !defined HAVE_LROUND && !defined HAVE_DECL_LROUND
270 /* The autoconf tests are not in use, but C99 and C++11 both added lround(),
271 * so set HAVE_LROUND and HAVE_DECL_LROUND conservatively based on the language
272 * standard version the compiler claims to support. */
273 # if (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) || \
274 (defined __cplusplus && __cplusplus >= 201103L)
275 # define HAVE_LROUND 1
276 # define HAVE_DECL_LROUND 1
277 # endif
278 #endif
280 #ifdef HAVE_LROUND
281 # if defined HAVE_DECL_LROUND && !HAVE_DECL_LROUND
282 /* On older systems, the prototype may be missing. */
283 extern long lround(double);
284 # endif
285 # define my_lround lround
286 #else
287 static long
288 my_lround(double x) {
289 return (x >= 0.0) ? (long)(x + 0.5) : -(long)(0.5 - x);
291 #endif
293 unsigned int img_output_version = IMG_VERSION_MAX;
295 static img_errcode img_errno = IMG_NONE;
297 #define FILEID "Survex 3D Image File"
299 /* Encode extension into integer for fast testing. */
300 #define EXT3(C1, C2, C3) (((C3) << 16) | ((C2) << 8) | (C1))
302 /* Attempt to string paste to ensure we are passed a literal string */
303 #define LITLEN(S) (sizeof(S"") - 1)
305 /* Fake "version numbers" for non-3d formats we can read. */
306 #define VERSION_CMAP_SHOT -4
307 #define VERSION_CMAP_STATION -3
308 #define VERSION_COMPASS_PLT -2
309 #define VERSION_SURVEX_POS -1
311 /* Flags bitwise-or-ed into pending to track XSECTs. */
312 #define PENDING_XSECT_END 0x100
313 #define PENDING_HAD_XSECT 0x001 /* Only for VERSION_COMPASS_PLT */
314 #define PENDING_MOVE 0x002 /* Only for VERSION_COMPASS_PLT */
315 #define PENDING_LINE 0x004 /* Only for VERSION_COMPASS_PLT */
316 #define PENDING_XSECT 0x008 /* Only for VERSION_COMPASS_PLT */
317 #define PENDING_FLAGS_SHIFT 9 /* Only for VERSION_COMPASS_PLT */
319 /* Days from start of 1900 to start of 1970. */
320 #define DAYS_1900 25567
322 /* Start of 1900 in time_t with standard Unix epoch of start of 1970. */
323 #define TIME_T_1900 -2208988800L
325 /* Seconds in a day. */
326 #define SECS_PER_DAY 86400L
328 static unsigned
329 hash_data(const char *s, unsigned len)
331 /* djb2 hash but with an initial value of zero. */
332 unsigned h = 0;
333 while (len) {
334 unsigned char c = (unsigned char)*s++;
335 h = ((h << 5) + h) + c;
336 --len;
338 return h;
341 struct compass_station {
342 struct compass_station *next;
343 unsigned char flags;
344 unsigned char len;
345 char name[1];
348 /* On the first pass, at the start of each survey we run through all the
349 * hash table entries that exist and set this flag.
351 * If this flag is set when we add flags to an existing station we
352 * know it appears in multiple surveys and can infer img_SFLAG_EXPORTED.
354 #define COMPASS_SFLAG_DIFFERENT_SURVEY 0x80
356 #define COMPASS_SFLAG_MASK 0x7f
358 /* How many hash buckets to use (must be a power of 2).
360 * Each bucket is a linked list so this doesn't limit how many entries we can
361 * store, but should be sized based on a plausible estimate of how many
362 * different stations we're likely to see in a single PLT file.
364 #define HASH_BUCKETS 0x2000U
366 static void*
367 compass_plt_allocate_hash(void)
369 struct compass_station_name** htab = xosmalloc(HASH_BUCKETS * sizeof(struct compass_station_name*));
370 if (htab) {
371 unsigned i;
372 for (i = 0; i < HASH_BUCKETS; ++i)
373 htab[i] = NULL;
375 return htab;
378 static int
379 compass_plt_update_station(img *pimg, const char *name, int name_len,
380 unsigned flags)
382 struct compass_station *p;
383 struct compass_station **htab = (struct compass_station**)pimg->data;
384 htab += hash_data(name, name_len) & (HASH_BUCKETS - 1U);
385 for (p = *htab; p; p = p->next) {
386 if (p->len == name_len) {
387 if (memcmp(name, p->name, name_len) == 0) {
388 p->flags |= flags;
389 if (p->flags & COMPASS_SFLAG_DIFFERENT_SURVEY)
390 p->flags |= img_SFLAG_EXPORTED;
391 return 0;
395 p = malloc(offsetof(struct compass_station, name) + name_len);
396 if (!p) return -1;
397 p->flags = flags;
398 p->len = name_len;
399 memcpy(p->name, name, name_len);
400 p->next = *htab;
401 *htab = p;
402 return 0;
405 static void
406 compass_plt_new_survey(img *pimg)
408 struct compass_station **htab = (struct compass_station**)pimg->data;
409 int i = HASH_BUCKETS;
410 while (--i) {
411 struct compass_station *p;
412 for (p = *htab; p; p = p->next) {
413 p->flags |= COMPASS_SFLAG_DIFFERENT_SURVEY;
415 ++htab;
419 static void
420 compass_plt_free_data(img *pimg)
422 struct compass_station **htab = (struct compass_station**)pimg->data;
423 int i = HASH_BUCKETS;
424 while (--i) {
425 struct compass_station *p = *htab;
426 while (p) {
427 struct compass_station *next = p->next;
428 osfree(p);
429 p = next;
431 ++htab;
433 osfree(pimg->data);
434 pimg->data = NULL;
437 static int
438 compass_plt_get_station_flags(img *pimg, const char *name, int name_len)
440 struct compass_station *p;
441 struct compass_station **htab = (struct compass_station**)pimg->data;
442 htab += hash_data(name, name_len) & (HASH_BUCKETS - 1U);
443 for (p = *htab; p; p = p->next) {
444 if (p->len == name_len) {
445 if (memcmp(name, p->name, name_len) == 0) {
446 if (p->flags & COMPASS_SFLAG_DIFFERENT_SURVEY) {
447 p->flags &= ~COMPASS_SFLAG_DIFFERENT_SURVEY;
448 return p->flags;
450 return p->flags | INT_MIN;
454 return -1;
457 static char *
458 my_strdup(const char *str)
460 char *p;
461 size_t len = strlen(str) + 1;
462 p = (char *)xosmalloc(len);
463 if (p) memcpy(p, str, len);
464 return p;
467 #define getline_alloc(FH) getline_alloc_len(FH, NULL)
469 static char *
470 getline_alloc_len(FILE *fh, size_t * p_len)
472 int ch;
473 size_t i = 0;
474 size_t len = 16;
475 char *buf = (char *)xosmalloc(len);
476 if (!buf) return NULL;
478 ch = GETC(fh);
479 while (ch != '\n' && ch != '\r' && ch != EOF) {
480 buf[i++] = ch;
481 if (i == len - 1) {
482 char *p;
483 len += len;
484 p = (char *)xosrealloc(buf, len);
485 if (!p) {
486 osfree(buf);
487 return NULL;
489 buf = p;
491 ch = GETC(fh);
493 if (ch == '\n' || ch == '\r') {
494 int otherone = ch ^ ('\n' ^ '\r');
495 ch = GETC(fh);
496 /* if it's not the other eol character, put it back */
497 if (ch != otherone) ungetc(ch, fh);
499 buf[i] = '\0';
500 if (p_len) *p_len = i;
501 return buf;
504 img_errcode
505 img_error(void)
507 return img_errno;
510 static int
511 check_label_space(img *pimg, size_t len)
513 if (len > pimg->buf_len) {
514 char *b = (char *)xosrealloc(pimg->label_buf, len);
515 if (!b) return 0;
516 pimg->label = (pimg->label - pimg->label_buf) + b;
517 pimg->label_buf = b;
518 pimg->buf_len = len;
520 return 1;
523 /* Check if a station name should be included. */
524 static int
525 stn_included(img *pimg)
527 if (!pimg->survey_len) return 1;
528 size_t l = pimg->survey_len;
529 const char *s = pimg->label_buf;
530 if (strncmp(pimg->survey, s, l + 1) != 0) {
531 return 0;
533 pimg->label += l + 1;
534 return 1;
537 /* Check if a survey name should be included. */
538 static int
539 survey_included(img *pimg)
541 if (!pimg->survey_len) return 1;
542 size_t l = pimg->survey_len;
543 const char *s = pimg->label_buf;
544 if (strncmp(pimg->survey, s, l) != 0 ||
545 !(s[l] == '.' || s[l] == '\0')) {
546 return 0;
548 pimg->label += l;
549 /* skip the dot if there */
550 if (*pimg->label) pimg->label++;
551 return 1;
554 /* Check if a survey name in a buffer should be included.
556 * For "foreign" formats which just have one level of surveys.
558 static int
559 buf_included(img *pimg, const char *buf, size_t len)
561 return pimg->survey_len == len && memcmp(buf, pimg->survey, len) == 0;
564 img *
565 img_open_survey(const char *fnm, const char *survey)
567 img *pimg;
568 FILE *fh;
569 char* filename_opened = NULL;
571 if (fDirectory(fnm)) {
572 img_errno = IMG_DIRECTORY;
573 return NULL;
576 fh = fopenWithPthAndExt("", fnm, EXT_SVX_3D, "rb", &filename_opened);
577 pimg = img_read_stream_survey(fh, fclose,
578 filename_opened ? filename_opened : fnm,
579 survey);
580 if (pimg) {
581 pimg->filename_opened = filename_opened;
582 } else {
583 osfree(filename_opened);
585 return pimg;
588 static int
589 compass_plt_open(img *pimg)
591 int utm_zone = 0;
592 int datum = img_DATUM_UNKNOWN;
593 long fpos;
594 char *from = NULL;
595 int from_len = 0;
597 pimg->version = VERSION_COMPASS_PLT;
598 /* Spaces aren't legal in Compass station names, but dots are, so
599 * use space as the level separator */
600 pimg->separator = ' ';
601 pimg->start = -1;
602 pimg->datestamp = my_strdup(TIMENA);
603 if (!pimg->datestamp) {
604 return IMG_OUTOFMEMORY;
606 pimg->data = compass_plt_allocate_hash();
607 if (!pimg->data) {
608 return IMG_OUTOFMEMORY;
611 /* Read through the whole file first, recording any station flags
612 * (pimg->data), finding where to start reading data from (pimg->start),
613 * and deciding what to report for "title".
615 while (1) {
616 int ch = GETC(pimg->fh);
617 switch (ch) {
618 case '\x1a':
619 fseek(pimg->fh, -1, SEEK_CUR);
620 /* FALL THRU */
621 case EOF:
622 if (pimg->start < 0) {
623 pimg->start = ftell(pimg->fh);
624 } else {
625 fseek(pimg->fh, pimg->start, SEEK_SET);
628 if (datum && utm_zone && abs(utm_zone) <= 60) {
629 /* Map to an EPSG code where we can. */
630 const char* template = "EPSG:%d";
631 int value = 0;
632 switch (datum) {
633 case img_DATUM_NAD27:
634 if (utm_zone < 0) {
635 template = "+proj=utm +zone=%d +datum=NAD27 +south +units=m +no_defs +type=crs";
636 value = -utm_zone;
637 } else if (utm_zone <= 23) {
638 value = 26700 + utm_zone;
639 } else if (utm_zone < 59) {
640 template = "+proj=utm +zone=%d +datum=NAD27 +units=m +no_defs +type=crs";
641 value = utm_zone;
642 } else {
643 value = 3311 + utm_zone;
645 break;
646 case img_DATUM_NAD83:
647 if (utm_zone < 0) {
648 template = "+proj=utm +zone=%d +datum=NAD83 +south +units=m +no_defs +type=crs";
649 value = -utm_zone;
650 } else if (utm_zone <= 23) {
651 value = 26900 + utm_zone;
652 } else if (utm_zone == 24) {
653 value = 9712;
654 } else if (utm_zone < 59) {
655 template = "+proj=utm +zone=%d +datum=NAD83 +units=m +no_defs +type=crs";
656 value = utm_zone;
657 } else {
658 value = 3313 + utm_zone;
660 break;
661 case img_DATUM_WGS84:
662 if (utm_zone > 0) {
663 value = 32600 + utm_zone;
664 } else {
665 value = 32700 - utm_zone;
667 break;
669 if (value) {
670 size_t len = strlen(template) + 4;
671 pimg->cs = (char*)xosmalloc(len);
672 if (!pimg->cs) {
673 goto out_of_memory_error;
675 SNPRINTF(pimg->cs, len, template, value);
679 osfree(from);
680 return 0;
681 case 'S':
682 /* "Section" - in the case where we aren't filtering by survey
683 * (i.e. pimg->survey == NULL): if there's only one non-empty
684 * section name specified, we use it as the title.
686 if (pimg->survey == NULL && (!pimg->title || pimg->title[0])) {
687 char *line = getline_alloc(pimg->fh);
688 if (!line) {
689 goto out_of_memory_error;
691 if (line[0]) {
692 if (pimg->title) {
693 if (strcmp(pimg->title, line) != 0) {
694 /* Two different non-empty section names found. */
695 pimg->title[0] = '\0';
697 osfree(line);
698 } else {
699 pimg->title = line;
701 } else {
702 osfree(line);
704 continue;
706 break;
707 case 'N': {
708 char *line, *q;
709 size_t len;
710 compass_plt_new_survey(pimg);
711 if (pimg->start >= 0) break;
712 fpos = ftell(pimg->fh) - 1;
713 if (!pimg->survey) {
714 /* We're not filtering by survey so just note down the file
715 * offset for the first N command. */
716 pimg->start = fpos;
717 break;
719 line = getline_alloc(pimg->fh);
720 if (!line) {
721 goto out_of_memory_error;
723 len = 0;
724 while (line[len] > 32) ++len;
725 if (!buf_included(pimg, line, len)) {
726 /* Not the survey we are looking for. */
727 osfree(line);
728 continue;
730 q = strchr(line + len, 'C');
731 if (q && q[1]) {
732 osfree(pimg->title);
733 pimg->title = my_strdup(q + 1);
734 } else if (!pimg->title) {
735 pimg->title = my_strdup(pimg->label);
737 osfree(line);
738 if (!pimg->title) {
739 goto out_of_memory_error;
741 pimg->start = fpos;
742 continue;
744 case 'M':
745 case 'D':
746 case 'd': {
747 /* Move or Draw */
748 int command = ch;
749 char *q, *name;
750 unsigned station_flags = 0;
751 int name_len;
752 int not_plotted = (command == 'd');
754 /* Find station name. */
755 do { ch = GETC(pimg->fh); } while (ch >= ' ' && ch != 'S');
757 if (ch != 'S') {
758 /* Leave reporting error to second pass for consistency. */
759 break;
762 name = getline_alloc(pimg->fh);
763 if (!name) {
764 goto out_of_memory_error;
767 name_len = 0;
768 while (name[name_len] > ' ') ++name_len;
769 if (name_len > 255) {
770 /* The spec says "up to 12 characters", we allow up to 255. */
771 osfree(name);
772 osfree(from);
773 return IMG_BADFORMAT;
776 /* Check for the "distance from entrance" field. */
777 q = strchr(name + name_len, 'I');
778 if (q) {
779 double distance_from_entrance;
780 int bytes_used = 0;
781 ++q;
782 if (sscanf(q, "%lf%n",
783 &distance_from_entrance, &bytes_used) == 1 &&
784 distance_from_entrance == 0.0) {
785 /* Infer an entrance. */
786 station_flags |= img_SFLAG_ENTRANCE;
788 q += bytes_used;
789 while (*q && *q <= ' ') q++;
790 } else {
791 q = strchr(name + name_len, 'F');
794 if (q && *q == 'F') {
795 /* "Shot Flags". */
796 while (isalpha((unsigned char)*++q)) {
797 switch (*q) {
798 case 'S':
799 /* The format specification says «The shot is a "splay"
800 * shot, which is a shot from a station to the wall to
801 * define the passage shape.» so we set the wall flag
802 * for the to station.
804 station_flags |= img_SFLAG_WALL;
805 break;
806 case 'P':
807 not_plotted = 1;
808 break;
813 /* Shot flag P (which is also implied by command d) is "Exclude
814 * this shot from plotting", but the use suggested in the Compass
815 * docs is for surface data, and they "[do] not support passage
816 * modeling".
818 * Even if it's actually being used for a different purpose,
819 * Survex programs don't show surface legs by default so the end
820 * effect is at least to not plot as intended.
822 if (command != 'M') {
823 int surface_or_not = not_plotted ? img_SFLAG_SURFACE
824 : img_SFLAG_UNDERGROUND;
825 station_flags |= surface_or_not;
826 if (compass_plt_update_station(pimg, from, from_len,
827 surface_or_not) < 0) {
828 goto out_of_memory_error;
832 if (compass_plt_update_station(pimg, name, name_len,
833 station_flags) < 0) {
834 goto out_of_memory_error;
836 osfree(from);
837 from = name;
838 from_len = name_len;
839 continue;
841 case 'P': {
842 /* Fixed point. */
843 char *line, *q, *name;
844 int name_len;
846 line = getline_alloc(pimg->fh);
847 if (!line) {
848 goto out_of_memory_error;
850 q = line;
851 while (*q && *q <= ' ') q++;
852 name = q;
853 name_len = 0;
854 while (name[name_len] > ' ') ++name_len;
856 if (name_len > 255) {
857 /* The spec says "up to 12 characters", we allow up to 255. */
858 osfree(line);
859 osfree(from);
860 return IMG_BADFORMAT;
863 if (compass_plt_update_station(pimg, name, name_len,
864 img_SFLAG_FIXED) < 0) {
865 goto out_of_memory_error;
868 osfree(line);
869 continue;
871 case 'G': {
872 /* UTM Zone - 1 to 60 for North, -1 to -60 for South. */
873 char *line = getline_alloc(pimg->fh);
874 char *p = line;
875 long v = strtol(p, &p, 10);
876 if (v < -60 || v > 60 || v == 0 || *p > ' ') {
877 osfree(line);
878 continue;
880 if (utm_zone && utm_zone != v) {
881 /* More than one UTM zone specified. */
882 /* FIXME: We could handle this by reprojecting, but then we'd
883 * need access to PROJ from img.
885 utm_zone = 99;
886 } else {
887 utm_zone = v;
889 osfree(line);
890 continue;
892 case 'O': {
893 /* Datum. */
894 int new_datum;
895 char *line = getline_alloc(pimg->fh);
896 if (!line) {
897 goto out_of_memory_error;
899 if (utm_zone == 99) {
900 osfree(line);
901 continue;
904 new_datum = img_parse_compass_datum_string(line, strlen(line));
905 if (new_datum == img_DATUM_UNKNOWN) {
906 utm_zone = 99;
907 } else if (datum == img_DATUM_UNKNOWN) {
908 datum = new_datum;
909 } else if (datum != new_datum) {
910 utm_zone = 99;
913 osfree(line);
914 continue;
917 while (ch != '\n' && ch != '\r') {
918 ch = GETC(pimg->fh);
921 out_of_memory_error:
922 osfree(from);
923 return IMG_OUTOFMEMORY;
926 static int
927 cmap_xyz_open(img *pimg)
929 size_t len;
930 char *line = getline_alloc(pimg->fh);
931 if (!line) {
932 return IMG_OUTOFMEMORY;
935 /* Spaces aren't legal in CMAP station names, but dots are, so
936 * use space as the level separator. */
937 pimg->separator = ' ';
939 /* There doesn't seem to be a spec for what happens after 1999 with cmap
940 * files, so this code allows for:
941 * * 21xx -> xx (up to 2150)
942 * * 21xx -> 1xx (up to 2199)
943 * * full year being specified instead of 2 digits
945 len = strlen(line);
946 if (len > 59) {
947 /* Don't just truncate at column 59, allow for a > 2 digit year. */
948 char * p = strstr(line + len, "Page");
949 if (p) {
950 while (p > line && p[-1] == ' ')
951 --p;
952 *p = '\0';
953 len = p - line;
954 } else {
955 line[59] = '\0';
958 if (len > 45) {
959 /* YY/MM/DD HH:MM */
960 struct tm tm;
961 unsigned long v;
962 char * p;
963 pimg->datestamp = my_strdup(line + 45);
964 p = pimg->datestamp;
965 v = strtoul(p, &p, 10);
966 if (v <= 50) {
967 /* In the absence of a spec for cmap files, assume <= 50 means 21st
968 * century. */
969 v += 2000;
970 } else if (v < 200) {
971 /* Map 100-199 to 21st century. */
972 v += 1900;
974 if (v == ULONG_MAX || *p++ != '/')
975 goto bad_cmap_date;
976 tm.tm_year = v - 1900;
977 v = strtoul(p, &p, 10);
978 if (v < 1 || v > 12 || *p++ != '/')
979 goto bad_cmap_date;
980 tm.tm_mon = v - 1;
981 v = strtoul(p, &p, 10);
982 if (v < 1 || v > 31 || *p++ != ' ')
983 goto bad_cmap_date;
984 tm.tm_mday = v;
985 v = strtoul(p, &p, 10);
986 if (v >= 24 || *p++ != ':')
987 goto bad_cmap_date;
988 tm.tm_hour = v;
989 v = strtoul(p, &p, 10);
990 if (v >= 60)
991 goto bad_cmap_date;
992 tm.tm_min = v;
993 if (*p == ':') {
994 v = strtoul(p + 1, &p, 10);
995 if (v > 60)
996 goto bad_cmap_date;
997 tm.tm_sec = v;
998 } else {
999 tm.tm_sec = 0;
1001 tm.tm_isdst = 0;
1002 /* We have no indication of what timezone this timestamp is in. It's
1003 * probably local time for whoever processed the data, so just assume
1004 * UTC, which is at least fairly central in the possibilities.
1006 pimg->datestamp_numeric = mktime_with_tz(&tm, "");
1007 } else {
1008 pimg->datestamp = my_strdup(TIMENA);
1010 bad_cmap_date:
1011 if (strncmp(line, " Cave Survey Data Processed by CMAP ",
1012 LITLEN(" Cave Survey Data Processed by CMAP ")) != 0) {
1013 if (len > 45) {
1014 line[45] = '\0';
1015 len = 45;
1017 while (len > 2 && line[len - 1] == ' ') --len;
1018 if (len > 2) {
1019 line[len] = '\0';
1020 pimg->title = my_strdup(line + 2);
1023 osfree(line);
1024 if (!pimg->datestamp || !pimg->title) {
1025 return IMG_OUTOFMEMORY;
1027 line = getline_alloc(pimg->fh);
1028 if (!line) {
1029 return IMG_OUTOFMEMORY;
1031 if (line[0] != ' ' || (line[1] != 'S' && line[1] != 'O')) {
1032 return IMG_BADFORMAT;
1034 if (line[1] == 'S') {
1035 pimg->version = VERSION_CMAP_STATION;
1036 } else {
1037 pimg->version = VERSION_CMAP_SHOT;
1039 osfree(line);
1040 line = getline_alloc(pimg->fh);
1041 if (!line) {
1042 return IMG_OUTOFMEMORY;
1044 if (line[0] != ' ' || line[1] != '-') {
1045 return IMG_BADFORMAT;
1047 osfree(line);
1048 pimg->start = ftell(pimg->fh);
1049 return 0;
1052 img *
1053 img_read_stream_survey(FILE *stream, int (*close_func)(FILE*),
1054 const char *fnm,
1055 const char *survey)
1057 img *pimg;
1058 size_t len;
1059 char buf[LITLEN(FILEID) + 9];
1060 int ch;
1061 UINT32_T ext;
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 /* Currently only 3 character extensions are tested below. */
1156 ext = 0;
1157 if (len > 4 && fnm[len - 4] == '.') {
1158 /* Read extension and pack into ext. */
1159 int i;
1160 for (i = 1; i < 4; ++i) {
1161 unsigned char ext_ch = fnm[len - i];
1162 ext = (ext << 8) | tolower(ext_ch);
1165 switch (ext) {
1166 case EXT3('p', 'o', 's'): /* Survex .pos */
1167 pos_file:
1168 pimg->version = VERSION_SURVEX_POS;
1169 pimg->datestamp = my_strdup(TIMENA);
1170 if (!pimg->datestamp) {
1171 goto out_of_memory_error;
1173 pimg->start = 0;
1174 goto successful_return;
1176 case EXT3('p', 'l', 't'): /* Compass .plt */
1177 case EXT3('p', 'l', 'f'): /* Compass .plf */ {
1178 int result;
1179 plt_file:
1180 result = compass_plt_open(pimg);
1181 if (result) {
1182 img_errno = result;
1183 goto error;
1185 goto successful_return;
1188 /* Although these are often referred to as "CMAP .XYZ files", it seems
1189 * that actually, the extension .XYZ isn't used, rather .SHT (shot
1190 * variant, produced by CMAP v16 and later), .UNA (unadjusted) and .ADJ
1191 * (adjusted) extensions are. Since img has long checked for .XYZ, we
1192 * continue to do so in case anyone is relying on it.
1194 case EXT3('s', 'h', 't'): /* CMAP .sht */
1195 case EXT3('a', 'd', 'j'): /* CMAP .adj */
1196 case EXT3('u', 'n', 'a'): /* CMAP .una */
1197 case EXT3('x', 'y', 'z'): /* CMAP .xyz */ {
1198 int result;
1199 xyz_file:
1200 result = cmap_xyz_open(pimg);
1201 if (result) {
1202 img_errno = result;
1203 goto error;
1205 goto successful_return;
1209 if (fread(buf, LITLEN(FILEID) + 1, 1, pimg->fh) != 1 ||
1210 memcmp(buf, FILEID"\n", LITLEN(FILEID) + 1) != 0) {
1211 if (fread(buf + LITLEN(FILEID) + 1, 8, 1, pimg->fh) == 1 &&
1212 memcmp(buf, FILEID"\r\nv0.01\r\n", LITLEN(FILEID) + 9) == 0) {
1213 /* v0 3d file with DOS EOLs */
1214 pimg->version = 0;
1215 goto v03d;
1217 rewind(pimg->fh);
1218 if (buf[1] == ' ') {
1219 if (buf[0] == ' ') {
1220 /* Looks like a CMAP .xyz file ... */
1221 goto xyz_file;
1222 } else if (strchr("ZSNF", buf[0])) {
1223 /* Looks like a Compass .plt file ... */
1224 /* Almost certainly it'll start "Z " */
1225 goto plt_file;
1228 if (buf[0] == '(') {
1229 /* Looks like a Survex .pos file ... */
1230 goto pos_file;
1232 img_errno = IMG_BADFORMAT;
1233 goto error;
1236 /* check file format version */
1237 ch = GETC(pimg->fh);
1238 pimg->version = 0;
1239 if (tolower(ch) == 'b') {
1240 /* binary file iff B/b prefix */
1241 pimg->version = 1;
1242 ch = GETC(pimg->fh);
1244 if (ch != 'v') {
1245 img_errno = IMG_BADFORMAT;
1246 goto error;
1248 ch = GETC(pimg->fh);
1249 if (ch == '0') {
1250 if (fread(buf, 4, 1, pimg->fh) != 1 || memcmp(buf, ".01\n", 4) != 0) {
1251 img_errno = IMG_BADFORMAT;
1252 goto error;
1254 /* nothing special to do */
1255 } else if (pimg->version == 0) {
1256 if (ch < '2' || ch > '0' + IMG_VERSION_MAX || GETC(pimg->fh) != '\n') {
1257 img_errno = IMG_TOONEW;
1258 goto error;
1260 pimg->version = ch - '0';
1261 } else {
1262 img_errno = IMG_BADFORMAT;
1263 goto error;
1266 v03d:
1268 size_t title_len;
1269 char * title = getline_alloc_len(pimg->fh, &title_len);
1270 if (!title) goto out_of_memory_error;
1271 if (pimg->version == 8) {
1272 /* We sneak in extra fields after a zero byte here, containing the
1273 * specified coordinate system (if any) and the level separator
1274 * character. Older readers will just not see these fields (which
1275 * is OK), and this trick avoids us having to bump the 3d format
1276 * version.
1278 size_t real_len = strlen(title);
1279 if (real_len != title_len) {
1280 char * cs = title + real_len + 1;
1281 real_len += strlen(cs) + 1;
1282 if (memcmp(cs, "+init=", 6) == 0) {
1283 /* PROJ 5 and later don't handle +init=esri:<number> but
1284 * that's what cavern used to put in .3d files for
1285 * coordinate systems specified using ESRI codes. We parse
1286 * and convert the strings cavern used to generate and
1287 * convert to the form ESRI:<number> which is still
1288 * understood.
1290 * PROJ 6 and later don't recognise +init=epsg:<number>
1291 * by default and don't apply datum shift terms in some
1292 * cases, so we also convert these to the form
1293 * EPSG:<number>.
1295 char * p = cs + 6;
1296 if (p[4] == ':' && isdigit((unsigned char)p[5]) &&
1297 ((memcmp(p, "epsg", 4) == 0 || memcmp(p, "esri", 4) == 0))) {
1298 p = p + 6;
1299 while (isdigit((unsigned char)*p)) {
1300 ++p;
1302 /* Allow +no_defs to be omitted as it seems to not
1303 * actually do anything with recent PROJ - cavern always
1304 * included it, but other software generating 3d files
1305 * may not.
1307 if (*p == '\0' || strcmp(p, " +no_defs") == 0) {
1308 int i;
1309 cs = cs + 6;
1310 for (i = 0; i < 4; ++i) {
1311 cs[i] = toupper(cs[i]);
1313 *p = '\0';
1316 } else if (memcmp(cs, "+proj=", 6) == 0) {
1317 /* Convert S_MERC and UTM proj strings which cavern used
1318 * to generate to their corresponding EPSG:<number> codes.
1320 char * p = cs + 6;
1321 if (memcmp(p, "utm +ellps=WGS84 +datum=WGS84 +units=m +zone=", 45) == 0) {
1322 int n = 0;
1323 p += 45;
1324 while (isdigit((unsigned char)*p)) {
1325 n = n * 10 + (*p - '0');
1326 ++p;
1328 if (memcmp(p, " +south", 7) == 0) {
1329 p += 7;
1330 n += 32700;
1331 } else {
1332 n += 32600;
1334 /* Allow +no_defs to be omitted as it seems to not
1335 * actually do anything with recent PROJ - cavern always
1336 * included it, but other software generating 3d files
1337 * may not have.
1339 if (*p == '\0' || strcmp(p, " +no_defs") == 0) {
1340 /* There are at least 45 bytes (see memcmp above)
1341 * which is ample for EPSG: plus an integer.
1343 SNPRINTF(cs, 45, "EPSG:%d", n);
1345 } 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) {
1346 p = p + 89;
1347 /* Allow +no_defs to be omitted as it seems to not
1348 * actually do anything with recent PROJ - cavern always
1349 * included it, but other software generating 3d files
1350 * may not have.
1352 if (*p == '\0' || strcmp(p, " +no_defs") == 0) {
1353 strcpy(cs, "EPSG:3857");
1357 if (cs[0]) pimg->cs = my_strdup(cs);
1360 if (real_len != title_len) {
1361 pimg->separator = title[real_len + 1];
1364 if (!pimg->title) {
1365 pimg->title = title;
1366 } else {
1367 osfree(title);
1370 pimg->datestamp = getline_alloc(pimg->fh);
1371 if (!pimg->datestamp) {
1372 out_of_memory_error:
1373 img_errno = IMG_OUTOFMEMORY;
1374 error:
1375 osfree(pimg->title);
1376 osfree(pimg->cs);
1377 osfree(pimg->datestamp);
1378 osfree(pimg->filename_opened);
1379 if (pimg->close_func) pimg->close_func(pimg->fh);
1380 osfree(pimg);
1381 return NULL;
1384 if (pimg->version >= 8) {
1385 int flags = GETC(pimg->fh);
1386 if (flags & img_FFLAG_EXTENDED) pimg->is_extended_elevation = 1;
1387 } else if (pimg->title) {
1388 len = strlen(pimg->title);
1389 if (len > 11 && strcmp(pimg->title + len - 11, " (extended)") == 0) {
1390 pimg->title[len - 11] = '\0';
1391 pimg->is_extended_elevation = 1;
1395 if (pimg->datestamp[0] == '@') {
1396 unsigned long v;
1397 char * p;
1398 errno = 0;
1399 v = strtoul(pimg->datestamp + 1, &p, 10);
1400 if (errno == 0 && *p == '\0')
1401 pimg->datestamp_numeric = v;
1402 /* FIXME: We're assuming here that the C time_t epoch is 1970, which is
1403 * true for Unix-like systems, macOS and Windows, but isn't guaranteed
1404 * by ISO C.
1406 } else {
1407 /* %a,%Y.%m.%d %H:%M:%S %Z */
1408 struct tm tm;
1409 unsigned long v;
1410 char * p = pimg->datestamp;
1411 while (isalpha((unsigned char)*p)) ++p;
1412 if (*p == ',') ++p;
1413 while (isspace((unsigned char)*p)) ++p;
1414 v = strtoul(p, &p, 10);
1415 if (v == ULONG_MAX || *p++ != '.')
1416 goto bad_3d_date;
1417 tm.tm_year = v - 1900;
1418 v = strtoul(p, &p, 10);
1419 if (v < 1 || v > 12 || *p++ != '.')
1420 goto bad_3d_date;
1421 tm.tm_mon = v - 1;
1422 v = strtoul(p, &p, 10);
1423 if (v < 1 || v > 31 || *p++ != ' ')
1424 goto bad_3d_date;
1425 tm.tm_mday = v;
1426 v = strtoul(p, &p, 10);
1427 if (v >= 24 || *p++ != ':')
1428 goto bad_3d_date;
1429 tm.tm_hour = v;
1430 v = strtoul(p, &p, 10);
1431 if (v >= 60 || *p++ != ':')
1432 goto bad_3d_date;
1433 tm.tm_min = v;
1434 v = strtoul(p, &p, 10);
1435 if (v > 60)
1436 goto bad_3d_date;
1437 tm.tm_sec = v;
1438 tm.tm_isdst = 0;
1439 while (isspace((unsigned char)*p)) ++p;
1440 /* p now points to the timezone string.
1442 * However, it's likely to be a string like "BST", and such strings can
1443 * be ambiguous (BST could be UTC+1 or UTC+6), so it is impossible to
1444 * reliably convert in all cases. Just pass what we have to tzset() - if
1445 * it doesn't handle it, UTC will be used.
1447 pimg->datestamp_numeric = mktime_with_tz(&tm, p);
1449 bad_3d_date:
1451 pimg->start = ftell(pimg->fh);
1453 successful_return:
1454 /* If no title from another source, default to the base leafname. */
1455 if (!pimg->title || !pimg->title[0]) {
1456 osfree(pimg->title);
1457 pimg->title = baseleaf_from_fnm(fnm);
1459 return pimg;
1463 img_rewind(img *pimg)
1465 if (!pimg->fRead) {
1466 img_errno = IMG_WRITEERROR;
1467 return 0;
1469 if (fseek(pimg->fh, pimg->start, SEEK_SET) != 0) {
1470 img_errno = IMG_READERROR;
1471 return 0;
1473 clearerr(pimg->fh);
1474 /* [VERSION_SURVEX_POS] already skipped heading line, or there wasn't one
1475 * [version 0] not in the middle of a 'LINE' command
1476 * [version >= 3] not in the middle of turning a LINE into a MOVE */
1477 pimg->pending = 0;
1479 img_errno = IMG_NONE;
1481 /* for version >= 3 we use label_buf to store the prefix for reuse */
1482 /* for VERSION_COMPASS_PLT, 0 value indicates we haven't entered a survey
1483 * yet */
1484 /* for VERSION_CMAP_SHOT, we store the last station here to detect whether
1485 * we MOVE or LINE */
1486 pimg->label_len = 0;
1487 pimg->style = img_STYLE_UNKNOWN;
1488 return 1;
1491 img *
1492 img_open_write_cs(const char *fnm, const char *title, const char *cs, int flags)
1494 if (fDirectory(fnm)) {
1495 img_errno = IMG_DIRECTORY;
1496 return NULL;
1499 return img_write_stream(fopen(fnm, "wb"), fclose, title, cs, flags);
1502 img *
1503 img_write_stream(FILE *stream, int (*close_func)(FILE*),
1504 const char *title, const char *cs, int flags)
1506 time_t tm;
1507 img *pimg;
1509 if (stream == NULL) {
1510 img_errno = IMG_FILENOTFOUND;
1511 return NULL;
1514 pimg = osnew(img);
1515 if (pimg == NULL) {
1516 img_errno = IMG_OUTOFMEMORY;
1517 if (close_func) close_func(stream);
1518 return NULL;
1521 pimg->fh = stream;
1522 pimg->close_func = close_func;
1523 pimg->buf_len = 257;
1524 pimg->label_buf = (char *)xosmalloc(pimg->buf_len);
1525 if (!pimg->label_buf) {
1526 if (pimg->close_func) pimg->close_func(pimg->fh);
1527 osfree(pimg);
1528 img_errno = IMG_OUTOFMEMORY;
1529 return NULL;
1532 pimg->filename_opened = NULL;
1533 pimg->data = NULL;
1535 pimg->separator = (flags & 0x100) ? (flags >> 9) : '.';
1537 /* Output image file header */
1538 fputs("Survex 3D Image File\n", pimg->fh); /* file identifier string */
1539 if (img_output_version < 2) {
1540 pimg->version = 1;
1541 fputs("Bv0.01\n", pimg->fh); /* binary file format version number */
1542 } else {
1543 pimg->version = (img_output_version > IMG_VERSION_MAX) ? IMG_VERSION_MAX : img_output_version;
1544 fprintf(pimg->fh, "v%d\n", pimg->version); /* file format version no. */
1547 fputs(title, pimg->fh);
1548 if (pimg->version < 8 && (flags & img_FFLAG_EXTENDED)) {
1549 /* Older format versions append " (extended)" to the title to mark
1550 * extended elevations. */
1551 size_t len = strlen(title);
1552 if (len < 11 || strcmp(title + len - 11, " (extended)") != 0)
1553 fputs(" (extended)", pimg->fh);
1555 if (pimg->version == 8 && ((cs && *cs) || pimg->separator != '.')) {
1556 /* We sneak in extra fields after a zero byte here, containing the
1557 * specified coordinate system (if any) and the separator character
1558 * if it isn't the default of '.'. Older readers will just not see
1559 * these (which is fine for the coordinate system, and not very
1560 * problematic for the separator), and this trick avoids us having to
1561 * bump the 3d format version.
1563 PUTC('\0', pimg->fh);
1564 if (cs && *cs) fputs(cs, pimg->fh);
1565 if (pimg->separator != '.') {
1566 PUTC('\0', pimg->fh);
1567 PUTC(pimg->separator, pimg->fh);
1570 PUTC('\n', pimg->fh);
1572 if (getenv("SOURCE_DATE_EPOCH")) {
1573 /* Support reproducible builds which create .3d files by not embedding a
1574 * timestamp if SOURCE_DATE_EPOCH is set. We don't bother trying to
1575 * parse the timestamp as it is simpler and seems cleaner to just not
1576 * embed a timestamp at all given the 3d file format already provides
1577 * a way not to.
1579 * See https://reproducible-builds.org/docs/source-date-epoch/
1581 tm = (time_t)-1;
1582 } else {
1583 tm = time(NULL);
1586 if (tm == (time_t)-1) {
1587 fputsnl(TIMENA, pimg->fh);
1588 } else if (pimg->version <= 7) {
1589 char date[256];
1590 /* output current date and time in format specified */
1591 strftime(date, 256, TIMEFMT, localtime(&tm));
1592 fputsnl(date, pimg->fh);
1593 } else {
1594 fprintf(pimg->fh, "@%ld\n", (long)tm);
1597 if (pimg->version >= 8) {
1598 /* Clear bit one in case anyone has been passing true for fBinary. */
1599 flags &=~ 1;
1600 PUTC(flags, pimg->fh);
1603 #if 0
1604 if (img_output_version >= 5) {
1605 static const unsigned char codelengths[32] = {
1606 4, 8, 8, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1607 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1609 fwrite(codelengths, 32, 1, pimg->fh);
1611 #endif
1612 pimg->fRead = 0; /* writing to this file */
1613 img_errno = IMG_NONE;
1615 /* for version >= 3 we use label_buf to store the prefix for reuse */
1616 pimg->label_buf[0] = '\0';
1617 pimg->label_len = 0;
1619 #if IMG_API_VERSION == 0
1620 pimg->date1 = pimg->date2 = 0;
1621 pimg->olddate1 = pimg->olddate2 = 0;
1622 #else /* IMG_API_VERSION == 1 */
1623 pimg->days1 = pimg->days2 = -1;
1624 pimg->olddays1 = pimg->olddays2 = -1;
1625 #endif
1626 pimg->style = pimg->oldstyle = img_STYLE_UNKNOWN;
1628 pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
1630 pimg->n_legs = 0;
1631 pimg->length = 0.0;
1632 pimg->E = pimg->H = pimg->V = 0.0;
1634 /* Don't check for write errors now - let img_close() report them... */
1635 return pimg;
1638 static void
1639 read_xyz_station_coords(img_point *pt, const char *line)
1641 char num[12];
1642 memcpy(num, line + 6, 9);
1643 num[9] = '\0';
1644 pt->x = atof(num) / METRES_PER_FOOT;
1645 memcpy(num, line + 15, 9);
1646 pt->y = atof(num) / METRES_PER_FOOT;
1647 memcpy(num, line + 24, 8);
1648 num[8] = '\0';
1649 pt->z = atof(num) / METRES_PER_FOOT;
1652 static void
1653 read_xyz_shot_coords(img_point *pt, const char *line)
1655 char num[12];
1656 memcpy(num, line + 40, 10);
1657 num[10] = '\0';
1658 pt->x = atof(num) / METRES_PER_FOOT;
1659 memcpy(num, line + 50, 10);
1660 pt->y = atof(num) / METRES_PER_FOOT;
1661 memcpy(num, line + 60, 9);
1662 num[9] = '\0';
1663 pt->z = atof(num) / METRES_PER_FOOT;
1666 static void
1667 subtract_xyz_shot_deltas(img_point *pt, const char *line)
1669 char num[12];
1670 memcpy(num, line + 15, 9);
1671 num[9] = '\0';
1672 pt->x -= atof(num) / METRES_PER_FOOT;
1673 memcpy(num, line + 24, 8);
1674 num[8] = '\0';
1675 pt->y -= atof(num) / METRES_PER_FOOT;
1676 memcpy(num, line + 32, 8);
1677 pt->z -= atof(num) / METRES_PER_FOOT;
1680 static int
1681 read_coord(FILE *fh, img_point *pt)
1683 SVX_ASSERT(fh);
1684 SVX_ASSERT(pt);
1685 pt->x = get32(fh) / 100.0;
1686 pt->y = get32(fh) / 100.0;
1687 pt->z = get32(fh) / 100.0;
1688 if (ferror(fh) || feof(fh)) {
1689 img_errno = feof(fh) ? IMG_BADFORMAT : IMG_READERROR;
1690 return 0;
1692 return 1;
1695 static int
1696 skip_coord(FILE *fh)
1698 return (fseek(fh, 12, SEEK_CUR) == 0);
1701 static int
1702 read_v3label(img *pimg)
1704 char *q;
1705 long len = GETC(pimg->fh);
1706 if (len == EOF) {
1707 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1708 return img_BAD;
1710 if (len == 0xfe) {
1711 len += get16(pimg->fh);
1712 if (feof(pimg->fh)) {
1713 img_errno = IMG_BADFORMAT;
1714 return img_BAD;
1716 if (ferror(pimg->fh)) {
1717 img_errno = IMG_READERROR;
1718 return img_BAD;
1720 } else if (len == 0xff) {
1721 len = get32(pimg->fh);
1722 if (ferror(pimg->fh)) {
1723 img_errno = IMG_READERROR;
1724 return img_BAD;
1726 if (feof(pimg->fh) || len < 0xfe + 0xffff) {
1727 img_errno = IMG_BADFORMAT;
1728 return img_BAD;
1732 if (!check_label_space(pimg, pimg->label_len + len + 1)) {
1733 img_errno = IMG_OUTOFMEMORY;
1734 return img_BAD;
1736 q = pimg->label_buf + pimg->label_len;
1737 pimg->label_len += len;
1738 if (len && fread(q, len, 1, pimg->fh) != 1) {
1739 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1740 return img_BAD;
1742 q[len] = '\0';
1743 return 0;
1746 static int
1747 read_v8label(img *pimg, int common_flag, size_t common_val)
1749 char *q;
1750 size_t del, add;
1751 if (common_flag) {
1752 if (common_val == 0) return 0;
1753 add = del = common_val;
1754 } else {
1755 int ch = GETC(pimg->fh);
1756 if (ch == EOF) {
1757 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1758 return img_BAD;
1760 if (ch != 0x00) {
1761 del = ch >> 4;
1762 add = ch & 0x0f;
1763 } else {
1764 ch = GETC(pimg->fh);
1765 if (ch == EOF) {
1766 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1767 return img_BAD;
1769 if (ch != 0xff) {
1770 del = ch;
1771 } else {
1772 del = get32(pimg->fh);
1773 if (ferror(pimg->fh)) {
1774 img_errno = IMG_READERROR;
1775 return img_BAD;
1778 ch = GETC(pimg->fh);
1779 if (ch == EOF) {
1780 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1781 return img_BAD;
1783 if (ch != 0xff) {
1784 add = ch;
1785 } else {
1786 add = get32(pimg->fh);
1787 if (ferror(pimg->fh)) {
1788 img_errno = IMG_READERROR;
1789 return img_BAD;
1794 if (add > del && !check_label_space(pimg, pimg->label_len + add - del + 1)) {
1795 img_errno = IMG_OUTOFMEMORY;
1796 return img_BAD;
1799 if (del > pimg->label_len) {
1800 img_errno = IMG_BADFORMAT;
1801 return img_BAD;
1803 pimg->label_len -= del;
1804 q = pimg->label_buf + pimg->label_len;
1805 pimg->label_len += add;
1806 if (add && fread(q, add, 1, pimg->fh) != 1) {
1807 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1808 return img_BAD;
1810 q[add] = '\0';
1811 return 0;
1814 static int img_read_item_new(img *pimg, img_point *p);
1815 static int img_read_item_v3to7(img *pimg, img_point *p);
1816 static int img_read_item_ancient(img *pimg, img_point *p);
1817 static int img_read_item_ascii_wrapper(img *pimg, img_point *p);
1818 static int img_read_item_ascii(img *pimg, img_point *p);
1821 img_read_item(img *pimg, img_point *p)
1823 pimg->flags = 0;
1825 if (pimg->version >= 8) {
1826 return img_read_item_new(pimg, p);
1827 } else if (pimg->version >= 3) {
1828 return img_read_item_v3to7(pimg, p);
1829 } else if (pimg->version >= 1) {
1830 return img_read_item_ancient(pimg, p);
1831 } else {
1832 return img_read_item_ascii_wrapper(pimg, p);
1836 static int
1837 img_read_item_new(img *pimg, img_point *p)
1839 int result;
1840 int opt;
1841 pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
1842 if (pimg->pending >= 0x40) {
1843 if (pimg->pending == PENDING_XSECT_END) {
1844 pimg->pending = 0;
1845 return img_XSECT_END;
1847 *p = pimg->mv;
1848 pimg->flags = (int)(pimg->pending) & 0x3f;
1849 pimg->pending = 0;
1850 return img_LINE;
1852 again3: /* label to goto if we get a prefix, date, or lrud */
1853 pimg->label = pimg->label_buf;
1854 opt = GETC(pimg->fh);
1855 if (opt == EOF) {
1856 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1857 return img_BAD;
1859 if (opt >> 6 == 0) {
1860 if (opt <= 4) {
1861 if (opt == 0 && pimg->style == 0)
1862 return img_STOP; /* end of data marker */
1863 /* STYLE */
1864 pimg->style = opt;
1865 goto again3;
1867 if (opt >= 0x10) {
1868 switch (opt) {
1869 case 0x10: { /* No date info */
1870 #if IMG_API_VERSION == 0
1871 pimg->date1 = pimg->date2 = 0;
1872 #else /* IMG_API_VERSION == 1 */
1873 pimg->days1 = pimg->days2 = -1;
1874 #endif
1875 break;
1877 case 0x11: { /* Single date */
1878 int days1 = (int)getu16(pimg->fh);
1879 #if IMG_API_VERSION == 0
1880 pimg->date2 = pimg->date1 = (days1 - DAYS_1900) * SECS_PER_DAY;
1881 #else /* IMG_API_VERSION == 1 */
1882 pimg->days2 = pimg->days1 = days1;
1883 #endif
1884 break;
1886 case 0x12: { /* Date range (short) */
1887 int days1 = (int)getu16(pimg->fh);
1888 int days2 = days1 + GETC(pimg->fh) + 1;
1889 #if IMG_API_VERSION == 0
1890 pimg->date1 = (days1 - DAYS_1900) * SECS_PER_DAY;
1891 pimg->date2 = (days2 - DAYS_1900) * SECS_PER_DAY;
1892 #else /* IMG_API_VERSION == 1 */
1893 pimg->days1 = days1;
1894 pimg->days2 = days2;
1895 #endif
1896 break;
1898 case 0x13: { /* Date range (long) */
1899 int days1 = (int)getu16(pimg->fh);
1900 int days2 = (int)getu16(pimg->fh);
1901 #if IMG_API_VERSION == 0
1902 pimg->date1 = (days1 - DAYS_1900) * SECS_PER_DAY;
1903 pimg->date2 = (days2 - DAYS_1900) * SECS_PER_DAY;
1904 #else /* IMG_API_VERSION == 1 */
1905 pimg->days1 = days1;
1906 pimg->days2 = days2;
1907 #endif
1908 break;
1910 case 0x1f: /* Error info */
1911 pimg->n_legs = get32(pimg->fh);
1912 pimg->length = get32(pimg->fh) / 100.0;
1913 pimg->E = get32(pimg->fh) / 100.0;
1914 pimg->H = get32(pimg->fh) / 100.0;
1915 pimg->V = get32(pimg->fh) / 100.0;
1916 return img_ERROR_INFO;
1917 case 0x30: case 0x31: /* LRUD */
1918 case 0x32: case 0x33: /* Big LRUD! */
1919 if (read_v8label(pimg, 0, 0) == img_BAD) return img_BAD;
1920 pimg->flags = (int)opt & 0x01;
1921 if (opt < 0x32) {
1922 pimg->l = get16(pimg->fh) / 100.0;
1923 pimg->r = get16(pimg->fh) / 100.0;
1924 pimg->u = get16(pimg->fh) / 100.0;
1925 pimg->d = get16(pimg->fh) / 100.0;
1926 } else {
1927 pimg->l = get32(pimg->fh) / 100.0;
1928 pimg->r = get32(pimg->fh) / 100.0;
1929 pimg->u = get32(pimg->fh) / 100.0;
1930 pimg->d = get32(pimg->fh) / 100.0;
1932 if (!stn_included(pimg)) {
1933 return img_XSECT_END;
1935 /* If this is the last cross-section in this passage, set
1936 * pending so we return img_XSECT_END next time. */
1937 if (pimg->flags & 0x01) {
1938 pimg->pending = PENDING_XSECT_END;
1939 pimg->flags &= ~0x01;
1941 return img_XSECT;
1942 default: /* 0x25 - 0x2f and 0x34 - 0x3f are currently unallocated. */
1943 img_errno = IMG_BADFORMAT;
1944 return img_BAD;
1946 goto again3;
1948 if (opt != 15) {
1949 /* 1-14 and 16-31 reserved */
1950 img_errno = IMG_BADFORMAT;
1951 return img_BAD;
1953 result = img_MOVE;
1954 } else if (opt >= 0x80) {
1955 if (read_v8label(pimg, 0, 0) == img_BAD) return img_BAD;
1957 result = img_LABEL;
1959 if (!stn_included(pimg)) {
1960 if (!skip_coord(pimg->fh)) return img_BAD;
1961 pimg->pending = 0;
1962 goto again3;
1965 pimg->flags = (int)opt & 0x7f;
1966 } else if ((opt >> 6) == 1) {
1967 if (read_v8label(pimg, opt & 0x20, 0) == img_BAD) return img_BAD;
1969 result = img_LINE;
1971 if (!survey_included(pimg)) {
1972 if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1973 pimg->pending = 15;
1974 goto again3;
1977 if (pimg->pending) {
1978 *p = pimg->mv;
1979 if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1980 pimg->pending = opt;
1981 return img_MOVE;
1983 pimg->flags = (int)opt & 0x1f;
1984 } else {
1985 img_errno = IMG_BADFORMAT;
1986 return img_BAD;
1988 if (!read_coord(pimg->fh, p)) return img_BAD;
1989 pimg->pending = 0;
1990 return result;
1993 static int
1994 img_read_item_v3to7(img *pimg, img_point *p)
1996 int result;
1997 int opt;
1998 pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
1999 if (pimg->pending == PENDING_XSECT_END) {
2000 pimg->pending = 0;
2001 return img_XSECT_END;
2003 if (pimg->pending >= 0x80) {
2004 *p = pimg->mv;
2005 pimg->flags = (int)(pimg->pending) & 0x3f;
2006 pimg->pending = 0;
2007 return img_LINE;
2009 again3: /* label to goto if we get a prefix, date, or lrud */
2010 pimg->label = pimg->label_buf;
2011 opt = GETC(pimg->fh);
2012 if (opt == EOF) {
2013 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
2014 return img_BAD;
2016 switch (opt >> 6) {
2017 case 0:
2018 if (opt == 0) {
2019 if (!pimg->label_len) return img_STOP; /* end of data marker */
2020 pimg->label_len = 0;
2021 goto again3;
2023 if (opt < 15) {
2024 /* 1-14 mean trim that many levels from current prefix */
2025 int c;
2026 if (pimg->label_len <= 17) {
2027 /* zero prefix using "0" */
2028 img_errno = IMG_BADFORMAT;
2029 return img_BAD;
2031 /* extra - 1 because label_len points to one past the end */
2032 c = pimg->label_len - 17 - 1;
2033 while (pimg->label_buf[c] != '.' || --opt > 0) {
2034 if (--c < 0) {
2035 /* zero prefix using "0" */
2036 img_errno = IMG_BADFORMAT;
2037 return img_BAD;
2040 c++;
2041 pimg->label_len = c;
2042 goto again3;
2044 if (opt == 15) {
2045 result = img_MOVE;
2046 break;
2048 if (opt >= 0x20) {
2049 switch (opt) {
2050 case 0x20: /* Single date */
2051 if (pimg->version < 7) {
2052 int date1 = get32(pimg->fh);
2053 #if IMG_API_VERSION == 0
2054 pimg->date2 = pimg->date1 = date1;
2055 #else /* IMG_API_VERSION == 1 */
2056 if (date1 != 0) {
2057 pimg->days1 = (date1 - TIME_T_1900) / SECS_PER_DAY;
2058 pimg->days2 = pimg->days1;
2059 } else {
2060 pimg->days2 = pimg->days1 = -1;
2062 #endif
2063 } else {
2064 int days1 = (int)getu16(pimg->fh);
2065 #if IMG_API_VERSION == 0
2066 pimg->date1 = (days1 - DAYS_1900) * SECS_PER_DAY;
2067 pimg->date2 = pimg->date1;
2068 #else /* IMG_API_VERSION == 1 */
2069 pimg->days2 = pimg->days1 = days1;
2070 #endif
2072 break;
2073 case 0x21: /* Date range (short for v7+) */
2074 if (pimg->version < 7) {
2075 INT32_T date1 = get32(pimg->fh);
2076 INT32_T date2 = get32(pimg->fh);
2077 #if IMG_API_VERSION == 0
2078 pimg->date1 = date1;
2079 pimg->date2 = date2;
2080 #else /* IMG_API_VERSION == 1 */
2081 pimg->days1 = (date1 - TIME_T_1900) / SECS_PER_DAY;
2082 pimg->days2 = (date2 - TIME_T_1900) / SECS_PER_DAY;
2083 #endif
2084 } else {
2085 int days1 = (int)getu16(pimg->fh);
2086 int days2 = days1 + GETC(pimg->fh) + 1;
2087 #if IMG_API_VERSION == 0
2088 pimg->date1 = (days1 - DAYS_1900) * SECS_PER_DAY;
2089 pimg->date2 = (days2 - DAYS_1900) * SECS_PER_DAY;
2090 #else /* IMG_API_VERSION == 1 */
2091 pimg->days1 = days1;
2092 pimg->days2 = days2;
2093 #endif
2095 break;
2096 case 0x22: /* Error info */
2097 pimg->n_legs = get32(pimg->fh);
2098 pimg->length = get32(pimg->fh) / 100.0;
2099 pimg->E = get32(pimg->fh) / 100.0;
2100 pimg->H = get32(pimg->fh) / 100.0;
2101 pimg->V = get32(pimg->fh) / 100.0;
2102 if (feof(pimg->fh)) {
2103 img_errno = IMG_BADFORMAT;
2104 return img_BAD;
2106 if (ferror(pimg->fh)) {
2107 img_errno = IMG_READERROR;
2108 return img_BAD;
2110 return img_ERROR_INFO;
2111 case 0x23: { /* v7+: Date range (long) */
2112 if (pimg->version < 7) {
2113 img_errno = IMG_BADFORMAT;
2114 return img_BAD;
2116 int days1 = (int)getu16(pimg->fh);
2117 int days2 = (int)getu16(pimg->fh);
2118 if (feof(pimg->fh)) {
2119 img_errno = IMG_BADFORMAT;
2120 return img_BAD;
2122 if (ferror(pimg->fh)) {
2123 img_errno = IMG_READERROR;
2124 return img_BAD;
2126 #if IMG_API_VERSION == 0
2127 pimg->date1 = (days1 - DAYS_1900) * SECS_PER_DAY;
2128 pimg->date2 = (days2 - DAYS_1900) * SECS_PER_DAY;
2129 #else /* IMG_API_VERSION == 1 */
2130 pimg->days1 = days1;
2131 pimg->days2 = days2;
2132 #endif
2133 break;
2135 case 0x24: { /* v7+: No date info */
2136 #if IMG_API_VERSION == 0
2137 pimg->date1 = pimg->date2 = 0;
2138 #else /* IMG_API_VERSION == 1 */
2139 pimg->days1 = pimg->days2 = -1;
2140 #endif
2141 break;
2143 case 0x30: case 0x31: /* LRUD */
2144 case 0x32: case 0x33: /* Big LRUD! */
2145 if (read_v3label(pimg) == img_BAD) return img_BAD;
2146 pimg->flags = (int)opt & 0x01;
2147 if (opt < 0x32) {
2148 pimg->l = get16(pimg->fh) / 100.0;
2149 pimg->r = get16(pimg->fh) / 100.0;
2150 pimg->u = get16(pimg->fh) / 100.0;
2151 pimg->d = get16(pimg->fh) / 100.0;
2152 } else {
2153 pimg->l = get32(pimg->fh) / 100.0;
2154 pimg->r = get32(pimg->fh) / 100.0;
2155 pimg->u = get32(pimg->fh) / 100.0;
2156 pimg->d = get32(pimg->fh) / 100.0;
2158 if (feof(pimg->fh)) {
2159 img_errno = IMG_BADFORMAT;
2160 return img_BAD;
2162 if (ferror(pimg->fh)) {
2163 img_errno = IMG_READERROR;
2164 return img_BAD;
2166 if (!stn_included(pimg)) {
2167 return img_XSECT_END;
2169 /* If this is the last cross-section in this passage, set
2170 * pending so we return img_XSECT_END next time. */
2171 if (pimg->flags & 0x01) {
2172 pimg->pending = PENDING_XSECT_END;
2173 pimg->flags &= ~0x01;
2175 return img_XSECT;
2176 default: /* 0x25 - 0x2f and 0x34 - 0x3f are currently unallocated. */
2177 img_errno = IMG_BADFORMAT;
2178 return img_BAD;
2180 if (feof(pimg->fh)) {
2181 img_errno = IMG_BADFORMAT;
2182 return img_BAD;
2184 if (ferror(pimg->fh)) {
2185 img_errno = IMG_READERROR;
2186 return img_BAD;
2188 goto again3;
2190 /* 16-31 mean remove (n - 15) characters from the prefix */
2191 /* zero prefix using 0 */
2192 if (pimg->label_len <= (size_t)(opt - 15)) {
2193 img_errno = IMG_BADFORMAT;
2194 return img_BAD;
2196 pimg->label_len -= (opt - 15);
2197 goto again3;
2198 case 1:
2199 if (read_v3label(pimg) == img_BAD) return img_BAD;
2201 result = img_LABEL;
2203 if (!stn_included(pimg)) {
2204 if (!skip_coord(pimg->fh)) return img_BAD;
2205 pimg->pending = 0;
2206 goto again3;
2209 pimg->flags = (int)opt & 0x3f;
2210 break;
2211 case 2:
2212 if (read_v3label(pimg) == img_BAD) return img_BAD;
2214 result = img_LINE;
2216 if (!survey_included(pimg)) {
2217 if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
2218 pimg->pending = 15;
2219 goto again3;
2222 if (pimg->pending) {
2223 *p = pimg->mv;
2224 if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
2225 pimg->pending = opt;
2226 return img_MOVE;
2228 pimg->flags = (int)opt & 0x3f;
2229 break;
2230 default:
2231 img_errno = IMG_BADFORMAT;
2232 return img_BAD;
2234 if (!read_coord(pimg->fh, p)) return img_BAD;
2235 pimg->pending = 0;
2236 return result;
2239 static int
2240 img_read_item_ancient(img *pimg, img_point *p)
2242 int result;
2243 static long opt_lookahead = 0;
2244 static img_point pt = { 0.0, 0.0, 0.0 };
2245 long opt;
2247 again: /* label to goto if we get a cross */
2248 pimg->label = pimg->label_buf;
2249 pimg->label[0] = '\0';
2251 if (pimg->version == 1) {
2252 if (opt_lookahead) {
2253 opt = opt_lookahead;
2254 opt_lookahead = 0;
2255 } else {
2256 opt = get32(pimg->fh);
2258 } else {
2259 opt = GETC(pimg->fh);
2262 if (feof(pimg->fh)) {
2263 img_errno = IMG_BADFORMAT;
2264 return img_BAD;
2266 if (ferror(pimg->fh)) {
2267 img_errno = IMG_READERROR;
2268 return img_BAD;
2271 switch (opt) {
2272 case -1: case 0:
2273 return img_STOP; /* end of data marker */
2274 case 1:
2275 /* skip coordinates */
2276 if (!skip_coord(pimg->fh)) {
2277 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
2278 return img_BAD;
2280 goto again;
2281 case 2: case 3: {
2282 size_t len;
2283 result = img_LABEL;
2284 if (!fgets(pimg->label_buf, pimg->buf_len, pimg->fh)) {
2285 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
2286 return img_BAD;
2288 if (pimg->label[0] == '\\') pimg->label++;
2289 len = strlen(pimg->label);
2290 if (len == 0 || pimg->label[len - 1] != '\n') {
2291 img_errno = IMG_BADFORMAT;
2292 return img_BAD;
2294 /* Ignore empty labels in some .3d files (caused by a bug) */
2295 if (len == 1) goto again;
2296 pimg->label[len - 1] = '\0';
2297 pimg->flags = img_SFLAG_UNDERGROUND; /* no flags given... */
2298 if (opt == 2) goto done;
2299 break;
2301 case 6: case 7: {
2302 long len;
2303 result = img_LABEL;
2305 if (opt == 7)
2306 pimg->flags = GETC(pimg->fh);
2307 else
2308 pimg->flags = img_SFLAG_UNDERGROUND; /* no flags given... */
2310 len = get32(pimg->fh);
2312 if (feof(pimg->fh)) {
2313 img_errno = IMG_BADFORMAT;
2314 return img_BAD;
2316 if (ferror(pimg->fh)) {
2317 img_errno = IMG_READERROR;
2318 return img_BAD;
2321 /* Ignore empty labels in some .3d files (caused by a bug) */
2322 if (len == 0) goto again;
2323 if (!check_label_space(pimg, len + 1)) {
2324 img_errno = IMG_OUTOFMEMORY;
2325 return img_BAD;
2327 if (fread(pimg->label_buf, len, 1, pimg->fh) != 1) {
2328 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
2329 return img_BAD;
2331 pimg->label_buf[len] = '\0';
2332 break;
2334 case 4:
2335 result = img_MOVE;
2336 break;
2337 case 5:
2338 result = img_LINE;
2339 break;
2340 default:
2341 switch ((int)opt & 0xc0) {
2342 case 0x80:
2343 pimg->flags = (int)opt & 0x3f;
2344 result = img_LINE;
2345 break;
2346 case 0x40: {
2347 char *q;
2348 pimg->flags = (int)opt & 0x3f;
2349 result = img_LABEL;
2350 if (!fgets(pimg->label_buf, pimg->buf_len, pimg->fh)) {
2351 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
2352 return img_BAD;
2354 q = pimg->label_buf + strlen(pimg->label_buf) - 1;
2355 /* Ignore empty-labels in some .3d files (caused by a bug) */
2356 if (q == pimg->label_buf) goto again;
2357 if (*q != '\n') {
2358 img_errno = IMG_BADFORMAT;
2359 return img_BAD;
2361 *q = '\0';
2362 break;
2364 default:
2365 img_errno = IMG_BADFORMAT;
2366 return img_BAD;
2368 break;
2371 if (!read_coord(pimg->fh, &pt)) return img_BAD;
2373 if (result == img_LABEL && !stn_included(pimg)) {
2374 goto again;
2377 done:
2378 *p = pt;
2380 if (result == img_MOVE && pimg->version == 1) {
2381 /* peek at next code and see if it's an old-style label */
2382 opt_lookahead = get32(pimg->fh);
2384 if (feof(pimg->fh)) {
2385 img_errno = IMG_BADFORMAT;
2386 return img_BAD;
2388 if (ferror(pimg->fh)) {
2389 img_errno = IMG_READERROR;
2390 return img_BAD;
2393 if (opt_lookahead == 2) return img_read_item_ancient(pimg, p);
2396 return result;
2399 static int
2400 img_read_item_ascii_wrapper(img *pimg, img_point *p)
2402 /* We need to set the default locale for fscanf() to work on
2403 * numbers with "." as decimal point. */
2404 int result;
2405 char * current_locale = my_strdup(setlocale(LC_NUMERIC, NULL));
2406 setlocale(LC_NUMERIC, "C");
2407 result = img_read_item_ascii(pimg, p);
2408 setlocale(LC_NUMERIC, current_locale);
2409 free(current_locale);
2410 return result;
2413 /* Handle all ASCII formats. */
2414 static int
2415 img_read_item_ascii(img *pimg, img_point *p)
2417 int result;
2418 pimg->label = pimg->label_buf;
2419 if (pimg->version == 0) {
2420 ascii_again:
2421 pimg->label[0] = '\0';
2422 if (feof(pimg->fh)) return img_STOP;
2423 if (pimg->pending) {
2424 pimg->pending = 0;
2425 result = img_LINE;
2426 } else {
2427 char cmd[7];
2428 /* Stop if nothing found */
2429 if (fscanf(pimg->fh, "%6s", cmd) < 1) return img_STOP;
2430 if (strcmp(cmd, "move") == 0)
2431 result = img_MOVE;
2432 else if (strcmp(cmd, "draw") == 0)
2433 result = img_LINE;
2434 else if (strcmp(cmd, "line") == 0) {
2435 /* set flag to indicate to process second triplet as LINE */
2436 pimg->pending = 1;
2437 result = img_MOVE;
2438 } else if (strcmp(cmd, "cross") == 0) {
2439 if (fscanf(pimg->fh, "%lf%lf%lf", &p->x, &p->y, &p->z) < 3) {
2440 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
2441 return img_BAD;
2443 goto ascii_again;
2444 } else if (strcmp(cmd, "name") == 0) {
2445 size_t off = 0;
2446 int ch = GETC(pimg->fh);
2447 if (ch == ' ') ch = GETC(pimg->fh);
2448 while (ch != ' ') {
2449 if (ch == '\n' || ch == EOF) {
2450 img_errno = ferror(pimg->fh) ? IMG_READERROR : IMG_BADFORMAT;
2451 return img_BAD;
2453 if (off == pimg->buf_len) {
2454 if (!check_label_space(pimg, pimg->buf_len * 2)) {
2455 img_errno = IMG_OUTOFMEMORY;
2456 return img_BAD;
2459 pimg->label_buf[off++] = ch;
2460 ch = GETC(pimg->fh);
2462 pimg->label_buf[off] = '\0';
2464 pimg->label = pimg->label_buf;
2465 if (pimg->label[0] == '\\') pimg->label++;
2467 pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
2469 result = img_LABEL;
2470 } else {
2471 img_errno = IMG_BADFORMAT;
2472 return img_BAD; /* unknown keyword */
2476 if (fscanf(pimg->fh, "%lf%lf%lf", &p->x, &p->y, &p->z) < 3) {
2477 img_errno = ferror(pimg->fh) ? IMG_READERROR : IMG_BADFORMAT;
2478 return img_BAD;
2481 if (result == img_LABEL && !stn_included(pimg)) {
2482 goto ascii_again;
2485 return result;
2486 } else if (pimg->version == VERSION_SURVEX_POS) {
2487 /* Survex .pos file */
2488 int ch;
2489 size_t off;
2490 pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
2491 againpos:
2492 while (fscanf(pimg->fh, "(%lf,%lf,%lf )", &p->x, &p->y, &p->z) != 3) {
2493 if (ferror(pimg->fh)) {
2494 img_errno = IMG_READERROR;
2495 return img_BAD;
2497 if (feof(pimg->fh)) return img_STOP;
2498 if (pimg->pending) {
2499 img_errno = IMG_BADFORMAT;
2500 return img_BAD;
2502 pimg->pending = 1;
2503 /* ignore rest of line */
2504 do {
2505 ch = GETC(pimg->fh);
2506 } while (ch != '\n' && ch != '\r' && ch != EOF);
2509 pimg->label_buf[0] = '\0';
2510 do {
2511 ch = GETC(pimg->fh);
2512 } while (ch == ' ' || ch == '\t');
2513 if (ch == '\n' || ch == EOF) {
2514 /* If there's no label, set img_SFLAG_ANON. */
2515 pimg->flags |= img_SFLAG_ANON;
2516 return img_LABEL;
2518 pimg->label_buf[0] = ch;
2519 off = 1;
2520 while (!feof(pimg->fh)) {
2521 if (!fgets(pimg->label_buf + off, pimg->buf_len - off, pimg->fh)) {
2522 img_errno = IMG_READERROR;
2523 return img_BAD;
2526 off += strlen(pimg->label_buf + off);
2527 if (off && pimg->label_buf[off - 1] == '\n') {
2528 pimg->label_buf[off - 1] = '\0';
2529 break;
2531 if (!check_label_space(pimg, pimg->buf_len * 2)) {
2532 img_errno = IMG_OUTOFMEMORY;
2533 return img_BAD;
2537 pimg->label = pimg->label_buf;
2539 if (pimg->label[0] == '\\') pimg->label++;
2541 if (!stn_included(pimg)) goto againpos;
2543 return img_LABEL;
2544 } else if (pimg->version == VERSION_COMPASS_PLT) {
2545 /* Compass .plt file */
2546 if ((pimg->pending & ~PENDING_HAD_XSECT) > 0) {
2547 /* -1 signals we've entered the first survey we want to read, and
2548 * need to fudge lots if the first action is 'D' or 'd'...
2550 pimg->flags = 0;
2551 if (pimg->pending & PENDING_XSECT_END) {
2552 /* pending XSECT_END */
2553 pimg->pending &= ~PENDING_XSECT_END;
2554 return img_XSECT_END;
2556 if (pimg->pending & PENDING_XSECT) {
2557 /* pending XSECT */
2558 pimg->pending &= ~PENDING_XSECT;
2559 return img_XSECT;
2561 pimg->label[pimg->label_len] = '\0';
2562 if (pimg->pending & PENDING_LINE) {
2563 pimg->flags = (pimg->pending >> PENDING_FLAGS_SHIFT);
2564 pimg->pending &= ((1 << PENDING_FLAGS_SHIFT) - 1) & ~PENDING_LINE;
2565 return img_LINE;
2567 pimg->pending &= ~PENDING_MOVE;
2568 return img_MOVE;
2571 while (1) {
2572 char *line;
2573 char *q;
2574 size_t len = 0;
2575 int ch = GETC(pimg->fh);
2577 switch (ch) {
2578 case '\x1a': case EOF: /* Don't insist on ^Z at end of file */
2579 if (pimg->pending == PENDING_HAD_XSECT) {
2580 ungetc('\x1a', pimg->fh);
2581 pimg->pending = 0;
2582 return img_XSECT_END;
2584 return img_STOP;
2585 case 'X': case 'F': case 'S':
2586 /* bounding boX (marks end of survey), Feature survey, or
2587 * new Section - skip to next survey */
2588 if (pimg->pending == PENDING_HAD_XSECT) {
2589 ungetc(ch, pimg->fh);
2590 pimg->pending = 0;
2591 return img_XSECT_END;
2593 if (pimg->survey) return img_STOP;
2594 skip_to_N:
2595 while (1) {
2596 do {
2597 ch = GETC(pimg->fh);
2598 } while (ch != '\n' && ch != '\r' && ch != EOF);
2599 while (ch == '\n' || ch == '\r') ch = GETC(pimg->fh);
2600 if (ch == 'N') break;
2601 if (ch == '\x1a' || ch == EOF) return img_STOP;
2603 /* FALLTHRU */
2604 case 'N':
2605 compass_plt_new_survey(pimg);
2606 line = getline_alloc(pimg->fh);
2607 if (!line) {
2608 img_errno = IMG_OUTOFMEMORY;
2609 return img_BAD;
2611 while (line[len] > 32) ++len;
2612 if (pimg->label_len == 0) pimg->pending = -1;
2613 if (!check_label_space(pimg, len + 1)) {
2614 osfree(line);
2615 img_errno = IMG_OUTOFMEMORY;
2616 return img_BAD;
2618 pimg->label_len = len;
2619 pimg->label = pimg->label_buf;
2620 memcpy(pimg->label, line, len);
2621 pimg->label[len] = '\0';
2622 /* Handle the survey date. */
2623 while (line[len] && line[len] <= 32) ++len;
2624 if (line[len] == 'D') {
2625 struct tm tm;
2626 memset(&tm, 0, sizeof(tm));
2627 unsigned long v;
2628 q = line + len + 1;
2629 /* NB Order is Month Day Year order. */
2630 v = strtoul(q, &q, 10);
2631 if (v < 1 || v > 12)
2632 goto bad_plt_date;
2633 tm.tm_mon = v - 1;
2635 v = strtoul(q, &q, 10);
2636 if (v < 1 || v > 31)
2637 goto bad_plt_date;
2638 tm.tm_mday = v;
2640 v = strtoul(q, &q, 10);
2641 if (v == ULONG_MAX)
2642 goto bad_plt_date;
2643 if (v < 1900) {
2644 /* "The Year is expected to be the full year like 1994 not
2645 * 94", but "expected to" != "must" so treat a two digit
2646 * year as 19xx.
2648 v += 1900;
2650 if (v == 1901 && tm.tm_mday == 1 && tm.tm_mon == 0) {
2651 /* Compass uses 1/1/1 or 1/1/1901 for "date unknown". */
2652 goto bad_plt_date;
2654 tm.tm_year = v - 1900;
2655 /* We have no indication of what timezone this date is
2656 * in. It's probably local time for whoever processed the
2657 * data, so just assume noon in UTC, which is at least fairly
2658 * central in the possibilities.
2660 tm.tm_hour = 12;
2662 time_t datestamp = mktime_with_tz(&tm, "");
2663 #if IMG_API_VERSION == 0
2664 pimg->date1 = pimg->date2 = datestamp;
2665 #else /* IMG_API_VERSION == 1 */
2666 pimg->days1 = (datestamp - TIME_T_1900) / SECS_PER_DAY;
2667 pimg->days2 = pimg->days1;
2668 #endif
2670 } else {
2671 bad_plt_date:
2672 #if IMG_API_VERSION == 0
2673 pimg->date1 = pimg->date2 = 0;
2674 #else /* IMG_API_VERSION == 1 */
2675 pimg->days1 = pimg->days2 = -1;
2676 #endif
2678 osfree(line);
2679 break;
2680 case 'M':
2681 if (pimg->pending == PENDING_HAD_XSECT) {
2682 pimg->pending = PENDING_XSECT_END;
2684 /* FALLTHRU */
2685 case 'D':
2686 case 'd': {
2687 /* Move or Draw */
2688 unsigned shot_flags = (ch == 'd' ? img_FLAG_SURFACE : 0);
2689 long fpos = -1;
2690 if (pimg->survey && pimg->label_len == 0) {
2691 /* We're only holding onto this line in case the first line
2692 * of the 'N' is a 'D', so skip it for now...
2694 goto skip_to_N;
2696 if (pimg->pending == -1) {
2697 pimg->pending = 0;
2698 if (ch != 'M') {
2699 if (pimg->survey) {
2700 fpos = ftell(pimg->fh) - 1;
2701 fseek(pimg->fh, pimg->start, SEEK_SET);
2702 ch = GETC(pimg->fh);
2703 } else {
2704 /* If a file actually has a 'D' or 'd' before any
2705 * 'M', then pretend the action is 'M' - one of the
2706 * examples in the docs was like this!
2708 ch = 'M';
2712 line = getline_alloc(pimg->fh);
2713 if (!line) {
2714 img_errno = IMG_OUTOFMEMORY;
2715 return img_BAD;
2717 /* Compass stores coordinates as North, East, Up = (y,x,z)! */
2718 if (sscanf(line, "%lf%lf%lf", &p->y, &p->x, &p->z) != 3) {
2719 osfree(line);
2720 if (ferror(pimg->fh)) {
2721 img_errno = IMG_READERROR;
2722 } else {
2723 img_errno = IMG_BADFORMAT;
2725 return img_BAD;
2727 p->x *= METRES_PER_FOOT;
2728 p->y *= METRES_PER_FOOT;
2729 p->z *= METRES_PER_FOOT;
2730 q = strchr(line, 'S');
2731 if (!q) {
2732 osfree(line);
2733 img_errno = IMG_BADFORMAT;
2734 return img_BAD;
2736 ++q;
2737 len = 0;
2738 while (q[len] > ' ') ++len;
2739 /* Add 2 for ' ' before and terminating '\0'. */
2740 if (!check_label_space(pimg, pimg->label_len + len + 2)) {
2741 img_errno = IMG_OUTOFMEMORY;
2742 return img_BAD;
2744 pimg->flags = compass_plt_get_station_flags(pimg, q, len);
2745 pimg->label = pimg->label_buf;
2746 if (pimg->label_len) {
2747 pimg->label[pimg->label_len] = ' ';
2748 memcpy(pimg->label + pimg->label_len + 1, q, len);
2749 pimg->label[pimg->label_len + 1 + len] = '\0';
2750 } else {
2751 memcpy(pimg->label, q, len);
2752 pimg->label[len] = '\0';
2754 q += len;
2756 /* Now read LRUD. Technically, this is optional but virtually
2757 * all PLT files have it (with dummy negative values if no LRUD
2758 * was recorded) and some versions of Compass can't read PLT
2759 * files without it!
2761 while (*q && *q <= ' ') q++;
2762 if (*q == 'P') {
2763 int bytes_used;
2764 ++q;
2765 if (sscanf(q, "%lf%lf%lf%lf%n",
2766 &pimg->l, &pimg->r, &pimg->u, &pimg->d,
2767 &bytes_used) != 4) {
2768 osfree(line);
2769 if (ferror(pimg->fh)) {
2770 img_errno = IMG_READERROR;
2771 } else {
2772 img_errno = IMG_BADFORMAT;
2774 return img_BAD;
2776 if ((pimg->flags & img_SFLAG_UNDERGROUND) &&
2777 (pimg->l >= 0 || pimg->r >= 0 || pimg->u >= 0 || pimg->d >= 0)) {
2778 if (pimg->l >= 0) pimg->l *= METRES_PER_FOOT; else pimg->l = -1;
2779 if (pimg->r >= 0) pimg->r *= METRES_PER_FOOT; else pimg->r = -1;
2780 if (pimg->u >= 0) pimg->u *= METRES_PER_FOOT; else pimg->u = -1;
2781 if (pimg->d >= 0) pimg->d *= METRES_PER_FOOT; else pimg->d = -1;
2782 pimg->pending |= PENDING_XSECT | PENDING_HAD_XSECT;
2783 } else if (pimg->pending == PENDING_HAD_XSECT) {
2784 pimg->pending = PENDING_XSECT_END;
2786 q += bytes_used;
2787 } else {
2788 pimg->l = pimg->r = pimg->u = pimg->d = -1;
2789 if (pimg->pending == PENDING_HAD_XSECT) {
2790 pimg->pending = PENDING_XSECT_END;
2793 while (*q && *q <= ' ') q++;
2794 if (*q == 'I') {
2795 /* Skip distance from entrance. */
2796 do ++q; while (*q && *q <= ' ');
2797 while (*q > ' ') q++;
2798 while (*q && *q <= ' ') q++;
2800 if (*q == 'F') {
2801 /* "Shot Flags". Defined flags we currently ignore here:
2802 * C: "Do not adjust this shot when closing loops."
2803 * X: "you will never see this flag in a plot file."
2805 while (isalpha((unsigned char)*++q)) {
2806 switch (*q) {
2807 case 'L':
2808 shot_flags |= img_FLAG_DUPLICATE;
2809 break;
2810 case 'S':
2811 shot_flags |= img_FLAG_SPLAY;
2812 break;
2813 case 'P':
2814 /* P is "Exclude this shot from plotting", but the
2815 * use suggested in the Compass docs is for surface
2816 * data, and they "[do] not support passage
2817 * modeling".
2819 * Even if it's actually being used for a different
2820 * purpose, Survex programs don't show surface legs
2821 * by default so img_FLAG_SURFACE matches fairly
2822 * well.
2824 shot_flags |= img_FLAG_SURFACE;
2825 break;
2829 if (shot_flags & img_FLAG_SURFACE) {
2830 /* Suppress passage? */
2832 osfree(line);
2833 if (fpos != -1) {
2834 fseek(pimg->fh, fpos, SEEK_SET);
2837 if (pimg->flags < 0) {
2838 pimg->flags = shot_flags;
2839 /* We've already emitted img_LABEL for this station. */
2840 if (ch == 'M') {
2841 return img_MOVE;
2843 return img_LINE;
2845 if (fpos == -1) {
2846 if (ch == 'M') {
2847 pimg->pending |= PENDING_MOVE;
2848 } else {
2849 pimg->pending |= PENDING_LINE | (shot_flags << PENDING_FLAGS_SHIFT);
2853 return img_LABEL;
2855 default:
2856 img_errno = IMG_BADFORMAT;
2857 return img_BAD;
2860 } else {
2861 /* CMAP .xyz file */
2862 char *line = NULL;
2863 char *q;
2864 size_t len;
2866 if (pimg->pending) {
2867 /* pending MOVE or LINE or LABEL or STOP */
2868 int r = pimg->pending - 4;
2869 /* Set label to empty - don't use "" as we adjust label relative
2870 * to label_buf when label_buf is reallocated. */
2871 pimg->label = pimg->label_buf + strlen(pimg->label_buf);
2872 pimg->flags = 0;
2873 if (r == img_LABEL) {
2874 /* nasty magic */
2875 read_xyz_shot_coords(p, pimg->label_buf + 16);
2876 subtract_xyz_shot_deltas(p, pimg->label_buf + 16);
2877 pimg->pending = img_STOP + 4;
2878 return img_MOVE;
2881 pimg->pending = 0;
2883 if (r == img_STOP) {
2884 /* nasty magic */
2885 read_xyz_shot_coords(p, pimg->label_buf + 16);
2886 return img_LINE;
2889 return r;
2892 pimg->label = pimg->label_buf;
2893 do {
2894 osfree(line);
2895 if (feof(pimg->fh)) return img_STOP;
2896 line = getline_alloc(pimg->fh);
2897 if (!line) {
2898 img_errno = IMG_OUTOFMEMORY;
2899 return img_BAD;
2901 } while (line[0] == ' ' || line[0] == '\0');
2902 if (line[0] == '\x1a') return img_STOP;
2904 len = strlen(line);
2905 if (pimg->version == VERSION_CMAP_STATION) {
2906 /* station variant */
2907 if (len < 37) {
2908 osfree(line);
2909 img_errno = IMG_BADFORMAT;
2910 return img_BAD;
2912 memcpy(pimg->label, line, 6);
2913 q = (char *)memchr(pimg->label, ' ', 6);
2914 if (!q) q = pimg->label + 6;
2915 *q = '\0';
2917 read_xyz_station_coords(p, line);
2919 /* FIXME: look at prev for lines (line + 32, 5) */
2920 /* FIXME: duplicate stations... */
2921 return img_LABEL;
2922 } else {
2923 /* Shot variant (VERSION_CMAP_SHOT) */
2924 char old[8], new_[8];
2925 if (len < 61) {
2926 osfree(line);
2927 img_errno = IMG_BADFORMAT;
2928 return img_BAD;
2931 memcpy(old, line, 7);
2932 q = (char *)memchr(old, ' ', 7);
2933 if (!q) q = old + 7;
2934 *q = '\0';
2936 memcpy(new_, line + 7, 7);
2937 q = (char *)memchr(new_, ' ', 7);
2938 if (!q) q = new_ + 7;
2939 *q = '\0';
2941 pimg->flags = img_SFLAG_UNDERGROUND;
2943 if (strcmp(old, new_) == 0) {
2944 pimg->pending = img_MOVE + 4;
2945 read_xyz_shot_coords(p, line);
2946 strcpy(pimg->label, new_);
2947 osfree(line);
2948 return img_LABEL;
2951 if (strcmp(old, pimg->label) == 0) {
2952 pimg->pending = img_LINE + 4;
2953 read_xyz_shot_coords(p, line);
2954 strcpy(pimg->label, new_);
2955 osfree(line);
2956 return img_LABEL;
2959 pimg->pending = img_LABEL + 4;
2960 read_xyz_shot_coords(p, line);
2961 strcpy(pimg->label, new_);
2962 memcpy(pimg->label + 16, line, 70);
2964 osfree(line);
2965 return img_LABEL;
2970 static void
2971 write_coord(FILE *fh, double x, double y, double z)
2973 SVX_ASSERT(fh);
2974 /* Output in cm */
2975 INT32_T X = my_lround(x * 100.0);
2976 INT32_T Y = my_lround(y * 100.0);
2977 INT32_T Z = my_lround(z * 100.0);
2979 put32(X, fh);
2980 put32(Y, fh);
2981 put32(Z, fh);
2984 static int
2985 write_v3label(img *pimg, int opt, const char *s)
2987 size_t len, n, dot;
2989 /* find length of common prefix */
2990 dot = 0;
2991 for (len = 0; s[len] == pimg->label_buf[len] && s[len] != '\0'; len++) {
2992 if (s[len] == '.') dot = len + 1;
2995 SVX_ASSERT(len <= pimg->label_len);
2996 n = pimg->label_len - len;
2997 if (len == 0) {
2998 if (pimg->label_len) PUTC(0, pimg->fh);
2999 } else if (n <= 16) {
3000 if (n) PUTC(n + 15, pimg->fh);
3001 } else if (dot == 0) {
3002 if (pimg->label_len) PUTC(0, pimg->fh);
3003 len = 0;
3004 } else {
3005 const char *p = pimg->label_buf + dot;
3006 n = 1;
3007 for (len = pimg->label_len - dot - 17; len; len--) {
3008 if (*p++ == '.') n++;
3010 if (n <= 14) {
3011 PUTC(n, pimg->fh);
3012 len = dot;
3013 } else {
3014 if (pimg->label_len) PUTC(0, pimg->fh);
3015 len = 0;
3019 n = strlen(s + len);
3020 PUTC(opt, pimg->fh);
3021 if (n < 0xfe) {
3022 PUTC(n, pimg->fh);
3023 } else if (n < 0xffff + 0xfe) {
3024 PUTC(0xfe, pimg->fh);
3025 put16((short)(n - 0xfe), pimg->fh);
3026 } else {
3027 PUTC(0xff, pimg->fh);
3028 put32(n, pimg->fh);
3030 fwrite(s + len, n, 1, pimg->fh);
3032 n += len;
3033 pimg->label_len = n;
3034 if (!check_label_space(pimg, n + 1))
3035 return 0; /* FIXME: distinguish out of memory... */
3036 memcpy(pimg->label_buf + len, s + len, n - len + 1);
3038 return !ferror(pimg->fh);
3041 static int
3042 write_v8label(img *pimg, int opt, int common_flag, size_t common_val,
3043 const char *s)
3045 size_t len, del, add;
3047 /* find length of common prefix */
3048 for (len = 0; s[len] == pimg->label_buf[len] && s[len] != '\0'; len++) {
3051 SVX_ASSERT(len <= pimg->label_len);
3052 del = pimg->label_len - len;
3053 add = strlen(s + len);
3055 if (add == common_val && del == common_val) {
3056 PUTC(opt | common_flag, pimg->fh);
3057 } else {
3058 PUTC(opt, pimg->fh);
3059 if (del <= 15 && add <= 15 && (del || add)) {
3060 PUTC((del << 4) | add, pimg->fh);
3061 } else {
3062 PUTC(0x00, pimg->fh);
3063 if (del < 0xff) {
3064 PUTC(del, pimg->fh);
3065 } else {
3066 PUTC(0xff, pimg->fh);
3067 put32(del, pimg->fh);
3069 if (add < 0xff) {
3070 PUTC(add, pimg->fh);
3071 } else {
3072 PUTC(0xff, pimg->fh);
3073 put32(add, pimg->fh);
3078 if (add)
3079 fwrite(s + len, add, 1, pimg->fh);
3081 pimg->label_len = len + add;
3082 if (add > del && !check_label_space(pimg, pimg->label_len + 1))
3083 return 0; /* FIXME: distinguish out of memory... */
3085 memcpy(pimg->label_buf + len, s + len, add + 1);
3087 return !ferror(pimg->fh);
3090 static void
3091 img_write_item_date_new(img *pimg)
3093 int same, unset;
3094 /* Only write dates when they've changed. */
3095 #if IMG_API_VERSION == 0
3096 if (pimg->date1 == pimg->olddate1 && pimg->date2 == pimg->olddate2)
3097 return;
3099 same = (pimg->date1 == pimg->date2);
3100 unset = (pimg->date1 == 0);
3101 #else /* IMG_API_VERSION == 1 */
3102 if (pimg->days1 == pimg->olddays1 && pimg->days2 == pimg->olddays2)
3103 return;
3105 same = (pimg->days1 == pimg->days2);
3106 unset = (pimg->days1 == -1);
3107 #endif
3109 if (same) {
3110 if (unset) {
3111 PUTC(0x10, pimg->fh);
3112 } else {
3113 PUTC(0x11, pimg->fh);
3114 #if IMG_API_VERSION == 0
3115 put16((pimg->date1 - TIME_T_1900) / SECS_PER_DAY, pimg->fh);
3116 #else /* IMG_API_VERSION == 1 */
3117 put16(pimg->days1, pimg->fh);
3118 #endif
3120 } else {
3121 #if IMG_API_VERSION == 0
3122 int diff = (pimg->date2 - pimg->date1) / SECS_PER_DAY;
3123 if (diff > 0 && diff <= 256) {
3124 PUTC(0x12, pimg->fh);
3125 put16((pimg->date1 - TIME_T_1900) / SECS_PER_DAY, pimg->fh);
3126 PUTC(diff - 1, pimg->fh);
3127 } else {
3128 PUTC(0x13, pimg->fh);
3129 put16((pimg->date1 - TIME_T_1900) / SECS_PER_DAY, pimg->fh);
3130 put16((pimg->date2 - TIME_T_1900) / SECS_PER_DAY, pimg->fh);
3132 #else /* IMG_API_VERSION == 1 */
3133 int diff = pimg->days2 - pimg->days1;
3134 if (diff > 0 && diff <= 256) {
3135 PUTC(0x12, pimg->fh);
3136 put16(pimg->days1, pimg->fh);
3137 PUTC(diff - 1, pimg->fh);
3138 } else {
3139 PUTC(0x13, pimg->fh);
3140 put16(pimg->days1, pimg->fh);
3141 put16(pimg->days2, pimg->fh);
3143 #endif
3145 #if IMG_API_VERSION == 0
3146 pimg->olddate1 = pimg->date1;
3147 pimg->olddate2 = pimg->date2;
3148 #else /* IMG_API_VERSION == 1 */
3149 pimg->olddays1 = pimg->days1;
3150 pimg->olddays2 = pimg->days2;
3151 #endif
3154 static void
3155 img_write_item_date(img *pimg)
3157 int same, unset;
3158 /* Only write dates when they've changed. */
3159 #if IMG_API_VERSION == 0
3160 if (pimg->date1 == pimg->olddate1 && pimg->date2 == pimg->olddate2)
3161 return;
3163 same = (pimg->date1 == pimg->date2);
3164 unset = (pimg->date1 == 0);
3165 #else /* IMG_API_VERSION == 1 */
3166 if (pimg->days1 == pimg->olddays1 && pimg->days2 == pimg->olddays2)
3167 return;
3169 same = (pimg->days1 == pimg->days2);
3170 unset = (pimg->days1 == -1);
3171 #endif
3173 if (same) {
3174 if (img_output_version < 7) {
3175 PUTC(0x20, pimg->fh);
3176 #if IMG_API_VERSION == 0
3177 put32(pimg->date1, pimg->fh);
3178 #else /* IMG_API_VERSION == 1 */
3179 put32((pimg->days1 - DAYS_1900) * SECS_PER_DAY, pimg->fh);
3180 #endif
3181 } else {
3182 if (unset) {
3183 PUTC(0x24, pimg->fh);
3184 } else {
3185 PUTC(0x20, pimg->fh);
3186 #if IMG_API_VERSION == 0
3187 put16((pimg->date1 - TIME_T_1900) / SECS_PER_DAY, pimg->fh);
3188 #else /* IMG_API_VERSION == 1 */
3189 put16(pimg->days1, pimg->fh);
3190 #endif
3193 } else {
3194 if (img_output_version < 7) {
3195 PUTC(0x21, pimg->fh);
3196 #if IMG_API_VERSION == 0
3197 put32(pimg->date1, pimg->fh);
3198 put32(pimg->date2, pimg->fh);
3199 #else /* IMG_API_VERSION == 1 */
3200 put32((pimg->days1 - DAYS_1900) * SECS_PER_DAY, pimg->fh);
3201 put32((pimg->days2 - DAYS_1900) * SECS_PER_DAY, pimg->fh);
3202 #endif
3203 } else {
3204 #if IMG_API_VERSION == 0
3205 int diff = (pimg->date2 - pimg->date1) / SECS_PER_DAY;
3206 if (diff > 0 && diff <= 256) {
3207 PUTC(0x21, pimg->fh);
3208 put16((pimg->date1 - TIME_T_1900) / SECS_PER_DAY, pimg->fh);
3209 PUTC(diff - 1, pimg->fh);
3210 } else {
3211 PUTC(0x23, pimg->fh);
3212 put16((pimg->date1 - TIME_T_1900) / SECS_PER_DAY, pimg->fh);
3213 put16((pimg->date2 - TIME_T_1900) / SECS_PER_DAY, pimg->fh);
3215 #else /* IMG_API_VERSION == 1 */
3216 int diff = pimg->days2 - pimg->days1;
3217 if (diff > 0 && diff <= 256) {
3218 PUTC(0x21, pimg->fh);
3219 put16(pimg->days1, pimg->fh);
3220 PUTC(diff - 1, pimg->fh);
3221 } else {
3222 PUTC(0x23, pimg->fh);
3223 put16(pimg->days1, pimg->fh);
3224 put16(pimg->days2, pimg->fh);
3226 #endif
3229 #if IMG_API_VERSION == 0
3230 pimg->olddate1 = pimg->date1;
3231 pimg->olddate2 = pimg->date2;
3232 #else /* IMG_API_VERSION == 1 */
3233 pimg->olddays1 = pimg->days1;
3234 pimg->olddays2 = pimg->days2;
3235 #endif
3238 static void
3239 img_write_item_new(img *pimg, int code, int flags, const char *s,
3240 double x, double y, double z);
3241 static void
3242 img_write_item_v3to7(img *pimg, int code, int flags, const char *s,
3243 double x, double y, double z);
3244 static void
3245 img_write_item_ancient(img *pimg, int code, int flags, const char *s,
3246 double x, double y, double z);
3248 void
3249 img_write_item(img *pimg, int code, int flags, const char *s,
3250 double x, double y, double z)
3252 if (!pimg) return;
3253 if (pimg->version >= 8) {
3254 img_write_item_new(pimg, code, flags, s, x, y, z);
3255 } else if (pimg->version >= 3) {
3256 img_write_item_v3to7(pimg, code, flags, s, x, y, z);
3257 } else {
3258 img_write_item_ancient(pimg, code, flags, s, x, y, z);
3262 static void
3263 img_write_item_new(img *pimg, int code, int flags, const char *s,
3264 double x, double y, double z)
3266 switch (code) {
3267 case img_LABEL:
3268 write_v8label(pimg, 0x80 | flags, 0, -1, s);
3269 break;
3270 case img_XSECT: {
3271 INT32_T l, r, u, d, max_dim;
3272 img_write_item_date_new(pimg);
3273 l = (INT32_T)my_lround(pimg->l * 100.0);
3274 r = (INT32_T)my_lround(pimg->r * 100.0);
3275 u = (INT32_T)my_lround(pimg->u * 100.0);
3276 d = (INT32_T)my_lround(pimg->d * 100.0);
3277 if (l < 0) l = -1;
3278 if (r < 0) r = -1;
3279 if (u < 0) u = -1;
3280 if (d < 0) d = -1;
3281 max_dim = max(max(l, r), max(u, d));
3282 flags = (flags & img_XFLAG_END) ? 1 : 0;
3283 if (max_dim >= 32768) flags |= 2;
3284 write_v8label(pimg, 0x30 | flags, 0, -1, s);
3285 if (flags & 2) {
3286 /* Big passage! Need to use 4 bytes. */
3287 put32(l, pimg->fh);
3288 put32(r, pimg->fh);
3289 put32(u, pimg->fh);
3290 put32(d, pimg->fh);
3291 } else {
3292 put16(l, pimg->fh);
3293 put16(r, pimg->fh);
3294 put16(u, pimg->fh);
3295 put16(d, pimg->fh);
3297 return;
3299 case img_MOVE:
3300 PUTC(15, pimg->fh);
3301 break;
3302 case img_LINE:
3303 img_write_item_date_new(pimg);
3304 if (pimg->style != pimg->oldstyle) {
3305 switch (pimg->style) {
3306 case img_STYLE_NORMAL:
3307 case img_STYLE_DIVING:
3308 case img_STYLE_CARTESIAN:
3309 case img_STYLE_CYLPOLAR:
3310 case img_STYLE_NOSURVEY:
3311 PUTC(pimg->style, pimg->fh);
3312 break;
3314 pimg->oldstyle = pimg->style;
3316 write_v8label(pimg, 0x40 | flags, 0x20, 0x00, s ? s : "");
3317 break;
3318 default: /* ignore for now */
3319 return;
3321 write_coord(pimg->fh, x, y, z);
3324 static void
3325 img_write_item_v3to7(img *pimg, int code, int flags, const char *s,
3326 double x, double y, double z)
3328 switch (code) {
3329 case img_LABEL:
3330 write_v3label(pimg, 0x40 | flags, s);
3331 break;
3332 case img_XSECT: {
3333 INT32_T l, r, u, d, max_dim;
3334 /* Need at least version 5 for img_XSECT. */
3335 if (pimg->version < 5) return;
3336 img_write_item_date(pimg);
3337 l = (INT32_T)my_lround(pimg->l * 100.0);
3338 r = (INT32_T)my_lround(pimg->r * 100.0);
3339 u = (INT32_T)my_lround(pimg->u * 100.0);
3340 d = (INT32_T)my_lround(pimg->d * 100.0);
3341 if (l < 0) l = -1;
3342 if (r < 0) r = -1;
3343 if (u < 0) u = -1;
3344 if (d < 0) d = -1;
3345 max_dim = max(max(l, r), max(u, d));
3346 flags = (flags & img_XFLAG_END) ? 1 : 0;
3347 if (max_dim >= 32768) flags |= 2;
3348 write_v3label(pimg, 0x30 | flags, s);
3349 if (flags & 2) {
3350 /* Big passage! Need to use 4 bytes. */
3351 put32(l, pimg->fh);
3352 put32(r, pimg->fh);
3353 put32(u, pimg->fh);
3354 put32(d, pimg->fh);
3355 } else {
3356 put16(l, pimg->fh);
3357 put16(r, pimg->fh);
3358 put16(u, pimg->fh);
3359 put16(d, pimg->fh);
3361 return;
3363 case img_MOVE:
3364 PUTC(15, pimg->fh);
3365 break;
3366 case img_LINE:
3367 if (pimg->version >= 4) {
3368 img_write_item_date(pimg);
3370 write_v3label(pimg, 0x80 | flags, s ? s : "");
3371 break;
3372 default: /* ignore for now */
3373 return;
3375 write_coord(pimg->fh, x, y, z);
3378 static void
3379 img_write_item_ancient(img *pimg, int code, int flags, const char *s,
3380 double x, double y, double z)
3382 size_t len;
3383 INT32_T opt = 0;
3384 SVX_ASSERT(pimg->version > 0);
3385 switch (code) {
3386 case img_LABEL:
3387 if (pimg->version == 1) {
3388 /* put a move before each label */
3389 img_write_item_ancient(pimg, img_MOVE, 0, NULL, x, y, z);
3390 put32(2, pimg->fh);
3391 fputsnl(s, pimg->fh);
3392 return;
3394 len = strlen(s);
3395 if (len > 255 || strchr(s, '\n')) {
3396 /* long label - not in early incarnations of v2 format, but few
3397 * 3d files will need these, so better not to force incompatibility
3398 * with a new version I think... */
3399 PUTC(7, pimg->fh);
3400 PUTC(flags, pimg->fh);
3401 put32(len, pimg->fh);
3402 fputs(s, pimg->fh);
3403 } else {
3404 PUTC(0x40 | (flags & 0x3f), pimg->fh);
3405 fputsnl(s, pimg->fh);
3407 opt = 0;
3408 break;
3409 case img_MOVE:
3410 opt = 4;
3411 break;
3412 case img_LINE:
3413 if (pimg->version > 1) {
3414 opt = 0x80 | (flags & 0x3f);
3415 break;
3417 opt = 5;
3418 break;
3419 default: /* ignore for now */
3420 return;
3422 if (pimg->version == 1) {
3423 put32(opt, pimg->fh);
3424 } else {
3425 if (opt) PUTC(opt, pimg->fh);
3427 write_coord(pimg->fh, x, y, z);
3430 /* Write error information for the current traverse
3431 * n_legs is the number of legs in the traverse
3432 * length is the traverse length (in m)
3433 * E is the ratio of the observed misclosure to the theoretical one
3434 * H is the ratio of the observed horizontal misclosure to the theoretical one
3435 * V is the ratio of the observed vertical misclosure to the theoretical one
3437 void
3438 img_write_errors(img *pimg, int n_legs, double length,
3439 double E, double H, double V)
3441 PUTC((pimg->version >= 8 ? 0x1f : 0x22), pimg->fh);
3442 put32(n_legs, pimg->fh);
3443 put32((INT32_T)my_lround(length * 100.0), pimg->fh);
3444 put32((INT32_T)my_lround(E * 100.0), pimg->fh);
3445 put32((INT32_T)my_lround(H * 100.0), pimg->fh);
3446 put32((INT32_T)my_lround(V * 100.0), pimg->fh);
3450 img_close(img *pimg)
3452 int result = 1;
3453 if (pimg) {
3454 if (pimg->fh) {
3455 if (pimg->fRead) {
3456 osfree(pimg->survey);
3457 osfree(pimg->title);
3458 osfree(pimg->cs);
3459 osfree(pimg->datestamp);
3460 } else {
3461 /* write end of data marker */
3462 switch (pimg->version) {
3463 case 1:
3464 put32((INT32_T)-1, pimg->fh);
3465 break;
3466 default:
3467 if (pimg->version <= 7 ?
3468 (pimg->label_len != 0) :
3469 (pimg->style != img_STYLE_NORMAL)) {
3470 PUTC(0, pimg->fh);
3472 /* FALL THROUGH */
3473 case 2:
3474 PUTC(0, pimg->fh);
3475 break;
3478 if (ferror(pimg->fh)) result = 0;
3479 if (pimg->close_func && pimg->close_func(pimg->fh))
3480 result = 0;
3481 if (!result) img_errno = pimg->fRead ? IMG_READERROR : IMG_WRITEERROR;
3483 if (pimg->data) {
3484 switch (pimg->version) {
3485 case VERSION_COMPASS_PLT:
3486 compass_plt_free_data(pimg);
3487 break;
3488 default:
3489 osfree(pimg->data);
3492 osfree(pimg->label_buf);
3493 osfree(pimg->filename_opened);
3494 osfree(pimg);
3496 return result;
3499 img_datum
3500 img_parse_compass_datum_string(const char *s, size_t len)
3502 #define EQ(S) len == LITLEN(S) && memcmp(s, S, LITLEN(S)) == 0
3503 /* First check the three which seem to be commonly used in Compass data. */
3504 if (EQ("WGS 1984"))
3505 return img_DATUM_WGS84;
3506 if (EQ("North American 1927"))
3507 return img_DATUM_NAD27;
3508 if (EQ("North American 1983"))
3509 return img_DATUM_NAD83;
3511 if (EQ("Adindan"))
3512 return img_DATUM_ADINDAN;
3513 if (EQ("Arc 1950"))
3514 return img_DATUM_ARC1950;
3515 if (EQ("Arc 1960"))
3516 return img_DATUM_ARC1960;
3517 if (EQ("Cape"))
3518 return img_DATUM_CAPE;
3519 if (EQ("European 1950"))
3520 return img_DATUM_EUROPEAN1950;
3521 if (EQ("Geodetic 1949"))
3522 return img_DATUM_NZGD49;
3523 if (EQ("Hu Tzu Shan"))
3524 return img_DATUM_HUTZUSHAN1950;
3525 if (EQ("Indian"))
3526 return img_DATUM_INDIAN1960;
3527 if (EQ("Tokyo"))
3528 return img_DATUM_TOKYO;
3529 if (EQ("WGS 1972"))
3530 return img_DATUM_WGS72;
3532 return img_DATUM_UNKNOWN;
3535 char *
3536 img_compass_utm_proj_str(img_datum datum, int utm_zone)
3538 int epsg_code = 0;
3539 const char* proj4_datum = NULL;
3541 if (utm_zone < -60 || utm_zone > 60 || utm_zone == 0)
3542 return NULL;
3544 switch (datum) {
3545 case img_DATUM_UNKNOWN:
3546 break;
3547 case img_DATUM_ADINDAN:
3548 if (utm_zone >= 35 && utm_zone <= 38)
3549 epsg_code = 20100 + utm_zone;
3550 break;
3551 case img_DATUM_ARC1950:
3552 if (utm_zone >= -36 && utm_zone <= -34)
3553 epsg_code = 20900 - utm_zone;
3554 break;
3555 case img_DATUM_ARC1960:
3556 if (utm_zone >= -37 && utm_zone <= -35)
3557 epsg_code = 21000 - utm_zone;
3558 break;
3559 case img_DATUM_CAPE:
3560 if (utm_zone >= -36 && utm_zone <= -34)
3561 epsg_code = 22200 - utm_zone;
3562 break;
3563 case img_DATUM_EUROPEAN1950:
3564 if (utm_zone >= 28 && utm_zone <= 38)
3565 epsg_code = 23000 + utm_zone;
3566 break;
3567 case img_DATUM_NZGD49:
3568 if (utm_zone >= 58)
3569 epsg_code = 27200 + utm_zone;
3570 break;
3571 case img_DATUM_HUTZUSHAN1950:
3572 if (utm_zone == 51)
3573 epsg_code = 3829;
3574 break;
3575 case img_DATUM_INDIAN1960:
3576 if (utm_zone >= 48 && utm_zone <= 49)
3577 epsg_code = 3100 + utm_zone;
3578 break;
3579 case img_DATUM_NAD27:
3580 if (utm_zone > 0 && utm_zone <= 23)
3581 epsg_code = 26700 + utm_zone;
3582 else if (utm_zone >= 59)
3583 epsg_code = 3311 + utm_zone;
3584 else
3585 proj4_datum = "NAD27";
3586 break;
3587 case img_DATUM_NAD83:
3588 if (utm_zone > 0 && utm_zone <= 23)
3589 epsg_code = 26900 + utm_zone;
3590 else if (utm_zone == 24)
3591 epsg_code = 9712;
3592 else if (utm_zone >= 59)
3593 epsg_code = 3313 + utm_zone;
3594 else
3595 proj4_datum = "NAD83";
3596 break;
3597 case img_DATUM_TOKYO:
3598 if (utm_zone >= 51 && utm_zone <= 55)
3599 epsg_code = 3041 + utm_zone;
3600 break;
3601 case img_DATUM_WGS72:
3602 if (utm_zone > 0)
3603 epsg_code = 32200 + utm_zone;
3604 else
3605 epsg_code = 32300 - utm_zone;
3606 break;
3607 case img_DATUM_WGS84:
3608 if (utm_zone > 0)
3609 epsg_code = 32600 + utm_zone;
3610 else
3611 epsg_code = 32700 - utm_zone;
3612 break;
3615 if (epsg_code) {
3616 char *proj_str = xosmalloc(11);
3617 if (!proj_str) {
3618 img_errno = IMG_OUTOFMEMORY;
3619 return NULL;
3621 SNPRINTF(proj_str, 11, "EPSG:%d", epsg_code);
3622 return proj_str;
3625 if (proj4_datum) {
3626 char *proj_str;
3627 size_t len = strlen(proj4_datum) + 52 + 2 + 1;
3628 const char *south = "";
3629 if (utm_zone < 0) {
3630 utm_zone = -utm_zone;
3631 south = "+south ";
3632 len += 7;
3634 proj_str = xosmalloc(len);
3635 if (!proj_str) {
3636 img_errno = IMG_OUTOFMEMORY;
3637 return NULL;
3639 SNPRINTF(proj_str, len,
3640 "+proj=utm +zone=%d %s+datum=%s +units=m +no_defs +type=crs",
3641 utm_zone, south, proj4_datum);
3642 return proj_str;
3645 return NULL;