From 838aaba43dd632b02fbfd8c11a0d7444ef8302f1 Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Fri, 10 Jul 2026 15:08:30 +0800 Subject: [PATCH] =?UTF-8?q?2.5D=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.cpp | 283 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 247 insertions(+), 36 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 007f072..ba59c7c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,10 +2,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -17,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -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& 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& sources, const std::s } return nullptr; } +const StyleMode *find_mode(const std::vector& 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(file), std::istreambuf_iterator()); } +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 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 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::of(&QComboBox::currentIndexChanged), [&](int) { reloadCurrentSource(); }); + QObject::connect(modeBox, QOverload::of(&QComboBox::currentIndexChanged), [&](int) { + reloadCurrentSource(); + }); + QObject::connect(pitchBox, QOverload::of(&QDoubleSpinBox::valueChanged), [&](double) { + applyCameraBoxes(); + }); + QObject::connect(bearingBox, QOverload::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; -} \ No newline at end of file +}