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
39 # define INT32_T int32_t
40 # define UINT32_T uint32_t
42 # include "filelist.h"
43 # include "filename.h"
46 # define TIMEFMT msg(/*%a,%Y.%m.%d %H:%M:%S %Z*/107)
48 # if defined HAVE_STDINT_H || \
49 (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) || \
50 (defined __cplusplus && __cplusplus >= 201103L)
52 # define INT32_T int32_t
53 # define UINT32_T uint32_t
56 # if INT_MAX >= 2147483647
58 # define UINT32_T unsigned
61 # define UINT32_T unsigned long
64 # define TIMEFMT "%a,%Y.%m.%d %H:%M:%S %Z"
65 # define EXT_SVX_3D "3d"
66 # define EXT_SVX_POS "pos"
67 # define FNM_SEP_EXT '.'
68 # define METRES_PER_FOOT 0.3048 /* exact value */
69 # define xosmalloc(L) malloc((L))
70 # define xosrealloc(L,S) realloc((L),(S))
71 # define osfree(P) free((P))
72 # define osnew(T) (T*)malloc(sizeof(T))
74 /* in IMG_HOSTED mode, this tests if a filename refers to a directory */
75 # define fDirectory(X) 0
76 /* open file FNM with mode MODE, maybe using path PTH and/or extension EXT */
77 /* path isn't used in img.c, but EXT is */
78 # define fopenWithPthAndExt(PTH,FNM,EXT,MODE,X) \
79 ((*(X) = NULL), fopen(FNM,MODE))
81 # define PUTC(C, FH) putc(C, FH)
84 # define GETC(FH) getc(FH)
86 # define fputsnl(S, FH) (fputs((S), (FH)) == EOF ? EOF : putc('\n', (FH)))
87 # define SVX_ASSERT(X)
94 /* Return max/min of two numbers. */
95 /* May be defined already (e.g. by Borland C in stdlib.h) */
96 /* NB Bad news if X or Y has side-effects... */
98 # define max(X, Y) ((X) > (Y) ? (X) : (Y))
101 # define min(X, Y) ((X) < (Y) ? (X) : (Y))
108 UINT32_T w
= GETC(fh
);
109 w
|= (UINT32_T
)GETC(fh
) << 8l;
110 w
|= (UINT32_T
)GETC(fh
) << 16l;
111 w
|= (UINT32_T
)GETC(fh
) << 24l;
116 put32(UINT32_T w
, FILE *fh
)
119 PUTC((char)(w
>> 8l), fh
);
120 PUTC((char)(w
>> 16l), fh
);
121 PUTC((char)(w
>> 24l), fh
);
127 UINT32_T w
= GETC(fh
);
128 w
|= (UINT32_T
)GETC(fh
) << 8l;
133 put16(short word
, FILE *fh
)
135 unsigned short w
= (unsigned short)word
;
137 PUTC((char)(w
>> 8l), fh
);
141 baseleaf_from_fnm(const char *fnm
)
151 q
= strrchr(p
, '\\');
154 q
= strrchr(p
, FNM_SEP_EXT
);
155 if (q
) len
= (const char *)q
- p
; else len
= strlen(p
);
157 res
= (char *)xosmalloc(len
+ 1);
158 if (!res
) return NULL
;
165 static char * my_strdup(const char *str
);
168 mktime_with_tz(struct tm
* tm
, const char * tz
)
171 char * old_tz
= getenv("TZ");
174 old_tz
= my_strdup(old_tz
);
178 if (_putenv_s("TZ", tz
) != 0) {
182 #elif defined HAVE_SETENV
184 old_tz
= my_strdup(old_tz
);
188 if (setenv("TZ", tz
, 1) < 0) {
195 size_t len
= strlen(old_tz
) + 1;
196 p
= (char *)xosmalloc(len
+ 3);
200 memcpy(p
+ 3, tz
, len
);
203 p
= (char *)xosmalloc(strlen(tz
) + 4);
210 if (putenv(p
) != 0) {
215 #define CLEANUP() osfree(p)
221 _putenv_s("TZ", old_tz
);
222 #elif !defined HAVE_SETENV
225 setenv("TZ", old_tz
, 1);
231 #elif !defined HAVE_UNSETENV
244 static unsigned short
247 return (unsigned short)get16(fh
);
252 #if !defined HAVE_LROUND && !defined HAVE_DECL_LROUND
253 /* The autoconf tests are not in use, but C99 and C++11 both added lround(),
254 * so set HAVE_LROUND and HAVE_DECL_LROUND conservatively based on the language
255 * standard version the compiler claims to support. */
256 # if (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) || \
257 (defined __cplusplus && __cplusplus >= 201103L)
258 # define HAVE_LROUND 1
259 # define HAVE_DECL_LROUND 1
264 # if defined HAVE_DECL_LROUND && !HAVE_DECL_LROUND
265 /* On older systems, the prototype may be missing. */
266 extern long lround(double);
268 # define my_lround lround
271 my_lround(double x
) {
272 return (x
>= 0.0) ? (long)(x
+ 0.5) : -(long)(0.5 - x
);
276 /* portable case insensitive string compare */
277 #if defined(strcasecmp) || defined(HAVE_STRCASECMP)
278 # define my_strcasecmp strcasecmp
280 static int my_strcasecmp(const char *s1
, const char *s2
) {
281 unsigned char c1
, c2
;
285 } while (c1
&& toupper(c1
) == toupper(c2
));
286 /* now calculate real difference */
291 unsigned int img_output_version
= IMG_VERSION_MAX
;
293 static img_errcode img_errno
= IMG_NONE
;
295 #define FILEID "Survex 3D Image File"
297 #define EXT_PLT "plt"
298 #define EXT_PLF "plf"
300 /* Attempt to string paste to ensure we are passed a literal string */
301 #define LITLEN(S) (sizeof(S"") - 1)
303 /* Fake "version numbers" for non-3d formats we can read. */
304 #define VERSION_CMAP_SHOT -4
305 #define VERSION_CMAP_STATION -3
306 #define VERSION_COMPASS_PLT -2
307 #define VERSION_SURVEX_POS -1
309 /* Flags bitwise-or-ed into pending to track XSECTs. */
310 #define PENDING_XSECT_END 0x100
311 #define PENDING_HAD_XSECT 0x001 /* Only for VERSION_COMPASS_PLT */
312 #define PENDING_MOVE 0x002 /* Only for VERSION_COMPASS_PLT */
313 #define PENDING_LINE 0x004 /* Only for VERSION_COMPASS_PLT */
314 #define PENDING_XSECT 0x008 /* Only for VERSION_COMPASS_PLT */
315 #define PENDING_FLAGS_SHIFT 9 /* Only for VERSION_COMPASS_PLT */
317 /* Days from start of 1900 to start of 1970. */
318 #define DAYS_1900 25567
320 /* Start of 1900 in time_t with standard Unix epoch of start of 1970. */
321 #define TIME_T_1900 -2208988800L
323 /* Seconds in a day. */
324 #define SECS_PER_DAY 86400L
327 hash_data(const char *s
, unsigned len
)
329 /* djb2 hash but with an initial value of zero. */
332 unsigned char c
= (unsigned char)*s
++;
333 h
= ((h
<< 5) + h
) + c
;
339 struct compass_station
{
340 struct compass_station
*next
;
346 /* On the first pass, at the start of each survey we run through all the
347 * hash table entries that exist and set this flag.
349 * If this flag is set when we add flags to an existing station we
350 * know it appears in multiple surveys and can infer img_SFLAG_EXPORTED.
352 #define COMPASS_SFLAG_DIFFERENT_SURVEY 0x80
354 #define COMPASS_SFLAG_MASK 0x7f
356 /* How many hash buckets to use (must be a power of 2).
358 * Each bucket is a linked list so this doesn't limit how many entries we can
359 * store, but should be sized based on a plausible estimate of how many
360 * different stations we're likely to see in a single PLT file.
362 #define HASH_BUCKETS 0x2000U
365 compass_plt_allocate_hash(void)
367 struct compass_station_name
** htab
= xosmalloc(HASH_BUCKETS
* sizeof(struct compass_station_name
*));
370 for (i
= 0; i
< HASH_BUCKETS
; ++i
)
377 compass_plt_update_station(img
*pimg
, const char *name
, int name_len
,
380 struct compass_station
*p
;
381 struct compass_station
**htab
= (struct compass_station
**)pimg
->data
;
382 htab
+= hash_data(name
, name_len
) & (HASH_BUCKETS
- 1U);
383 for (p
= *htab
; p
; p
= p
->next
) {
384 if (p
->len
== name_len
) {
385 if (memcmp(name
, p
->name
, name_len
) == 0) {
387 if (p
->flags
& COMPASS_SFLAG_DIFFERENT_SURVEY
)
388 p
->flags
|= img_SFLAG_EXPORTED
;
393 p
= malloc(offsetof(struct compass_station
, name
) + name_len
);
397 memcpy(p
->name
, name
, name_len
);
404 compass_plt_new_survey(img
*pimg
)
406 struct compass_station
**htab
= (struct compass_station
**)pimg
->data
;
407 int i
= HASH_BUCKETS
;
409 struct compass_station
*p
;
410 for (p
= *htab
; p
; p
= p
->next
) {
411 p
->flags
|= COMPASS_SFLAG_DIFFERENT_SURVEY
;
418 compass_plt_free_data(img
*pimg
)
420 struct compass_station
**htab
= (struct compass_station
**)pimg
->data
;
421 int i
= HASH_BUCKETS
;
423 struct compass_station
*p
= *htab
;
425 struct compass_station
*next
= p
->next
;
436 compass_plt_get_station_flags(img
*pimg
, const char *name
, int name_len
)
438 struct compass_station
*p
;
439 struct compass_station
**htab
= (struct compass_station
**)pimg
->data
;
440 htab
+= hash_data(name
, name_len
) & (HASH_BUCKETS
- 1U);
441 for (p
= *htab
; p
; p
= p
->next
) {
442 if (p
->len
== name_len
) {
443 if (memcmp(name
, p
->name
, name_len
) == 0) {
444 if (p
->flags
& COMPASS_SFLAG_DIFFERENT_SURVEY
) {
445 p
->flags
&= ~COMPASS_SFLAG_DIFFERENT_SURVEY
;
448 return p
->flags
| INT_MIN
;
456 my_strdup(const char *str
)
459 size_t len
= strlen(str
) + 1;
460 p
= (char *)xosmalloc(len
);
461 if (p
) memcpy(p
, str
, len
);
465 #define getline_alloc(FH) getline_alloc_len(FH, NULL)
468 getline_alloc_len(FILE *fh
, size_t * p_len
)
473 char *buf
= (char *)xosmalloc(len
);
474 if (!buf
) return NULL
;
477 while (ch
!= '\n' && ch
!= '\r' && ch
!= EOF
) {
482 p
= (char *)xosrealloc(buf
, len
);
491 if (ch
== '\n' || ch
== '\r') {
492 int otherone
= ch
^ ('\n' ^ '\r');
494 /* if it's not the other eol character, put it back */
495 if (ch
!= otherone
) ungetc(ch
, fh
);
498 if (p_len
) *p_len
= i
;
509 check_label_space(img
*pimg
, size_t len
)
511 if (len
> pimg
->buf_len
) {
512 char *b
= (char *)xosrealloc(pimg
->label_buf
, len
);
514 pimg
->label
= (pimg
->label
- pimg
->label_buf
) + b
;
521 /* Check if a station name should be included. */
523 stn_included(img
*pimg
)
525 if (!pimg
->survey_len
) return 1;
526 size_t l
= pimg
->survey_len
;
527 const char *s
= pimg
->label_buf
;
528 if (strncmp(pimg
->survey
, s
, l
+ 1) != 0) {
531 pimg
->label
+= l
+ 1;
535 /* Check if a survey name should be included. */
537 survey_included(img
*pimg
)
539 if (!pimg
->survey_len
) return 1;
540 size_t l
= pimg
->survey_len
;
541 const char *s
= pimg
->label_buf
;
542 if (strncmp(pimg
->survey
, s
, l
) != 0 ||
543 !(s
[l
] == '.' || s
[l
] == '\0')) {
547 /* skip the dot if there */
548 if (*pimg
->label
) pimg
->label
++;
552 /* Check if a survey name in a buffer should be included.
554 * For "foreign" formats which just have one level of surveys.
557 buf_included(img
*pimg
, const char *buf
, size_t len
)
559 return pimg
->survey_len
== len
&& memcmp(buf
, pimg
->survey
, len
) == 0;
562 #define has_ext(F,L,E) ((L) > LITLEN(E) + 1 &&\
563 (F)[(L) - LITLEN(E) - 1] == FNM_SEP_EXT &&\
564 my_strcasecmp((F) + (L) - LITLEN(E), E) == 0)
567 img_open_survey(const char *fnm
, const char *survey
)
571 char* filename_opened
= NULL
;
573 if (fDirectory(fnm
)) {
574 img_errno
= IMG_DIRECTORY
;
578 fh
= fopenWithPthAndExt("", fnm
, EXT_SVX_3D
, "rb", &filename_opened
);
579 pimg
= img_read_stream_survey(fh
, fclose
,
580 filename_opened
? filename_opened
: fnm
,
583 pimg
->filename_opened
= filename_opened
;
585 osfree(filename_opened
);
591 compass_plt_open(img
*pimg
)
594 int datum
= img_DATUM_UNKNOWN
;
599 pimg
->version
= VERSION_COMPASS_PLT
;
600 /* Spaces aren't legal in Compass station names, but dots are, so
601 * use space as the level separator */
602 pimg
->separator
= ' ';
604 pimg
->datestamp
= my_strdup(TIMENA
);
605 if (!pimg
->datestamp
) {
606 return IMG_OUTOFMEMORY
;
608 pimg
->data
= compass_plt_allocate_hash();
610 return IMG_OUTOFMEMORY
;
613 /* Read through the whole file first, recording any station flags
614 * (pimg->data), finding where to start reading data from (pimg->start),
615 * and deciding what to report for "title".
618 int ch
= GETC(pimg
->fh
);
621 fseek(pimg
->fh
, -1, SEEK_CUR
);
624 if (pimg
->start
< 0) {
625 pimg
->start
= ftell(pimg
->fh
);
627 fseek(pimg
->fh
, pimg
->start
, SEEK_SET
);
630 if (datum
&& utm_zone
&& abs(utm_zone
) <= 60) {
631 /* Map to an EPSG code where we can. */
632 const char* template = "EPSG:%d";
635 case img_DATUM_NAD27
:
637 template = "+proj=utm +zone=%d +datum=NAD27 +south +units=m +no_defs +type=crs";
639 } else if (utm_zone
<= 23) {
640 value
= 26700 + utm_zone
;
641 } else if (utm_zone
< 59) {
642 template = "+proj=utm +zone=%d +datum=NAD27 +units=m +no_defs +type=crs";
645 value
= 3311 + utm_zone
;
648 case img_DATUM_NAD83
:
650 template = "+proj=utm +zone=%d +datum=NAD83 +south +units=m +no_defs +type=crs";
652 } else if (utm_zone
<= 23) {
653 value
= 26900 + utm_zone
;
654 } else if (utm_zone
== 24) {
656 } else if (utm_zone
< 59) {
657 template = "+proj=utm +zone=%d +datum=NAD83 +units=m +no_defs +type=crs";
660 value
= 3313 + utm_zone
;
663 case img_DATUM_WGS84
:
665 value
= 32600 + utm_zone
;
667 value
= 32700 - utm_zone
;
672 pimg
->cs
= (char*)xosmalloc(strlen(template) + 4);
674 goto out_of_memory_error
;
676 sprintf(pimg
->cs
, template, value
);
683 /* "Section" - in the case where we aren't filtering by survey
684 * (i.e. pimg->survey == NULL): if there's only one non-empty
685 * section name specified, we use it as the title.
687 if (pimg
->survey
== NULL
&& (!pimg
->title
|| pimg
->title
[0])) {
688 char *line
= getline_alloc(pimg
->fh
);
690 goto out_of_memory_error
;
694 if (strcmp(pimg
->title
, line
) != 0) {
695 /* Two different non-empty section names found. */
696 pimg
->title
[0] = '\0';
711 compass_plt_new_survey(pimg
);
712 if (pimg
->start
>= 0) break;
713 fpos
= ftell(pimg
->fh
) - 1;
715 /* We're not filtering by survey so just note down the file
716 * offset for the first N command. */
720 line
= getline_alloc(pimg
->fh
);
722 goto out_of_memory_error
;
725 while (line
[len
] > 32) ++len
;
726 if (!buf_included(pimg
, line
, len
)) {
727 /* Not the survey we are looking for. */
731 q
= strchr(line
+ len
, 'C');
734 pimg
->title
= my_strdup(q
+ 1);
735 } else if (!pimg
->title
) {
736 pimg
->title
= my_strdup(pimg
->label
);
740 goto out_of_memory_error
;
751 unsigned station_flags
= 0;
753 int not_plotted
= (command
== 'd');
755 /* Find station name. */
756 do { ch
= GETC(pimg
->fh
); } while (ch
>= ' ' && ch
!= 'S');
759 /* Leave reporting error to second pass for consistency. */
763 name
= getline_alloc(pimg
->fh
);
765 goto out_of_memory_error
;
769 while (name
[name_len
] > ' ') ++name_len
;
770 if (name_len
> 255) {
771 /* The spec says "up to 12 characters", we allow up to 255. */
774 return IMG_BADFORMAT
;
777 /* Check for the "distance from entrance" field. */
778 q
= strchr(name
+ name_len
, 'I');
780 double distance_from_entrance
;
783 if (sscanf(q
, "%lf%n",
784 &distance_from_entrance
, &bytes_used
) == 1 &&
785 distance_from_entrance
== 0.0) {
786 /* Infer an entrance. */
787 station_flags
|= img_SFLAG_ENTRANCE
;
790 while (*q
&& *q
<= ' ') q
++;
792 q
= strchr(name
+ name_len
, 'F');
795 if (q
&& *q
== 'F') {
797 while (isalpha((unsigned char)*++q
)) {
800 /* The format specification says «The shot is a "splay"
801 * shot, which is a shot from a station to the wall to
802 * define the passage shape.» so we set the wall flag
803 * for the to station.
805 station_flags
|= img_SFLAG_WALL
;
814 /* Shot flag P (which is also implied by command d) is "Exclude
815 * this shot from plotting", but the use suggested in the Compass
816 * docs is for surface data, and they "[do] not support passage
819 * Even if it's actually being used for a different purpose,
820 * Survex programs don't show surface legs by default so the end
821 * effect is at least to not plot as intended.
823 if (command
!= 'M') {
824 int surface_or_not
= not_plotted
? img_SFLAG_SURFACE
825 : img_SFLAG_UNDERGROUND
;
826 station_flags
|= surface_or_not
;
827 if (compass_plt_update_station(pimg
, from
, from_len
,
828 surface_or_not
) < 0) {
829 goto out_of_memory_error
;
833 if (compass_plt_update_station(pimg
, name
, name_len
,
834 station_flags
) < 0) {
835 goto out_of_memory_error
;
844 char *line
, *q
, *name
;
847 line
= getline_alloc(pimg
->fh
);
849 goto out_of_memory_error
;
852 while (*q
&& *q
<= ' ') q
++;
855 while (name
[name_len
] > ' ') ++name_len
;
857 if (name_len
> 255) {
858 /* The spec says "up to 12 characters", we allow up to 255. */
861 return IMG_BADFORMAT
;
864 if (compass_plt_update_station(pimg
, name
, name_len
,
865 img_SFLAG_FIXED
) < 0) {
866 goto out_of_memory_error
;
873 /* UTM Zone - 1 to 60 for North, -1 to -60 for South. */
874 char *line
= getline_alloc(pimg
->fh
);
876 long v
= strtol(p
, &p
, 10);
877 if (v
< -60 || v
> 60 || v
== 0 || *p
> ' ') {
881 if (utm_zone
&& utm_zone
!= v
) {
882 /* More than one UTM zone specified. */
883 /* FIXME: We could handle this by reprojecting, but then we'd
884 * need access to PROJ from img.
896 char *line
= getline_alloc(pimg
->fh
);
898 goto out_of_memory_error
;
900 if (utm_zone
== 99) {
905 new_datum
= img_parse_compass_datum_string(line
, strlen(line
));
906 if (new_datum
== img_DATUM_UNKNOWN
) {
908 } else if (datum
== img_DATUM_UNKNOWN
) {
910 } else if (datum
!= new_datum
) {
918 while (ch
!= '\n' && ch
!= '\r') {
924 return IMG_OUTOFMEMORY
;
928 cmap_xyz_open(img
*pimg
)
931 char *line
= getline_alloc(pimg
->fh
);
933 return IMG_OUTOFMEMORY
;
936 /* Spaces aren't legal in CMAP station names, but dots are, so
937 * use space as the level separator. */
938 pimg
->separator
= ' ';
940 /* There doesn't seem to be a spec for what happens after 1999 with cmap
941 * files, so this code allows for:
942 * * 21xx -> xx (up to 2150)
943 * * 21xx -> 1xx (up to 2199)
944 * * full year being specified instead of 2 digits
948 /* Don't just truncate at column 59, allow for a > 2 digit year. */
949 char * p
= strstr(line
+ len
, "Page");
951 while (p
> line
&& p
[-1] == ' ')
964 pimg
->datestamp
= my_strdup(line
+ 45);
966 v
= strtoul(p
, &p
, 10);
968 /* In the absence of a spec for cmap files, assume <= 50 means 21st
971 } else if (v
< 200) {
972 /* Map 100-199 to 21st century. */
975 if (v
== ULONG_MAX
|| *p
++ != '/')
977 tm
.tm_year
= v
- 1900;
978 v
= strtoul(p
, &p
, 10);
979 if (v
< 1 || v
> 12 || *p
++ != '/')
982 v
= strtoul(p
, &p
, 10);
983 if (v
< 1 || v
> 31 || *p
++ != ' ')
986 v
= strtoul(p
, &p
, 10);
987 if (v
>= 24 || *p
++ != ':')
990 v
= strtoul(p
, &p
, 10);
995 v
= strtoul(p
+ 1, &p
, 10);
1003 /* We have no indication of what timezone this timestamp is in. It's
1004 * probably local time for whoever processed the data, so just assume
1005 * UTC, which is at least fairly central in the possibilities.
1007 pimg
->datestamp_numeric
= mktime_with_tz(&tm
, "");
1009 pimg
->datestamp
= my_strdup(TIMENA
);
1012 if (strncmp(line
, " Cave Survey Data Processed by CMAP ",
1013 LITLEN(" Cave Survey Data Processed by CMAP ")) != 0) {
1018 while (len
> 2 && line
[len
- 1] == ' ') --len
;
1021 pimg
->title
= my_strdup(line
+ 2);
1025 if (!pimg
->datestamp
|| !pimg
->title
) {
1026 return IMG_OUTOFMEMORY
;
1028 line
= getline_alloc(pimg
->fh
);
1030 return IMG_OUTOFMEMORY
;
1032 if (line
[0] != ' ' || (line
[1] != 'S' && line
[1] != 'O')) {
1033 return IMG_BADFORMAT
;
1035 if (line
[1] == 'S') {
1036 pimg
->version
= VERSION_CMAP_STATION
;
1038 pimg
->version
= VERSION_CMAP_SHOT
;
1041 line
= getline_alloc(pimg
->fh
);
1043 return IMG_OUTOFMEMORY
;
1045 if (line
[0] != ' ' || line
[1] != '-') {
1046 return IMG_BADFORMAT
;
1049 pimg
->start
= ftell(pimg
->fh
);
1054 img_read_stream_survey(FILE *stream
, int (*close_func
)(FILE*),
1060 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 if (has_ext(fnm
, len
, EXT_SVX_POS
)) {
1157 pimg
->version
= VERSION_SURVEX_POS
;
1158 pimg
->datestamp
= my_strdup(TIMENA
);
1159 if (!pimg
->datestamp
) {
1160 goto out_of_memory_error
;
1163 goto successful_return
;
1166 if (has_ext(fnm
, len
, EXT_PLT
) || has_ext(fnm
, len
, EXT_PLF
)) {
1169 result
= compass_plt_open(pimg
);
1174 goto successful_return
;
1177 /* Although these are often referred to as "CMAP .XYZ files", it seems
1178 * that actually, the extension .XYZ isn't used, rather .SHT (shot
1179 * variant, produced by CMAP v16 and later), .UNA (unadjusted) and
1180 * .ADJ (adjusted) extensions are. Since img has long checked for
1181 * .XYZ, we continue to do so in case anyone is relying on it.
1183 if (has_ext(fnm
, len
, "sht") ||
1184 has_ext(fnm
, len
, "adj") ||
1185 has_ext(fnm
, len
, "una") ||
1186 has_ext(fnm
, len
, "xyz")) {
1189 result
= cmap_xyz_open(pimg
);
1194 goto successful_return
;
1197 if (fread(buf
, LITLEN(FILEID
) + 1, 1, pimg
->fh
) != 1 ||
1198 memcmp(buf
, FILEID
"\n", LITLEN(FILEID
) + 1) != 0) {
1199 if (fread(buf
+ LITLEN(FILEID
) + 1, 8, 1, pimg
->fh
) == 1 &&
1200 memcmp(buf
, FILEID
"\r\nv0.01\r\n", LITLEN(FILEID
) + 9) == 0) {
1201 /* v0 3d file with DOS EOLs */
1206 if (buf
[1] == ' ') {
1207 if (buf
[0] == ' ') {
1208 /* Looks like a CMAP .xyz file ... */
1210 } else if (strchr("ZSNF", buf
[0])) {
1211 /* Looks like a Compass .plt file ... */
1212 /* Almost certainly it'll start "Z " */
1216 if (buf
[0] == '(') {
1217 /* Looks like a Survex .pos file ... */
1220 img_errno
= IMG_BADFORMAT
;
1224 /* check file format version */
1225 ch
= GETC(pimg
->fh
);
1227 if (tolower(ch
) == 'b') {
1228 /* binary file iff B/b prefix */
1230 ch
= GETC(pimg
->fh
);
1233 img_errno
= IMG_BADFORMAT
;
1236 ch
= GETC(pimg
->fh
);
1238 if (fread(buf
, 4, 1, pimg
->fh
) != 1 || memcmp(buf
, ".01\n", 4) != 0) {
1239 img_errno
= IMG_BADFORMAT
;
1242 /* nothing special to do */
1243 } else if (pimg
->version
== 0) {
1244 if (ch
< '2' || ch
> '0' + IMG_VERSION_MAX
|| GETC(pimg
->fh
) != '\n') {
1245 img_errno
= IMG_TOONEW
;
1248 pimg
->version
= ch
- '0';
1250 img_errno
= IMG_BADFORMAT
;
1257 char * title
= getline_alloc_len(pimg
->fh
, &title_len
);
1258 if (!title
) goto out_of_memory_error
;
1259 if (pimg
->version
== 8) {
1260 /* We sneak in an extra field after a zero byte here, containing the
1261 * specified coordinate system (if any). Older readers will just
1262 * not see it (which is fine), and this trick avoids us having to
1263 * bump the 3d format version.
1265 size_t real_len
= strlen(title
);
1266 if (real_len
!= title_len
) {
1267 char * cs
= title
+ real_len
+ 1;
1268 if (memcmp(cs
, "+init=", 6) == 0) {
1269 /* PROJ 5 and later don't handle +init=esri:<number> but
1270 * that's what cavern used to put in .3d files for
1271 * coordinate systems specified using ESRI codes. We parse
1272 * and convert the strings cavern used to generate and
1273 * convert to the form ESRI:<number> which is still
1276 * PROJ 6 and later don't recognise +init=epsg:<number>
1277 * by default and don't apply datum shift terms in some
1278 * cases, so we also convert these to the form
1282 if (p
[4] == ':' && isdigit((unsigned char)p
[5]) &&
1283 ((memcmp(p
, "epsg", 4) == 0 || memcmp(p
, "esri", 4) == 0))) {
1285 while (isdigit((unsigned char)*p
)) {
1288 /* Allow +no_defs to be omitted as it seems to not
1289 * actually do anything with recent PROJ - cavern always
1290 * included it, but other software generating 3d files
1293 if (*p
== '\0' || strcmp(p
, " +no_defs") == 0) {
1296 for (i
= 0; i
< 4; ++i
) {
1297 cs
[i
] = toupper(cs
[i
]);
1302 } else if (memcmp(cs
, "+proj=", 6) == 0) {
1303 /* Convert S_MERC and UTM proj strings which cavern used
1304 * to generate to their corresponding EPSG:<number> codes.
1307 if (memcmp(p
, "utm +ellps=WGS84 +datum=WGS84 +units=m +zone=", 45) == 0) {
1310 while (isdigit((unsigned char)*p
)) {
1311 n
= n
* 10 + (*p
- '0');
1314 if (memcmp(p
, " +south", 7) == 0) {
1320 /* Allow +no_defs to be omitted as it seems to not
1321 * actually do anything with recent PROJ - cavern always
1322 * included it, but other software generating 3d files
1325 if (*p
== '\0' || strcmp(p
, " +no_defs") == 0) {
1326 sprintf(cs
, "EPSG:%d", n
);
1328 } else if (memcmp(p
, "merc +lat_ts=0 +lon_0=0 +k=1 +x_0=0 +y_0=0 +a=6378137 +b=6378137 +units=m +nadgrids=@null", 89) == 0) {
1330 /* Allow +no_defs to be omitted as it seems to not
1331 * actually do anything with recent PROJ - cavern always
1332 * included it, but other software generating 3d files
1335 if (*p
== '\0' || strcmp(p
, " +no_defs") == 0) {
1336 strcpy(cs
, "EPSG:3857");
1340 pimg
->cs
= my_strdup(cs
);
1344 pimg
->title
= title
;
1349 pimg
->datestamp
= getline_alloc(pimg
->fh
);
1350 if (!pimg
->datestamp
) {
1351 out_of_memory_error
:
1352 img_errno
= IMG_OUTOFMEMORY
;
1354 osfree(pimg
->title
);
1356 osfree(pimg
->datestamp
);
1357 osfree(pimg
->filename_opened
);
1358 if (pimg
->close_func
) pimg
->close_func(pimg
->fh
);
1363 if (pimg
->version
>= 8) {
1364 int flags
= GETC(pimg
->fh
);
1365 if (flags
& img_FFLAG_EXTENDED
) pimg
->is_extended_elevation
= 1;
1366 } else if (pimg
->title
) {
1367 len
= strlen(pimg
->title
);
1368 if (len
> 11 && strcmp(pimg
->title
+ len
- 11, " (extended)") == 0) {
1369 pimg
->title
[len
- 11] = '\0';
1370 pimg
->is_extended_elevation
= 1;
1374 if (pimg
->datestamp
[0] == '@') {
1378 v
= strtoul(pimg
->datestamp
+ 1, &p
, 10);
1379 if (errno
== 0 && *p
== '\0')
1380 pimg
->datestamp_numeric
= v
;
1381 /* FIXME: We're assuming here that the C time_t epoch is 1970, which is
1382 * true for Unix-like systems, macOS and Windows, but isn't guaranteed
1386 /* %a,%Y.%m.%d %H:%M:%S %Z */
1389 char * p
= pimg
->datestamp
;
1390 while (isalpha((unsigned char)*p
)) ++p
;
1392 while (isspace((unsigned char)*p
)) ++p
;
1393 v
= strtoul(p
, &p
, 10);
1394 if (v
== ULONG_MAX
|| *p
++ != '.')
1396 tm
.tm_year
= v
- 1900;
1397 v
= strtoul(p
, &p
, 10);
1398 if (v
< 1 || v
> 12 || *p
++ != '.')
1401 v
= strtoul(p
, &p
, 10);
1402 if (v
< 1 || v
> 31 || *p
++ != ' ')
1405 v
= strtoul(p
, &p
, 10);
1406 if (v
>= 24 || *p
++ != ':')
1409 v
= strtoul(p
, &p
, 10);
1410 if (v
>= 60 || *p
++ != ':')
1413 v
= strtoul(p
, &p
, 10);
1418 while (isspace((unsigned char)*p
)) ++p
;
1419 /* p now points to the timezone string.
1421 * However, it's likely to be a string like "BST", and such strings can
1422 * be ambiguous (BST could be UTC+1 or UTC+6), so it is impossible to
1423 * reliably convert in all cases. Just pass what we have to tzset() - if
1424 * it doesn't handle it, UTC will be used.
1426 pimg
->datestamp_numeric
= mktime_with_tz(&tm
, p
);
1430 pimg
->start
= ftell(pimg
->fh
);
1433 /* If no title from another source, default to the base leafname. */
1434 if (!pimg
->title
|| !pimg
->title
[0]) {
1435 osfree(pimg
->title
);
1436 pimg
->title
= baseleaf_from_fnm(fnm
);
1442 img_rewind(img
*pimg
)
1445 img_errno
= IMG_WRITEERROR
;
1448 if (fseek(pimg
->fh
, pimg
->start
, SEEK_SET
) != 0) {
1449 img_errno
= IMG_READERROR
;
1453 /* [VERSION_SURVEX_POS] already skipped heading line, or there wasn't one
1454 * [version 0] not in the middle of a 'LINE' command
1455 * [version >= 3] not in the middle of turning a LINE into a MOVE */
1458 img_errno
= IMG_NONE
;
1460 /* for version >= 3 we use label_buf to store the prefix for reuse */
1461 /* for VERSION_COMPASS_PLT, 0 value indicates we haven't entered a survey
1463 /* for VERSION_CMAP_SHOT, we store the last station here to detect whether
1464 * we MOVE or LINE */
1465 pimg
->label_len
= 0;
1466 pimg
->style
= img_STYLE_UNKNOWN
;
1471 img_open_write_cs(const char *fnm
, const char *title
, const char *cs
, int flags
)
1473 if (fDirectory(fnm
)) {
1474 img_errno
= IMG_DIRECTORY
;
1478 return img_write_stream(fopen(fnm
, "wb"), fclose
, title
, cs
, flags
);
1482 img_write_stream(FILE *stream
, int (*close_func
)(FILE*),
1483 const char *title
, const char *cs
, int flags
)
1488 if (stream
== NULL
) {
1489 img_errno
= IMG_FILENOTFOUND
;
1495 img_errno
= IMG_OUTOFMEMORY
;
1496 if (close_func
) close_func(stream
);
1501 pimg
->close_func
= close_func
;
1502 pimg
->buf_len
= 257;
1503 pimg
->label_buf
= (char *)xosmalloc(pimg
->buf_len
);
1504 if (!pimg
->label_buf
) {
1505 if (pimg
->close_func
) pimg
->close_func(pimg
->fh
);
1507 img_errno
= IMG_OUTOFMEMORY
;
1511 pimg
->filename_opened
= NULL
;
1514 /* Output image file header */
1515 fputs("Survex 3D Image File\n", pimg
->fh
); /* file identifier string */
1516 if (img_output_version
< 2) {
1518 fputs("Bv0.01\n", pimg
->fh
); /* binary file format version number */
1520 pimg
->version
= (img_output_version
> IMG_VERSION_MAX
) ? IMG_VERSION_MAX
: img_output_version
;
1521 fprintf(pimg
->fh
, "v%d\n", pimg
->version
); /* file format version no. */
1524 fputs(title
, pimg
->fh
);
1525 if (pimg
->version
< 8 && (flags
& img_FFLAG_EXTENDED
)) {
1526 /* Older format versions append " (extended)" to the title to mark
1527 * extended elevations. */
1528 size_t len
= strlen(title
);
1529 if (len
< 11 || strcmp(title
+ len
- 11, " (extended)") != 0)
1530 fputs(" (extended)", pimg
->fh
);
1532 if (pimg
->version
== 8 && cs
&& *cs
) {
1533 /* We sneak in an extra field after a zero byte here, containing the
1534 * specified coordinate system (if any). Older readers will just not
1535 * see it (which is fine), and this trick avoids us having to bump the
1536 * 3d format version.
1538 PUTC('\0', pimg
->fh
);
1539 fputs(cs
, pimg
->fh
);
1541 PUTC('\n', pimg
->fh
);
1543 if (getenv("SOURCE_DATE_EPOCH")) {
1544 /* Support reproducible builds which create .3d files by not embedding a
1545 * timestamp if SOURCE_DATE_EPOCH is set. We don't bother trying to
1546 * parse the timestamp as it is simpler and seems cleaner to just not
1547 * embed a timestamp at all given the 3d file format already provides
1550 * See https://reproducible-builds.org/docs/source-date-epoch/
1557 if (tm
== (time_t)-1) {
1558 fputsnl(TIMENA
, pimg
->fh
);
1559 } else if (pimg
->version
<= 7) {
1561 /* output current date and time in format specified */
1562 strftime(date
, 256, TIMEFMT
, localtime(&tm
));
1563 fputsnl(date
, pimg
->fh
);
1565 fprintf(pimg
->fh
, "@%ld\n", (long)tm
);
1568 if (pimg
->version
>= 8) {
1569 /* Clear bit one in case anyone has been passing true for fBinary. */
1571 PUTC(flags
, pimg
->fh
);
1575 if (img_output_version
>= 5) {
1576 static const unsigned char codelengths
[32] = {
1577 4, 8, 8, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1578 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1580 fwrite(codelengths
, 32, 1, pimg
->fh
);
1583 pimg
->fRead
= 0; /* writing to this file */
1584 img_errno
= IMG_NONE
;
1586 /* for version >= 3 we use label_buf to store the prefix for reuse */
1587 pimg
->label_buf
[0] = '\0';
1588 pimg
->label_len
= 0;
1590 #if IMG_API_VERSION == 0
1591 pimg
->date1
= pimg
->date2
= 0;
1592 pimg
->olddate1
= pimg
->olddate2
= 0;
1593 #else /* IMG_API_VERSION == 1 */
1594 pimg
->days1
= pimg
->days2
= -1;
1595 pimg
->olddays1
= pimg
->olddays2
= -1;
1597 pimg
->style
= pimg
->oldstyle
= img_STYLE_UNKNOWN
;
1599 pimg
->l
= pimg
->r
= pimg
->u
= pimg
->d
= -1.0;
1603 pimg
->E
= pimg
->H
= pimg
->V
= 0.0;
1605 /* Don't check for write errors now - let img_close() report them... */
1610 read_xyz_station_coords(img_point
*pt
, const char *line
)
1613 memcpy(num
, line
+ 6, 9);
1615 pt
->x
= atof(num
) / METRES_PER_FOOT
;
1616 memcpy(num
, line
+ 15, 9);
1617 pt
->y
= atof(num
) / METRES_PER_FOOT
;
1618 memcpy(num
, line
+ 24, 8);
1620 pt
->z
= atof(num
) / METRES_PER_FOOT
;
1624 read_xyz_shot_coords(img_point
*pt
, const char *line
)
1627 memcpy(num
, line
+ 40, 10);
1629 pt
->x
= atof(num
) / METRES_PER_FOOT
;
1630 memcpy(num
, line
+ 50, 10);
1631 pt
->y
= atof(num
) / METRES_PER_FOOT
;
1632 memcpy(num
, line
+ 60, 9);
1634 pt
->z
= atof(num
) / METRES_PER_FOOT
;
1638 subtract_xyz_shot_deltas(img_point
*pt
, const char *line
)
1641 memcpy(num
, line
+ 15, 9);
1643 pt
->x
-= atof(num
) / METRES_PER_FOOT
;
1644 memcpy(num
, line
+ 24, 8);
1646 pt
->y
-= atof(num
) / METRES_PER_FOOT
;
1647 memcpy(num
, line
+ 32, 8);
1648 pt
->z
-= atof(num
) / METRES_PER_FOOT
;
1652 read_coord(FILE *fh
, img_point
*pt
)
1656 pt
->x
= get32(fh
) / 100.0;
1657 pt
->y
= get32(fh
) / 100.0;
1658 pt
->z
= get32(fh
) / 100.0;
1659 if (ferror(fh
) || feof(fh
)) {
1660 img_errno
= feof(fh
) ? IMG_BADFORMAT
: IMG_READERROR
;
1667 skip_coord(FILE *fh
)
1669 return (fseek(fh
, 12, SEEK_CUR
) == 0);
1673 read_v3label(img
*pimg
)
1676 long len
= GETC(pimg
->fh
);
1678 img_errno
= feof(pimg
->fh
) ? IMG_BADFORMAT
: IMG_READERROR
;
1682 len
+= get16(pimg
->fh
);
1683 if (feof(pimg
->fh
)) {
1684 img_errno
= IMG_BADFORMAT
;
1687 if (ferror(pimg
->fh
)) {
1688 img_errno
= IMG_READERROR
;
1691 } else if (len
== 0xff) {
1692 len
= get32(pimg
->fh
);
1693 if (ferror(pimg
->fh
)) {
1694 img_errno
= IMG_READERROR
;
1697 if (feof(pimg
->fh
) || len
< 0xfe + 0xffff) {
1698 img_errno
= IMG_BADFORMAT
;
1703 if (!check_label_space(pimg
, pimg
->label_len
+ len
+ 1)) {
1704 img_errno
= IMG_OUTOFMEMORY
;
1707 q
= pimg
->label_buf
+ pimg
->label_len
;
1708 pimg
->label_len
+= len
;
1709 if (len
&& fread(q
, len
, 1, pimg
->fh
) != 1) {
1710 img_errno
= feof(pimg
->fh
) ? IMG_BADFORMAT
: IMG_READERROR
;
1718 read_v8label(img
*pimg
, int common_flag
, size_t common_val
)
1723 if (common_val
== 0) return 0;
1724 add
= del
= common_val
;
1726 int ch
= GETC(pimg
->fh
);
1728 img_errno
= feof(pimg
->fh
) ? IMG_BADFORMAT
: IMG_READERROR
;
1735 ch
= GETC(pimg
->fh
);
1737 img_errno
= feof(pimg
->fh
) ? IMG_BADFORMAT
: IMG_READERROR
;
1743 del
= get32(pimg
->fh
);
1744 if (ferror(pimg
->fh
)) {
1745 img_errno
= IMG_READERROR
;
1749 ch
= GETC(pimg
->fh
);
1751 img_errno
= feof(pimg
->fh
) ? IMG_BADFORMAT
: IMG_READERROR
;
1757 add
= get32(pimg
->fh
);
1758 if (ferror(pimg
->fh
)) {
1759 img_errno
= IMG_READERROR
;
1765 if (add
> del
&& !check_label_space(pimg
, pimg
->label_len
+ add
- del
+ 1)) {
1766 img_errno
= IMG_OUTOFMEMORY
;
1770 if (del
> pimg
->label_len
) {
1771 img_errno
= IMG_BADFORMAT
;
1774 pimg
->label_len
-= del
;
1775 q
= pimg
->label_buf
+ pimg
->label_len
;
1776 pimg
->label_len
+= add
;
1777 if (add
&& fread(q
, add
, 1, pimg
->fh
) != 1) {
1778 img_errno
= feof(pimg
->fh
) ? IMG_BADFORMAT
: IMG_READERROR
;
1785 static int img_read_item_new(img
*pimg
, img_point
*p
);
1786 static int img_read_item_v3to7(img
*pimg
, img_point
*p
);
1787 static int img_read_item_ancient(img
*pimg
, img_point
*p
);
1788 static int img_read_item_ascii_wrapper(img
*pimg
, img_point
*p
);
1789 static int img_read_item_ascii(img
*pimg
, img_point
*p
);
1792 img_read_item(img
*pimg
, img_point
*p
)
1796 if (pimg
->version
>= 8) {
1797 return img_read_item_new(pimg
, p
);
1798 } else if (pimg
->version
>= 3) {
1799 return img_read_item_v3to7(pimg
, p
);
1800 } else if (pimg
->version
>= 1) {
1801 return img_read_item_ancient(pimg
, p
);
1803 return img_read_item_ascii_wrapper(pimg
, p
);
1808 img_read_item_new(img
*pimg
, img_point
*p
)
1812 pimg
->l
= pimg
->r
= pimg
->u
= pimg
->d
= -1.0;
1813 if (pimg
->pending
>= 0x40) {
1814 if (pimg
->pending
== PENDING_XSECT_END
) {
1816 return img_XSECT_END
;
1819 pimg
->flags
= (int)(pimg
->pending
) & 0x3f;
1823 again3
: /* label to goto if we get a prefix, date, or lrud */
1824 pimg
->label
= pimg
->label_buf
;
1825 opt
= GETC(pimg
->fh
);
1827 img_errno
= feof(pimg
->fh
) ? IMG_BADFORMAT
: IMG_READERROR
;
1830 if (opt
>> 6 == 0) {
1832 if (opt
== 0 && pimg
->style
== 0)
1833 return img_STOP
; /* end of data marker */
1840 case 0x10: { /* No date info */
1841 #if IMG_API_VERSION == 0
1842 pimg
->date1
= pimg
->date2
= 0;
1843 #else /* IMG_API_VERSION == 1 */
1844 pimg
->days1
= pimg
->days2
= -1;
1848 case 0x11: { /* Single date */
1849 int days1
= (int)getu16(pimg
->fh
);
1850 #if IMG_API_VERSION == 0
1851 pimg
->date2
= pimg
->date1
= (days1
- DAYS_1900
) * SECS_PER_DAY
;
1852 #else /* IMG_API_VERSION == 1 */
1853 pimg
->days2
= pimg
->days1
= days1
;
1857 case 0x12: { /* Date range (short) */
1858 int days1
= (int)getu16(pimg
->fh
);
1859 int days2
= days1
+ GETC(pimg
->fh
) + 1;
1860 #if IMG_API_VERSION == 0
1861 pimg
->date1
= (days1
- DAYS_1900
) * SECS_PER_DAY
;
1862 pimg
->date2
= (days2
- DAYS_1900
) * SECS_PER_DAY
;
1863 #else /* IMG_API_VERSION == 1 */
1864 pimg
->days1
= days1
;
1865 pimg
->days2
= days2
;
1869 case 0x13: { /* Date range (long) */
1870 int days1
= (int)getu16(pimg
->fh
);
1871 int days2
= (int)getu16(pimg
->fh
);
1872 #if IMG_API_VERSION == 0
1873 pimg
->date1
= (days1
- DAYS_1900
) * SECS_PER_DAY
;
1874 pimg
->date2
= (days2
- DAYS_1900
) * SECS_PER_DAY
;
1875 #else /* IMG_API_VERSION == 1 */
1876 pimg
->days1
= days1
;
1877 pimg
->days2
= days2
;
1881 case 0x1f: /* Error info */
1882 pimg
->n_legs
= get32(pimg
->fh
);
1883 pimg
->length
= get32(pimg
->fh
) / 100.0;
1884 pimg
->E
= get32(pimg
->fh
) / 100.0;
1885 pimg
->H
= get32(pimg
->fh
) / 100.0;
1886 pimg
->V
= get32(pimg
->fh
) / 100.0;
1887 return img_ERROR_INFO
;
1888 case 0x30: case 0x31: /* LRUD */
1889 case 0x32: case 0x33: /* Big LRUD! */
1890 if (read_v8label(pimg
, 0, 0) == img_BAD
) return img_BAD
;
1891 pimg
->flags
= (int)opt
& 0x01;
1893 pimg
->l
= get16(pimg
->fh
) / 100.0;
1894 pimg
->r
= get16(pimg
->fh
) / 100.0;
1895 pimg
->u
= get16(pimg
->fh
) / 100.0;
1896 pimg
->d
= get16(pimg
->fh
) / 100.0;
1898 pimg
->l
= get32(pimg
->fh
) / 100.0;
1899 pimg
->r
= get32(pimg
->fh
) / 100.0;
1900 pimg
->u
= get32(pimg
->fh
) / 100.0;
1901 pimg
->d
= get32(pimg
->fh
) / 100.0;
1903 if (!stn_included(pimg
)) {
1904 return img_XSECT_END
;
1906 /* If this is the last cross-section in this passage, set
1907 * pending so we return img_XSECT_END next time. */
1908 if (pimg
->flags
& 0x01) {
1909 pimg
->pending
= PENDING_XSECT_END
;
1910 pimg
->flags
&= ~0x01;
1913 default: /* 0x25 - 0x2f and 0x34 - 0x3f are currently unallocated. */
1914 img_errno
= IMG_BADFORMAT
;
1920 /* 1-14 and 16-31 reserved */
1921 img_errno
= IMG_BADFORMAT
;
1925 } else if (opt
>= 0x80) {
1926 if (read_v8label(pimg
, 0, 0) == img_BAD
) return img_BAD
;
1930 if (!stn_included(pimg
)) {
1931 if (!skip_coord(pimg
->fh
)) return img_BAD
;
1936 pimg
->flags
= (int)opt
& 0x7f;
1937 } else if ((opt
>> 6) == 1) {
1938 if (read_v8label(pimg
, opt
& 0x20, 0) == img_BAD
) return img_BAD
;
1942 if (!survey_included(pimg
)) {
1943 if (!read_coord(pimg
->fh
, &(pimg
->mv
))) return img_BAD
;
1948 if (pimg
->pending
) {
1950 if (!read_coord(pimg
->fh
, &(pimg
->mv
))) return img_BAD
;
1951 pimg
->pending
= opt
;
1954 pimg
->flags
= (int)opt
& 0x1f;
1956 img_errno
= IMG_BADFORMAT
;
1959 if (!read_coord(pimg
->fh
, p
)) return img_BAD
;
1965 img_read_item_v3to7(img
*pimg
, img_point
*p
)
1969 pimg
->l
= pimg
->r
= pimg
->u
= pimg
->d
= -1.0;
1970 if (pimg
->pending
== PENDING_XSECT_END
) {
1972 return img_XSECT_END
;
1974 if (pimg
->pending
>= 0x80) {
1976 pimg
->flags
= (int)(pimg
->pending
) & 0x3f;
1980 again3
: /* label to goto if we get a prefix, date, or lrud */
1981 pimg
->label
= pimg
->label_buf
;
1982 opt
= GETC(pimg
->fh
);
1984 img_errno
= feof(pimg
->fh
) ? IMG_BADFORMAT
: IMG_READERROR
;
1990 if (!pimg
->label_len
) return img_STOP
; /* end of data marker */
1991 pimg
->label_len
= 0;
1995 /* 1-14 mean trim that many levels from current prefix */
1997 if (pimg
->label_len
<= 17) {
1998 /* zero prefix using "0" */
1999 img_errno
= IMG_BADFORMAT
;
2002 /* extra - 1 because label_len points to one past the end */
2003 c
= pimg
->label_len
- 17 - 1;
2004 while (pimg
->label_buf
[c
] != '.' || --opt
> 0) {
2006 /* zero prefix using "0" */
2007 img_errno
= IMG_BADFORMAT
;
2012 pimg
->label_len
= c
;
2021 case 0x20: /* Single date */
2022 if (pimg
->version
< 7) {
2023 int date1
= get32(pimg
->fh
);
2024 #if IMG_API_VERSION == 0
2025 pimg
->date2
= pimg
->date1
= date1
;
2026 #else /* IMG_API_VERSION == 1 */
2028 pimg
->days1
= (date1
- TIME_T_1900
) / SECS_PER_DAY
;
2029 pimg
->days2
= pimg
->days1
;
2031 pimg
->days2
= pimg
->days1
= -1;
2035 int days1
= (int)getu16(pimg
->fh
);
2036 #if IMG_API_VERSION == 0
2037 pimg
->date1
= (days1
- DAYS_1900
) * SECS_PER_DAY
;
2038 pimg
->date2
= pimg
->date1
;
2039 #else /* IMG_API_VERSION == 1 */
2040 pimg
->days2
= pimg
->days1
= days1
;
2044 case 0x21: /* Date range (short for v7+) */
2045 if (pimg
->version
< 7) {
2046 INT32_T date1
= get32(pimg
->fh
);
2047 INT32_T date2
= get32(pimg
->fh
);
2048 #if IMG_API_VERSION == 0
2049 pimg
->date1
= date1
;
2050 pimg
->date2
= date2
;
2051 #else /* IMG_API_VERSION == 1 */
2052 pimg
->days1
= (date1
- TIME_T_1900
) / SECS_PER_DAY
;
2053 pimg
->days2
= (date2
- TIME_T_1900
) / SECS_PER_DAY
;
2056 int days1
= (int)getu16(pimg
->fh
);
2057 int days2
= days1
+ GETC(pimg
->fh
) + 1;
2058 #if IMG_API_VERSION == 0
2059 pimg
->date1
= (days1
- DAYS_1900
) * SECS_PER_DAY
;
2060 pimg
->date2
= (days2
- DAYS_1900
) * SECS_PER_DAY
;
2061 #else /* IMG_API_VERSION == 1 */
2062 pimg
->days1
= days1
;
2063 pimg
->days2
= days2
;
2067 case 0x22: /* Error info */
2068 pimg
->n_legs
= get32(pimg
->fh
);
2069 pimg
->length
= get32(pimg
->fh
) / 100.0;
2070 pimg
->E
= get32(pimg
->fh
) / 100.0;
2071 pimg
->H
= get32(pimg
->fh
) / 100.0;
2072 pimg
->V
= get32(pimg
->fh
) / 100.0;
2073 if (feof(pimg
->fh
)) {
2074 img_errno
= IMG_BADFORMAT
;
2077 if (ferror(pimg
->fh
)) {
2078 img_errno
= IMG_READERROR
;
2081 return img_ERROR_INFO
;
2082 case 0x23: { /* v7+: Date range (long) */
2083 if (pimg
->version
< 7) {
2084 img_errno
= IMG_BADFORMAT
;
2087 int days1
= (int)getu16(pimg
->fh
);
2088 int days2
= (int)getu16(pimg
->fh
);
2089 if (feof(pimg
->fh
)) {
2090 img_errno
= IMG_BADFORMAT
;
2093 if (ferror(pimg
->fh
)) {
2094 img_errno
= IMG_READERROR
;
2097 #if IMG_API_VERSION == 0
2098 pimg
->date1
= (days1
- DAYS_1900
) * SECS_PER_DAY
;
2099 pimg
->date2
= (days2
- DAYS_1900
) * SECS_PER_DAY
;
2100 #else /* IMG_API_VERSION == 1 */
2101 pimg
->days1
= days1
;
2102 pimg
->days2
= days2
;
2106 case 0x24: { /* v7+: No date info */
2107 #if IMG_API_VERSION == 0
2108 pimg
->date1
= pimg
->date2
= 0;
2109 #else /* IMG_API_VERSION == 1 */
2110 pimg
->days1
= pimg
->days2
= -1;
2114 case 0x30: case 0x31: /* LRUD */
2115 case 0x32: case 0x33: /* Big LRUD! */
2116 if (read_v3label(pimg
) == img_BAD
) return img_BAD
;
2117 pimg
->flags
= (int)opt
& 0x01;
2119 pimg
->l
= get16(pimg
->fh
) / 100.0;
2120 pimg
->r
= get16(pimg
->fh
) / 100.0;
2121 pimg
->u
= get16(pimg
->fh
) / 100.0;
2122 pimg
->d
= get16(pimg
->fh
) / 100.0;
2124 pimg
->l
= get32(pimg
->fh
) / 100.0;
2125 pimg
->r
= get32(pimg
->fh
) / 100.0;
2126 pimg
->u
= get32(pimg
->fh
) / 100.0;
2127 pimg
->d
= get32(pimg
->fh
) / 100.0;
2129 if (feof(pimg
->fh
)) {
2130 img_errno
= IMG_BADFORMAT
;
2133 if (ferror(pimg
->fh
)) {
2134 img_errno
= IMG_READERROR
;
2137 if (!stn_included(pimg
)) {
2138 return img_XSECT_END
;
2140 /* If this is the last cross-section in this passage, set
2141 * pending so we return img_XSECT_END next time. */
2142 if (pimg
->flags
& 0x01) {
2143 pimg
->pending
= PENDING_XSECT_END
;
2144 pimg
->flags
&= ~0x01;
2147 default: /* 0x25 - 0x2f and 0x34 - 0x3f are currently unallocated. */
2148 img_errno
= IMG_BADFORMAT
;
2151 if (feof(pimg
->fh
)) {
2152 img_errno
= IMG_BADFORMAT
;
2155 if (ferror(pimg
->fh
)) {
2156 img_errno
= IMG_READERROR
;
2161 /* 16-31 mean remove (n - 15) characters from the prefix */
2162 /* zero prefix using 0 */
2163 if (pimg
->label_len
<= (size_t)(opt
- 15)) {
2164 img_errno
= IMG_BADFORMAT
;
2167 pimg
->label_len
-= (opt
- 15);
2170 if (read_v3label(pimg
) == img_BAD
) return img_BAD
;
2174 if (!stn_included(pimg
)) {
2175 if (!skip_coord(pimg
->fh
)) return img_BAD
;
2180 pimg
->flags
= (int)opt
& 0x3f;
2183 if (read_v3label(pimg
) == img_BAD
) return img_BAD
;
2187 if (!survey_included(pimg
)) {
2188 if (!read_coord(pimg
->fh
, &(pimg
->mv
))) return img_BAD
;
2193 if (pimg
->pending
) {
2195 if (!read_coord(pimg
->fh
, &(pimg
->mv
))) return img_BAD
;
2196 pimg
->pending
= opt
;
2199 pimg
->flags
= (int)opt
& 0x3f;
2202 img_errno
= IMG_BADFORMAT
;
2205 if (!read_coord(pimg
->fh
, p
)) return img_BAD
;
2211 img_read_item_ancient(img
*pimg
, img_point
*p
)
2214 static long opt_lookahead
= 0;
2215 static img_point pt
= { 0.0, 0.0, 0.0 };
2218 again
: /* label to goto if we get a cross */
2219 pimg
->label
= pimg
->label_buf
;
2220 pimg
->label
[0] = '\0';
2222 if (pimg
->version
== 1) {
2223 if (opt_lookahead
) {
2224 opt
= opt_lookahead
;
2227 opt
= get32(pimg
->fh
);
2230 opt
= GETC(pimg
->fh
);
2233 if (feof(pimg
->fh
)) {
2234 img_errno
= IMG_BADFORMAT
;
2237 if (ferror(pimg
->fh
)) {
2238 img_errno
= IMG_READERROR
;
2244 return img_STOP
; /* end of data marker */
2246 /* skip coordinates */
2247 if (!skip_coord(pimg
->fh
)) {
2248 img_errno
= feof(pimg
->fh
) ? IMG_BADFORMAT
: IMG_READERROR
;
2255 if (!fgets(pimg
->label_buf
, pimg
->buf_len
, pimg
->fh
)) {
2256 img_errno
= feof(pimg
->fh
) ? IMG_BADFORMAT
: IMG_READERROR
;
2259 if (pimg
->label
[0] == '\\') pimg
->label
++;
2260 len
= strlen(pimg
->label
);
2261 if (len
== 0 || pimg
->label
[len
- 1] != '\n') {
2262 img_errno
= IMG_BADFORMAT
;
2265 /* Ignore empty labels in some .3d files (caused by a bug) */
2266 if (len
== 1) goto again
;
2267 pimg
->label
[len
- 1] = '\0';
2268 pimg
->flags
= img_SFLAG_UNDERGROUND
; /* no flags given... */
2269 if (opt
== 2) goto done
;
2277 pimg
->flags
= GETC(pimg
->fh
);
2279 pimg
->flags
= img_SFLAG_UNDERGROUND
; /* no flags given... */
2281 len
= get32(pimg
->fh
);
2283 if (feof(pimg
->fh
)) {
2284 img_errno
= IMG_BADFORMAT
;
2287 if (ferror(pimg
->fh
)) {
2288 img_errno
= IMG_READERROR
;
2292 /* Ignore empty labels in some .3d files (caused by a bug) */
2293 if (len
== 0) goto again
;
2294 if (!check_label_space(pimg
, len
+ 1)) {
2295 img_errno
= IMG_OUTOFMEMORY
;
2298 if (fread(pimg
->label_buf
, len
, 1, pimg
->fh
) != 1) {
2299 img_errno
= feof(pimg
->fh
) ? IMG_BADFORMAT
: IMG_READERROR
;
2302 pimg
->label_buf
[len
] = '\0';
2312 switch ((int)opt
& 0xc0) {
2314 pimg
->flags
= (int)opt
& 0x3f;
2319 pimg
->flags
= (int)opt
& 0x3f;
2321 if (!fgets(pimg
->label_buf
, pimg
->buf_len
, pimg
->fh
)) {
2322 img_errno
= feof(pimg
->fh
) ? IMG_BADFORMAT
: IMG_READERROR
;
2325 q
= pimg
->label_buf
+ strlen(pimg
->label_buf
) - 1;
2326 /* Ignore empty-labels in some .3d files (caused by a bug) */
2327 if (q
== pimg
->label_buf
) goto again
;
2329 img_errno
= IMG_BADFORMAT
;
2336 img_errno
= IMG_BADFORMAT
;
2342 if (!read_coord(pimg
->fh
, &pt
)) return img_BAD
;
2344 if (result
== img_LABEL
&& !stn_included(pimg
)) {
2351 if (result
== img_MOVE
&& pimg
->version
== 1) {
2352 /* peek at next code and see if it's an old-style label */
2353 opt_lookahead
= get32(pimg
->fh
);
2355 if (feof(pimg
->fh
)) {
2356 img_errno
= IMG_BADFORMAT
;
2359 if (ferror(pimg
->fh
)) {
2360 img_errno
= IMG_READERROR
;
2364 if (opt_lookahead
== 2) return img_read_item_ancient(pimg
, p
);
2371 img_read_item_ascii_wrapper(img
*pimg
, img_point
*p
)
2373 /* We need to set the default locale for fscanf() to work on
2374 * numbers with "." as decimal point. */
2376 char * current_locale
= my_strdup(setlocale(LC_NUMERIC
, NULL
));
2377 setlocale(LC_NUMERIC
, "C");
2378 result
= img_read_item_ascii(pimg
, p
);
2379 setlocale(LC_NUMERIC
, current_locale
);
2380 free(current_locale
);
2384 /* Handle all ASCII formats. */
2386 img_read_item_ascii(img
*pimg
, img_point
*p
)
2389 pimg
->label
= pimg
->label_buf
;
2390 if (pimg
->version
== 0) {
2392 pimg
->label
[0] = '\0';
2393 if (feof(pimg
->fh
)) return img_STOP
;
2394 if (pimg
->pending
) {
2399 /* Stop if nothing found */
2400 if (fscanf(pimg
->fh
, "%6s", cmd
) < 1) return img_STOP
;
2401 if (strcmp(cmd
, "move") == 0)
2403 else if (strcmp(cmd
, "draw") == 0)
2405 else if (strcmp(cmd
, "line") == 0) {
2406 /* set flag to indicate to process second triplet as LINE */
2409 } else if (strcmp(cmd
, "cross") == 0) {
2410 if (fscanf(pimg
->fh
, "%lf%lf%lf", &p
->x
, &p
->y
, &p
->z
) < 3) {
2411 img_errno
= feof(pimg
->fh
) ? IMG_BADFORMAT
: IMG_READERROR
;
2415 } else if (strcmp(cmd
, "name") == 0) {
2417 int ch
= GETC(pimg
->fh
);
2418 if (ch
== ' ') ch
= GETC(pimg
->fh
);
2420 if (ch
== '\n' || ch
== EOF
) {
2421 img_errno
= ferror(pimg
->fh
) ? IMG_READERROR
: IMG_BADFORMAT
;
2424 if (off
== pimg
->buf_len
) {
2425 if (!check_label_space(pimg
, pimg
->buf_len
* 2)) {
2426 img_errno
= IMG_OUTOFMEMORY
;
2430 pimg
->label_buf
[off
++] = ch
;
2431 ch
= GETC(pimg
->fh
);
2433 pimg
->label_buf
[off
] = '\0';
2435 pimg
->label
= pimg
->label_buf
;
2436 if (pimg
->label
[0] == '\\') pimg
->label
++;
2438 pimg
->flags
= img_SFLAG_UNDERGROUND
; /* default flags */
2442 img_errno
= IMG_BADFORMAT
;
2443 return img_BAD
; /* unknown keyword */
2447 if (fscanf(pimg
->fh
, "%lf%lf%lf", &p
->x
, &p
->y
, &p
->z
) < 3) {
2448 img_errno
= ferror(pimg
->fh
) ? IMG_READERROR
: IMG_BADFORMAT
;
2452 if (result
== img_LABEL
&& !stn_included(pimg
)) {
2457 } else if (pimg
->version
== VERSION_SURVEX_POS
) {
2458 /* Survex .pos file */
2461 pimg
->flags
= img_SFLAG_UNDERGROUND
; /* default flags */
2463 while (fscanf(pimg
->fh
, "(%lf,%lf,%lf )", &p
->x
, &p
->y
, &p
->z
) != 3) {
2464 if (ferror(pimg
->fh
)) {
2465 img_errno
= IMG_READERROR
;
2468 if (feof(pimg
->fh
)) return img_STOP
;
2469 if (pimg
->pending
) {
2470 img_errno
= IMG_BADFORMAT
;
2474 /* ignore rest of line */
2476 ch
= GETC(pimg
->fh
);
2477 } while (ch
!= '\n' && ch
!= '\r' && ch
!= EOF
);
2480 pimg
->label_buf
[0] = '\0';
2482 ch
= GETC(pimg
->fh
);
2483 } while (ch
== ' ' || ch
== '\t');
2484 if (ch
== '\n' || ch
== EOF
) {
2485 /* If there's no label, set img_SFLAG_ANON. */
2486 pimg
->flags
|= img_SFLAG_ANON
;
2489 pimg
->label_buf
[0] = ch
;
2491 while (!feof(pimg
->fh
)) {
2492 if (!fgets(pimg
->label_buf
+ off
, pimg
->buf_len
- off
, pimg
->fh
)) {
2493 img_errno
= IMG_READERROR
;
2497 off
+= strlen(pimg
->label_buf
+ off
);
2498 if (off
&& pimg
->label_buf
[off
- 1] == '\n') {
2499 pimg
->label_buf
[off
- 1] = '\0';
2502 if (!check_label_space(pimg
, pimg
->buf_len
* 2)) {
2503 img_errno
= IMG_OUTOFMEMORY
;
2508 pimg
->label
= pimg
->label_buf
;
2510 if (pimg
->label
[0] == '\\') pimg
->label
++;
2512 if (!stn_included(pimg
)) goto againpos
;
2515 } else if (pimg
->version
== VERSION_COMPASS_PLT
) {
2516 /* Compass .plt file */
2517 if ((pimg
->pending
& ~PENDING_HAD_XSECT
) > 0) {
2518 /* -1 signals we've entered the first survey we want to read, and
2519 * need to fudge lots if the first action is 'D' or 'd'...
2522 if (pimg
->pending
& PENDING_XSECT_END
) {
2523 /* pending XSECT_END */
2524 pimg
->pending
&= ~PENDING_XSECT_END
;
2525 return img_XSECT_END
;
2527 if (pimg
->pending
& PENDING_XSECT
) {
2529 pimg
->pending
&= ~PENDING_XSECT
;
2532 pimg
->label
[pimg
->label_len
] = '\0';
2533 if (pimg
->pending
& PENDING_LINE
) {
2534 pimg
->flags
= (pimg
->pending
>> PENDING_FLAGS_SHIFT
);
2535 pimg
->pending
&= ((1 << PENDING_FLAGS_SHIFT
) - 1) & ~PENDING_LINE
;
2538 pimg
->pending
&= ~PENDING_MOVE
;
2546 int ch
= GETC(pimg
->fh
);
2549 case '\x1a': case EOF
: /* Don't insist on ^Z at end of file */
2550 if (pimg
->pending
== PENDING_HAD_XSECT
) {
2551 ungetc('\x1a', pimg
->fh
);
2553 return img_XSECT_END
;
2556 case 'X': case 'F': case 'S':
2557 /* bounding boX (marks end of survey), Feature survey, or
2558 * new Section - skip to next survey */
2559 if (pimg
->pending
== PENDING_HAD_XSECT
) {
2560 ungetc(ch
, pimg
->fh
);
2562 return img_XSECT_END
;
2564 if (pimg
->survey
) return img_STOP
;
2568 ch
= GETC(pimg
->fh
);
2569 } while (ch
!= '\n' && ch
!= '\r' && ch
!= EOF
);
2570 while (ch
== '\n' || ch
== '\r') ch
= GETC(pimg
->fh
);
2571 if (ch
== 'N') break;
2572 if (ch
== '\x1a' || ch
== EOF
) return img_STOP
;
2576 compass_plt_new_survey(pimg
);
2577 line
= getline_alloc(pimg
->fh
);
2579 img_errno
= IMG_OUTOFMEMORY
;
2582 while (line
[len
] > 32) ++len
;
2583 if (pimg
->label_len
== 0) pimg
->pending
= -1;
2584 if (!check_label_space(pimg
, len
+ 1)) {
2586 img_errno
= IMG_OUTOFMEMORY
;
2589 pimg
->label_len
= len
;
2590 pimg
->label
= pimg
->label_buf
;
2591 memcpy(pimg
->label
, line
, len
);
2592 pimg
->label
[len
] = '\0';
2593 /* Handle the survey date. */
2594 while (line
[len
] && line
[len
] <= 32) ++len
;
2595 if (line
[len
] == 'D') {
2597 memset(&tm
, 0, sizeof(tm
));
2600 /* NB Order is Month Day Year order. */
2601 v
= strtoul(q
, &q
, 10);
2602 if (v
< 1 || v
> 12)
2606 v
= strtoul(q
, &q
, 10);
2607 if (v
< 1 || v
> 31)
2611 v
= strtoul(q
, &q
, 10);
2615 /* "The Year is expected to be the full year like 1994 not
2616 * 94", but "expected to" != "must" so treat a two digit
2621 if (v
== 1901 && tm
.tm_mday
== 1 && tm
.tm_mon
== 0) {
2622 /* Compass uses 1/1/1 or 1/1/1901 for "date unknown". */
2625 tm
.tm_year
= v
- 1900;
2626 /* We have no indication of what timezone this date is
2627 * in. It's probably local time for whoever processed the
2628 * data, so just assume noon in UTC, which is at least fairly
2629 * central in the possibilities.
2633 time_t datestamp
= mktime_with_tz(&tm
, "");
2634 #if IMG_API_VERSION == 0
2635 pimg
->date1
= pimg
->date2
= datestamp
;
2636 #else /* IMG_API_VERSION == 1 */
2637 pimg
->days1
= (datestamp
- TIME_T_1900
) / SECS_PER_DAY
;
2638 pimg
->days2
= pimg
->days1
;
2643 #if IMG_API_VERSION == 0
2644 pimg
->date1
= pimg
->date2
= 0;
2645 #else /* IMG_API_VERSION == 1 */
2646 pimg
->days1
= pimg
->days2
= -1;
2652 if (pimg
->pending
== PENDING_HAD_XSECT
) {
2653 pimg
->pending
= PENDING_XSECT_END
;
2659 unsigned shot_flags
= (ch
== 'd' ? img_FLAG_SURFACE
: 0);
2661 if (pimg
->survey
&& pimg
->label_len
== 0) {
2662 /* We're only holding onto this line in case the first line
2663 * of the 'N' is a 'D', so skip it for now...
2667 if (pimg
->pending
== -1) {
2671 fpos
= ftell(pimg
->fh
) - 1;
2672 fseek(pimg
->fh
, pimg
->start
, SEEK_SET
);
2673 ch
= GETC(pimg
->fh
);
2675 /* If a file actually has a 'D' or 'd' before any
2676 * 'M', then pretend the action is 'M' - one of the
2677 * examples in the docs was like this!
2683 line
= getline_alloc(pimg
->fh
);
2685 img_errno
= IMG_OUTOFMEMORY
;
2688 /* Compass stores coordinates as North, East, Up = (y,x,z)! */
2689 if (sscanf(line
, "%lf%lf%lf", &p
->y
, &p
->x
, &p
->z
) != 3) {
2691 if (ferror(pimg
->fh
)) {
2692 img_errno
= IMG_READERROR
;
2694 img_errno
= IMG_BADFORMAT
;
2698 p
->x
*= METRES_PER_FOOT
;
2699 p
->y
*= METRES_PER_FOOT
;
2700 p
->z
*= METRES_PER_FOOT
;
2701 q
= strchr(line
, 'S');
2704 img_errno
= IMG_BADFORMAT
;
2709 while (q
[len
] > ' ') ++len
;
2710 /* Add 2 for ' ' before and terminating '\0'. */
2711 if (!check_label_space(pimg
, pimg
->label_len
+ len
+ 2)) {
2712 img_errno
= IMG_OUTOFMEMORY
;
2715 pimg
->flags
= compass_plt_get_station_flags(pimg
, q
, len
);
2716 pimg
->label
= pimg
->label_buf
;
2717 if (pimg
->label_len
) {
2718 pimg
->label
[pimg
->label_len
] = ' ';
2719 memcpy(pimg
->label
+ pimg
->label_len
+ 1, q
, len
);
2720 pimg
->label
[pimg
->label_len
+ 1 + len
] = '\0';
2722 memcpy(pimg
->label
, q
, len
);
2723 pimg
->label
[len
] = '\0';
2727 /* Now read LRUD. Technically, this is optional but virtually
2728 * all PLT files have it (with dummy negative values if no LRUD
2729 * was recorded) and some versions of Compass can't read PLT
2732 while (*q
&& *q
<= ' ') q
++;
2736 if (sscanf(q
, "%lf%lf%lf%lf%n",
2737 &pimg
->l
, &pimg
->r
, &pimg
->u
, &pimg
->d
,
2738 &bytes_used
) != 4) {
2740 if (ferror(pimg
->fh
)) {
2741 img_errno
= IMG_READERROR
;
2743 img_errno
= IMG_BADFORMAT
;
2747 if ((pimg
->flags
& img_SFLAG_UNDERGROUND
) &&
2748 (pimg
->l
>= 0 || pimg
->r
>= 0 || pimg
->u
>= 0 || pimg
->d
>= 0)) {
2749 if (pimg
->l
>= 0) pimg
->l
*= METRES_PER_FOOT
; else pimg
->l
= -1;
2750 if (pimg
->r
>= 0) pimg
->r
*= METRES_PER_FOOT
; else pimg
->r
= -1;
2751 if (pimg
->u
>= 0) pimg
->u
*= METRES_PER_FOOT
; else pimg
->u
= -1;
2752 if (pimg
->d
>= 0) pimg
->d
*= METRES_PER_FOOT
; else pimg
->d
= -1;
2753 pimg
->pending
|= PENDING_XSECT
| PENDING_HAD_XSECT
;
2754 } else if (pimg
->pending
== PENDING_HAD_XSECT
) {
2755 pimg
->pending
= PENDING_XSECT_END
;
2759 pimg
->l
= pimg
->r
= pimg
->u
= pimg
->d
= -1;
2760 if (pimg
->pending
== PENDING_HAD_XSECT
) {
2761 pimg
->pending
= PENDING_XSECT_END
;
2764 while (*q
&& *q
<= ' ') q
++;
2766 /* Skip distance from entrance. */
2767 do ++q
; while (*q
&& *q
<= ' ');
2768 while (*q
> ' ') q
++;
2769 while (*q
&& *q
<= ' ') q
++;
2772 /* "Shot Flags". Defined flags we currently ignore here:
2773 * C: "Do not adjust this shot when closing loops."
2774 * X: "you will never see this flag in a plot file."
2776 while (isalpha((unsigned char)*++q
)) {
2779 shot_flags
|= img_FLAG_DUPLICATE
;
2782 shot_flags
|= img_FLAG_SPLAY
;
2785 /* P is "Exclude this shot from plotting", but the
2786 * use suggested in the Compass docs is for surface
2787 * data, and they "[do] not support passage
2790 * Even if it's actually being used for a different
2791 * purpose, Survex programs don't show surface legs
2792 * by default so img_FLAG_SURFACE matches fairly
2795 shot_flags
|= img_FLAG_SURFACE
;
2800 if (shot_flags
& img_FLAG_SURFACE
) {
2801 /* Suppress passage? */
2805 fseek(pimg
->fh
, fpos
, SEEK_SET
);
2808 if (pimg
->flags
< 0) {
2809 pimg
->flags
= shot_flags
;
2810 /* We've already emitted img_LABEL for this station. */
2818 pimg
->pending
|= PENDING_MOVE
;
2820 pimg
->pending
|= PENDING_LINE
| (shot_flags
<< PENDING_FLAGS_SHIFT
);
2827 img_errno
= IMG_BADFORMAT
;
2832 /* CMAP .xyz file */
2837 if (pimg
->pending
) {
2838 /* pending MOVE or LINE or LABEL or STOP */
2839 int r
= pimg
->pending
- 4;
2840 /* Set label to empty - don't use "" as we adjust label relative
2841 * to label_buf when label_buf is reallocated. */
2842 pimg
->label
= pimg
->label_buf
+ strlen(pimg
->label_buf
);
2844 if (r
== img_LABEL
) {
2846 read_xyz_shot_coords(p
, pimg
->label_buf
+ 16);
2847 subtract_xyz_shot_deltas(p
, pimg
->label_buf
+ 16);
2848 pimg
->pending
= img_STOP
+ 4;
2854 if (r
== img_STOP
) {
2856 read_xyz_shot_coords(p
, pimg
->label_buf
+ 16);
2863 pimg
->label
= pimg
->label_buf
;
2866 if (feof(pimg
->fh
)) return img_STOP
;
2867 line
= getline_alloc(pimg
->fh
);
2869 img_errno
= IMG_OUTOFMEMORY
;
2872 } while (line
[0] == ' ' || line
[0] == '\0');
2873 if (line
[0] == '\x1a') return img_STOP
;
2876 if (pimg
->version
== VERSION_CMAP_STATION
) {
2877 /* station variant */
2880 img_errno
= IMG_BADFORMAT
;
2883 memcpy(pimg
->label
, line
, 6);
2884 q
= (char *)memchr(pimg
->label
, ' ', 6);
2885 if (!q
) q
= pimg
->label
+ 6;
2888 read_xyz_station_coords(p
, line
);
2890 /* FIXME: look at prev for lines (line + 32, 5) */
2891 /* FIXME: duplicate stations... */
2894 /* Shot variant (VERSION_CMAP_SHOT) */
2895 char old
[8], new_
[8];
2898 img_errno
= IMG_BADFORMAT
;
2902 memcpy(old
, line
, 7);
2903 q
= (char *)memchr(old
, ' ', 7);
2904 if (!q
) q
= old
+ 7;
2907 memcpy(new_
, line
+ 7, 7);
2908 q
= (char *)memchr(new_
, ' ', 7);
2909 if (!q
) q
= new_
+ 7;
2912 pimg
->flags
= img_SFLAG_UNDERGROUND
;
2914 if (strcmp(old
, new_
) == 0) {
2915 pimg
->pending
= img_MOVE
+ 4;
2916 read_xyz_shot_coords(p
, line
);
2917 strcpy(pimg
->label
, new_
);
2922 if (strcmp(old
, pimg
->label
) == 0) {
2923 pimg
->pending
= img_LINE
+ 4;
2924 read_xyz_shot_coords(p
, line
);
2925 strcpy(pimg
->label
, new_
);
2930 pimg
->pending
= img_LABEL
+ 4;
2931 read_xyz_shot_coords(p
, line
);
2932 strcpy(pimg
->label
, new_
);
2933 memcpy(pimg
->label
+ 16, line
, 70);
2942 write_coord(FILE *fh
, double x
, double y
, double z
)
2946 INT32_T X
= my_lround(x
* 100.0);
2947 INT32_T Y
= my_lround(y
* 100.0);
2948 INT32_T Z
= my_lround(z
* 100.0);
2956 write_v3label(img
*pimg
, int opt
, const char *s
)
2960 /* find length of common prefix */
2962 for (len
= 0; s
[len
] == pimg
->label_buf
[len
] && s
[len
] != '\0'; len
++) {
2963 if (s
[len
] == '.') dot
= len
+ 1;
2966 SVX_ASSERT(len
<= pimg
->label_len
);
2967 n
= pimg
->label_len
- len
;
2969 if (pimg
->label_len
) PUTC(0, pimg
->fh
);
2970 } else if (n
<= 16) {
2971 if (n
) PUTC(n
+ 15, pimg
->fh
);
2972 } else if (dot
== 0) {
2973 if (pimg
->label_len
) PUTC(0, pimg
->fh
);
2976 const char *p
= pimg
->label_buf
+ dot
;
2978 for (len
= pimg
->label_len
- dot
- 17; len
; len
--) {
2979 if (*p
++ == '.') n
++;
2985 if (pimg
->label_len
) PUTC(0, pimg
->fh
);
2990 n
= strlen(s
+ len
);
2991 PUTC(opt
, pimg
->fh
);
2994 } else if (n
< 0xffff + 0xfe) {
2995 PUTC(0xfe, pimg
->fh
);
2996 put16((short)(n
- 0xfe), pimg
->fh
);
2998 PUTC(0xff, pimg
->fh
);
3001 fwrite(s
+ len
, n
, 1, pimg
->fh
);
3004 pimg
->label_len
= n
;
3005 if (!check_label_space(pimg
, n
+ 1))
3006 return 0; /* FIXME: distinguish out of memory... */
3007 memcpy(pimg
->label_buf
+ len
, s
+ len
, n
- len
+ 1);
3009 return !ferror(pimg
->fh
);
3013 write_v8label(img
*pimg
, int opt
, int common_flag
, size_t common_val
,
3016 size_t len
, del
, add
;
3018 /* find length of common prefix */
3019 for (len
= 0; s
[len
] == pimg
->label_buf
[len
] && s
[len
] != '\0'; len
++) {
3022 SVX_ASSERT(len
<= pimg
->label_len
);
3023 del
= pimg
->label_len
- len
;
3024 add
= strlen(s
+ len
);
3026 if (add
== common_val
&& del
== common_val
) {
3027 PUTC(opt
| common_flag
, pimg
->fh
);
3029 PUTC(opt
, pimg
->fh
);
3030 if (del
<= 15 && add
<= 15 && (del
|| add
)) {
3031 PUTC((del
<< 4) | add
, pimg
->fh
);
3033 PUTC(0x00, pimg
->fh
);
3035 PUTC(del
, pimg
->fh
);
3037 PUTC(0xff, pimg
->fh
);
3038 put32(del
, pimg
->fh
);
3041 PUTC(add
, pimg
->fh
);
3043 PUTC(0xff, pimg
->fh
);
3044 put32(add
, pimg
->fh
);
3050 fwrite(s
+ len
, add
, 1, pimg
->fh
);
3052 pimg
->label_len
= len
+ add
;
3053 if (add
> del
&& !check_label_space(pimg
, pimg
->label_len
+ 1))
3054 return 0; /* FIXME: distinguish out of memory... */
3056 memcpy(pimg
->label_buf
+ len
, s
+ len
, add
+ 1);
3058 return !ferror(pimg
->fh
);
3062 img_write_item_date_new(img
*pimg
)
3065 /* Only write dates when they've changed. */
3066 #if IMG_API_VERSION == 0
3067 if (pimg
->date1
== pimg
->olddate1
&& pimg
->date2
== pimg
->olddate2
)
3070 same
= (pimg
->date1
== pimg
->date2
);
3071 unset
= (pimg
->date1
== 0);
3072 #else /* IMG_API_VERSION == 1 */
3073 if (pimg
->days1
== pimg
->olddays1
&& pimg
->days2
== pimg
->olddays2
)
3076 same
= (pimg
->days1
== pimg
->days2
);
3077 unset
= (pimg
->days1
== -1);
3082 PUTC(0x10, pimg
->fh
);
3084 PUTC(0x11, pimg
->fh
);
3085 #if IMG_API_VERSION == 0
3086 put16((pimg
->date1
- TIME_T_1900
) / SECS_PER_DAY
, pimg
->fh
);
3087 #else /* IMG_API_VERSION == 1 */
3088 put16(pimg
->days1
, pimg
->fh
);
3092 #if IMG_API_VERSION == 0
3093 int diff
= (pimg
->date2
- pimg
->date1
) / SECS_PER_DAY
;
3094 if (diff
> 0 && diff
<= 256) {
3095 PUTC(0x12, pimg
->fh
);
3096 put16((pimg
->date1
- TIME_T_1900
) / SECS_PER_DAY
, pimg
->fh
);
3097 PUTC(diff
- 1, pimg
->fh
);
3099 PUTC(0x13, pimg
->fh
);
3100 put16((pimg
->date1
- TIME_T_1900
) / SECS_PER_DAY
, pimg
->fh
);
3101 put16((pimg
->date2
- TIME_T_1900
) / SECS_PER_DAY
, pimg
->fh
);
3103 #else /* IMG_API_VERSION == 1 */
3104 int diff
= pimg
->days2
- pimg
->days1
;
3105 if (diff
> 0 && diff
<= 256) {
3106 PUTC(0x12, pimg
->fh
);
3107 put16(pimg
->days1
, pimg
->fh
);
3108 PUTC(diff
- 1, pimg
->fh
);
3110 PUTC(0x13, pimg
->fh
);
3111 put16(pimg
->days1
, pimg
->fh
);
3112 put16(pimg
->days2
, pimg
->fh
);
3116 #if IMG_API_VERSION == 0
3117 pimg
->olddate1
= pimg
->date1
;
3118 pimg
->olddate2
= pimg
->date2
;
3119 #else /* IMG_API_VERSION == 1 */
3120 pimg
->olddays1
= pimg
->days1
;
3121 pimg
->olddays2
= pimg
->days2
;
3126 img_write_item_date(img
*pimg
)
3129 /* Only write dates when they've changed. */
3130 #if IMG_API_VERSION == 0
3131 if (pimg
->date1
== pimg
->olddate1
&& pimg
->date2
== pimg
->olddate2
)
3134 same
= (pimg
->date1
== pimg
->date2
);
3135 unset
= (pimg
->date1
== 0);
3136 #else /* IMG_API_VERSION == 1 */
3137 if (pimg
->days1
== pimg
->olddays1
&& pimg
->days2
== pimg
->olddays2
)
3140 same
= (pimg
->days1
== pimg
->days2
);
3141 unset
= (pimg
->days1
== -1);
3145 if (img_output_version
< 7) {
3146 PUTC(0x20, pimg
->fh
);
3147 #if IMG_API_VERSION == 0
3148 put32(pimg
->date1
, pimg
->fh
);
3149 #else /* IMG_API_VERSION == 1 */
3150 put32((pimg
->days1
- DAYS_1900
) * SECS_PER_DAY
, pimg
->fh
);
3154 PUTC(0x24, pimg
->fh
);
3156 PUTC(0x20, pimg
->fh
);
3157 #if IMG_API_VERSION == 0
3158 put16((pimg
->date1
- TIME_T_1900
) / SECS_PER_DAY
, pimg
->fh
);
3159 #else /* IMG_API_VERSION == 1 */
3160 put16(pimg
->days1
, pimg
->fh
);
3165 if (img_output_version
< 7) {
3166 PUTC(0x21, pimg
->fh
);
3167 #if IMG_API_VERSION == 0
3168 put32(pimg
->date1
, pimg
->fh
);
3169 put32(pimg
->date2
, pimg
->fh
);
3170 #else /* IMG_API_VERSION == 1 */
3171 put32((pimg
->days1
- DAYS_1900
) * SECS_PER_DAY
, pimg
->fh
);
3172 put32((pimg
->days2
- DAYS_1900
) * SECS_PER_DAY
, pimg
->fh
);
3175 #if IMG_API_VERSION == 0
3176 int diff
= (pimg
->date2
- pimg
->date1
) / SECS_PER_DAY
;
3177 if (diff
> 0 && diff
<= 256) {
3178 PUTC(0x21, pimg
->fh
);
3179 put16((pimg
->date1
- TIME_T_1900
) / SECS_PER_DAY
, pimg
->fh
);
3180 PUTC(diff
- 1, pimg
->fh
);
3182 PUTC(0x23, pimg
->fh
);
3183 put16((pimg
->date1
- TIME_T_1900
) / SECS_PER_DAY
, pimg
->fh
);
3184 put16((pimg
->date2
- TIME_T_1900
) / SECS_PER_DAY
, pimg
->fh
);
3186 #else /* IMG_API_VERSION == 1 */
3187 int diff
= pimg
->days2
- pimg
->days1
;
3188 if (diff
> 0 && diff
<= 256) {
3189 PUTC(0x21, pimg
->fh
);
3190 put16(pimg
->days1
, pimg
->fh
);
3191 PUTC(diff
- 1, pimg
->fh
);
3193 PUTC(0x23, pimg
->fh
);
3194 put16(pimg
->days1
, pimg
->fh
);
3195 put16(pimg
->days2
, pimg
->fh
);
3200 #if IMG_API_VERSION == 0
3201 pimg
->olddate1
= pimg
->date1
;
3202 pimg
->olddate2
= pimg
->date2
;
3203 #else /* IMG_API_VERSION == 1 */
3204 pimg
->olddays1
= pimg
->days1
;
3205 pimg
->olddays2
= pimg
->days2
;
3210 img_write_item_new(img
*pimg
, int code
, int flags
, const char *s
,
3211 double x
, double y
, double z
);
3213 img_write_item_v3to7(img
*pimg
, int code
, int flags
, const char *s
,
3214 double x
, double y
, double z
);
3216 img_write_item_ancient(img
*pimg
, int code
, int flags
, const char *s
,
3217 double x
, double y
, double z
);
3220 img_write_item(img
*pimg
, int code
, int flags
, const char *s
,
3221 double x
, double y
, double z
)
3224 if (pimg
->version
>= 8) {
3225 img_write_item_new(pimg
, code
, flags
, s
, x
, y
, z
);
3226 } else if (pimg
->version
>= 3) {
3227 img_write_item_v3to7(pimg
, code
, flags
, s
, x
, y
, z
);
3229 img_write_item_ancient(pimg
, code
, flags
, s
, x
, y
, z
);
3234 img_write_item_new(img
*pimg
, int code
, int flags
, const char *s
,
3235 double x
, double y
, double z
)
3239 write_v8label(pimg
, 0x80 | flags
, 0, -1, s
);
3242 INT32_T l
, r
, u
, d
, max_dim
;
3243 img_write_item_date_new(pimg
);
3244 l
= (INT32_T
)my_lround(pimg
->l
* 100.0);
3245 r
= (INT32_T
)my_lround(pimg
->r
* 100.0);
3246 u
= (INT32_T
)my_lround(pimg
->u
* 100.0);
3247 d
= (INT32_T
)my_lround(pimg
->d
* 100.0);
3252 max_dim
= max(max(l
, r
), max(u
, d
));
3253 flags
= (flags
& img_XFLAG_END
) ? 1 : 0;
3254 if (max_dim
>= 32768) flags
|= 2;
3255 write_v8label(pimg
, 0x30 | flags
, 0, -1, s
);
3257 /* Big passage! Need to use 4 bytes. */
3274 img_write_item_date_new(pimg
);
3275 if (pimg
->style
!= pimg
->oldstyle
) {
3276 switch (pimg
->style
) {
3277 case img_STYLE_NORMAL
:
3278 case img_STYLE_DIVING
:
3279 case img_STYLE_CARTESIAN
:
3280 case img_STYLE_CYLPOLAR
:
3281 case img_STYLE_NOSURVEY
:
3282 PUTC(pimg
->style
, pimg
->fh
);
3285 pimg
->oldstyle
= pimg
->style
;
3287 write_v8label(pimg
, 0x40 | flags
, 0x20, 0x00, s
? s
: "");
3289 default: /* ignore for now */
3292 write_coord(pimg
->fh
, x
, y
, z
);
3296 img_write_item_v3to7(img
*pimg
, int code
, int flags
, const char *s
,
3297 double x
, double y
, double z
)
3301 write_v3label(pimg
, 0x40 | flags
, s
);
3304 INT32_T l
, r
, u
, d
, max_dim
;
3305 /* Need at least version 5 for img_XSECT. */
3306 if (pimg
->version
< 5) return;
3307 img_write_item_date(pimg
);
3308 l
= (INT32_T
)my_lround(pimg
->l
* 100.0);
3309 r
= (INT32_T
)my_lround(pimg
->r
* 100.0);
3310 u
= (INT32_T
)my_lround(pimg
->u
* 100.0);
3311 d
= (INT32_T
)my_lround(pimg
->d
* 100.0);
3316 max_dim
= max(max(l
, r
), max(u
, d
));
3317 flags
= (flags
& img_XFLAG_END
) ? 1 : 0;
3318 if (max_dim
>= 32768) flags
|= 2;
3319 write_v3label(pimg
, 0x30 | flags
, s
);
3321 /* Big passage! Need to use 4 bytes. */
3338 if (pimg
->version
>= 4) {
3339 img_write_item_date(pimg
);
3341 write_v3label(pimg
, 0x80 | flags
, s
? s
: "");
3343 default: /* ignore for now */
3346 write_coord(pimg
->fh
, x
, y
, z
);
3350 img_write_item_ancient(img
*pimg
, int code
, int flags
, const char *s
,
3351 double x
, double y
, double z
)
3355 SVX_ASSERT(pimg
->version
> 0);
3358 if (pimg
->version
== 1) {
3359 /* put a move before each label */
3360 img_write_item_ancient(pimg
, img_MOVE
, 0, NULL
, x
, y
, z
);
3362 fputsnl(s
, pimg
->fh
);
3366 if (len
> 255 || strchr(s
, '\n')) {
3367 /* long label - not in early incarnations of v2 format, but few
3368 * 3d files will need these, so better not to force incompatibility
3369 * with a new version I think... */
3371 PUTC(flags
, pimg
->fh
);
3372 put32(len
, pimg
->fh
);
3375 PUTC(0x40 | (flags
& 0x3f), pimg
->fh
);
3376 fputsnl(s
, pimg
->fh
);
3384 if (pimg
->version
> 1) {
3385 opt
= 0x80 | (flags
& 0x3f);
3390 default: /* ignore for now */
3393 if (pimg
->version
== 1) {
3394 put32(opt
, pimg
->fh
);
3396 if (opt
) PUTC(opt
, pimg
->fh
);
3398 write_coord(pimg
->fh
, x
, y
, z
);
3401 /* Write error information for the current traverse
3402 * n_legs is the number of legs in the traverse
3403 * length is the traverse length (in m)
3404 * E is the ratio of the observed misclosure to the theoretical one
3405 * H is the ratio of the observed horizontal misclosure to the theoretical one
3406 * V is the ratio of the observed vertical misclosure to the theoretical one
3409 img_write_errors(img
*pimg
, int n_legs
, double length
,
3410 double E
, double H
, double V
)
3412 PUTC((pimg
->version
>= 8 ? 0x1f : 0x22), pimg
->fh
);
3413 put32(n_legs
, pimg
->fh
);
3414 put32((INT32_T
)my_lround(length
* 100.0), pimg
->fh
);
3415 put32((INT32_T
)my_lround(E
* 100.0), pimg
->fh
);
3416 put32((INT32_T
)my_lround(H
* 100.0), pimg
->fh
);
3417 put32((INT32_T
)my_lround(V
* 100.0), pimg
->fh
);
3421 img_close(img
*pimg
)
3427 osfree(pimg
->survey
);
3428 osfree(pimg
->title
);
3430 osfree(pimg
->datestamp
);
3432 /* write end of data marker */
3433 switch (pimg
->version
) {
3435 put32((INT32_T
)-1, pimg
->fh
);
3438 if (pimg
->version
<= 7 ?
3439 (pimg
->label_len
!= 0) :
3440 (pimg
->style
!= img_STYLE_NORMAL
)) {
3449 if (ferror(pimg
->fh
)) result
= 0;
3450 if (pimg
->close_func
&& pimg
->close_func(pimg
->fh
))
3452 if (!result
) img_errno
= pimg
->fRead
? IMG_READERROR
: IMG_WRITEERROR
;
3455 switch (pimg
->version
) {
3456 case VERSION_COMPASS_PLT
:
3457 compass_plt_free_data(pimg
);
3463 osfree(pimg
->label_buf
);
3464 osfree(pimg
->filename_opened
);
3471 img_parse_compass_datum_string(const char *s
, size_t len
)
3473 #define EQ(S) len == LITLEN(S) && memcmp(s, S, LITLEN(S)) == 0
3474 /* First check the three which seem to be commonly used in Compass data. */
3476 return img_DATUM_WGS84
;
3477 if (EQ("North American 1927"))
3478 return img_DATUM_NAD27
;
3479 if (EQ("North American 1983"))
3480 return img_DATUM_NAD83
;
3483 return img_DATUM_ADINDAN
;
3485 return img_DATUM_ARC1950
;
3487 return img_DATUM_ARC1960
;
3489 return img_DATUM_CAPE
;
3490 if (EQ("European 1950"))
3491 return img_DATUM_EUROPEAN1950
;
3492 if (EQ("Geodetic 1949"))
3493 return img_DATUM_NZGD49
;
3494 if (EQ("Hu Tzu Shan"))
3495 return img_DATUM_HUTZUSHAN1950
;
3497 return img_DATUM_INDIAN1960
;
3499 return img_DATUM_TOKYO
;
3501 return img_DATUM_WGS72
;
3503 return img_DATUM_UNKNOWN
;
3507 img_compass_utm_proj_str(img_datum datum
, int utm_zone
)
3510 const char* proj4_datum
= NULL
;
3513 case img_DATUM_UNKNOWN
:
3515 case img_DATUM_ADINDAN
:
3516 if (utm_zone
>= 35 && utm_zone
<= 38)
3517 epsg_code
= 20100 + utm_zone
;
3519 case img_DATUM_ARC1950
:
3520 if (utm_zone
>= -36 && utm_zone
<= -34)
3521 epsg_code
= 20900 - utm_zone
;
3523 case img_DATUM_ARC1960
:
3524 if (utm_zone
>= -37 && utm_zone
<= -35)
3525 epsg_code
= 21000 - utm_zone
;
3527 case img_DATUM_CAPE
:
3528 if (utm_zone
>= -36 && utm_zone
<= -34)
3529 epsg_code
= 22200 - utm_zone
;
3531 case img_DATUM_EUROPEAN1950
:
3532 if (utm_zone
>= 28 && utm_zone
<= 38)
3533 epsg_code
= 23000 + utm_zone
;
3535 case img_DATUM_NZGD49
:
3537 epsg_code
= 27200 + utm_zone
;
3539 case img_DATUM_HUTZUSHAN1950
:
3543 case img_DATUM_INDIAN1960
:
3544 if (utm_zone
>= 48 && utm_zone
<= 49)
3545 epsg_code
= 3100 + utm_zone
;
3547 case img_DATUM_NAD27
:
3548 if (utm_zone
> 0 && utm_zone
<= 23)
3549 epsg_code
= 26700 + utm_zone
;
3550 else if (utm_zone
>= 59)
3551 epsg_code
= 3311 + utm_zone
;
3553 proj4_datum
= "NAD27";
3555 case img_DATUM_NAD83
:
3556 if (utm_zone
> 0 && utm_zone
<= 23)
3557 epsg_code
= 26900 + utm_zone
;
3558 else if (utm_zone
== 24)
3560 else if (utm_zone
>= 59)
3561 epsg_code
= 3313 + utm_zone
;
3563 proj4_datum
= "NAD83";
3565 case img_DATUM_TOKYO
:
3566 if (utm_zone
>= 51 && utm_zone
<= 55)
3567 epsg_code
= 3041 + utm_zone
;
3569 case img_DATUM_WGS72
:
3571 epsg_code
= 32200 + utm_zone
;
3573 epsg_code
= 32300 - utm_zone
;
3575 case img_DATUM_WGS84
:
3577 epsg_code
= 32600 + utm_zone
;
3579 epsg_code
= 32700 - utm_zone
;
3584 char *proj_str
= xosmalloc(11);
3586 img_errno
= IMG_OUTOFMEMORY
;
3589 sprintf(proj_str
, "EPSG:%d", epsg_code
);
3595 size_t len
= strlen(proj4_datum
) + 52 + 2 + 1;
3596 const char *south
= "";
3598 utm_zone
= -utm_zone
;
3602 proj_str
= xosmalloc(len
);
3604 img_errno
= IMG_OUTOFMEMORY
;
3608 "+proj=utm +zone=%d %s+datum=%s +units=m +no_defs +type=crs",
3609 utm_zone
, south
, proj4_datum
);