1
0
Fork 0
flightgear/DEM/dem.cxx
curt c4666a4c4a When outputing to a .node file, first check for an optional
"index.node.ex" file in case there is a .poly file to go along with this
node file.  Include these nodes first since they are referenced by position
from the .poly file.  This is my first pass at adding an area "cutout"
feature to the terrain generation pipeline.
1998-07-20 12:46:11 +00:00

879 lines
22 KiB
C++

// -*- Mode: C++ -*-
//
// dem.c -- DEM management class
//
// Written by Curtis Olson, started March 1998.
//
// Copyright (C) 1998 Curtis L. Olson - curt@me.umn.edu
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
// $Id$
// (Log is kept at end of this file)
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <ctype.h> // isspace()
#include <stdlib.h> // atoi()
#include <math.h> // rint()
#include <stdio.h>
#include <string.h>
#include <sys/stat.h> // stat()
#include <unistd.h> // stat()
#include <zlib/zlib.h>
#include "dem.hxx"
#include "leastsqs.hxx"
#include <Include/fg_constants.h>
#define MAX_EX_NODES 10000
#ifdef WIN32
# define MKDIR(a) mkdir(a,S_IRWXU) // I am just guessing at this flag (NHV)
#endif // WIN32
fgDEM::fgDEM( void ) {
// printf("class fgDEM CONstructor called.\n");
dem_data = new float[DEM_SIZE_1][DEM_SIZE_1];
output_data = new float[DEM_SIZE_1][DEM_SIZE_1];
}
#ifdef WIN32
// return the file path name ( foo/bar/file.ext = foo/bar )
void extract_path (char *in, char *base) {
int len, i;
len = strlen (in);
strcpy (base, in);
i = len - 1;
while ( (i >= 0) && (in[i] != '/') ) {
i--;
}
base[i] = '\0';
}
// Make a subdirectory
int my_mkdir (char *dir) {
struct stat stat_buf;
int result;
printf ("mk_dir() ");
result = stat (dir, &stat_buf);
if (result != 0) {
MKDIR (dir);
result = stat (dir, &stat_buf);
if (result != 0) {
printf ("problem creating %s\n", dir);
} else {
printf ("%s created\n", dir);
}
} else {
printf ("%s already exists\n", dir);
}
return (result);
}
#endif // WIN32
// open a DEM file
int fgDEM::open ( char *file ) {
// open input file (or read from stdin)
if ( strcmp(file, "-") == 0 ) {
printf("Loading DEM data file: stdin\n");
// fd = stdin;
fd = gzdopen(STDIN_FILENO, "r");
} else {
if ( (fd = gzopen(file, "rb")) == NULL ) {
printf("Cannot gzopen %s\n", file);
return(0);
}
printf("Loading DEM data file: %s\n", file);
}
return(1);
}
// close a DEM file
int fgDEM::close ( void ) {
gzclose(fd);
return(1);
}
// return next token from input stream
static void next_token(gzFile fd, char *token) {
int i, result;
char c;
i = 0;
c = gzgetc(fd);
// skip past spaces
while ( (c != -1) && (c == ' ') ) {
c = gzgetc(fd);
}
while ( (c != -1) && (c != ' ') && (c != '\n') ){
token[i] = c;
i++;
c = gzgetc(fd);
}
token[i] = '\0';
if ( c == -1 ) {
strcpy(token, "__END_OF_FILE__");
printf(" Warning: Reached end of file!\n");
}
// printf(" returning %s\n", token);
}
// return next integer from input stream
static int next_int(gzFile fd) {
char token[80];
next_token(fd, token);
return ( atoi(token) );
}
// return next double from input stream
static double next_double(gzFile fd) {
char token[80];
next_token(fd, token);
return ( atof(token) );
}
// return next exponential num from input stream
static int next_exp(gzFile fd) {
char token[80];
double mantissa;
int exp, acc;
int i;
next_token(fd, token);
sscanf(token, "%lfD%d", &mantissa, &exp);
// printf(" Mantissa = %.4f Exp = %d\n", mantissa, exp);
acc = 1;
if ( exp > 0 ) {
for ( i = 1; i <= exp; i++ ) {
acc *= 10;
}
} else if ( exp < 0 ) {
for ( i = -1; i >= exp; i-- ) {
acc /= 10;
}
}
return( (int)rint(mantissa * (double)acc) );
}
// read and parse DEM "A" record
int fgDEM::read_a_record( void ) {
int i, inum;
double dnum;
char name[144];
char token[80];
char *ptr;
// get the name field (144 characters)
for ( i = 0; i < 144; i++ ) {
name[i] = gzgetc(fd);
}
name[i+1] = '\0';
// clean off the whitespace at the end
for ( i = strlen(name)-2; i > 0; i-- ) {
if ( !isspace(name[i]) ) {
i=0;
} else {
name[i] = '\0';
}
}
printf(" Quad name field: %s\n", name);
// DEM level code, 3 reflects processing by DMA
inum = next_int(fd);
printf(" DEM level code = %d\n", inum);
if ( inum > 3 ) {
return(0);
}
// Pattern code, 1 indicates a regular elevation pattern
inum = next_int(fd);
printf(" Pattern code = %d\n", inum);
// Planimetric reference system code, 0 indicates geographic
// coordinate system.
inum = next_int(fd);
printf(" Planimetric reference code = %d\n", inum);
// Zone code
inum = next_int(fd);
printf(" Zone code = %d\n", inum);
// Map projection parameters (ignored)
for ( i = 0; i < 15; i++ ) {
dnum = next_double(fd);
// printf("%d: %f\n",i,dnum);
}
// Units code, 3 represents arc-seconds as the unit of measure for
// ground planimetric coordinates throughout the file.
inum = next_int(fd);
if ( inum != 3 ) {
printf(" Unknown (X,Y) units code = %d!\n", inum);
exit(-1);
}
// Units code; 2 represents meters as the unit of measure for
// elevation coordinates throughout the file.
inum = next_int(fd);
if ( inum != 2 ) {
printf(" Unknown (Z) units code = %d!\n", inum);
exit(-1);
}
// Number (n) of sides in the polygon which defines the coverage of
// the DEM file (usually equal to 4).
inum = next_int(fd);
if ( inum != 4 ) {
printf(" Unknown polygon dimension = %d!\n", inum);
exit(-1);
}
// Ground coordinates of bounding box in arc-seconds
dem_x1 = originx = next_exp(fd);
dem_y1 = originy = next_exp(fd);
printf(" Origin = (%.2f,%.2f)\n", originx, originy);
dem_x2 = next_exp(fd);
dem_y2 = next_exp(fd);
dem_x3 = next_exp(fd);
dem_y3 = next_exp(fd);
dem_x4 = next_exp(fd);
dem_y4 = next_exp(fd);
// Minimum/maximum elevations in meters
dem_z1 = next_exp(fd);
dem_z2 = next_exp(fd);
printf(" Elevation range %.4f %.4f\n", dem_z1, dem_z2);
// Counterclockwise angle from the primary axis of ground
// planimetric referenced to the primary axis of the DEM local
// reference system.
next_token(fd, token);
// Accuracy code; 0 indicates that a record of accuracy does not
// exist and that no record type C will follow.
// DEM spacial resolution. Usually (3,3,1) (3,6,1) or (3,9,1)
// depending on latitude
// I will eventually have to do something with this for data at
// higher latitudes */
next_token(fd, token);
printf(" accuracy & spacial resolution string = %s\n", token);
i = strlen(token);
printf(" length = %d\n", i);
ptr = token + i - 12;
printf(" last field = %s = %.2f\n", ptr, atof(ptr));
ptr[0] = '\0';
ptr = ptr - 12;
col_step = atof(ptr);
printf(" last field = %s = %.2f\n", ptr, col_step);
ptr[0] = '\0';
ptr = ptr - 12;
row_step = atof(ptr);
printf(" last field = %s = %.2f\n", ptr, row_step);
ptr[0] = '\0';
// accuracy code = atod(token)
inum = atoi(token);
printf(" Accuracy code = %d\n", inum);
printf(" column step = %.2f row step = %.2f\n",
col_step, row_step);
// dimension of arrays to follow (1)
next_token(fd, token);
// number of profiles
dem_num_profiles = cols = next_int(fd);
printf(" Expecting %d profiles\n", dem_num_profiles);
return(1);
}
// read and parse DEM "B" record
void fgDEM::read_b_record( void ) {
char token[80];
int i;
// row / column id of this profile
prof_row = next_int(fd);
prof_col = next_int(fd);
// printf("col id = %d row id = %d\n", prof_col, prof_row);
// Number of columns and rows (elevations) in this profile
prof_num_rows = rows = next_int(fd);
prof_num_cols = next_int(fd);
// printf(" profile num rows = %d\n", prof_num_rows);
// Ground planimetric coordinates (arc-seconds) of the first
// elevation in the profile
prof_x1 = next_exp(fd);
prof_y1 = next_exp(fd);
// printf(" Starting at %.2f %.2f\n", prof_x1, prof_y1);
// Elevation of local datum for the profile. Always zero for
// 1-degree DEM, the reference is mean sea level.
next_token(fd, token);
// Minimum and maximum elevations for the profile.
next_token(fd, token);
next_token(fd, token);
// One (usually) dimensional array (prof_num_cols,1) of elevations
for ( i = 0; i < prof_num_rows; i++ ) {
prof_data = next_int(fd);
dem_data[cur_col][i] = (float)prof_data;
}
}
// parse dem file
int fgDEM::parse( void ) {
int i;
cur_col = 0;
if ( !read_a_record() ) {
return(0);
}
for ( i = 0; i < dem_num_profiles; i++ ) {
// printf("Ready to read next b record\n");
read_b_record();
cur_col++;
if ( cur_col % 100 == 0 ) {
printf(" loaded %d profiles of data\n", cur_col);
}
}
printf(" Done parsing\n");
return(1);
}
// return the current altitude based on mesh data. We should rewrite
// this to interpolate exact values, but for now this is good enough
double fgDEM::interpolate_altitude( double lon, double lat ) {
// we expect incoming (lon,lat) to be in arcsec for now
double xlocal, ylocal, dx, dy, zA, zB, elev;
int x1, x2, x3, y1, y2, y3;
float z1, z2, z3;
int xindex, yindex;
/* determine if we are in the lower triangle or the upper triangle
______
| /|
| / |
| / |
|/ |
------
then calculate our end points
*/
xlocal = (lon - originx) / col_step;
ylocal = (lat - originy) / row_step;
xindex = (int)(xlocal);
yindex = (int)(ylocal);
// printf("xindex = %d yindex = %d\n", xindex, yindex);
if ( xindex + 1 == cols ) {
xindex--;
}
if ( yindex + 1 == rows ) {
yindex--;
}
if ( (xindex < 0) || (xindex + 1 >= cols) ||
(yindex < 0) || (yindex + 1 >= rows) ) {
return(-9999);
}
dx = xlocal - xindex;
dy = ylocal - yindex;
if ( dx > dy ) {
// lower triangle
// printf(" Lower triangle\n");
x1 = xindex;
y1 = yindex;
z1 = dem_data[x1][y1];
x2 = xindex + 1;
y2 = yindex;
z2 = dem_data[x2][y2];
x3 = xindex + 1;
y3 = yindex + 1;
z3 = dem_data[x3][y3];
// printf(" dx = %.2f dy = %.2f\n", dx, dy);
// printf(" (x1,y1,z1) = (%d,%d,%d)\n", x1, y1, z1);
// printf(" (x2,y2,z2) = (%d,%d,%d)\n", x2, y2, z2);
// printf(" (x3,y3,z3) = (%d,%d,%d)\n", x3, y3, z3);
zA = dx * (z2 - z1) + z1;
zB = dx * (z3 - z1) + z1;
// printf(" zA = %.2f zB = %.2f\n", zA, zB);
if ( dx > FG_EPSILON ) {
elev = dy * (zB - zA) / dx + zA;
} else {
elev = zA;
}
} else {
// upper triangle
// printf(" Upper triangle\n");
x1 = xindex;
y1 = yindex;
z1 = dem_data[x1][y1];
x2 = xindex;
y2 = yindex + 1;
z2 = dem_data[x2][y2];
x3 = xindex + 1;
y3 = yindex + 1;
z3 = dem_data[x3][y3];
// printf(" dx = %.2f dy = %.2f\n", dx, dy);
// printf(" (x1,y1,z1) = (%d,%d,%d)\n", x1, y1, z1);
// printf(" (x2,y2,z2) = (%d,%d,%d)\n", x2, y2, z2);
// printf(" (x3,y3,z3) = (%d,%d,%d)\n", x3, y3, z3);
zA = dy * (z2 - z1) + z1;
zB = dy * (z3 - z1) + z1;
// printf(" zA = %.2f zB = %.2f\n", zA, zB );
// printf(" xB - xA = %.2f\n", col_step * dy / row_step);
if ( dy > FG_EPSILON ) {
elev = dx * (zB - zA) / dy + zA;
} else {
elev = zA;
}
}
return(elev);
}
// Use least squares to fit a simpler data set to dem data
void fgDEM::fit( double error, fgBUCKET *p ) {
double x[DEM_SIZE_1], y[DEM_SIZE_1];
double m, b, ave_error, max_error;
double cury, lasty;
int n, row, start, end, good_fit;
int colmin, colmax, rowmin, rowmax;
// FILE *dem, *fit, *fit1;
printf("Initializing output mesh structure\n");
outputmesh_init();
// determine dimensions
colmin = p->x * ( (cols - 1) / 8);
colmax = colmin + ( (cols - 1) / 8);
rowmin = p->y * ( (rows - 1) / 8);
rowmax = rowmin + ( (rows - 1) / 8);
printf("Fitting region = %d,%d to %d,%d\n", colmin, rowmin, colmax, rowmax);
// include the corners explicitly
outputmesh_set_pt(colmin, rowmin, dem_data[colmin][rowmin]);
outputmesh_set_pt(colmin, rowmax, dem_data[colmin][rowmax]);
outputmesh_set_pt(colmax, rowmax, dem_data[colmax][rowmax]);
outputmesh_set_pt(colmax, rowmin, dem_data[colmax][rowmin]);
printf("Beginning best fit procedure\n");
for ( row = rowmin; row <= rowmax; row++ ) {
// fit = fopen("fit.dat", "w");
// fit1 = fopen("fit1.dat", "w");
start = colmin;
// printf(" fitting row = %d\n", row);
while ( start < colmax ) {
end = start + 1;
good_fit = 1;
x[(end - start) - 1] = 0.0 + ( start * col_step );
y[(end - start) - 1] = dem_data[start][row];
while ( (end <= colmax) && good_fit ) {
n = (end - start) + 1;
// printf("Least square of first %d points\n", n);
x[end - start] = 0.0 + ( end * col_step );
y[end - start] = dem_data[end][row];
least_squares(x, y, n, &m, &b);
ave_error = least_squares_error(x, y, n, m, b);
max_error = least_squares_max_error(x, y, n, m, b);
/*
printf("%d - %d ave error = %.2f max error = %.2f y = %.2f*x + %.2f\n",
start, end, ave_error, max_error, m, b);
f = fopen("gnuplot.dat", "w");
for ( j = 0; j <= end; j++) {
fprintf(f, "%.2f %.2f\n", 0.0 + ( j * col_step ),
dem_data[row][j]);
}
for ( j = start; j <= end; j++) {
fprintf(f, "%.2f %.2f\n", 0.0 + ( j * col_step ),
dem_data[row][j]);
}
fclose(f);
printf("Please hit return: "); gets(junk);
*/
if ( max_error > error ) {
good_fit = 0;
}
end++;
}
if ( !good_fit ) {
// error exceeded the threshold, back up
end -= 2; // back "end" up to the last good enough fit
n--; // back "n" up appropriately too
} else {
// we popped out of the above loop while still within
// the error threshold, so we must be at the end of
// the data set
end--;
}
least_squares(x, y, n, &m, &b);
ave_error = least_squares_error(x, y, n, m, b);
max_error = least_squares_max_error(x, y, n, m, b);
/*
printf("\n");
printf("%d - %d ave error = %.2f max error = %.2f y = %.2f*x + %.2f\n",
start, end, ave_error, max_error, m, b);
printf("\n");
fprintf(fit1, "%.2f %.2f\n", x[0], m * x[0] + b);
fprintf(fit1, "%.2f %.2f\n", x[end-start], m * x[end-start] + b);
*/
if ( start > colmin ) {
// skip this for the first line segment
cury = m * x[0] + b;
outputmesh_set_pt(start, row, (lasty + cury) / 2);
// fprintf(fit, "%.2f %.2f\n", x[0], (lasty + cury) / 2);
}
lasty = m * x[end-start] + b;
start = end;
}
/*
fclose(fit);
fclose(fit1);
dem = fopen("gnuplot.dat", "w");
for ( j = 0; j < DEM_SIZE_1; j++) {
fprintf(dem, "%.2f %.2f\n", 0.0 + ( j * col_step ),
dem_data[j][row]);
}
fclose(dem);
*/
// NOTICE, this is for testing only. This instance of
// output_nodes should be removed. It should be called only
// once at the end once all the nodes have been generated.
// newmesh_output_nodes(&nm, "mesh.node");
// printf("Please hit return: "); gets(junk);
}
// outputmesh_output_nodes(fg_root, p);
}
// Initialize output mesh structure
void fgDEM::outputmesh_init( void ) {
int i, j;
for ( j = 0; j < DEM_SIZE_1; j++ ) {
for ( i = 0; i < DEM_SIZE_1; i++ ) {
output_data[i][j] = -9999.0;
}
}
}
// Get the value of a mesh node
double fgDEM::outputmesh_get_pt( int i, int j ) {
return ( output_data[i][j] );
}
// Set the value of a mesh node
void fgDEM::outputmesh_set_pt( int i, int j, double value ) {
// printf("Setting data[%d][%d] = %.2f\n", i, j, value);
output_data[i][j] = value;
}
// Write out a node file that can be used by the "triangle" program.
// Check for an optional "index.node.ex" file in case there is a .poly
// file to go along with this node file. Include these nodes first
// since they are referenced by position from the .poly file.
void fgDEM::outputmesh_output_nodes( char *fg_root, fgBUCKET *p ) {
double exnodes[MAX_EX_NODES][3];
double junk1, junk2, junk3;
struct stat stat_buf;
char base_path[256], dir[256], file[256], exfile[256];
#ifdef WIN32
char tmp_path[256];
#endif
char command[256];
FILE *fd;
long int index;
int colmin, colmax, rowmin, rowmax;
int i, j, count, excount, result;
// determine dimensions
colmin = p->x * ( (cols - 1) / 8);
colmax = colmin + ( (cols - 1) / 8);
rowmin = p->y * ( (rows - 1) / 8);
rowmax = rowmin + ( (rows - 1) / 8);
printf(" dumping region = %d,%d to %d,%d\n",
colmin, rowmin, colmax, rowmax);
// generate the base directory
fgBucketGenBasePath(p, base_path);
printf("fg_root = %s Base Path = %s\n", fg_root, base_path);
sprintf(dir, "%s/Scenery/%s", fg_root, base_path);
printf("Dir = %s\n", dir);
// stat() directory and create if needed
result = stat(dir, &stat_buf);
if ( result != 0 ) {
printf("Stat error need to create directory\n");
#ifndef WIN32
sprintf(command, "mkdir -p %s\n", dir);
system(command);
#else // WIN32
// Cygwin crashes when trying to output to node file
// explicitly making directory structure seems OK on Win95
extract_path (base_path, tmp_path);
sprintf (dir, "%s/Scenery", fg_root);
if (my_mkdir (dir)) { exit (-1); }
sprintf (dir, "%s/Scenery/%s", fg_root, tmp_path);
if (my_mkdir (dir)) { exit (-1); }
sprintf (dir, "%s/Scenery/%s", fg_root, base_path);
if (my_mkdir (dir)) { exit (-1); }
#endif // WIN32
} else {
// assume directory exists
}
// get index and generate output file name
index = fgBucketGenIndex(p);
sprintf(file, "%s/%ld.node", dir, index);
// get (optional) extra node file name (in case there is matching
// .poly file.
strcpy(exfile, file);
strcat(exfile, ".ex");
// load extra nodes if they exist
excount = 0;
if ( (fd = fopen(exfile, "r")) != NULL ) {
fscanf(fd, "%d %d %d %d", &excount, &junk1, &junk2, &junk3);
if ( excount > MAX_EX_NODES - 1 ) {
printf("Error, too many 'extra' nodes, increase array size\n");
exit(-1);
} else {
printf(" Expecting %d 'extra' nodes\n", excount);
}
for ( i = 1; i <= excount; i++ ) {
fscanf(fd, "%d %lf %lf %lf\n", &junk1,
&exnodes[i][0], &exnodes[i][1], &exnodes[i][2]);
printf("(extra) %d %.2f %.2f %.2f\n",
i, exnodes[i][0], exnodes[i][1], exnodes[i][2]);
}
}
fclose(fd);
printf("Creating node file: %s\n", file);
fd = fopen(file, "w");
// first count regular nodes to generate header
count = 0;
for ( j = rowmin; j <= rowmax; j++ ) {
for ( i = colmin; i <= colmax; i++ ) {
if ( output_data[i][j] > -9000.0 ) {
count++;
}
}
// printf(" count = %d\n", count);
}
fprintf(fd, "%d 2 1 0\n", count + excount);
// now write out extra node data
for ( i = 1; i <= excount; i++ ) {
fprintf(fd, "%d %.2f %.2f %.2f\n",
i, exnodes[i][0], exnodes[i][1], exnodes[i][2]);
}
// write out actual node data
count = excount + 1;
for ( j = rowmin; j <= rowmax; j++ ) {
for ( i = colmin; i <= colmax; i++ ) {
if ( output_data[i][j] > -9000.0 ) {
fprintf(fd, "%d %.2f %.2f %.2f\n",
count++,
originx + (double)i * col_step,
originy + (double)j * row_step,
output_data[i][j]);
}
}
// printf(" count = %d\n", count);
}
fclose(fd);
}
fgDEM::~fgDEM( void ) {
// printf("class fgDEM DEstructor called.\n");
free(dem_data);
free(output_data);
}
// $Log$
// Revision 1.11 1998/07/20 12:46:11 curt
// When outputing to a .node file, first check for an optional
// "index.node.ex" file in case there is a .poly file to go along with this
// node file. Include these nodes first since they are referenced by position
// from the .poly file. This is my first pass at adding an area "cutout"
// feature to the terrain generation pipeline.
//
// Revision 1.10 1998/07/13 20:58:02 curt
// .
//
// Revision 1.9 1998/07/13 15:29:49 curt
// Added #ifdef HAVE_CONFIG_H
//
// Revision 1.8 1998/07/04 00:47:18 curt
// typedef'd struct fgBUCKET.
//
// Revision 1.7 1998/06/05 18:14:39 curt
// Abort out early when reading the "A" record if it doesn't look like
// a proper DEM file.
//
// Revision 1.6 1998/05/02 01:49:21 curt
// Fixed a bug where the wrong variable was being initialized.
//
// Revision 1.5 1998/04/25 15:00:32 curt
// Changed "r" to "rb" in gzopen() options. This fixes bad behavior in win32.
//
// Revision 1.4 1998/04/22 13:14:46 curt
// Fixed a bug in zlib usage.
//
// Revision 1.3 1998/04/18 03:53:05 curt
// Added zlib support.
//
// Revision 1.2 1998/04/14 02:43:27 curt
// Used "new" to auto-allocate large DEM parsing arrays in class constructor.
//
// Revision 1.1 1998/04/08 22:57:22 curt
// Adopted Gnu automake/autoconf system.
//
// Revision 1.3 1998/04/06 21:09:41 curt
// Additional win32 support.
// Fixed a bad bug in dem file parsing that was causing the output to be
// flipped about x = y.
//
// Revision 1.2 1998/03/23 20:35:41 curt
// Updated to use FG_EPSILON
//
// Revision 1.1 1998/03/19 02:54:47 curt
// Reorganized into a class lib called fgDEM.
//
// Revision 1.1 1998/03/19 01:46:28 curt
// Initial revision.
//