本节主要介绍矢量图层的数据组成,包括属性、几何体和数据源三个层面,一起解构QGIS中对矢量数据的抽象吧!

要素的属性

要素是构成矢量数据模型的基本单元。 点图层中的一个点,线图层中的一条线,多边形图层中的一个面,都是以要素为单元进行描述。

属性是要素的重要组成部分,主要用于描述要素的非空间特征,另一个重要组成则是几何体。可以将图层想象为一个二维的表。每一列代表一个属性,每个属性都有各自的类型,几何体也可以理解为一个独立的属性。 每一行则是一个具体的要素。基于数据表的这种理解方式,很多时候我们可以用SQL中的字段来等价的替代属性

属性类型

QGIS中的要素属性类型有如下几种:

类型 用途 示例
文本数据 存储字符串 名称、地址
整数 存储整数 ID、人口、计数
小数/实数 存储带小数的数字 面积、长度、坐标
日期/时间 存储时间信息 创建日期、调查时间
布尔值 存储是/否值 是否可见、是否有效
BLOB 存储文件 照片、文档

根据数据源格式的不同,某些属性类型会不被支持或者会有新的属性类型出现。如BLOB类型,其底层使用二进制的形式存储数据,geojson格式在存储层面对此属性类型的支持不够完善。

  • 文本数据: 用于存储文本格式的数据,对应字符串
  • 整数: 用于存储 正负整数
  • 实数: 用于存储实数,对应于 浮点数
  • 应根据需要选择合适的类型,存放合适的数据

QgsField

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Constructor. Constructs a new QgsField object.
* \param name Field name
* \param type Field variant type, currently supported: String / Int / Double
* \param typeName Field type (e.g., char, varchar, text, int, serial, double).
* Field types are usually unique to the source and are stored exactly
* as returned from the data store.
* \param len Field length
* \param prec Field precision. Usually decimal places but may also be
* used in conjunction with other fields types (e.g., variable character fields)
* \param comment Comment for the field
* \param subType If the field is a collection, its element's type. When
* all the elements don't need to have the same type, leave
* this to QVariant::Invalid.
*/
QgsField( const QString &name = QString(),
QVariant::Type type = QVariant::Invalid,
const QString &typeName = QString(),
int len = 0,
int prec = 0,
const QString &comment = QString(),
QVariant::Type subType = QVariant::Invalid );
参数名 是否必需 描述 示例
name 字段的名称(字符串)。 "population", "名称"
type 字段的数据类型,使用 QVariant 枚举。 QVariant.String, QVariant.Int
typeName 数据类型的字符串名称。通常留空,系统会根据 type 自动设置。 "text", "integer"
len 字段长度(整数)。对文本字段和整数字段很重要。 255(文本最大长度),10(整数位数)
prec 字段精度(整数)。对于小数类型,表示小数点后的位数。 2(表示保留两位小数)
comment 字段的注释或别名(字符串)。 "2020年常住人口"
subType 更细粒度的子类型,如 QgsField.NoneSubTypeQgsField.Integer64等。 QVariant.LongLong

type 和 typeName

大家需要注意到,type和typeName是两个不同的参数。

  • 事实上,type是类型的抽象,具有跨平台的特性,不依赖于具体的文件格式或者数据库类型。如整数,type没有具体的描述是INT8 INT32 INT64等

  • typeName是一个字符串,代表类型的具体的实现,根据底层数据源的不同,会需要有不同的映射。

    • Shapefile: 使用 "Integer""Real""String""Date"
    • GeoPackage/SQLite: 使用 "INTEGER""REAL""TEXT""BLOB"
    • PostGIS: 使用 "int4""varchar""float8""timestamp"
  • 一般来说,typeName不需要填写。但需要针对特定的数据源做底层定制时,可以使用该参数

    1
    2
    # 明确指定 type 和 typeName
    field = QgsField("name", QVariant.String, "varchar", 255)

使用QgsField

1
2
3
4
5
6
7
8
9
10
11
12
13
fields = QgsFields()
fields.append(QgsField("ID", QVariant.Int, len=10)) # 整数字段,长度10
fields.append(QgsField("名称", QVariant.String, len=50)) # 文本字段,最大长度50
fields.append(QgsField("人口", QVariant.Int, len=10))
fields.append(QgsField("面积", QVariant.Double, len=10, prec=2)) # 小数字段,总长10,小数点后2位

feat = QgsFeature()
feat.setFields(fields) # 让要素知道字段结构
feat.setAttributes([1, "北京", 21540000, 16410.54]) # 设置属性值,顺序与字段定义一致

feat.setAttribute("ID",2)
print(feat.attribute("ID"))
## 2
  • 定义一个QgsField集合
  • 按顺序添加多个属性
  • 将属性设置给要素
  • 设置、修改和获取要素属性对应的值
  • 至此我们可以更直观的理解要素属性,要素属性是存放在要素本身的键值对(map,dict 等不同语言有不同的描述),属性名称是key,属性类型则规定了value的类型

图层属性与要素属性

QGIS中矢量图层和要素都可以定义属性类型,他们之前存在默认约束行为。

  • 当要素独立存在时,可以将要素作为一个键值对使用,自定义其要素类型并存入和取出其对应的值
  • 当要素依附于矢量图层时,必须符合矢量图层的属性定义。也就是说,矢量图层定义的属性类型是一种结构性约束,是一种蓝图,要求存放在该图层中的要素必须具有同样的属性。
  • 从属性类型约束的角度来理解另外一个事实,几何体类型。当我们把几何体类型也作为属性类型看待时就可以很好的理解为什么图层需要区分点、线、面等集合类型。

几何体

对于初学者,PyQGIS中的几何体类常具有较强的迷惑性,QGIS提供了三种互相关联的类来描述几何体。QgsGeometry , QgsPointXY即XY系列类以及QgsAbastractGeometry及其继承类。

三个类(组)的功能

XY 系列

  • 轻量结构体,用于快速存坐标

  • 仅用作数据存储,没有几何体的空间分析功能

  • 基于QgsPointXY定义多个自定义结构

    1
    2
    3
    4
    5
    6
    QgsPointXY
    typedef QVector<QgsPointXY> QgsPolylineXY;
    typedef QVector<QgsPolylineXY> QgsPolygonXY;
    typedef QVector<QgsPointXY> QgsMultiPointXY;
    typedef QVector<QgsPolylineXY> QgsMultiPolylineXY;
    typedef QVector<QgsPolygonXY> QgsMultiPolygonXY;

QgsAbstractGeometry 类族

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
QgsAbstractGeometry
QgsCurve
QgsCircularString
QgsCompoundCurve
QgsLineString
QgsGeometryCollection
QgsMultiCurve
QgsMultiLineString
QgsMultiPoint
QgsMultiSurface
QgsMultiPolygon
QgsPoint
QgsSurface
QgsCurvePolygon
QgsPolygon
QgsTriangle
QgsPolyhedralSurface
QgsTriangulatedSurface

上面的代码简要的描述了QgsAbstractGeometry类族的继承结构。

  • QgsAbstractGeometry 定义了QGIS中真正的几何体,其基础数据使用XY系列存储
  • 除去坐标信息,还存储了Z值,M值等数据存储
  • 具有buffer intersects distance等空间分析功能

QgsGeometry

classDiagram
class QgsAbstractGeometry
class QgsPoint 
class QgsGeometry 
class QgsPointXY 
QgsPoint --|> QgsAbstractGeometry
QgsPoint..> QgsPointXY 
QgsGeometry ..> QgsAbstractGeometry

从上图我们可以发现,QgsGeometryQgsAbstractGeometry并无明显的继承或者实现的关系,事实上,QgsGeometry更像是一个容器,通过持有 QgsAbstractGeometry的指针,实现对所有真实的几何体的管理。

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 1. 点几何类型转换

from qgis.core import (
QgsPoint, QgsPointXY, QgsLineString, QgsPolygon,
QgsGeometry, QgsMultiPoint, QgsMultiLineString, QgsMultiPolygon
)

# 1. QgsPointXY 相关转换
print("=== QgsPointXY 转换演示 ===")
# 创建 QgsPointXY
point_xy = QgsPointXY(10, 20)
print(f"QgsPointXY: {point_xy}")

# QgsPointXY → QgsPoint
qgs_point = QgsPoint(point_xy)
print(f"QgsPointXY → QgsPoint: {qgs_point}")

# QgsPointXY → QgsGeometry
geom_from_point_xy = QgsGeometry.fromPointXY(point_xy)
print(f"QgsPointXY → QgsGeometry: {geom_from_point_xy}")

# QgsPoint → QgsPointXY
point_xy_from_qgs = QgsPointXY(qgs_point)
print(f"QgsPoint → QgsPointXY: {point_xy_from_qgs}")

print("\n" + "="*50 + "\n")

## === QgsPointXY 转换演示 ===
## QgsPointXY: <QgsPointXY: POINT(10 20)>
## QgsPointXY → QgsPoint: <QgsPoint: Point (10 20)>
## QgsPointXY → QgsGeometry: <QgsGeometry: Point (10 20)>
## QgsPoint → QgsPointXY: <QgsPointXY: POINT(10 20)>
## ==================================================
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 2. 线状几何类型转换

from qgis.core import (
QgsPoint, QgsPointXY, QgsLineString, QgsPolygon,
QgsGeometry, QgsMultiPoint, QgsMultiLineString, QgsMultiPolygon
)
print("=== 线状几何类型转换 ===")

# 创建 QgsPolylineXY (QVector<QgsPointXY>)
polyline_xy = [QgsPointXY(0, 0), QgsPointXY(10, 0), QgsPointXY(10, 10), QgsPointXY(0, 10)]
print(f"QgsPolylineXY: {polyline_xy}")

# QgsPolylineXY → QgsLineString
line_string = QgsLineString()
for point in polyline_xy:
line_string.addVertex(QgsPoint(point))
print(f"QgsPolylineXY → QgsLineString: {line_string.asWkt()}")

# QgsLineString → QgsPolylineXY
polyline_from_line_string = [QgsPointXY(point) for point in line_string.points()]
print(f"QgsLineString → QgsPolylineXY: {polyline_from_line_string}")

# QgsPolylineXY → QgsGeometry
geom_from_polyline = QgsGeometry.fromPolylineXY(polyline_xy)
print(f"QgsPolylineXY → QgsGeometry: {geom_from_polyline.asWkt()}")

print("\n" + "="*50 + "\n")

## === 线状几何类型转换 ===
## QgsPolylineXY: [<QgsPointXY: POINT(0 0)>, <QgsPointXY: POINT(10 0)>, <QgsPointXY: POINT(10 10)>, <QgsPointXY: POINT(0 10)>]
## QgsPolylineXY → QgsLineString: LineString (0 0, 10 0, 10 10, 0 10)
## QgsLineString → QgsPolylineXY: [<QgsPointXY: POINT(0 0)>, <QgsPointXY: POINT(10 0)>, <QgsPointXY: POINT(10 10)>, <QgsPointXY: POINT(0 10)>]
## QgsPolylineXY → QgsGeometry: LineString (0 0, 10 0, 10 10, 0 10)

## ==================================================
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 3. 面状几何类型转换
print("=== 面状几何类型转换 ===")

# 创建 QgsPolygonXY (QVector<QgsPolylineXY>)
# 外环
exterior_ring = [QgsPointXY(0, 0), QgsPointXY(10, 0), QgsPointXY(10, 10), QgsPointXY(0, 10), QgsPointXY(0, 0)]
# 内环(孔洞)
interior_ring = [QgsPointXY(2, 2), QgsPointXY(8, 2), QgsPointXY(8, 8), QgsPointXY(2, 8), QgsPointXY(2, 2)]

polygon_xy = [exterior_ring, interior_ring]
print(f"QgsPolygonXY: 外环{len(exterior_ring)}个点, 内环{len(interior_ring)}个点")

# QgsPolygonXY → QgsPolygon
qgs_polygon = QgsPolygon()
exterior_line_string = QgsLineString([QgsPoint(p) for p in exterior_ring])
interior_line_string = QgsLineString([QgsPoint(p) for p in interior_ring])

qgs_polygon.setExteriorRing(exterior_line_string)
qgs_polygon.addInteriorRing(interior_line_string)
print(f"QgsPolygonXY → QgsPolygon: {qgs_polygon.asWkt()}")

# QgsPolygonXY → QgsGeometry
geom_from_polygon = QgsGeometry.fromPolygonXY(polygon_xy)
print(f"QgsPolygonXY → QgsGeometry: {geom_from_polygon.asWkt()}")

# QgsPolygon → QgsPolygonXY
polygon_xy_from_qgs = []
if qgs_polygon.exteriorRing():
exterior = [QgsPointXY(point) for point in qgs_polygon.exteriorRing().points()]
polygon_xy_from_qgs.append(exterior)

for i in range(qgs_polygon.numInteriorRings()):
interior = [QgsPointXY(point) for point in qgs_polygon.interiorRing(i).points()]
polygon_xy_from_qgs.append(interior)

print(f"QgsPolygon → QgsPolygonXY: {len(polygon_xy_from_qgs)}个环")

print("\n" + "="*50 + "\n")

## === 面状几何类型转换 ===
## QgsPolygonXY: 外环5个点, 内环5个点
## QgsPolygonXY → QgsPolygon: Polygon ((0 0, 10 0, 10 10, 0 10, 0 0),(2 2, 8 2, 8 8, 2 8, 2 2))
## QgsPolygonXY → QgsGeometry: Polygon ((0 0, 10 0, 10 10, 0 10, 0 0),(2 2, 8 2, 8 8, 2 8, 2 2))
## QgsPolygon → QgsPolygonXY: 2个环
## ==================================================
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 4. QgsGeometry 与其他类型的相互转换
print("=== QgsGeometry 转换演示 ===")

# 从各种类型创建 QgsGeometry
point_geom = QgsGeometry.fromPointXY(QgsPointXY(5, 5))
line_geom = QgsGeometry.fromPolylineXY([QgsPointXY(0, 0), QgsPointXY(5, 5)])
polygon_geom = QgsGeometry.fromPolygonXY([[QgsPointXY(0, 0), QgsPointXY(5, 0), QgsPointXY(5, 5), QgsPointXY(0, 5), QgsPointXY(0, 0)]])

print(f"点几何: {point_geom.asWkt()}")
print(f"线几何: {line_geom.asWkt()}")
print(f"面几何: {polygon_geom.asWkt()}")

# QgsGeometry → QgsPointXY
if point_geom.type() == QgsWkbTypes.PointGeometry:
point_from_geom = point_geom.asPoint()
print(f"QgsGeometry(点) → QgsPointXY: {point_from_geom}")

# QgsGeometry → QgsPolylineXY
if line_geom.type() == QgsWkbTypes.LineGeometry:
polyline_from_geom = line_geom.asPolyline()
print(f"QgsGeometry(线) → QgsPolylineXY: {polyline_from_geom}")

# QgsGeometry → QgsPolygonXY
if polygon_geom.type() == QgsWkbTypes.PolygonGeometry:
polygon_from_geom = polygon_geom.asPolygon()
print(f"QgsGeometry(面) → QgsPolygonXY: 外环{len(polygon_from_geom[0])}个点")

# QgsGeometry → QgsLineString
line_string_from_geom = QgsLineString(line_geom.constGet().clone())
print(f"QgsGeometry → QgsLineString: {line_string_from_geom.asWkt()}")

print("\n" + "="*50 + "\n")

## === QgsGeometry 转换演示 ===
## 点几何: Point (5 5)
## 线几何: LineString (0 0, 5 5)
## 面几何: Polygon ((0 0, 5 0, 5 5, 0 5, 0 0))
## QgsGeometry(点) → QgsPointXY: <QgsPointXY: POINT(5 5)>
## QgsGeometry(线) → QgsPolylineXY: [<QgsPointXY: POINT(0 0)>, <QgsPointXY: POINT(5 5)>]
## QgsGeometry(面) → QgsPolygonXY: 外环5个点
## QgsGeometry → QgsLineString: LineString (0 0, 5 5)

## ==================================================

矢量数据源

“分层设计,职责明确”一直是现代软件设计的基本原则之一,QGIS中矢量数据的数据源结构设计充分体现了该设计原则。QgsVectorDataProviderQgsVectorLayer分别代表数据源层和矢量数据应用层。

数据源层

5-vector_data_source

QGIS使用Provider 机制,实现对各个矢量数据源的独立封装。图中出现的QgsOgrProvider是基于OGR引擎的DataProvider。此外,如内存数据源、Postgres数据源、SpatiaLite数据源等均有针对性的实现。

数据源层负责如下功能:

  • 与数据源直接数据流动,要素检索、数据修改、写入等
  • 属性的增删改操作
  • 某些数据源直接支持创建并持有空间索引
  • 支持插件式加载,用户可以参考DataProvider的规范,为私有数据格式提供自定义的数据源

矢量图层

QgsVectorLayer为不同的数据类型提供了公共的接口。负责如下功能:

  • 提供操作缓冲区。对图层的任何编辑都暂存在缓冲区,仅在保存时才调用数据源层的函数写入到具体的数据源图层
  • 操作缓冲区,支持撤销和重做逻辑,支持批量回滚
  • 持有一个DataProvider ,在大批量数据操作时,DataProvier 更有优势

对比来看,QgsVectorLayer面向QGIS的用户操作,包括渲染、表达式、选择、缓存、编辑 buffer;QgsVectorDataProvider面向底层的数据源驱动,包括原始读写、高性能、直接操作存储等。

总结

好的,本小节重点学习了QGIS对矢量数据的概化、定义和描述,从要素属性、几何体和数据源三个方面阐述矢量数据格式在QGIS中的表达形式。

如果你觉得有所帮助,不妨为我买杯咖啡吧。

alipay

本节主要介绍PyQGIS中如何加载常见格式的矢量和栅格图层,和大家一起梳理QGIS对于图层生命周期的管理。

支持的数据格式

矢量数据格式

借助于ORG引擎的兼容能力,QGIS底层支持非常多的矢量数据源格式。

格式类别 格式名称 文件扩展名 简介与说明
QGIS 原生格式 QGIS 项目文件 .qgz, .qgs QGIS 的主项目文件,保存地图布局、图层样式、数据源路径等,但其本身不是数据格式。
QGIS 内存临时层 (无) 在 QGIS 会话期间创建的临时图层,用于中间处理,关闭后消失。
常见GIS格式 ESRI Shapefile .shp (主文件) 事实上的工业标准。由多个文件组成(.shp, .shx, .dbf 等)。功能成熟,但不支持复杂数据类型(如曲线)。
GeoPackage .gpkg 现代推荐格式。基于 SQLite 的单一文件格式,可包含矢量、栅格、样式、扩展等。是 Shapefile 的强大替代品。
GeoJSON .geojson, .json 基于 JSON 文本的格式,易于Web开发和数据交换。但文件体积较大,拓扑错误常见。
GML .gml, .xml 基于 XML 的开放标准,常用于数据互操作和Web服务(WFS)。
KML/KMZ .kml, .kmz Google Earth 的原生格式,KMZ 是压缩的 KML。在 QGIS 中打开和创建都非常方便。
数据库格式 PostGIS (无,连接数据库) 功能最强大的空间数据库。支持海量数据、空间拓扑、复杂查询、事务处理等,适合大型项目和企业级应用。
SpatiaLite .sqlite, .db 轻量级的单文件空间数据库,基于 SQLite。功能类似迷你版的 PostGIS,适合桌面和移动端。
ESRI File Geodatabase .gdb (目录) ESRI 的专有文件数据库格式。QGIS 可以读取编辑 File GDB,但创建功能可能有限制。
ESRI Personal Geodatabase .mdb 基于 Microsoft Access 的旧版 ESRI 格式。需要系统有相应驱动才能读取。
CAD格式 DXF .dxf AutoCAD 数据交换格式。QGIS 可以读取其中的图形和属性,并可导出为 DXF。
DWG .dwg AutoCAD 原生格式。需要通过 GDAL 的 DWGDriver 或使用第三方工具(如 ODA Converter)进行转换后读取。
Web数据服务 WFS (URL) Web要素服务,用于通过HTTP请求在线传输矢量数据。QGIS 可以连接并加载为可编辑的图层。
WMS (URL) Web地图服务,通常返回图像,但某些 WMS 也支持返回矢量要素(GetFeatureInfo)。
Vector Tiles .pbf, (URL) 金字塔分块的矢量数据,加载速度快,适合作为底图。QGIS 支持多种矢量切片协议。
其他格式 GPX .gpx GPS 数据交换格式,用于存储航点、轨迹和路线。
CSV/TXT .csv, .txt 逗号分隔文本文件。如果包含坐标列(如X/Y或WKT几何),QGIS 可以将其作为矢量图层打开。
MapInfo .tab, .mif/.mid MapInfo Professional 软件的格式。

栅格数据格式

借助GDAL引擎的强大能力,QGIS支持多种栅格数据格式。

高程数据格式

格式 扩展名 主要用途
GeoTIFF .tif 通用高程数据
SRTM HGT .hgt 航天飞机雷达地形数据
USGS DEM .dem 美国地质调查局DEM
Arc/Info ASCII Grid .asc 网格高程数据

网络服务格式

格式 类型 协议
WMS Web地图服务 HTTP
WMTS Web地图瓦片服务 HTTP
TMS 瓦片地图服务 HTTP
XYZ 标准瓦片服务 HTTP

加载图层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import os
import sys

from qgis._core import QgsRasterLayer
from qgis.core import QgsProject, QgsVectorLayer, QgsPointXY, QgsFeature, QgsGeometry, QgsField
from qgis.gui import QgsMapCanvas
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QToolBar, QAction
from PyQt5.QtCore import QVariant, Qt

current_path = os.path.abspath(__file__)
data_path = os.path.dirname(os.path.dirname(current_path))+"/data/"

class QGISMainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.canvas = None
self.initUI()

def initUI(self):
# 设置窗口标题和大小
self.setWindowTitle('QGIS 主界面')
self.setGeometry(100, 100, 1200, 800)

self.canvas = QgsMapCanvas()
self.canvas.setCanvasColor(Qt.white)
self.canvas.enableAntiAliasing(True)
self.setCentralWidget(self.canvas)
self.createToolbar()

def createToolbar(self):
toolbar = QToolBar("主工具栏")
self.addToolBar(toolbar)
refresh_action = QAction("加载所有矢量图层", self)
refresh_action.triggered.connect(self.loadAllVectorLayers)
toolbar.addAction(refresh_action)

def loadAllVectorLayers(self):
QgsProject.instance().removeAllMapLayers()

geojson_layer = self.loadGeojsonLayer(data_path + "test_points.geojson", "GeoJSON点图层")
shp_layer = self.load_shapefile(data_path + "test_lines.shp", "SHP线图层")
spatialite_layer = self.load_spatialite(data_path + "test_ploygons.sqlite", "new_layer")
raster_layer = self.load_tif(data_path + "random_china_raster.tif", "raster_layer")
QgsProject.instance().addMapLayer(geojson_layer)
QgsProject.instance().addMapLayer(shp_layer)
QgsProject.instance().addMapLayer(spatialite_layer)
QgsProject.instance().addMapLayer(raster_layer)

# 设置画布图层
self.canvas.setLayers([geojson_layer,shp_layer,spatialite_layer,raster_layer])
self.canvas.zoomToFullExtent()


def loadGeojsonLayer(self, geojson_path, layer_name):
layer = QgsVectorLayer(geojson_path, layer_name, "ogr")
# 检查图层是否有效加载
if not layer.isValid():
print(f"图层 {layer_name} 加载失败!")
return None
return layer

def load_shapefile(self, path, layer_name):
layer_name = "SHP图层"
layer = QgsVectorLayer(path, layer_name, "ogr")
if not layer.isValid():
print(f"图层 {layer_name} 加载失败!")
return None
return layer

def load_spatialite(self, path, layer_name):
layer = QgsVectorLayer(path.replace("\\", "/"), layer_name, "ogr")
# 检查图层是否有效加载
if not layer.isValid():
print(f"图层 {layer_name} 加载失败!")
return None
return layer
def load_tif(self, path, layer_name):
layer = QgsRasterLayer(path, layer_name)
if not layer.isValid():
print(f"图层 {layer_name} 加载失败!")
return None
return layer
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setApplicationName("PyQGIS 加载图层示例")
main_window = QGISMainWindow()
main_window.show()
sys.exit(app.exec_())
  • 程序直接移植了上一小节中的主界面代码

  • 从本地加载了三种矢量格式数据和一个栅格格式数据

  • 最终将图层显示到地图画布上

  • 下面我们来探讨一些较为底层的问题:

    • QGIS中图层的概念。QgsVectorLayer 和 QgsRasterLayer 是什么,他们之间有什么关系
    • 图层的生命周期
    • QgsMapCanvas 地图画布和图层的关系

QGIS的图层

图层概念

QGIS中,图层是单一地理要素集合的载体,它链接到数据源并定义了这些要素的视觉外观(样式)。由此我们可以清晰的知道图层由两个部分组成:数据源+样式。

  • 数据源:指存储在硬盘或者数据库中的数据,由几何体和属性组成,在GIS层名的表述为要素集。
  • 样式: 基于数据源的信息,描述了如何在界面上显示数据源中的要素,颜色?大小?ICON等。
  • 本节重点在于数据层面,样式层面的讨论我们放到后面的章节。

图层的继承结构

讲完了定义,我们一起来探究QGIS中的图层在代码层面如何抽象和实现。

classDiagram
    direction TB

    %% 根节点:抽象基类
    class QgsMapLayer {
        <<abstract>>
        #QgsMapLayerType type()
        +string name()
        +string id()
        +QgsCoordinateReferenceSystem crs()
        +QgsRectangle extent()
        +bool isValid()
    }

    %% 第一级子类:三种核心图层类型
    QgsMapLayer <|-- QgsVectorLayer
    QgsMapLayer <|-- QgsRasterLayer
    QgsMapLayer <|-- QgsMeshLayer
    QgsMapLayer <|-- QgsPointCloudLayer
    QgsMapLayer <|-- QgsAnnotationLayer
    QgsMapLayer <|-- QgsGroupLayer
    QgsMapLayer <|-- QgsPluginLayer

    %% 矢量图层
    class QgsVectorLayer {
        +QgsFeatureIterator getFeatures()
        +QgsVectorDataProvider* dataProvider()
        +QgsFields fields()
        +bool addFeature()
        +bool deleteFeature()
        +QgsGeometryType geometryType()
        #bool readXml()
        #bool writeXml()
    }

    %% 栅格图层
    class QgsRasterLayer {
        +QgsRasterDataProvider* dataProvider()
        +int width()
        +int height()
        +QgsRasterRenderer* renderer()
        #bool readXml()
        #bool writeXml()
    }

    %% 网格图层 (主要用于水文、气象数据)
    class QgsMeshLayer {
        +QgsMeshDataProvider* dataProvider()
        +int datasetGroupCount()
        +QgsMeshRendererSettings rendererSettings()
        #bool readXml()
        #bool writeXml()
    }

    %% 点云图层 (如LiDAR数据)
    class QgsPointCloudLayer {
        +QgsPointCloudDataProvider* dataProvider()
        +QgsPointCloudAttributeCollection attributes()
        +QgsPointCloudRenderer* renderer()
        #bool readXml()
        #bool writeXml()
    }

    %% 注记图层 (用于存储地图标注、图形等)
    class QgsAnnotationLayer {
        +QList~QgsAnnotationItem~ items()
        +void addItem()
        +void removeItem()
        #bool readXml()
        #bool writeXml()
    }

    %% 图层组 (用于管理多个子图层)
    class QgsGroupLayer {
        +QList~QgsMapLayer~ childLayers()
        +void setChildLayers()
        #bool readXml()
        #bool writeXml()
    }

    %% 插件图层 (已废弃,用于旧版插件)
    class QgsPluginLayer {
        <<abstract>>
        +string pluginLayerType()
        #bool readXml()
        #bool writeXml()
    }
  1. 抽象基类 QgsMapLayer:
    • 它是所有具体图层类型的父类
    • 定义了所有图层都必须具有的通用接口和属性,例如:
      • name(): 图层名称
      • id(): 唯一ID
      • crs(): 坐标参考系统
      • extent(): 图层的地理范围
      • isValid(): 检查图层是否有效加载
    • 它本身是抽象的,你不能直接创建一个 QgsMapLayer 对象。
  2. 核心具体类:
    • QgsVectorLayer: 处理矢量数据(点、线、面)。这是最常用、功能最丰富的图层类之一。
    • QgsRasterLayer: 处理栅格数据(图像、DEM等)。
    • QgsMeshLayer: 处理非结构网格数据,常用于水文、海洋、气象模拟(如流速、水温)。
    • QgsPointCloudLayer: 处理点云数据(如LiDAR激光雷达)。
    • QgsVectorTileLayer: 处理矢量瓦片数据。
  3. 结构与组织类:
    • QgsAnnotationLayer: 用于管理地图上的临时注记、图形、文本等,独立于原始数据。
    • QgsGroupLayer: 一个“容器”图层,可以将多个其他图层组合在一起,作为一个单元进行管理(如显示/隐藏)。

可以清晰的看到,QGIS中的图层对象是一个非常典型的父类-子类的继承结构。QgsMapLayer作为抽象基类,负责描述公共的通用的信息。各个子类根据需要在抽象基类上进行功能扩展。

对比QgsVectorLayer和QgsRasterLayer:

特性维度 QgsVectorLayer (矢量图层) QgsRasterLayer (栅格图层)
数据模型 基于几何对象:点(Point)、线(LineString)、面(Polygon) 基于像素矩阵,每个像素有数值
数据结构 由**要素(Feature)**组成,每个要素包含: 几何形状 (Geometry) 和 属性 (Attributes)。 由**波段(Band)**组成,每个波段是数值矩阵。 单波段:灰度、高程和多波段:RGB彩色影像。
数据来源示例 Shapefile GeoPackage PostGIS GeoJSON GeoTIFF JPEG/PNG DEM文件
数据提供者 QgsVectorDataProvider QgsRasterDataProvider
编辑能力 完全可编辑: • 添加/删除要素 • 修改几何形状 • 编辑属性表 基本不可编辑: • 主要只读 • 可通过插件进行有限栅格绘图
分析操作 • 缓冲区分析 • 相交/联合 • 最近邻分析 • 网络分析 • 栅格计算器 • 坡度/坡向分析 • 重分类 • 栅格矢量化
属性查询 强大: • 基于SQL的查询 • 复杂的属性过滤 有限: • 主要基于像素值 • 无复杂属性结构
空间查询 • 包含、相交、相邻等 • 精确的几何关系判断 • 基于位置提取像素值 • 区域统计
精度 高精度: • 明确的边界和坐标 • 不受缩放级别影响 分辨率依赖: • 精度受像素大小限制 • 缩放过大会出现像素化
  • QgsVectorLayer
    • 描述矢量数据图层,其底层的数据结构采用了 几何体+属性 -> 要素 这样的表达
    • 支持编辑修改,提供了新增、删除、修改要素等功能
    • 提供强大的要素检索功能
  • QgsRasterLayer
    • 描述栅格数据图层,其底层采用多波段像素矩阵
    • 常用于统计分析

图层的生命周期

程序中的任意一个对象都会占用一定的内存空间,在python中,内存的申请和释放由解释器自动处理,不需要开发者过多的关注。但是,当开发者面对一个体量较大的,由C++封装的库时,这个问题会隐隐约约的出现在开发的各个环节。如果没有清晰明确的理解QGIS程序中对于图层生命周期的管理,很可能导致程序出现异常的内存释放,进而导致程序崩溃。

图层“消失”了

通俗来讲,图层的生命周期代表着图层对象的创建、修改、使用和消亡的全过程。为了更直观的表现生命周期的影响,我们对上面的代码做如下改动:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def loadAllVectorLayers(self):
QgsProject.instance().removeAllMapLayers()

geojson_layer = self.loadGeojsonLayer(data_path + "test_points.geojson", "GeoJSON点图层")
shp_layer = self.load_shapefile(data_path + "test_lines.shp", "SHP线图层")
spatialite_layer = self.load_spatialite(data_path + "test_ploygons.sqlite", "new_layer")
raster_layer = self.load_tif(data_path + "random_china_raster.tif", "raster_layer")
#QgsProject.instance().addMapLayer(geojson_layer)
#QgsProject.instance().addMapLayer(shp_layer)
#QgsProject.instance().addMapLayer(spatialite_layer)
#QgsProject.instance().addMapLayer(raster_layer)

# 设置画布图层
self.canvas.setLayers([geojson_layer,shp_layer,spatialite_layer,raster_layer])
self.canvas.zoomToFullExtent()

修改代码后,图层将不会显示在地图画布上。这是为什么呢? 按常规的思路分析,self.canvas.setLayers([geojson_layer,shp_layer,spatialite_layer,raster_layer])此句代码应该是将图层显示在画布上,只要执行到该代码,画布上应该会出现四个图层的内容,但是很遗憾,程序并没有绘制出我们期待的结果。

谁在管理图层的内存

python中,当一个对象被持有时,其内存会一直存在;当一个对象没有被任何引用指向时,会触发垃圾回收。根据上面注释后的代码分析,可以得到如下步骤:

  1. 创建geojson_layer
  2. 设置给了canvas对象
  3. 随后离开了loadAllVectorLayers的作用域
  4. 离开作用域后,因为没有被指向,所以触发了geojson_layer 的垃圾回收,图层对象不再可用。所以,图层没有被正确的绘制到地图画布上

那么,聪明的你可能要问了,在步骤2中,已知geojson_layer被设置给了canvas对象,为什么离开作用域会被回收呢?因为canvas对象并没有持有图层对象。

至此,答案已经非常清晰了。谁在管理图层的内存呢? 答案就是 QgsProject.instance()QgsProject.instance() 是QGIS中的一个全局单例,负责管理项目的属性和配置,管理图层。读者如果对单例不太了解,可以简单的理解为存在于程序整个生命过程的一个变量,在程序结束时才会被销毁。

应该如何管理图层的生命周期

在一个单纯的GIS程序中,一般只存在一个地图画布。针对需要显示在画布上的一组图层,可以直接交给全局单例来管理,QGIS的底层可以很好的处理这个问题。

当我们开发的程序是一个含有多个地图画布的程序,每个地图画布都需要显示一组区别于其它画布的图层,此时需要我们手动来创建一个对象负责管理该组图层,管理对象的生存范围要大于等于地图画布的范围。

地图画布与图层的关系

经过上面的分析,我们可以非常容易的得出一个结论,两者在内存方面没有任何的持有关系。两者关系可以理解为数据和界面的分层设计:

  • 图层持有数据和样式,告诉画布如何将它自身的内容渲染出来
  • 画布执行渲染流程,按照图层的渲染需求进行渲染
  • 图层的变化会直接触发画布的重绘

总结

好的,本小节我们从图层加载和显示出发,学习了QGIS支持的矢量格式和栅格格式。在此基础上,我们深入探讨了图层的概化及实现、图层的生命周期管理、画布与图层的关系等内容。

如果你觉得有所帮助,不妨为我买杯咖啡吧。

alipay

从本节开始,我们正式进入PyQGIS的开发教程。让我们从PyQGIS的开发环境搭建开始吧!

工具准备

搭建一套PyQGIS开发环境,我们需要安装如下工具:

  1. QGIS客户端软件,最好是ltr版本,本次以3.34版本为例。 可以直接到官网下载。
  2. PyCharm , python开发的IDE。

配置步骤

  1. 新建python项目,选择python解释器为:{OSGeo4W_root}/bin/python-qgis-ltr.bat{OSGeo4W_root}代表OSGeo4W的根路径。

    3-python_env

  2. 配置Pyqt开发需要的额外工具,主要是DESIGNER UIC RCC三个工具。为了方便使用,在Pycharm中以 External Tool 的形式引入。

    • QTDesigner: QT界面设计工具
      • Working directory: \$FileDir\$

    3-qt_designer

    • UIC: QT 界面编译工具
      • Working directory: \$FileDir\$
      • Arguments: -m PyQt5.uic.pyuic $FileName$ -o $FileNameWithoutExtension$.py

    3-qt_UIC

    • RCC : QT 资源编译工具

      • Working directory: \$FileDir\$

      • Arguments: -m PyQt5.pyrcc_main $FileName$ -o $FileNameWithoutExtension$_rc.py

    3-qt_rcc

  3. 包管理

    QGIS客户端的自带了一套完整的python环境,预先安装了包括 pyqt5 , gdal ,pyproj 在内的多个依赖库。根据业务需要,也可以手动安装需要的其它依赖包。

    1
    ./bin/python-qgis-ltr.bat -m pip install -r requirements.txt

跑个程序试试

经过一系列复杂的安装和配置之后,我们终于完成的PyQGIS开发环境的搭建。下面跑个程序试试吧。

在mian函数中引入qigs包,获得qgis的版本并打印。

1
2
3
4
5
6
7
8
9
10
11
12
13
from qgis.core import Qgis
from qgis.core import QgsApplication
if __name__ == '__main__':
qgs = QgsApplication([], True)
qgs.setPrefixPath('qgis', True)
qgs.initQgis()
version = Qgis.version()
print(QgsApplication.prefixPath())
print("Hello qgis, version is {} ".format( version))
qgs.exitQgis()

## C:/OSGeo4W/apps/qgis-ltr
## Hello qgis, version is 3.34.6-Prizren

第一个PyQGIS界面程序,启动!

我们需要回忆(或者重新学习)一下pyqt5的相关知识。pyqt5是一个qt5版本在python中的封装库,几乎可以在python环境中使用qt相关的所有功能。

现在我们想要实现一个主界面,主界面的中央是一个qgis的画布,显示一个随机生成的点图层。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import sys
import random

from qgis.core import QgsProject, QgsVectorLayer, QgsPointXY, QgsFeature, QgsGeometry, QgsField
from qgis.gui import QgsMapCanvas
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QToolBar, QAction
from PyQt5.QtCore import QVariant, Qt


class QGISMainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
# 设置窗口标题和大小
self.setWindowTitle('QGIS 主界面 - 随机点图层')
self.setGeometry(100, 100, 1200, 800)

# 创建中央部件
central_widget = QWidget()
self.setCentralWidget(central_widget)

layout = QVBoxLayout(central_widget)

# 创建QGIS地图画布
self.canvas = QgsMapCanvas()
self.canvas.setCanvasColor(Qt.white)
self.canvas.enableAntiAliasing(True)

# 将画布添加到布局中
layout.addWidget(self.canvas)

# 创建工具栏
self.createToolbar()

# 生成随机点图层
self.createRandomPointLayer()

# 设置画布范围
self.zoomToLayer()

def createToolbar(self):
"""创建工具栏"""
toolbar = QToolBar("主工具栏")
self.addToolBar(toolbar)

# 刷新按钮
refresh_action = QAction("刷新点图层", self)
refresh_action.triggered.connect(self.refreshPoints)
toolbar.addAction(refresh_action)

# 缩放至图层按钮
zoom_action = QAction("缩放至图层", self)
zoom_action.triggered.connect(self.zoomToLayer)
toolbar.addAction(zoom_action)

def createRandomPointLayer(self):
# 创建内存图层
self.point_layer = QgsVectorLayer("Point?crs=EPSG:4326", "随机点", "memory")
provider = self.point_layer.dataProvider()

# 添加属性字段
provider.addAttributes([QgsField("id", QVariant.Int),
QgsField("x", QVariant.Double),
QgsField("y", QVariant.Double)])
self.point_layer.updateFields()

# 生成随机点
features = []
for i in range(50): # 生成50个随机点
# 在经纬度范围内生成随机点
x = random.uniform(116.0, 117.0) # 经度范围
y = random.uniform(39.0, 40.0) # 纬度范围

# 创建点几何
point = QgsPointXY(x, y)
geometry = QgsGeometry.fromPointXY(point)

# 创建要素
feature = QgsFeature()
feature.setGeometry(geometry)
feature.setAttributes([i, x, y])
features.append(feature)

# 添加要素到图层
provider.addFeatures(features)
self.point_layer.updateExtents()

# 将图层添加到项目
QgsProject.instance().addMapLayer(self.point_layer)

# 设置画布图层
self.canvas.setLayers([self.point_layer])

def refreshPoints(self):
"""刷新随机点"""
# 移除旧图层
QgsProject.instance().removeMapLayer(self.point_layer.id())

# 生成新图层
self.createRandomPointLayer()

def zoomToLayer(self):
"""缩放至图层范围"""
if self.point_layer:
self.canvas.setExtent(self.point_layer.extent())
self.canvas.refresh()



if __name__ == '__main__':
# 创建QApplication实例
app = QApplication(sys.argv)

# 设置应用信息
app.setApplicationName("QGIS随机点示例")

# 创建并显示主窗口
main_window = QGISMainWindow()
main_window.show()

# 运行应用
sys.exit(app.exec_())
  • 新建一个QGISMainWindow 继承自QMainWindow,用来描述主界面

  • 新建一个 QgsMapCanvas对象,放置到中央窗体中

  • 创建两个工具按钮,用于刷新和缩放到点图层

  • createRandomPointLayer 函数用于创建随机点图层。更多的细节将在之后的章节中详细阐述,包括图层创建、要素属性设置、内存管理等。

  • 最终运行成果如下图:

    3-example1

结语

本小节我们完成了一个PyQGIS开发环境从无到有的配置。跟随笔者的步伐,我们可以轻松的创建出第一个PyQGIS界面程序。请大家一定要动手实现哦。

如果你觉得有所帮助,不妨为我买杯咖啡吧。

alipay

入门

本节主要介绍如何搭建QGIS的python控制台开发环境,完成第一个嵌入程序

安装QGIS并打开控制台

控制台应用程序的QGIS安装非常简单,QGIS应用程序内置了python控制台,所以正常安装的QGIS程序可以直接作为开发环境。

2-open_console

打开后,你会看到一个类似命令行的窗口,主要由三部分组成:

  1. 主输入区: 窗口下半部分,带有 >>> 提示符。这是你输入代码的地方。

  2. 输出/历史面板: 窗口上半部分,显示你执行的命令历史、代码运行结果和任何错误信息。

  3. 工具栏:

    • 清除控制台: 清空历史记录。

    • 运行脚本: 加载并执行外部的 .py 脚本文件。

    • 保存脚本: 将你在控制台中输入的代码保存到文件。

    • 显示编辑器: 打开一个多行代码编辑器,方便编写更复杂的脚本。

2-open_console1

​ 至此我们可以把python console 当做一个基础的python环境来使用,试试打印“hello world”吧。

2-console_helloworld

基础操作

在开始之前,我们先简单回忆一下QGIS中对图层、要素和几何体的表达。

graph TD
    A[QGIS Main Window] --> B[Map Canvas]
    
    B --> C[Layer 1]
    B --> D[Layer 2]
    B --> E[...]
    B --> F[Layer N]
    
    C --> C1[Feature 1]
    C --> C3[...]
    C --> C4[Feature M]
    
    D --> D1[Feature 1]
    D --> D3[...]
    D --> D4[Feature K]
    
    C1 --> C1a[Geometry]
    C1 --> C1b[Attributes]
    
    
    D1 --> D1a[Geometry]
    D1 --> D1b[Attributes]
    
    C1a --> C1a1[Point]
    C1a --> C1a2[LineString]
    C1a --> C1a3[Polygon]
    C1a --> C1a4[MultiGeometry]
    
    %% 样式定义
    classDef canvas fill:#e1f5fe,stroke:#01579b,stroke-width:2px
    classDef layer fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
    classDef feature fill:#e8f5e8,stroke:#1b5e20,stroke-width:1px
    classDef geometry fill:#fff3e0,stroke:#e65100,stroke-width:1px
    
    class B canvas
    class C,D,E,F layer
    class C1,C2,C3,C4,D1,D2,D3,D4 feature
    class C1a,C2a,D1a,D2a geometry

简单来说:

  • QGIS画布可以同时呈现多个矢量图层

  • 一个矢量图层包含多个固定格式(含有相同的属性字段)的要素

  • 一个要素由多个属性字段和一个几何体组成

另外,我们需要介绍一个新朋友 iface.

  • iface是qgis.utils模块中的一个特殊变量,代表整个QGIS图形界面的实例。它是我们与QGIS UI界面交互的桥梁。

好的,让我们小试牛刀吧!

  1. mapCanvas() 地图画布

    1
    2
    3
    4
    5
    canvas = iface.mapCanvas()
    extent = canvas.extent()
    print(f"当前地图范围是:{extent.toString()}")

    ## 当前地图范围是:-3.5000000000000000,-1.0000000000000000 :3.5000000000000000,1.0000000000000000
  2. 创建新的图层与几何体

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    # 创建一个内存中的点图层
    vlayer = QgsVectorLayer("Point?crs=epsg:4326", "我的临时点", "memory")
    provider = vlayer.dataProvider()

    # 添加一个属性字段
    provider.addAttributes([QgsField("名称", QVariant.String)])
    vlayer.updateFields()

    # 创建一个新的点要素
    feat = QgsFeature()
    feat.setAttributes(["上海"])

    # 设置几何体 (经度,纬度)
    point = QgsGeometry.fromPointXY(QgsPointXY(121.47, 31.23))
    feat.setGeometry(point)

    # 将要素添加到图层
    provider.addFeatures([feat])

    # 将新图层添加到项目中
    QgsProject.instance().addMapLayer(vlayer)
    print("新点图层已创建并添加到地图!")
    • 此时可以在图层窗口的看到,新建一个点图层。 该点图层含有一个坐标为(121.47, 31.23)的点要素。
  3. 与图层进行交互

    1
    2
    3
    4
    5
    6
    7
    active_layer = iface.activeLayer()
    if active_layer:
    print(f"当前激活图层是:{active_layer.name()}")
    else:
    print("没有激活的图层!")

    ## 当前激活图层是:我的临时点
    1
    2
    3
    4
    5
    layers = QgsProject.instance().mapLayers()
    for layer_id, layer in layers.items():
    print(f"图层ID: {layer_id}, 图层名: {layer.name()}")

    ## 图层ID: ______8330813e_ecdc_4093_9b77_def51f9d5080, 图层名: 我的临时点
  4. 遍历要素与属性查询

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    layer = iface.activeLayer()
    if not layer or not layer.isValid():
    print("图层无效!")
    else:
    # 遍历所有要素
    for feature in layer.getFeatures():
    # 打印要素的ID和属性(假设有一个名为 'name' 的字段)
    print(f"要素ID: {feature.id()}, 名称: {feature['名称']}")
    # 获取几何体信息
    geom = feature.geometry()
    print(f" 几何类型: {geom.type()}, 中心点: {geom.centroid().asPoint()}")

    ## 要素ID: 1, 名称: 上海
    ## 几何类型: 0, 中心点: <QgsPointXY: POINT(121.46999999999999886 31.23000000000000043)>

地图工具

地图工具是用户与QGIS地图进行交互的主要方式,了解和学习QGIS中地图工具的使用对于二次开发大有裨益。

iface通过触发界面的action实现工具间的切换

主类别 子类别 工具名称 功能描述 对应 iface 方法
导航工具 缩放工具 缩放至选择 缩放至选中要素范围 iface.actionZoomToSelected().trigger()
缩放至图层 缩放至活动图层范围 iface.zoomToActiveLayer()
放大/缩小 按比例缩放地图 iface.actionZoomIn().trigger()
平移工具 平移地图 拖动地图移动视图 iface.actionPan().trigger()
选择工具 几何选择 矩形选择 矩形框选要素 iface.actionSelectRectangle().trigger()
多边形选择 多边形框选要素 iface.actionSelectPolygon().trigger()
自由手绘选择 手绘形状选择 iface.actionSelectFreehand().trigger()
半径选择 圆形区域选择 iface.actionSelectRadius().trigger()
测量工具 空间量测 距离测量 测量线段长度 iface.actionMeasure().trigger()
面积测量 测量多边形面积 iface.actionMeasureArea().trigger()
识别工具 属性查询 要素识别 点击查看要素属性 iface.actionIdentify().trigger()
编辑工具 编辑会话 开始编辑 进入编辑模式 iface.actionToggleEditing().trigger()
保存编辑 保存修改内容 iface.actionSaveEdits().trigger()
取消编辑 放弃所有修改 iface.actionCancelEdits().trigger()
要素操作 添加要素 创建新要素 iface.actionAddFeature().trigger()
删除要素 移除选中要素 iface.actionDeleteSelected().trigger()
移动要素 移动要素位置 iface.actionMoveFeature().trigger()
几何编辑 节点工具 编辑要素顶点 iface.actionVertexTool().trigger()
简化要素 简化要素几何 iface.actionSimplifyFeature().trigger()
分割要素 分割要素几何 iface.actionSplitFeatures().trigger()

地图工具的底层结构

classDiagram
    %% 核心抽象基类
    class QgsMapTool {
        <<abstract>>
        #QgsMapCanvas* mCanvas
        +canvasMoveEvent(QgsMapMouseEvent* e)
        +canvasPressEvent(QgsMapMouseEvent* e)
        +canvasReleaseEvent(QgsMapMouseEvent* e)
        +keyPressEvent(QKeyEvent* e)
        +activate()
        +deactivate()
        +setCursor(QCursor cursor)
    }

   class QgsMeasureTool {
        +canvasPressEvent(...)
        +canvasMoveEvent(...)
        +canvasReleaseEvent(...)
    }
 	QgsMapTool <|-- QgsMeasureTool

    %% 基本交互工具
    class QgsMapToolPan {
        +canvasPressEvent(...)
        +canvasMoveEvent(...)
        +canvasReleaseEvent(...)
    }
    QgsMapTool <|-- QgsMapToolPan

    class QgsMapToolZoom {
        +canvasPressEvent(...)
        +canvasMoveEvent(...)
        +canvasReleaseEvent(...)
    }
    QgsMapTool <|-- QgsMapToolZoom
    
    class QgsMapToolIdentify {
        +canvasReleaseEvent(...)
    }
    QgsMapTool <|-- QgsMapToolIdentify

    class QgsMapToolZoom {
        +canvasReleaseEvent(...)
    }
    QgsMapTool <|-- QgsMapToolZoom

    %% 要素选择工具
    class QgsMapToolSelect {
        +canvasReleaseEvent(...)
    }
    QgsMapTool <|-- QgsMapToolSelect

    %% 基础编辑工具

    class QgsMapToolEdit {
        <<abstract>>
        +cadDockManage()
        +currentVectorLayer()
    }
    QgsMapTool <|-- QgsMapToolEdit

  • 所有的地图工具均继承自QgsMapTool
  • 根据需要可以编写自己的地图工具

简单案例1

在此处我们实现一个简单案例。假设存在一个多边形图层和一个点图层。现在需要自定义一个选择工具,在选中多边形后,自动将多边形范围内的点标识为选中状态。

案例的最终实现请参考 https://github.com/c101088/QGIS_Example.git 中的example1.py

实现效果如下:

2-example1

总结

好的,至此我们已经对QGIS的python控制台程序有了基本的认知。本小节的内容非常的简略,走马观花式的了解python控制的基础功能。之后的章节会深入的讲解QGIS的各种概念、设计逻辑以及程序实现。掌握这些知识之后,参考官方手册再来写控制台程序,就会得心应手。

如果你觉得有所帮助,不妨为我买杯咖啡吧。

alipay

前言

​ 地理信息系统(GIS)早已超越了传统地图学的范畴,它作为一门融合了计算机科学、地理学、空间统计学的交叉学科,正深刻地改变着我们理解、管理和决策世界的方式。在众多GIS软件中,QGIS凭借其开源、免费、功能强大且社区活跃的特质,已成为全球数百万用户的首选工具。

​ 当面对重复性的工作流程、独特的业务需求或是复杂的系统集成时,标准化的QGIS桌面功能有时会显得力不从心。QGIS二次开发可以帮助我们扩展QGIS应用程序功能,更进一步,甚至基于QGIS的组件进行自定义应用程序的开发,你可以将繁琐的操作自动化,将专业的分析模型化,甚至打造出专属于你个人或行业的定制化GIS应用。

为什么选择本教程?

​ 市面上关于QGIS操作的资料已非常丰富,但系统性地引导开发者进入二次开发领域的中文资源却相对零散。本教程的诞生,正是为了填补这一空白。本教程基于笔者的二次开发经验,与大家一道学习和掌握QGIS二次开发。本教程核心目标在于:

  • 构建坚实的认知框架:我们不仅教你“怎么做”,更着重解释“为什么这么做”,帮助你理解QGIS的对象模型与核心架构。
  • 遵循渐进的学习路径:教程的设计遵循“由浅入深、螺旋上升”的原则。我们从最简单的示例入手,逐步过渡到图形界面,最终完成综合性的示例项目。
  • 全面与深度:本教程涵盖嵌入QGIS程序的python控制台开发、基于PYQGIS的独立应用程序开发以及基于C++的独立应用程序开发,覆盖了QGIS二次开发的多种需求。
  • 强调实战与应用:理论唯有通过实践才能内化为技能。因此,每一章都配备了可操作的代码示例,确保你能学以致用。

目录大纲概览(暂定)

  • 第一部分:入门:我们将帮助你搭建高效的开发环境,并熟悉QGIS Python控制台这个强大的“实验沙盒”,迈出代码驱动的第一步。
  • 第二部分:核心概念与基础操作:这是课程的基石。我们将深入探讨QGIS项目的结构、矢量与栅格数据的编程处理方式,让你具备操纵GIS核心要素的能力。
  • 第三部分:创建用户界面与自定义工具:从这里开始,我们将从脚本走向应用。你将学习如何使用Qt Designer设计对话框,如何构建标准的QGIS插件,以及如何创建可与Processing框架集成的分析算法,从而将你的代码包装成用户友好的工具。
  • 第四部分:基于C++ 的QGIS独立程序开发:在掌握了python开发QGIS的相关知识后,我们将探索基于C++的独立应用程序开发,将此前所学的知识融会贯通,一起创建一个属于你自己的跨平台、高性能的GIS工具。

启程前的准备

为了能顺利地跟随本教程学习,我们期望你具备以下基础知识:

  • 基本的Python编程能力:熟悉变量、数据类型、函数、循环和条件判断等基础语法,并对面向对象编程(类、对象、方法)有初步了解。
  • 对QGIS基本操作的了解:你应该知道如何加载数据、操作图层、进行一些基本的空间查询与分析。这份教程的重点是“开发”,而非“软件操作”。
  • 对QT编程有基础的了解:QGIS是基于跨平台的QT GUI框架开发,利用了许多QT的特性与能力。了解QT才能更好的理解本教程的相关内容
  • 基本的C++编程能力:如果您需要学习C++开发QGIS的相关知识,那么C++的编程基础是必不可少的。
  • 一颗乐于探索与解决问题的热心:开发过程中遇到问题与报错是常态,官方文档、在线社区和你的调试技巧将是你最好的伙伴。
0%