File raster_data.hpp
File List > gui > raster_data.hpp
Go to the documentation of this file
#pragma once
#include <gdal.h>
#include <algorithm>
#include <atomic>
#include <cmath>
#include <future>
#include <mutex>
#include <optional>
#include <vector>
#include "grid/grid.hpp"
#include "io/crs.hpp"
#include "io/gdal_init.hpp"
#include "tif/tif.hpp"
#include "utilities/progress_tracker.hpp"
struct TifMetadata {
GeoProjection projection;
Extent3D extent;
};
inline TifMetadata read_tif_metadata(const fs::path& path) {
ensure_gdal_initialized();
GDALDataset* dataset = static_cast<GDALDataset*>(GDALOpen(path.string().c_str(), GA_ReadOnly));
Assert(dataset, "Could not open raster " + path.string());
GeoProjection projection = make_projection_from_wkt(std::string(dataset->GetProjectionRef()));
GeoTransform transform(*dataset);
const size_t width = dataset->GetRasterXSize();
const size_t height = dataset->GetRasterYSize();
Extent3D extent;
const std::array<double, 2> x_corners{0.0, static_cast<double>(width)};
const std::array<double, 2> y_corners{0.0, static_cast<double>(height)};
for (double corner_y : y_corners) {
for (double corner_x : x_corners) {
Coordinate2D<double> coord = transform.pixel_to_projection({corner_x, corner_y});
extent.grow(coord.x(), coord.y(), 0);
}
}
GDALClose(dataset);
return {std::move(projection), extent};
}
inline std::array<size_t, 2> read_tif_dimensions(const fs::path& path) {
ensure_gdal_initialized();
GDALDataset* dataset = static_cast<GDALDataset*>(GDALOpen(path.string().c_str(), GA_ReadOnly));
Assert(dataset, "Could not open raster " + path.string());
const size_t width = dataset->GetRasterXSize();
const size_t height = dataset->GetRasterYSize();
GDALClose(dataset);
return {{width, height}};
}
inline double flex_grid_value(const FlexGrid& band, size_t x, size_t y) {
if (!band.in_bounds({x, y})) {
return std::numeric_limits<double>::quiet_NaN();
}
switch (band.data_type()) {
case GDT_Float32:
return band.get<float>({static_cast<long long>(x), static_cast<long long>(y)});
case GDT_Float64:
return band.get<double>({static_cast<long long>(x), static_cast<long long>(y)});
case GDT_Int16:
return band.get<int16_t>({static_cast<long long>(x), static_cast<long long>(y)});
case GDT_UInt16:
return band.get<uint16_t>({static_cast<long long>(x), static_cast<long long>(y)});
case GDT_Byte:
return band.get<uint8_t>({static_cast<long long>(x), static_cast<long long>(y)});
default:
return band.get<float>({static_cast<long long>(x), static_cast<long long>(y)});
}
}
inline bool is_valid_elevation(double value) { return std::isfinite(value) && value > -1e10; }
inline double sample_grid_bilinear(const FlexGrid& band, double px, double py) {
if (band.width() == 0 || band.height() == 0) {
return std::numeric_limits<double>::quiet_NaN();
}
const long long x0 = std::clamp(static_cast<long long>(std::floor(px)), 0ll,
static_cast<long long>(band.width() - 1));
const long long y0 = std::clamp(static_cast<long long>(std::floor(py)), 0ll,
static_cast<long long>(band.height() - 1));
const long long x1 = std::min(x0 + 1, static_cast<long long>(band.width() - 1));
const long long y1 = std::min(y0 + 1, static_cast<long long>(band.height() - 1));
const double fx = px - static_cast<double>(x0);
const double fy = py - static_cast<double>(y0);
auto sample = [&](long long x, long long y) {
return flex_grid_value(band, static_cast<size_t>(x), static_cast<size_t>(y));
};
const double v00 = sample(x0, y0);
const double v10 = sample(x1, y0);
const double v01 = sample(x0, y1);
const double v11 = sample(x1, y1);
if (!is_valid_elevation(v00) && !is_valid_elevation(v10) && !is_valid_elevation(v01) &&
!is_valid_elevation(v11)) {
return std::numeric_limits<double>::quiet_NaN();
}
const auto replace_invalid = [](double value, double fallback) {
return is_valid_elevation(value) ? value : fallback;
};
const double v00_f = replace_invalid(v00, v10);
const double v10_f = replace_invalid(v10, v00_f);
const double v01_f = replace_invalid(v01, v00_f);
const double v11_f = replace_invalid(v11, v10_f);
const double top = v00_f + fx * (v10_f - v00_f);
const double bottom = v01_f + fx * (v11_f - v01_f);
return top + fy * (bottom - top);
}
// slope.tif stores byte values with flat terrain at 255 and vertical cliffs at 0.
struct DemMeshData {
std::vector<double> vertices;
std::vector<float> colors;
std::vector<float> texcoords;
std::vector<float> normals;
std::vector<unsigned int> indices;
Extent3D extent;
bool has_texture = false;
};
inline std::pair<float, float> texture_uv_from_projection(const GeoTransform& texture_transform,
size_t texture_width,
size_t texture_height,
const Coordinate2D<double>& coord) {
const Coordinate2D<double> tex_pixel = texture_transform.projection_to_pixel(coord);
if (texture_width <= 1 || texture_height <= 1) {
return {0.5f, 0.5f};
}
// Sample at pixel centres; flip V because GDAL rows grow downward while OpenGL v grows upward.
const float u = static_cast<float>((tex_pixel.x() + 0.5) / static_cast<double>(texture_width));
const float v =
static_cast<float>(1.0 - (tex_pixel.y() + 0.5) / static_cast<double>(texture_height));
return {std::clamp(u, 0.f, 1.f), std::clamp(v, 0.f, 1.f)};
}
inline void compute_terrain_normals_from_dem(DemMeshData& mesh,
const Geo<MultiBand<FlexGrid>>& dem_grid, int stride,
size_t cols, size_t rows) {
const size_t width = dem_grid.width();
const size_t height = dem_grid.height();
const double cell_x = std::abs(dem_grid.transform().dx()) * stride;
const double cell_y = std::abs(dem_grid.transform().dy()) * stride;
mesh.normals.assign(mesh.vertices.size(), 0.0f);
for (size_t row = 0; row < rows; ++row) {
for (size_t col = 0; col < cols; ++col) {
const size_t x = std::min(col * static_cast<size_t>(stride), width - 1);
const size_t y = std::min(row * static_cast<size_t>(stride), height - 1);
const size_t vi = (row * cols + col) * 3;
auto sample_z = [&](long long sx, long long sy) -> double {
const long long clamped_x = std::clamp(sx, 0ll, static_cast<long long>(width) - 1);
const long long clamped_y = std::clamp(sy, 0ll, static_cast<long long>(height) - 1);
const double z = flex_grid_value(dem_grid[0], static_cast<size_t>(clamped_x),
static_cast<size_t>(clamped_y));
return is_valid_elevation(z) ? z : 0.0;
};
const long long stride_ll = static_cast<long long>(stride);
const double z_w = sample_z(static_cast<long long>(x) - stride_ll, y);
const double z_e = sample_z(static_cast<long long>(x) + stride_ll, y);
const double z_n = sample_z(x, static_cast<long long>(y) - stride_ll);
const double z_s = sample_z(x, static_cast<long long>(y) + stride_ll);
const double dzdx = (z_e - z_w) / (2.0 * cell_x);
const double dzdy = (z_n - z_s) / (2.0 * cell_y);
float nx = static_cast<float>(-dzdx);
float ny = static_cast<float>(-dzdy);
float nz = 1.f;
const float len = std::sqrt(nx * nx + ny * ny + nz * nz);
if (len > 1e-8f) {
mesh.normals[vi] = nx / len;
mesh.normals[vi + 1] = ny / len;
mesh.normals[vi + 2] = nz / len;
} else {
mesh.normals[vi] = 0.f;
mesh.normals[vi + 1] = 0.f;
mesh.normals[vi + 2] = 1.f;
}
}
}
}
inline void compute_mesh_normals(DemMeshData& mesh) {
const size_t vertex_count = mesh.vertices.size() / 3;
mesh.normals.assign(mesh.vertices.size(), 0.0f);
auto accumulate = [&](unsigned int i0, unsigned int i1, unsigned int i2) {
if (i0 >= vertex_count || i1 >= vertex_count || i2 >= vertex_count) {
return;
}
const double* v0 = &mesh.vertices[i0 * 3];
const double* v1 = &mesh.vertices[i1 * 3];
const double* v2 = &mesh.vertices[i2 * 3];
const double e1x = v1[0] - v0[0];
const double e1y = v1[1] - v0[1];
const double e1z = v1[2] - v0[2];
const double e2x = v2[0] - v0[0];
const double e2y = v2[1] - v0[1];
const double e2z = v2[2] - v0[2];
const double nx = e1y * e2z - e1z * e2y;
const double ny = e1z * e2x - e1x * e2z;
const double nz = e1x * e2y - e1y * e2x;
for (unsigned int idx : {i0, i1, i2}) {
mesh.normals[idx * 3] += static_cast<float>(nx);
mesh.normals[idx * 3 + 1] += static_cast<float>(ny);
mesh.normals[idx * 3 + 2] += static_cast<float>(nz);
}
};
for (size_t i = 0; i + 2 < mesh.indices.size(); i += 3) {
accumulate(mesh.indices[i], mesh.indices[i + 1], mesh.indices[i + 2]);
}
for (size_t i = 0; i < vertex_count; ++i) {
float nx = mesh.normals[i * 3];
float ny = mesh.normals[i * 3 + 1];
float nz = mesh.normals[i * 3 + 2];
const float len = std::sqrt(nx * nx + ny * ny + nz * nz);
if (len > 1e-8f) {
mesh.normals[i * 3] = nx / len;
mesh.normals[i * 3 + 1] = ny / len;
mesh.normals[i * 3 + 2] = nz / len;
} else {
mesh.normals[i * 3] = 0.f;
mesh.normals[i * 3 + 1] = 0.f;
mesh.normals[i * 3 + 2] = 1.f;
}
}
}
inline DemMeshData build_dem_mesh(const Geo<MultiBand<FlexGrid>>& grid, int stride = 1,
const Geo<MultiBand<FlexGrid>>* texture = nullptr) {
DemMeshData mesh;
const size_t width = grid.width();
const size_t height = grid.height();
stride = std::max(1, stride);
double min_z = std::numeric_limits<double>::infinity();
double max_z = -std::numeric_limits<double>::infinity();
for (size_t y = 0; y < height; y += stride) {
for (size_t x = 0; x < width; x += stride) {
double z = flex_grid_value(grid[0], x, y);
if (is_valid_elevation(z)) {
min_z = std::min(min_z, z);
max_z = std::max(max_z, z);
}
}
}
double z_range = std::max(max_z - min_z, 1.0);
const auto& transform = grid.transform();
mesh.has_texture = texture != nullptr;
auto sample_texture = [&](size_t x, size_t y) -> std::array<float, 3> {
if (!texture) {
return {{0.5f, 0.7f, 0.4f}};
}
size_t tx = x * texture->width() / width;
size_t ty = y * texture->height() / height;
tx = std::min(tx, texture->width() - 1);
ty = std::min(ty, texture->height() - 1);
double r = flex_grid_value((*texture)[0], tx, ty) / 255.0;
double g = texture->size() > 1 ? flex_grid_value((*texture)[1], tx, ty) / 255.0 : r;
double b = texture->size() > 2 ? flex_grid_value((*texture)[2], tx, ty) / 255.0 : r;
return {{static_cast<float>(r), static_cast<float>(g), static_cast<float>(b)}};
};
const size_t cols = (width + stride - 1) / stride;
const size_t rows = (height + stride - 1) / stride;
auto vertex_index = [&](size_t col, size_t row) { return row * cols + col; };
for (size_t row = 0; row < rows; ++row) {
for (size_t col = 0; col < cols; ++col) {
size_t x = std::min(col * stride, width - 1);
size_t y = std::min(row * stride, height - 1);
Coordinate2D<double> coord = transform.pixel_to_projection({x + 0.5, y + 0.5});
double z = flex_grid_value(grid[0], x, y);
if (!is_valid_elevation(z)) {
z = min_z;
}
mesh.vertices.push_back(coord.x());
mesh.vertices.push_back(coord.y());
mesh.vertices.push_back(z);
if (mesh.has_texture) {
auto rgb = sample_texture(x, y);
mesh.colors.push_back(rgb[0]);
mesh.colors.push_back(rgb[1]);
mesh.colors.push_back(rgb[2]);
const auto grid_coord = [](size_t idx, size_t count) -> float {
return count <= 1 ? 0.f : static_cast<float>(idx) / static_cast<float>(count - 1);
};
mesh.texcoords.push_back(grid_coord(col, cols));
mesh.texcoords.push_back(
rows <= 1 ? 0.f : static_cast<float>(row) / static_cast<float>(rows - 1));
} else {
float t = static_cast<float>((z - min_z) / z_range);
mesh.colors.push_back(0.2f + 0.6f * t);
mesh.colors.push_back(0.5f + 0.3f * (1.0f - t));
mesh.colors.push_back(0.2f);
}
mesh.extent.grow(coord.x(), coord.y(), z);
}
}
for (size_t row = 0; row + 1 < rows; ++row) {
for (size_t col = 0; col + 1 < cols; ++col) {
unsigned int i0 = static_cast<unsigned int>(vertex_index(col, row));
unsigned int i1 = static_cast<unsigned int>(vertex_index(col + 1, row));
unsigned int i2 = static_cast<unsigned int>(vertex_index(col, row + 1));
unsigned int i3 = static_cast<unsigned int>(vertex_index(col + 1, row + 1));
mesh.indices.push_back(i0);
mesh.indices.push_back(i1);
mesh.indices.push_back(i2);
mesh.indices.push_back(i1);
mesh.indices.push_back(i3);
mesh.indices.push_back(i2);
}
}
compute_terrain_normals_from_dem(mesh, grid, stride, cols, rows);
return mesh;
}
inline DemMeshData build_flat_cell_dem_mesh(const Geo<MultiBand<FlexGrid>>& grid, int stride = 1) {
DemMeshData mesh;
const size_t width = grid.width();
const size_t height = grid.height();
if (width == 0 || height == 0) {
return mesh;
}
stride = std::max(1, stride);
double min_z = std::numeric_limits<double>::infinity();
double max_z = -std::numeric_limits<double>::infinity();
for (size_t y = 0; y < height; y += stride) {
for (size_t x = 0; x < width; x += stride) {
const double z = flex_grid_value(grid[0], x, y);
if (is_valid_elevation(z)) {
min_z = std::min(min_z, z);
max_z = std::max(max_z, z);
}
}
}
if (!std::isfinite(min_z)) {
min_z = 0.0;
max_z = 1.0;
}
const double z_range = std::max(max_z - min_z, 1.0);
const auto& transform = grid.transform();
const size_t cols = (width + stride - 1) / stride;
const size_t rows = (height + stride - 1) / stride;
mesh.vertices.reserve(cols * rows * 12);
mesh.colors.reserve(cols * rows * 12);
mesh.normals.reserve(cols * rows * 12);
mesh.indices.reserve(cols * rows * 6);
for (size_t row = 0; row < rows; ++row) {
for (size_t col = 0; col < cols; ++col) {
const size_t x = col * static_cast<size_t>(stride);
const size_t y = row * static_cast<size_t>(stride);
if (x >= width || y >= height) {
continue;
}
double z = flex_grid_value(grid[0], x, y);
if (!is_valid_elevation(z)) {
z = min_z;
}
// GDAL pixel-is-area: cell (x, y) spans pixel coords [x, x + stride) x [y, y + stride).
const double px0 = static_cast<double>(x);
const double py0 = static_cast<double>(y);
const double px1 = px0 + static_cast<double>(stride);
const double py1 = py0 + static_cast<double>(stride);
const Coordinate2D<double> corner00 = transform.pixel_to_projection({px0, py0});
const Coordinate2D<double> corner10 = transform.pixel_to_projection({px1, py0});
const Coordinate2D<double> corner11 = transform.pixel_to_projection({px1, py1});
const Coordinate2D<double> corner01 = transform.pixel_to_projection({px0, py1});
const float t = static_cast<float>((z - min_z) / z_range);
const std::array<float, 3> color{0.2f + 0.6f * t, 0.5f + 0.3f * (1.0f - t), 0.2f};
const unsigned int base_index = static_cast<unsigned int>(mesh.vertices.size() / 3);
const std::array<Coordinate2D<double>, 4> corners{corner00, corner10, corner11, corner01};
for (const Coordinate2D<double>& corner : corners) {
mesh.vertices.push_back(corner.x());
mesh.vertices.push_back(corner.y());
mesh.vertices.push_back(z);
mesh.colors.push_back(color[0]);
mesh.colors.push_back(color[1]);
mesh.colors.push_back(color[2]);
mesh.normals.push_back(0.f);
mesh.normals.push_back(0.f);
mesh.normals.push_back(1.f);
mesh.extent.grow(corner.x(), corner.y(), z);
}
mesh.indices.push_back(base_index);
mesh.indices.push_back(base_index + 1);
mesh.indices.push_back(base_index + 2);
mesh.indices.push_back(base_index);
mesh.indices.push_back(base_index + 2);
mesh.indices.push_back(base_index + 3);
}
}
return mesh;
}
inline DemMeshData build_textured_dem_mesh(const Geo<MultiBand<FlexGrid>>& dem_grid,
const Geo<MultiBand<FlexGrid>>& texture_grid,
int stride = 1,
OGRCoordinateTransformation* coord_transform = nullptr) {
DemMeshData mesh;
const size_t width = dem_grid.width();
const size_t height = dem_grid.height();
stride = std::max(1, stride);
double min_z = std::numeric_limits<double>::infinity();
double max_z = -std::numeric_limits<double>::infinity();
const auto& dem_transform = dem_grid.transform();
const auto& texture_transform = texture_grid.transform();
for (size_t y = 0; y < height; y += stride) {
for (size_t x = 0; x < width; x += stride) {
const double z = flex_grid_value(dem_grid[0], x, y);
if (is_valid_elevation(z)) {
min_z = std::min(min_z, z);
max_z = std::max(max_z, z);
}
}
}
if (!std::isfinite(min_z)) {
min_z = 0.0;
max_z = 1.0;
}
mesh.has_texture = true;
const size_t cols = (width + stride - 1) / stride;
const size_t rows = (height + stride - 1) / stride;
auto vertex_index = [&](size_t col, size_t row) { return row * cols + col; };
for (size_t row = 0; row < rows; ++row) {
for (size_t col = 0; col < cols; ++col) {
const size_t x = std::min(col * stride, width - 1);
const size_t y = std::min(row * stride, height - 1);
Coordinate2D<double> coord = dem_transform.pixel_to_projection({x + 0.5, y + 0.5});
double z = flex_grid_value(dem_grid[0], x, y);
if (!is_valid_elevation(z)) {
z = min_z;
}
double vx = coord.x();
double vy = coord.y();
if (coord_transform && !transform_xy_h(coord_transform, vx, vy)) {
Fail("Failed to reproject textured DEM mesh vertex.");
}
const auto [u, v] = texture_uv_from_projection(texture_transform, texture_grid.width(),
texture_grid.height(), coord);
mesh.vertices.push_back(vx);
mesh.vertices.push_back(vy);
mesh.vertices.push_back(z);
mesh.texcoords.push_back(u);
mesh.texcoords.push_back(v);
mesh.extent.grow(vx, vy, z);
}
}
for (size_t row = 0; row + 1 < rows; ++row) {
for (size_t col = 0; col + 1 < cols; ++col) {
unsigned int i0 = static_cast<unsigned int>(vertex_index(col, row));
unsigned int i1 = static_cast<unsigned int>(vertex_index(col + 1, row));
unsigned int i2 = static_cast<unsigned int>(vertex_index(col, row + 1));
unsigned int i3 = static_cast<unsigned int>(vertex_index(col + 1, row + 1));
mesh.indices.push_back(i0);
mesh.indices.push_back(i1);
mesh.indices.push_back(i2);
mesh.indices.push_back(i1);
mesh.indices.push_back(i3);
mesh.indices.push_back(i2);
}
}
compute_terrain_normals_from_dem(mesh, dem_grid, stride, cols, rows);
return mesh;
}
inline void reproject_dem_mesh_horizontal(DemMeshData& mesh, OGRCoordinateTransformation* ct) {
if (!ct) return;
mesh.extent = Extent3D();
for (size_t i = 0; i + 2 < mesh.vertices.size(); i += 3) {
double& x = mesh.vertices[i];
double& y = mesh.vertices[i + 1];
const double z = mesh.vertices[i + 2];
if (!transform_xy_h(ct, x, y)) {
Fail("Failed to reproject DEM mesh vertex.");
}
mesh.extent.grow(x, y, z);
}
}
inline DemMeshData build_slope_colored_mesh(const Geo<MultiBand<FlexGrid>>& slope_grid,
const Geo<MultiBand<FlexGrid>>& dem_grid,
int stride = 1) {
DemMeshData mesh;
const size_t width = slope_grid.width();
const size_t height = slope_grid.height();
stride = std::max(1, stride);
const auto& slope_transform = slope_grid.transform();
const auto& dem_transform = dem_grid.transform();
const size_t cols = (width + stride - 1) / stride;
const size_t rows = (height + stride - 1) / stride;
auto vertex_index = [&](size_t col, size_t row) { return row * cols + col; };
struct VertexScratch {
double x = 0.0;
double y = 0.0;
double z = 0.0;
float gray = 0.f;
bool valid_z = false;
};
std::vector<VertexScratch> scratch;
scratch.reserve(cols * rows);
double min_z = std::numeric_limits<double>::infinity();
double max_z = -std::numeric_limits<double>::infinity();
for (size_t row = 0; row < rows; ++row) {
for (size_t col = 0; col < cols; ++col) {
const size_t x = std::min(col * stride, width - 1);
const size_t y = std::min(row * stride, height - 1);
const Coordinate2D<double> coord = slope_transform.pixel_to_projection({x + 0.5, y + 0.5});
const Coordinate2D<double> dem_pixel = dem_transform.projection_to_pixel(coord);
const double z = sample_grid_bilinear(dem_grid[0], dem_pixel.x(), dem_pixel.y());
const double slope_byte = flex_grid_value(slope_grid[0], x, y);
VertexScratch vertex;
vertex.x = coord.x();
vertex.y = coord.y();
vertex.z = z;
vertex.gray = static_cast<float>(std::clamp(slope_byte, 0.0, 255.0) / 255.0);
vertex.valid_z = is_valid_elevation(z);
if (vertex.valid_z) {
min_z = std::min(min_z, z);
max_z = std::max(max_z, z);
}
scratch.push_back(vertex);
}
}
if (!std::isfinite(min_z)) {
min_z = 0.0;
max_z = 1.0;
}
for (const VertexScratch& vertex : scratch) {
const double z = vertex.valid_z ? vertex.z : min_z;
mesh.vertices.push_back(vertex.x);
mesh.vertices.push_back(vertex.y);
mesh.vertices.push_back(z);
mesh.colors.push_back(vertex.gray);
mesh.colors.push_back(vertex.gray);
mesh.colors.push_back(vertex.gray);
mesh.extent.grow(vertex.x, vertex.y, z);
}
for (size_t row = 0; row + 1 < rows; ++row) {
for (size_t col = 0; col + 1 < cols; ++col) {
unsigned int i0 = static_cast<unsigned int>(vertex_index(col, row));
unsigned int i1 = static_cast<unsigned int>(vertex_index(col + 1, row));
unsigned int i2 = static_cast<unsigned int>(vertex_index(col, row + 1));
unsigned int i3 = static_cast<unsigned int>(vertex_index(col + 1, row + 1));
mesh.indices.push_back(i0);
mesh.indices.push_back(i1);
mesh.indices.push_back(i2);
mesh.indices.push_back(i1);
mesh.indices.push_back(i3);
mesh.indices.push_back(i2);
}
}
compute_terrain_normals_from_dem(mesh, dem_grid, stride, cols, rows);
return mesh;
}
class AsyncRasterData {
mutable std::mutex m_mutex;
GeoProjection m_header_projection;
GeoProjection m_native_projection;
Extent3D m_header_extent;
std::optional<Geo<MultiBand<FlexGrid>>> m_grid;
std::optional<Geo<MultiBand<FlexGrid>>> m_texture;
DemMeshData m_mesh;
std::atomic<bool> m_ready{false};
std::future<void> m_future;
public:
AsyncRasterData(const fs::path& dem_path, AsyncProgressTracker async_progress_tracker,
const std::optional<fs::path>& texture_path = std::nullopt, int stride = 1,
bool slope_colored = false,
const std::optional<fs::path>& slope_path = std::nullopt,
std::function<void()> on_ready = {}, const std::string& target_crs_wkt = {},
bool flat_cells = false) {
TifMetadata metadata = read_tif_metadata(dem_path);
m_header_projection = std::move(metadata.projection);
m_native_projection = m_header_projection;
m_header_extent = metadata.extent;
if (!target_crs_wkt.empty()) {
if (auto ct = make_coord_transform(m_header_projection.to_string(), target_crs_wkt)) {
m_header_extent = reproject_extent3d_horizontal(m_header_extent, ct.get());
m_header_projection = make_projection_from_wkt(target_crs_wkt);
}
}
m_future =
std::async(std::launch::async, [this, dem_path, texture_path, slope_path, slope_colored,
flat_cells, stride, async_progress_tracker,
on_ready = std::move(on_ready), target_crs_wkt]() mutable {
ProgressTracker progress_tracker = async_progress_tracker.tracker()->subtracker(
0, 1.0, "load raster " + dem_path.filename().string());
const bool load_texture = texture_path.has_value();
const bool load_slope = slope_colored && slope_path.has_value();
const int read_count = 1 + (load_texture ? 1 : 0) + (load_slope ? 1 : 0);
const double slice = 1.0 / read_count;
double lo = 0.0;
auto dem = read_tif(dem_path, SUBTRACKER(lo, lo + slice));
lo += slice;
std::optional<Geo<MultiBand<FlexGrid>>> loaded_texture;
if (load_texture) {
loaded_texture = read_tif(*texture_path, SUBTRACKER(lo, lo + slice));
lo += slice;
}
std::unique_ptr<OGRCoordinateTransformation> coord_transform =
make_coord_transform(m_native_projection.to_string(), target_crs_wkt);
DemMeshData mesh;
if (flat_cells) {
mesh = build_flat_cell_dem_mesh(dem, stride);
reproject_dem_mesh_horizontal(mesh, coord_transform.get());
} else if (slope_colored && slope_path) {
auto slope = read_tif(*slope_path, SUBTRACKER(lo, 1.0));
mesh = build_slope_colored_mesh(slope, dem, stride);
reproject_dem_mesh_horizontal(mesh, coord_transform.get());
} else if (loaded_texture) {
mesh = build_textured_dem_mesh(dem, *loaded_texture, stride, coord_transform.get());
} else {
mesh = build_dem_mesh(dem, stride, nullptr);
reproject_dem_mesh_horizontal(mesh, coord_transform.get());
}
{
std::lock_guard<std::mutex> lock(m_mutex);
m_grid = std::move(dem);
if (loaded_texture) {
m_texture = std::move(*loaded_texture);
}
m_mesh = std::move(mesh);
m_ready.store(true, std::memory_order_release);
}
if (on_ready) {
on_ready();
}
});
}
std::mutex& mutex() const { return m_mutex; }
bool ready() const { return m_ready.load(std::memory_order_acquire); }
bool has_texture() const {
std::lock_guard<std::mutex> lock(m_mutex);
return m_texture.has_value();
}
// Caller must hold mutex().
const DemMeshData& mesh() const { return m_mesh; }
// Caller must hold mutex().
const Geo<MultiBand<FlexGrid>>& texture_grid() const {
Assert(m_texture.has_value(), "No texture loaded");
return *m_texture;
}
const GeoProjection& projection() const { return m_header_projection; }
const GeoProjection& native_projection() const { return m_native_projection; }
Extent3D extent() const {
if (m_ready && m_mesh.extent.max_extent() > 0) {
return m_mesh.extent;
}
return m_header_extent;
}
};