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
40 # define INT32_T int32_t
41 # define UINT32_T uint32_t
42 # define SNPRINTF snprintf
44 # include "filelist.h"
45 # include "filename.h"
48 # define TIMEFMT msg(/*%a,%Y.%m.%d %H:%M:%S %Z*/107)
50 # if defined HAVE_STDINT_H || \
51 (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) || \
52 (defined __cplusplus && __cplusplus >= 201103L)
54 # define INT32_T int32_t
55 # define UINT32_T uint32_t
58 # if INT_MAX >= 2147483647
60 # define UINT32_T unsigned
63 # define UINT32_T unsigned long
66 # if defined HAVE_SNPRINTF || \
67 (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) || \
68 (defined __cplusplus && __cplusplus >= 201103L)
69 # define SNPRINTF snprintf
71 # define SNPRINTF my_snprintf
72 static int my_snprintf(char *s
, size_t size
, const char *format
, ...) {
76 (void)size
; /* The buffer passed should always be large enough. */
77 result
= vsprintf(s
, format
, ap
);
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))
98 # define PUTC(C, FH) putc(C, FH)
101 # define GETC(FH) getc(FH)
103 # define fputsnl(S, FH) (fputs((S), (FH)) == EOF ? EOF : putc('\n', (FH)))
104 # define SVX_ASSERT(X)
107 # include <algorithm>
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... */
115 # define max(X, Y) ((X) > (Y) ? (X) : (Y))
118 # define min(X, Y) ((X) < (Y) ? (X) : (Y))
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;
133 put32(UINT32_T w
, FILE *fh
)
136 PUTC((char)(w
>> 8l), fh
);
137 PUTC((char)(w
>> 16l), fh
);
138 PUTC((char)(w
>> 24l), fh
);
144 UINT32_T w
= GETC(fh
);
145 w
|= (UINT32_T
)GETC(fh
) << 8l;
150 put16(short word
, FILE *fh
)
152 unsigned short w
= (unsigned short)word
;
154 PUTC((char)(w
>> 8l), fh
);
158 baseleaf_from_fnm(const char *fnm
)
168 q
= strrchr(p
, '\\');
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
;
182 static char * my_strdup(const char *str
);
185 mktime_with_tz(struct tm
* tm
, const char * tz
)
188 char * old_tz
= getenv("TZ");
191 old_tz
= my_strdup(old_tz
);
195 if (_putenv_s("TZ", tz
) != 0) {
199 #elif defined HAVE_SETENV
201 old_tz
= my_strdup(old_tz
);
205 if (setenv("TZ", tz
, 1) < 0) {
212 size_t len
= strlen(old_tz
) + 1;
213 p
= (char *)xosmalloc(len
+ 3);
217 memcpy(p
+ 3, tz
, len
);
220 p
= (char *)xosmalloc(strlen(tz
) + 4);
227 if (putenv(p
) != 0) {
232 #define CLEANUP() osfree(p)
238 _putenv_s("TZ", old_tz
);
239 #elif !defined HAVE_SETENV
242 setenv("TZ", old_tz
, 1);
248 #elif !defined HAVE_UNSETENV
261 static unsigned short
264 return (unsigned short)get16(fh
);
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
281 # if defined HAVE_DECL_LROUND && !HAVE_DECL_LROUND
282 /* On older systems, the prototype may be missing. */
283 extern long lround(double);
285 # define my_lround lround
288 my_lround(double x
) {
289 return (x
>= 0.0) ? (long)(x
+ 0.5) : -(long)(0.5 - x
);
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
329 hash_data(const char *s
, unsigned len
)
331 /* djb2 hash but with an initial value of zero. */
334 unsigned char c
= (unsigned char)*s
++;
335 h
= ((h
<< 5) + h
) + c
;
341 struct compass_station
{
342 struct compass_station
*next
;
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
367 compass_plt_allocate_hash(void)
369 struct compass_station_name
** htab
= xosmalloc(HASH_BUCKETS
* sizeof(struct compass_station_name
*));
372 for (i
= 0; i
< HASH_BUCKETS
; ++i
)
379 compass_plt_update_station(img
*pimg
, const char *name
, int name_len
,
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) {
389 if (p
->flags
& COMPASS_SFLAG_DIFFERENT_SURVEY
)
390 p
->flags
|= img_SFLAG_EXPORTED
;
395 p
= malloc(offsetof(struct compass_station
, name
) + name_len
);
399 memcpy(p
->name
, name
, name_len
);
406 compass_plt_new_survey(img
*pimg
)
408 struct compass_station
**htab
= (struct compass_station
**)pimg
->data
;
409 int i
= HASH_BUCKETS
;
411 struct compass_station
*p
;
412 for (p
= *htab
; p
; p
= p
->next
) {
413 p
->flags
|= COMPASS_SFLAG_DIFFERENT_SURVEY
;
420 compass_plt_free_data(img
*pimg
)
422 struct compass_station
**htab
= (struct compass_station
**)pimg
->data
;
423 int i
= HASH_BUCKETS
;
425 struct compass_station
*p
= *htab
;
427 struct compass_station
*next
= p
->next
;
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
;
450 return p
->flags
| INT_MIN
;
458 my_strdup(const char *str
)
461 size_t len
= strlen(str
) + 1;
462 p
= (char *)xosmalloc(len
);
463 if (p
) memcpy(p
, str
, len
);
467 #define getline_alloc(FH) getline_alloc_len(FH, NULL)
470 getline_alloc_len(FILE *fh
, size_t * p_len
)
475 char *buf
= (char *)xosmalloc(len
);
476 if (!buf
) return NULL
;
479 while (ch
!= '\n' && ch
!= '\r' && ch
!= EOF
) {
484 p
= (char *)xosrealloc(buf
, len
);
493 if (ch
== '\n' || ch
== '\r') {
494 int otherone
= ch
^ ('\n' ^ '\r');
496 /* if it's not the other eol character, put it back */
497 if (ch
!= otherone
) ungetc(ch
, fh
);
500 if (p_len
) *p_len
= i
;
511 check_label_space(img
*pimg
, size_t len
)
513 if (len
> pimg
->buf_len
) {
514 char *b
= (char *)xosrealloc(pimg
->label_buf
, len
);
516 pimg
->label
= (pimg
->label
- pimg
->label_buf
) + b
;
523 /* Check if a station name should be included. */
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) {
533 pimg
->label
+= l
+ 1;
537 /* Check if a survey name should be included. */
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')) {
549 /* skip the dot if there */
550 if (*pimg
->label
) pimg
->label
++;
554 /* Check if a survey name in a buffer should be included.
556 * For "foreign" formats which just have one level of surveys.
559 buf_included(img
*pimg
, const char *buf
, size_t len
)
561 return pimg
->survey_len
== len
&& memcmp(buf
, pimg
->survey
, len
) == 0;
565 img_open_survey(const char *fnm
, const char *survey
)
569 char* filename_opened
= NULL
;
571 if (fDirectory(fnm
)) {
572 img_errno
= IMG_DIRECTORY
;
576 fh
= fopenWithPthAndExt("", fnm
, EXT_SVX_3D
, "rb", &filename_opened
);
577 pimg
= img_read_stream_survey(fh
, fclose
,
578 filename_opened
? filename_opened
: fnm
,
581 pimg
->filename_opened
= filename_opened
;
583 osfree(filename_opened
);
589 compass_plt_open(img
*pimg
)
592 int datum
= img_DATUM_UNKNOWN
;
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
= ' ';
602 pimg
->datestamp
= my_strdup(TIMENA
);
603 if (!pimg
->datestamp
) {
604 return IMG_OUTOFMEMORY
;
606 pimg
->data
= compass_plt_allocate_hash();
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".
616 int ch
= GETC(pimg
->fh
);
619 fseek(pimg
->fh
, -1, SEEK_CUR
);
622 if (pimg
->start
< 0) {
623 pimg
->start
= ftell(pimg
->fh
);
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";
633 case img_DATUM_NAD27
:
635 template = "+proj=utm +zone=%d +datum=NAD27 +south +units=m +no_defs +type=crs";
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";
643 value
= 3311 + utm_zone
;
646 case img_DATUM_NAD83
:
648 template = "+proj=utm +zone=%d +datum=NAD83 +south +units=m +no_defs +type=crs";
650 } else if (utm_zone
<= 23) {
651 value
= 26900 + utm_zone
;
652 } else if (utm_zone
== 24) {
654 } else if (utm_zone
< 59) {
655 template = "+proj=utm +zone=%d +datum=NAD83 +units=m +no_defs +type=crs";
658 value
= 3313 + utm_zone
;
661 case img_DATUM_WGS84
:
663 value
= 32600 + utm_zone
;
665 value
= 32700 - utm_zone
;
670 size_t len
= strlen(template) + 4;
671 pimg
->cs
= (char*)xosmalloc(len
);
673 goto out_of_memory_error
;
675 SNPRINTF(pimg
->cs
, len
, template, value
);
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
);
689 goto out_of_memory_error
;
693 if (strcmp(pimg
->title
, line
) != 0) {
694 /* Two different non-empty section names found. */
695 pimg
->title
[0] = '\0';
710 compass_plt_new_survey(pimg
);
711 if (pimg
->start
>= 0) break;
712 fpos
= ftell(pimg
->fh
) - 1;
714 /* We're not filtering by survey so just note down the file
715 * offset for the first N command. */
719 line
= getline_alloc(pimg
->fh
);
721 goto out_of_memory_error
;
724 while (line
[len
] > 32) ++len
;
725 if (!buf_included(pimg
, line
, len
)) {
726 /* Not the survey we are looking for. */
730 q
= strchr(line
+ len
, 'C');
733 pimg
->title
= my_strdup(q
+ 1);
734 } else if (!pimg
->title
) {
735 pimg
->title
= my_strdup(pimg
->label
);
739 goto out_of_memory_error
;
750 unsigned station_flags
= 0;
752 int not_plotted
= (command
== 'd');
754 /* Find station name. */
755 do { ch
= GETC(pimg
->fh
); } while (ch
>= ' ' && ch
!= 'S');
758 /* Leave reporting error to second pass for consistency. */
762 name
= getline_alloc(pimg
->fh
);
764 goto out_of_memory_error
;
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. */
773 return IMG_BADFORMAT
;
776 /* Check for the "distance from entrance" field. */
777 q
= strchr(name
+ name_len
, 'I');
779 double distance_from_entrance
;
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
;
789 while (*q
&& *q
<= ' ') q
++;
791 q
= strchr(name
+ name_len
, 'F');
794 if (q
&& *q
== 'F') {
796 while (isalpha((unsigned char)*++q
)) {
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
;
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
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
;
843 char *line
, *q
, *name
;
846 line
= getline_alloc(pimg
->fh
);
848 goto out_of_memory_error
;
851 while (*q
&& *q
<= ' ') q
++;
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. */
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
;
872 /* UTM Zone - 1 to 60 for North, -1 to -60 for South. */
873 char *line
= getline_alloc(pimg
->fh
);
875 long v
= strtol(p
, &p
, 10);
876 if (v
< -60 || v
> 60 || v
== 0 || *p
> ' ') {
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.
895 char *line
= getline_alloc(pimg
->fh
);
897 goto out_of_memory_error
;
899 if (utm_zone
== 99) {
904 new_datum
= img_parse_compass_datum_string(line
, strlen(line
));
905 if (new_datum
== img_DATUM_UNKNOWN
) {
907 } else if (datum
== img_DATUM_UNKNOWN
) {
909 } else if (datum
!= new_datum
) {
917 while (ch
!= '\n' && ch
!= '\r') {
923 return IMG_OUTOFMEMORY
;
927 cmap_xyz_open(img
*pimg
)
930 char *line
= getline_alloc(pimg
->fh
);
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
947 /* Don't just truncate at column 59, allow for a > 2 digit year. */
948 char * p
= strstr(line
+ len
, "Page");
950 while (p
> line
&& p
[-1] == ' ')
963 pimg
->datestamp
= my_strdup(line
+ 45);
965 v
= strtoul(p
, &p
, 10);
967 /* In the absence of a spec for cmap files, assume <= 50 means 21st
970 } else if (v
< 200) {
971 /* Map 100-199 to 21st century. */
974 if (v
== ULONG_MAX
|| *p
++ != '/')
976 tm
.tm_year
= v
- 1900;
977 v
= strtoul(p
, &p
, 10);
978 if (v
< 1 || v
> 12 || *p
++ != '/')
981 v
= strtoul(p
, &p
, 10);
982 if (v
< 1 || v
> 31 || *p
++ != ' ')
985 v
= strtoul(p
, &p
, 10);
986 if (v
>= 24 || *p
++ != ':')
989 v
= strtoul(p
, &p
, 10);
994 v
= strtoul(p
+ 1, &p
, 10);
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
, "");
1008 pimg
->datestamp
= my_strdup(TIMENA
);
1011 if (strncmp(line
, " Cave Survey Data Processed by CMAP ",
1012 LITLEN(" Cave Survey Data Processed by CMAP ")) != 0) {
1017 while (len
> 2 && line
[len
- 1] == ' ') --len
;
1020 pimg
->title
= my_strdup(line
+ 2);
1024 if (!pimg
->datestamp
|| !pimg
->title
) {
1025 return IMG_OUTOFMEMORY
;
1027 line
= getline_alloc(pimg
->fh
);
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
;
1037 pimg
->version
= VERSION_CMAP_SHOT
;
1040 line
= getline_alloc(pimg
->fh
);
1042 return IMG_OUTOFMEMORY
;
1044 if (line
[0] != ' ' || line
[1] != '-') {
1045 return IMG_BADFORMAT
;
1048 pimg
->start
= ftell(pimg
->fh
);
1053 img_read_stream_survey(FILE *stream
, int (*close_func
)(FILE*),
1059 char buf
[LITLEN(FILEID
) + 9];
1063 if (stream
== NULL
) {
1064 img_errno
= IMG_FILENOTFOUND
;
1070 img_errno
= IMG_OUTOFMEMORY
;
1071 if (close_func
) close_func(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
);
1083 img_errno
= IMG_OUTOFMEMORY
;
1087 pimg
->fRead
= 1; /* reading from this file */
1088 img_errno
= IMG_NONE
;
1091 pimg
->filename_opened
= 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;
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;
1120 len
= strlen(survey
);
1122 if (survey
[len
- 1] == '.') len
--;
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
);
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
1155 /* Currently only 3 character extensions are tested below. */
1157 if (len
> 4 && fnm
[len
- 4] == '.') {
1158 /* Read extension and pack into ext. */
1160 for (i
= 1; i
< 4; ++i
) {
1161 unsigned char ext_ch
= fnm
[len
- i
];
1162 ext
= (ext
<< 8) | tolower(ext_ch
);
1166 case EXT3('p', 'o', 's'): /* Survex .pos */
1168 pimg
->version
= VERSION_SURVEX_POS
;
1169 pimg
->datestamp
= my_strdup(TIMENA
);
1170 if (!pimg
->datestamp
) {
1171 goto out_of_memory_error
;
1174 goto successful_return
;
1176 case EXT3('p', 'l', 't'): /* Compass .plt */
1177 case EXT3('p', 'l', 'f'): /* Compass .plf */ {
1180 result
= compass_plt_open(pimg
);
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 */ {
1200 result
= cmap_xyz_open(pimg
);
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 */
1218 if (buf
[1] == ' ') {
1219 if (buf
[0] == ' ') {
1220 /* Looks like a CMAP .xyz file ... */
1222 } else if (strchr("ZSNF", buf
[0])) {
1223 /* Looks like a Compass .plt file ... */
1224 /* Almost certainly it'll start "Z " */
1228 if (buf
[0] == '(') {
1229 /* Looks like a Survex .pos file ... */
1232 img_errno
= IMG_BADFORMAT
;
1236 /* check file format version */
1237 ch
= GETC(pimg
->fh
);
1239 if (tolower(ch
) == 'b') {
1240 /* binary file iff B/b prefix */
1242 ch
= GETC(pimg
->fh
);
1245 img_errno
= IMG_BADFORMAT
;
1248 ch
= GETC(pimg
->fh
);
1250 if (fread(buf
, 4, 1, pimg
->fh
) != 1 || memcmp(buf
, ".01\n", 4) != 0) {
1251 img_errno
= IMG_BADFORMAT
;
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
;
1260 pimg
->version
= ch
- '0';
1262 img_errno
= IMG_BADFORMAT
;
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
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
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
1296 if (p
[4] == ':' && isdigit((unsigned char)p
[5]) &&
1297 ((memcmp(p
, "epsg", 4) == 0 || memcmp(p
, "esri", 4) == 0))) {
1299 while (isdigit((unsigned char)*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
1307 if (*p
== '\0' || strcmp(p
, " +no_defs") == 0) {
1310 for (i
= 0; i
< 4; ++i
) {
1311 cs
[i
] = toupper(cs
[i
]);
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.
1321 if (memcmp(p
, "utm +ellps=WGS84 +datum=WGS84 +units=m +zone=", 45) == 0) {
1324 while (isdigit((unsigned char)*p
)) {
1325 n
= n
* 10 + (*p
- '0');
1328 if (memcmp(p
, " +south", 7) == 0) {
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
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) {
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
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];
1365 pimg
->title
= title
;
1370 pimg
->datestamp
= getline_alloc(pimg
->fh
);
1371 if (!pimg
->datestamp
) {
1372 out_of_memory_error
:
1373 img_errno
= IMG_OUTOFMEMORY
;
1375 osfree(pimg
->title
);
1377 osfree(pimg
->datestamp
);
1378 osfree(pimg
->filename_opened
);
1379 if (pimg
->close_func
) pimg
->close_func(pimg
->fh
);
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] == '@') {
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
1407 /* %a,%Y.%m.%d %H:%M:%S %Z */
1410 char * p
= pimg
->datestamp
;
1411 while (isalpha((unsigned char)*p
)) ++p
;
1413 while (isspace((unsigned char)*p
)) ++p
;
1414 v
= strtoul(p
, &p
, 10);
1415 if (v
== ULONG_MAX
|| *p
++ != '.')
1417 tm
.tm_year
= v
- 1900;
1418 v
= strtoul(p
, &p
, 10);
1419 if (v
< 1 || v
> 12 || *p
++ != '.')
1422 v
= strtoul(p
, &p
, 10);
1423 if (v
< 1 || v
> 31 || *p
++ != ' ')
1426 v
= strtoul(p
, &p
, 10);
1427 if (v
>= 24 || *p
++ != ':')
1430 v
= strtoul(p
, &p
, 10);
1431 if (v
>= 60 || *p
++ != ':')
1434 v
= strtoul(p
, &p
, 10);
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
);
1451 pimg
->start
= ftell(pimg
->fh
);
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
);
1463 img_rewind(img
*pimg
)
1466 img_errno
= IMG_WRITEERROR
;
1469 if (fseek(pimg
->fh
, pimg
->start
, SEEK_SET
) != 0) {
1470 img_errno
= IMG_READERROR
;
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 */
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
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
;
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
;
1499 return img_write_stream(fopen(fnm
, "wb"), fclose
, title
, cs
, flags
);
1503 img_write_stream(FILE *stream
, int (*close_func
)(FILE*),
1504 const char *title
, const char *cs
, int flags
)
1509 if (stream
== NULL
) {
1510 img_errno
= IMG_FILENOTFOUND
;
1516 img_errno
= IMG_OUTOFMEMORY
;
1517 if (close_func
) close_func(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
);
1528 img_errno
= IMG_OUTOFMEMORY
;
1532 pimg
->filename_opened
= 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) {
1541 fputs("Bv0.01\n", pimg
->fh
); /* binary file format version number */
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
1579 * See https://reproducible-builds.org/docs/source-date-epoch/
1586 if (tm
== (time_t)-1) {
1587 fputsnl(TIMENA
, pimg
->fh
);
1588 } else if (pimg
->version
<= 7) {
1590 /* output current date and time in format specified */
1591 strftime(date
, 256, TIMEFMT
, localtime(&tm
));
1592 fputsnl(date
, pimg
->fh
);
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. */
1600 PUTC(flags
, pimg
->fh
);
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
);
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;
1626 pimg
->style
= pimg
->oldstyle
= img_STYLE_UNKNOWN
;
1628 pimg
->l
= pimg
->r
= pimg
->u
= pimg
->d
= -1.0;
1632 pimg
->E
= pimg
->H
= pimg
->V
= 0.0;
1634 /* Don't check for write errors now - let img_close() report them... */
1639 read_xyz_station_coords(img_point
*pt
, const char *line
)
1642 memcpy(num
, line
+ 6, 9);
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);
1649 pt
->z
= atof(num
) / METRES_PER_FOOT
;
1653 read_xyz_shot_coords(img_point
*pt
, const char *line
)
1656 memcpy(num
, line
+ 40, 10);
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);
1663 pt
->z
= atof(num
) / METRES_PER_FOOT
;
1667 subtract_xyz_shot_deltas(img_point
*pt
, const char *line
)
1670 memcpy(num
, line
+ 15, 9);
1672 pt
->x
-= atof(num
) / METRES_PER_FOOT
;
1673 memcpy(num
, line
+ 24, 8);
1675 pt
->y
-= atof(num
) / METRES_PER_FOOT
;
1676 memcpy(num
, line
+ 32, 8);
1677 pt
->z
-= atof(num
) / METRES_PER_FOOT
;
1681 read_coord(FILE *fh
, img_point
*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
;
1696 skip_coord(FILE *fh
)
1698 return (fseek(fh
, 12, SEEK_CUR
) == 0);
1702 read_v3label(img
*pimg
)
1705 long len
= GETC(pimg
->fh
);
1707 img_errno
= feof(pimg
->fh
) ? IMG_BADFORMAT
: IMG_READERROR
;
1711 len
+= get16(pimg
->fh
);
1712 if (feof(pimg
->fh
)) {
1713 img_errno
= IMG_BADFORMAT
;
1716 if (ferror(pimg
->fh
)) {
1717 img_errno
= IMG_READERROR
;
1720 } else if (len
== 0xff) {
1721 len
= get32(pimg
->fh
);
1722 if (ferror(pimg
->fh
)) {
1723 img_errno
= IMG_READERROR
;
1726 if (feof(pimg
->fh
) || len
< 0xfe + 0xffff) {
1727 img_errno
= IMG_BADFORMAT
;
1732 if (!check_label_space(pimg
, pimg
->label_len
+ len
+ 1)) {
1733 img_errno
= IMG_OUTOFMEMORY
;
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
;
1747 read_v8label(img
*pimg
, int common_flag
, size_t common_val
)
1752 if (common_val
== 0) return 0;
1753 add
= del
= common_val
;
1755 int ch
= GETC(pimg
->fh
);
1757 img_errno
= feof(pimg
->fh
) ? IMG_BADFORMAT
: IMG_READERROR
;
1764 ch
= GETC(pimg
->fh
);
1766 img_errno
= feof(pimg
->fh
) ? IMG_BADFORMAT
: IMG_READERROR
;
1772 del
= get32(pimg
->fh
);
1773 if (ferror(pimg
->fh
)) {
1774 img_errno
= IMG_READERROR
;
1778 ch
= GETC(pimg
->fh
);
1780 img_errno
= feof(pimg
->fh
) ? IMG_BADFORMAT
: IMG_READERROR
;
1786 add
= get32(pimg
->fh
);
1787 if (ferror(pimg
->fh
)) {
1788 img_errno
= IMG_READERROR
;
1794 if (add
> del
&& !check_label_space(pimg
, pimg
->label_len
+ add
- del
+ 1)) {
1795 img_errno
= IMG_OUTOFMEMORY
;
1799 if (del
> pimg
->label_len
) {
1800 img_errno
= IMG_BADFORMAT
;
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
;
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
)
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
);
1832 return img_read_item_ascii_wrapper(pimg
, p
);
1837 img_read_item_new(img
*pimg
, img_point
*p
)
1841 pimg
->l
= pimg
->r
= pimg
->u
= pimg
->d
= -1.0;
1842 if (pimg
->pending
>= 0x40) {
1843 if (pimg
->pending
== PENDING_XSECT_END
) {
1845 return img_XSECT_END
;
1848 pimg
->flags
= (int)(pimg
->pending
) & 0x3f;
1852 again3
: /* label to goto if we get a prefix, date, or lrud */
1853 pimg
->label
= pimg
->label_buf
;
1854 opt
= GETC(pimg
->fh
);
1856 img_errno
= feof(pimg
->fh
) ? IMG_BADFORMAT
: IMG_READERROR
;
1859 if (opt
>> 6 == 0) {
1861 if (opt
== 0 && pimg
->style
== 0)
1862 return img_STOP
; /* end of data marker */
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;
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
;
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
;
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
;
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;
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;
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;
1942 default: /* 0x25 - 0x2f and 0x34 - 0x3f are currently unallocated. */
1943 img_errno
= IMG_BADFORMAT
;
1949 /* 1-14 and 16-31 reserved */
1950 img_errno
= IMG_BADFORMAT
;
1954 } else if (opt
>= 0x80) {
1955 if (read_v8label(pimg
, 0, 0) == img_BAD
) return img_BAD
;
1959 if (!stn_included(pimg
)) {
1960 if (!skip_coord(pimg
->fh
)) return img_BAD
;
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
;
1971 if (!survey_included(pimg
)) {
1972 if (!read_coord(pimg
->fh
, &(pimg
->mv
))) return img_BAD
;
1977 if (pimg
->pending
) {
1979 if (!read_coord(pimg
->fh
, &(pimg
->mv
))) return img_BAD
;
1980 pimg
->pending
= opt
;
1983 pimg
->flags
= (int)opt
& 0x1f;
1985 img_errno
= IMG_BADFORMAT
;
1988 if (!read_coord(pimg
->fh
, p
)) return img_BAD
;
1994 img_read_item_v3to7(img
*pimg
, img_point
*p
)
1998 pimg
->l
= pimg
->r
= pimg
->u
= pimg
->d
= -1.0;
1999 if (pimg
->pending
== PENDING_XSECT_END
) {
2001 return img_XSECT_END
;
2003 if (pimg
->pending
>= 0x80) {
2005 pimg
->flags
= (int)(pimg
->pending
) & 0x3f;
2009 again3
: /* label to goto if we get a prefix, date, or lrud */
2010 pimg
->label
= pimg
->label_buf
;
2011 opt
= GETC(pimg
->fh
);
2013 img_errno
= feof(pimg
->fh
) ? IMG_BADFORMAT
: IMG_READERROR
;
2019 if (!pimg
->label_len
) return img_STOP
; /* end of data marker */
2020 pimg
->label_len
= 0;
2024 /* 1-14 mean trim that many levels from current prefix */
2026 if (pimg
->label_len
<= 17) {
2027 /* zero prefix using "0" */
2028 img_errno
= IMG_BADFORMAT
;
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) {
2035 /* zero prefix using "0" */
2036 img_errno
= IMG_BADFORMAT
;
2041 pimg
->label_len
= c
;
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 */
2057 pimg
->days1
= (date1
- TIME_T_1900
) / SECS_PER_DAY
;
2058 pimg
->days2
= pimg
->days1
;
2060 pimg
->days2
= pimg
->days1
= -1;
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
;
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
;
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
;
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
;
2106 if (ferror(pimg
->fh
)) {
2107 img_errno
= IMG_READERROR
;
2110 return img_ERROR_INFO
;
2111 case 0x23: { /* v7+: Date range (long) */
2112 if (pimg
->version
< 7) {
2113 img_errno
= IMG_BADFORMAT
;
2116 int days1
= (int)getu16(pimg
->fh
);
2117 int days2
= (int)getu16(pimg
->fh
);
2118 if (feof(pimg
->fh
)) {
2119 img_errno
= IMG_BADFORMAT
;
2122 if (ferror(pimg
->fh
)) {
2123 img_errno
= IMG_READERROR
;
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
;
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;
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;
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;
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
;
2162 if (ferror(pimg
->fh
)) {
2163 img_errno
= IMG_READERROR
;
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;
2176 default: /* 0x25 - 0x2f and 0x34 - 0x3f are currently unallocated. */
2177 img_errno
= IMG_BADFORMAT
;
2180 if (feof(pimg
->fh
)) {
2181 img_errno
= IMG_BADFORMAT
;
2184 if (ferror(pimg
->fh
)) {
2185 img_errno
= IMG_READERROR
;
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
;
2196 pimg
->label_len
-= (opt
- 15);
2199 if (read_v3label(pimg
) == img_BAD
) return img_BAD
;
2203 if (!stn_included(pimg
)) {
2204 if (!skip_coord(pimg
->fh
)) return img_BAD
;
2209 pimg
->flags
= (int)opt
& 0x3f;
2212 if (read_v3label(pimg
) == img_BAD
) return img_BAD
;
2216 if (!survey_included(pimg
)) {
2217 if (!read_coord(pimg
->fh
, &(pimg
->mv
))) return img_BAD
;
2222 if (pimg
->pending
) {
2224 if (!read_coord(pimg
->fh
, &(pimg
->mv
))) return img_BAD
;
2225 pimg
->pending
= opt
;
2228 pimg
->flags
= (int)opt
& 0x3f;
2231 img_errno
= IMG_BADFORMAT
;
2234 if (!read_coord(pimg
->fh
, p
)) return img_BAD
;
2240 img_read_item_ancient(img
*pimg
, img_point
*p
)
2243 static long opt_lookahead
= 0;
2244 static img_point pt
= { 0.0, 0.0, 0.0 };
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
;
2256 opt
= get32(pimg
->fh
);
2259 opt
= GETC(pimg
->fh
);
2262 if (feof(pimg
->fh
)) {
2263 img_errno
= IMG_BADFORMAT
;
2266 if (ferror(pimg
->fh
)) {
2267 img_errno
= IMG_READERROR
;
2273 return img_STOP
; /* end of data marker */
2275 /* skip coordinates */
2276 if (!skip_coord(pimg
->fh
)) {
2277 img_errno
= feof(pimg
->fh
) ? IMG_BADFORMAT
: IMG_READERROR
;
2284 if (!fgets(pimg
->label_buf
, pimg
->buf_len
, pimg
->fh
)) {
2285 img_errno
= feof(pimg
->fh
) ? IMG_BADFORMAT
: IMG_READERROR
;
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
;
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
;
2306 pimg
->flags
= GETC(pimg
->fh
);
2308 pimg
->flags
= img_SFLAG_UNDERGROUND
; /* no flags given... */
2310 len
= get32(pimg
->fh
);
2312 if (feof(pimg
->fh
)) {
2313 img_errno
= IMG_BADFORMAT
;
2316 if (ferror(pimg
->fh
)) {
2317 img_errno
= IMG_READERROR
;
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
;
2327 if (fread(pimg
->label_buf
, len
, 1, pimg
->fh
) != 1) {
2328 img_errno
= feof(pimg
->fh
) ? IMG_BADFORMAT
: IMG_READERROR
;
2331 pimg
->label_buf
[len
] = '\0';
2341 switch ((int)opt
& 0xc0) {
2343 pimg
->flags
= (int)opt
& 0x3f;
2348 pimg
->flags
= (int)opt
& 0x3f;
2350 if (!fgets(pimg
->label_buf
, pimg
->buf_len
, pimg
->fh
)) {
2351 img_errno
= feof(pimg
->fh
) ? IMG_BADFORMAT
: IMG_READERROR
;
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
;
2358 img_errno
= IMG_BADFORMAT
;
2365 img_errno
= IMG_BADFORMAT
;
2371 if (!read_coord(pimg
->fh
, &pt
)) return img_BAD
;
2373 if (result
== img_LABEL
&& !stn_included(pimg
)) {
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
;
2388 if (ferror(pimg
->fh
)) {
2389 img_errno
= IMG_READERROR
;
2393 if (opt_lookahead
== 2) return img_read_item_ancient(pimg
, p
);
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. */
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
);
2413 /* Handle all ASCII formats. */
2415 img_read_item_ascii(img
*pimg
, img_point
*p
)
2418 pimg
->label
= pimg
->label_buf
;
2419 if (pimg
->version
== 0) {
2421 pimg
->label
[0] = '\0';
2422 if (feof(pimg
->fh
)) return img_STOP
;
2423 if (pimg
->pending
) {
2428 /* Stop if nothing found */
2429 if (fscanf(pimg
->fh
, "%6s", cmd
) < 1) return img_STOP
;
2430 if (strcmp(cmd
, "move") == 0)
2432 else if (strcmp(cmd
, "draw") == 0)
2434 else if (strcmp(cmd
, "line") == 0) {
2435 /* set flag to indicate to process second triplet as LINE */
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
;
2444 } else if (strcmp(cmd
, "name") == 0) {
2446 int ch
= GETC(pimg
->fh
);
2447 if (ch
== ' ') ch
= GETC(pimg
->fh
);
2449 if (ch
== '\n' || ch
== EOF
) {
2450 img_errno
= ferror(pimg
->fh
) ? IMG_READERROR
: IMG_BADFORMAT
;
2453 if (off
== pimg
->buf_len
) {
2454 if (!check_label_space(pimg
, pimg
->buf_len
* 2)) {
2455 img_errno
= IMG_OUTOFMEMORY
;
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 */
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
;
2481 if (result
== img_LABEL
&& !stn_included(pimg
)) {
2486 } else if (pimg
->version
== VERSION_SURVEX_POS
) {
2487 /* Survex .pos file */
2490 pimg
->flags
= img_SFLAG_UNDERGROUND
; /* default flags */
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
;
2497 if (feof(pimg
->fh
)) return img_STOP
;
2498 if (pimg
->pending
) {
2499 img_errno
= IMG_BADFORMAT
;
2503 /* ignore rest of line */
2505 ch
= GETC(pimg
->fh
);
2506 } while (ch
!= '\n' && ch
!= '\r' && ch
!= EOF
);
2509 pimg
->label_buf
[0] = '\0';
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
;
2518 pimg
->label_buf
[0] = ch
;
2520 while (!feof(pimg
->fh
)) {
2521 if (!fgets(pimg
->label_buf
+ off
, pimg
->buf_len
- off
, pimg
->fh
)) {
2522 img_errno
= IMG_READERROR
;
2526 off
+= strlen(pimg
->label_buf
+ off
);
2527 if (off
&& pimg
->label_buf
[off
- 1] == '\n') {
2528 pimg
->label_buf
[off
- 1] = '\0';
2531 if (!check_label_space(pimg
, pimg
->buf_len
* 2)) {
2532 img_errno
= IMG_OUTOFMEMORY
;
2537 pimg
->label
= pimg
->label_buf
;
2539 if (pimg
->label
[0] == '\\') pimg
->label
++;
2541 if (!stn_included(pimg
)) goto againpos
;
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'...
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
) {
2558 pimg
->pending
&= ~PENDING_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
;
2567 pimg
->pending
&= ~PENDING_MOVE
;
2575 int ch
= GETC(pimg
->fh
);
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
);
2582 return img_XSECT_END
;
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
);
2591 return img_XSECT_END
;
2593 if (pimg
->survey
) return img_STOP
;
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
;
2605 compass_plt_new_survey(pimg
);
2606 line
= getline_alloc(pimg
->fh
);
2608 img_errno
= IMG_OUTOFMEMORY
;
2611 while (line
[len
] > 32) ++len
;
2612 if (pimg
->label_len
== 0) pimg
->pending
= -1;
2613 if (!check_label_space(pimg
, len
+ 1)) {
2615 img_errno
= IMG_OUTOFMEMORY
;
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') {
2626 memset(&tm
, 0, sizeof(tm
));
2629 /* NB Order is Month Day Year order. */
2630 v
= strtoul(q
, &q
, 10);
2631 if (v
< 1 || v
> 12)
2635 v
= strtoul(q
, &q
, 10);
2636 if (v
< 1 || v
> 31)
2640 v
= strtoul(q
, &q
, 10);
2644 /* "The Year is expected to be the full year like 1994 not
2645 * 94", but "expected to" != "must" so treat a two digit
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". */
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.
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
;
2672 #if IMG_API_VERSION == 0
2673 pimg
->date1
= pimg
->date2
= 0;
2674 #else /* IMG_API_VERSION == 1 */
2675 pimg
->days1
= pimg
->days2
= -1;
2681 if (pimg
->pending
== PENDING_HAD_XSECT
) {
2682 pimg
->pending
= PENDING_XSECT_END
;
2688 unsigned shot_flags
= (ch
== 'd' ? img_FLAG_SURFACE
: 0);
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...
2696 if (pimg
->pending
== -1) {
2700 fpos
= ftell(pimg
->fh
) - 1;
2701 fseek(pimg
->fh
, pimg
->start
, SEEK_SET
);
2702 ch
= GETC(pimg
->fh
);
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!
2712 line
= getline_alloc(pimg
->fh
);
2714 img_errno
= IMG_OUTOFMEMORY
;
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) {
2720 if (ferror(pimg
->fh
)) {
2721 img_errno
= IMG_READERROR
;
2723 img_errno
= IMG_BADFORMAT
;
2727 p
->x
*= METRES_PER_FOOT
;
2728 p
->y
*= METRES_PER_FOOT
;
2729 p
->z
*= METRES_PER_FOOT
;
2730 q
= strchr(line
, 'S');
2733 img_errno
= IMG_BADFORMAT
;
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
;
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';
2751 memcpy(pimg
->label
, q
, len
);
2752 pimg
->label
[len
] = '\0';
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
2761 while (*q
&& *q
<= ' ') q
++;
2765 if (sscanf(q
, "%lf%lf%lf%lf%n",
2766 &pimg
->l
, &pimg
->r
, &pimg
->u
, &pimg
->d
,
2767 &bytes_used
) != 4) {
2769 if (ferror(pimg
->fh
)) {
2770 img_errno
= IMG_READERROR
;
2772 img_errno
= IMG_BADFORMAT
;
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
;
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
++;
2795 /* Skip distance from entrance. */
2796 do ++q
; while (*q
&& *q
<= ' ');
2797 while (*q
> ' ') q
++;
2798 while (*q
&& *q
<= ' ') q
++;
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
)) {
2808 shot_flags
|= img_FLAG_DUPLICATE
;
2811 shot_flags
|= img_FLAG_SPLAY
;
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
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
2824 shot_flags
|= img_FLAG_SURFACE
;
2829 if (shot_flags
& img_FLAG_SURFACE
) {
2830 /* Suppress passage? */
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. */
2847 pimg
->pending
|= PENDING_MOVE
;
2849 pimg
->pending
|= PENDING_LINE
| (shot_flags
<< PENDING_FLAGS_SHIFT
);
2856 img_errno
= IMG_BADFORMAT
;
2861 /* CMAP .xyz file */
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
);
2873 if (r
== img_LABEL
) {
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;
2883 if (r
== img_STOP
) {
2885 read_xyz_shot_coords(p
, pimg
->label_buf
+ 16);
2892 pimg
->label
= pimg
->label_buf
;
2895 if (feof(pimg
->fh
)) return img_STOP
;
2896 line
= getline_alloc(pimg
->fh
);
2898 img_errno
= IMG_OUTOFMEMORY
;
2901 } while (line
[0] == ' ' || line
[0] == '\0');
2902 if (line
[0] == '\x1a') return img_STOP
;
2905 if (pimg
->version
== VERSION_CMAP_STATION
) {
2906 /* station variant */
2909 img_errno
= IMG_BADFORMAT
;
2912 memcpy(pimg
->label
, line
, 6);
2913 q
= (char *)memchr(pimg
->label
, ' ', 6);
2914 if (!q
) q
= pimg
->label
+ 6;
2917 read_xyz_station_coords(p
, line
);
2919 /* FIXME: look at prev for lines (line + 32, 5) */
2920 /* FIXME: duplicate stations... */
2923 /* Shot variant (VERSION_CMAP_SHOT) */
2924 char old
[8], new_
[8];
2927 img_errno
= IMG_BADFORMAT
;
2931 memcpy(old
, line
, 7);
2932 q
= (char *)memchr(old
, ' ', 7);
2933 if (!q
) q
= old
+ 7;
2936 memcpy(new_
, line
+ 7, 7);
2937 q
= (char *)memchr(new_
, ' ', 7);
2938 if (!q
) q
= new_
+ 7;
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_
);
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_
);
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);
2971 write_coord(FILE *fh
, double x
, double y
, double z
)
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);
2985 write_v3label(img
*pimg
, int opt
, const char *s
)
2989 /* find length of common prefix */
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
;
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
);
3005 const char *p
= pimg
->label_buf
+ dot
;
3007 for (len
= pimg
->label_len
- dot
- 17; len
; len
--) {
3008 if (*p
++ == '.') n
++;
3014 if (pimg
->label_len
) PUTC(0, pimg
->fh
);
3019 n
= strlen(s
+ len
);
3020 PUTC(opt
, pimg
->fh
);
3023 } else if (n
< 0xffff + 0xfe) {
3024 PUTC(0xfe, pimg
->fh
);
3025 put16((short)(n
- 0xfe), pimg
->fh
);
3027 PUTC(0xff, pimg
->fh
);
3030 fwrite(s
+ len
, n
, 1, pimg
->fh
);
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
);
3042 write_v8label(img
*pimg
, int opt
, int common_flag
, size_t common_val
,
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
);
3058 PUTC(opt
, pimg
->fh
);
3059 if (del
<= 15 && add
<= 15 && (del
|| add
)) {
3060 PUTC((del
<< 4) | add
, pimg
->fh
);
3062 PUTC(0x00, pimg
->fh
);
3064 PUTC(del
, pimg
->fh
);
3066 PUTC(0xff, pimg
->fh
);
3067 put32(del
, pimg
->fh
);
3070 PUTC(add
, pimg
->fh
);
3072 PUTC(0xff, pimg
->fh
);
3073 put32(add
, pimg
->fh
);
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
);
3091 img_write_item_date_new(img
*pimg
)
3094 /* Only write dates when they've changed. */
3095 #if IMG_API_VERSION == 0
3096 if (pimg
->date1
== pimg
->olddate1
&& pimg
->date2
== pimg
->olddate2
)
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
)
3105 same
= (pimg
->days1
== pimg
->days2
);
3106 unset
= (pimg
->days1
== -1);
3111 PUTC(0x10, pimg
->fh
);
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
);
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
);
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
);
3139 PUTC(0x13, pimg
->fh
);
3140 put16(pimg
->days1
, pimg
->fh
);
3141 put16(pimg
->days2
, pimg
->fh
);
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
;
3155 img_write_item_date(img
*pimg
)
3158 /* Only write dates when they've changed. */
3159 #if IMG_API_VERSION == 0
3160 if (pimg
->date1
== pimg
->olddate1
&& pimg
->date2
== pimg
->olddate2
)
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
)
3169 same
= (pimg
->days1
== pimg
->days2
);
3170 unset
= (pimg
->days1
== -1);
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
);
3183 PUTC(0x24, pimg
->fh
);
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
);
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
);
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
);
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
);
3222 PUTC(0x23, pimg
->fh
);
3223 put16(pimg
->days1
, pimg
->fh
);
3224 put16(pimg
->days2
, pimg
->fh
);
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
;
3239 img_write_item_new(img
*pimg
, int code
, int flags
, const char *s
,
3240 double x
, double y
, double z
);
3242 img_write_item_v3to7(img
*pimg
, int code
, int flags
, const char *s
,
3243 double x
, double y
, double z
);
3245 img_write_item_ancient(img
*pimg
, int code
, int flags
, const char *s
,
3246 double x
, double y
, double z
);
3249 img_write_item(img
*pimg
, int code
, int flags
, const char *s
,
3250 double x
, double y
, double z
)
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
);
3258 img_write_item_ancient(pimg
, code
, flags
, s
, x
, y
, z
);
3263 img_write_item_new(img
*pimg
, int code
, int flags
, const char *s
,
3264 double x
, double y
, double z
)
3268 write_v8label(pimg
, 0x80 | flags
, 0, -1, s
);
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);
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
);
3286 /* Big passage! Need to use 4 bytes. */
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
);
3314 pimg
->oldstyle
= pimg
->style
;
3316 write_v8label(pimg
, 0x40 | flags
, 0x20, 0x00, s
? s
: "");
3318 default: /* ignore for now */
3321 write_coord(pimg
->fh
, x
, y
, z
);
3325 img_write_item_v3to7(img
*pimg
, int code
, int flags
, const char *s
,
3326 double x
, double y
, double z
)
3330 write_v3label(pimg
, 0x40 | flags
, s
);
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);
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
);
3350 /* Big passage! Need to use 4 bytes. */
3367 if (pimg
->version
>= 4) {
3368 img_write_item_date(pimg
);
3370 write_v3label(pimg
, 0x80 | flags
, s
? s
: "");
3372 default: /* ignore for now */
3375 write_coord(pimg
->fh
, x
, y
, z
);
3379 img_write_item_ancient(img
*pimg
, int code
, int flags
, const char *s
,
3380 double x
, double y
, double z
)
3384 SVX_ASSERT(pimg
->version
> 0);
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
);
3391 fputsnl(s
, pimg
->fh
);
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... */
3400 PUTC(flags
, pimg
->fh
);
3401 put32(len
, pimg
->fh
);
3404 PUTC(0x40 | (flags
& 0x3f), pimg
->fh
);
3405 fputsnl(s
, pimg
->fh
);
3413 if (pimg
->version
> 1) {
3414 opt
= 0x80 | (flags
& 0x3f);
3419 default: /* ignore for now */
3422 if (pimg
->version
== 1) {
3423 put32(opt
, pimg
->fh
);
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
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
)
3456 osfree(pimg
->survey
);
3457 osfree(pimg
->title
);
3459 osfree(pimg
->datestamp
);
3461 /* write end of data marker */
3462 switch (pimg
->version
) {
3464 put32((INT32_T
)-1, pimg
->fh
);
3467 if (pimg
->version
<= 7 ?
3468 (pimg
->label_len
!= 0) :
3469 (pimg
->style
!= img_STYLE_NORMAL
)) {
3478 if (ferror(pimg
->fh
)) result
= 0;
3479 if (pimg
->close_func
&& pimg
->close_func(pimg
->fh
))
3481 if (!result
) img_errno
= pimg
->fRead
? IMG_READERROR
: IMG_WRITEERROR
;
3484 switch (pimg
->version
) {
3485 case VERSION_COMPASS_PLT
:
3486 compass_plt_free_data(pimg
);
3492 osfree(pimg
->label_buf
);
3493 osfree(pimg
->filename_opened
);
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. */
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
;
3512 return img_DATUM_ADINDAN
;
3514 return img_DATUM_ARC1950
;
3516 return img_DATUM_ARC1960
;
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
;
3526 return img_DATUM_INDIAN1960
;
3528 return img_DATUM_TOKYO
;
3530 return img_DATUM_WGS72
;
3532 return img_DATUM_UNKNOWN
;
3536 img_compass_utm_proj_str(img_datum datum
, int utm_zone
)
3539 const char* proj4_datum
= NULL
;
3541 if (utm_zone
< -60 || utm_zone
> 60 || utm_zone
== 0)
3545 case img_DATUM_UNKNOWN
:
3547 case img_DATUM_ADINDAN
:
3548 if (utm_zone
>= 35 && utm_zone
<= 38)
3549 epsg_code
= 20100 + utm_zone
;
3551 case img_DATUM_ARC1950
:
3552 if (utm_zone
>= -36 && utm_zone
<= -34)
3553 epsg_code
= 20900 - utm_zone
;
3555 case img_DATUM_ARC1960
:
3556 if (utm_zone
>= -37 && utm_zone
<= -35)
3557 epsg_code
= 21000 - utm_zone
;
3559 case img_DATUM_CAPE
:
3560 if (utm_zone
>= -36 && utm_zone
<= -34)
3561 epsg_code
= 22200 - utm_zone
;
3563 case img_DATUM_EUROPEAN1950
:
3564 if (utm_zone
>= 28 && utm_zone
<= 38)
3565 epsg_code
= 23000 + utm_zone
;
3567 case img_DATUM_NZGD49
:
3569 epsg_code
= 27200 + utm_zone
;
3571 case img_DATUM_HUTZUSHAN1950
:
3575 case img_DATUM_INDIAN1960
:
3576 if (utm_zone
>= 48 && utm_zone
<= 49)
3577 epsg_code
= 3100 + utm_zone
;
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
;
3585 proj4_datum
= "NAD27";
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)
3592 else if (utm_zone
>= 59)
3593 epsg_code
= 3313 + utm_zone
;
3595 proj4_datum
= "NAD83";
3597 case img_DATUM_TOKYO
:
3598 if (utm_zone
>= 51 && utm_zone
<= 55)
3599 epsg_code
= 3041 + utm_zone
;
3601 case img_DATUM_WGS72
:
3603 epsg_code
= 32200 + utm_zone
;
3605 epsg_code
= 32300 - utm_zone
;
3607 case img_DATUM_WGS84
:
3609 epsg_code
= 32600 + utm_zone
;
3611 epsg_code
= 32700 - utm_zone
;
3616 char *proj_str
= xosmalloc(11);
3618 img_errno
= IMG_OUTOFMEMORY
;
3621 SNPRINTF(proj_str
, 11, "EPSG:%d", epsg_code
);
3627 size_t len
= strlen(proj4_datum
) + 52 + 2 + 1;
3628 const char *south
= "";
3630 utm_zone
= -utm_zone
;
3634 proj_str
= xosmalloc(len
);
3636 img_errno
= IMG_OUTOFMEMORY
;
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
);