From bd322bf18e1136e54e2d73b6f085bf8cffb54eea Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Wed, 22 Jul 2026 10:59:00 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B8=B8=E8=A7=84=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + main.cmake | 12 +- module/osg_earth.ps1 | 4 +- module/test_osg_earth/main.cpp | 797 ++++++++++++++++++++++++++++----- 4 files changed, 701 insertions(+), 113 deletions(-) diff --git a/.gitignore b/.gitignore index 8e69e07..5619435 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /.idea +/tile_cache/ diff --git a/main.cmake b/main.cmake index 79f4464..5aca9e8 100644 --- a/main.cmake +++ b/main.cmake @@ -28,15 +28,21 @@ endblock() #set(osgEarth_DIR "D:/ae/cached_external_library/vs2019_Debug_global/install/osgearth/lib/cmake/osgearth") block() - set(rely osgearth) - rcl_load_dependency_environment(osg_earth ${rely}) + set(rely osg_earth::osgearth) + rcl_load_dependency_environment(${rely}) set(dir module/test_osg_earth) file(GLOB_RECURSE srcs ${dir}/*.qrc ${dir}/*.c ${dir}/*.h ${dir}/*.cpp ${dir}/*.hpp) add_executable(test_osg_earth ${srcs}) + target_compile_definitions(test_osg_earth PRIVATE QT_MAPLIBRE_STATIC WIN32_LEAN_AND_MEAN NOMINMAX) find_package(osgEarth CONFIG REQUIRED) + target_compile_definitions(test_osg_earth PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT) target_link_libraries(test_osg_earth PRIVATE osgEarth::osgEarth) if(MSVC) target_compile_options(test_osg_earth PRIVATE /utf-8) endif() - rcl_unload_dependency_environment(osg_earth ${rely}) + + target_link_libraries(test_osg_earth PRIVATE Qt5::Widgets QMapLibre::Widgets) + find_package(OpenSSL REQUIRED) + target_link_libraries(test_osg_earth PRIVATE OpenSSL::SSL OpenSSL::Crypto) + rcl_unload_dependency_environment(${rely}) endblock() diff --git a/module/osg_earth.ps1 b/module/osg_earth.ps1 index 2c4b8b7..98cc3cc 100644 --- a/module/osg_earth.ps1 +++ b/module/osg_earth.ps1 @@ -15,5 +15,7 @@ $geos_bin = "$base\geos\bin" $jpeg_bin = "$base\jpeg\bin" $tiff_bin = "$base\tiff\bin" $png_bin = "$base\png\bin" +$freetype_bin = "$base\freetype\bin" -$env:Path = "$png_bin;$tiff_bin;$jpeg_bin;$geos_bin;$zlib_bin;$proj_bin;$curl_bin;$gdal_bin;$osgearth_bin;$osg_bin;$env:Path" + +$env:Path = "$freetype_bin;$png_bin;$tiff_bin;$jpeg_bin;$geos_bin;$zlib_bin;$proj_bin;$curl_bin;$gdal_bin;$osgearth_bin;$osg_bin;$env:Path" diff --git a/module/test_osg_earth/main.cpp b/module/test_osg_earth/main.cpp index f06534e..72d08d1 100644 --- a/module/test_osg_earth/main.cpp +++ b/module/test_osg_earth/main.cpp @@ -1,105 +1,658 @@ +#ifndef CPPHTTPLIB_OPENSSL_SUPPORT +#define CPPHTTPLIB_OPENSSL_SUPPORT +#endif +#include "httplib.h" +#include +#include +#include #include +#include +#include +#include #include +#include +#include #include +#include +#include +#include #include #include +#include #include -#include +#include +#include +#include +#include #include +#include #include -#include +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#include +#endif #include #include #include +#include +#include #include +#include #include -#ifdef _WIN32 -#include -#endif -namespace ui = osgEarth::Util::Controls; +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class Topmost_Combo_Box : public QComboBox { +public: + explicit Topmost_Combo_Box(QWidget* parent = nullptr) : QComboBox(parent) { + setView(new QListView(this)); + setMaxVisibleItems(18); + view()->setTextElideMode(Qt::ElideRight); + } + void showPopup() override { + QWidget* popup = view()->window(); + popup->setWindowFlags(Qt::Popup | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); + QComboBox::showPopup(); + popup->raise(); + popup->activateWindow(); + if (popup->windowHandle()) { + popup->windowHandle()->raise(); + } + } +}; struct Tile_Source { std::string name; + std::string cache_key; std::string url; + std::string extension; + std::string content_type; + std::string elevation_encoding; + unsigned int max_level; }; -class Tile_Source_Controller { +std::string replace_all(std::string value, const std::string& from, const std::string& to) { + std::size_t pos = 0; + while ((pos = value.find(from, pos)) != std::string::npos) { + value.replace(pos, from.size(), to); + pos += to.size(); + } + return value; +} +osgEarth::ProfileOptions spherical_mercator_profile_options() { + return osgEarth::Profile::create(osgEarth::Profile::SPHERICAL_MERCATOR)->toProfileOptions(); +} +osgEarth::MapNode::Options create_map_node_options() { + osgEarth::TerrainOptions terrain_options; + terrain_options.tileSize() = 33; + terrain_options.maxLOD() = 15u; + terrain_options.minLOD() = 0u; + terrain_options.firstLOD() = 0u; + terrain_options.enableLighting() = true; + terrain_options.useNormalMaps() = false; + terrain_options.normalizeEdges() = true; + terrain_options.morphTerrain() = true; + terrain_options.morphImagery() = true; + terrain_options.screenSpaceError() = 1.0f; + osgEarth::MapNode::Options options; + options.enableLighting() = true; + options.screenSpaceError() = 2.0f; + options.terrain() = terrain_options; + return options; +} +void print_versions() { + std::cout << "osgEarth header: " << OSGEARTH_MAJOR_VERSION << "." << OSGEARTH_MINOR_VERSION << "." << OSGEARTH_PATCH_VERSION << "\n"; + std::cout << "osgEarth runtime: " << osgEarthGetVersion() << "\n"; + std::cout << "osgEarth soversion: " << osgEarthGetSOVersion() << "\n"; + std::cout << "OSG runtime: " << osgGetVersion() << "\n"; +} +QString tile_source_name(const Tile_Source& source) { + return QString::fromStdString(source.name); +} +QString format_scale(float value) { + return QString("Terrain scale: %1x").arg(static_cast(value), 0, 'f', 2); +} +const char* terrain_vertical_scale_shader() { + return "#pragma version 330\n" + "#pragma vp_location vertex_model\n" + "#pragma vp_entryPoint terrainVerticalScale\n" + "uniform float u_terrain_vertical_scale;\n" + "vec3 vp_Normal;\n" + "float oe_terrain_getElevation();\n" + "void terrainVerticalScale(inout vec4 vertex)\n" + "{\n" + " float elevation = oe_terrain_getElevation();\n" + " vertex.xyz += vp_Normal * elevation * (u_terrain_vertical_scale - 1.0);\n" + "}\n"; +} +class Terrain_Scale_Controller { public: - explicit Tile_Source_Controller(osgEarth::Map* map) : map_(map) {} - void add_source(Tile_Source source, bool visible) { - osg::ref_ptr layer = new osgEarth::XYZImageLayer(); - layer->setName(source.name); - layer->setURL(source.url); - layer->setProfile(osgEarth::Profile::create(osgEarth::Profile::SPHERICAL_MERCATOR)); - layer->setVisible(visible); - map_->addLayer(layer.get()); + Terrain_Scale_Controller(osg::Node* node, float scale) : scale_(scale) { + auto* state_set = node->getOrCreateStateSet(); + auto* vp = osgEarth::VirtualProgram::getOrCreate(state_set); + vp->setFunction("terrainVerticalScale", terrain_vertical_scale_shader(), osgEarth::VirtualProgram::LOCATION_VERTEX_MODEL); + uniform_ = new osg::Uniform(osg::Uniform::FLOAT, "u_terrain_vertical_scale"); + state_set->addUniform(uniform_.get()); + set_scale(scale_); + } + void set_status(QLabel* status) { + status_ = status; + status_->setText(format_scale(scale_)); + } + void set_scale(float scale) { + scale_ = scale; + uniform_->set(scale_); + if (status_) { + status_->setText(format_scale(scale_)); + } + } + [[nodiscard]] float scale() const { + return scale_; + } +private: + float scale_; + osg::ref_ptr uniform_; + QLabel* status_ = nullptr; +}; +const std::string& zero_terrarium_png() { + static const unsigned char data[] = { + 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, + 0, 0, 1, 0, 0, 0, 1, 0, 8, 6, 0, 0, 0, 92, 114, 168, + 102, 0, 0, 2, 92, 73, 68, 65, 84, 120, 218, 237, 212, 65, 1, 0, + 48, 8, 196, 176, 99, 202, 113, 14, 6, 230, 128, 68, 66, 31, 173, 78, + 38, 192, 73, 79, 2, 48, 0, 192, 0, 0, 3, 0, 12, 0, 48, 0, + 192, 0, 0, 3, 0, 12, 0, 48, 0, 192, 0, 0, 3, 0, 12, 0, + 48, 0, 192, 0, 0, 3, 0, 12, 0, 48, 0, 192, 0, 0, 3, 0, + 12, 0, 48, 0, 192, 0, 0, 3, 0, 12, 0, 48, 0, 192, 0, 0, + 3, 0, 12, 0, 48, 0, 192, 0, 0, 3, 0, 12, 0, 48, 0, 192, + 0, 0, 3, 0, 12, 0, 12, 0, 48, 0, 192, 0, 0, 3, 0, 12, + 0, 48, 0, 192, 0, 0, 3, 0, 12, 0, 48, 0, 192, 0, 0, 3, + 0, 12, 0, 48, 0, 192, 0, 0, 3, 0, 12, 0, 48, 0, 192, 0, + 0, 3, 0, 12, 0, 48, 0, 192, 0, 0, 3, 0, 12, 0, 48, 0, + 192, 0, 0, 3, 0, 12, 0, 48, 0, 192, 0, 0, 3, 0, 12, 0, + 48, 0, 192, 0, 0, 3, 0, 12, 0, 12, 0, 48, 0, 192, 0, 0, + 3, 0, 12, 0, 48, 0, 192, 0, 0, 3, 0, 12, 0, 48, 0, 192, + 0, 0, 3, 0, 12, 0, 48, 0, 192, 0, 0, 3, 0, 12, 0, 48, + 0, 192, 0, 0, 3, 0, 12, 0, 48, 0, 192, 0, 0, 3, 0, 12, + 0, 48, 0, 192, 0, 0, 3, 0, 12, 0, 48, 0, 192, 0, 0, 3, + 0, 12, 0, 48, 0, 192, 0, 0, 3, 0, 12, 0, 12, 0, 48, 0, + 192, 0, 0, 3, 0, 12, 0, 48, 0, 192, 0, 0, 3, 0, 12, 0, + 48, 0, 192, 0, 0, 3, 0, 12, 0, 48, 0, 192, 0, 0, 3, 0, + 12, 0, 48, 0, 192, 0, 0, 3, 0, 12, 0, 48, 0, 192, 0, 0, + 3, 0, 12, 0, 48, 0, 192, 0, 0, 3, 0, 12, 0, 48, 0, 192, + 0, 0, 3, 0, 12, 0, 48, 0, 192, 0, 0, 3, 0, 12, 0, 12, + 0, 48, 0, 192, 0, 0, 3, 0, 12, 0, 48, 0, 192, 0, 0, 3, + 0, 12, 0, 48, 0, 192, 0, 0, 3, 0, 12, 0, 48, 0, 192, 0, + 0, 3, 0, 12, 0, 48, 0, 192, 0, 0, 3, 0, 12, 0, 48, 0, + 192, 0, 0, 3, 0, 12, 0, 48, 0, 192, 0, 0, 3, 0, 12, 0, + 48, 0, 192, 0, 0, 3, 0, 12, 0, 48, 0, 192, 0, 0, 3, 0, + 12, 0, 12, 0, 48, 0, 192, 0, 0, 3, 0, 12, 0, 48, 0, 192, + 0, 0, 3, 0, 12, 0, 48, 0, 192, 0, 0, 3, 0, 12, 0, 48, + 0, 192, 0, 0, 3, 0, 12, 0, 48, 0, 192, 0, 0, 3, 0, 12, + 0, 48, 0, 192, 0, 0, 3, 0, 12, 0, 48, 0, 192, 0, 0, 3, + 0, 12, 0, 48, 0, 192, 0, 0, 3, 0, 12, 0, 48, 0, 192, 0, + 0, 3, 0, 12, 0, 12, 0, 48, 0, 192, 0, 0, 3, 0, 12, 0, + 48, 0, 192, 0, 0, 3, 0, 12, 0, 48, 0, 192, 0, 0, 3, 0, + 12, 0, 48, 0, 192, 0, 0, 3, 0, 12, 0, 48, 0, 192, 0, 0, + 3, 0, 12, 0, 48, 0, 192, 0, 0, 3, 0, 12, 0, 48, 0, 192, + 0, 0, 3, 0, 12, 0, 48, 0, 192, 0, 0, 3, 0, 12, 0, 248, + 89, 17, 177, 3, 127, 196, 120, 103, 46, 0, 0, 0, 0, 73, 69, 78, + 68, 174, 66, 96, 130 + }; + static const std::string content(reinterpret_cast(data), sizeof(data)); + return content; +} +class Tile_Cache_Server { +public: + Tile_Cache_Server(std::filesystem::path cache_dir, std::vector imagery_sources, std::vector elevation_sources) + : cache_dir_(std::move(cache_dir)), imagery_sources_(std::move(imagery_sources)), elevation_sources_(std::move(elevation_sources)) {} + void start() { + std::filesystem::create_directories(cache_dir_); + server_.Get(R"(/imagery/(\d+)/(\d+)/(\d+)/(\d+)\.([A-Za-z0-9]+))", [this](const httplib::Request& req, httplib::Response& res) { + serve_tile(req, res, imagery_sources_, "imagery"); + }); + server_.Get(R"(/elevation/(\d+)/(\d+)/(\d+)/(\d+)\.([A-Za-z0-9]+))", [this](const httplib::Request& req, httplib::Response& res) { + serve_tile(req, res, elevation_sources_, "elevation"); + }); + thread_ = std::thread([this] { + server_.listen("127.0.0.1", 18080); + }); + } + void stop() { + server_.stop(); + if (thread_.joinable()) { + thread_.join(); + } + } +private: + void serve_tile(const httplib::Request& req, httplib::Response& res, const std::vector& sources, const std::string& category) { + const std::size_t source_index = std::stoull(req.matches[1].str()); + if (source_index >= sources.size()) { + res.status = 404; + return; + } + const Tile_Source& source = sources[source_index]; + if (req.matches[5].str() != source.extension) { + res.status = 404; + return; + } + const std::string z = req.matches[2].str(); + const std::string x = req.matches[3].str(); + const std::string y = req.matches[4].str(); + const auto cache_file = cache_dir_ / category / source.cache_key / z / x / (y + "." + source.extension); + std::lock_guard lock(cache_locks_[std::hash{}(cache_file.string()) % cache_locks_.size()]); + if (std::filesystem::exists(cache_file)) { + std::ifstream input(cache_file, std::ios::binary); + std::string content((std::istreambuf_iterator(input)), std::istreambuf_iterator()); + res.set_content(std::move(content), source.content_type); + return; + } + std::string remote_url = replace_all(source.url, "{z}", z); + remote_url = replace_all(remote_url, "{x}", x); + remote_url = replace_all(remote_url, "{y}", y); + constexpr std::string_view https_prefix = "https://"; + const std::size_t path_begin = remote_url.find('/', https_prefix.size()); + if (remote_url.compare(0, https_prefix.size(), https_prefix.data(), https_prefix.size()) != 0 || path_begin == std::string::npos) { + res.status = 500; + res.set_content("unsupported upstream URL", "text/plain"); + return; + } + const std::string host = remote_url.substr(https_prefix.size(), path_begin - https_prefix.size()); + const std::string path = remote_url.substr(path_begin); + httplib::SSLClient client(host); + client.set_follow_location(true); + client.set_connection_timeout(5, 0); + client.set_read_timeout(20, 0); + const httplib::Headers headers{{"User-Agent", "osgEarth-terrain-viewer/1.0"}, {"Accept", "image/avif,image/webp,image/apng,image/*,*/*;q=0.8"}}; + auto result = client.Get(path, headers); + if (!result) { + if (category == "elevation") { + res.set_content(zero_terrarium_png(), "image/png"); + return; + } + res.status = 502; + res.set_content("upstream connection failed", "text/plain"); + return; + } + if (result->status != 200) { + if (category == "elevation") { + res.set_content(zero_terrarium_png(), "image/png"); + return; + } + res.status = result->status; + res.set_content("upstream status: " + std::to_string(result->status), "text/plain"); + return; + } + std::filesystem::create_directories(cache_file.parent_path()); + const auto temporary_file = cache_file.string() + ".tmp"; + { + std::ofstream output(temporary_file, std::ios::binary | std::ios::trunc); + output.write(result->body.data(), static_cast(result->body.size())); + } + std::filesystem::rename(temporary_file, cache_file); + res.set_content(result->body, result->get_header_value("Content-Type", source.content_type.c_str())); + } + std::filesystem::path cache_dir_; + std::vector imagery_sources_; + std::vector elevation_sources_; + httplib::Server server_; + std::thread thread_; + std::array cache_locks_; +}; + +class Imagery_Source_Controller { +public: + explicit Imagery_Source_Controller(osgEarth::Map* map) : map_(map) {} + void add_source(Tile_Source source) { sources_.emplace_back(std::move(source)); - layers_.emplace_back(std::move(layer)); } void select(std::size_t index) { - for (std::size_t i = 0; i < layers_.size(); ++i) { - layers_[i]->setVisible(i == index); + if (active_index_ == index) { + return; + } + if (active_layer_) { + map_->removeLayer(active_layer_.get()); + } + const Tile_Source& source = sources_[index]; + osgEarth::XYZImageLayer::Options options; + options.url() = "http://127.0.0.1:18080/imagery/" + std::to_string(index) + "/{z}/{x}/{y}." + source.extension; + options.format() = source.extension; + options.profile() = spherical_mercator_profile_options(); + options.minLevel() = 0u; + options.maxLevel() = source.max_level; + options.maxDataLevel() = source.max_level; + active_layer_ = new osgEarth::XYZImageLayer(options); + active_layer_->setName(source.name); + map_->addLayer(active_layer_.get()); + active_index_ = index; + if (status_) { + status_->setText(QString("Imagery: %1").arg(tile_source_name(source))); } - status_->setText(std::string("Current: ") + sources_[index].name); } - void set_status(ui::LabelControl* status) { + void shutdown() { + if (active_layer_) { + map_->removeLayer(active_layer_.get()); + active_layer_ = nullptr; + } + active_index_ = std::numeric_limits::max(); + status_ = nullptr; + } + void set_status(QLabel* status) { status_ = status; } [[nodiscard]] std::size_t size() const { return sources_.size(); } - [[nodiscard]] const std::string& name(std::size_t index) const { - return sources_[index].name; + [[nodiscard]] const Tile_Source& source(std::size_t index) const { + return sources_[index]; } private: osgEarth::Map* map_; std::vector sources_; - std::vector> layers_; - ui::LabelControl* status_; + osg::ref_ptr active_layer_; + std::size_t active_index_ = std::numeric_limits::max(); + QLabel* status_ = nullptr; }; -class Select_Tile_Source_Handler : public ui::ControlEventHandler { +class Elevation_Source_Controller { public: - Select_Tile_Source_Handler(Tile_Source_Controller& controller, std::size_t index) - : controller_(controller), index_(index) {} - void onClick(ui::Control*) { - controller_.select(index_); + explicit Elevation_Source_Controller(osgEarth::Map* map) : map_(map) {} + void add_source(Tile_Source source) { + sources_.emplace_back(std::move(source)); + } + void select(std::size_t index) { + if (active_index_ == index) { + return; + } + if (active_layer_) { + map_->removeLayer(active_layer_.get()); + } + const Tile_Source& source = sources_[index]; + osgEarth::XYZElevationLayer::Options options; + options.url() = "http://127.0.0.1:18080/elevation/" + std::to_string(index) + "/{z}/{x}/{y}." + source.extension; + options.format() = source.extension; + options.profile() = spherical_mercator_profile_options(); + options.minLevel() = 0u; + options.maxLevel() = source.max_level; + options.maxDataLevel() = source.max_level; + options.elevationEncoding() = source.elevation_encoding; + options.stitchEdges() = true; + active_layer_ = new osgEarth::XYZElevationLayer(options); + active_layer_->setName(source.name); + map_->addLayer(active_layer_.get()); + active_index_ = index; + if (status_) { + status_->setText(QString("Elevation: %1").arg(tile_source_name(source))); + } + } + void disable() { + if (active_layer_) { + map_->removeLayer(active_layer_.get()); + active_layer_ = nullptr; + } + active_index_ = std::numeric_limits::max(); + if (status_) { + status_->setText("Elevation: disabled"); + } + } + void shutdown() { + if (active_layer_) { + map_->removeLayer(active_layer_.get()); + active_layer_ = nullptr; + } + active_index_ = std::numeric_limits::max(); + status_ = nullptr; + } + void set_status(QLabel* status) { + status_ = status; + } + [[nodiscard]] std::size_t size() const { + return sources_.size(); + } + [[nodiscard]] const Tile_Source& source(std::size_t index) const { + return sources_[index]; } private: - Tile_Source_Controller& controller_; - std::size_t index_; + osgEarth::Map* map_; + std::vector sources_; + osg::ref_ptr active_layer_; + std::size_t active_index_ = std::numeric_limits::max(); + QLabel* status_ = nullptr; }; -ui::Control* create_tile_source_panel(Tile_Source_Controller& controller) { - auto* grid = new ui::Grid(); - grid->setBackColor(0.0f, 0.0f, 0.0f, 0.72f); - grid->setPadding(10.0f); - grid->setChildSpacing(6.0f); - grid->setAbsorbEvents(true); - unsigned int row = 0; - grid->setControl(0, row++, new ui::LabelControl("Tile source")); - for (std::size_t i = 0; i < controller.size(); ++i) { - auto* button = grid->setControl( - 0, - row++, - new ui::ButtonControl( - controller.name(i), - new Select_Tile_Source_Handler(controller, i) - ) - ); - button->setHorizFill(true, 220.0f); +class Osg_Viewer_Widget : public QWidget { +public: + explicit Osg_Viewer_Widget(osgViewer::Viewer& viewer, QWidget* parent = nullptr) : QWidget(parent), viewer_(viewer) { + setAttribute(Qt::WA_NativeWindow); + setAttribute(Qt::WA_PaintOnScreen); + setAttribute(Qt::WA_NoSystemBackground); + setAutoFillBackground(false); + setUpdatesEnabled(false); + setFocusPolicy(Qt::StrongFocus); + setMinimumSize(640, 480); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + winId(); } - auto* status = grid->setControl( - 0, - row, - new ui::LabelControl("Current: OpenStreetMap") - ); - controller.set_status(status); - return grid; + QPaintEngine* paintEngine() const override { + return nullptr; + } + void initialize_graphics_window() { + const int w = std::max(1, width()); + const int h = std::max(1, height()); + osg::ref_ptr traits = new osg::GraphicsContext::Traits; + traits->x = 0; + traits->y = 0; + traits->width = w; + traits->height = h; + traits->windowDecoration = false; + traits->doubleBuffer = true; + traits->sharedContext = nullptr; +#ifdef _WIN32 + traits->inheritedWindowData = new osgViewer::GraphicsWindowWin32::WindowData(reinterpret_cast(winId())); +#endif + graphics_context_ = osg::GraphicsContext::createGraphicsContext(traits.get()); + graphics_window_ = dynamic_cast(graphics_context_.get()); + viewer_.getCamera()->setGraphicsContext(graphics_context_.get()); + viewer_.getCamera()->setViewport(new osg::Viewport(0, 0, w, h)); + } +protected: + void resizeEvent(QResizeEvent* event) override { + QWidget::resizeEvent(event); + update_graphics_window(event->size().width(), event->size().height()); + } + void mousePressEvent(QMouseEvent* event) override { + setFocus(Qt::MouseFocusReason); + if (auto* queue = event_queue()) { + queue->mouseButtonPress(event->x(), event->y(), map_button(event->button())); + } + } + void mouseReleaseEvent(QMouseEvent* event) override { + if (auto* queue = event_queue()) { + queue->mouseButtonRelease(event->x(), event->y(), map_button(event->button())); + } + } + void mouseMoveEvent(QMouseEvent* event) override { + if (auto* queue = event_queue()) { + queue->mouseMotion(event->x(), event->y()); + } + } + void wheelEvent(QWheelEvent* event) override { + auto* queue = event_queue(); + if (!queue) { + return; + } + const int delta = event->angleDelta().y(); + if (delta > 0) { + queue->mouseScroll(osgGA::GUIEventAdapter::SCROLL_UP); + } + else if (delta < 0) { + queue->mouseScroll(osgGA::GUIEventAdapter::SCROLL_DOWN); + } + event->accept(); + } + void keyPressEvent(QKeyEvent* event) override { + if (auto* queue = event_queue()) { + queue->keyPress(event->key()); + } + } + void keyReleaseEvent(QKeyEvent* event) override { + if (auto* queue = event_queue()) { + queue->keyRelease(event->key()); + } + } +private: + static int map_button(Qt::MouseButton button) { + if (button == Qt::LeftButton) { + return 1; + } + if (button == Qt::MiddleButton) { + return 2; + } + if (button == Qt::RightButton) { + return 3; + } + return 0; + } + void update_graphics_window(int width, int height) { + if (!graphics_context_) { + return; + } + const int w = std::max(1, width); + const int h = std::max(1, height); + graphics_context_->resized(0, 0, w, h); + if (auto* queue = event_queue()) { + queue->windowResize(0, 0, w, h); + } + auto* viewport = viewer_.getCamera()->getViewport(); + if (viewport) { + viewport->setViewport(0, 0, w, h); + } + else { + viewer_.getCamera()->setViewport(new osg::Viewport(0, 0, w, h)); + } + } + osgGA::EventQueue* event_queue() { + return graphics_window_ ? graphics_window_->getEventQueue() : nullptr; + } + osgViewer::Viewer& viewer_; + osg::ref_ptr graphics_context_; + osgViewer::GraphicsWindow* graphics_window_ = nullptr; +}; +QGroupBox* create_imagery_group(Imagery_Source_Controller& imagery) { + auto* group = new QGroupBox("Imagery source"); + auto* layout = new QVBoxLayout(group); + auto* combo = new Topmost_Combo_Box(group); + for (std::size_t i = 0; i < imagery.size(); ++i) { + combo->addItem(tile_source_name(imagery.source(i))); + } + auto* status = new QLabel("Imagery: none", group); + imagery.set_status(status); + layout->addWidget(combo); + layout->addWidget(status); + QObject::connect(combo, static_cast(&QComboBox::currentIndexChanged), [&imagery](int index) { + if (index >= 0) { + imagery.select(static_cast(index)); + } + }); + combo->setCurrentIndex(2); + return group; } +QGroupBox* create_elevation_group(Elevation_Source_Controller& elevation) { + auto* group = new QGroupBox("Elevation source"); + auto* layout = new QVBoxLayout(group); + auto* combo = new Topmost_Combo_Box(group); + combo->addItem("Disabled"); + for (std::size_t i = 0; i < elevation.size(); ++i) { + combo->addItem(tile_source_name(elevation.source(i))); + } + auto* status = new QLabel("Elevation: none", group); + elevation.set_status(status); + layout->addWidget(combo); + layout->addWidget(status); + QObject::connect(combo, static_cast(&QComboBox::currentIndexChanged), [&elevation](int index) { + if (index <= 0) { + elevation.disable(); + return; + } + elevation.select(static_cast(index - 1)); + }); + combo->setCurrentIndex(1); + return group; +} +QGroupBox* create_scale_group(Terrain_Scale_Controller& terrain_scale) { + auto* group = new QGroupBox("Terrain exaggeration"); + auto* layout = new QVBoxLayout(group); + auto* slider = new QSlider(Qt::Horizontal, group); + auto* spin = new QDoubleSpinBox(group); + auto* status = new QLabel(group); + auto* row = new QHBoxLayout(); + slider->setRange(10, 80); + slider->setSingleStep(1); + slider->setPageStep(5); + slider->setValue(static_cast(std::lround(terrain_scale.scale() * 10.0f))); + spin->setRange(1.0, 8.0); + spin->setDecimals(2); + spin->setSingleStep(0.25); + spin->setValue(terrain_scale.scale()); + terrain_scale.set_status(status); + row->addWidget(new QLabel("Scale", group)); + row->addWidget(spin); + layout->addWidget(slider); + layout->addLayout(row); + layout->addWidget(status); + QObject::connect(slider, &QSlider::valueChanged, [spin, &terrain_scale](int value) { + const double scale = static_cast(value) / 10.0; + QSignalBlocker blocker(spin); + spin->setValue(scale); + terrain_scale.set_scale(static_cast(scale)); + }); + QObject::connect(spin, static_cast(&QDoubleSpinBox::valueChanged), [slider, &terrain_scale](double value) { + QSignalBlocker blocker(slider); + slider->setValue(static_cast(std::lround(value * 10.0))); + terrain_scale.set_scale(static_cast(value)); + }); + return group; +} +QWidget* create_control_panel(Imagery_Source_Controller& imagery, Elevation_Source_Controller& elevation, Terrain_Scale_Controller& terrain_scale, QWidget* parent = nullptr) { + auto* panel = new QWidget(parent); + panel->setMinimumWidth(360); + panel->setMaximumWidth(420); + auto* layout = new QVBoxLayout(panel); + layout->addWidget(create_imagery_group(imagery)); + layout->addWidget(create_elevation_group(elevation)); + layout->addWidget(create_scale_group(terrain_scale)); + layout->addWidget(new QLabel("Terrain data: Mapzen / AWS Open Data", panel)); + layout->addWidget(new QLabel("Normal maps: disabled", panel)); + layout->addStretch(1); + return panel; +} + int main(int argc, char** argv) { #ifdef _WIN32 SetConsoleOutputCP(CP_UTF8); SetConsoleCP(CP_UTF8); #endif - const std::string install_root = - "D:/ae/cached_external_library/vs2019_Debug_osg_earth/install"; + QApplication app(argc, argv); + osg::ArgumentParser args(&argc, argv); + const std::string install_root = "D:/ae/cached_external_library/vs2019_Debug_osg_earth/install"; osgDB::FilePathList plugin_paths{ install_root + "/osgearth/bin/osgPlugins-3.6.5", install_root + "/openscenegraph/bin/osgPlugins-3.6.5" @@ -107,58 +660,84 @@ int main(int argc, char** argv) { auto* registry = osgDB::Registry::instance(); registry->setLibraryFilePathList(plugin_paths); osg::setNotifyLevel(osg::NOTICE); - if (registry->getReaderWriterForExtension("png") == nullptr) { - std::cerr << "无法加载 OSG PNG 插件,请检查 osgdb_pngd.dll 和它依赖的 DLL\n"; + if (registry->getReaderWriterForExtension("png") == nullptr || registry->getReaderWriterForExtension("jpg") == nullptr) { + std::cerr << "无法加载 OSG PNG/JPEG 插件,请检查 osgdb_pngd.dll、osgdb_jpegd.dll 和它们依赖的 DLL\n"; return 1; } osgEarth::initialize(); - osg::ArgumentParser args(&argc, argv); - osg::ref_ptr map = new osgEarth::Map(); - Tile_Source_Controller tile_sources(map.get()); - tile_sources.add_source( - { - "OpenStreetMap", - "https://tile.openstreetmap.org/{z}/{x}/{y}.png" - }, - true - ); - tile_sources.add_source( - { - "OpenTopoMap", - "https://[abc].tile.opentopomap.org/{z}/{x}/{y}.png" - }, - false - ); - tile_sources.add_source( - { - "Esri World Imagery", - "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}" - }, - false - ); - osg::ref_ptr map_node = new osgEarth::MapNode(map.get()); - osgViewer::Viewer viewer(args); - viewer.setUpViewInWindow(100, 100, 1280, 720); - viewer.setRealizeOperation(new osgEarth::GL3RealizeOperation()); - viewer.getCamera()->setSmallFeatureCullingPixelSize(-1.0f); - viewer.setSceneData(map_node.get()); - ui::ControlCanvas::getOrCreate(&viewer)->addControl( - create_tile_source_panel(tile_sources) - ); - osg::ref_ptr manipulator = - new osgEarth::EarthManipulator(args); - viewer.setCameraManipulator(manipulator.get()); - manipulator->setViewpoint( - osgEarth::Viewpoint( - "Beijing", - 116.4074, - 39.9042, - 0.0, - 0.0, - -90.0, - 1500000.0 - ), - 0.0 - ); - return viewer.run(); + print_versions(); + const std::vector imagery_sources{ + {"OpenStreetMap", "openstreetmap", "https://tile.openstreetmap.org/{z}/{x}/{y}.png", "png", "image/png", "", 19}, + {"OpenTopoMap", "opentopomap", "https://tile.opentopomap.org/{z}/{x}/{y}.png", "png", "image/png", "", 17}, + {"Esri World Imagery", "esri_world_imagery", "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", "jpg", "image/jpeg", "", 19}, + {"Esri World Topographic", "esri_world_topographic", "https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}", "jpg", "image/jpeg", "", 19}, + {"Esri World Street", "esri_world_street", "https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}", "jpg", "image/jpeg", "", 19}, + {"Esri World Terrain", "esri_world_terrain", "https://server.arcgisonline.com/ArcGIS/rest/services/World_Terrain_Base/MapServer/tile/{z}/{y}/{x}", "jpg", "image/jpeg", "", 13}, + {"CARTO Positron", "carto_positron", "https://a.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png", "png", "image/png", "", 20}, + {"CARTO Dark Matter", "carto_dark_matter", "https://a.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png", "png", "image/png", "", 20}, + {"CARTO Voyager", "carto_voyager", "https://a.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png", "png", "image/png", "", 20} + }; + const std::vector elevation_sources{ + {"Mapzen Terrarium", "mapzen_terrarium", "https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png", "png", "image/png", "terrarium", 15} + }; + Tile_Cache_Server cache_server("C:/Users/wyc/CLionProjects/test_maplibre/tile_cache", imagery_sources, elevation_sources); + cache_server.start(); + int result = 0; + { + osg::ref_ptr map = new osgEarth::Map(); + Imagery_Source_Controller imagery(map.get()); + Elevation_Source_Controller elevation(map.get()); + for (const auto& source : imagery_sources) { + imagery.add_source(source); + } + for (const auto& source : elevation_sources) { + elevation.add_source(source); + } + osg::ref_ptr map_node = new osgEarth::MapNode(map.get(), create_map_node_options()); + Terrain_Scale_Controller terrain_scale(map_node.get(), 2.5f); + osgViewer::Viewer viewer(args); + viewer.setThreadingModel(osgViewer::Viewer::SingleThreaded); + viewer.setRealizeOperation(new osgEarth::GL3RealizeOperation()); + viewer.getCamera()->setSmallFeatureCullingPixelSize(-1.0f); + viewer.setSceneData(map_node.get()); + osg::ref_ptr manipulator = new osgEarth::EarthManipulator(args); + viewer.setCameraManipulator(manipulator.get()); + manipulator->setViewpoint(osgEarth::Viewpoint("Everest", 86.9250, 27.9881, 0.0, 35.0, -35.0, 30000.0), 0.0); + QWidget main_window; + main_window.setWindowTitle("osgEarth terrain viewer"); + main_window.resize(1280, 720); + auto* root_layout = new QHBoxLayout(&main_window); + root_layout->setContentsMargins(0, 0, 0, 0); + root_layout->setSpacing(0); + auto* control_panel = create_control_panel(imagery, elevation, terrain_scale, &main_window); + auto* render_widget = new Osg_Viewer_Widget(viewer, &main_window); + root_layout->addWidget(control_panel, 0); + root_layout->addWidget(render_widget, 1); + main_window.show(); + render_widget->initialize_graphics_window(); + viewer.realize(); + QTimer frame_timer; + QObject::connect(&frame_timer, &QTimer::timeout, [&viewer]() { + if (viewer.done()) { + QApplication::quit(); + return; + } + viewer.frame(); + }); + QObject::connect(&main_window, &QWidget::destroyed, [&viewer]() { + viewer.setDone(true); + QApplication::quit(); + }); + frame_timer.start(16); + result = app.exec(); + frame_timer.stop(); + viewer.setDone(true); + viewer.stopThreading(); + imagery.shutdown(); + elevation.shutdown(); + viewer.setCameraManipulator(nullptr); + viewer.setSceneData(nullptr); + } + cache_server.stop(); + return result; } \ No newline at end of file