File layer.hpp
Go to the documentation of this file
#pragma once
#include <QMetaObject>
#include <QObject>
#include <algorithm>
#include <array>
#include <atomic>
#include <future>
#include <mutex>
#include <vector>
#include "contour/contour.hpp"
#include "gui/octree_las_data.hpp"
#include "gui/point_cloud_visualization.hpp"
#include "gui/raster_data.hpp"
#include "io/crs.hpp"
#include "io/gpkg.hpp"
#include "las/las_file.hpp"
#include "utilities/coordinate.hpp"
#include "utilities/filesystem.hpp"
#include "utilities/progress_tracker.hpp"
enum class LayerKind { PointCloud, DemSurface, Contours, SlopeSurface, TexturedDem };
class Layer : public QObject {
Q_OBJECT
protected:
std::string m_name;
LayerKind m_kind;
bool m_visible = true;
float m_opacity = 1.0f;
float m_vertical_offset = 0.0f;
signals:
void data_updated() const;
void visibility_changed(bool visible) const;
void opacity_changed() const;
void vertical_offset_changed() const;
public:
Layer(std::string name, LayerKind kind) : m_name(std::move(name)), m_kind(kind) {}
virtual Extent3D extent() const = 0;
virtual std::string projection() const = 0;
virtual std::string native_projection() const { return projection(); }
virtual std::string name() const { return m_name; }
LayerKind kind() const { return m_kind; }
bool visible() const { return m_visible; }
float opacity() const { return m_opacity; }
float vertical_offset() const { return m_vertical_offset; }
void set_visible(bool visible) {
if (m_visible != visible) {
m_visible = visible;
emit visibility_changed(visible);
}
}
void set_opacity(float opacity) {
const float clamped = std::clamp(opacity, 0.0f, 1.0f);
if (m_opacity != clamped) {
m_opacity = clamped;
emit opacity_changed();
}
}
void set_vertical_offset(float vertical_offset) {
if (m_vertical_offset != vertical_offset) {
m_vertical_offset = vertical_offset;
emit vertical_offset_changed();
}
}
virtual ~Layer() = default;
};
class PointLayer : public Layer {
public:
PointLayer(std::string name) : Layer(std::move(name), LayerKind::PointCloud) {}
virtual ~PointLayer() = default;
};
class LASLayer : public PointLayer {
Q_OBJECT
fs::path m_file_path;
AsyncOctreeLASData m_las_data;
float m_point_radius_m = 0.15f;
float m_point_alpha = 1.0f;
float m_point_stream_budget_ms = 30.0f;
float m_lod_quality_multiplier = 1.0f;
size_t m_max_points_per_node = 32'000;
PointColorMode m_point_color_mode = PointColorMode::File;
std::array<uint8_t, 3> m_fixed_point_color = {{217, 230, 255}};
signals:
void point_size_changed() const;
void point_colors_changed() const;
void point_opacity_changed() const;
void point_stream_budget_changed() const;
void lod_settings_changed() const;
public:
explicit LASLayer(const fs::path& file, AsyncProgressTracker progress_tracker,
const std::string& target_crs_wkt = {})
: PointLayer(file.filename().string()),
m_file_path(file),
m_las_data(file, progress_tracker, {[this] {
QMetaObject::invokeMethod(this, "notify_data_updated", Qt::QueuedConnection);
}},
target_crs_wkt) {
if (!m_las_data.point_format_has_rgb()) {
m_point_color_mode = PointColorMode::Classification;
}
}
const fs::path& file_path() const { return m_file_path; }
float point_radius_m() const { return m_point_radius_m; }
float point_alpha() const { return m_point_alpha; }
float point_stream_budget_ms() const { return m_point_stream_budget_ms; }
float lod_quality_multiplier() const { return m_lod_quality_multiplier; }
size_t max_points_per_node() const { return m_max_points_per_node; }
PointColorMode point_color_mode() const { return m_point_color_mode; }
const std::array<uint8_t, 3>& fixed_point_color() const { return m_fixed_point_color; }
void set_point_radius_m(float radius_m) {
const float clamped = std::clamp(radius_m, 0.008f, 1.2f);
if (m_point_radius_m != clamped) {
m_point_radius_m = clamped;
emit point_size_changed();
}
}
void set_point_alpha(float alpha) {
const float clamped = std::clamp(alpha, 0.0f, 1.0f);
if (m_point_alpha != clamped) {
m_point_alpha = clamped;
emit point_opacity_changed();
}
}
void set_point_stream_budget_ms(float ms) {
const float clamped = std::clamp(ms, 8.0f, 200.0f);
if (m_point_stream_budget_ms != clamped) {
m_point_stream_budget_ms = clamped;
emit point_stream_budget_changed();
}
}
void set_lod_quality_multiplier(float multiplier) {
const float clamped = std::clamp(multiplier, 0.1f, 10.0f);
if (m_lod_quality_multiplier != clamped) {
m_lod_quality_multiplier = clamped;
emit lod_settings_changed();
}
}
void set_max_points_per_node(size_t max_points) {
const size_t clamped = std::clamp(max_points, size_t{1000}, size_t{100'000});
if (m_max_points_per_node != clamped) {
m_max_points_per_node = clamped;
emit lod_settings_changed();
}
}
void set_point_color_mode(PointColorMode mode) {
if (m_point_color_mode != mode) {
m_point_color_mode = mode;
emit point_colors_changed();
}
}
void set_fixed_point_color(const std::array<uint8_t, 3>& color) {
if (m_fixed_point_color != color) {
m_fixed_point_color = color;
if (m_point_color_mode == PointColorMode::Fixed) {
emit point_colors_changed();
}
}
}
virtual Extent3D extent() const override { return m_las_data.bounds(); }
virtual std::string projection() const override { return m_las_data.projection().to_string(); }
virtual std::string native_projection() const override {
return m_las_data.native_projection().to_string();
}
const AsyncOctreeLASData& las_data() const { return m_las_data; }
AsyncOctreeLASData& las_data() { return m_las_data; }
private slots:
void notify_data_updated() {
if (m_las_data.load_complete() && m_las_data.point_format_has_rgb() &&
!m_las_data.has_rgb_data() && m_point_color_mode == PointColorMode::File) {
set_point_color_mode(PointColorMode::Classification);
}
emit data_updated();
}
};
class DemLayer : public Layer {
Q_OBJECT
AsyncRasterData m_raster;
public:
DemLayer(const fs::path& dem_path, AsyncProgressTracker progress_tracker,
const std::optional<fs::path>& texture_path = std::nullopt,
const std::string& target_crs_wkt = {}, bool flat_cells = false)
: Layer(flat_cells ? dem_path.stem().string() + " (grid)" : dem_path.filename().string(),
LayerKind::DemSurface),
m_raster(
dem_path, progress_tracker, texture_path, 1, false, std::nullopt,
[this] {
QMetaObject::invokeMethod(this, "notify_data_updated", Qt::QueuedConnection);
},
target_crs_wkt, flat_cells) {}
virtual Extent3D extent() const override { return m_raster.extent(); }
virtual std::string projection() const override { return m_raster.projection().to_string(); }
virtual std::string native_projection() const override {
return m_raster.native_projection().to_string();
}
const AsyncRasterData& raster() const { return m_raster; }
AsyncRasterData& raster() { return m_raster; }
private slots:
void notify_data_updated() { emit data_updated(); }
};
class SlopeLayer : public Layer {
Q_OBJECT
AsyncRasterData m_raster;
public:
SlopeLayer(const fs::path& dem_path, const fs::path& slope_path,
AsyncProgressTracker progress_tracker, const std::string& target_crs_wkt = {})
: Layer(slope_path.filename().string(), LayerKind::SlopeSurface),
m_raster(
dem_path, progress_tracker, std::nullopt, 1, true, slope_path,
[this] {
QMetaObject::invokeMethod(this, "notify_data_updated", Qt::QueuedConnection);
},
target_crs_wkt) {}
virtual Extent3D extent() const override { return m_raster.extent(); }
virtual std::string projection() const override { return m_raster.projection().to_string(); }
virtual std::string native_projection() const override {
return m_raster.native_projection().to_string();
}
const AsyncRasterData& raster() const { return m_raster; }
AsyncRasterData& raster() { return m_raster; }
private slots:
void notify_data_updated() { emit data_updated(); }
};
class TexturedDemLayer : public Layer {
Q_OBJECT
AsyncRasterData m_raster;
public:
TexturedDemLayer(const fs::path& dem_path, const fs::path& texture_path,
AsyncProgressTracker progress_tracker, const std::string& target_crs_wkt = {})
: Layer(texture_path.filename().string(), LayerKind::TexturedDem),
m_raster(
dem_path, progress_tracker, texture_path, 1, false, std::nullopt,
[this] {
QMetaObject::invokeMethod(this, "notify_data_updated", Qt::QueuedConnection);
},
target_crs_wkt) {}
virtual Extent3D extent() const override { return m_raster.extent(); }
virtual std::string projection() const override { return m_raster.projection().to_string(); }
virtual std::string native_projection() const override {
return m_raster.native_projection().to_string();
}
const AsyncRasterData& raster() const { return m_raster; }
AsyncRasterData& raster() { return m_raster; }
private slots:
void notify_data_updated() { emit data_updated(); }
};
class ContourLayer : public Layer {
Q_OBJECT
mutable std::mutex m_mutex;
std::vector<Contour> m_contours;
Extent3D m_extent;
std::string m_projection;
std::string m_native_projection;
std::atomic<bool> m_ready{false};
std::future<void> m_future;
public:
ContourLayer(const fs::path& gpkg_path, AsyncProgressTracker async_progress_tracker,
const std::string& target_crs_wkt = {})
: Layer(gpkg_path.filename().string(), LayerKind::Contours) {
ensure_gdal_initialized();
GDALDataset* header_dataset = (GDALDataset*)GDALOpenEx(
gpkg_path.string().c_str(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr);
if (header_dataset) {
m_native_projection =
make_projection_from_wkt(std::string(header_dataset->GetProjectionRef())).to_string();
GDALClose(header_dataset);
}
m_projection = target_crs_wkt.empty() ? m_native_projection : target_crs_wkt;
m_future = std::async(
std::launch::async, [this, gpkg_path, async_progress_tracker, target_crs_wkt]() mutable {
ProgressTracker progress_tracker = async_progress_tracker.tracker()->subtracker(
0, 1.0, "load contours " + gpkg_path.filename().string());
auto contours = read_gpkg(gpkg_path, std::move(progress_tracker));
const std::string native_projection = m_native_projection;
std::unique_ptr<OGRCoordinateTransformation> coord_transform =
make_coord_transform(native_projection, target_crs_wkt);
if (coord_transform) {
for (auto& contour : contours) {
for (auto& pt : contour.points()) {
double x = pt.x();
double y = pt.y();
if (!transform_xy_h(coord_transform.get(), x, y)) {
Fail("Failed to reproject contour vertex.");
}
pt = Coordinate2D<double>(x, y);
}
}
}
Extent3D extent;
for (const auto& contour : contours) {
for (const auto& pt : contour.points()) {
extent.grow(pt.x(), pt.y(), contour.height());
}
}
{
std::lock_guard<std::mutex> lock(m_mutex);
m_contours = std::move(contours);
m_projection = coord_transform ? target_crs_wkt : native_projection;
m_extent = std::move(extent);
m_ready.store(true, std::memory_order_release);
}
QMetaObject::invokeMethod(this, "notify_data_updated", Qt::QueuedConnection);
});
}
private slots:
void notify_data_updated() { emit data_updated(); }
public:
virtual Extent3D extent() const override {
std::lock_guard<std::mutex> lock(m_mutex);
return m_extent;
}
virtual std::string projection() const override {
std::lock_guard<std::mutex> lock(m_mutex);
return m_projection;
}
virtual std::string native_projection() const override {
std::lock_guard<std::mutex> lock(m_mutex);
return m_native_projection;
}
bool ready() const { return m_ready.load(std::memory_order_acquire); }
std::vector<Contour> copy_contours() const {
std::lock_guard<std::mutex> lock(m_mutex);
return m_contours;
}
const std::vector<Contour>& contours() const {
std::lock_guard<std::mutex> lock(m_mutex);
return m_contours;
}
};