Reputation: 190
I am learning C as a hobbyist. As a fun project I decided to code a .hgt file reader. hgt file are earth elevation files. I found few information on this file format : https://dds.cr.usgs.gov/srtm/version2_1/Documentation/Quickstart.pdf
You can find files for the whole planet here : http://viewfinderpanoramas.org/Coverage%20map%20viewfinderpanoramas_org3.htm
but it seems to be pretty straight forward : a list of signed two bytes integers they say.
I found that two bytes integers are well represented by "signed short" type, is that right ? In my code , you will see I used int_16t ( I tried that when having issues with signed shorts) I believe they have the same range ) Anyway, I open the file , dump the data in an array, and write it to a bmp file.
At first I thought it worked great, but that was because I was viewing the result of a very low elevation part of the earth. When I tried to render some files corresponding to areas with mountains, the image below shows the issue.
Below is my code so far. I was pretty sure the issue is at the beginning, when reading the data, but I don't know anymore.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <stdint.h>
#include <math.h>
int main(int argc, char *argv[]) {
if(argc < 2) {
printf("I need a hgt file path as a paramater\n");
return 0;
} else {
const int DIM = 1201;
FILE *fp;
int16_t *elevation_buffer;
elevation_buffer = malloc(sizeof(int16_t) * DIM * DIM); // 2 bytes integers
fp = fopen(argv[1], "rb");
/* Seek to the beginning of the file */
fseek(fp, 0, SEEK_SET);
/* read elevation data from HGT file */
fread(elevation_buffer, sizeof(int16_t), DIM*DIM, fp);
fclose(fp);
printf("sizeof signed short int : %d\n", sizeof(signed short int));
printf("sizeof int16_t : %d\n", sizeof(int16_t));
/* creating a bmp file to visualize elevation tile*/
int w = DIM;
int h = DIM;
int x,y,r,g,b;
FILE *f;
unsigned char *img = NULL;
int filesize = 54 + 3 * w * h; //w is your image width, h is image height, both int
img = (unsigned char *)malloc(3 * w * h);
memset(img, 0, 3 * w * h);
for (int i = 0; i < w; i++)
{
for (int j = 0; j < h; j++)
{
x = i;
y = (h - 1) - j;
float elevation = (elevation_buffer[x + y * w] - INT16_MIN) / (float)(INT16_MAX - INT16_MIN);
r = (int)(elevation * 255);
float freq = 100.0f;
if (r > 255) {
r = 255;
} else if(r < 0) {
r = 0;
}
g = r;
b = r;
img[(x + y * w) * 3 + 2] = (unsigned char)(r);
img[(x + y * w) * 3 + 1] = (unsigned char)(g);
img[(x + y * w) * 3 + 0] = (unsigned char)(b);
}
}
unsigned char bmpfileheader[14] = {'B', 'M', 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0};
unsigned char bmpinfoheader[40] = {40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 24, 0};
unsigned char bmppad[3] = {0, 0, 0};
bmpfileheader[2] = (unsigned char)(filesize);
bmpfileheader[3] = (unsigned char)(filesize >> 8);
bmpfileheader[4] = (unsigned char)(filesize >> 16);
bmpfileheader[5] = (unsigned char)(filesize >> 24);
bmpinfoheader[4] = (unsigned char)(w);
bmpinfoheader[5] = (unsigned char)(w >> 8);
bmpinfoheader[6] = (unsigned char)(w >> 16);
bmpinfoheader[7] = (unsigned char)(w >> 24);
bmpinfoheader[8] = (unsigned char)(h);
bmpinfoheader[9] = (unsigned char)(h >> 8);
bmpinfoheader[10] = (unsigned char)(h >> 16);
bmpinfoheader[11] = (unsigned char)(h >> 24);
f = fopen("img.bmp", "wb");
fwrite(bmpfileheader, 1, 14, f);
fwrite(bmpinfoheader, 1, 40, f);
for (int i = 0; i < h; i++)
{
fwrite(img + (w * (h - i - 1) * 3), 3, w, f);
fwrite(bmppad, 1, (4 - (w * 3) % 4) % 4, f);
}
free(img);
free(elevation_buffer);
fclose(f);
return 0;
}
}
Upvotes: 1
Views: 160
Reputation: 13458
You should account for the byte order... your input file format is specified to contain bytes in big endian byte order.
As a simple fix, you could just check your byteorder and reverse the read in data as necessary. The answer https://stackoverflow.com/a/8571139/5265292 explains a way how to detect byte order on your system.
// ...
fread(elevation_buffer, sizeof(int16_t), DIM*DIM, fp);
fclose(fp);
int byteOrderCheck = 1;
if (*(char *)&byteOrderCheck == 1)
{
// need to revert byte order for little endian
for (int i = 0; i < DIM*DIM; ++i)
{
elevation_buffer[i] = (int16_t)reverse_byte_order((uint16_t)elevation_buffer[i]);
}
}
// ...
with function reverse_byte_order
as
uint16_t reverse_byte_order(uint16_t num)
{
return ((num & 0xff) << 8) | (num >> 8);
}
Note this is untested, you may need to change some details.
Upvotes: 1