安装环境搭建

QGIS的二次开发支持Windows和Linux下进行,这里分别介绍一下环境搭建。

Windows下环境搭建

网上有详细的Windows上安装QGIS二次开发环境的说明,这里就不详细介绍了。

Ubuntu24下环境搭建

(可选)WSl安装Ubuntu 24

我是直接在Windows上安装的WSL2来开发调试的,直接在Windows上安装Ubuntu24.04即可。

wsl --install Ubuntu-24.04

安装系统依赖

参考:QGIS官方安装说明

sudo apt-get update
sudo apt-get install bison build-essential ca-certificates ccache cmake cmake-curses-gui dh-python expect flex flip gdal-bin git graphviz grass-dev libdraco-dev libexiv2-dev libexpat1-dev libfcgi-dev libgdal-dev libgeos-dev libgsl-dev libmeshoptimizer-dev libpq-dev libproj-dev libprotobuf-dev libqca-qt5-2-dev libqca-qt5-2-plugins libqscintilla2-qt5-dev libqt5opengl5-dev libqt5serialport5-dev libqt5sql5-sqlite libqt5svg5-dev libqt5webkit5-dev libqt5xmlpatterns5-dev libspatialindex-dev libspatialite-dev libsqlite3-dev libsqlite3-mod-spatialite libyaml-tiny-perl libzip-dev libzstd-dev lighttpd locales ninja-build nlohmann-json3-dev ocl-icd-opencl-dev opencl-headers pandoc pkgconf poppler-utils protobuf-compiler pyqt5-dev pyqt5-dev-tools pyqt5.qsci-dev python3-all-dev python3-autopep8 python3-dev python3-gdal python3-matplotlib python3-mock python3-nose2 python3-owslib python3-packaging python3-psycopg2 python3-pyqt5 python3-pyqt5.qsci python3-pyqt5.qtmultimedia python3-pyqt5.qtpositioning python3-pyqt5.qtserialport python3-pyqt5.qtsql python3-pyqt5.qtsvg python3-pyqt6.sip python3-pyqtbuild python3-termcolor python3-yaml qt3d-assimpsceneimport-plugin qt3d-defaultgeometryloader-plugin qt3d-gltfsceneio-plugin qt3d-scene2d-plugin qt3d5-dev qtbase5-dev qtbase5-private-dev qtkeychain-qt5-dev qtmultimedia5-dev qtpositioning5-dev qttools5-dev qttools5-dev-tools sip-tools spawn-fcgi xauth xfonts-100dpi xfonts-75dpi xfonts-base xfonts-scalable xvfb

设置 ccache(可选,但推荐)

您还应该设置 ccache 以加快编译时间:

cd /usr/local/bin
sudo ln -s /usr/bin/ccache gcc
sudo ln -s /usr/bin/ccache g++

下载QGIS源码

mkdir ~/dev
cd ~/dev
git clone https://github.com/qgis/QGIS.git
cd QGIS
# 切换到3.44分支
git checkout release-3_44

启动编译

这里是直接安装到${HOME}/apps下,不带CMAKE_INSTALL_PREFIX设置安装目录则会安装到系统目录下需要sudo权限。

mkdir build
cd build
cmake -G Ninja -D WITH_3D=true -D CMAKE_INSTALL_PREFIX=${HOME}/apps ..

构建

ninja
ninja install # 如果是不带CMAKE_INSTALL_PREFIX设置安装目录,需要带sudo

创建Qt项目

项目结构

ProjectName/
├── CMakeLists.txt
├── main.cpp
├── mainwindow.cpp
├── mainwindow.h
├── mainwindow.ui
├── resources.qrc

CMakeLists.txt 配置

下面是cmake的关键配置

# QGIS configuration
set(QGIS_DEV_PATH "$ENV{HOME}/apps")
set(QGIS_INCLUDE_DIR "${QGIS_DEV_PATH}/include/qgis")
set(QGIS_LIB_DIR "${QGIS_DEV_PATH}/lib")

find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Xml PrintSupport 3DRender)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Xml PrintSupport 3DRender)

# QGIS configuration - use system installation
find_package(PkgConfig QUIET)
if(PkgConfig_FOUND)
    pkg_check_modules(QGIS QUIET qgis)
endif()

find_library(QGIS_CORE_LIBRARY NAMES qgis_core PATHS ${QGIS_LIB_DIR} NO_DEFAULT_PATH)
find_library(QGIS_GUI_LIBRARY NAMES qgis_gui PATHS ${QGIS_LIB_DIR} NO_DEFAULT_PATH)
find_library(QGIS_APP_LIBRARY NAMES qgis_app PATHS ${QGIS_LIB_DIR} NO_DEFAULT_PATH)
find_library(QGIS_NATIVE_LIBRARY NAMES qgis_native PATHS ${QGIS_LIB_DIR} NO_DEFAULT_PATH)
find_library(QGIS_ANALYSIS_LIBRARY NAMES qgis_analysis PATHS ${QGIS_LIB_DIR} NO_DEFAULT_PATH)
find_library(QGIS_3D_LIBRARY NAMES qgis_3d PATHS ${QGIS_LIB_DIR} NO_DEFAULT_PATH)

# Check if QGIS is available
if(QGIS_CORE_LIBRARY)
    message(STATUS "QGIS libraries found in system")
    set(QGIS_FOUND TRUE)
    
    # Try to find QGIS include directory
    find_path(QGIS_INCLUDE_DIR qgis.h
        PATHS /usr/include/qgis /usr/local/include/qgis
        PATH_SUFFIXES qgis
    )
    
    if(QGIS_INCLUDE_DIR)
        message(STATUS "QGIS include directory: ${QGIS_INCLUDE_DIR}")
    else()
        message(WARNING "QGIS include directory not found")
    endif()
else()
    message(WARNING "QGIS libraries not found in system - building without QGIS support")
    set(QGIS_FOUND FALSE)
endif()

# Add QGIS support if available
if(QGIS_FOUND)
    if(QGIS_INCLUDE_DIR)
        target_include_directories(ProjectName PRIVATE ${QGIS_INCLUDE_DIR})
    endif()
    target_compile_definitions(ProjectName PRIVATE
        _USE_MATH_DEFINES
        QGIS_ENABLED
    )
else()
    target_compile_definitions(ProjectName PRIVATE _USE_MATH_DEFINES)
endif()

# Link Qt libraries
target_link_libraries(ProjectName PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Xml Qt${QT_VERSION_MAJOR}::PrintSupport Qt${QT_VERSION_MAJOR}::3DRender)

# Link QGIS libraries if available
if(QGIS_FOUND)
    target_link_libraries(ProjectName PRIVATE
        ${QGIS_CORE_LIBRARY}
        ${QGIS_GUI_LIBRARY}
        ${QGIS_APP_LIBRARY}
        ${QGIS_NATIVE_LIBRARY}
        ${QGIS_ANALYSIS_LIBRARY}
        ${QGIS_3D_LIBRARY}
    )

    # Set RPATH to find QGIS libraries at runtime (system paths)
    set_target_properties(ProjectName PROPERTIES
        BUILD_WITH_INSTALL_RPATH TRUE
        INSTALL_RPATH_USE_LINK_PATH TRUE
    )
endif()

主要代码片段

main.cpp

#include "mainwindow.h"

#include "qgsapplication.h"

int main(int argc, char *argv[]) {
  // QGIS 初始化
  QgsApplication a(argc, argv, true);
  a.init();
  a.initQgis();
  MainWindow w;
  w.show();
  return a.exec();
}

mainwindow.cpp

#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include "mapcanvas/mapcanvas.h"
#include "qgsmaptoolpan.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent), ui(new Ui::MainWindow) {
  ui->setupUi(this);
  setWindowTitle(QStringLiteral("软件标题"));
  // 自定义地图类,实现自己的地图功能
  MapCanvas *mapcanvas = new MapCanvas(this);
  // 地图画布设置为黑色
  mapcanvas->setCanvasColor(Qt::black);
  // 这一步是把MapCanvas放到窗口布局的下面,这样可以实现Qt的控件叠加显示在地图上面
  ui->gridLayout->addWidget(mapcanvas, 0, 0, 1, 1);
  mapcanvas->lower();

  // 设置地图工具,默认自带拖拽移动和缩放,可创建新的类实现自己想要的操作逻辑
  QgsMapToolPan *maptool = new QgsMapToolPan(mapcanvas);
  mapcanvas->setMapTool(maptool);
}

mapcanvas.cpp

#include "mapcanvas.h"

QgsRasterLayer *MapCanvas::getInitRasterLayer(const QString &url,
                                              const QString &name) {
  QgsRasterLayer *raster = new QgsRasterLayer(url, name);
  //   raster->setCrs(QgsCoordinateReferenceSystem::fromEpsgId(3857));   // 可以指定投影坐标系

  // 设置地图显示范围为raster图层的范围
  this->setExtent(raster->extent());

  // 把raster图层添加到地图画布中
  auto layers = this->layers();
  layers.append(raster);
  this->setLayers(layers);

  // 设置地图显示范围为raster图层的范围
  this->zoomToFullExtent();
  // 刷新地图画布
  this->refresh();
  return raster;
}

MapCanvas::~MapCanvas() {
  if (_qgsproject) {
    _qgsproject->removeAllMapLayers();
  }
}

MapCanvas::MapCanvas(QWidget *parent) : QgsMapCanvas(parent) {
  _qgsproject = QgsProject::instance();
  // 加载openstreetmap在线地图
  _street_layer = getInitRasterLayer("/path/map.tif", "street");
  // _street_layer = getInitRasterLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", "street","wms"); //在线地图示例
  // 把图层添加到qgsproject中
  _qgsproject->addMapLayer(_street_layer);
  setProject(_qgsproject);
}

坐标系转换

GPS原始数据:EPSG:4326(WGS84,经纬度) 地图展示常用:EPSG:3857(Web Mercator,平面米制坐标) 在GIS中,坐标系转换是一个非常重要的功能。QGIS提供了一个坐标转换工具,可以将一个坐标系转换为另一个坐标系。 使用示例:

QgsPointXY Crs4326To3857(const QgsPointXY &wgs84Point) {
      return QgsCoordinateTransform(
        QgsCoordinateReferenceSystem::fromEpsgId(4326),
        QgsCoordinateReferenceSystem::fromEpsgId(3857),
        QgsProject::instance()).transform(wgs84Point);
}

QgsPointXY Crs3857To4326(const QgsPointXY &webMercatorPoint) {
      return QgsCoordinateTransform(
        QgsCoordinateReferenceSystem::fromEpsgId(3857),
        QgsCoordinateReferenceSystem::fromEpsgId(4326),
        QgsProject::instance()).transform(webMercatorPoint);
}

实时获取地图缩放

在QGIS中,我们可以通过连接地图画布的scaleChanged信号来实时获取地图的缩放级别。

使用示例:

connect(this, &QgsMapCanvas::scaleChanged, this, &MapCanvas::onScaleChanged);

MapCanvas类中,我们需要实现onScaleChanged槽函数来处理缩放级别变化的事件。

void MapCanvas::onScaleChanged(double scale) {
  qDebug() << "Map scale changed to:" << scale;
}

自定义地图工具

在QGIS中,我们可以通过继承QgsMapTool类来创建自定义的地图工具。自定义地图工具可以实现自己想要的地图操作逻辑,比如测量距离、绘制多边形等。 具体可重载函数可查看qgsmaptool.h头文件。 可借鉴QGIS官方实现的一些地图工具,比如QgsMapToolPan、QgsMapToolZoom等。

使用示例:

class MyMapTool : public QgsMapTool {
public:
  MyMapTool(QgsMapCanvas *canvas) : QgsMapTool(canvas) {}

  void canvasPressEvent(QgsMapMouseEvent *e) override {
    if (e->button() == Qt::LeftButton) {
      qDebug() << "Left mouse button pressed at:" << e->mapPoint();
    }
  }
};

QgsVectorLayer矢量图层添加

在QGIS中,我们可以通过加载矢量图层来添加矢量数据。矢量图层可以是点、线、面等几何类型的数据集。

单一符号矢量图层使用示例:

QgsVectorLayer *vectorLayer = new QgsVectorLayer("Point?crs=EPSG:3857&field=node_count:integer&field=label:string",
      "clusters", "memory");
if (!vectorLayer->isValid()) {
  qDebug() << "Failed to load vector layer:" << vectorLayer->errorMessage();
} else {
  QgsProject::instance()->addMapLayer(vectorLayer);
}

  // 创建单一符号渲染器
  std::unique_ptr<QgsMarkerSymbol> symbol =
      QgsMarkerSymbol::createSimple(properties);
  QgsSingleSymbolRenderer *renderer =
      new QgsSingleSymbolRenderer(symbol.release());
  vectorLayer->setRenderer(renderer);

  // 设置标签显示节点数量
  QgsPalLayerSettings labelSettings;
  labelSettings.fieldName = "label";
  labelSettings.placement = Qgis::LabelPlacement::OverPoint;
  // labelSettings.yOffset = 0; // 标签显示Y轴偏移量,默认0
  labelSettings.offsetUnits = Qgis::RenderUnit::Points;

  // 设置文本格式
  QgsTextFormat textFormat;
  textFormat.setSize(20); // 比普通节点文字更大
  textFormat.setSizeUnit(Qgis::RenderUnit::Points);
  textFormat.setColor(QColor(255, 0, 0)); // 黑色文字

  // 设置文本缓冲区
  QgsTextBufferSettings bufferSettings;
  bufferSettings.setEnabled(true);
  bufferSettings.setSize(1);
  textFormat.setBuffer(bufferSettings);

  labelSettings.setFormat(textFormat);

  // 应用标签设置
  QgsVectorLayerSimpleLabeling *labeling =
      new QgsVectorLayerSimpleLabeling(labelSettings);

  vectorLayer->setLabeling(labeling);

QGIS 3D地图显示

QGIS 3.0及以上版本支持3D地图显示。我们可以在QGIS中加载3D模型图层,实现3D地图展示。

使用示例:

初始化3D地图资源

Qgs3D::initialize();

使用示例,这里是设置为跟2D地图一样的视野缩放

  // 初始化3D地图画布
  Qgs3DMapCanvas *mapcanvas_3d = new Qgs3DMapCanvas();

  Qgs3DMapSettings *map = new Qgs3DMapSettings();

  // 获取2D地图的当前视图范围,而不是整个项目范围
  QgsRectangle currentExtent = mapcanvas->projectExtent();
  QgsPointXY currentCenter = mapcanvas->center();
  double currentScale = mapcanvas->scale();

  // 设置3D地图的视图范围和中心
  map->setExtent(currentExtent);
  map->setCenter(currentCenter);
  map->setScale(currentScale);

  // 设置3D地图的选择颜色和背景颜色
  map->setSelectionColor(mapcanvas->selectionColor());
  map->setBackgroundColor(QColor(0, 255, 0)); // 设置为浅灰色背景

  // 设置3D地图的投影坐标系
  map->setCrs(QgsCoordinateReferenceSystem::fromEpsgId(3857));

  // 检查图层信息
  QList<QgsMapLayer *> layers = mapcanvas->layers();
  QList<QgsMapLayer *> validLayers;  // 3D地图要添加的图层

  for (int i = 0; i < layers.size(); ++i) {
    QgsMapLayer *layer = layers[i];
    if (layer) {
      // 使用动态类型转换检查是否为栅格图层
      QgsRasterLayer *rasterLayer = qobject_cast<QgsRasterLayer *>(layer);
      if (rasterLayer) {
        // 3D地图只添加QgsRasterLayer
        validLayers.append(rasterLayer);
      }
    } else {
      qDebug() << "图层" << i << ":空指针";
    }
  }
  map->setLayers(validLayers);

  // 设置3D地图的时间范围
  map->setTemporalRange(mapcanvas->temporalRange());

  // 启用平面地形生成器
  QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator;
  flatTerrain->setCrs(map->crs(), prj->transformContext());

  // 检查范围有效性并设置
  if (currentExtent.isEmpty() || currentExtent.isNull()) {
    qDebug() << "警告:当前范围无效,尝试使用默认范围";
    // 使用一个默认的范围(武汉周边区域)
    QgsRectangle defaultExtent(12700000, 3550000, 12750000,
                               3600000); // Web Mercator坐标
    flatTerrain->setExtent(defaultExtent);
    qDebug() << "使用默认范围:" << defaultExtent.toString();
  } else {
    flatTerrain->setExtent(currentExtent);
    qDebug() << "地形生成器范围设置为:" << currentExtent.toString();
  }

  map->setTerrainGenerator(flatTerrain);

// 配置地形材质
  //   QgsPhongMaterialSettings terrainMaterial;
  //   terrainMaterial.setAmbient(QColor(120, 120, 120));  // 增强环境光颜色
  //   terrainMaterial.setDiffuse(QColor(220, 220, 220));  // 增强漫反射颜色
  //   terrainMaterial.setSpecular(QColor(255, 255, 255)); // 镜面反射颜色
  //   terrainMaterial.setShininess(50.0f);                // 增加光泽度
  //   map->setTerrainShadingMaterial(terrainMaterial);
  //   map->setTerrainShadingEnabled(true);

  // 设置光照 - 增强光照效果
  //   QgsPointLightSettings *pointLight = new QgsPointLightSettings;
  //   pointLight->setPosition(QgsVector3D(0, 0, 2000)); // 提高光源高度
  //   pointLight->setColor(QColor(255, 255, 255));
  //   pointLight->setIntensity(1.0); // 增强点光源强度

  //   QgsDirectionalLightSettings *dirLight = new QgsDirectionalLightSettings;
  //   dirLight->setDirection(QgsVector3D(-0.3, -0.3, -1.0)); // 调整光照角度
  //   dirLight->setColor(QColor(255, 255, 255));
  //   dirLight->setIntensity(1.0); // 增强方向光强度

  //   // 添加额外的环境光源
  //   QgsPointLightSettings *ambientLight = new QgsPointLightSettings;
  //   ambientLight->setPosition(QgsVector3D(1000, 1000, 1500));
  //   ambientLight->setColor(QColor(255, 255, 200)); // 暖色调环境光
  //   ambientLight->setIntensity(1.0);

  //   QList<QgsLightSource *> lightSources;
  //   lightSources.append(pointLight);
  //   lightSources.append(dirLight);
  //   lightSources.append(ambientLight); // 添加环境光源
  //   map->setLightSources(lightSources);

  // 设置相机导航模式 - 使用Qgis::NavigationMode枚举
  // mapcanvas_3d->cameraController()->setCameraNavigationMode(Qgis::NavigationMode::TerrainBased);

  // 设置相机移动速度
  map->setCameraMovementSpeed(
      settings
          .value(QStringLiteral("map3d/defaultMovementSpeed"), 8,
                 QgsSettings::App)
          .toDouble());

  // 设置投影类型和视野
  const Qt3DRender::QCameraLens::ProjectionType defaultProjection =
      settings.enumValue(QStringLiteral("map3d/defaultProjection"),
                         Qt3DRender::QCameraLens::PerspectiveProjection,
                         QgsSettings::App);
  map->setProjectionType(defaultProjection);
  map->setFieldOfView(settings
                          .value(QStringLiteral("map3d/defaultFieldOfView"), 45,
                                 QgsSettings::App)
                          .toInt());

  // 设置变换上下文和路径解析器
  map->setTransformContext(prj->transformContext());
  map->setPathResolver(prj->pathResolver());
  map->setMapThemeCollection(prj->mapThemeCollection());
  connect(prj, &QgsProject::transformContextChanged, map, [map] {
    map->setTransformContext(QgsProject::instance()->transformContext());
  });


  // 3D地图设置使能
  mapcanvas_3d->setMapSettings(map);
  // 显示3D地图。PS.这里没有给设置parent,单独弹窗显示,实际使用需要进行管理
  mapcanvas_3d->show();

加载3D模型图层

Qgs3D::add3DModelLayer(url, name);

3D地图也有Qgs3DMapTool,用于处理3D地图的交互操作,如相机移动、旋转、缩放等。 可以通过mapcanvas_3d->setMapTool()设置当前的交互工具。

矢量图层添加3D地图显示效果

// 为矢量图层添加3D显示效果
QgsVectorLayer3DRenderer *PointRule_Node_3D() {
  QgsSymbolLayerList svg_list;
  QgsSimpleMarkerSymbolLayer *sim = new QgsSimpleMarkerSymbolLayer();
  QgsSvgMarkerSymbolLayer *svgmarker =
      new QgsSvgMarkerSymbolLayer(QString(":/new/prefix1/resource/Node.svg"));
  svg_list.append(svgmarker);
  //    svg_list.append(sim);
  QgsMarkerSymbol *markerSymbol = new QgsMarkerSymbol(svg_list);
  QgsPoint3DSymbol *point3DSymbol = new QgsPoint3DSymbol();
  point3DSymbol->setBillboardSymbol(markerSymbol);
  point3DSymbol->setShape(Qgis::Point3DShape::Billboard);
  return (new QgsVectorLayer3DRenderer(point3DSymbol));
}

// 然后将渲染器设置给矢量图层
QgsAbstract3DRenderer *renderer3D = PointRule_Node_3D();
vectorLayer->setRenderer3D(renderer3D);

未完待续

参考效果

image.png