多种数据源更新

This commit is contained in:
2026-07-08 16:28:33 +08:00
parent e9cef07864
commit ac02626dd1
5 changed files with 263 additions and 18 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ if (${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_LIST_DIR})
set(test_maplibre_VERSION 1.0.0)
set(CMAKE_CXX_STANDARD 20)
include(third_party/build_infra/start.cmake)
include(third_party/CPP_Core/main.cmake)
set(test_maplibre_rely QMapLibre_qt_5_15_2)
library_is_installed_with_rely(global ok ${test_maplibre_rely})
+8 -2
View File
@@ -7,10 +7,11 @@ find_package(Qt5 REQUIRED COMPONENTS Widgets)
find_package(QMapLibre COMPONENTS Widgets REQUIRED)
file(GLOB_RECURSE srcs ${dir}/*.c ${dir}/*.h ${dir}/*.cpp ${dir}/*.hpp)
file(GLOB_RECURSE srcs ${dir}/*.qrc ${dir}/*.c ${dir}/*.h ${dir}/*.cpp ${dir}/*.hpp)
add_executable(test_maplibre ${srcs})
target_link_libraries(test_maplibre PRIVATE Qt5::Widgets QMapLibre::Widgets)
target_compile_definitions(test_maplibre PRIVATE QT_MAPLIBRE_STATIC)
@@ -20,4 +21,9 @@ if(WIN32)
endif()
if(MSVC)
target_compile_options(test_maplibre PRIVATE /utf-8)
endif()
endif()
set_property(TARGET test_maplibre PROPERTY AUTOMOC ON)
set_property(TARGET test_maplibre PROPERTY AUTOUIC ON)
set_property(TARGET test_maplibre PROPERTY AUTORCC ON)
+249 -15
View File
@@ -1,32 +1,266 @@
#include "httplib.h"
#include <QApplication>
#include <QDebug>
#include <QFileInfo>
#include <QByteArray>
#include <QComboBox>
#include <QFileDialog>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLineEdit>
#include <QMainWindow>
#include <QMapLibre/Map>
#include <QMapLibre/Settings>
#include <QMapLibreWidgets/GLWidget>
#include <QPushButton>
#include <QSettings>
#include <QToolBar>
#include <QString>
#include <filesystem>
#include <fstream>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
struct MapSource {
const char *id;
const char *name;
const char *tileUrl;
const char *overlayUrl;
int minZoom;
int maxZoom;
};
const MapSource *find_source(const std::vector<MapSource>& sources, const std::string& id) {
for (const auto& source : sources) {
if (id == source.id) {
return &source;
}
}
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) {
value.replace(pos, from.size(), to);
pos += to.size();
}
return value;
}
std::string get_content_type(const std::string& path) {
std::string ext = std::filesystem::path(path).extension().string();
if (ext == ".png") {
return "image/png";
}
if (ext == ".jpg" || ext == ".jpeg") {
return "image/jpeg";
}
if (ext == ".webp") {
return "image/webp";
}
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>());
}
QJsonObject make_raster_source(const std::string& url, const MapSource& source) {
QJsonObject rasterSource;
QJsonArray tiles;
tiles.append(QString::fromUtf8(url.c_str()));
rasterSource["type"] = "raster";
rasterSource["tiles"] = tiles;
rasterSource["tileSize"] = 256;
rasterSource["minzoom"] = source.minZoom;
rasterSource["maxzoom"] = source.maxZoom;
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;
}
QByteArray make_raster_style_json(const MapSource& source, 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"));
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"));
}
QJsonObject root;
root["version"] = 8;
root["name"] = QString::fromUtf8(source.name);
root["sources"] = sources;
root["layers"] = layers;
return QJsonDocument(root).toJson(QJsonDocument::Compact);
}
void register_tile_server(httplib::Server& server, std::string& localTileDir, std::mutex& localTileDirMutex) {
server.Get(R"(/tiles/(.+))", [&localTileDir, &localTileDirMutex](const httplib::Request& req, httplib::Response& res) {
std::string root;
{
std::lock_guard<std::mutex> lock(localTileDirMutex);
root = localTileDir;
}
std::string relative = req.matches[1].str();
std::filesystem::path path = std::filesystem::path(root) / std::filesystem::path(relative);
if (!std::filesystem::exists(path) || !std::filesystem::is_regular_file(path)) {
res.status = 404;
res.set_content("tile not found", "text/plain");
return;
}
std::string body = read_binary_file(path);
std::string contentType = get_content_type(path.string());
res.set_content(body, contentType.c_str());
});
}
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QString stylePath = "C:/Users/wyc/CLionProjects/test_maplibre/src/style.json";
QFileInfo styleInfo(stylePath);
qDebug() << "style exists:" << styleInfo.exists();
qDebug() << "style path:" << styleInfo.absoluteFilePath();
QSettings appSettings("wyc", "test_maplibre");
std::string tiandituKey = appSettings.value("tianditu/key", "").toString().trimmed().toStdString();
std::string localTileDir = appSettings.value("local/tile_dir", "D:/tiles").toString().trimmed().toStdString();
std::mutex tiandituKeyMutex;
std::mutex localTileDirMutex;
std::vector<MapSource> sources = {
{"local_png", "本地瓦片 PNG", "http://127.0.0.1:18080/tiles/{z}/{x}/{y}.png", nullptr, 0, 20},
{"local_jpg", "本地瓦片 JPG", "http://127.0.0.1:18080/tiles/{z}/{x}/{y}.jpg", nullptr, 0, 20},
{"osm", "OSM 标准地图", "https://tile.openstreetmap.org/{z}/{x}/{y}.png", nullptr, 0, 19},
{"google_m", "谷歌街道 m", "https://mt0.google.com/vt/lyrs=m&x={x}&y={y}&z={z}", nullptr, 0, 20},
{"google_p", "谷歌街道 p", "https://mt0.google.com/vt/lyrs=p&x={x}&y={y}&z={z}", nullptr, 0, 20},
{"google_r", "谷歌街道 r", "https://mt0.google.com/vt/lyrs=r&x={x}&y={y}&z={z}", nullptr, 0, 20},
{"google_t", "谷歌地形 t", "https://mt0.google.com/vt/lyrs=t&x={x}&y={y}&z={z}", nullptr, 0, 20},
{"google_s", "谷歌影像 s", "https://mt0.google.com/vt/lyrs=s&x={x}&y={y}&z={z}", nullptr, 0, 20},
{"google_y", "谷歌影像含标注 y", "https://mt0.google.com/vt/lyrs=y&x={x}&y={y}&z={z}", nullptr, 0, 20},
{"google_h", "谷歌透明标注 h", "https://mt0.google.com/vt/lyrs=h&x={x}&y={y}&z={z}", nullptr, 0, 20},
{"google_cn_s", "谷歌中国影像 s", "https://mt0.google.com/vt/lyrs=s&gl=CN&x={x}&y={y}&z={z}", nullptr, 0, 20},
{"google_cn_h", "谷歌中国透明标注 h", "https://mt0.google.com/vt/lyrs=h&gl=CN&x={x}&y={y}&z={z}", nullptr, 0, 20},
{"google_cn_y", "谷歌中国影像含标注 y", "https://mt0.google.com/vt/lyrs=y&gl=CN&x={x}&y={y}&z={z}", nullptr, 0, 20},
{"amap_road", "高德地图", "https://webrd01.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}", nullptr, 0, 20},
{"amap_sat", "高德卫星", "https://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}", nullptr, 0, 20},
{"amap_sat_label", "高德卫星标注", "https://webst01.is.autonavi.com/appmaptile?style=8&x={x}&y={y}&z={z}", nullptr, 0, 20},
{"tdt_img", "天地图影像", "https://t0.tianditu.gov.cn/DataServer?T=img_w&x={x}&y={y}&l={z}&tk={tk}", nullptr, 0, 18},
{"tdt_cia", "天地图影像标注", "https://t0.tianditu.gov.cn/DataServer?T=cia_w&x={x}&y={y}&l={z}&tk={tk}", nullptr, 0, 18},
{"tdt_img_cia", "天地图影像+标注", "https://t0.tianditu.gov.cn/DataServer?T=img_w&x={x}&y={y}&l={z}&tk={tk}", "https://t0.tianditu.gov.cn/DataServer?T=cia_w&x={x}&y={y}&l={z}&tk={tk}", 0, 18},
{"tdt_vec", "天地图矢量", "https://t0.tianditu.gov.cn/DataServer?T=vec_w&x={x}&y={y}&l={z}&tk={tk}", nullptr, 0, 18},
{"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}
};
httplib::Server server;
server.set_mount_point("/", "C:/Users/wyc/CLionProjects/test_maplibre/src");
server.set_mount_point("/tiles", "D:/tiles");
server.Get(R"(/style/([A-Za-z0-9_]+)\.json)", [&sources, &tiandituKey, &tiandituKeyMutex](const httplib::Request& req, httplib::Response& res) {
const MapSource *source = find_source(sources, req.matches[1].str());
if (!source) {
res.status = 404;
res.set_content("style not found", "text/plain");
return;
}
std::string key;
{
std::lock_guard<std::mutex> lock(tiandituKeyMutex);
key = tiandituKey;
}
QByteArray styleJson = make_raster_style_json(*source, key);
res.set_content(styleJson.constData(), styleJson.size(), "application/json");
});
register_tile_server(server, localTileDir, localTileDirMutex);
std::thread serverThread([&server]() {
server.listen("127.0.0.1", 18080);
});
server.wait_until_ready();
QMapLibre::Settings settings;
QMapLibre::GLWidget widget(settings);
widget.resize(1200, 800);
widget.setWindowTitle("MapLibre Qt Widgets Demo");
widget.show();
//widget.map()->setStyleUrl("https://demotiles.maplibre.org/style.json");
widget.map()->setStyleUrl("http://127.0.0.1:18080/style.json");
widget.map()->setCoordinateZoom(QMapLibre::Coordinate(35.0, 105.0), 4.0);
QMainWindow window;
QMapLibre::GLWidget *mapWidget = new QMapLibre::GLWidget(settings);
QToolBar *toolbar = window.addToolBar("Map Source");
QComboBox *sourceBox = 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);
int styleVersion = 0;
for (const auto& source : sources) {
sourceBox->addItem(QString::fromUtf8(source.name), QString::fromUtf8(source.id));
}
tiandituKeyEdit->setText(QString::fromStdString(tiandituKey));
tiandituKeyEdit->setPlaceholderText("输入天地图Key");
tiandituKeyEdit->setMinimumWidth(320);
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);
window.setCentralWidget(mapWidget);
window.resize(1400, 800);
window.setWindowTitle("MapLibre Qt Widgets Demo");
window.show();
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));
};
auto saveTiandituKey = [&]() {
QString value = tiandituKeyEdit->text().trimmed();
{
std::lock_guard<std::mutex> lock(tiandituKeyMutex);
tiandituKey = value.toStdString();
}
appSettings.setValue("tianditu/key", value);
appSettings.sync();
};
auto saveLocalTileDir = [&]() {
QString value = localTileDirEdit->text().trimmed();
{
std::lock_guard<std::mutex> lock(localTileDirMutex);
localTileDir = value.toStdString();
}
appSettings.setValue("local/tile_dir", value);
appSettings.sync();
};
QObject::connect(sourceBox, QOverload<int>::of(&QComboBox::currentIndexChanged), [&](int) {
reloadCurrentSource();
});
QObject::connect(applyTiandituKeyButton, &QPushButton::clicked, [&]() {
saveTiandituKey();
reloadCurrentSource();
});
QObject::connect(tiandituKeyEdit, &QLineEdit::returnPressed, [&]() {
saveTiandituKey();
reloadCurrentSource();
});
QObject::connect(selectLocalTileDirButton, &QPushButton::clicked, [&]() {
QString dir = QFileDialog::getExistingDirectory(&window, "选择本地瓦片目录", localTileDirEdit->text().trimmed());
if (!dir.isEmpty()) {
localTileDirEdit->setText(dir);
saveLocalTileDir();
reloadCurrentSource();
}
});
QObject::connect(applyLocalTileDirButton, &QPushButton::clicked, [&]() {
saveLocalTileDir();
reloadCurrentSource();
});
QObject::connect(localTileDirEdit, &QLineEdit::returnPressed, [&]() {
saveLocalTileDir();
reloadCurrentSource();
});
reloadCurrentSource();
mapWidget->map()->setCoordinateZoom(QMapLibre::Coordinate(35.0, 105.0), 4.0);
int ret = app.exec();
server.stop();
serverThread.join();
+5
View File
@@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/style">
<file alias="style.json">../style/style.json</file>
</qresource>
</RCC>
View File