常规更新

This commit is contained in:
2026-07-22 10:59:00 +08:00
parent 05569053e4
commit bd322bf18e
4 changed files with 701 additions and 113 deletions
+1
View File
@@ -1,2 +1,3 @@
/.idea
/tile_cache/
+9 -3
View File
@@ -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()
+3 -1
View File
@@ -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"
+688 -109
View File
@@ -1,105 +1,658 @@
#ifndef CPPHTTPLIB_OPENSSL_SUPPORT
#define CPPHTTPLIB_OPENSSL_SUPPORT
#endif
#include "httplib.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstddef>
#include <filesystem>
#include <fstream>
#include <functional>
#include <iostream>
#include <limits>
#include <mutex>
#include <string>
#include <string_view>
#include <thread>
#include <utility>
#include <vector>
#include <osg/ArgumentParser>
#include <osg/GraphicsContext>
#include <osg/Notify>
#include <osgDB/FileUtils>
#include <osg/Uniform>
#include <osg/Version>
#include <osgGA/EventQueue>
#include <osgGA/GUIEventAdapter>
#include <osgDB/Registry>
#include <osgViewer/GraphicsWindow>
#include <osgViewer/Viewer>
#include <osgEarth/Controls>
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#include <osgViewer/api/Win32/GraphicsWindowWin32>
#endif
#include <osgEarth/EarthManipulator>
#include <osgEarth/GLUtils>
#include <osgEarth/MapNode>
#include <osgEarth/TerrainEngineNode>
#include <osgEarth/Version>
#include <osgEarth/Viewpoint>
#include <osgEarth/VirtualProgram>
#include <osgEarth/XYZ>
#ifdef _WIN32
#include <windows.h>
#endif
namespace ui = osgEarth::Util::Controls;
#include <QApplication>
#include <QAbstractItemView>
#include <QComboBox>
#include <QDoubleSpinBox>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QListView>
#include <QLabel>
#include <QMouseEvent>
#include <QPaintEngine>
#include <QPushButton>
#include <QResizeEvent>
#include <QSignalBlocker>
#include <QSizePolicy>
#include <QSlider>
#include <QTimer>
#include <QVBoxLayout>
#include <QWheelEvent>
#include <QWidget>
#include <QWindow>
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<double>(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<osgEarth::XYZImageLayer> 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<osg::Uniform> 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<const char*>(data), sizeof(data));
return content;
}
class Tile_Cache_Server {
public:
Tile_Cache_Server(std::filesystem::path cache_dir, std::vector<Tile_Source> imagery_sources, std::vector<Tile_Source> 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<Tile_Source>& 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<std::string>{}(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<char>(input)), std::istreambuf_iterator<char>());
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<std::streamsize>(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<Tile_Source> imagery_sources_;
std::vector<Tile_Source> elevation_sources_;
httplib::Server server_;
std::thread thread_;
std::array<std::mutex, 256> 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<std::size_t>::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<Tile_Source> sources_;
std::vector<osg::ref_ptr<osgEarth::XYZImageLayer>> layers_;
ui::LabelControl* status_;
osg::ref_ptr<osgEarth::XYZImageLayer> active_layer_;
std::size_t active_index_ = std::numeric_limits<std::size_t>::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<std::size_t>::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<std::size_t>::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<Tile_Source> sources_;
osg::ref_ptr<osgEarth::XYZElevationLayer> active_layer_;
std::size_t active_index_ = std::numeric_limits<std::size_t>::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<osg::GraphicsContext::Traits> 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<HWND>(winId()));
#endif
graphics_context_ = osg::GraphicsContext::createGraphicsContext(traits.get());
graphics_window_ = dynamic_cast<osgViewer::GraphicsWindow*>(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<osg::GraphicsContext> 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<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [&imagery](int index) {
if (index >= 0) {
imagery.select(static_cast<std::size_t>(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<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [&elevation](int index) {
if (index <= 0) {
elevation.disable();
return;
}
elevation.select(static_cast<std::size_t>(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<int>(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<double>(value) / 10.0;
QSignalBlocker blocker(spin);
spin->setValue(scale);
terrain_scale.set_scale(static_cast<float>(scale));
});
QObject::connect(spin, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), [slider, &terrain_scale](double value) {
QSignalBlocker blocker(slider);
slider->setValue(static_cast<int>(std::lround(value * 10.0)));
terrain_scale.set_scale(static_cast<float>(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<osgEarth::Map> 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<osgEarth::MapNode> 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<osgEarth::EarthManipulator> 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<Tile_Source> 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<Tile_Source> 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<osgEarth::Map> 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<osgEarth::MapNode> 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<osgEarth::EarthManipulator> 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;
}