常规更新
This commit is contained in:
@@ -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
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user