2 * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
4 * Copyright (C) 2003-2008, Evan Battaglia <gtoevan@gmx.net>
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
38 #include <sys/types.h>
44 #include <glib/gstdio.h>
45 #include <glib/gi18n.h>
50 #define DEM_BLOCK_SIZE 1024
51 #define GET_COLUMN(dem,n) ((VikDEMColumn *)g_ptr_array_index( (dem)->columns, (n) ))
53 static gboolean
get_double_and_continue ( gchar
**buffer
, gdouble
*tmp
, gboolean warn
)
56 *tmp
= g_strtod(*buffer
, &endptr
);
57 if ( endptr
== NULL
|| endptr
== *buffer
) {
59 g_warning(_("Invalid DEM"));
67 static gboolean
get_int_and_continue ( gchar
**buffer
, gint
*tmp
, gboolean warn
)
70 *tmp
= strtol(*buffer
, &endptr
, 10);
71 if ( endptr
== NULL
|| endptr
== *buffer
) {
73 g_warning(_("Invalid DEM"));
80 static gboolean
dem_parse_header ( gchar
*buffer
, VikDEM
*dem
)
87 /* incomplete header */
88 if ( strlen(buffer
) != 1024 )
91 /* fix Fortran-style exponentiation 1.0D5 -> 1.0E5 */
101 /* "DEM level code, pattern code, palaimetric reference system code" -- skip */
102 get_int_and_continue(&buffer
, &int_val
, TRUE
);
103 get_int_and_continue(&buffer
, &int_val
, TRUE
);
104 get_int_and_continue(&buffer
, &int_val
, TRUE
);
107 get_int_and_continue(&buffer
, &int_val
, TRUE
);
108 dem
->utm_zone
= int_val
;
109 /* TODO -- southern or northern hemisphere?! */
110 dem
->utm_letter
= 'N';
112 /* skip numbers 5-19 */
113 for ( i
= 0; i
< 15; i
++ ) {
114 if ( ! get_double_and_continue(&buffer
, &val
, FALSE
) ) {
115 g_warning (_("Invalid DEM header"));
120 /* number 20 -- horizontal unit code (utm/ll) */
121 get_double_and_continue(&buffer
, &val
, TRUE
);
122 dem
->horiz_units
= val
;
123 get_double_and_continue(&buffer
, &val
, TRUE
);
124 /* dem->orig_vert_units = val; now done below */
126 /* TODO: do this for real. these are only for 1:24k and 1:250k USGS */
127 if ( dem
->horiz_units
== VIK_DEM_HORIZ_UTM_METERS
) {
128 dem
->east_scale
= 10.0; /* meters */
129 dem
->north_scale
= 10.0;
130 dem
->orig_vert_units
= VIK_DEM_VERT_DECIMETERS
;
132 dem
->east_scale
= 3.0; /* arcseconds */
133 dem
->north_scale
= 3.0;
134 dem
->orig_vert_units
= VIK_DEM_VERT_METERS
;
138 get_double_and_continue(&buffer
, &val
, TRUE
);
140 /* now we get the four corner points. record the min and max. */
141 get_double_and_continue(&buffer
, &val
, TRUE
);
142 dem
->min_east
= dem
->max_east
= val
;
143 get_double_and_continue(&buffer
, &val
, TRUE
);
144 dem
->min_north
= dem
->max_north
= val
;
146 for ( i
= 0; i
< 3; i
++ ) {
147 get_double_and_continue(&buffer
, &val
, TRUE
);
148 if ( val
< dem
->min_east
) dem
->min_east
= val
;
149 if ( val
> dem
->max_east
) dem
->max_east
= val
;
150 get_double_and_continue(&buffer
, &val
, TRUE
);
151 if ( val
< dem
->min_north
) dem
->min_north
= val
;
152 if ( val
> dem
->max_north
) dem
->max_north
= val
;
158 static void dem_parse_block_as_cont ( gchar
*buffer
, VikDEM
*dem
, gint
*cur_column
, gint
*cur_row
)
161 while ( *cur_row
< GET_COLUMN(dem
, *cur_column
)->n_points
) {
162 if ( get_int_and_continue(&buffer
, &tmp
,FALSE
) ) {
163 if ( dem
->orig_vert_units
== VIK_DEM_VERT_DECIMETERS
)
164 GET_COLUMN(dem
, *cur_column
)->points
[*cur_row
] = (gint16
) (tmp
/ 10);
166 GET_COLUMN(dem
, *cur_column
)->points
[*cur_row
] = (gint16
) tmp
;
171 *cur_row
= -1; /* expecting new column */
174 static void dem_parse_block_as_header ( gchar
*buffer
, VikDEM
*dem
, gint
*cur_column
, gint
*cur_row
)
178 gdouble east_west
, south
;
181 /* 1 x n_rows 1 east_west south x x x DATA */
183 if ( (!get_double_and_continue(&buffer
, &tmp
, TRUE
)) || tmp
!= 1 ) {
184 g_warning(_("Incorrect DEM Class B record: expected 1"));
188 /* don't need this */
189 if ( !get_double_and_continue(&buffer
, &tmp
, TRUE
) ) return;
192 if ( !get_double_and_continue(&buffer
, &tmp
, TRUE
) )
194 n_rows
= (guint
) tmp
;
196 if ( (!get_double_and_continue(&buffer
, &tmp
, TRUE
)) || tmp
!= 1 ) {
197 g_warning(_("Incorrect DEM Class B record: expected 1"));
201 if ( !get_double_and_continue(&buffer
, &east_west
, TRUE
) )
203 if ( !get_double_and_continue(&buffer
, &south
, TRUE
) )
206 /* next three things we don't need */
207 if ( !get_double_and_continue(&buffer
, &tmp
, TRUE
)) return;
208 if ( !get_double_and_continue(&buffer
, &tmp
, TRUE
)) return;
209 if ( !get_double_and_continue(&buffer
, &tmp
, TRUE
)) return;
215 /* empty spaces for things before that were skipped */
216 (*cur_row
) = (south
- dem
->min_north
) / dem
->north_scale
;
217 if ( south
> dem
->max_north
|| (*cur_row
) < 0 )
222 g_ptr_array_add ( dem
->columns
, g_malloc(sizeof(VikDEMColumn
)) );
223 GET_COLUMN(dem
,*cur_column
)->east_west
= east_west
;
224 GET_COLUMN(dem
,*cur_column
)->south
= south
;
225 GET_COLUMN(dem
,*cur_column
)->n_points
= n_rows
;
226 GET_COLUMN(dem
,*cur_column
)->points
= g_malloc(sizeof(gint16
)*n_rows
);
228 /* no information for things before that */
229 for ( i
= 0; i
< (*cur_row
); i
++ )
230 GET_COLUMN(dem
,*cur_column
)->points
[i
] = VIK_DEM_INVALID_ELEVATION
;
232 /* now just continue */
233 dem_parse_block_as_cont ( buffer
, dem
, cur_column
, cur_row
);
238 static void dem_parse_block ( gchar
*buffer
, VikDEM
*dem
, gint
*cur_column
, gint
*cur_row
)
240 /* if haven't read anything or have read all items in a columns and are expecting a new column */
241 if ( *cur_column
== -1 || *cur_row
== -1 ) {
242 dem_parse_block_as_header(buffer
, dem
, cur_column
, cur_row
);
244 dem_parse_block_as_cont(buffer
, dem
, cur_column
, cur_row
);
248 /* return size of unzip data or 0 if failed */
249 /* can be made generic to uncompress zip, gzip, bzip2 data */
250 static guint
uncompress_data(void *uncompressed_buffer
, guint uncompressed_size
, void *compressed_data
, guint compressed_size
)
255 stream
.next_in
= compressed_data
;
256 stream
.avail_in
= compressed_size
;
257 stream
.next_out
= uncompressed_buffer
;
258 stream
.avail_out
= uncompressed_size
;
259 stream
.zalloc
= (alloc_func
)0;
260 stream
.zfree
= (free_func
)0;
261 stream
.opaque
= (voidpf
)0;
263 /* negative windowBits to inflateInit2 means "no header" */
264 if ((err
= inflateInit2(&stream
, -MAX_WBITS
)) != Z_OK
) {
265 g_warning("%s(): inflateInit2 failed", __PRETTY_FUNCTION__
);
269 err
= inflate(&stream
, Z_FINISH
);
270 if ((err
!= Z_OK
) && (err
!= Z_STREAM_END
) && stream
.msg
) {
271 g_warning("%s() inflate failed err=%d \"%s\"", __PRETTY_FUNCTION__
, err
, stream
.msg
== NULL
? "unknown" : stream
.msg
);
277 return(stream
.total_out
);
280 static void *unzip_hgt_file(gchar
*zip_file
, gulong
*unzip_size
)
282 void *unzip_data
= NULL
;
286 guint16 extract_version
;
292 guint32 compressed_size
;
293 guint32 uncompressed_size
;
294 guint16 filename_len
;
295 guint16 extra_field_len
;
296 } __attribute__ ((__packed__
)) *local_file_header
= NULL
;
299 local_file_header
= (struct _lfh
*) zip_file
;
300 if (local_file_header
->sig
!= 0x04034b50) {
301 g_warning("%s(): wrong format", __PRETTY_FUNCTION__
);
306 zip_data
= zip_file
+ sizeof(struct _lfh
) + local_file_header
->filename_len
+ local_file_header
->extra_field_len
;
307 unzip_data
= g_malloc(local_file_header
->uncompressed_size
);
308 gulong uncompressed_size
= local_file_header
->uncompressed_size
;
310 if (!(*unzip_size
= uncompress_data(unzip_data
, uncompressed_size
, zip_data
, local_file_header
->compressed_size
))) {
320 static VikDEM
*vik_dem_read_srtm_hgt(const gchar
*file_name
, const gchar
*basename
, gboolean zip
)
325 gint16
*dem_mem
= NULL
;
326 gchar
*dem_file
= NULL
;
327 const gint num_rows_3sec
= 1201;
328 const gint num_rows_1sec
= 3601;
332 GError
*error
= NULL
;
334 dem
= g_malloc(sizeof(VikDEM
));
336 dem
->horiz_units
= VIK_DEM_HORIZ_LL_ARCSECONDS
;
337 dem
->orig_vert_units
= VIK_DEM_VERT_DECIMETERS
;
340 dem
->min_north
= atoi(basename
+1) * 3600;
341 dem
->min_east
= atoi(basename
+4) * 3600;
342 if ( basename
[0] == 'S' )
343 dem
->min_north
= - dem
->min_north
;
344 if ( basename
[3] == 'W' )
345 dem
->min_east
= - dem
->min_east
;
347 dem
->max_north
= 3600 + dem
->min_north
;
348 dem
->max_east
= 3600 + dem
->min_east
;
350 dem
->columns
= g_ptr_array_new();
353 if ((mf
= g_mapped_file_new(file_name
, FALSE
, &error
)) == NULL
) {
354 g_error(_("Couldn't map file %s: %s"), file_name
, error
->message
);
359 file_size
= g_mapped_file_get_length(mf
);
360 dem_file
= g_mapped_file_get_contents(mf
);
363 void *unzip_mem
= NULL
;
366 if ((unzip_mem
= unzip_hgt_file(dem_file
, &ucsize
)) == NULL
) {
367 g_mapped_file_free(mf
);
368 g_ptr_array_free(dem
->columns
, TRUE
);
377 if (file_size
== (num_rows_3sec
* num_rows_3sec
* sizeof(gint16
)))
379 else if (file_size
== (num_rows_1sec
* num_rows_1sec
* sizeof(gint16
)))
382 g_warning("%s(): file %s does not have right size", __PRETTY_FUNCTION__
, basename
);
383 g_mapped_file_free(mf
);
388 num_rows
= (arcsec
== 3) ? num_rows_3sec
: num_rows_1sec
;
389 dem
->east_scale
= dem
->north_scale
= arcsec
;
391 for ( i
= 0; i
< num_rows
; i
++ ) {
393 g_ptr_array_add ( dem
->columns
, g_malloc(sizeof(VikDEMColumn
)) );
394 GET_COLUMN(dem
,i
)->east_west
= dem
->min_east
+ arcsec
*i
;
395 GET_COLUMN(dem
,i
)->south
= dem
->min_north
;
396 GET_COLUMN(dem
,i
)->n_points
= num_rows
;
397 GET_COLUMN(dem
,i
)->points
= g_malloc(sizeof(gint16
)*num_rows
);
401 for ( i
= (num_rows
- 1); i
>= 0; i
-- ) {
402 for ( j
= 0; j
< num_rows
; j
++ ) {
403 GET_COLUMN(dem
,j
)->points
[i
] = GINT16_FROM_BE(dem_mem
[ent
]);
411 g_mapped_file_free(mf
);
415 #define IS_SRTM_HGT(fn) (strlen((fn))==11 && (fn)[7]=='.' && (fn)[8]=='h' && (fn)[9]=='g' && (fn)[10]=='t' && ((fn)[0]=='N' || (fn)[0]=='S') && ((fn)[3]=='E' || (fn)[3]=='W'))
417 VikDEM
*vik_dem_new_from_file(const gchar
*file
)
421 gchar buffer
[DEM_BLOCK_SIZE
+1];
423 /* use to record state for dem_parse_block */
424 gint cur_column
= -1;
426 const gchar
*basename
= a_file_basename(file
);
428 if ( g_access ( file
, R_OK
) != 0 )
431 if ( (strlen(basename
)==11 || ((strlen(basename
) == 15) && (basename
[11] == '.' && basename
[12] == 'z' && basename
[13] == 'i' && basename
[14] == 'p'))) &&
432 basename
[7]=='.' && basename
[8]=='h' && basename
[9]=='g' && basename
[10]=='t' &&
433 (basename
[0] == 'N' || basename
[0] == 'S') && (basename
[3] == 'E' || basename
[3] =='W')) {
434 gboolean is_zip_file
= (strlen(basename
) == 15);
435 rv
= vik_dem_read_srtm_hgt(file
, basename
, is_zip_file
);
439 /* Create Structure */
440 rv
= g_malloc(sizeof(VikDEM
));
443 f
= g_fopen(file
, "r");
448 buffer
[fread(buffer
, 1, DEM_BLOCK_SIZE
, f
)] = '\0';
449 if ( ! dem_parse_header ( buffer
, rv
) ) {
453 /* TODO: actually use header -- i.e. GET # OF COLUMNS EXPECTED */
455 rv
->columns
= g_ptr_array_new();
463 buffer
[fread(buffer
, 1, DEM_BLOCK_SIZE
, f
)] = '\0';
465 /* Fix Fortran-style exponentiation */
473 dem_parse_block(buffer
, rv
, &cur_column
, &cur_row
);
476 /* TODO - class C records (right now says 'Invalid' and dies) */
482 if ( rv
->horiz_units
== VIK_DEM_HORIZ_UTM_METERS
&& rv
->n_columns
>= 2 )
483 rv
->north_scale
= rv
->east_scale
= GET_COLUMN(rv
, 1)->east_west
- GET_COLUMN(rv
,0)->east_west
;
485 /* FIXME bug in 10m DEM's */
486 if ( rv
->horiz_units
== VIK_DEM_HORIZ_UTM_METERS
&& rv
->north_scale
== 10 ) {
488 rv
->min_north
+= 200;
495 void vik_dem_free ( VikDEM
*dem
)
498 for ( i
= 0; i
< dem
->n_columns
; i
++)
499 g_free ( GET_COLUMN(dem
, i
)->points
);
500 g_ptr_array_free ( dem
->columns
, TRUE
);
504 gint16
vik_dem_get_xy ( VikDEM
*dem
, guint col
, guint row
)
506 if ( col
< dem
->n_columns
)
507 if ( row
< GET_COLUMN(dem
, col
)->n_points
)
508 return GET_COLUMN(dem
, col
)->points
[row
];
509 return VIK_DEM_INVALID_ELEVATION
;
512 gint16
vik_dem_get_east_north ( VikDEM
*dem
, gdouble east
, gdouble north
)
516 if ( east
> dem
->max_east
|| east
< dem
->min_east
||
517 north
> dem
->max_north
|| north
< dem
->min_north
)
518 return VIK_DEM_INVALID_ELEVATION
;
520 col
= (gint
) floor((east
- dem
->min_east
) / dem
->east_scale
);
521 row
= (gint
) floor((north
- dem
->min_north
) / dem
->north_scale
);
523 return vik_dem_get_xy ( dem
, col
, row
);
526 static gboolean
dem_get_ref_points_elev_dist(VikDEM
*dem
,
527 gdouble east
, gdouble north
, /* in seconds */
528 gint16
*elevs
, gint16
*dists
)
531 int cols
[4], rows
[4];
535 if ( east
> dem
->max_east
|| east
< dem
->min_east
||
536 north
> dem
->max_north
|| north
< dem
->min_north
)
537 return FALSE
; /* got nothing */
540 pos
.lat
= north
/3600;
542 /* order of the data: sw, nw, ne, se */
544 cols
[0] = (gint
) floor((east
- dem
->min_east
) / dem
->east_scale
);
545 rows
[0] = (gint
) floor((north
- dem
->min_north
) / dem
->north_scale
);
546 ll
[0].lon
= (dem
->min_east
+ dem
->east_scale
*cols
[0])/3600;
547 ll
[0].lat
= (dem
->min_north
+ dem
->north_scale
*rows
[0])/3600;
550 rows
[1] = rows
[0] + 1;
551 ll
[1].lon
= ll
[0].lon
;
552 ll
[1].lat
= ll
[0].lat
+ (gdouble
)dem
->north_scale
/3600;
554 cols
[2] = cols
[0] + 1;
555 rows
[2] = rows
[0] + 1;
556 ll
[2].lon
= ll
[0].lon
+ (gdouble
)dem
->east_scale
/3600;
557 ll
[2].lat
= ll
[0].lat
+ (gdouble
)dem
->north_scale
/3600;
559 cols
[3] = cols
[0] + 1;
561 ll
[3].lon
= ll
[0].lon
+ (gdouble
)dem
->east_scale
/3600;
562 ll
[3].lat
= ll
[0].lat
;
564 for (i
= 0; i
< 4; i
++) {
565 if ((elevs
[i
] = vik_dem_get_xy(dem
, cols
[i
], rows
[i
])) == VIK_DEM_INVALID_ELEVATION
)
567 dists
[i
] = a_coords_latlon_diff(&pos
, &ll
[i
]);
571 for (i
= 0; i
< 4; i
++)
572 fprintf(stderr
, "%f:%f:%d:%d ", ll
[i
].lat
, ll
[i
].lon
, dists
[i
], elevs
[i
]);
573 fprintf(stderr
, " north_scale=%f\n", dem
->north_scale
);
576 return TRUE
; /* all OK */
579 gint16
vik_dem_get_simple_interpol ( VikDEM
*dem
, gdouble east
, gdouble north
)
582 gint16 elevs
[4], dists
[4];
584 if (!dem_get_ref_points_elev_dist(dem
, east
, north
, elevs
, dists
))
585 return VIK_DEM_INVALID_ELEVATION
;
587 for (i
= 0; i
< 4; i
++) {
593 gdouble t
= (gdouble
)elevs
[0]/dists
[0] + (gdouble
)elevs
[1]/dists
[1] + (gdouble
)elevs
[2]/dists
[2] + (gdouble
)elevs
[3]/dists
[3];
594 gdouble b
= 1.0/dists
[0] + 1.0/dists
[1] + 1.0/dists
[2] + 1.0/dists
[3];
599 gint16
vik_dem_get_shepard_interpol ( VikDEM
*dem
, gdouble east
, gdouble north
)
602 gint16 elevs
[4], dists
[4];
607 if (!dem_get_ref_points_elev_dist(dem
, east
, north
, elevs
, dists
))
608 return VIK_DEM_INVALID_ELEVATION
;
611 for (i
= 0; i
< 4; i
++) {
615 if (dists
[i
] > max_dist
)
620 #if 0 /* derived method by Franke & Nielson. Does not seem to work too well here */
621 for (i
= 0; i
< 4; i
++) {
622 tmp
= pow((1.0*(max_dist
- dists
[i
])/max_dist
*dists
[i
]), 2);
628 for (i
= 0; i
< 4; i
++) {
629 tmp
= pow((1.0/dists
[i
]), 2);
634 // fprintf(stderr, "DEBUG: tmp=%f t=%f b=%f %f\n", tmp, t, b, t/b);
640 void vik_dem_east_north_to_xy ( VikDEM
*dem
, gdouble east
, gdouble north
, guint
*col
, guint
*row
)
642 *col
= (gint
) floor((east
- dem
->min_east
) / dem
->east_scale
);
643 *row
= (gint
) floor((north
- dem
->min_north
) / dem
->north_scale
);
644 if ( *col
< 0 ) *col
= 0;
645 if ( *row
< 0 ) *row
= 0;