Qt与QGIS结合实现离线地图开发全攻略

张开发
2026/4/13 16:59:12 15 分钟阅读

分享文章

Qt与QGIS结合实现离线地图开发全攻略
1. 开发环境搭建从零开始配置Qt与QGIS搞地图开发最头疼的就是环境配置我当年第一次用Qt和QGIS对接时光配环境就折腾了两天。现在把踩过的坑都总结出来让你10分钟搞定基础环境。首先需要准备的是QGIS长期支持版LTR建议直接去官网下载最新稳定版。这里有个小技巧安装时记得勾选将QGIS添加到系统PATH选项不然后面调用库文件会报错。我用的QGIS 3.28版本实测与Qt 5.15.2兼容性最好。VS2019的配置其实比想象中简单新建Qt Widgets Application项目在项目属性页添加包含目录C:\Program Files\QGIS 3.28\include添加库目录C:\Program Files\QGIS 3.28\lib链接器输入里添加核心库qgis_core.lib和qgis_gui.lib// 测试环境是否配置成功的简单代码 #include qgsapplication.h #include qgsproviderregistry.h int main(int argc, char *argv[]) { QgsApplication app(argc, argv, true); QgsProviderRegistry::instance(C:/Program Files/QGIS 3.28/plugins); return app.exec(); }如果看到控制台输出插件加载信息说明环境配置成功了。有个容易忽略的点QGIS的路径中不能有中文或空格否则会导致插件加载失败。我建议直接把QGIS安装在C盘根目录下省去很多麻烦。2. 离线地图数据准备与加载离线地图的核心在于数据源常见的有两种方案本地矢量切片和MBTiles格式。我推荐新手先用MBTiles就像个sqlite数据库把地图切片都打包在一个文件里管理起来特别方便。制作离线地图包有个神器叫QTiles插件在QGIS中安装QTiles插件加载在线地图比如OSM标准地图设置导出范围和解像度生成MBTiles文件# 用Python脚本批量生成不同层级的MBTiles from qgis.core import * import processing for z in range(12,15): processing.run(qgis:tilesxyzdirectory, { EXTENT:116.2,39.8,116.6,40.1, ZOOM:z, DPI:96, TILE_FORMAT:1, QUALITY:75, OUTPUT_DIR:tiles, OUTPUT_HTML:temp.html }) processing.run(qgis:tilesxyzmerge, { INPUT_DIRECTORY:tiles, OUTPUT_FILE:fbeijing_z{z}.mbtiles })加载离线地图到Qt项目时要注意坐标系问题。国内项目建议使用GCJ-02或BD-09坐标系WGS84直接使用会有偏移。我封装了个工具类来处理这个问题class MapLoader : public QObject { Q_OBJECT public: explicit MapLoader(QgsMapCanvas *canvas); bool loadMBTiles(const QString path); private: QgsRasterLayer *createLayer(const QString url); QgsMapCanvas *m_canvas; };3. 地图交互功能实现有了基础地图后最常用的就是标点、画线这些功能。Qt和QGIS配合做这个特别简单但有几个性能优化的技巧值得分享。标点功能的实现示例void addMarker(double x, double y, const QString iconPath) { QgsVectorLayer *layer new QgsVectorLayer(Point, markers, memory); QgsFeature feature; feature.setGeometry(QgsGeometry::fromPointXY(QgsPointXY(x,y))); QgsSymbol *symbol QgsMarkerSymbol::createSimple({ {name, circle}, {color, red}, {size, 8} }); layer-renderer()-setSymbol(symbol); QgsProject::instance()-addMapLayer(layer); }画线功能有个常见坑直接使用QgsRubberBand会导致性能下降。我的解决方案是改用顶点渲染技术void drawPolyline(const QVectorQgsPointXY points) { QgsLineString *line new QgsLineString(); foreach (const QgsPointXY p, points) { line-addVertex(p); } QgsFeature feature; feature.setGeometry(QgsGeometry(line)); QgsSimpleLineSymbolLayer *lineLayer new QgsSimpleLineSymbolLayer(); lineLayer-setColor(QColor(0, 0, 255)); lineLayer-setWidth(2); QgsSymbol *symbol QgsSymbol::defaultSymbol(QgsWkbTypes::LineGeometry); symbol-changeSymbolLayer(0, lineLayer); QgsSingleSymbolRenderer *renderer new QgsSingleSymbolRenderer(symbol); m_lineLayer-setRenderer(renderer); m_lineLayer-dataProvider()-addFeature(feature); }运动轨迹的实现可以结合Qt的动画框架QPropertyAnimation *anim new QPropertyAnimation(marker, geometry); anim-setDuration(5000); anim-setKeyValueAt(0, QgsGeometry::fromPointXY(startPoint)); anim-setKeyValueAt(1, QgsGeometry::fromPointXY(endPoint)); anim-start();4. 高级功能与性能优化当数据量变大时地图渲染会出现卡顿。经过多次测试我总结出几个性能优化方案矢量切片技术将大数据量图层预处理为矢量切片# 使用ogr2ogr生成GeoPackage格式的矢量切片 ogr2ogr -f GPKG output.gpkg input.shp -nln layer_name -dialect sqlite -sql SELECT ST_Simplify(geometry,0.0001) FROM input多线程加载用QtConcurrent处理数据解析void loadLayerAsync(const QString path) { QtConcurrent::run([](){ QgsVectorLayer *layer new QgsVectorLayer(path, QFileInfo(path).baseName(), ogr); emit layerLoaded(layer); }); }细节层次渲染LOD根据缩放级别显示不同细节connect(m_canvas, QgsMapCanvas::scaleChanged, [](double scale){ bool showDetails (scale 5000); m_detailLayer-setRenderer(showDetails ? m_detailRenderer : m_simpleRenderer); });对于需要离线路径规划的场景可以集成OSRM引擎void RoutingEngine::calculateRoute(const QgsPointXY start, const QgsPointXY end) { osrm::EngineConfig config; config.storage_config {path/to/osrm/data}; config.use_shared_memory false; auto osrm std::make_sharedosrm::OSRM(config); osrm::RouteParameters params; params.coordinates.push_back({start.x(), start.y()}); params.coordinates.push_back({end.x(), end.y()}); osrm::json::Object result; const auto status osrm-Route(params, result); if (status osrm::Status::Ok) { auto route result.values[routes].getosrm::json::Array().values[0]; auto geometry route.getosrm::json::Object().values[geometry].getstd::string(); // 解码polyline格式的路径 } }5. 实战案例景区导航系统开发去年给某景区做的离线导航系统正好用到了这套技术栈。分享几个关键实现点分层地图设计底图10-15级矢量切片建筑物轮廓POI层16-18级详细标注路径层实时渲染的导航路径// 动态加载可见区域的POI void MapWidget::updateVisiblePOIs() { QgsRectangle extent m_canvas-extent(); QString query QString(SELECT * FROM pois WHERE x %1 AND x %2 AND y %3 AND y %4) .arg(extent.xMinimum()).arg(extent.xMaximum()) .arg(extent.yMinimum()).arg(extent.yMaximum()); m_poiLayer-setSubsetString(query); }离线搜索功能采用空间索引加速QListPOI SpatialIndex::search(const QgsPointXY center, double radius) { QListPOI results; QgsSpatialIndex index(m_allPois); QgsRectangle rect( center.x() - radius, center.y() - radius, center.x() radius, center.y() radius ); foreach (const QgsFeatureId id, index.intersects(rect)) { QgsPointXY poi m_allPois[id].geometry().asPoint(); if (center.distance(poi) radius) { results.append(m_allPois[id]); } } return results; }遇到的最大挑战是内存优化解决方案是使用QgsVectorLayer的setSubsetString动态过滤数据对大数据量图层启用QgsFeatureIterator的渐进式加载利用QgsMapLayer的setRendererV2减少GPU内存占用// 渐进式加载示例 void loadLargeLayer(const QString path) { QgsVectorLayer *layer new QgsVectorLayer(path, large_layer, ogr); QgsFeatureIterator it layer-getFeatures(); QTimer *timer new QTimer(this); connect(timer, QTimer::timeout, [](){ QgsFeature f; if (it.nextFeature(f)) { m_tempStorage.addFeature(f); } else { timer-stop(); layer-updateExtents(); } }); timer-start(50); }

更多文章