2.5D更新
This commit is contained in:
+246
-35
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user