2.5D更新

This commit is contained in:
2026-07-10 15:08:30 +08:00
parent 4a80ff878a
commit 838aaba43d
+247 -36
View File
@@ -2,10 +2,12 @@
#include <QApplication>
#include <QByteArray>
#include <QComboBox>
#include <QDoubleSpinBox>
#include <QFileDialog>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLabel>
#include <QLineEdit>
#include <QMainWindow>
#include <QMapLibre/Map>
@@ -17,6 +19,7 @@
#include <QString>
#include <filesystem>
#include <fstream>
#include <iterator>
#include <mutex>
#include <string>
#include <thread>
@@ -29,6 +32,10 @@ struct MapSource {
int minZoom;
int maxZoom;
};
struct StyleMode {
const char *id;
const char *name;
};
const MapSource *find_source(const std::vector<MapSource>& sources, const std::string& id) {
for (const auto& source : sources) {
if (id == source.id) {
@@ -37,6 +44,14 @@ const MapSource *find_source(const std::vector<MapSource>& sources, const std::s
}
return nullptr;
}
const StyleMode *find_mode(const std::vector<StyleMode>& modes, const std::string& id) {
for (const auto& mode : modes) {
if (id == mode.id) {
return &mode;
}
}
return nullptr;
}
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) {
@@ -56,12 +71,61 @@ std::string get_content_type(const std::string& path) {
if (ext == ".webp") {
return "image/webp";
}
if (ext == ".pbf" || ext == ".mvt") {
return "application/x-protobuf";
}
return "application/octet-stream";
}
std::string read_binary_file(const std::filesystem::path& path) {
std::ifstream file(path, std::ios::binary);
return std::string(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
}
QJsonArray make_number_pair(double x, double y) {
QJsonArray pair;
pair.append(x);
pair.append(y);
return pair;
}
QJsonArray make_ring(double west, double south, double east, double north) {
QJsonArray ring;
ring.append(make_number_pair(west, south));
ring.append(make_number_pair(east, south));
ring.append(make_number_pair(east, north));
ring.append(make_number_pair(west, north));
ring.append(make_number_pair(west, south));
return ring;
}
QJsonObject make_building_feature(double west, double south, double east, double north, double base, double height) {
QJsonArray polygon;
polygon.append(make_ring(west, south, east, north));
QJsonObject geometry;
geometry["type"] = "Polygon";
geometry["coordinates"] = polygon;
QJsonObject properties;
properties["base"] = base;
properties["height"] = height;
QJsonObject feature;
feature["type"] = "Feature";
feature["geometry"] = geometry;
feature["properties"] = properties;
return feature;
}
QJsonObject make_demo_buildings_source() {
QJsonArray features;
features.append(make_building_feature(121.4932, 31.2382, 121.4941, 31.2391, 0, 180));
features.append(make_building_feature(121.4945, 31.2378, 121.4952, 31.2386, 0, 260));
features.append(make_building_feature(121.4956, 31.2386, 121.4965, 31.2395, 0, 220));
features.append(make_building_feature(121.4920, 31.2368, 121.4930, 31.2377, 0, 120));
features.append(make_building_feature(121.4968, 31.2372, 121.4978, 31.2382, 0, 150));
features.append(make_building_feature(121.4910, 31.2392, 121.4920, 31.2401, 0, 90));
QJsonObject data;
data["type"] = "FeatureCollection";
data["features"] = features;
QJsonObject source;
source["type"] = "geojson";
source["data"] = data;
return source;
}
QJsonObject make_raster_source(const std::string& url, const MapSource& source) {
QJsonObject rasterSource;
QJsonArray tiles;
@@ -74,37 +138,99 @@ QJsonObject make_raster_source(const std::string& url, const MapSource& source)
rasterSource["scheme"] = "xyz";
return rasterSource;
}
QJsonObject make_raster_layer(const char *id, const char *source) {
QJsonObject rasterPaint;
rasterPaint["raster-opacity"] = 1.0;
QJsonObject rasterLayer;
rasterLayer["id"] = QString::fromUtf8(id);
rasterLayer["type"] = "raster";
rasterLayer["source"] = QString::fromUtf8(source);
rasterLayer["paint"] = rasterPaint;
return rasterLayer;
QJsonObject make_dem_source() {
QJsonObject source;
QJsonArray tiles;
tiles.append("https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png");
source["type"] = "raster-dem";
source["tiles"] = tiles;
source["tileSize"] = 256;
source["minzoom"] = 0;
source["maxzoom"] = 12;
source["encoding"] = "terrarium";
source["attribution"] = "AWS Terrain Tiles";
return source;
}
QByteArray make_raster_style_json(const MapSource& source, const std::string& tiandituKey) {
QJsonObject make_background_layer(const QString& color) {
QJsonObject paint;
paint["background-color"] = color;
QJsonObject layer;
layer["id"] = "background";
layer["type"] = "background";
layer["paint"] = paint;
return layer;
}
QJsonObject make_raster_layer(const char *id, const char *source, const std::string& modeId) {
QJsonObject paint;
paint["raster-opacity"] = modeId == "dark" ? 0.72 : 1.0;
if (modeId == "dark") {
paint["raster-brightness-min"] = 0.10;
paint["raster-brightness-max"] = 0.62;
paint["raster-contrast"] = 0.28;
paint["raster-saturation"] = -0.78;
}
QJsonObject layer;
layer["id"] = QString::fromUtf8(id);
layer["type"] = "raster";
layer["source"] = QString::fromUtf8(source);
layer["paint"] = paint;
return layer;
}
QJsonObject make_hillshade_layer() {
QJsonObject paint;
paint["hillshade-exaggeration"] = 0.62;
paint["hillshade-shadow-color"] = "#111111";
paint["hillshade-highlight-color"] = "#ffffff";
paint["hillshade-accent-color"] = "#666666";
QJsonObject layer;
layer["id"] = "terrain-hillshade";
layer["type"] = "hillshade";
layer["source"] = "terrain-dem";
layer["paint"] = paint;
return layer;
}
QJsonObject make_building_layer() {
QJsonArray heightExpression;
heightExpression.append("get");
heightExpression.append("height");
QJsonArray baseExpression;
baseExpression.append("get");
baseExpression.append("base");
QJsonObject paint;
paint["fill-extrusion-color"] = "#00c8ff";
paint["fill-extrusion-opacity"] = 0.72;
paint["fill-extrusion-height"] = heightExpression;
paint["fill-extrusion-base"] = baseExpression;
QJsonObject layer;
layer["id"] = "demo-building-extrusion";
layer["type"] = "fill-extrusion";
layer["source"] = "demo-buildings";
layer["paint"] = paint;
return layer;
}
QByteArray make_style_json(const MapSource& source, const std::string& modeId, const std::string& tiandituKey) {
QJsonObject sources;
std::string tileUrl = replace_all(source.tileUrl, "{tk}", tiandituKey);
sources["base"] = make_raster_source(tileUrl, source);
QJsonObject backgroundPaint;
backgroundPaint["background-color"] = "#000000";
QJsonObject backgroundLayer;
backgroundLayer["id"] = "background";
backgroundLayer["type"] = "background";
backgroundLayer["paint"] = backgroundPaint;
QJsonArray layers;
layers.append(backgroundLayer);
layers.append(make_raster_layer("base", "base"));
layers.append(make_background_layer(modeId == "dark" ? "#02050a" : "#000000"));
layers.append(make_raster_layer("base", "base", modeId));
if (source.overlayUrl && source.overlayUrl[0] != '\0') {
std::string overlayUrl = replace_all(source.overlayUrl, "{tk}", tiandituKey);
sources["overlay"] = make_raster_source(overlayUrl, source);
layers.append(make_raster_layer("overlay", "overlay"));
layers.append(make_raster_layer("overlay", "overlay", modeId));
}
if (modeId == "hillshade") {
sources["terrain-dem"] = make_dem_source();
layers.append(make_hillshade_layer());
}
if (modeId == "extrusion") {
sources["demo-buildings"] = make_demo_buildings_source();
layers.append(make_building_layer());
}
QJsonObject root;
root["version"] = 8;
root["name"] = QString::fromUtf8(source.name);
root["name"] = QString("%1-%2").arg(QString::fromUtf8(source.name), QString::fromUtf8(modeId.c_str()));
root["sources"] = sources;
root["layers"] = layers;
return QJsonDocument(root).toJson(QJsonDocument::Compact);
@@ -159,10 +285,18 @@ int main(int argc, char *argv[]) {
{"tdt_cva", "天地图矢量标注", "https://t0.tianditu.gov.cn/DataServer?T=cva_w&x={x}&y={y}&l={z}&tk={tk}", nullptr, 0, 18},
{"tdt_vec_cva", "天地图矢量+标注", "https://t0.tianditu.gov.cn/DataServer?T=vec_w&x={x}&y={y}&l={z}&tk={tk}", "https://t0.tianditu.gov.cn/DataServer?T=cva_w&x={x}&y={y}&l={z}&tk={tk}", 0, 18}
};
std::vector<StyleMode> modes = {
{"raster", "普通栅格"},
{"dark", "大屏暗色"},
{"hillshade", "DEM山体阴影"},
{"extrusion", "建筑挤压演示"}
};
httplib::Server server;
server.Get(R"(/style/([A-Za-z0-9_]+)\.json)", [&sources, &tiandituKey, &tiandituKeyMutex](const httplib::Request& req, httplib::Response& res) {
server.Get(R"(/style/([A-Za-z0-9_]+)\.json)", [&sources, &modes, &tiandituKey, &tiandituKeyMutex](const httplib::Request& req, httplib::Response& res) {
const MapSource *source = find_source(sources, req.matches[1].str());
if (!source) {
std::string modeId = req.has_param("mode") ? req.get_param_value("mode") : "raster";
const StyleMode *mode = find_mode(modes, modeId);
if (!source || !mode) {
res.status = 404;
res.set_content("style not found", "text/plain");
return;
@@ -172,7 +306,7 @@ int main(int argc, char *argv[]) {
std::lock_guard<std::mutex> lock(tiandituKeyMutex);
key = tiandituKey;
}
QByteArray styleJson = make_raster_style_json(*source, key);
QByteArray styleJson = make_style_json(*source, modeId, key);
res.set_content(styleJson.constData(), styleJson.size(), "application/json");
});
register_tile_server(server, localTileDir, localTileDirMutex);
@@ -183,36 +317,73 @@ int main(int argc, char *argv[]) {
QMapLibre::Settings settings;
QMainWindow window;
QMapLibre::GLWidget *mapWidget = new QMapLibre::GLWidget(settings);
QToolBar *toolbar = window.addToolBar("Map Source");
QToolBar *sourceToolbar = window.addToolBar("Source");
QToolBar *viewToolbar = window.addToolBar("2.5D");
QComboBox *sourceBox = new QComboBox(&window);
QComboBox *modeBox = new QComboBox(&window);
QLineEdit *tiandituKeyEdit = new QLineEdit(&window);
QPushButton *applyTiandituKeyButton = new QPushButton("保存天地图Key", &window);
QLineEdit *localTileDirEdit = new QLineEdit(&window);
QPushButton *selectLocalTileDirButton = new QPushButton("选择瓦片目录", &window);
QPushButton *applyLocalTileDirButton = new QPushButton("保存瓦片目录", &window);
QDoubleSpinBox *pitchBox = new QDoubleSpinBox(&window);
QDoubleSpinBox *bearingBox = new QDoubleSpinBox(&window);
QPushButton *flatButton = new QPushButton("平面", &window);
QPushButton *tiltButton = new QPushButton("倾斜", &window);
QPushButton *northButton = new QPushButton("正北", &window);
QPushButton *chinaButton = new QPushButton("中国", &window);
QPushButton *terrainButton = new QPushButton("山地测试", &window);
QPushButton *buildingButton = new QPushButton("上海楼块", &window);
int styleVersion = 0;
for (const auto& source : sources) {
sourceBox->addItem(QString::fromUtf8(source.name), QString::fromUtf8(source.id));
}
for (const auto& mode : modes) {
modeBox->addItem(QString::fromUtf8(mode.name), QString::fromUtf8(mode.id));
}
tiandituKeyEdit->setText(QString::fromStdString(tiandituKey));
tiandituKeyEdit->setPlaceholderText("输入天地图Key");
tiandituKeyEdit->setMinimumWidth(320);
tiandituKeyEdit->setMinimumWidth(260);
localTileDirEdit->setText(QString::fromStdString(localTileDir));
localTileDirEdit->setPlaceholderText("输入本地瓦片目录,例如 D:/tiles");
localTileDirEdit->setMinimumWidth(360);
toolbar->addWidget(sourceBox);
toolbar->addWidget(tiandituKeyEdit);
toolbar->addWidget(applyTiandituKeyButton);
toolbar->addWidget(localTileDirEdit);
toolbar->addWidget(selectLocalTileDirButton);
toolbar->addWidget(applyLocalTileDirButton);
localTileDirEdit->setMinimumWidth(320);
pitchBox->setRange(0.0, 60.0);
pitchBox->setDecimals(0);
pitchBox->setSingleStep(5.0);
pitchBox->setValue(0.0);
bearingBox->setRange(0.0, 360.0);
bearingBox->setDecimals(0);
bearingBox->setSingleStep(15.0);
bearingBox->setValue(0.0);
sourceToolbar->addWidget(sourceBox);
sourceToolbar->addWidget(modeBox);
sourceToolbar->addWidget(tiandituKeyEdit);
sourceToolbar->addWidget(applyTiandituKeyButton);
sourceToolbar->addWidget(localTileDirEdit);
sourceToolbar->addWidget(selectLocalTileDirButton);
sourceToolbar->addWidget(applyLocalTileDirButton);
viewToolbar->addWidget(new QLabel("Pitch", &window));
viewToolbar->addWidget(pitchBox);
viewToolbar->addWidget(new QLabel("Bearing", &window));
viewToolbar->addWidget(bearingBox);
viewToolbar->addWidget(flatButton);
viewToolbar->addWidget(tiltButton);
viewToolbar->addWidget(northButton);
viewToolbar->addWidget(chinaButton);
viewToolbar->addWidget(terrainButton);
viewToolbar->addWidget(buildingButton);
window.setCentralWidget(mapWidget);
window.resize(1400, 800);
window.setWindowTitle("MapLibre Qt Widgets Demo");
window.resize(1500, 860);
window.setWindowTitle("MapLibre Qt 2.5D Boundary Demo");
window.show();
auto applyCameraBoxes = [&]() {
mapWidget->map()->setPitch(pitchBox->value());
mapWidget->map()->setBearing(bearingBox->value());
};
auto reloadCurrentSource = [&]() {
QString id = sourceBox->currentData().toString();
mapWidget->map()->setStyleUrl(QString("http://127.0.0.1:18080/style/%1.json?v=%2").arg(id).arg(++styleVersion));
QString mode = modeBox->currentData().toString();
mapWidget->map()->setStyleUrl(QString("http://127.0.0.1:18080/style/%1.json?mode=%2&v=%3").arg(id, mode).arg(++styleVersion));
};
auto saveTiandituKey = [&]() {
QString value = tiandituKeyEdit->text().trimmed();
@@ -235,6 +406,46 @@ int main(int argc, char *argv[]) {
QObject::connect(sourceBox, QOverload<int>::of(&QComboBox::currentIndexChanged), [&](int) {
reloadCurrentSource();
});
QObject::connect(modeBox, QOverload<int>::of(&QComboBox::currentIndexChanged), [&](int) {
reloadCurrentSource();
});
QObject::connect(pitchBox, QOverload<double>::of(&QDoubleSpinBox::valueChanged), [&](double) {
applyCameraBoxes();
});
QObject::connect(bearingBox, QOverload<double>::of(&QDoubleSpinBox::valueChanged), [&](double) {
applyCameraBoxes();
});
QObject::connect(flatButton, &QPushButton::clicked, [&]() {
pitchBox->setValue(0.0);
bearingBox->setValue(0.0);
applyCameraBoxes();
});
QObject::connect(tiltButton, &QPushButton::clicked, [&]() {
pitchBox->setValue(60.0);
bearingBox->setValue(35.0);
applyCameraBoxes();
});
QObject::connect(northButton, &QPushButton::clicked, [&]() {
bearingBox->setValue(0.0);
applyCameraBoxes();
});
QObject::connect(chinaButton, &QPushButton::clicked, [&]() {
mapWidget->map()->setCoordinateZoom(QMapLibre::Coordinate(35.0, 105.0), 4.0);
});
QObject::connect(terrainButton, &QPushButton::clicked, [&]() {
modeBox->setCurrentIndex(modeBox->findData("hillshade"));
pitchBox->setValue(60.0);
bearingBox->setValue(320.0);
mapWidget->map()->setCoordinateZoom(QMapLibre::Coordinate(30.55, 101.5), 8.0);
applyCameraBoxes();
});
QObject::connect(buildingButton, &QPushButton::clicked, [&]() {
modeBox->setCurrentIndex(modeBox->findData("extrusion"));
pitchBox->setValue(60.0);
bearingBox->setValue(25.0);
mapWidget->map()->setCoordinateZoom(QMapLibre::Coordinate(31.2384, 121.4948), 15.5);
applyCameraBoxes();
});
QObject::connect(applyTiandituKeyButton, &QPushButton::clicked, [&]() {
saveTiandituKey();
reloadCurrentSource();
@@ -265,4 +476,4 @@ int main(int argc, char *argv[]) {
server.stop();
serverThread.join();
return ret;
}
}