前言
说明
API (Application Programming Interface)
摘要
关于QGIS Python API
解密C ++文档
组织QGIS Python库
qgis.core包
地图和地图图层
协调参考系统
矢量图层
显示矢量数据
访问矢量数据
空间索引
栅格图层
显示栅格数据
访问栅格数据
其他有用的qgis.core类
qgis.gui包
QgisInterface类
QgsMapCanvas类
QgsMapCanvasItem类
QgsMapTool类
其他有用的qgis.gui类
使用PyQGIS库
分析栅格数据
操纵矢量数据并将其保存到shapefile
在地图中为不同的要素使用不同的符号
计算两个用户定义点之间的距离
文章资源
原版英文由作者Packet创作,此处首先感谢Packet的辛苦创作。此处借助翻译软件,做了全网页翻译,方便大家阅读理解,不妥或错误的地方,请谅解,大家可参考原作网址。
原作网址:Learning the QGIS Python API,作者: Packet,日期:December 23, 2014 - 12:00 am
在计算机编程中,应用程序编程接口(API)是一组子例程定义,通信协议和用于构建软件的工具。一般而言,它是一组明确定义的各种组件之间的通信方法。一个好的API可以通过提供所有构建块来更容易地开发计算机程序,然后由程序员将它们组合在一起。
API可以用于基于web的系统,操作系统,数据库系统,计算机硬件或软件库。
API规范可以采用多种形式,但通常包括例程,数据结构,对象类,变量或远程调用的规范。POSIX,Windows API和ASPI是不同形式的API的示例。通常提供API的文档以便于使用和实现。 ---译自Wikipedia (API)
在本文中,我们深入了解了PyQGIS库以及如何在自己的程序中使用它们。我们了解到QGIS Python库是围绕用C ++实现的QGIS API的包装器实现的。我们看到了Python程序员如何理解和使用QGIS参考文档,即使它是为C ++开发人员编写的。我们还研究了PyQGIS库组织到不同包中的方式,并了解了qgis.core和qgis.gui包中定义的最重要的类。
然后我们看到如何使用坐标参考系统(CRS)从地球三维表面上的点转换为二维地图平面内的坐标。
我们了解到矢量格式数据由特征组成,其中每个特征都具有ID,几何和一组属性,并且这些符号用于将矢量几何绘制到地图图层上,而渲染器用于选择哪个符号用于给定的功能。
我们学习了如何使用空间索引来加速对矢量要素的访问。
下来,我们看到如何将栅格格式数据组织成表示颜色,高程等信息的波段,并查看栅格数据源在地图图层中的各种显示方式。在此过程中,我们学习了如何访问栅格数据源的内容。
最后,我们研究了使用PyQGIS库执行有用任务的各种技术。
在下一篇文章中,我们将了解有关QGIS Python插件的更多信息,然后继续使用插件体系结构作为在地图应用程序中实现有用功能的一种方式。
在本文中,我们将详细介绍可用于QGIS Python开发人员的Python库,并查看我们可以使用这些库在QGIS中执行有用任务的各种方法。
特别是,您将学习:
QGIS系统本身是用C ++编写的,它有自己的API集,也是用C ++编写的。Python API实现为围绕这些C ++ API的包装器。例如,有一个名为QgisInterface的Python类,它充当同名C ++类的包装器。由C ++版本的QgisInterface实现的所有方法,类变量等都可以通过Python包装器获得。
这意味着当您访问Python QGIS API时,您不会直接访问API。相反,包装器将您的代码连接到底层C ++对象和方法,如下所示:
幸运的是,在大多数情况下,QGIS Python包装器只是隐藏了底层C ++代码的复杂性,因此PyQGIS库可以像您期望的那样工作。然而,有一些问题,我们将在它们出现时涵盖这些问题。
由于QGIS是用C ++实现的,QGIS API的文档都基于C ++。这可能使Python开发人员难以理解和使用QGIS API。例如,QgsInterface.zoomToActiveLayer()方法的API文档:
如果你不熟悉C ++,这可能会令人困惑。幸运的是,作为Python程序员,您可以跳过大部分复杂性,因为它不适用于您。特别是:
就像在Python中一样,括号表明该方法不带任何参数。因此,如果您有QgisInterface的实例(例如,作为Python控制台中可用的标准iface变量),您只需键入以下内容即可调用此方法:
iface.zoomToActiveLayer()
现在,让我们看一个稍微复杂的例子:QgisInterface.addVectorLayer()方法的C ++文档如下所示:
从技术上讲,*表示该方法返回一个指向QgsVectorLayer类型对象的指针。幸运的是,Python包装器会自动处理指针,因此您无需担心这一点。
请注意此方法文档底部的简要说明; 虽然许多C ++方法只有很少(如果有的话)附加信息,但其他方法有相当广泛的描述。显然,您应该仔细阅读这些描述,因为它们会告诉您有关该方法的更多信息。
即使没有任何描述,C ++文档仍然很有用,因为它告诉您调用方法的内容,接受的参数以及返回的数据类型。
在上述方法中,您可以看到括号中列出了三个参数。由于C ++是一种强类型语言,因此在定义函数时必须定义每个参数的类型。这对Python程序员很有用,因为它告诉你要提供什么类型的值。除QGIS对象外,您可能还会在C ++文档中遇到以下数据类型:
数据类型 | 描述 |
int | 标准Python整数值 |
long | 标准的Python长整数值 |
float | 标准Python浮点(实数) |
bool | 布尔值(true或false) |
QString | 字符串值。请注意,QGIS Python包装器会自动将Python字符串转换为C ++字符串,因此您无需直接处理QString对象 |
QList | 此对象用于封装其他对象的列表。例如,QList 表示字符串列表 |
就像在Python中一样,方法可以为每个参数采用默认值。例如,QgisInterface.newProject()方法如下所示:
在这种情况下,thePromptToSaveFlag参数具有默认值,如果未提供任何值,则将使用此默认值。
在Python中,使用__init__方法初始化类。在C ++中,这称为构造函数。例如,QgsLabel类的构造函数如下所示:
就像在Python中一样,C ++类继承了超类中定义的方法。幸运的是,QGIS没有广泛的类层次结构,因此大多数类都没有超类。但是,如果在类本身的文档中找不到您要查找的方法,请不要忘记检查超类。
最后,请注意C ++支持方法重载的概念。可以多次定义单个方法,其中每个版本接受一组不同的参数。例如,看一下QgsRectangle类的构造函数- 您将看到此方法有四个不同的版本。
第一个版本接受四个坐标作为浮点数:
第二个版本使用两个QgsPoint对象构造一个矩形:
第三个版本将坐标从QRectF(Qt数据类型)复制到QgsRectangle对象:
最终版本从另一个QgsRectangle对象复制坐标:
C ++编译器根据提供的参数选择要使用的正确方法。Python没有方法重载的概念; 只需选择接受您想要提供的参数的方法版本,QGIS Python包装器将自动为您选择正确的方法。
如果您牢记这些准则,那么解密QGIS的C ++文档并不是那么难。由于C ++特有的复杂性,它看起来比实际上更复杂。但是,您的大脑开始过滤掉C ++并使用QGIS参考文档几乎就像为Python而不是C ++编写的那样。
现在我们可以理解面向C ++的文档,让我们看看PyQGIS库是如何构建的。所有PyQGIS库都在一个名为qgis的包下组织。但是,您通常不会直接导入qgis,因为所有有趣的库都是此主程序包中的子包; 以下是构成PyQGIS库的五个包:
qgis.core | 这样可以访问整个QGIS中使用的核心GIS功能。 |
qgis.gui | 这定义了一系列GUI小部件,您可以在自己的程序中包含这些小部件。 |
qgis.analysis | 这提供了空间分析工具来分析矢量和栅格格式数据。 |
qgis.networkanalysis | 这提供了构建和分析拓扑的工具。 |
qgis.utils | 这实现了各种功能,允许您使用Python使用QGIS应用程序。 |
前两个包(qgis.core和qgis.gui)实现了PyQGIS库中最重要的部分,值得花一些时间来熟悉它们定义的概念和类。现在让我们仔细看看这两个包。
qgis.core包定义了整个QGIS系统中使用的基本类。该软件包的很大一部分专门用于处理矢量和栅格格式的地理空间数据,并在地图中显示这些类型的数据。我们来看看这是如何完成的。
地图由一个在另一个上面绘制的多个图层组成:
QGIS支持三种类型的地图图层:
这些类型的地图图层中的每一个都在qgis.core库中具有相应的类。例如,矢量地图图层将由qgis.core.QgsVectorLayer类型的对象表示。
我们将很快详细介绍矢量和栅格地图图层。但是,在我们这样做之前,我们需要了解地理空间数据(矢量和栅格数据)如何
定位在地图上。
由于地球是一个三维物体,地图只会将地球表面表示为一个二维平面,因此必须有一种方法将地球表面上的点转换为地图内的(x, y)坐标。这是使用坐标参考系统(CRS)完成的:
地球图片由维基媒体提供(http://commons.wikimedia.org/wiki/File:Rotating_globe.gif)
CRS有两个部分:椭圆体,它是地球表面的数学模型,以及投影,它是将球体表面上的点转换为地图上(x, y)坐标的公式。
通常,您不必担心所有这些细节。您只需选择与您正在使用的数据的CRS匹配的相应CRS即可。但是,由于多年来设计了许多不同的坐标参考系统,因此在绘制地理空间数据时使用正确的CRS至关重要。如果不这样做,您的功能将显示在错误的位置或形状错误。
目前可用的大多数地理空间数据使用EPSG 4326坐标参考系统(有时也称为WGS84)。此CRS将坐标定义为纬度和经度值。这是用于导入QGIS的新数据的默认CRS。但是,如果您的数据使用不同的坐标参照系,则需要为地图图层创建和使用不同的CRS。
qgis.core.QgsCoordinateReferenceSystem类表示CRS。创建坐标参照系后,可以在访问基础数据时告诉地图图层使用该CRS。例如:
crs = QgsCoordinateReferenceSystem(4326, QgsCoordinateReferenceSystem.EpsgCrsId))
layer.setCrs(crs)
请注意,不同的地图图层可以使用不同的坐标参考系 在将图层的内容绘制到地图上时,每个图层都将使用其CRS。
矢量图层以点,线,多边形等形式将地理空间数据绘制到地图上。矢量格式地理空间数据通常从矢量数据源(例如shapefile或数据库)加载。其他矢量数据源可以将矢量数据保存在存储器中,或者通过因特网从Web服务加载数据。
矢量格式数据源具有许多功能,其中每个功能代表数据源中的单个记录。qgis.core.QgsFeature类表示数据源中的功能。每个功能都有以下原则:
在QGIS中,数据提供程序允许矢量图层访问数据源中的要素。数据提供程序是qgis.core.QgsVectorDataProvider的一个实例,包括:
您可以使用qgis.core.QgsProviderRegistry类访问各种矢量(以及栅格)数据提供程序。
矢量图层本身由qgis.core.QgsVectorLayer对象表示。每个矢量图层包括:
让我们仔细看看渲染器的概念以及如何在矢量地图图层中显示要素。
矢量地图图层中的要素使用渲染器和符号对象的组合显示。渲染器选择用于给定特征的符号,以及执行实际绘制的符号。
QGIS定义了三种基本类型的符号:
这三种类型的符号实现为qgis.core.QgsSymbolV2类的子类:
在内部,符号相当复杂,因为“符号层”允许多个元素彼此重叠绘制。但是,在大多数情况下,您可以使用符号的“简单”版本。这使得在不必处理符号层的内部复杂性的情况下更容易创建新符号。例如:
symbol = QgsMarkerSymbolV2.createSimple({'width': 1.0, 'color':"255,0,0"})
当符号将要素绘制到地图上时,渲染器用于选择用于绘制特定要素的符号。在最简单的情况下,相同的符号用于图层中的每个要素。这称为单符号渲染器,由qgis.core.QgsSingleSymbolRenderV2类表示。其他可能性包括:
使用单个符号渲染器非常简单:
symbol = ...
renderer = QgsSingleSymbolRendererV2(symbol)
layer.setRendererV2(renderer)
要使用分类符号渲染器,首先要定义qgis.core列表。QgsRendererCategoryV2对象,然后使用它来创建渲染器。
例如:
symbol_male = ...
symbol_female = ...
categories = [] categories.append(QgsRendererCategoryV2("M", symbol_male, "Male"))
categories.append(QgsRendererCategoryV2("F", symbol_female, "Female"))
renderer = QgsCategorizedSymbolRendererV2("", categories)
renderer.setClassAttribute("GENDER")
layer.setRendererV2(renderer)
请注意,QgsRendererCategoryV2构造函数有三个参数:所需的值,使用的符号以及用于描述该类别的标签。
最后,要使用渐变符号渲染器,您可以定义qgis.core.QgsRendererRangeV2对象的列表,然后使用它来创建渲染器。例如:
symbol1 = ...
symbol2 = ...
ranges = [] ranges.append(QgsRendererRangeV2(0, 10, symbol1, "Range 1"))
ranges.append(QgsRendererRange(11, 20, symbol2, "Range 2"))
renderer = QgsGraduatedSymbolRendererV2("", ranges)
renderer.setClassAttribute("FIELD")
layer.setRendererV2(renderer)
除了在地图中显示矢量图层的内容之外,您还可以使用Python直接访问基础数据。这可以使用数据提供程序的getFeatures()方法完成。例如,要迭代图层中的所有要素,您可以执行以下操作:
provider.getFeatures(QgsFeatureRequest())
中的功能的provider = layer.dataProvider():
...
如果要根据某些条件搜索要素,可以使用QgsFeatureRequest对象的setFilterExpression()方法,如下所示:
provider = layer.dataProvider()
request = QgsFeatureRequest()
request.setFilterExpression('"GENDER"="M"')
for provider.getFeatures(QgsFeatureRequest())中的功能:
...
获得这些功能后,可以轻松访问功能的几何,ID 和属性。例如:
geometry = feature.geometry()
id = feature.id()
name = feature.attribute("NAME")
由feature.geometry()调用返回的对象(将是qgis.core.QgsGeometry的实例)表示要素的几何。此对象有许多方法可用于提取基础数据并执行各种地理空间计算。
在上一节中,我们根据属性值搜索了要素。但是,有时您可能希望根据它们在太空中的位置找到特征。例如,您可能希望查找位于给定点的特定距离内的所有要素。为此,您可以使用空间索引,该索引根据功能的位置和范围对功能进行索引。空间索引由QgsSpatialIndex类在QGIS中表示。
出于性能原因,不会为每个矢量图层自动创建空间索引。但是,在您需要时可以轻松创建一个:
provider = layer.dataProvider()
index = QgsSpatialIndex()
用于provider.getFeatures(QgsFeatureRequest())中的
功能:index.insertFeature(feature)
不要忘记您可以使用QgsFeatureRequest.SetFilterExpression()方法来限制添加到索引的功能集。
获得空间索引后,可以使用它根据要素的位置执行查询。特别是:
features = index.nearestNeighbor(QgsPoint(long,lat),5)
请注意,此方法有两个参数:作为QgsPoint对象的所需点和要返回的要素数。
features = index.intersects(QgsRectangle(左,下,右,上))
栅格格式的地理空间数据本质上是位图图像,其中图像中的每个像素或“单元”对应于地球表面的特定部分。栅格数据通常被组织成频带,其中每个频带代表不同的信息。波段的常见用途是将像素颜色的红色,绿色和蓝色分量存储在单独的波段中。波段也可能代表其他类型的信息,例如湿度,海拔或土壤类型。
有许多方法可以显示栅格信息。例如:
让我们仔细看看如何将光栅数据绘制到地图上。
与栅格波段关联的绘图样式控制栅格数据的显示方式。目前支持以下绘图样式:
绘画风格 | 描述 |
PalettedColor | 对于单波段栅格数据源,调色板将每个栅格值映射为颜色。 |
SingleBandGray | 对于单波段栅格数据源,栅格值直接用作灰度值。 |
SingleBandPseudoColor | 对于单波段栅格数据源,栅格值用于计算伪彩色。 |
PalettedSingleBandGray | 对于具有调色板的单波段栅格数据源,此绘图样式告诉QGIS忽略调色板并直接将栅格值用作灰度值。 |
PalettedSingleBandPseudoColor | 对于具有调色板的单波段栅格数据源,此绘图样式告诉QGIS忽略调色板并使用栅格值计算伪彩色。 |
MultiBandColor | 对于多波段栅格数据源,请为红色,绿色和蓝色组件中的每一个使用单独的波段。对于此绘图样式,可以使用setRedBand(),setGreenBand()和setBlueBand()方法来选择要用于每个颜色分量的波段。 |
MultiBandSingleBandGray | 对于多波段栅格数据源,请选择单个波段作为灰度颜色值。对于此绘图样式,请使用setGrayBand()方法指定要使用的波段。 |
MultiBandSingleBandPseudoColor |
对于多波段栅格数据源,选择用于计算伪彩色的单个波段。对于此绘图样式,请使用setGrayBand()方法指定要使用的波段。 |
要设置绘图样式,请使用layer.setDrawingStyle()方法,传入包含所需绘图样式名称的字符串。您还需要调用各种setXXXBand()方法(如上表所述),以告诉栅格图层哪些波段包含用于绘制每个像素的值。
请注意,当您调用前面的函数来更改栅格数据的显示方式时,QGIS不会自动更新地图。要立即显示更改,您需要执行以下操作:
provider = layer.dataProvider()
values = provider.identify(QgsPoint(x, y), QgsRaster.IdentifyFormatValue)
if values.isValid():
for band, values in values.results().items():
...
如您所见,您需要检查栅格数据中是否存在给定坐标(使用isValid()调用)。values.results()方法返回一个将band编号映射到值的字典。
使用此技术,您可以提取与栅格图层中给定坐标关联的所有基础数据。
您还可以使用Provider.Block()方法一次性检索大量坐标的波段数据。我们将在本文后面介绍如何执行此操作。
除了处理数据源和地图图层所涉及的所有类和功能之外,qgis.core库还定义了许多您可能会觉得有用的其他类:
类 | 描述 |
QgsProject | 这代表了当前的QGIS项目。请注意,这是一个单例对象,因为一次只能打开一个项目。所述QgsProject类负责加载和存储的特性,其可以是对插件有用。 |
QGIS | 该类定义了整个QGIS系统中使用的各种常量,数据类型和函数。 |
QgsPoint | 这是一个通用类,用于存储二维平面内点的坐标。 |
QgsRectangle | 这是一个通用类,用于存储二维平面内矩形区域的坐标。 |
QgsRasterInterface | 这是用于处理栅格数据的基类。这可以用于将一组栅格数据重新投影到新的坐标系中,应用滤镜来更改栅格数据的亮度或颜色,重新采样栅格数据,以及通过渲染现有数据来生成新的栅格数据各种方式。 |
QgsDistanceArea | 此类可用于计算给定几何体的距离和面积,自动从源坐标参考系统转换为米。 |
QgsMapLayerRegistry | 此类提供对当前项目中所有已注册地图图层的访问。 |
QgsMessageLog | 此类提供QGIS程序中的常规日志记录功能。这使您可以将调试消息,警告和错误发送到QGIS“日志消息”面板。 |
qgis.gui包定义了许多可以包含在程序中的用户界面小部件。让我们从查看最重要的qgis.gui类开始,然后简要介绍一下您可能会觉得有用的其他类。
QgisInterface表示QGIS系统的用户界面。它允许以编程方式访问地图画布,菜单栏和QGIS应用程序的其他部分。在脚本或插件中运行Python代码时,或直接从QGIS Python控制台运行时,通常可以通过iface全局变量引用QgisInterface。
QgisInterface对象仅在运行QGIS应用程序本身时可用。如果您正在运行外部应用程序并将PyQGIS库导入应用程序,则QgisInterface将不可用。
您可以使用QgisInterface对象执行的一些更重要的事情是:
地图画布负责将各种地图图层绘制到窗口中。QgsMapCanvas类表示地图画布。这堂课包括:
当前显示的地图图层列表。这可以使用layers()方法访问。
请注意,地图画布中可用的地图图层列表与QgisInterface.LegendInterface()方法中包含的地图图层列表之间存在细微差别。地图画布的图层列表仅包含当前可见的图层列表,而QgisInterface.LegendInterface()则返回所有地图图层,包括当前隐藏的图层。
地图画布项是在地图画布顶部绘制的项。地图画布项目将显示在地图图层的前面。虽然如果要在地图画布上绘制自定义项目,可以创建自己的QgsMapCanvasItem子类,但是使用现有的子类来为您完成工作会更有用。目前有三个QgsMapCanvasItem的子类,您可能会发现它们很有用:
地图工具允许用户与地图画布交互并操纵地图画布,捕获鼠标事件并进行适当的响应。许多QgsMapTool子类提供标准的地图交互行为,例如单击以放大,拖动以平移地图,以及单击要识别的特征。您还可以通过继承QgsMapTool并实现响应用户界面事件的各种方法(如按下鼠标按钮,拖动画布等)来创建自己的自定义地图工具。
创建地图工具后,您可以允许用户通过将地图工具与工具栏按钮相关联来激活它。或者,您可以通过调用mapCanvas.setMapTool(...)方法在Python代码中激活它。
我们将在下表中使用PyQGIS库一节中查看创建自定义地图工具的过程:
虽然qgis.gui包定义了大量类,但您最有可能发现有用的类在下表中给出:
类 | 描述 |
QgsLegendInterface | 这样可以访问地图图例,即当前项目中的地图图层列表。请注意,地图图层可以在地图图例中进行分组,隐藏和显示。 |
QgsMapTip | 当用户将鼠标悬停在要素上时,这会在地图画布上显示提示。地图提示将显示该功能的显示字段; 你可以通过调用layer.setDisplayField("FIELD")来设置它。 |
QgsColorDialog | 这是一个允许用户选择颜色的对话框。 |
QgsDialog | 这是一个带有垂直框布局和按钮框的通用对话框,可以轻松地向对话框添加内容和标准按钮。 |
QgsMessageBar | 这是一个用户界面小部件,用于向用户显示非阻止消息。我们查看了上一篇文章中的消息栏类。 |
QgsMessageViewer | 这是一个通用类,它在模式对话框中向用户显示长消息。 |
QgsBlendModeComboBox QgsBrushStyleComboBox QgsColorRampComboBox QgsPenCapStyleComboBox QgsPenJoinStyleComboBox QgsScaleComboBox |
这些QComboBox用户界面小部件允许您提示用户使用各种绘图选项。除了允许用户选择地图比例的QgsScaleComboBox之外,所有其他QComboBox子类都允许用户选择各种Qt绘图选项。 |
在上一节中,我们查看了PyQGIS库提供的许多类。让我们利用这些类来执行一些真实的地理空间开发任务。
我们将首先编写一个程序来加载一些栅格格式的数据并分析其内容。为了使这更有趣,我们将使用数字高程模型(DEM)文件,该文件是包含高程数据的栅格格式数据文件。
全球土地一公里基础高程项目(GLOBE)为世界提供免费的DEM数据,其中每个像素代表地球表面一平方公里。GLOBE数据可以从http://www.ngdc.noaa.gov/mgg/topo/gltiles.html下载。下载E tile,其中包括美国的西半部。生成的文件名为e10g,包含您需要的高度信息。您还需要下载e10g.hdr头文件以便QGIS可以读取该文件 - 您可以从http://www.ngdc.noaa.gov/mgg/topo/elev/esri/hdr下载该文件。下载这两个文件后,将它们放在一个方便的目录中。
您现在可以使用以下代码将DEM数据加载到QGIS中:
registry = QgsProviderRegistry.instance()
provider = registry.provider("gdal", "/path/to/e10g")
不幸的是,这里有一点点复杂性。由于QGIS不知道哪个坐标参考系统用于数据,因此它会显示一个对话框,要求您选择CRS。由于GLOBE DEM数据位于QGS默认使用的WGS84 CRS中,因此该对话框是多余的。要禁用它,您需要将以下内容添加到程序的顶部:
来自PyQt4.QtCore导入QSettings
QSettings().setValue("/Projections/defaultBehaviour", "useGlobal")
现在我们已经将我们的栅格DEM数据加载到QGIS中,我们可以进行分析。我们可以对DEM数据做很多事情,所以让我们计算每个唯一高程值在数据中出现的频率。
请注意,我们使用QgsRasterDataProvider直接加载DEM数据。我们不希望在地图上显示此信息,因此我们不希望(或需要)将其加载到QgsRasterLayer中。
由于DEM数据采用栅格格式,因此您需要迭代单个像素或单元格以获取每个高度值。provider.xSize()和provider.ySize()方法告诉我们DEM中有多少个单元格,而provider.extent()方法为我们提供了DEM所覆盖的地球表面区域。
使用此信息,我们可以通过以下方式从DEM的内容中提取单个高程值:
raster_extent = provider.extent()
raster_width = provider.xSize()
raster_height = provider.ySize()
block = provider.block(1, raster_extent ,raster_width, raster_height)
返回的块变量是QgsRasterBlock类型的对象,它本质上是一个二维数组值。让我们迭代栅格并提取各个高程值:
for x in range(raster_width):
for y in range(raster_height):
elevation = block.value(x, y):
....
现在我们已经加载了各个高程值,可以很容易地从这些值中构建直方图。这是将DEM数据加载到内存中,并计算和显示直方图的整个程序:
从进口PyQt4.QtCore QSettings
QSettings()的setValue("/预测/defaultBehaviour", "useGlobal")
注册表= QgsProviderRegistry.instance()
提供商= registry.provider("GDAL", "/路径/到/e10g")
raster_extent = provider.extent()
raster_width = provider.xSize()
raster_height = provider.ySize()
no_data_value = provider.srcNoDataValue(1)
histogram = {}#将高程映射到出现次数。
block = provider.block(1, raster_extent, raster_width, raster_height)
if block.isValid():
for x in range(raster_width):
for y in range(raster_height):
elevation = block.value(x, y)
if elevation! = no_data_value:
直方图[海拔] + = 1
除KeyError外:
直方图[海拔] = 1
高度排序(histogram.keys()):
打印高度,直方图[高度]
请注意,我们在代码中添加了无数据值检查。栅格数据通常包括没有与之关联的值的像素。就DEM而言,仅为陆地区域提供高程数据; 海上的像素没有高程,我们必须排除它们,否则我们的直方图将不准确。
让我们创建一个程序,它接受两个矢量数据源,从另一个矢量中减去一组矢量,并将得到的几何保存到一个新的shapefile中。在此过程中,我们将学习有关PyQGIS库的一些重要事项。
我们将使用QgsGeometry.difference()函数。此函数执行一个几何与另一个几何的相减,类似于:
让我们首先要求用户选择第一个shapefile并打开该文件的矢量数据提供程序:
filename_1 = QFileDialog.getOpenFileName(iface.mainWindow(),"First Shapefile", "〜", "* .shp")
如果不是filename_1:
return
registry = QgsProviderRegistry.instance()
provider_1 = registry.provider("ogr", filename_1)
然后我们可以从该文件中读取几何到内存中:
geometry_1 = []用于provider_1.getFeatures(QgsFeatureRequest())中的要素:
geometrytries_1.append(QgsGeometry(feature.geometry()))
最后一行代码做了一些非常重要的事情,起初可能并不明显。请注意,我们使用以下内容:
QgsGeometry(feature.geometry())
我们使用前面的行而不是以下行:
feature.geometry()
这将创建QgsGeometry对象的新实例,将几何体复制到新对象中,而不是仅将现有几何体对象添加到列表中。我们必须这样做是因为QGIS Python包装器工作方式的限制:feature.geometry()方法返回对几何的引用,但是C ++代码不知道你将这个引用存储在Python中码。因此,当不再需要该特征时,该特征的几何体所使用的内存也将被释放。如果稍后尝试访问该几何体,整个QGIS系统将崩溃。为了解决这个问题,我们制作了几何体的副本,以便即使在释放了特征的内存之后我们也可以参考它。
现在我们已经将第一组几何图形加载到内存中,让我们对第二个shapefile执行相同的操作:
filename_2 = QFileDialog.getOpenFileName(iface.mainWindow(), "Second Shapefile", "〜", "* .shp")
如果不是filename_2:
返回
provider_2 = registry.provider("ogr", filename_2)geometrytries_2
= [] for feature在provider_2.getFeatures((QgsFeatureRequest())中:
geometrytries_2.append(QgsGeometry(feature.geometry()))
将两组几何图形加载到内存中后,我们就可以开始从另一组中减去一个。但是,为了使这个过程更有效,我们将第二个shapefile中的几何图形组合成一个大的几何图形,然后我们可以一次减去所有几何图形,而不是一次减去一个。这将使减法过程更快:
combined_geometry =
几何中几何的无为_2:
如果combined_geometry ==无:
combined_geometry = geometry
else:
combined_geometry = combined_geometry.combine(geometry)
我们现在可以通过从另一个中减去一个来计算新的几何图形集:
dst_geometries = []表示geometry几何中的几何:
dst_geometry = geometry.difference(combined_geometry)
如果不是dst_geometry.isGeosValid():
如果dst_geometry.isGeosEmpty():continue, 则继续
dst_geometries.append(dst_geometry)
请注意,我们检查以确保目标几何在数学上有效且不为空。
在处理复杂形状时,无效几何是常见问题。有修复它们的选项,例如拆分多个几何和执行缓冲操作。
我们的最后一项任务是将生成的几何图形保存到新的shapefile中。我们首先会询问用户目标shapefile的名称:
dst_filename = QFileDialog.getSaveFileName(iface.mainWindow(), "将结果保存到:", "〜", "* .shp")
如果不是dst_filename:
return
我们将使用矢量文件编写器将几何图形保存到shapefile中。让我们从初始化文件编写器对象开始:
fields = QgsFields()
writer = QgsVectorFileWriter(dst_filename,"ASCII", fields, dst_geometries[0].wkbType(), None, "ESRI Shapefile")
if writer.hasError() != QgsVectorFileWriter.NoError:
print"Error"
返回
我们的shapefile中没有任何属性,因此字段列表为空。既然已经设置了编写器,我们可以将几何保存到文件中:
对于dst_geometries中的几何:
feature = QgsFeature()
feature.setGeometry(geometry)
writer.addFeature(feature)
现在所有数据都已写入磁盘,让我们显示一个消息框,通知用户我们已完成:
QMessageBox.information(iface.mainWindow(), "", "减去的功能保存到磁盘。")
正如您所看到的,在PyQGIS中创建一个新的shapefile非常简单,并且只要您复制要保留的QgsGeometry,就可以轻松地使用Python操作几何。如果你的Python代码在操作几何体时开始崩溃,那么这可能是你应该首先考虑的事情。
让我们使用您在上一篇文章中下载的World Borders Dataset来绘制世界地图,使用不同大洲的不同符号。这是使用分类符号渲染器的一个很好的示例,但我们将它组合到一个脚本中,该脚本将shapefile加载到地图图层中,并设置符号和地图渲染器以完全按照您的需要显示地图。然后,我们将此地图另存为图像。
让我们首先创建一个地图图层来显示World Borders Dataset shapefile的内容:
layer = iface.addVectorLayer("/path/to/TM_WORLD_BORDERS-0.3.shp", "continents", "ogr")
World Borders Dataset shapefile中的每个唯一区域代码对应一个大陆。我们要定义用于每个区域的名称和颜色,并使用此信息设置显示地图时要使用的各种类别:
来自PyQt4.QtGui导入QColor
类别= []表示值, 颜色, 标签[(0, "#660000", "南极洲"), (2, "#006600", "非洲"),
(9, "#000066", "大洋洲"), (19, "#660066", "美洲"),
(142, "#666600", "亚洲"), (150, "#006666", "欧洲"),
symbol = QgsSymbolV2.defaultSymbol(layer.geometryType())
symbol.setColor(QColor(color))
categories.append(QgsRendererCategoryV2(value,symbol,label))
设置这些类别后,我们只需更新地图图层以使用基于region属性值的分类渲染器,然后重绘地图:
layer.setRendererV2(QgsCategorizedSymbolRendererV2("region", categories))
layer.triggerRepaint()
还有一件事要做; 因为这是一个可以多次运行的脚本,所以让我们的脚本自动删除现有的大陆层(如果存在),然后再添加新的大陆层。为此,我们可以在脚本的开头添加以下内容:
layer_registry = QgsMapLayerRegistry.instance()
对于layer_registry.mapLayersByName("continents")中的图层:
layer_registry.removeMapLayer(layer.id())
当我们的脚本运行时,它将创建一个(并且只有一个)图层,以不同的颜色显示各个大陆。这些将在打印文章中显示为不同的灰色阴影,但颜色将在计算机屏幕上显示:
现在,让我们使用相同的数据集根据每个国家的相对人口为每个国家着色。我们首先删除现有的填充层(如果存在):
layer_registry.mapLayersByName
("population")中图层的layer_registry = QgsMapLayerRegistry.instance():
layer_registry.removeMapLayer(layer.id())
接下来,我们将World Borders Dataset打开成一个名为“population”的新层:
layer = iface.addVectorLayer("/path /to/TM_WORLD_BORDERS-0.3.shp", "population", "ogr")
然后我们需要设置各种人口范围:
来自PyQt4.QtGui导入QColor
range = [] for min_pop, max_pop, color in [(0, 99999, "#332828"), (100000, 999999, "#4c3535"),
(1000000, 4999999, "#663d3d"), (5000000, 9999999, "#804040"),
(10000000, 19999999, "#993d3d"), (20000000, 49999999, "#b33535"),
(50000000, 999999999, "#cc2828")]:
symbol = QgsSymbolV2.defaultSymbol(layer.geometryType())
symbol.setColor(QColor(color))
ranges.append(QgsRendererRangeV2(min_pop,max_pop, symbol, ""))
现在我们有了人口范围及其相关颜色,我们只需设置一个渐变符号渲染器,根据pop2005属性的值选择符号,并告诉地图重绘自己:
layer.setRendererV2(QgsGraduatedSymbolRendererV2("pop2005", range))
layer.triggerRepaint()
结果将是一个地图图层,根据其人口对每个国家进行着色:
在我们使用PyQGIS库的最后一个例子中,我们将编写一些代码,这些代码在运行时开始监听用户的鼠标事件。如果用户点击某个点,拖动鼠标,然后再次释放鼠标按钮,我们将显示这两个点之间的距离。这是一个很好的例子,说明如何添加你的
使用QgsMapTool类将自己的地图交互逻辑提供给QGIS。
这是我们的QgsMapTool子类的基本结构:
class DistanceCalculator(QgsMapTool):
def __init__(self,iface):
QgsMapTool.__init__(self, iface.mapCanvas())
self.iface = iface
def canvasPressEvent(self,event):
...
def canvasReleaseEvent(self,event):
...
要使此地图工具处于活动状态,我们将创建它的新实例并将其传递给mapCanvas.setMapTool()方法。完成此操作后,只要用户在地图画布上单击或释放鼠标按钮,就会调用canvasPressEvent()和canvasReleaseEvent()方法。
让我们从处理用户点击画布的代码开始。在此方法中,我们将从用户单击的像素坐标转换为地图坐标(即纬度和经度值)。然后我们将记住这些坐标,以便我们稍后可以参考它们。这是必要的代码:
def canvasPressEvent(self,event):
transform = self.iface.mapCanvas().getCoordinateTransform()
self._startPt = transform.toMapCoordinates(event.pos().x(), event.pos().y())
调用canvasReleaseEvent()方法时,我们希望对用户释放鼠标按钮的点做同样的事情:
def canvasReleaseEvent(self,event):
transform = self.iface.mapCanvas().getCoordinateTransform()
endPt = transform.toMapCoordinates(event.pos().x(), event.pos().y())
现在我们有了两个所需的坐标,我们想要计算它们之间的距离。我们可以使用QgsDistanceArea对象执行此操作:
crs = self.iface.mapCanvas().mapRenderer().destinationCrs()
distance_calc = QgsDistanceArea()
distance_calc.setSourceCrs(crs)
distance_calc.setEllipsoid(crs.ellipsoidAcronym())
distance_calc.setEllipsoidalMode(crs.geographicFlag())
distance = distance_calc.measureLine([self._startPt, endPt]) / 1000
请注意,我们将结果值除以1000.这是因为QgsDistanceArea对象返回以米为单位的距离,我们希望以千米为单位显示距离。
最后,我们将在QGIS消息栏中显示计算的距离:
messageBar = self.iface.messageBar()
messageBar.pushMessage("Distance =%d km"%distance, level = QgsMessageBar.INFO, duration = 2)
现在我们已经创建了地图工具,我们需要激活它。我们可以通过在脚本的末尾添加以下内容来完成此操作:
calculator = DistanceCalculator(iface)
iface.mapCanvas().setMapTool(calculator)
激活地图工具后,用户可以在地图上单击并拖动。释放鼠标按钮后,两个点之间的距离(以千米为单位)将显示在消息栏中:
有关该主题的进一步资源:
再次感谢Packet的辛苦创作!!!