add c++ source code for use hgt file into controlpanel
This commit is contained in:
parent
e438d0a1c8
commit
044f72d20b
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,3 +11,4 @@ _req_packages/
|
||||
map_tile_cache/
|
||||
map_tile_cache_ge/
|
||||
geoelevation_dem_cache/
|
||||
build/
|
||||
21
.vscode/launch.json
vendored
21
.vscode/launch.json
vendored
@ -1,7 +1,4 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
@ -9,6 +6,22 @@
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "geoelevation"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "(Windows) Launch C++ CLI",
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/build/elevation_cli.exe",
|
||||
"args": [
|
||||
// Argomenti a riga di comando per il test
|
||||
"${workspaceFolder}/geoelevation_dem_cache/hgt_tiles",
|
||||
"46.1705234",
|
||||
"9.403258"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}",
|
||||
"environment": [],
|
||||
"console": "integratedTerminal" // Mostra l'output nel terminale integrato
|
||||
},
|
||||
]
|
||||
}
|
||||
6
.vscode/settings.json
vendored
Normal file
6
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"cmake.sourceDirectory": "C:/src/____GitProjects/GeoElevation/source_c",
|
||||
"files.associations": {
|
||||
"string": "cpp"
|
||||
}
|
||||
}
|
||||
7
source_c/CMakeLists.txt
Normal file
7
source_c/CMakeLists.txt
Normal file
@ -0,0 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(GeoElevationCpp)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
add_executable(elevation_cli main.cpp ElevationReader.cpp)
|
||||
134
source_c/ElevationReader.cpp
Normal file
134
source_c/ElevationReader.cpp
Normal file
@ -0,0 +1,134 @@
|
||||
#include "ElevationReader.h"
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
|
||||
// The value used within HGT files to represent "no data" points (e.g., water bodies).
|
||||
// This corresponds to the minimum possible value for a signed 16-bit integer.
|
||||
constexpr int16_t HGT_NO_DATA_VALUE = std::numeric_limits<int16_t>::min(); // -32768
|
||||
|
||||
ElevationReader::ElevationReader(const std::string& hgt_directory)
|
||||
: hgt_dir_path_(hgt_directory) {}
|
||||
|
||||
double ElevationReader::get_elevation(double latitude, double longitude) {
|
||||
// Validate input coordinates to be within the standard WGS 84 range.
|
||||
if (latitude < -90.0 || latitude >= 90.0 || longitude < -180.0 || longitude >= 180.0) {
|
||||
return USER_NO_DATA_VALUE;
|
||||
}
|
||||
|
||||
// Determine which tile file is needed for the given coordinates.
|
||||
std::string basename = get_tile_basename(latitude, longitude);
|
||||
|
||||
// Load the tile from disk if it's not already in the cache.
|
||||
if (!ensure_tile_is_loaded(basename)) {
|
||||
// This occurs if the file doesn't exist or is invalid (e.g., wrong size).
|
||||
std::cerr << "Warning: Tile file for " << basename << ".hgt not found or is invalid." << std::endl;
|
||||
return USER_NO_DATA_VALUE;
|
||||
}
|
||||
|
||||
// Retrieve the tile data from the cache. `at()` is used for safe access.
|
||||
const auto& data = tile_cache_.at(basename);
|
||||
|
||||
// Calculate the fractional part of the coordinates to find the position within the 1x1 degree tile.
|
||||
double lat_fractional = latitude - floor(latitude);
|
||||
double lon_fractional = longitude - floor(longitude);
|
||||
|
||||
// IMPORTANT: HGT data is stored in rows from North to South (top to bottom).
|
||||
// A higher latitude corresponds to a lower row index. So, we invert the latitude logic.
|
||||
// Example: For N45 tile (45-46 deg), lat 45.99 is near row 0; lat 45.01 is near row 3600.
|
||||
int row = static_cast<int>(round((1.0 - lat_fractional) * (HGT_GRID_DIMENSION - 1)));
|
||||
int col = static_cast<int>(round(lon_fractional * (HGT_GRID_DIMENSION - 1)));
|
||||
|
||||
// Calculate the 1D index into the data vector.
|
||||
size_t index = static_cast<size_t>(row) * HGT_GRID_DIMENSION + col;
|
||||
|
||||
// Safety check to prevent out-of-bounds access.
|
||||
if (index >= data.size()) {
|
||||
return USER_NO_DATA_VALUE;
|
||||
}
|
||||
|
||||
int16_t elevation = data[index];
|
||||
|
||||
// Check if the point corresponds to a "no data" value within the file.
|
||||
if (elevation == HGT_NO_DATA_VALUE) {
|
||||
return USER_NO_DATA_VALUE;
|
||||
}
|
||||
|
||||
return static_cast<double>(elevation);
|
||||
}
|
||||
|
||||
std::string ElevationReader::get_tile_basename(double latitude, double longitude) const {
|
||||
// The HGT filename convention is based on the integer coordinates of the bottom-left (SW) corner.
|
||||
int lat_int = static_cast<int>(floor(latitude));
|
||||
int lon_int = static_cast<int>(floor(longitude));
|
||||
|
||||
char lat_char = (lat_int >= 0) ? 'N' : 'S';
|
||||
char lon_char = (lon_int >= 0) ? 'E' : 'W';
|
||||
|
||||
// Use a stringstream for safe and clean string formatting.
|
||||
std::stringstream ss;
|
||||
ss << lat_char << std::setfill('0') << std::setw(2) << std::abs(lat_int)
|
||||
<< lon_char << std::setfill('0') << std::setw(3) << std::abs(lon_int);
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
bool ElevationReader::ensure_tile_is_loaded(const std::string& tile_basename) {
|
||||
// 1. Check if the tile is already in our memory cache to avoid disk I/O.
|
||||
if (tile_cache_.count(tile_basename)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. Construct the full path to the .hgt file.
|
||||
std::string file_path = hgt_dir_path_ + "/" + tile_basename + ".hgt";
|
||||
|
||||
std::ifstream file(file_path, std::ios::binary);
|
||||
if (!file) {
|
||||
return false; // File does not exist or cannot be opened.
|
||||
}
|
||||
|
||||
// 3. Validate the file size to ensure it's a complete 1-arc-second tile.
|
||||
file.seekg(0, std::ios::end);
|
||||
std::streampos file_size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
const std::streampos expected_size = HGT_GRID_DIMENSION * HGT_GRID_DIMENSION * sizeof(int16_t);
|
||||
if (file_size != expected_size) {
|
||||
std::cerr << "Warning: Invalid file size for " << file_path
|
||||
<< ". Expected " << expected_size << " bytes, but got " << file_size << " bytes." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. Read the entire file into a vector of 16-bit integers.
|
||||
std::vector<int16_t> tile_data(HGT_GRID_DIMENSION * HGT_GRID_DIMENSION);
|
||||
file.read(reinterpret_cast<char*>(tile_data.data()), expected_size);
|
||||
|
||||
if (!file) {
|
||||
std::cerr << "Error: Failed to read all data from " << file_path << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 5. CRITICAL STEP: Convert every value from Big-Endian to the host's native endianness.
|
||||
for (auto& val : tile_data) {
|
||||
val = byteswap(val);
|
||||
}
|
||||
|
||||
// 6. Store the loaded and processed data in the cache using std::move for efficiency.
|
||||
tile_cache_[tile_basename] = std::move(tile_data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int16_t ElevationReader::byteswap(int16_t value) const {
|
||||
// Cast to unsigned to prevent issues with sign extension during bit shifts on negative numbers.
|
||||
uint16_t u_value = static_cast<uint16_t>(value);
|
||||
|
||||
// Example: 0x1234 (Big Endian) becomes 0x3412 (Little Endian)
|
||||
// - (u_value & 0xFF00) >> 8 -> (0x1200) >> 8 -> 0x0012
|
||||
// - (u_value & 0x00FF) << 8 -> (0x0034) << 8 -> 0x3400
|
||||
// - 0x0012 | 0x3400 = 0x3412
|
||||
return static_cast<int16_t>(((u_value & 0xFF00) >> 8) | ((u_value & 0x00FF) << 8));
|
||||
}
|
||||
83
source_c/ElevationReader.h
Normal file
83
source_c/ElevationReader.h
Normal file
@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
|
||||
// A constant for the expected grid dimension of 1-arc-second NASADEM HGT files.
|
||||
constexpr int HGT_GRID_DIMENSION = 3601;
|
||||
|
||||
// The special value returned to the user when elevation data is not available.
|
||||
constexpr double USER_NO_DATA_VALUE = -999.0;
|
||||
|
||||
/**
|
||||
* @class ElevationReader
|
||||
* @brief Reads elevation data from .hgt files downloaded by the GeoElevation tool.
|
||||
*
|
||||
* This class manages the loading, caching, and querying of 1-arc-second NASADEM .hgt files.
|
||||
* It is designed to find the correct tile for a given coordinate, handle the binary
|
||||
* file reading, and correctly interpret the big-endian data format used in HGT files.
|
||||
*/
|
||||
class ElevationReader {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs an ElevationReader.
|
||||
* @param hgt_directory The path to the directory containing the .hgt tile files.
|
||||
*/
|
||||
explicit ElevationReader(const std::string& hgt_directory);
|
||||
|
||||
/**
|
||||
* @brief Gets the elevation for a specific geographic coordinate.
|
||||
*
|
||||
* This is the main public method of the class. It orchestrates finding the correct
|
||||
* tile, loading it if necessary, and calculating the elevation.
|
||||
*
|
||||
* @param latitude The latitude in decimal degrees (e.g., 45.072).
|
||||
* @param longitude The longitude in decimal degrees (e.g., 7.685).
|
||||
* @return The elevation in meters as a double. Returns USER_NO_DATA_VALUE (-999.0)
|
||||
* if the corresponding .hgt tile is not found, coordinates are invalid,
|
||||
* or if the point corresponds to a "no data" value in the file.
|
||||
*/
|
||||
double get_elevation(double latitude, double longitude);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Generates the base filename (e.g., "N45E007") for a given coordinate.
|
||||
* @param latitude The latitude of the point.
|
||||
* @param longitude The longitude of the point.
|
||||
* @return The standard base filename for the tile.
|
||||
*/
|
||||
std::string get_tile_basename(double latitude, double longitude) const;
|
||||
|
||||
/**
|
||||
* @brief Loads a tile from disk into the cache if it's not already present.
|
||||
*
|
||||
* This method implements a lazy-loading strategy. A tile is only read from disk
|
||||
* the first time it is needed. Subsequent requests for the same tile will use
|
||||
* the in-memory cache.
|
||||
*
|
||||
* @param tile_basename The base name of the tile to load (e.g., "N45E007").
|
||||
* @return True if the tile was loaded successfully or was already in the cache, false otherwise.
|
||||
*/
|
||||
bool ensure_tile_is_loaded(const std::string& tile_basename);
|
||||
|
||||
/**
|
||||
* @brief Converts a 16-bit integer from big-endian to the host system's endianness.
|
||||
*
|
||||
* HGT files store data in big-endian (network byte order), while most modern CPUs
|
||||
* (x86/x64) are little-endian. This byte swap is a critical step to correctly
|
||||
* interpret the elevation values.
|
||||
*
|
||||
* @param value The big-endian short value read from the file.
|
||||
* @return The value converted to the host system's native byte order.
|
||||
*/
|
||||
int16_t byteswap(int16_t value) const;
|
||||
|
||||
// The path to the directory where .hgt files are stored.
|
||||
std::string hgt_dir_path_;
|
||||
|
||||
// In-memory cache for tile data to avoid repeated, slow file reads from disk.
|
||||
// The key is the tile basename (e.g., "N45E007"), the value is the elevation data.
|
||||
std::map<std::string, std::vector<int16_t>> tile_cache_;
|
||||
};
|
||||
BIN
source_c/elevation_cli.exe
Normal file
BIN
source_c/elevation_cli.exe
Normal file
Binary file not shown.
58
source_c/main.cpp
Normal file
58
source_c/main.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
#include "ElevationReader.h"
|
||||
void print_usage()
|
||||
{
|
||||
std::cerr << "Usage: elevation_cli <path_to_hgt_directory> <latitude> <longitude>" << std::endl;
|
||||
std::cerr << "Example: elevation_cli ../geoelevation_dem_cache/hgt_tiles 45.07 7.68" << std::endl;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
if (argc != 4)
|
||||
{
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string hgt_dir;
|
||||
double lat, lon;
|
||||
|
||||
try
|
||||
{
|
||||
hgt_dir = argv[1];
|
||||
lat = std::stod(argv[2]);
|
||||
lon = std::stod(argv[3]);
|
||||
} catch (const std::invalid_argument& e)
|
||||
{
|
||||
std::cerr << "Error: Invalid latitude or longitude format." << std::endl;
|
||||
print_usage();
|
||||
return 1;
|
||||
} catch (const std::out_of_range& e)
|
||||
{
|
||||
std::cerr << "Error: Latitude or longitude value is out of range." << std::endl;
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ElevationReader reader(hgt_dir);
|
||||
double elevation = reader.get_elevation(lat, lon);
|
||||
std::cout << "Querying for Latitude=" << lat << ", Longitude=" << lon << std::endl;
|
||||
if (elevation == USER_NO_DATA_VALUE)
|
||||
{
|
||||
std::cout << "Result: Elevation data not available for this location." << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "Result: Elevation is " << elevation << " meters." << std::endl;
|
||||
}
|
||||
} catch (const std::exception& e)
|
||||
{
|
||||
std::cerr << "An unexpected error occurred: " << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user