QGIS-Python-编程秘籍-全-

QGIS Python 编程秘籍(全)

原文:zh.annas-archive.org/md5/7bd6d5082b84bf4b8abfdb02d6ba84cc

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

开源地理信息系统 QGIS,在 2.6 版本中,其功能性和易用性已经可以与最昂贵的商业 GIS 软件相媲美。它也是展示最佳地理空间开源技术的典范。它不仅仅是一个项目,而是将数十个开源项目融合在一个简洁的界面中。

地理空间技术不仅仅是将技术应用于地理学的综合应用。它是地理学、数学、计算机科学、统计学、物理学和其他领域的交响乐。QGIS 实现的底层算法非常复杂,以至于世界上只有少数人能够理解所有这些算法。然而,QGIS 将这些复杂性封装得如此之好,以至于小学生、城市管理者、疾病研究人员、地质学家以及许多其他专业人士都能轻松地使用这款强大的软件,做出改善地球生活的决策。

然而,本书还涉及 QGIS 的另一个特性,使其成为地理空间工作的最佳选择。QGIS 拥有最深入集成和设计精良的 Python 接口之一。在最新版本中,几乎没有程序方面是 Python 无法触及的,使其成为可用的最大地理空间 Python 库。几乎无一例外,Python API,称为 PyQGIS,是一致的且可预测的。

本书利用 QGIS 的最佳特性,展示了 140 多个可重用的食谱,您可以使用这些食谱来自动化 QGIS 中的工作流程或构建独立的 GIS 应用程序。大多数食谱都非常紧凑,即使您找不到您所寻找的精确解决方案,也应该能够找到接近的解决方案。本书涵盖了大量内容,并将散布在互联网上的碎片化思想和文档以及许多小时的 PyQGIS API 边缘实验结果汇集在一起。

本书涵盖的内容

第一章,自动化 QGIS,简要概述了您可以使用 Python 与 QGIS 结合的不同方式,包括 QGIS Python 控制台、独立应用程序、插件和脚本运行器插件。本章还涵盖了如何设置和检索应用程序设置以及一些其他 Python 特定功能。

第二章,查询矢量数据,介绍了如何使用 Python 从矢量数据中提取信息而不改变数据。涵盖的主题包括测量、从数据库加载数据、过滤数据以及其他相关过程。

第三章, 编辑矢量数据,介绍了创建和更新数据以添加新信息的话题。它还教您如何根据空间或数据库属性将数据集拆分,以及如何合并数据集。本章还将教您如何将数据转换为不同的格式,更改投影,简化数据等。

第四章, 使用栅格数据,展示了 25 个使用和转换栅格数据以创建派生产品的食谱。本章突出了 QGIS 作为栅格处理引擎的能力,而不仅仅是矢量 GIS。

第五章, 创建动态地图,过渡到控制 QGIS 整体以控制地图、项目和应用程序级设置的食谱。它包括访问外部网络服务和构建自定义地图工具的食谱。

第六章, 制作静态地图,展示了如何使用 QGIS 地图编绘器创建打印地图。您将学习如何在地图上放置参考元素以及设计元素,如标志。

第七章, 与用户交互,教您如何控制由底层 Qt 框架创建的 QGIS GUI 元素,以便为脚本、插件或独立应用程序创建交互式输入小部件。

第八章, QGIS 工作流程,包含更高级的食谱,这些食谱产生最终产品或扩展功能。这些食谱针对地理空间分析师或程序员在工作中遇到的实际任务。

第九章, 其他技巧和窍门,包含超出前几章范围的有趣食谱。许多这些食谱在单个食谱中演示了多个概念,您可能会发现这些食谱对各种任务都很有用。

您需要这本书的

完成本书中所有食谱,您需要以下软件;如果特定版本不可用,请使用最新版本:

  • QGIS 2.6

  • Python 2.7.6(应包含在 QGIS 本身中)

  • IBM Java 7 开发工具包

  • Eclipse Luna 4.4.x

  • Google Earth 7.1.2.2041

这本书面向谁

如果您是一位希望了解更多关于自动化日常 GIS 任务的地理空间分析师或负责构建 GIS 应用程序的程序员,这本书适合您。基本的 Python 知识是必需的,并且一些 QGIS 经验将是一个额外的优势。

短小、可重用的食谱使概念易于理解。当它们组合在一起时,您可以构建易于维护的更大应用程序。

章节

在这本书中,您会发现一些经常出现的标题(准备就绪、如何操作、工作原理、更多信息以及相关内容)。

为了清楚地说明如何完成食谱,我们使用以下部分如下:

准备就绪

本节告诉您在食谱中可以期待什么,并描述了为食谱设置任何软件或任何初步设置的方法。

如何操作…

本节包含遵循食谱所需的步骤。

工作原理…

本节通常包含对上一节发生情况的详细解释。

更多信息…

本节包含有关食谱的附加信息,以便使读者对食谱有更深入的了解。

相关内容

本节提供了对其他有用信息的链接,这些信息有助于理解食谱。

术语约定

在这本书中,您会发现许多不同风格的文本,用于区分不同类型的信息。以下是一些这些风格的示例及其含义的解释。

文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名如下所示:“在 QGIS Python 控制台中,我们将导入random模块。”

代码块按如下方式设置:

proj = QgsProject.instance()
proj.title("My QGIS Project")
proj.title()
proj.writeEntry("MyPlugin", "splash", "Geospatial Python Rocks!")
proj.readEntry("MyPlugin", "splash", "Welcome!")[0]

任何命令行输入或输出都按如下方式编写:

sudo easy_install PyPDF2

新术语重要词汇以粗体显示。屏幕上显示的单词,例如在菜单或对话框中,在文本中如下所示:“在表单中输入信息,然后单击发送按钮。”

注意

警告或重要注意事项以如下方式显示。

小贴士

小技巧和技巧如下所示。

读者反馈

我们始终欢迎读者的反馈。告诉我们您对这本书的看法——您喜欢什么或可能不喜欢什么。读者反馈对我们开发您真正从中受益的标题非常重要。

要发送一般反馈,只需发送电子邮件到<feedback@packtpub.com>,并在邮件主题中提及书名。

如果您在某个主题上具有专业知识,并且您对撰写或为书籍做出贡献感兴趣,请参阅我们的作者指南www.packtpub.com/authors

客户支持

现在您是 Packt 书籍的骄傲拥有者,我们有一些事情可以帮助您从购买中获得最大收益。

下载示例代码

您可以从www.packtpub.com的账户下载您购买的所有 Packt 书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。

下载本书的颜色图像

我们还为您提供了一个包含本书中使用的截图/图表彩色图像的 PDF 文件。彩色图像将帮助您更好地理解输出中的变化。您可以从www.packtpub.com/sites/default/files/downloads/4985OS_ColoredImages.pdf下载此文件。

错误

尽管我们已经尽一切努力确保我们内容的准确性,但错误仍然可能发生。如果您在我们的书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何错误,请通过访问www.packtpub.com/submit-errata,选择您的书籍,点击errata submission form链接,并输入您的错误详情来报告它们。一旦您的错误得到验证,您的提交将被接受,错误将被上传到我们的网站,或添加到该标题的 Errata 部分下的现有错误列表中。任何现有错误都可以通过从www.packtpub.com/support中选择您的标题来查看。

盗版

在互联网上,版权材料的盗版是一个跨所有媒体持续存在的问题。在 Packt,我们非常重视我们版权和许可证的保护。如果您在网上发现了我们作品的任何非法副本,无论形式如何,请立即提供地址或网站名称,以便我们可以寻求补救措施。

如果您发现了疑似盗版材料,请通过<copyright@packtpub.com>与我们联系,并提供链接。

我们感谢您在保护我们作者和为我们提供有价值内容的能力方面提供的帮助。

问答

如果您在本书的任何方面遇到问题,可以通过<questions@packtpub.com>与我们联系,我们将尽力解决。

第一章:自动化 QGIS

本章将涵盖以下菜谱:

  • 为开发安装 QGIS

  • 使用 QGIS Python 控制台

  • 使用 Python 的 ScriptRunner 插件

  • 设置您的 QGIS IDE

  • 调试 QGIS Python 脚本

  • 导航 PyQGIS API

  • 创建 QGIS 插件

  • 分发插件

  • 构建独立应用程序

  • 存储和读取全局首选项

  • 存储和读取项目首选项

  • 在您的脚本中访问脚本路径

简介

本章解释了如何使用 Python 配置 QGIS 以实现自动化。除了设置 QGIS 之外,我们还将配置带有 PyDev 插件的免费 Eclipse 集成开发环境IDE),以便更容易地编写、编辑和调试脚本。我们还将通过 PyQGIS API 学习不同类型的 QGIS 自动化 Python 脚本的基础知识。最后,我们将检查一些核心的 QGIS 插件,这些插件显著扩展了 QGIS 的功能。

为开发安装 QGIS

QGIS 有一套可以从 QGIS 中的 Python 控制台访问的 Python 模块和库。然而,它们也可以从 QGIS 外部访问以编写独立应用程序。首先,您必须确保为您的平台安装了 PyQGIS,然后设置一些必需的系统环境变量。

在这个菜谱中,我们将向您介绍在正常 QGIS 安装之外所需的额外步骤,以准备您的系统进行开发。每个平台的步骤都提供,其中包括不同的 Linux 包管理器样式。

准备工作

QGIS 针对 Windows、GNU/Linux 和 Mac OS X 使用略微不同的安装方法。Windows 安装程序安装了 Python 开发所需的所有内容,包括 Python 本身。

然而,在 Linux 发行版和 Mac OS X 上,您可能需要手动安装 Python 模块以进行系统 Python 安装。在 Mac OS X 上,您可以从www.kyngchaos.com/software/python下载一些常用 Python 模块的安装程序,与 QGIS 一起使用。

如何操作

在 Linux 上,您可以选择从源代码编译,或者您可以通过包管理器指定要安装的 Python QGIS 接口。

使用 Debian 包管理器安装 PyQGIS

  1. 对于基于 Debian Linux 包管理器的 Linux 发行版,包括 Ubuntu 和 Debian,请在 shell 中使用以下命令:

    sudo apt-get update
    
    
  2. 接下来,安装 QGIS、PyQGIS 和 QGIS GRASS 插件:

    sudo apt-get install qgis python-qgis qgis-plugin-grass
    
    

使用 RPM 包管理器安装 PyQGIS

  1. 对于基于Red Hat 包管理器RPM)的 Linux 发行版,首先更新包管理器,如下所示:

    sudo yum update
    
    
  2. 然后,安装 QGIS、PyQGIS 和 QGIS GRASS 插件的包:

    sudo yum install qgis qgis-python qgis-grass
    
    

设置环境变量

现在,我们必须将PYTHONPATH设置为 PyQGIS 目录。同时,将此目录的路径追加到PATH变量中,以便您可以使用外部 IDE 使用 PyQGIS 模块。

在 Windows 上设置环境变量

  1. 在命令提示符中设置PYTHONPATH变量为 QGIS 安装的bin目录:

    set PYTHONPATH="C:\Program Files\QGIS Brighton\bin"
    
    
  2. 接下来,将 QGIS 的bin目录追加到系统的PATH变量中:

    set PATH="C:\Program Files\QGIS Brighton\bin";"C:\Program Files\QGIS Brighton\bin\apps\qgis\bin";%PATH%
    
    

在 Linux 上设置环境变量

  1. 在命令提示符中设置PYTHONPATH变量为 QGIS 安装的bin目录:

    export PYTHONPATH=/usr/share/qgis/python
    
    
  2. 现在,将 QGIS 共享库目录追加到运行时搜索路径中。请注意,此位置可能因您的特定系统配置而异:

    export LD_LIBRARY_PATH=/usr/share/qgis/python
    
    

它是如何工作的...

QGIS 安装过程和包管理器在 QGIS 内部设置了 Python 模块的配置。当您在 QGIS 内部使用 Python 控制台时,它知道所有 PyQGIS 模块的位置。然而,如果您想在 QGIS 之外使用 PyQGIS API,在 Windows 或 Linux 上使用系统 Python 安装,则需要设置一些系统变量,以便 Python 可以找到所需的 PyQGIS 模块。

还有更多...

此配方使用每个平台的默认 QGIS 路径。如果您不确定哪个 PyQGIS 路径适用于您的系统,您可以从 QGIS 中的 Python 控制台找出这一点。

在 Windows 上查找 PyQGIS 路径

Windows 上的库存储位置与其他平台不同。要定位路径,您可以检查 Python 控制台当前的工作目录:

  1. 启动 QGIS。

  2. 从 QGIS 应用程序窗口的右下角出现的插件菜单中选择Python 控制台,如图所示:在 Windows 上查找 PyQGIS 路径

  3. 使用os模块获取当前工作目录:

    import os
    os.getcwd()
    
    
  4. 验证 Python 控制台当前的工作目录是否返回。

在其他平台上查找 QGIS Python 安装的位置

执行以下步骤以找到除 Windows 之外所有平台所需的路径:

  1. 启动 QGIS。

  2. 启动 QGIS Python 控制台

  3. 使用sys模块定位 PyQGIS 路径:

    import sys
    sys.path
    
    
  4. Python 将返回一个路径列表。

  5. 找到以/python结尾的路径,这是 QGIS 使用的 Python 安装位置

使用 QGIS Python 控制台进行交互式控制

QGIS Python 控制台允许您交互式地控制 QGIS。您可以测试想法或进行一些快速自动化。控制台是使用 QGIS Python API 的最简单方式。

如何操作...

在以下步骤中,我们将打开 QGIS Python 控制台,在内存中创建一个矢量图层,并在地图上显示它:

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. 以下代码将在地图画布上创建一个点:

    layer =  QgsVectorLayer('Point?crs=epsg:4326', 'MyPoint' , 'memory')
    pr = layer.dataProvider()
    pt = QgsFeature()
    point1 = QgsPoint(20,20)
    pt.setGeometry(QgsGeometry.fromPoint(point1))
    pr.addFeatures([pt])
    layer.updateExtents()
    QgsMapLayerRegistry.instance().addMapLayers([layer])
    

它是如何工作的...

此示例使用一个内存图层来避免与磁盘或网络上的任何数据交互,以保持事情简单。请注意,当我们声明图层类型时,我们添加了坐标参考系统CRS)参数为 EPSG:4326。如果没有这个声明,QGIS 将提示您选择一个。创建地图画布上的单个点甚至需要三个部分或抽象级别,如下所示:

  • 首先,创建一个几何类型的图层。然后,设置一个数据提供者以接受数据源。

  • 然后,创建一个通用的要素对象,接着是点几何。

  • 接下来,将对象堆叠在一起并将它们添加到地图中。

图层类型是内存,这意味着您可以在代码中而不是在外部数据源中内联定义几何形状和属性。在此配方中,我们只定义几何形状并跳过定义任何属性。

使用 Python ScriptRunner 插件

QGIS Python ScriptRunner 插件为 QGIS 自动化提供了一个中间地带,介于交互式控制台和插件开销之间。它提供了一个脚本管理对话框,允许您轻松加载、创建、编辑和运行脚本,以实现大规模的 QGIS 自动化。

准备工作

使用 QGIS 插件管理器安装ScriptRunner插件。然后,从插件菜单运行插件以打开ScriptRunner对话框。按照以下步骤配置默认编辑器以编辑脚本:

  1. 找到代表ScriptRunner 首选设置对话框的齿轮图标,并点击它。

  2. 常规选项部分,勾选使用以下方式编辑脚本复选框。

  3. 点击按钮浏览到您系统上的文本编辑器位置。

  4. 点击打开按钮。

  5. 首选项对话框中点击OK按钮。

如何操作…

  1. ScriptRunner对话框中,点击下面的截图所示的新建脚本图标:如何操作…

  2. 浏览到可以保存您的脚本的位置,命名脚本,并保存它。

  3. 验证新脚本是否已加载到ScriptRunner中。

  4. 在 ScriptRunner 中的脚本名称上右键单击(或在 Mac 上控制单击)并选择在外部编辑器中编辑脚本

  5. 在编辑器中,将模板代码替换为以下代码:

    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    from qgis.core import *
    from qgis.gui import *
    
    def run_script(iface):
        layer =  QgsVectorLayer('Polygon?crs=epsg:4326', 'Mississippi' , "memory")
    pr = layer.dataProvider()
        poly = QgsFeature()
        geom = QgsGeometry.fromWkt("POLYGON ((-88.82 34.99,-88.09 34.89,-88.39 30.34,-89.57 30.18,-89.73 31,-91.63 30.99,-90.87 32.37,-91.23 33.44,-90.93 34.23,-90.30 34.99,-88.82 34.99))")
        poly.setGeometry(geom)
        pr.addFeatures([poly])
        layer.updateExtents()
    QgsMapLayerRegistry.instance().addMapLayers([layer])
    
  6. 点击代表绿色箭头的运行脚本图标。

  7. 关闭ScriptRunner插件。

  8. 验证内存图层多边形是否已添加到 QGIS 地图中,如下面的截图所示:如何操作…

它是如何工作的…

ScriptRunner 是一个简单但强大的想法。它允许您构建自动化脚本库,并在 QGIS 内部使用它们,但无需构建插件或独立应用程序的开销。所有 Python 和系统路径变量都设置正确,并从 QGIS 继承;然而,您仍然必须导入 QGIS 和 Qt 库。

设置您的 QGIS IDE

配备 PyDev 插件的 Eclipse IDE 是跨平台的,具有高级调试工具,并且是免费的。

注意

您可以参考pydev.org/manual_101_install.html以正确安装 PyDev。

此工具是一个出色的 PyQGIS IDE。Eclipse 允许您为不同的 Python 环境配置多个 Python 解释器。当您安装 PyDev 时,它将自动找到已安装的系统 Python 安装。在 Windows 上,您还必须添加与 PyQGIS 一起安装的 Python 解释器。在所有平台上,您都必须告诉 PyDev PyQGIS 库的位置。

准备工作

此配方使用 Eclipse 和 PyDev。您可以使用操作系统支持的最新版本的任一包。除了 Windows 之外的所有平台都依赖于系统 Python 解释器。因此,在 Windows 上添加 QGIS Python 解释器需要额外的一步。

如何操作…

以下步骤将指导您如何将 QGIS 特定的 Python 解释器添加到 Eclipse 中,以便支持运行独立的 QGIS 应用程序或调试 QGIS 插件。

在 Windows 上添加 QGIS Python 解释器

在 Windows 上添加 QGIS Python 解释器到 Eclipse 的过程与在 Linux 上使用的过程不同。以下步骤描述了如何在 Windows 版本的 Eclipse 上设置解释器:

  1. 打开 Eclipse。

  2. 窗口菜单中选择首选项。在 OS X 上,您必须点击Eclipse菜单以找到首选项菜单。

  3. 首选项窗口的左侧面板中,点击PyDev旁边的加号。

  4. 从 PyDev 首选项列表中选择解释器 Python

  5. 在标记为 Python 解释器的面板中,点击新建按钮。

  6. 选择解释器对话框中,将解释器命名为PyQGIS

  7. 浏览到 QGIS 程序文件夹中的bin文件夹内名为python.exe的 QGIS Python 解释器位置。在 OS X 和 Linux 上,您可以使用系统 Python 安装。在 Windows 上,Python 包含在 QGIS 中。Windows 上的默认位置如以下截图所示:在 Windows 上添加 QGIS Python 解释器

  8. 当您点击确定按钮时,Eclipse 将尝试自动将找到的每个 Python 库添加到该解释器配置的 Python 路径中。我们需要控制添加哪些库以防止冲突。点击取消选择全部按钮,然后点击确定在 Windows 上添加 QGIS Python 解释器

  9. Eclipse 将显示一个警告对话框,因为您尚未选择任何核心库。点击继续按钮,如以下截图所示:在 Windows 上添加 QGIS Python 解释器

将 PyQGIS 模块路径添加到解释器

除了添加 Python 解释器外,你还必须使用以下步骤添加 PyQGIS 需要的模块路径。这些步骤将需要你在 QGIS 和 Eclipse 之间来回切换:

  1. 启动 QGIS。

  2. 插件菜单启动 QGIS Python 控制台

  3. 使用 sys 模块定位 PyQGIS Python 路径,如前一个食谱中所述,设置环境变量

    import sys
    sys.path
    
    
  4. 我们还希望添加 PyQGIS API。接下来,使用 QGIS Python 控制台输入以下命令来查找该路径:

    qgis
    
  5. 对于返回列表中的每个路径,在 Eclipse 的 QGIS 解释器面板中点击新建文件夹按钮,浏览到该文件夹,直到所有路径都已添加。如果系统上不存在某个文件夹,只需忽略它,如图所示:将 PyQGIS 模块路径添加到解释器

  6. 首选项对话框中点击确定按钮。

将 PyQGIS API 添加到 IDE

为了充分利用 Eclipse 的功能,包括代码补全,我们将向 PyQGIS Eclipse 解释器首选项添加 QGIS 和 Qt4 模块。以下步骤将允许 Eclipse 在你输入时建议 QGIS 对象的可能方法和属性;这个功能被称为自动完成

  1. 在 PyDev 的 PyQGIS 解释器首选项中,选择强制内置选项卡,如图所示:将 PyQGIS API 添加到 IDE

  2. 点击新建按钮。

  3. 内置要添加对话框中,输入 qgis将 PyQGIS API 添加到 IDE

  4. 点击确定按钮。

添加环境变量

你还需要创建一个指向 QGIS 二进制库、Windows 上的 DLL 和所有平台在运行时所需的库的 PATH 变量。

  1. PyDev 首选项对话框中,确保在解释器列表中选择了PyQGIS解释器。

  2. 选择环境选项卡。

  3. 点击新建按钮。

名称字段中,输入PATH

  1. 字段中,添加 QGIS 程序目录和包含二进制的任何 QGIS 目录的路径,这些路径由分号分隔。以下是一个来自 Windows 机器的示例:

    C:\Program Files\QGIS Brighton;C:\Program Files\QGIS Brighton\bin;C:\Program Files\QGIS Brighton\apps\qgis\bin;C:\Program Files\QGIS Brighton\apps\Python27\DLLs
    
    

它是如何工作的...

Eclipse 和 PyDev 仅使用你提供的信息在 Eclipse 工作区中运行脚本。这种方法与流行的 Python 工具 virtualenv 非常相似,它提供了一个干净的环境,以确保你在编写和调试代码时不会浪费时间在调试由环境引起的问题上。

调试 QGIS Python 脚本

在本食谱中,我们将配置 Eclipse 以调试 QGIS Python 脚本。

如何操作...

QGIS 和 Eclipse 都必须配置为调试,以便这两款软件能够通信。Eclipse 会附加到 QGIS 上,以便您了解在 QGIS 中运行的 Python 脚本。这种方法允许您以受控的方式运行脚本,可以在您监控程序以捕获错误时暂停执行。

配置 QGIS

以下步骤将为 QGIS 添加两个插件,这允许 Eclipse 与 QGIS 通信。一个插件是插件重载器,允许您在不重启 QGIS 的情况下将 QGIS 插件重新加载到内存中,以便更快地进行测试。第二个插件是远程调试,它将 QGIS 连接到 Eclipse。

远程调试是一个实验性插件,因此您必须确保实验性插件在可用插件列表中可见,以便 QGIS 插件管理器可以访问。

  1. 启动 QGIS。

  2. 插件菜单下,选择管理并安装插件

  3. 插件对话框的左侧面板中,选择设置选项卡。

  4. 设置窗口中向下滚动,并确保已勾选显示实验性插件复选框,如下截图所示:配置 QGIS

  5. 点击确定按钮。

  6. 插件窗口左侧的面板中选择标签为所有的选项卡。

  7. 在窗口顶部的搜索对话框中,搜索插件重载器

  8. 从搜索结果中选择插件重载器,然后点击安装插件按钮。

  9. 接下来,搜索远程调试插件并安装它。

  10. 最后,安装HelloWorld插件。

配置 Eclipse

现在 QGIS 已配置为在 Eclipse 中进行调试,我们将配置 Eclipse 以完成调试通信循环,具体步骤如下:

  1. 启动 Eclipse。

  2. 文件菜单中,选择新建,然后点击项目

  3. 新建项目对话框中,选择通用,然后点击项目

  4. 点击下一步>按钮。

  5. 给项目命名为HelloWorldPlugin

  6. 点击完成按钮。

  7. 在项目资源管理器中选择新的HelloWorldPlugin项目,然后选择新建;接着,从文件菜单中选择文件夹

  8. 新建文件夹对话框中,点击高级>>按钮。

  9. 选择链接到备用位置(链接文件夹)单选按钮。

  10. 点击浏览按钮,浏览到HelloWorldPlugin文件夹的位置,如下截图所示:

    小贴士

    您可以从 QGIS 插件管理器中找到 HelloWorld 插件的存放位置。

    配置 Eclipse

  11. 点击完成按钮。

测试调试器

本食谱的前几部分配置了 Eclipse 和 QGIS 以协同工作以调试 QGIS 插件。在本节中,我们将使用最简单的插件 HelloWorld 来测试配置,使用 Debug Perspective 运行 Eclipse。我们将在插件中设置一个断点以暂停执行,然后在 Eclipse 内部监控插件执行,如下所示:

  1. HelloWorld 文件夹下打开文件 HelloWorld.py

  2. 从 Eclipse 窗口 菜单中选择 OpenPerspective,然后点击 Other…

  3. OpenPerspective 对话框中选择 调试

  4. 点击 OK 按钮。

  5. 滚动到 hello_world() 函数的第一行,并在行号左侧双击以设置断点,它显示为绿色图标:测试调试器

  6. Pydev 菜单中选择 Start Debug Server

  7. 通过在窗口底部的调试控制台中查找类似以下的消息来验证服务器正在运行:

    Debug Server at port: 5678
    
    
  8. 切换到 QGIS。

  9. 从 QGIS 插件 菜单中选择 RemoteDebug,然后选择 RemoteDebug 命令。

  10. 验证窗口左下角的 QGIS 状态栏显示以下信息:

    Python Debugging Active
    
    
  11. 现在,从 QGIS 插件 菜单中选择 HelloWorld,然后选择 HelloWorld

  12. 切换回 Eclipse。

  13. 验证 hello_world() 函数在断点处被突出显示。

  14. 运行 菜单中选择 继续

  15. 切换回 QGIS。

  16. 验证 HelloWorld 对话框已出现。

它是如何工作的…

RemoteDebug 插件作为 PyDev 调试服务器的客户端,用于将 QGIS 中的 Python 脚本执行状态发送到 Eclipse。尽管它已经伴随 QGIS 几个版本了,但仍被视为实验性的。

PluginReloader 插件可以重置在运行时维护状态的插件。HelloWorld 插件非常简单,因此不需要重新加载来重复测试。然而,当你调试更复杂的插件时,你需要在每次测试之前运行它以重置它。这种方法比关闭 QGIS、编辑插件代码然后重新启动要高效得多且易于使用。

注意

你可以在 docs.qgis.org/2.6/en/docs/pyqgis_developer_cookbook/ide_debugging.html 了解更多关于调试 QGIS 的信息,包括使用其他 IDE。

导航 PyQGIS API

QGIS Python API,也称为 PyQGIS,允许你控制 QGIS 的几乎所有方面。找到 PyQGIS 对象以便访问 QGIS 的特定功能的能力对于自动化至关重要。

准备中

PyQGIS API 基于 QGIS C++ API。C++ API 在线保持最新,并且有很好的文档记录。

注意

QGIS API 的网页位于 qgis.org/api/2.6/modules.html

注意 URL 中的版本号,2.2。您可以将此版本号更改为您正在使用的 QGIS 版本,以便找到适当的文档。

PyQGIS API 文档更新不频繁,因为它几乎与 C++ API 的结构相同。然而,github.com上的 QGIS 项目维护了一个最新版本的 PyQGIS 类的列表。PyQGIS 2.6 API 位于github.com/qgis/QGIS/blob/master/python/qsci_apis/Python-2.6.api

您可以在主 C++ API 中找到已记录的类并了解它。然后,使用 PyQGIS API 列表查找相应的 Python 模块和类。在大多数情况下,类的 C++ API 名称与 Python 中的名称相同。

在本菜谱中,我们将定位控制 QGIS 标签的 PyQGIS 类。

如何操作…

我们将执行以下步骤,以查看 QGIS 标签对象和 QgsLabel 位于哪个 PyQGIS 模块中:

  1. 前往 QGIS API 页面qgis.org/api/2.6/index.html

  2. 点击模块标签。

  3. 点击链接QGIS 核心

  4. 按字母顺序滚动模块列表,直到您看到QgsLabel

  5. 点击QgsLabel链接以访问标签对象文档。

  6. 现在,前往 PyQGIS API 列表github.com/qgis/QGIS/blob/master/python/qsci_apis/Python-2.6.api

  7. 按字母顺序滚动类列表,直到您看到qgis.core.QgsLabel.LabelField

工作原理…

QGIS API 分为五个不同的类别,如下所示:

  • 核心

  • GUI

  • 分析

  • 地图编绘

  • 网络分析

大多数情况下,找到针对您所需功能的类很容易,因为 QGIS 的大部分功能都包含在通用的核心模块中。您使用 API 的次数越多,您就越快能找到您脚本所需的对象。

更多内容…

如果您在定位包含所需关键字的类时遇到困难,您可以使用 QGIS API 网站上的搜索引擎。

小贴士

然而,请注意,此搜索引擎返回的结果可能包含您不需要的项目,甚至可能因为不同模块中相似的关键词而将您引向错误的方向。

创建 QGIS 插件

插件是扩展 QGIS 的最佳方式,因为它们可以轻松更新并由其他人重用。

准备工作

创建插件的最简单方法就是使用插件构建器插件来快速启动开发。您可以在主 QGIS 插件仓库中找到它并进行安装。

如何操作…

执行以下步骤以创建一个显示自定义消息对话框的简单插件:

  1. 启动 QGIS。

  2. 插件菜单中选择插件构建器,然后在子菜单下点击插件构建器

  3. QGIS 插件构建器对话框中,将类命名为MyPlugin

  4. 将插件命名为 My Plugin

  5. 输入简短描述,例如 一个关于构建 QGIS 插件的演示。

  6. 模块 名称输入为 myplugin

  7. 保持默认版本号不变。

  8. 菜单项文本 字段中输入 My Plugin

  9. 输入您的姓名和电子邮件地址作为作者信息。

  10. 确保选中了标记为 将插件标记为实验性 的复选框,如图所示:如何操作…

  11. 点击 确定 按钮。

  12. 将会出现一个文件浏览器对话框;您可以选择一个文件夹来创建您的插件。在主用户目录或 QGIS 程序目录中的 python 文件夹中的 plugins 文件夹中选择一个文件夹。以下示例来自 Windows 机器。您应该使用用户目录中的文件夹,这是第三方插件的推荐位置。QGIS 标准插件位于主程序目录中:

    C:\Documents and Settings\Joel\.qgis2\python\plugins
    C:\Program Files\QGIS Brighton\apps\qgis\python\plugins
    
    
  13. 通过点击 确定 按钮关闭后续的 插件构建器 信息对话框。

  14. 使用命令提示符,导航到您的新插件模板文件夹。

  15. 使用 pyrcc4 命令编译资源文件:

    pyrcc4 –o resources_rc.py resources.qrc
    
    

    小贴士

    如果您使用的是 Windows,则非常重要,需要使用与 QGIS 一起安装的 OSGEO4W shell,以确保 Qt 编译工具能够正常工作。

  16. 在文本编辑器中,例如 Windows 记事本或 Linux 上的 vi,打开名为 myplugin_dialog_base.ui 的用户界面 XML 文件。

  17. 在第 31 行附近,在最后一个 </widget> 标签之前插入以下 XML 以创建自定义标签。编辑后保存文件:

    <widget class="QLabel" name="label">
    <property name="geometry">
    <rect>
    <x>120</x>
    <y>80</y>
    <width>201</width>
    <height>20</height>
    </rect>
    </property>
    <property name="font">
    <font>
    <pointsize>14</pointsize>
    </font>
    </property>
    <property name="text">
    <string>Geospatial Python Rocks!</string>
    </property>
    </widget>
    
  18. 现在,使用 pyuic4 工具编译 ui 文件:

    pyuic4 –o ui_myplugin.py ui_myplugin.ui
    
    
  19. 您的插件现在已准备就绪。重新启动 QGIS。

  20. 插件 菜单中选择 My Plugin,然后从子菜单中选择 My Plugin,以查看 QGIS 中创建的对话框,如图所示:如何操作…

它是如何工作的…

此配方展示了制作一个工作插件所需的基本内容。尽管我们没有对其进行修改,但插件行为的代码包含在 myplugin.py 中。您可以更改图标和 GUI,并且随时重新编译。请注意,我们必须编译插件的 Qt4 部分,这会创建对话框。整个 QGIS GUI 都是建立在 Qt4 库之上的,因此 pyrrc4 编译器和 pyuic4 包含在内以编译 GUI 小部件。

您可以从 geospatialpython.googlecode.com/svn/MyPlugin.zip 下载包含源代码和编译后的 ui 和资源文件的完整插件。

注意

您可以在 QGIS 文档中找到更多关于 QGIS 插件的信息,包括目录中其他文件的目的,请参阅 docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/plugins.html

更多信息…

我们已经手动编辑了 myplugin_dialog_base.ui XML 文件以进行一些小的修改。然而,使用 Qt Creator 有更好的方法。Qt Creator 是一个完整的开源 GUI 设计器,用于 Qt 框架。它是一个易于使用的所见即所得编辑器,用于 Qt Widgets,包括 PyQGIS 插件,它使用包含的 Qt Designer 接口。在 Windows 上,Qt Designer 可以在 QGIS 程序目录中的 bin 目录下找到。它被命名为 designer.exe。在其他平台上,Qt Designer 作为 qt4-devel 软件包的一部分提供。

注意事项

您还可以从 qt-project.org/downloads 下载 Qt Creator,其中包含 Qt Designer。

当您运行安装程序时,您可以取消选中所有安装选项,除了 工具 类别,以仅安装 IDE。

分发插件

分发 QGIS 插件意味着将文件集合作为一个 ZIP 文件放置在服务器上,并包含一个特殊的配置文件,以便 QGIS 插件管理器能够定位和安装插件。QGIS 项目有一个官方仓库,但也允许第三方仓库。官方仓库对插件的上传方式非常严格。因此,对于这个配方,我们将为示例插件设置一个简单的第三方仓库,并使用 QGIS 插件管理器进行测试,以避免将测试项目污染主 QGIS 仓库。

准备工作

为了完成这个配方,您需要一个示例插件和一个可公开访问的网络目录。您还需要一个 zip 工具,例如免费的 7-zip 程序 (www.7-zip.org/download.html)。您可以使用 Creating a QGIS plugin 配方中的 MyPlugin 示例作为要分发的插件。对于网络目录,您可以使用 Google Code 仓库、GitHub 仓库或您可以访问的其他在线目录。代码仓库工作得很好,因为它们是存储您正在开发的插件的好地方。

如何操作...

在以下步骤中,我们将打包我们的插件,为其创建一个服务器配置文件,并将其放置在服务器上以创建一个 QGIS 插件仓库:

  1. 首先,将插件目录压缩成 .ZIP 文件。

  2. .ZIP 文件重命名为包含插件版本号:

    Myplugin.0.1.0.zip
    
    
  3. 将此文件上传到公开可访问的网络目录。

  4. 将您的插件目录中的 icon.png 文件上传到网络目录。

  5. 接下来,为您的插件自定义一个 plugins.xml 元数据文件。您需要的绝大多数数据都可以在插件目录中的 metatdata.txt 文件中找到。以下示例提供了一些指导:

    <?xml version = '1.0' encoding = 'UTF-8'?>
    <?xml-stylesheet type="text/xsl" href="" ?>
    <plugins>
    <pyqgis_plugin name="My Plugin" version="0.1.0" plugin_id="227">
    <description>
    <![CDATA[Demonstration of a QGIS Plugin]]>
    </description>
    <about></about>
    <version>0.1.0</version>
    <qgis_minimum_version>1.8.0</qgis_minimum_version>
    <qgis_maximum_version>2.9.9</qgis_maximum_version>
    <homepage>
    <![CDATA[https://code.google.com/p/geospatialpython]]>
    </homepage>
    <file_name>MyPlugin.0.1.0.zip</file_name>
    <icon>
    http://geospatialpython.googlecode.com/svn/icon_227.png
    </icon>
    <author_name><![CDATA[Joel Lawhead]]></author_name>
    <download_url> http://geospatialpython.googlecode.com/svn/MyPlugin.0.1.0.zip
    </download_url>
    <uploaded_by><![CDATA[jll]]></uploaded_by>
    <create_date>2014-05-16T15:31:19.824333</create_date>
    <update_date>2014-07-15T15:31:19.824333</update_date>
    <experimental>True</experimental>
    <deprecated>False</deprecated>
    <tracker>
    <![CDATA[http://code.google.com/p/geospatialpython/issues]]>
    </tracker>
    <repository>
    <![CDATA[https://geospatialpython.googlecode.com/svn/]]>
    </repository>
    <tags>
    <![CDATA[development,debugging,tools]]></tags>
    <downloads>0</downloads>
    <average_vote>0</average_vote>
    <rating_votes>0</rating_votes>
    </pyqgis_plugin>
    </plugins>
    
  6. plugins.xml 文件上传到您的网络目录。

  7. 现在,启动 QGIS 并通过访问 插件 菜单并选择 管理并安装插件… 来启动插件管理器。

  8. 插件设置 对话框的 设置 选项卡中,向下滚动并点击 添加… 按钮。

  9. 给插件起一个名字,然后在 URL 字段中添加您 plugins.xml 的完整 URL。

  10. 点击确定按钮。

  11. 为了使事情更简单,通过选择存储库名称,点击编辑按钮,并取消选择启用复选框来禁用其他存储库。

  12. 点击确定按钮。

  13. 点击未安装选项卡。

  14. 您的测试插件应该是列表中唯一的插件,因此从列表中选择它。

  15. 点击窗口右下角的安装插件按钮。

  16. 点击关闭按钮。

  17. 前往插件菜单并选择您的插件以确保它正常工作。

它是如何工作的…

QGIS 存储库的概念简单而有效。plugins.xml 文件包含一个指向同一服务器或不同服务器上 ZIP 文件插件的 download_url 标签。pyqgis_plugin 标签的 name 属性在 QGIS 插件管理器中显示。

创建独立应用程序

QGIS 是一个完整的桌面 GIS 应用程序。然而,通过 PyQGIS,它也可以成为一个全面的地理空间 Python 库,用于构建独立应用程序。在这个菜谱中,我们将构建一个简单的独立脚本,该脚本创建一个带有线条的地图。

准备工作

为了准备,您需要确保已经按照 设置您的 QGIS IDE 菜谱中描述的配置了 Eclipse 和 PyDev 以进行 PyQGIS 开发。

如何操作…

在 PyDev 中,创建一个名为 MyMap 的新项目,包含一个名为 MyMap.py 的 Python 脚本,如下所示:

  1. 在 Eclipse 的文件菜单中,选择新建然后点击PyDev 项目

  2. 在 PyDev 项目的名称字段中,输入 MyMap

  3. 然后,从项目类型列表中选择Python单选按钮。

  4. 解释器下拉菜单中选择PyQGIS

  5. 保持将项目目录添加到 PYTHONPATH单选按钮选中。

  6. 点击完成按钮。

  7. 现在,在 PyDev 包资源管理器中选择项目。

  8. 文件菜单中选择新建,然后点击文件

  9. 将文件命名为 myMap.py

  10. 点击完成按钮。

  11. 将以下代码添加到编辑器中打开的文件:

    from qgis.core import *
    from qgis.gui import *
    from qgis.utils import *
    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    
    app = QgsApplication([], True)
    app.setPrefixPath("C:/Program Files/QGIS Brighton/apps/qgis", True)
    app.initQgis()
    canvas = QgsMapCanvas()
    canvas.setWindowTitle("PyQGIS Standalone Application Example")
    canvas.setCanvasColor(Qt.white)
    layer =  QgsVectorLayer('LineString?crs=epsg:4326', 'MyLine' , "memory")
    pr = layer.dataProvider()
    linstr = QgsFeature()
    geom = QgsGeometry.fromWkt("LINESTRING (1 1, 10 15, 40 35)")
    linstr.setGeometry(geom)
    pr.addFeatures([linstr])
    layer.updateExtents()
    QgsMapLayerRegistry.instance().addMapLayer(layer)
    canvas.setExtent(layer.extent())
    canvas.setLayerSet([QgsMapCanvasLayer(layer)])
    canvas.zoomToFullExtent()
    canvas.freeze(True)
    canvas.show()
    canvas.refresh()
    canvas.freeze(False)
    canvas.repaint()
    exitcode = app._exec()
    QgsApplication.exitQgis()
    sys.exit(exitcode)
    
  12. 运行菜单中选择运行

  13. 确认独立的 QGIS 地图在新窗口中显示,如图所示:如何操作…

它是如何工作的…

这个菜谱尽可能少地使用代码来创建地图画布并绘制线条,以展示独立应用程序的骨架,可以进一步构建以添加更多功能。

要创建线条几何形状,我们使用已知文本WKT),它提供了一种简单的方法来定义线条顶点,而无需创建大量对象。在这段代码的末尾,我们通过冻结画布来解决 QGIS 2.2 中的一个错误。当画布被冻结时,它不会响应用户事件,在这种情况下,这阻止了画布更新。一旦我们刷新画布,我们解冻它并重新绘制它以绘制线条。这个解决方案在 QGIS 2.4 和 2.6 中仍然有效,但不是必需的。

更多内容...

独立的应用程序可以编译成可执行文件,无需安装 QGIS,使用 py2exe 或 PyInstaller:

您可以在 www.py2exe.org 了解更多关于 py2exe 的信息。

您可以在 github.com/pyinstaller/pyinstaller/wiki 了解更多关于 PyInstaller 的信息。

存储和读取全局首选项

PyQGIS 允许您存储应用程序级别的首选项并检索它们。

准备工作

此代码可以在任何类型的 PyQGIS 应用程序中运行。在这个例子中,我们将运行它以进行简单的演示。在这个例子中,我们将更改新项目的默认坐标参考系统(CRS),然后从全局设置中读取该值。

如何操作…

在这个食谱中,我们将使用 Python 控制台设置 QGIS 用于新项目的默认投影:

  1. 启动 QGIS。

  2. 插件 菜单中选择 Python 控制台

  3. 我们需要导入 Qt 核心库,如下所示:

    from PyQt4.QtCore import *
    
  4. 在 Python 控制台中,运行以下代码:

    settings = QSettings(QSettings.NativeFormat, QSettings.UserScope, 'QuantumGIS', 'QGis')
    settings.setValue('/Projections/projectDefaultCrs', 'EPSG:2278')
    settings.value('/Projections/projectDefaultCrs')
    

工作原理…

此 API 实际上是 QGIS 依赖的 Qt API,用于设置。在 QSettings 对象中,我们指定存储的 NativeFormat,这是平台的默认格式。在 Windows 上,格式是注册表;在 OS X 上,是 plist 文件;在 Unix 上,是文本文件。其他 QSettings 参数是 组织应用程序,通常用作存储信息的层次结构。请注意,即使更改了这些设置,QGIS GUI 中的某些属性也可能不会立即更改。在某些情况下,例如 Windows,必须重新启动系统才能使注册表更改生效。然而,一切都将通过编程方式工作。

更多内容…

如果您想查看可以更改的所有选项,请调用 QSettings 的 allKeys() 方法;这将返回所有设置名称的列表。

存储和读取项目首选项

QGIS 应用程序设置使用 Qt API 存储。然而,QGIS 项目设置有自己的对象。在这个食谱中,我们将设置和读取项目标题,然后设置和读取一个插件的自定义首选项。

准备工作

我们将使用之前创建的示例插件 创建 QGIS 插件 来设置插件首选项。您也可以替换您想要的任何插件名称。我们还将在这个 QGIS Python 控制台中运行此食谱以进行快速测试,但此代码通常用于插件中。

如何操作…

在这个食谱中,我们首先将写入并读取当前项目的标题。然后,我们将为名为 splash 的插件创建一个自定义值,如果需要,可以用于插件的启动动画屏幕。

  1. 启动 QGIS。

  2. 插件 菜单中选择 Python 控制台

  3. 在控制台中,运行以下代码:

    proj = QgsProject.instance()
    proj.title("My QGIS Project")
    proj.title()
    proj.writeEntry("MyPlugin", "splash", "Geospatial Python Rocks!")
    proj.readEntry("MyPlugin", "splash", "Welcome!")[0]
    

工作原理…

在前两行中,我们更改了当前活动项目的标题,然后将其回显。在下一组两行中,我们为插件设置了自定义设置并读取了它们。请注意,readEntry() 方法返回一个包含所需文本和布尔值的元组,确认该值已设置。因此,我们提取第一个索引以获取文本。读取方法还允许在属性未设置的情况下使用默认文本(而不是抛出必须处理的异常),以及布尔值 False 来通知你,由于属性未设置,使用了默认文本。使用此方法设置的值在保存项目时存储在项目的 XML 文件中。

更多内容…

QgsProject 对象有多个方法和属性可能很有用。QGIS API 文档详细介绍了它们在qgis.org/api/2.6/classQgsProject.html

在你的脚本中访问脚本路径

有时候,你需要确切地知道当前的工作目录在哪里,这样你才能访问外部资源。

准备工作

此代码使用 Python 内置库,可以在任何上下文中使用。我们将在这个 QGIS Python 控制台中运行这个菜谱。

如何操作…

在这个菜谱中,我们将获取 Python 控制台当前的工作目录,它可能会随着配置而改变:

  1. 启动 QGIS。

  2. 插件 菜单中选择 Python 控制台

  3. 在 Python 控制台中,运行以下代码:

    import os
    os.getcwd()
    

它是如何工作的…

QGIS 严重依赖于文件系统路径来运行应用程序和管理外部数据。当编写跨平台的 QGIS 代码时,你不能假设你的脚本的工作目录。

更多内容…

在他的博客上,一位 QGIS 开发者有一篇关于 QGIS 中路径变量各个方面的优秀文章,而不仅仅是执行目录;你可以在spatialgalaxy.net/2013/11/06/getting-paths-with-pyqgis/查看。

第二章:查询矢量数据

在本章中,我们将涵盖以下食谱:

  • 从文件加载矢量图层

  • 从地理数据库中加载矢量图层

  • 检查矢量图层要素

  • 检查矢量图层属性

  • 通过几何形状过滤图层

  • 通过属性过滤图层

  • 缓冲要素

  • 测量两点之间的距离

  • 沿着线测量距离

  • 计算多边形的面积

  • 创建空间索引

  • 计算线的方位角

简介

本章演示了如何在 QGIS 中通过 Python 处理矢量数据。我们首先将加载不同来源的矢量数据。接下来,我们将检查数据的内容。然后,我们将在本章的剩余部分对矢量数据进行空间和数据库操作。

从文件样本中加载矢量图层

本食谱描述了 QGIS 中最常用的数据类型,即文件。在大多数情况下,您将通过加载 shapefile 来开始一个 QGIS 项目。

准备工作

为了便于跟随本书中的示例,建议你在根目录或用户目录中创建一个名为 qgis_data 的目录,这提供了一个简短的路径名。这种设置将有助于防止因特定系统上路径相关问题的出现而导致的任何令人沮丧的错误。在本食谱和其他食谱中,我们将使用纽约市博物馆的点要素图层,您可以从geospatialpython.googlecode.com/svn/NYC_MUSEUMS_GEO.zip下载。

解压此文件,并将 shapfile 的内容放置在 qgis_data 目录中名为 nyc 的目录内。

如何操作...

现在,我们将逐步说明加载 shapefile 并将其添加到地图中的步骤,如下所示:

  1. 启动 QGIS。

  2. 插件 菜单中选择 Python 控制台

  3. 在 Python 控制台中创建图层:

    layer = QgsVectorLayer("/qgis_data/nyc/NYC_MUSEUMS_GEO.shp", "New York City Museums", "ogr")
    
    
  4. 接下来,确保图层已按预期创建:

    if not layer.isValid():
     print "Layer %s did not load" % layer.name()
    
    
  5. 最后,将图层添加到图层注册表中:

    QgsMapLayerRegistry.instance().addMapLayers([layer])
    
    

确认你的 QGIS 地图看起来与以下图像相似:

如何操作...

它是如何工作的...

QgsVectorLayer 对象需要文件的位置、QGIS 中图层的名称以及提供正确解析器和为文件格式管理功能的提供者。大多数矢量图层都由 ogr 提供者覆盖,它试图从文件名扩展名中猜测格式,以便使用适当的驱动程序。此提供者可用的格式列在www.gdal.org/ogr_formats.html

一旦我们创建了 QgsVector 对象,我们使用 layer.isValid() 方法进行快速检查,以查看文件是否正确加载。我们不会在每一个菜谱中使用此方法以保持代码简短,但此方法通常非常重要。它通常是唯一表明出错的指示。如果您在文件名中输入了拼写错误,或者您尝试连接到在线数据源但没有网络连接,您将看不到任何错误。您的第一个指示将是代码中更远处的另一个方法失败,这将使追踪根本原因更加困难。

在最后一行,我们将矢量图层添加到 QgsMapLayerRegistry 中,使其在地图上可用。注册表跟踪项目中的所有图层。QGIS 以这种方式工作的原因是为了您可以在将图层暴露给地图上的用户之前,加载多个图层,对它们进行样式设置,过滤它们,并执行其他操作。

从空间数据库加载矢量图层

PostGIS 地理数据库基于开源的 Postgres 数据库。地理数据库提供了强大的地理空间数据管理和操作功能。PyQGIS 完全支持 PostGIS 作为数据源。在这个菜谱中,我们将从一个 PostGIS 数据库中添加一个图层。

准备工作

安装和配置 PostGIS 超出了本书的范围,因此我们将使用来自优秀服务 www.QGISCloud.com 的示例地理空间数据库接口。 www.QGISCloud.com 有自己的 Python 插件,称为 QGIS Cloud。您可以免费注册并按照网站说明在线创建自己的地理数据库,或者您可以使用菜谱中使用的示例。

如何做...

执行以下步骤将 PostGIS 图层加载到 QGIS 地图中:

  1. 首先,创建一个新的 DataSourceURI 实例:

    uri = QgsDataSourceURI()
    
    
  2. 然后,创建数据库连接字符串:

    uri.setConnection("spacialdb.com", "9999", "lzmjzm_hwpqlf", "lzmjzm_hwpqlf", "0e9fcc39")
    
    
  3. 现在,描述数据源:

    uri.setDataSource("public", "islands", "wkb_geometry", "")
    
    
  4. 然后,创建图层:

    layer = QgsVectorLayer(uri.uri(), "Islands", "postgres")
    
    
  5. 为了确保安全,请确保一切正常工作:

    if not layer.isValid():
     print "Layer %s did not load" % layer.name()
    
    
  6. 最后,如果一切正常,将图层添加到地图中:

    QgsMapLayerRegistry.instance().addMapLayers([layer])
    
    

您可以在地图中看到 islands 图层,如下面的截图所示:

如何做...

它是如何工作的...

PyQGIS 在 API 中提供了一个对象,用于在 QgsDataSourceURI() 中创建 PostGIS 数据源。代码第二行的 connection 字符串参数是数据库服务器、端口、数据库名、用户和密码。在示例中,数据库、用户名和密码是随机生成的唯一名称。数据源参数是模式名称、表名称、几何列以及可选的 SQL WHERE 子句,根据需要子集图层。

检查矢量图层特征

一旦加载了矢量图层,您可能想调查数据。在这个菜谱中,我们将从一个 shapefile 中加载矢量点图层,并查看第一个点的 xy 值。

准备工作

我们将使用本章中从文件加载矢量图层配方中的相同的纽约市博物馆图层。你可以从geospatialpython.googlecode.com/svn/NYC_MUSEUMS_GEO.zip下载该图层。

解压该文件,并将 shapefile 的内容放置在你的根目录或主目录下的qgis_data目录中的nyc目录内。

如何操作...

在本配方中,我们将加载图层,获取特征,获取第一个特征,获取其几何形状,并查看第一个点的值:

  1. 首先,加载图层:

    layer = QgsVectorLayer("/qgis_data/nyc/NYC_MUSEUMS_GEO.shp", "New York City Museums", "ogr")
    
    
  2. 接下来,获取图层特征的迭代器:

    features = layer.getFeatures()
    
    
  3. 现在,从迭代器中获取第一个特征:

    f = features.next()
    
    
  4. 然后,获取特征的几何形状:

    g = f.geometry()
    
    
  5. 最后,获取点的值:

    g.asPoint()
    
    
  6. 确认 Python 控制台输出类似于以下 QgsPoint 对象:

    (-74.0138,40.7038)
    
    

工作原理...

当你使用之前演示的方法访问图层的特征或几何形状时,PyQGIS 返回一个 Python 迭代器。迭代器数据结构允许 Python 在不需要将整个数据集保留在内存中的情况下,高效地处理非常大的数据集。

检查矢量图层属性

一个真正的 GIS 图层包含空间几何和数据库属性。在本配方中,我们将使用 PyQGIS 访问矢量点图层的属性。我们将使用来自 shapefile 的基于文件的图层,但一旦图层在 QGIS 中加载,每个矢量图层的工作方式都是相同的。

准备工作

再次强调,我们将使用本章中从文件加载矢量图层配方中的相同的纽约市博物馆图层。你可以从geospatialpython.googlecode.com/svn/NYC_MUSEUMS_GEO.zip下载该图层。

解压该文件,并将 shapefile 的内容放置在你的根目录或主目录下的qgis_data目录中的nyc目录内。

如何操作...

在以下步骤中,我们将加载图层,访问features迭代器,获取第一个特征,然后以 Python 列表的形式查看属性:

  1. 首先,将 shapefile 作为矢量图层加载:

    layer = QgsVectorLayer("/qgis_data/nyc/NYC_MUSEUMS_GEO.shp", "New York City Museums", "ogr")
    
    
  2. 接下来,获取特征迭代器:

    features = layer.getFeatures()
    
    
  3. 现在,从迭代器中获取第一个特征:

    f = features.next()
    
    
  4. 最后,以 Python 列表的形式检查属性:

    f.attributes()
    
    
  5. 确认 Python 控制台的输出类似于以下列表:

    [u'Alexander Hamilton U.S. Custom House', u'(212) 514-3700', u'http://www.oldnycustomhouse.gov/', u'1 Bowling Grn', NULL, u'New York', 10004.0, -74.013756, 40.703817]
    
    

工作原理...

检查属性与访问图层几何形状的点值是一致的。请注意,所有字符串属性值都作为 unicode 字符串返回,这是所有 QGIS 字符串的情况。Unicode 允许 QGIS 除了英语以外的其他语言的国际化(即翻译)。

更多...

属性值如果没有了解这些值代表的意义,那么它们就没有太多意义。你还需要了解字段。你可以通过访问fields迭代器并调用每个字段的name()方法来获取字段列表。这个操作可以通过 Python 列表推导式轻松完成:

[c.name() for c in f.fields().toList()]

此示例返回以下结果:

[u'NAME', u'TEL', u'URL', u'ADRESS1', u'ADDRESS2', u'CITY', u'ZIP', u'XCOORD', u'YCOORD']

通过几何形状过滤图层

在本配方中,我们将执行空间操作,根据重叠的多边形图层中的点选择点图层的一个子集。我们将在这两种情况下都使用 shapefiles,其中一个是点图层,另一个是多边形。这种子集是 GIS 操作中最常见的一种。

准备工作

我们需要两个新的 shapefiles,这些 shapefiles 在之前的配方中尚未使用。你可以从 geospatialpython.googlecode.com/files/MSCities_Geo_Pts.zip 下载点图层。

类似地,你可以从 geospatialpython.googlecode.com/files/GIS_CensusTract.zip 下载几何图层。

解压这些 shapefiles 并将它们放置在你根目录或主目录下的 qgis_data 目录中的 ms 目录内。

如何操作...

在本配方中,我们将执行几个步骤来选择点图层中位于多边形图层内的要素,如下所示:

  1. 首先,加载点图层:

    lyrPts = QgsVectorLayer("/qgis_data/ms/MSCities_Geo_Pts.shp", "MSCities_Geo_Pts", "ogr")
    
    
  2. 接下来,加载多边形图层:

    lyrPoly = QgsVectorLayer("/qgis_data/ms/GIS_CensusTract_poly.shp", "GIS_CensusTract_poly", "ogr")
    
    
  3. 使用列表将图层添加到地图中:

    QgsMapLayerRegistry.instance().addMapLayers([lyrPts,lyrPoly])
    
    
  4. 访问多边形图层的要素:

    ftsPoly = lyrPoly.getFeatures()
    
    
  5. 现在,迭代多边形的要素:

    for feat in ftsPoly:
    
    
  6. 获取每个要素的几何形状:

     geomPoly = feat.geometry() 
    
    
  7. 访问点要素并通过多边形的边界框过滤点要素:

     featsPnt = lyrPts.getFeatures(QgsFeatureRequest().setFilterRect(geomPoly.boundingBox()))
    
    
  8. 迭代每个点并检查它是否位于多边形本身内:

     for featPnt in featsPnt:
     if featPnt.geometry().within(geomPoly):
    
    
  9. 如果多边形包含该点,打印点的 ID 并选择该点:

     print featPnt.id()
     lyrPts.select(featPnt.id())
    
    
  10. 现在,将多边形图层设置为活动地图图层:

    iface.setActiveLayer(lyrPoly)
    
    
  11. 将视图缩放到多边形图层的最大范围:

    iface.zoomToActiveLayer()
    
    

确认你的地图看起来与以下图像相似:

如何操作...

工作原理...

虽然 QGIS 提供了多种空间选择工具,但 PyQGIS 没有专门用于这些类型功能的 API。然而,由于底层 ogr/GEOS 库的存在,API 中有足够的方法,你可以轻松地为两层创建自己的空间过滤器。第 7 步并非完全必要,但使用多边形的边界框来限制我们检查的点特征数量可以提高一些效率。涉及矩形的计算比详细的点在多边形内查询要快得多。因此,我们快速减少需要迭代的点数量,以便进行更昂贵的空间操作。

通过属性过滤图层

除了前一个配方中概述的空间查询之外,我们还可以通过属性对图层进行子集划分。这种查询类似于更传统的数据库查询,实际上使用了 SQL 语句。在本配方中,我们将通过一个属性过滤基于点 shapefile 的图层。

准备工作

我们将使用本章中之前配方中使用的相同的纽约市博物馆层。您可以从geospatialpython.googlecode.com/svn/NYC_MUSEUMS_GEO.zip下载该层。

解压该文件,并将 shapefile 的内容放置在您的根目录或主目录中的qgis_data目录下的nyc目录中。

如何操作...

在此配方中,我们将通过属性过滤层,选择过滤后的要素,并缩放到它们,如下所示:

  1. 首先,我们加载点层:

    lyrPts = QgsVectorLayer("/qgis_data/nyc/NYC_MUSEUMS_GEO.shp", "Museums", "ogr")
    
    
  2. 接下来,我们将层添加到地图中以便可视化点:

    QgsMapLayerRegistry.instance().addMapLayers([lyrPts])
    
    
  3. 现在,我们过滤点层以匹配特定邮编的属性点:

    selection = lyrPts.getFeatures(QgsFeatureRequest().setFilterExpression(u'"ZIP" = 10002'))
    
    
  4. 然后,我们使用列表推导来创建一个特征 ID 列表,该列表被馈送到特征选择方法:

    lyrPts.setSelectedFeatures([s.id() for s in selection])
    
    
  5. 最后,我们将缩放到选择区域:

    iface.mapCanvas().zoomToSelected()
    
    

验证点层有三个选定的要素,以黄色显示。

它是如何工作的...

此配方利用 QGIS 过滤表达式,在第 3 步中突出显示。这些过滤表达式是 SQL 的一个子集。QgsFeatureRequest将查询表达式作为可选参数处理,以返回仅包含您想要的要素的迭代器。这些查询还允许一些基本的几何操作。此配方还介绍了mapCanvas().zoomToSelected()方法,这是一种方便地将地图范围设置为感兴趣要素的方法。

缓冲要素的中间过程

缓冲一个要素会在要素周围创建一个多边形,作为选择几何或简单的可视化。在此配方中,我们将缓冲点要素中的点,并将返回的多边形几何添加到地图上。

准备工作

再次,我们将使用相同的纽约市博物馆层。您可以从geospatialpython.googlecode.com/svn/NYC_MUSEUMS_GEO.zip下载该层。

解压该文件,并将 shapefile 的内容放置在您的根目录或主目录中的qgis_data目录下的nyc目录中。

如何操作...

此配方涉及空间操作和多个可视化。为此,执行以下步骤:

  1. 首先,加载层:

    lyr = QgsVectorLayer("/qgis_data/nyc/NYC_MUSEUMS_GEO.shp", "Museums", "ogr")
    
    
  2. 接下来,在地图上可视化层:

    QgsMapLayerRegistry.instance().addMapLayers([lyr])
    
    
  3. 访问层的要素:

    fts = lyr.getFeatures()
    
    
  4. 获取第一个要素:

    ft = fts.next()
    
    
  5. 选择此要素:

    lyr.setSelectedFeatures([ft.id()])
    
    
  6. 创建缓冲区:

    buff = ft.geometry().buffer(.2,8)
    
    
  7. 为缓冲的几何设置内存层:

    buffLyr =  QgsVectorLayer('Polygon?crs=EPSG:4326', 'Buffer' , 'memory')
    
    
  8. 访问层的提供者:

    pr = buffLyr.dataProvider()
    
    
  9. 创建一个新要素:

    b = QgsFeature()
    
    
  10. 使用缓冲几何设置要素的几何:

    b.setGeometry(buff)
    
    
  11. 将要素添加到数据提供者:

    pr.addFeatures([b])
    
    
  12. 更新缓冲层的范围:

    buffLyr.updateExtents()
    
    
  13. 设置缓冲层的透明度,以便您可以看到其他要素:

    buffLyr.setLayerTransparency(70)
    
    
  14. 将缓冲层添加到地图:

    QgsMapLayerRegistry.instance().addMapLayers([buffLyr])
    
    

验证您的地图看起来与这张截图相似:

如何操作...

它是如何工作的...

本菜谱的有趣部分从第 6 步开始,该步创建缓冲几何体。buffer()方法的参数是缓冲区的地图单位距离,然后是用于近似曲线的直线段数。您指定的段数越多,缓冲区就越像圆形。然而,更多的段数意味着更大的几何复杂性,因此渲染速度和几何计算速度都会变慢。本菜谱的另一个有趣特性是第 13 步,其中我们将图层的透明度设置为 70%。我们还介绍了创建新图层的方法,这是在内存中完成的。后面的章节将更深入地介绍创建数据。

测量两点之间的距离

QgsDistanceArea对象中,PyQGIS 具有出色的测量距离功能。我们将使用此对象进行多个菜谱,从测量两点之间的距离开始。

准备工作

如果您还没有本章之前菜谱中使用的纽约市博物馆图层,请从geospatialpython.googlecode.com/svn/NYC_MUSEUMS_GEO.zip下载图层。

解压该文件,并将 shapefile 的内容放置在您的根目录或主目录下的qgis_data目录中的名为nyc的目录内。

如何操作...

在以下步骤中,我们将提取图层点顺序中的第一个和最后一个点并测量它们之间的距离:

  1. 首先,导入包含 QGIS 内容的库:

    from qgis.core import QGis
    
    
  2. 然后,加载图层:

    lyr = QgsVectorLayer("/qgis_data/nyc/NYC_MUSEUMS_GEO.shp", "Museums", "ogr")
    
    
  3. 访问功能:

    fts = lyr.getFeatures()
    
    
  4. 获取第一个特征:

    first = fts.next()
    
    
  5. 为最后一个特征设置占位符:

    last = fts.next()
    
    
  6. 遍历特征直到获取最后一个:

    for f in fts:
     last = f
    
    
  7. 创建测量对象:

    d = QgsDistanceArea()
    
    
  8. 测量距离:

    m = d.measureLine(first.geometry().asPoint(), last.geometry().asPoint())
    
    
  9. 将测量值从十进制度数转换为米:

    d.convertMeasurement(m, 2, 0, False)
    
    
  10. 确保您的 Python 控制台输出类似于以下元组:

    (4401.1622240174165, 0)
    
    

它是如何工作的...

QgsDistanceArea对象接受不同类型的几何体作为输入。在这种情况下,我们使用两个点。该图层的地图单位是十进制度数,对于距离测量来说没有意义。因此,我们使用QgsDistanceArea.convertMeasurement()方法将输出转换为米。该方法参数包括测量输出、输入单位(十进制度数)、输出单位(米)以及一个布尔值,表示此转换是面积计算还是线性测量。

返回的元组是测量值和单位。值 0 告诉我们输出是在米。

沿线样测量距离

在本菜谱中,我们将测量具有多个顶点的线上的距离。

准备工作

对于本菜谱,我们将使用具有两个特征的线状 shapefile。您可以从geospatialpython.googlecode.com/svn/paths.zip下载 shapefile 作为.ZIP文件。

将 shapefile 解压到根目录或主目录下的 qgis_data/shapes 目录中。

如何操作...

此菜谱的步骤相当直接。我们将从第一行要素中提取几何形状,并将其传递给测量对象,如下所示:

  1. 首先,我们必须加载 QGIS 常量库:

    from qgis.core import QGis
    
    
  2. 加载线图层:

    lyr = QgsVectorLayer("/qgis_data/shapes/paths.shp", "Route", "ogr")
    
    
  3. 获取要素:

    fts = lyr.getFeatures()
    
    
  4. 获取第一个要素:

    route = fts.next()
    
    
  5. 创建测量对象实例:

    d = QgsDistanceArea()
    
    
  6. 然后,我们必须配置 QgsDistanceArea 对象以使用椭圆模式进行米级的精确测量:

    d.setEllipsoidalMode(True)
    
    
  7. 将行的几何形状传递给 measureLine 方法:

    m = d.measureLine(route.geometry().asPolyline())
    
    
  8. 将测量输出转换为英里:

    d.convertMeasurement(m, QGis.Meters, QGis.NauticalMiles, False)
    
    

确保你的输出看起来类似于以下内容:

(2314126.583384674, 7)

作用原理...

QgsDistanceArea 对象可以根据你调用的方法执行任何类型的测量。当你将测量值从米(用 0 表示)转换为英里(用数字 7 表示)时,你将得到一个包含测量值和单位标识符的元组。QGIS API 文档显示了所有单位常量的值

(qgis.org/api/classQGis.html).

计算多边形的面积

此菜谱仅测量多边形的面积。

准备工作

对于此菜谱,我们将使用单个要素的多边形 shapefile,您可以从 geospatialpython.googlecode.com/files/Mississippi.zip 下载

解压 shapefile 并将其放入根目录或主目录下的 qgis_data/ms 目录中。

如何操作...

执行以下步骤以测量大多边形的面积:

  1. 首先,导入 QGIS 常量库,如下所示:

    from qgis.core import QGis
    
    
  2. 加载图层:

    lyr = QgsVectorLayer("/qgis_data/ms/mississippi.shp", "Mississippi", "ogr")
    
    
  3. 访问图层的要素:

    fts = lyr.getFeatures()
    
    
  4. 获取边界要素:

    boundary = fts.next()
    
    
  5. 创建测量对象实例:

    d = QgsDistanceArea()
    
    
  6. 将多边形列表传递给 measureArea() 方法:

    m = d.measurePolygon(boundary.geometry().asPolygon()[0])
    
    
  7. 将测量值从十进制度数转换为英里:

    d.convertMeasurement(m, QGis.Degrees, QGis.NauticalMiles, True) 
    
    
  8. 确保你的输出看起来类似于以下内容:

    (42955.47889640281, 7)
    
    

作用原理...

PyQIS 没有提供 measureArea() 方法,但在 QgsDistanceArea 对象中有一个 measurePolygon() 方法。该方法接受一个点列表。在这种情况下,当我们从十进制度数转换为英里时,我们还在 convertMeasurement() 方法中指定 True,这样 QGIS 就知道这是一个面积计算。请注意,当我们获取边界几何形状作为多边形时,我们使用索引 0,这表明存在多个多边形。多边形几何形状可以具有内环,这些内环被指定为额外的多边形。最外层的环,在这种情况下是唯一的环,是第一个多边形。

创建空间索引

到目前为止,本书中的食谱使用了每个操作层的原始几何形状。在本食谱中,我们将采取不同的方法,在运行操作之前为图层创建空间索引。空间索引通过创建额外的、更简单的几何形状来优化图层,这些几何形状可以用来缩小复杂几何形状中的可能性范围。

准备工作

如果你还没有在本书前面的食谱中使用的纽约市博物馆图层,请从geospatialpython.googlecode.com/svn/NYC_MUSEUMS_GEO.zip下载图层。

解压缩该文件,并将形状文件的内容放置在根目录或主目录中的qgis_data目录下的nyc目录中。

如何操作...

在本食谱中,我们将为点图层创建空间索引,然后我们将使用它来执行空间查询,如下所示:

  1. 加载图层:

    lyr = QgsVectorLayer("/qgis_data/nyc/NYC_MUSEUMS_GEO.shp", "Museums", "ogr")
    
    
  2. 获取特征:

    fts = lyr.getFeatures()
    
    
  3. 获取集合中的第一个特征:

    first = fts.next()
    
    
  4. 现在,创建空间索引:

    index = QgsSpatialIndex()
    
    
  5. 开始加载特征:

    index.insertFeature(first)
    
    
  6. 插入剩余的特征:

    for f in fts:
     index.insertFeature(f)
    
    
  7. 现在,选择距离第一个点最近的 3 个点的 ID。我们使用数字4,因为起始点包含在输出中:

    hood = index.nearestNeighbor(first.geometry().asPoint(), 4)
    
    

工作原理...

索引可以加快空间操作的速度。但是,你必须逐个添加每个特征。此外,请注意,nearestNeighbor()方法将起始点的 ID 作为输出的一部分返回。因此,如果你想获取4个点,你必须指定5

计算线的方位角

有时,你需要知道线的罗盘方位角来创建专门的符号或将其用作空间计算中的输入。尽管其名称只提到距离和面积,但多功能的QgsDistanceArea对象也包括这个功能。在本食谱中,我们将计算线的端点的方位角。然而,这个食谱可以适用于任何两个点。

准备工作

我们将使用之前食谱中使用的线形状文件。您可以从geospatialpython.googlecode.com/svn/paths.zip下载形状文件作为.ZIP文件。

将形状文件解压缩到根目录或主目录中的qgis_data/shapes目录下。

如何操作...

要执行的操作步骤与获取所需的两个点并将它们通过方位角函数运行一样简单,从弧度转换为度数,然后转换为正罗盘方位角:

  1. 首先,导入 Python 数学模块:

    import math
    
    
  2. 接下来,加载图层:

    lyr = QgsVectorLayer("/qgis_data/shapes/paths.shp", "Route", "ogr")
    
    
  3. 现在,获取特征:

    fts = lyr.getFeatures()
    
    
  4. 然后,获取第一条线特征:

    route = fts.next()
    
    
  5. 创建测量对象:

    d = QgsDistanceArea()
    
    
  6. 你必须将椭球模式设置为True,以便在计算方位角之前投影数据:

    d.setEllipsoidalMode(True)
    
    
  7. 获取所有点作为列表:

    points = route.geometry().asPolyline()
    
    
  8. 获取第一个点:

    first = points[0]
    
    
  9. 获取最后一个点:

    last = points[-1]
    
    
  10. 以弧度计算方位角:

    r = d.bearing(first, last)
    
    
  11. 现在,将弧度转换为度数:

    b = math.degrees(r)
    
    
  12. 确保方位角为正值:

    if b < 0: b += 360
    
    
  13. 查看输出:

    print b
    
    

确认方位角接近以下数字:

320.3356091875395

它是如何工作的...

磁方位角计算默认输出为弧度。然而,Python 的 math 模块使得转换变得轻而易举。如果度数的转换结果为负数,大多数情况下我们都会想将这个数加到 360 上,以得到罗盘方位角,就像我们在这里做的那样。

从电子表格加载数据

电子表格是收集和存储简单地理数据最常用的方法之一。QGIS 可以处理称为 CSV 或逗号分隔值文件的文本文件。任何电子表格都可以使用电子表格程序转换为 CSV。只要 CSV 数据有一个表示 x 值的列,一个表示 y 值的列,以及其他包含第一行字段名的数据列,QGIS 就可以导入它。许多组织以 CSV 的形式分发地理信息,所以迟早您会发现自己正在导入 CSV。此外,PyQGIS 允许您以编程方式完成此操作。请注意,只要一致,CSV 可以由任何字符分隔。此外,CSV 文件的文件扩展名并不重要,只要您为 QGIS 指定文件类型即可。

准备工作

我们将使用一个包含点特征的样本 CSV 文件,这些特征代表一个区域内感兴趣点的位置。您可以从 geospatialpython.googlecode.com/svn/MS_Features.txt 下载此样本。

将其保存到您的根目录或主目录下的 qgis_data/ms 目录中。

如何操作...

我们将构建一个用于将 CSV 作为矢量图层加载的 URI 字符串。描述 CSV 结构的所有参数都包含在以下 URI 中:

  1. 首先,我们构建包含文件名的基本 URI 字符串:

    uri="""file:///qgis_data/ms/MS_Features.txt?"""
    
    
  2. 接下来,我们告诉 QGIS 该文件是 CSV 文件:

    uri += """type=csv&"""
    
    
  3. 现在,我们指定我们的分隔符,它是一个管道("|"),作为一个 URL 编码值:

    uri += """delimiter=%7C&"""
    
    
  4. 接下来,我们告诉 QGIS 去除字段两端的任何空格:

    uri += """trimFields=Yes&"""
    
    
  5. 现在,最重要的部分,我们指定 x 字段:

    uri += """xField=PRIM_LONG_DEC&"""
    
    
  6. 然后,我们指定 y 字段:

    uri += """yField=PRIM_LAT_DEC&"""
    
    
  7. 我们拒绝空间索引选项:

    uri += """spatialIndex=no&"""
    
    
  8. 我们拒绝子集选项:

    uri += """subsetIndex=no&"""
    
    
  9. 我们告诉 QGIS 不要监视文件变化:

    uri += """watchFile=no&"""
    
    
  10. 最后,我们使用图层的坐标参考系统(CRS)完成 uri

    uri += """crs=epsg:4326"""
    
    
  11. 我们使用 delimitedtext 数据提供者加载图层:

    layer=QgsVectorLayer(uri,"MS Features","delimitedtext")
    
    
  12. 最后,我们将它添加到地图上:

    QgsMapLayerRegistry.instance().addMapLayers([layer])
    
    

确认您的地图看起来与以下截图中的地图相似:

如何操作...

它是如何工作的...

URI 非常详细,但这是必要的,以便 QGIS 获取足够的信息来正确加载图层。在这个简单的例子中,我们使用了字符串,但使用 QUrl 对象更安全,因为它为您处理编码。QUrl 类的文档在 Qt 文档中,网址为 qt-project.org/doc/qt-4.8/qurl.html

注意,在 URI 中,我们告诉 QGIS 类型是 CSV,但当我们加载图层时,类型是 delimitedtext。只要所有列都平衡,QGIS 会忽略空字段。

还有更多...

如果你在加载图层时遇到困难,可以使用QGIS 添加分隔文本图层…对话框,在图层菜单下找出正确的参数。一旦图层加载成功,你可以查看其元数据以了解 QGIS 构建的用于加载它的 URI。你也可以通过使用layer.source()方法程序化地从已加载的分隔文本图层中获取正确的参数。当然,这两种方法都适用于任何类型的图层,而不仅仅是分隔文本图层。然而,与其他图层类型不同,你无法在 QGIS 中编辑分隔文本图层。

第三章。编辑向量数据

在本章中,我们将介绍以下配方:

  • 在内存中创建向量层

  • 向向量层添加点要素

  • 向向量层添加线要素

  • 向向量层添加多边形要素

  • 向向量层添加一组属性

  • 向向量层添加字段

  • 将 shapefile 属性表连接到 CSV 文件

  • 移动向量层几何形状

  • 修改向量层属性

  • 删除向量层几何形状

  • 删除向量层字段

  • 删除向量层属性

  • 重投影向量层

  • 将 shapefile 转换为 Keyhole 标记语言 (KML)

  • 合并 shapefile

  • 分割 shapefile

  • 通用化向量层

  • 溶解向量形状

  • 在向量形状上执行联合操作

  • 向量层栅格化

简介

本章详细介绍了如何使用 Python API 编辑 QGIS 向量数据。QgsVectorLayer 对象包含了添加、编辑和删除要素的基本功能。所有其他地理空间操作都可以通过 处理工具箱 或甚至通过自定义脚本来访问。

在内存中创建向量层

有时,你需要创建一个临时数据集以快速输出,或者在更复杂的操作中作为中间步骤,而不需要实际将文件写入磁盘。PyQGIS 使用 内存层 允许你创建一个完整的向量数据集,包括几何形状、字段和属性,虚拟地。一旦创建了内存层,你就可以像处理从磁盘加载的向量层一样处理它。

准备工作

此配方完全在 PyQGIS 控制台中运行,因此不需要准备或外部资源。

如何做到...

我们将创建一个名为 Layer 1Point 向量层,包含一些字段,然后验证它:

  1. 启动 QGIS。

  2. 插件 菜单中选择 Python 控制台

  3. 在 Python 控制台中创建一个 QgsVectorLayer,包括字段,并将其指定为内存数据提供者:

    vectorLyr =  QgsVectorLayer('Point?crs=epsg:4326&field=city:string(25)&field=population:nt', 'Layer 1' , "memory")
    
    
  4. 现在,验证层并确保控制台返回 True

    vectorLyr.isValid()
    
    

它是如何工作的...

QgsVectorLayer 需要三个参数。最后一个参数指定类型,在这种情况下是 memory。第二个参数指定图层名称。通常,第一个参数是磁盘上文件的路径,用于创建图层。在内存层的情况下,第一个参数成为图层的构建字符串。该格式使用遵循 key = value 约定的查询参数。我们首先指定坐标参考系统,然后指定我们想要的字段。在这种情况下,我们指定第一个字段,一个用于城市名称的字符串字段,然后是一个用于人口的整数字段。

更多内容...

你可以很容易地看到如何用字符串描述图层属性表结构可能会变得难以管理。你还可以使用 Python-有序字典动态构建字符串,如下面的步骤所示。

  1. 首先,你需要导入 OrderedDict 容器,它记得键插入的顺序:

    from collections import OrderedDict
    
    
  2. 然后,构建一个包含属性名称和类型的有序字典:

    fields = OrderedDict([('city','str(25)'),('population','int')])
    
    
  3. 接下来,通过连接一个 Python 列表推导式的输出构建一个字符串,该列表推导式遍历有序字典:

    path = '&'.join(['field={}:{}'.format(k,v) for k,v in fields.items()])
    
    
  4. 最后,使用此字符串定义图层:

    vectorLyr =  QgsVectorLayer('Point?crs=epsg:4326&' + path, 'Layer 1' , "memory")
    
    

向矢量图层添加点要素

此食谱执行了从 shapefile 实例化的矢量图层可能的最简单编辑。我们将向现有的点图层添加一个点。

准备工作

对于这个食谱,请从geospatialpython.googlecode.com/svn/NYC_MUSEUMS_GEO.zip下载压缩的 shapefile。

.shp.shx.dbf文件提取到/qgis_data/nyc目录。

如何操作...

我们将从 shapefile 加载矢量图层,创建一个新的点几何对象,创建一个新的要素,设置几何形状,并将其添加到图层的数据提供者。最后,我们将更新图层的范围,以确保图层的边界框包含新的点:

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. 首先,加载图层:

    vectorLyr =  QgsVectorLayer('/qgis_data/nyc/NYC_MUSEUMS_GEO.shp', 'Museums' , "ogr")
    
    
  4. 现在,将访问图层的数据提供者:

    vpr = vectorLyr.dataProvider()
    
    
  5. 接下来,使用QgsGeometry对象创建一个新的点:

    pnt = QgsGeometry.fromPoint(QgsPoint(-74.80,40.549))
    
    
  6. 现在,将创建一个新的QgsFeature对象来存放几何形状:

    f = QgsFeature()
    
    
  7. 接下来,使用我们的点设置要素的几何形状:

    f.setGeometry(pnt)
    
    
  8. 然后,将要素放入图层要素列表中:

    vpr.addFeatures([f])
    
    
  9. 最后,更新图层的范围以完成添加:

    vectorLyr.updateExtents()
    
    

它是如何工作的...

PyQGIS 将图层内的点抽象为四个级别。最低级别是QgsPoint对象,它包含的不仅仅是点的坐标。此对象被添加到一个抽象的QgsGeometry对象中。此对象成为QgsFeature对象的几何部分,该对象还具有存储和管理属性的能力。所有要素都由QgsDataProvider对象管理。数据提供者管理图层的地理空间方面,以将其与样式和其他与呈现相关的部分分开。QGIS 在 Python 中还有另一种编辑方法,称为编辑缓冲区。当你使用编辑缓冲区时,可以显示更改,但除非你提交它们,否则这些更改不是永久的。这种编辑方法最常见的使用场景是在 GUI 应用程序中,用户可能会选择通过取消编辑会话来回滚更改。"PyQGIS 开发者食谱"中有一个使用和编辑缓冲区的 Python 示例,可在docs.qgis.org/2.6/en/docs/pyqgis_developer_cookbook/vector.html找到。

向矢量图层添加线要素

在 QGIS 中向矢量图层添加一行与添加单个点相同,但在这里你只需向QgsGeometry对象添加更多点。

准备工作

对于这个配方,您需要下载一个包含两个线特征的压缩线形状文件,您可以从geospatialpython.googlecode.com/svn/paths.zip下载。

将 ZIP 文件解压到您/qgis_data目录中名为paths的目录中。

如何操作...

在这个配方中,我们将从形状文件中加载线图层,构建点列表,创建一个新的几何对象,并将点作为线添加。我们还将创建一个新的特征,设置几何形状,并将其添加到图层的数据提供者。最后,我们将更新图层的范围,以确保图层的边界框包含新的特征:

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. 首先,加载线图层并确保它是有效的:

    vectorLyr =  QgsVectorLayer('/qgis_data/paths/paths.shp', 'Paths' , "ogr")
    vectorLyr.isValid()
    
    
  4. 接下来,访问层的提供者:

    vpr = vectorLyr.dataProvider()
    
    
  5. 现在,构建我们新线的点列表:

    points = []
    points.append(QgsPoint(430841,5589485))
    points.append(QgsPoint(432438,5575114))
    points.append(QgsPoint(447252,5567663))
    
    
  6. 然后,从线创建一个几何对象:

    line = QgsGeometry.fromPolyline(points)
    
    
  7. 创建一个特征并将其几何设置为线:

    f = QgsFeature()
    f.setGeometry(line)
    
    
  8. 最后,将特征添加到图层数据提供者并更新范围:

    vpr.addFeatures([f])
    vectorLyr.updateExtents()
    
    

它是如何工作的...

与 QGIS 中的所有几何形状一样,我们使用构建点、几何、特征和数据提供者的四步流程来添加线。有趣的是,QgsGeometry对象接受 Python 列表作为点的集合,而不是像QgsPoint对象那样创建正式的对象。

向矢量层添加多边形特征

在这个配方中,我们将向图层添加一个多边形。多边形是最复杂的几何形状之一。然而,在 QGIS 中,API 与线非常相似。

准备工作

对于这个配方,我们将使用一个简单的多边形形状文件,您可以从geospatialpython.googlecode.com/files/polygon.zip下载为 ZIP 文件。

将此形状文件解压到您/qgis_data目录中名为polygon的文件夹中。

如何操作...

此配方将遵循标准的 PyQGIS 流程,加载图层、构建特征并将其添加到图层的数据提供者,如下所示:

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. 首先,加载图层并验证它:

    vectorLyr =  QgsVectorLayer('/qgis_data/polygon/polygon.shp', 'Polygon' , "ogr")
    vectorLyr.isValid()
    
    
  4. 接下来,访问层的提供者:

    vpr = vectorLyr.dataProvider()
    
    
  5. 现在,为多边形构建一个点列表:

    points = []
    points.append(QgsPoint(-123.26,49.06))
    points.append(QgsPoint(-127.19,43.07))
    points.append(QgsPoint(-120.70,35.21))
    points.append(QgsPoint(-115.89,40.02))
    points.append(QgsPoint(-113.04,48.47))
    points.append(QgsPoint(-123.26,49.06))
    
    
  6. 接下来,创建一个几何对象并将点作为多边形导入。我们将在另一个列表中嵌套我们的点列表,因为多边形可以有内环,这些内环将包含添加到该列表的附加点列表:

    poly = QgsGeometry.fromPolygon([points])
    
    
  7. 接下来,构建特征对象并添加点:

    f = QgsFeature()
    f.setGeometry(poly)
    
    
  8. 最后,将特征添加到图层的数据提供者并更新范围:

    vpr.addFeatures([f])
    
    

它是如何工作的...

添加多边形与添加线非常相似,有一个关键的区别是一个常见的陷阱。最后一个点必须与第一个点相同,以便闭合多边形。如果您不重复第一个点,您不会收到任何错误,但多边形将不会在 QGIS 中显示,这可能会很难调试。

向矢量层添加一组属性

每个 QGIS 特征有两个部分,几何形状和属性。在这个菜谱中,我们将为现有数据集的图层添加一个属性。

准备工作

我们将使用纽约市博物馆数据的点 shapefile,你可以从geospatialpython.googlecode.com/svn/NYC_MUSEUMS_GEO.zip下载 ZIP 文件。

将此 shapefile 提取到/qgis_data/nyc目录。

如何做...

一个特征必须有几何形状,但不需要属性。因此,我们将创建一个新特征,添加一些属性,然后将所有内容添加到图层中,如下所示:

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. 首先,加载图层并验证它:

    vectorLyr =  QgsVectorLayer('/qgis_data/nyc/NYC_MUSEUMS_GEO.shp', 'Museums' , "ogr")
    vectorLyr.isValid()
    
    
  4. 接下来,访问图层的数据提供者,以便我们可以获取字段列表:

    vpr = vectorLyr.dataProvider()
    
    
  5. 现在,创建一个点几何形状,在这个例子中是一个新的博物馆:

    pnt = QgsGeometry.fromPoint(QgsPoint(-74.13401,40.62148))
    
    
  6. 接下来,获取我们将需要创建新特征的图层的fields对象:

    fields = vpr.fields()
    
    
  7. 然后,创建一个新特征并初始化属性:

    f = QgsFeature(fields)
    
    
  8. 现在,设置我们新博物馆特征的几何形状:

    f.setGeometry(pnt)
    
    
  9. 现在,我们能够添加一个新属性。添加属性类似于更新 Python 字典,如下所示:

    f['NAME'] = 'Python Museum'
    
    
  10. 最后,我们将特征添加到图层并更新范围:

    vpr.addFeatures([f])
    vectorLyr.updateExtents()
    
    

它是如何工作的...

PyQGIS 属性定义为有序的array。引用字段的语法类似于 Python 字典的语法。我们使用图层的数据提供者对象来执行实际的编辑。当我们使用这种方法时,在图层对象级别不会触发任何信号。如果我们只是尝试在文件系统上编辑数据,那是可以的,但如果图层将被添加到地图画布以显示或用户交互,那么你应该使用QgsVectorLayer对象中的编辑缓冲区。这个编辑缓冲区允许你提交或回滚更改,并跟踪更改时的图层状态。

向矢量图层添加字段

这个菜谱演示了如何向图层添加新字段。每个字段代表数据集中新列的一个新属性。当你添加一个新属性时,所有特征都将该字段的索引设置为NULL

准备工作

我们将使用其他菜谱中使用的纽约市博物馆的 shapefile,你可以从geospatialpython.googlecode.com/svn/NYC_MUSEUMS_GEO.zip下载 ZIP 文件。

将此 shapefile 提取到/qgis_data/nyc

如何做...

图层的所有数据管理都通过图层的数据提供者来处理,字段也不例外。我们将加载图层,访问数据提供者,定义新字段,并最终完成更改,如下所示:

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. 首先,你必须导入Qt库的数据类型,PyQGIS 使用这些数据类型来指定图层字段的类型:

    from PyQt4.QtCore import QVariant
    
    
  4. 接下来,加载并验证图层:

    vectorLyr =  QgsVectorLayer('/qgis_data/nyc/NYC_MUSEUMS_GEO.shp', 'Museums' , "ogr")
    vectorLyr.isValid()
    
    
  5. 然后,访问图层数据提供者:

    vpr = vectorLyr.dataProvider()
    
    
  6. 现在,添加一个QgsField对象的 Python 列表,它定义了字段名称和类型。在这种情况下,我们将添加一个名为Admission的字段,类型为Double

    vpr.addAttributes([QgsField("Admission", QVariant.Double)])
    
    
  7. 最后,更新字段以完成更改:

    vectorLyr.updateFields()
    
    

它是如何工作的...

QGIS 中fieldsattributes使用的命名约定有些不一致,如果您使用过其他 GIS 软件包,可能会感到困惑。在 QGIS 中,一列是一个具有名称和类型的fieldattribute table为每个field列和每个feature行保存一个值。然而,在QgsVectorDataProvider对象中,您使用addAttributes()方法添加一个新的field列。在其他 GIS 软件中,您可能会看到fieldattribute的使用颠倒。

将 shapefile 属性表连接到 CSV 文件

将属性表与其他数据库表连接允许您使用空间数据集来引用没有几何形状的数据集,使用数据表之间的公共键。这种用例的一个非常常见的例子是将人口普查属性向量的数据集与更详细的人口普查属性数据集连接起来。在这里,我们将演示如何将美国人口普查跟踪文件与包含更深入信息的详细 CSV 文件连接起来。

准备工作

对于这个配方,您需要一个包含适当人口普查数据的tract shapefile 和一个 CSV 文件。您可以从geospatialpython.googlecode.com/svn/census.zip下载示例数据集。

将这些数据提取到名为/qgis_data/census的目录中。

如何操作...

连接操作相当复杂。我们将执行此操作,并将具有连接属性的图层保存为新的 shapefile。然后我们将加载新的图层,并将字段计数与原始图层进行比较,以确保连接发生。我们将使用术语目标图层连接图层目标图层将是 shapefile,而连接图层将是一个 CSV 文件,其中包含我们想要添加到 shapefile 的一些额外字段。为此,请执行以下步骤:

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. 首先,加载县的人口普查跟踪图层并验证它:

    vectorLyr =  QgsVectorLayer('/qgis_data/census/hancock_tracts.shp', 'Hancock' , "ogr")
    vectorLyr.isValid()
    
    
  4. 现在,将 CSV 文件作为图层加载并验证它:

    infoLyr = QgsVectorLayer('/qgis_data/census/ACS_12_5YR_S1901_with_ann.csv', 'Census' , "ogr")
    infoLyr.isValid()
    
    
  5. 完成此操作后,您必须将两个图层添加到地图注册表中,以便两个图层可以交互进行连接。但是,将可见性设置为False,以便图层不在地图上显示:

    QgsMapLayerRegistry.instance().addMapLayers([vectorLyr,infoLyr], False)
    
    
  6. 接下来,您必须创建一个特殊的连接对象:

    info = QgsVectorJoinInfo()
    
    
  7. 连接对象需要 CSV 文件的图层 ID:

    info.joinLayerId = infoLyr.id()
    
    
  8. 接下来,指定 CSV 文件中的关键字段,其值与 shapefile 中的值相对应:

    info.joinFieldName = "GEOid2"
    
    
  9. 然后,指定 shapefile 中的对应字段:

    info.targetFieldName = "GEOID"
    
    
  10. memoryCache属性设置为True以加快对连接数据的访问:

    info.memoryCache = True
    
    
  11. 现在将连接添加到图层:

    vectorLyr.addJoin(info)
    
    
  12. 接下来,将连接的 shapefile 写入磁盘上的新文件:

    QgsVectorFileWriter.writeAsVectorFormat(vectorLyr, "/qgis_data/census/joined.shp", "CP120", None, "ESRI Shapefile")
    
    
  13. 现在,将新的 shapefile 重新加载为图层以进行验证:

    joinedLyr =  QgsVectorLayer('/qgis_data/census/joined.shp', 'Joined' , "ogr")
    
    
  14. 验证原始图层中的字段数是12

    vectorLyr.dataProvider().fields().count()
    
    
  15. 最后,验证新图层从连接中具有142个字段数:

    joinedLyr.dataProvider().fields().count()
    
    

它是如何工作的...

这个菜谱触及了 PyQGIS API 的边缘,迫使你使用一些变通方法。大多数数据操作菜谱可以通过编程方式执行,而无需将数据写入磁盘或加载图层到地图上,但连接操作不同。因为QgsVectorJoinInfo对象需要 CSV 图层的图层 ID,我们必须将两个图层都添加到地图图层注册表中。幸运的是,如果我们只是尝试编写数据操作脚本,我们可以不使它们可见。连接操作被设计为查询数据集的临时操作。奇怪的是,PyQGIS 允许你创建连接,但你不能查询它。这种限制是如果你想要处理连接数据,你必须将其写入新的 shapefile 并重新加载的原因。幸运的是,PyQGIS 允许你这样做。

更多内容...

你可以在处理工具箱脚本中找到一个绕过 PyQGIS 限制的替代方法,该脚本在 Python 中手动匹配连接数据,在github.com/rldhont/Quantum-GIS/blob/master/python/plugins/processing/algs/qgis/JoinAttributes.py

移动矢量图层几何形状

有时,你需要更改特征的位置。你可以通过删除和重新添加特征来完成此操作,但 PyQGIS 提供了一个简单的方法来更改几何形状。

准备工作

你需要纽约市博物馆的 shapefile,你可以从geospatialpython.googlecode.com/svn/NYC_MUSEUMS_GEO.zip下载 ZIP 文件。

将此 shapefile 提取到/qgis_data/nyc.

如何操作...

我们将加载 shapefile 作为矢量图层,验证它,定义我们想要更改的特征 ID,创建新的几何形状,并在图层中更改特征。为此,执行以下步骤:

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. 首先,加载图层并验证它:

    vectorLyr =  QgsVectorLayer('/qgis_data/nyc/NYC_MUSEUMS_GEO.shp', 'Museums' , "ogr")
    vectorLyr.isValid()
    
    
  4. 接下来,定义我们想要更改的特征 ID:

    feat_id = 22
    
    
  5. 现在,创建新的点几何形状,它将成为新的位置:

    geom = QgsGeometry.fromPoint(QgsPoint(-74.20378,40.89642))
    
    
  6. 最后,更改几何形状并用我们的新几何形状替换它,指定特征 ID:

    vectorLyr.dataProvider().changeGeometryValues({feat_id : geom})
    
    

它是如何工作的...

changeGeometryValues()方法使得编辑变得轻而易举。如果我们不得不删除并重新添加特征,我们就必须经历读取属性、保留它们,然后使用新特征重新添加它们的麻烦。你必须当然知道你想要更改的特征 ID。你如何确定这个 ID 取决于你的应用程序。通常,你会查询属性以找到特定的值,或者你可以执行某种空间操作。

更改矢量图层特征的属性

在特征中更改属性的过程简单明了,并且得到了 PyQGIS API 的良好支持。在这个菜谱中,我们将更改单个属性,但您可以一次性更改特征的多个属性。

准备工作

您需要使用其他菜谱中使用的纽约市博物馆的 shapefile,您可以从geospatialpython.googlecode.com/svn/NYC_MUSEUMS_GEO.zip下载 ZIP 文件。

将此 shapefile 提取到/qgis_data/nyc

如何操作...

我们将加载 shapefile 作为矢量图层,验证它,定义我们想要更改的字段的特征 ID,获取我们将更改的字段名称的索引,定义新的属性值作为属性索引和值,并在图层中更改特征。为此,我们需要执行以下步骤:

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. 首先,加载图层并验证它:

    vectorLyr =  QgsVectorLayer('/qgis_data/nyc/NYC_MUSEUMS_GEO.shp', 'Museums' , "ogr")
    vectorLyr.isValid()
    
    
  4. 接下来,定义您想要更改的特征 ID:

    fid1 = 22
    fid2 = 23
    
    
  5. 然后,获取您想要更改的字段的索引,即电话号码和城市名称:

    tel = vectorLyr.fieldNameIndex("TEL")
    city = vectorLyr.fieldNameIndex("CITY")
    
    
  6. 现在,创建属性索引和新值的 Python 字典,在这个例子中是一个虚构的电话号码:

    attr1 = {tel:"(555) 555-1111", city:"NYC"}
    attr2 = {tel:"(555) 555-2222", city:"NYC"}
    
    
  7. 最后,使用图层的数据提供者更新字段:

    vectorLyr.dataProvider().changeAttributeValues({fid1:attr1, fid2:attr2})
    
    

它是如何工作的...

修改属性与在特征内修改几何形状非常相似。在这个例子中,我们明确命名了特征 ID,但在实际程序中,您会收集这些 ID 作为其他过程输出的部分,例如空间选择。这种类型空间选择的例子可以在第二章的通过几何形状过滤图层菜谱中找到,在查询矢量数据部分。

删除矢量图层特征

在这个菜谱中,我们将完全从图层中删除一个特征,包括几何形状和属性。

准备工作

您需要使用其他菜谱中使用的纽约市博物馆的 shapefile,您可以从geospatialpython.googlecode.com/svn/NYC_MUSEUMS_GEO.zip下载 ZIP 文件。

将此 shapefile 提取到/qgis_data/nyc

如何操作...

我们需要做的只是加载图层,然后通过 ID 删除所需的特征,使用图层的数据提供者:

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. 首先,加载并验证图层:

    vectorLyr =  QgsVectorLayer('/qgis_data/nyc/NYC_MUSEUMS_GEO.shp', 'Museums' , "ogr")
    vectorLyr.isValid()
    
    
  4. 接下来,指定一个包含特征 ID 的 Python 列表。在这种情况下,我们有两个:

    vectorLyr.dataProvider().deleteFeatures([ 22, 95 ])
    
    

它是如何工作的...

此操作无法更简单或设计得更好。我们可以以多种方式编程地将特征 ID 填充到 Python 列表中。例如,我们可以使用这个菜谱中的第二章的通过属性过滤图层。然后,我们只需将此列表传递给图层的数据提供者,任务就完成了。

删除矢量图层属性

在这个菜谱中,我们将删除一个矢量层的整个属性以及所有特征字段。

准备工作

你将需要纽约市博物馆在其它菜谱中使用的形状文件,你可以将其作为 ZIP 文件从 geospatialpython.googlecode.com/svn/NYC_MUSEUMS_GEO.zip 下载。

将此形状文件解压到 /qgis_data/nyc

如何操作...

这个操作很简单。我们将加载和验证图层,使用图层的数据提供者通过索引删除属性,最后,我们将更新所有字段以删除孤立值。为此,我们需要执行以下步骤:

  1. 启动 QGIS。

  2. 插件 菜单中选择 Python 控制台

  3. 首先,加载和验证图层:

    vectorLyr =  QgsVectorLayer('/qgis_data/nyc/NYC_MUSEUMS_GEO.shp', 'Museums' , "ogr")
    vectorLyr.isValid()
    
    
  4. 然后,删除第一个属性:

    vectorLyr.dataProvider().deleteAttributes([1]) 
    
    
  5. 最后,更新字段:

    vectorLyr.updateFields()
    
    

工作原理...

由于我们正在更改图层数据的实际结构,我们必须调用图层的 updateFields() 方法来删除不再具有属性的字段值。

重新投影矢量层

我们将使用 QGIS 的处理工具箱来将一个图层重新投影到不同的坐标系。

准备工作

对于这个菜谱,我们需要密西西比州城市在密西西比州横墨卡托投影(EPSG 3814)中的形状文件,你可以将其作为 ZIP 文件从 geospatialpython.googlecode.com/files/MSCities_MSTM.zip 下载。

将压缩的形状文件解压到名为 /qgis_data/ms 的目录中。

如何操作...

要重新投影图层,我们将简单地调用 qgis:reprojectlayer 处理算法,指定输入形状文件、新投影和输出文件名。为此,请执行以下步骤:

  1. 启动 QGIS。

  2. 插件 菜单中选择 Python 控制台

  3. 首先,你需要导入处理模块:

    import processing
    
    
  4. 接下来,运行重新投影算法,如下所示:

    processing.runalg("qgis:reprojectlayer", "/qgis_data/ms/MSCities_MSTM.shp", "epsg:4326", "/qgis_data/ms/MSCities_MSTM_4326.shp")
    
    

工作原理...

源数据最初在 EPSG 3814 中,但我们希望将其投影到 WGS 84 地理坐标系,这通常用于处理全球数据集,并且通常是 GPS 设备的默认坐标参考系统。目标 EPSG 代码是 4326。处理地图投影可能相当复杂。这个 QGIS 教程有一些更多示例,并解释了更多关于地图投影的信息,请参阅 manual.linfiniti.com/en/vector_analysis/reproject_transform.html

将形状文件转换为 KML

在这个菜谱中,我们将把一个图层转换为 KML。KML 是 开放地理空间联盟 (OGC) 标准,并且由 QGIS 所使用的底层 OGR 库支持。

准备工作

对于这个菜谱,下载以下压缩形状文件并将其解压到名为 /qgis_data/hancock 的目录中:

geospatialpython.googlecode.com/files/hancock.zip

如何操作...

要将 shapefile 转换为 KML XML 格式,我们将加载图层,然后使用QgsVectorFileWriter对象将其保存为 KML:

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. 首先加载图层并验证它:

    vectorLyr =  QgsVectorLayer('/qgis_data/hancock/hancock.shp', 'Hancock' , "ogr")
    vectorLyr.isValid()
    
    
  4. 然后,建立目标坐标参考系统。KML 始终应在 EPS:4326:

    dest_crs = QgsCoordinateReferenceSystem(4326)
    
    
  5. 接下来,使用文件写入器将其保存为 KML 文件,指定文件类型为 KML:

    QgsVectorFileWriter.writeAsVectorFormat(vectorLyr, "/qgis_data/hancock/hancock.kml", "utf-8", dest_crs, "KML")
    
    

它是如何工作的...

你将在你的 shapefile 旁边的目录中结束,得到一个 KML 文件。KML 支持样式信息。QGIS 使用一些默认的样式信息,你可以通过手动使用文本编辑器或使用 Python 的 ElementTree 等 XML 库编程方式来更改它们。KML 是你可以使用此方法导出的许多标准矢量格式之一。

合并 shapefile

将具有匹配投影和属性结构的 shapefile 合并是一个非常常见的操作。在 QGIS 中,合并矢量数据集的最佳方式是使用 Windows 和 OSX 上包含的另一个 GIS 系统SAGA。在其他平台上,你必须单独安装 SAGA 并在处理工具箱配置中激活它。在 PyQGIS 中,你可以通过处理工具箱访问 SAGA 函数。SAGA是另一个与 QGIS 类似的开源 GIS。然而,这两个包都有优点和缺点。通过处理工具箱使用 SAGA,你可以拥有两个系统的最佳之处。

准备工作

在这个菜谱中,我们将合并相邻区域的几个建筑足迹 shapefile 到一个单独的 shapefile 中。你可以从geospatialpython.googlecode.com/files/tiled_footprints.zip下载示例数据集。

将压缩的 shapefile 解压到名为/qgis_data/tiled_footprints的目录中。

如何做...

我们将在数据目录中定位所有.shp文件,并将它们交给saga:mergeshapeslayers对象以进行合并。

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. 导入 Python 的glob模块以进行通配符文件匹配:

    import glob
    
    
  4. 接下来,导入合并算法的处理模块:

    import processing
    
    
  5. 现在,指定我们的数据目录的路径:

    pth = "/qgis_data/tiled_footprints/"
    
    
  6. 定位所有.shp文件:

    files = glob.glob(pth + "*.shp")
    
    
  7. 然后,指定合并的 shapefile 的输出名称:

    out = pth + "merged.shp"
    
    
  8. 最后,运行将合并的 shapefile 加载到地图上的算法:

    processing.runandload("saga:mergeshapeslayers",files.pop(0),";".join(files),out)
    
    

它是如何工作的...

该算法接受一个基础文件,然后是一个分号分隔的附加文件列表,最后接受输出文件名。glob模块创建一个文件列表。为了获取基础文件,我们使用列表的pop()方法获取第一个文件名。然后,我们使用 Python 字符串的join()方法来创建所需的分隔列表。

还有更多...

QGIS 通过processing模块提供自己的合并方法,称为qgis:mergevectorlayers,但它有限,因为它只能合并两个文件。SAGA 方法允许合并任意数量的文件。

分割 shapefile

有时,您需要分割 shapefile,以便将较大的数据集分解成更易于管理的尺寸,或者隔离特定的感兴趣区域。Processing 工具箱中有一个脚本可以按属性分割 shapefile。尽管它作为编写处理脚本的示例提供,但它非常有用。

准备工作

我们将按县分割人口普查区 shapefile。您可以从geospatialpython.googlecode.com/files/GIS_CensusTract.zip下载示例压缩 shapefile。

  1. 将压缩的 shapefile 提取到名为/qgis_data/census的目录中。

  2. 您还需要以下脚本用于 Processing 工具箱:

    https://geospatialpython.googlecode.com/svn/Split_vector_layer_by_attribute.py
    
    
  3. 接下来,使用以下步骤将脚本添加到 Processing 工具箱中:

  4. 将脚本下载到您的/qgis_data/目录中。

  5. 在 QGIS Processing 工具箱中,打开脚本树菜单,然后转到工具子菜单。

  6. 然后,双击从文件添加脚本命令。

  7. 文件对话框中,导航到脚本。选择脚本并点击打开按钮。

现在舞台已经搭建好。执行下一节中的步骤以分割 shapefile。

如何操作...

此菜谱与运行算法并指定文件名和数据属性一样简单。执行以下步骤:

  1. 启动 QGIS。

  2. 插件菜单,选择Python 控制台

  3. 导入processing模块:

    import processing
    
    
  4. 将数据目录定义为变量以缩短处理命令:

    pth = "/qgis_data/census/"
    
    
  5. 最后,运行算法:

    processing.runalg("script:splitvectorlayerbyattribute",pth + "GIS_CensusTract_poly.shp","COUNTY_8",pth + "split")
    
    

它是如何工作的...

算法将按顺序将分割的文件存放在数据目录中。

泛化矢量层

泛化几何形状,也称为简化,通过从矢量层中删除点来减少存储在磁盘上的数据所需的空间、在网络中移动它所需的带宽以及使用它进行分析或显示在 QGIS 中所需的处理能力。在许多情况下,层的几何形状包含冗余点以及可以删除而不改变层空间属性的直线,除了拓扑约束之外。

准备工作

对于此菜谱,我们将使用密西西比州的边界文件,您可以从geospatialpython.googlecode.com/files/Mississippi.zip下载。

将压缩的 shapefile 提取到名为/qgis_data/ms的目录中。

如何操作...

在 QGIS 中,泛化是固有的,但我们将通过 Processing 工具箱使用qgis:simplifygeometries算法在 PyQGIS 中访问它,如下所示:

  1. 启动 QGIS。

  2. 插件菜单,选择Python 控制台

  3. 导入processing模块:

    import processing
    
    
  4. 现在,运行processing算法,指定算法名称、输入数据、容差值、点之间的间距——这定义了在删除一个点之前两个点在地图单位中的接近程度——以及输出数据集的名称:

    processing.runandload("qgis:simplifygeometries","/qgis_data/ms/mississippi.shp",0.3,"/qgis_data/ms/generalize.shp")
    
    

它是如何工作的...

simplifygeometries命令的简单性使得操作看起来很简单。然而,简化本身相当复杂。相同的设置很少能在多个数据集中产生理想的结果。

在以下可视化中,可以看到这个菜谱中的 shapefile 最初非常复杂,有数百个点:

如何工作...

简化版本只有10个点,如下图所示:

如何工作...

溶解矢量形状

溶解形状可以有两种不同的形式。您可以通过整个数据集的外部边界合并一组相邻的形状,或者您也可以通过具有相同属性值的相邻形状进行分组。

准备工作

geospatialpython.googlecode.com/files/GIS_CensusTract.zip下载 GIS 人口普查区 shapefile,其中包含几个县的人口普查区。

将其提取到/qgis_data目录下的一个名为census的目录中。

如何操作...

我们将使用处理工具箱来完成这个菜谱,并特别使用一个本地 QGIS 算法dissolve,如下所示:

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. 导入processing模块:

    import processing
    
    
  4. 接下来,运行dissolve算法,指定输入数据——False表示我们不想将所有形状溶解为一个,而是使用属性——我们想要使用的属性,以及输出文件名:

    processing.runandload("qgis:dissolve","/qgis_data/census/GIS_CensusTract_poly.shp",False,"COUNTY_8","/qgis_data/census/dissovle.shp")
    
    

如何工作...

通过仅将语句中的布尔值更改为True,我们可以将所有相邻的形状溶解为一个。还重要的是要注意,QGIS 将每个组中遇到的第一个形状的字段分配给最终形状。在大多数情况下,这将使属性几乎无用。这项操作主要是空间任务。

你可以看到,在原始层中,每个县边界都有若干个人口普查区,如下图所示:

如何工作...

一旦形状被溶解,你将只剩下县边界,如下图所示:

如何工作...

对矢量形状执行并操作

并联将两个重叠的形状合并为一个。这个任务可以通过处理工具箱轻松完成。在这个菜谱中,我们将合并覆盖建筑的轮廓与主建筑的足迹。

准备工作

您可以从geospatialpython.googlecode.com/svn/union.zip下载建筑文件,并将其提取到名为/qgis_data/union的目录中。

如何操作...

我们需要做的只是运行qgis:union算法,如下所示:

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. 导入processing模块:

    import processing
    
    
  4. 现在,通过指定两个输入形状和一个输出文件来运行算法:

    processing.runandload("qgis:union","/qgis_data/union/building.shp","/qgis_data/union/walkway.shp","/qgis_data/union/union.shp")
    
    

如何工作...

如您从命令的结构中可以看出,此工具一次只能合并两个形状。它找到两个形状相交的地方,然后移除重叠部分,在交点处将它们连接起来。

在原始数据中,形状文件最初是两个不同的形状,如图所示:

如何工作...

一旦合并完成,形状现在是一个形状文件,重叠部分是一个单独的特征,如图所示:

如何工作...

栅格化矢量层

有时,栅格数据集是显示复杂矢量数据(在地图中仅作为背景)的最有效方式。在这些情况下,你可以栅格化矢量层将其转换为图像。

准备工作

我们将演示如何使用以下等高线形状文件栅格化矢量层,您可以从geospatialpython.googlecode.com/svn/contour.zip下载该文件。

将其提取到您的/qgis_data/rasters目录中。

如何操作...

我们将运行gdalogr:rasterize算法将此矢量数据转换为栅格,如下所示:

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. 导入processing模块:

    import processing
    
    
  4. 运行算法,指定输入数据、需要绘制栅格值的属性、0以指定输出像素维度而不是地图维度、宽度和高度,以及最终的输出栅格名称:

    processing.runalg("gdalogr:rasterize","/qgis_data/rasters/contour.shp","ELEV",0,1000,1000,"/qgis_data/rasters/contour.tif")
    
    

它是如何工作的...

如果您想以地图单位指定输出维度,请使用1代替0。请注意,一旦您将其转换为栅格,该层的符号就会变得固定。栅格也不再是动态可缩放的。

以下图像显示了高程等高线形状文件的栅格化输出:

如何工作...

第四章:使用栅格数据

在本章中,我们将介绍以下菜谱:

  • 加载栅格图层

  • 获取栅格图层的单元格大小

  • 获取栅格的宽度和高度

  • 计算栅格波段数量

  • 交换栅格波段

  • 查询指定点的栅格值

  • 重新投影栅格

  • 创建高程晕渲

  • 从高程数据创建矢量等高线

  • 使用规则网格对栅格数据集进行采样

  • 使用数字高程模型将高程数据添加到线

  • 创建栅格的共同范围

  • 重采样栅格分辨率

  • 计算栅格中的唯一值

  • 栅格镶嵌

  • 将 TIFF 图像转换为 JPEG 图像

  • 为栅格创建金字塔

  • 将像素位置转换为地图坐标

  • 将地图坐标转换为像素位置

  • 为栅格创建 KML 图像叠加

  • 对栅格进行分类

  • 将栅格转换为矢量

  • 使用地面控制点对栅格进行地理配准

  • 使用形状文件裁剪栅格

简介

本章向您展示如何使用 QGIS 和 Python 将栅格数据引入 GIS,并创建派生栅格产品。QGIS 在处理栅格数据方面与矢量数据一样熟练,通过整合领先的开源库和算法,包括 GDAL、SAGA 和 Orfeo Toolbox。QGIS 为大量遥感工具提供了一个一致的接口。我们将通过处理工具箱在视觉上处理栅格数据和使用 QGIS 作为处理引擎之间来回切换,以完全自动化遥感工作流程。

栅格数据由行和列的单元格或像素组成,每个单元格代表一个单一值。将栅格数据视为图像是最简单的方法,这也是它们通常由软件表示的方式。然而,栅格数据集不一定以图像的形式存储。它们也可以是 ASCII 文本文件或数据库中的二进制大对象BLOBs)。

地理空间栅格数据与常规数字图像之间的另一个区别是它们的分辨率。如果以全尺寸打印,数字图像以每英寸点数表示分辨率。分辨率也可以表示为图像中的总像素数,定义为百万像素。然而,地理空间栅格数据使用每个单元格代表的地面距离。例如,具有两英尺分辨率的栅格数据集意味着单个单元格代表地面上的两英尺。这也意味着只有大于两英尺的物体可以在数据集中通过视觉识别。

栅格数据集可能包含多个波段,这意味着可以在同一区域同时收集不同波长的光。通常,这个范围从 3 到 7 个波段,但在高光谱系统中,波段可以宽达数百个。这些波段可以单独查看,也可以作为图像的 RGB 波段进行交换。它们还可以通过数学方法重新组合成派生单波段图像,然后使用一定数量的类别重新着色,这些类别在数据集中表示相似值。

加载栅格图层

QGSRasterLayer API 为栅格数据提供了一个方便的高级接口。要使用此接口,我们必须将图层加载到 QGIS 中。API 允许你在不将其添加到地图的情况下处理图层。因此,我们将先加载图层,然后再将其添加到地图中。

准备工作

与本书中的其他食谱一样,你需要在我们的根目录或用户目录中创建一个名为qgis_data的目录,这提供了一个没有空格的短路径名。这种设置将有助于防止因系统中的路径相关问题而导致的任何令人沮丧的错误。在本食谱和其他食谱中,我们将使用密西西比湾海岸的 Landsat 卫星图像,你可以从geospatialpython.googlecode.com/files/SatImage.zip下载。

解压SatImage.tifSatImage.tfw文件,并将它们放置在qgis_data目录下名为rasters的目录中。

如何操作...

现在,我们将逐步介绍如何加载栅格图层,并将其逐步添加到地图中。

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. 然后,在Python 控制台中,通过指定源文件和图层名称来创建图层:

    rasterLyr = QgsRasterLayer("/qgis_data/rasters/SatImage.tif", "Gulf Coast")
    
    
  4. 接下来,确保图层已按预期创建。以下命令应返回True

    rasterLyr.isValid()
    
    
  5. 最后,将图层添加到图层注册表中:

    QgsMapLayerRegistry.instance().addMapLayers([rasterLyr])
    
    
  6. 确认你的 QGIS 地图看起来与以下图像相似:如何操作...

    当加载栅格图层时,QGIS 会自动缩放到图层的范围,如下所示,这是一个密西西比湾海岸的 Landsat 卫星图像示例。

它是如何工作的...

QgsRasterLayer对象需要文件的路径和 QGIS 中图层的名称。底层 GDAL 库确定加载图层的适当方法。这种方法与QgsVectorLayer()方法不同,后者需要你指定数据提供者。栅格图层也有数据提供者,但与矢量图层不同,所有栅格图层都通过 GDAL 进行管理。QGIS 的其中一个最佳特性是它将最优秀的开源地理空间工具结合到一个软件包中。GDAL 可以作为库使用,正如我们在这里从 Python 中使用它一样,也可以作为命令行工具使用。

一旦我们创建了 QgsRasterLayer 对象,我们将使用 rasterLayer.isValid() 方法进行快速检查,以查看文件是否正确加载。如果图层有效,此方法将返回 True。我们不会在每一道菜谱中都使用此方法;然而,这是一个最佳实践,尤其是在构建接受用户输入的动态应用程序时。由于 PyQGIS API 大部分基于 C 库构建,许多方法在操作失败时不会抛出异常。你必须使用专用方法来验证输出。

最后,我们将图层添加到地图图层注册表中,使其在地图和图例中可用。注册表通过分离、加载和可视化图层来跟踪所有加载的图层。QGIS 允许你在幕后工作,以便在将最终产品添加到地图之前对图层执行无限的中继过程。

获取栅格图层的单元格大小

地理空间栅格的第一个关键元素是像素宽度和高度。第二个关键元素是每个像素的地面距离,也称为像素大小。一旦你知道单元格大小和图像上的某个坐标(通常是左上角),你就可以开始使用遥感工具处理图像。在本菜谱中,我们将查询栅格的单元格大小。

准备工作

再次,我们将使用可在 geospatialpython.googlecode.com/files/SatImage.zip 获取的 SatImage 栅格。

将此栅格放入你的 /qgis_data/rasters 目录。

如何操作...

我们将加载栅格作为图层,然后使用 QgsRasterLayer API 获取 xy 轴的单元格大小。为此,我们需要执行以下步骤:

  1. 启动 QGIS。

  2. 插件 菜单中选择 Python 控制台

  3. 加载图层并验证它:

    rasterLyr = QgsRasterLayer("/qgis_data/rasters/satimage.tif", "Sat Image")
    rasterLyr.isValid()
    
    
  4. 现在,调用 x 距离方法,它应返回 0.00029932313140079714:

    rasterLyr.rasterUnitsPerPixelX()
    
    
  5. 然后,调用 y 距离,其值应为 0.00029932313140079714

    rasterLyr.rasterUnitsPerPixelY()
    
    

工作原理...

GDAL 提供此信息,并将其传递给图层 API。请注意,虽然在此情况下 xy 值本质上相同,但 xy 距离完全可能是不同的——特别是如果图像以某种方式投影或扭曲。

获取栅格的宽度和高度

所有栅格图层都有像素宽度和高度。因为遥感数据可以被视为图像、数组或矩阵,所以你经常会看到使用不同的术语,包括列和行或像素和线。这些不同的术语在 QGIS API 中多次出现。

准备工作

我们将再次使用 SatImage 栅格,它可在 geospatialpython.googlecode.com/files/SatImage.zip 获取。

将此栅格放入你的 /qgis_data/rasters 目录。

如何操作...

  1. 启动 QGIS。

  2. 插件 菜单中选择 Python 控制台

  3. 在 Python 控制台中,加载图层并确保它是有效的:

    rasterLyr = QgsRasterLayer("/qgis_data/rasters/satimage.tif", "satimage")
    rasterLyr.isValid()
    
    

    解压 SatImage 后检查其名称。

  4. 获取图层的宽度,应该是2592

    rasterLyr.width()
    
    
  5. 现在,获取栅格的高度,这将返回2693

    rasterLyr.height()
    
    

它是如何工作的...

栅格的宽度和高度对于许多算法至关重要,包括计算栅格占用的地图单位。

计算栅格波段

一个栅格可能有一个或多个波段。波段代表栅格中的信息层。每个波段都有相同数量的列和行。

准备中

我们将再次使用位于geospatialpython.googlecode.com/files/SatImage.zip的 SatImage 栅格。

将此栅格放在你的/qgis_data/rasters目录中。

如何操作...

我们将加载图层并将波段数量打印到控制台。为此,我们需要执行以下步骤:

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. 在 Python 控制台中,加载图层并确保它是有效的:

    rasterLyr = QgsRasterLayer("/qgis_data/rasters/satimage.tif", "Sat Image")
    rasterLyr.isValid()
    
    
  4. 现在,获取波段数量,在这种情况下应该是3

    rasterLyr.bandCount()
    
    

它是如何工作的...

重要的是要注意,栅格波段不是基于零的索引。当你想访问第一个波段时,你将其引用为1而不是0。在编程环境中,大多数序列从0开始。

交换栅格波段

计算机显示器以红、绿、蓝光(RGB)的可视光谱渲染图像。然而,栅格图像可能包含可视光谱之外的波段。这类栅格图像的视觉效果较差,因此你通常会想要重新组合波段以改变 RGB 值。

准备中

对于这个配方,我们将使用假彩色图像,你可以从geospatialpython.googlecode.com/files/FalseColor.zip下载。

解压这个tif文件并将其放在你的/qgis_data/rasters目录中。

如何操作...

我们将加载这个栅格并交换第一和第二波段的位置。然后,我们将它添加到地图上。为此,我们需要执行以下步骤:

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. Python 控制台中,加载图层并确保它是有效的:

    rasterLyr = QgsRasterLayer("/qgis_data/rasters/FalseColor.tif", "Band Swap")
    rasterLyr.isValid()
    
    
  4. 现在,我们必须访问图层渲染器以操纵显示的波段顺序。请注意,此更改不会影响底层数据:

    ren = rasterLyr.renderer()
    
    
  5. 接下来,我们将设置红色波段为波段2

    ren.setRedBand(2)
    
    
  6. 现在,我们将设置绿色波段为波段1

    ren.setGreenBand(1)
    
    
  7. 最后,将修改后的栅格图层添加到地图上:

    QgsMapLayerRegistry.instance().addMapLayers([rasterLyr])
    
    

它是如何工作的...

将源图像也加载到 QGIS 中以比较结果。在假彩色图像中,植被呈红色,而在波段交换图像中,树木呈现更自然的绿色,水呈蓝色。QGIS 使用 RGB 顺序允许你继续通过编号引用波段。尽管波段 2 首先显示,但它仍然被引用为波段 2。此外,请注意,波段顺序是由图层实例化的 QgsMultiBandColorRenderer 对象控制的,而不是图层本身。所需的渲染器类型在加载时由数据类型和波段数量确定。

更多...

QgsMultiBandColorRenderer() 方法有其他方法可以控制每个波段的对比度增强,例如 setRedContrastEnhancement()。你可以在 QGIS API 文档中了解有关不同类型数据的栅格渲染器的更多信息,文档地址为 qgis.org/api/classQgsRasterRenderer.html

在指定点查询栅格的值

一个常见的遥感操作是在指定坐标处获取栅格数据值。在这个食谱中,我们将查询图像中心的值。碰巧的是,栅格图层会为你计算其范围的中心坐标。

准备工作

与本章中的许多食谱一样,我们再次使用 SatImage 栅格,该栅格可在 geospatialpython.googlecode.com/files/SatImage.zip 获取。

将此栅格放在你的 /qgis_data/rasters 目录中。

如何做...

我们将加载图层,获取中心坐标,然后查询值。为此,我们需要执行以下步骤:

  1. 首先,加载并验证图层:

    rasterLyr = QgsRasterLayer("/qgis_data/rasters/satimage.tif", "Sat Image")
    rasterLyr.isValid()
    
    
  2. 接下来,从图层的 QgsRectangle 范围 对象获取图层的中心点,它将返回一个包含 xy 值的元组:

    c = rasterLyr.extent().center()
    
    
  3. 现在,使用图层的数据提供者,我们可以使用 identify() 方法查询该点的数据值:

    qry = rasterLyr.dataProvider().identify(c, QgsRaster.IdentifyFormatValue)
    
    
  4. 因为查询错误不会抛出异常,我们必须验证查询:

    qry.isValid()
    
    
  5. 最后,我们可以查看查询结果,它将返回一个 Python 字典,其中每个波段编号作为键,指向该波段中的数据值:

    qry.results()
    
    
  6. 验证你是否得到以下输出:

    {1: 17.0, 2: 66.0, 3: 56.0}
    
    

它是如何工作的...

与其他食谱相比,这个食谱很短,但我们已经触及了 PyQGIS 栅格 API 的几个部分。首先从一个栅格图层开始,获取范围;然后我们计算中心并创建一个位于中心坐标的点,最后在该点查询栅格。如果我们使用底层 GDAL 库的 Python API 来执行这个看似简单的操作,该库负责执行工作,这个例子将大约长七倍。

重投影栅格

所有地理空间分析的核心要求之一是能够更改数据的地图投影,以便允许不同的图层在同一地图上打开。重投影可能具有挑战性,但 QGIS 使其变得轻而易举。从本食谱开始,我们将开始使用强大的 QGIS 处理工具箱。处理工具箱将 600 多个算法包装成一个高度一致的 API,可供 Python 使用,也可以作为交互式工具。这个工具箱最初是一个名为 SEXTANTE 的第三方插件,但现在它是与 QGIS 一起分发的标准插件。

准备工作

正如本章中许多食谱一样,我们将使用位于geospatialpython.googlecode.com/files/SatImage.zip的 SatImage 栅格。

将此栅格文件放置在您的/qgis_data/rasters目录中。

如何操作...

在本食谱中,我们将使用processing模块的gdal warp算法将我们的图像从EPSG 4326重新投影到3722。为此,我们需要执行以下步骤:

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. 代码的第一行用于导入processing模块:

    import processing
    
    
  4. 接下来,我们加载我们的栅格图层并验证它:

    rasterLyr = QgsRasterLayer("/qgis_data/rasters/SatImage.tif", "Reproject")
    rasterLyr.isValid()
    
    
  5. 最后,我们通过插入正确的参数来运行gdal warp算法,包括图层引用、当前投影、期望投影、None用于更改分辨率、0表示最近邻重采样、None用于其他参数、0 –Byte输出栅格数据类型(1 for int16),以及重投影图像的输出名称:

    processing.runalg("gdalogr:warpreproject", rasterLyr, "EPSG:4326", "EPSG:3722", None, 0, None, "/0, qgis_data/rasters/warped.tif")
    
    
  6. 验证输出图像warped.tif是否已在文件系统中正确创建。

工作原理...

处理工具箱基本上是对命令行工具的包装。然而,与它访问的工具不同,工具箱提供了一个一致且大部分可预测的 API。熟悉 Esri 的 ArcGIS ArcToolbox 的用户会发现这种方法很熟悉。除了一致性之外,工具箱还增加了参数验证和日志记录,使这些工具更加用户友好。重要的是要记住,您必须显式导入processing模块。PyQGIS 会自动加载 QGIS API,但此模块尚未包含。记住,它直到最近还是第三方插件。

更多内容...

runalg() 方法,即运行算法,是运行处理命令最常见的方式。尽管如此,您还可以使用其他处理方法。如果您想直接将命令的输出加载到 QGIS 中,可以将 runalg() 替换为 runandload() 方法。该方法的所有参数保持不变。您还可以通过运行 processing.alglist() 获取带有描述的处理算法列表。对于任何给定的算法,您可以通过运行 alghelp() 命令来查看它所需的输入类型,例如 processing.alghelp("gdalogr:warpproject")。您还可以根据算法的组合编写自己的处理脚本,并将它们添加到处理工具箱中。此外,还有一个用于将处理命令链接在一起的视觉模型器。

创建高程阴影

高程阴影,或称为阴影地形,是一种可视化高程数据的技术,以便将其作为地图以照片逼真的方式展示。这种功能是 GDAL 的一部分,在 QGIS 中有两种不同的方式提供。它位于 栅格 菜单下的 地形分析 菜单中的工具,同时也是处理工具箱中的一个算法。

准备工作

您需要从 geospatialpython.googlecode.com/files/dem.zip 下载 DEM。

解压缩名为 dem.asc 的文件,并将其放置在您的 /qgis_data/rasters 目录中。

如何操作...

在本食谱中,我们将加载 DEM 图层并对其运行 高程阴影 处理算法。为此,我们需要执行以下步骤:

  1. 启动 QGIS。

  2. 插件 菜单中选择 Python 控制台

  3. 导入 processing 模块:

    import processing
    
    
  4. 加载和验证图层:

    rasterLyr = QgsRasterLayer("/qgis_data/rasters/dem.asc", "Hillshade")
    rasterLyr.isValid()
    
    
  5. 运行 高程阴影 算法,提供算法名称、图层引用、波段号、计算边缘选项、zevenbergen 选项以获得更平滑的地形、z-因子高程夸张数值、垂直与水平单位的比例、方位角(光源的角度)、高度(光源的高度)以及输出图像的名称:

    processing.runandload("gdalogr:hillshade", rasterLyr, 1, False, False, 1.0, 1.0, 315.0, 45.0, "/qgis_data/rasters/hillshade.tif")
    
    
  6. 在 QGIS 中验证输出图像 hillshade.tif 是否与以下图像相似。它应通过 processing.runandload() 方法自动加载到 QGIS 中:如何操作...

它是如何工作的...

高程阴影 算法通过在高度数据集上模拟光源来使其更具视觉吸引力。通常,您只需要更改算法中的 z 因子、方位角和高度以获得不同的效果。然而,如果生成的图像看起来不正确,您可能需要更改比例。根据 GDAL 文档,如果您的 DEM 以度为单位,则应设置比例 111120,如果以米为单位,则应设置比例 370400。此数据集覆盖的区域较小,因此比例 1 就足够了。有关这些值的更多信息,请参阅 gdaldem 文档。

从海拔数据创建矢量等高线

等高线通过在数据集中以相同的海拔追踪线条,在设定的时间间隔内形成环,从而有效地可视化地形数据。类似于 QGIS 中的阴影能力,等高线工具由 GDAL 提供,既作为提取类别栅格菜单中的菜单选项,也作为处理工具箱算法。

准备工作

此配方使用来自geospatialpython.googlecode.com/files/dem.zip的 DEM,该 DEM 也用于其他配方。

解压名为dem.asc的文件,并将其放置在您的/qgis_data/rasters目录中。

如何操作...

在此配方中,我们将加载并验证 DEM 图层,将其添加到地图中,然后生成并加载等高线矢量图层。为此,我们需要执行以下步骤:

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. 导入processing模块。

    import processing
    
    
  4. 加载并验证 DEM:

    rasterLyr = QgsRasterLayer("/qgis_data/rasters/dem.asc", "DEM")
    rasterLyr.isValid()
    
    
  5. 使用mapLayerRegistry方法将 DEM 添加到地图中:

    QgsMapLayerRegistry.instance().addMapLayers([rasterLyr])
    
    
  6. 运行等高线算法,并在 DEM 图层上绘制结果,指定算法名称、图层引用、等高线之间的地图单位间隔、将包含海拔值的矢量数据属性字段名称、任何额外参数和输出文件名:

    processing.runandload("gdalogr:contour", rasterLyr, 50.0, "Elv", None, "/qgis_data/rasters/contours.shp")
    
    
  7. 确认 QGIS 中的输出类似于以下截图:如何操作...

    此配方将生成的海拔等高线叠加到 DEM 上,作为将海拔数据转换为矢量数据集的方法。

它是如何工作的...

等高线算法创建一个矢量数据集,即 shapefile。图层属性表包含每条线的海拔值。根据海拔数据集的分辨率,您可能需要更改等高线间隔,以防止等高线在所需的地图分辨率下过于密集或过于稀疏。通常,像这样的自动生成的等高线是一个起点,您必须手动编辑结果以使其更具视觉吸引力。您可能想要平滑线条或删除不必要的微小环。

使用规则网格对栅格数据集进行采样

有时,您需要以规则间隔对栅格数据集进行采样,以便提供汇总统计信息或对栅格数据进行质量保证。实现这种规则采样的常见方法是在数据集上创建一个点网格,在每个点上查询网格,并将结果作为属性分配给这些点。在此配方中,我们将在卫星图像上执行此类采样。QGIS 有一个名为规则点的工具来执行此操作,它位于研究工具下的矢量菜单中。然而,QGIS API 中没有工具可以以编程方式执行此操作。但是,我们可以直接使用 Python 的numpy模块实现此算法。

准备工作

在这个菜谱中,我们将使用之前使用的 SatImage 栅格,可在 geospatialpython.googlecode.com/files/SatImage.zip 找到。

将此栅格放在您的 /qgis_data/rasters 目录中。

如何做到这一点...

此菜谱的操作顺序是加载栅格图层,在内存中创建一个矢量图层,以规则间隔添加点,在这些点上采样栅格图层,然后将采样数据作为每个点的属性添加。为此,我们需要执行以下步骤:

  1. 启动 QGIS。

  2. 插件 菜单中选择 Python 控制台

  3. 我们需要导入 numpy 模块,它是 QGIS 的一部分,以及 Qt 核心模块:

    import numpy
    from PyQt4.QtCore import *
    
    
  4. 现在,我们将创建一个 spacing 变量来控制点在地图单位中的间隔距离:

    spacing = .1
    
    
  5. 接下来,我们将创建一个 inset 变量来决定点从图像边缘开始有多近,以地图单位计算:

    inset = .04
    
    
  6. 现在,我们加载并验证栅格图层:

    rasterLyr = QgsRasterLayer("/qgis_data/rasters/satimage.tif", "Sat Image")
    rasterLyr.isValid()
    
    
  7. 接下来,我们收集栅格图层的坐标参考系统和范围,以便将其传输到点图层:

    rpr = rasterLyr.dataProvider()
    epsg = rasterLyr.crs().postgisSrid()
    ext = rasterLyr.extent()
    
    
  8. 现在,我们创建一个内存中的矢量点图层,它不会写入磁盘:

    vectorLyr = QgsVectorLayer('Point?crs=epsg:%s' % epsg, 'Grid' , "memory")
    
    
  9. 为了向矢量图层添加点,我们必须访问其数据提供者:

    vpr = vectorLyr.dataProvider()
    qd = QVariant.Double
    
    
  10. 接下来,我们创建属性字段来存储栅格数据样本:

    vpr.addAttributes([QgsField("Red", qd), QgsField("Green", qd), QgsField("Blue", qd)])
    vectorLyr.updateFields()
    
    
  11. 我们使用 inset 变量来设置图层在栅格图层内的范围:

    xmin = ext.xMinimum() + inset
    xmax = ext.xMaximum()
    ymin = ext.yMinimum() + inset
    ymax = ext.yMaximum() – inset
    
    
  12. 现在,我们使用 numpy 模块高效地创建我们规则网格中点的坐标:

    pts = [(x,y) for x in (i for i in numpy.arange(xmin, xmax, spacing)) for y in (j for j in numpy.arange(ymin, ymax, spacing))]
    
    
  13. 然后,我们创建一个列表来存储我们将创建的点要素:

    feats = []
    
    
  14. 在一个循环中,我们创建点要素,查询栅格,然后更新属性表。我们目前将这些点存储在一个列表中:

    for x,y in pts:
     f = QgsFeature()
     f.initAttributes(3)
     p = QgsPoint(x,y)
     qry = rasterLyr.dataProvider().identify(p, QgsRaster.IdentifyFormatValue)
     r = qry.results()
     f.setAttribute(0, r[1])
     f.setAttribute(1, r[2])
     f.setAttribute(2, r[3])
     f.setGeometry(QgsGeometry.fromPoint(p))
     feats.append(f)
    
    
  15. 接下来,我们将点列表传递给点图层数据提供者:

    vpr.addFeatures(feats)
    
    
  16. 现在,我们更新图层的范围:

    vectorLyr.updateExtents()
    
    
  17. 然后,我们将栅格图层和矢量图层都添加到地图列表中。列表中的最后一项在最上面:

    QgsMapLayerRegistry.instance().addMapLayers([rasterLyr,vectoryr])
    
    
  18. 最后,我们刷新地图以查看结果:

    canvas = iface.mapCanvas()
    canvas.setExtent(rasterLyr.extent())
    canvas.refresh()
    
    

它是如何工作的...

以下截图显示了最终结果,其中一个网格点使用 Identify Features 地图工具被识别。结果对话框显示了所选点的栅格值:

它是如何工作的...

当你使用 QGIS 识别工具点击其中一个点时,结果对话框会显示从图像中提取的红色、绿色和蓝色值。

在 QGIS 中使用内存图层是执行快速、一次性操作的一种简单方法,无需在磁盘上创建文件的开销。如果您的机器有足够的资源,内存图层通常也很快。

还有更多...

在这个例子中,我们使用了一个规则网格,但我们可以轻松修改基于 numpy 的算法来创建一个随机点网格,在某些情况下这可能更有用。然而,处理工具箱还有一个简单的随机点算法,称为 grass:v.random

使用数字高程模型向线顶点添加高程数据

如果您有一些地形中的交通路线,了解该路线的高程剖面是有用的。此操作可以使用构成路线的线上的点查询 DEM 并将高程值分配给该点来完成。在这个菜谱中,我们将做 exactly that。

准备工作

您需要一个高程格网和一个路线。您可以从 geospatialpython.googlecode.com/svn/path.zip 下载此数据集。

解压包含 shapefile 和高程格网的 path 目录。将整个路径目录放置在您的 qgis_data/rasters 目录中。

如何操作...

我们需要两个处理算法来完成这个菜谱。我们将加载栅格和矢量图层,将线要素转换为点,然后使用这些点查询栅格。生成的点数据集将作为路线的高程剖面。为此,我们需要执行以下步骤:

  1. 启动 QGIS。

  2. 插件 菜单中选择 Python 控制台

  3. 导入 processing 模块:

    import processing
    
    
  4. 将文件名设置为变量,以便在整个脚本中使用:

    pth = "/qgis_data/rasters/path/"
    rasterPth = pth + "elevation.asc"
    vectorPth = pth + "path.shp"
    pointsPth = pth + "points.shp"
    elvPointsPth = pth + "elvPoints.shp"
    
    
  5. 加载并验证源图层:

    rasterLyr = QgsRasterLayer(rasterPth, "Elevation")
    rasterLyr.isValid()
    vectorLyr = QgsVectorLayer(vectorPth, "Path", "ogr")
    vectorLyr.isValid()
    
    
  6. 将图层添加到地图中:

    QgsMapLayerRegistry.instance().addMapLayers([vectorLyr, rasterLyr])
    
    
  7. 使用处理工具箱中的 SAGA 算法从线创建一个中间点数据集:

    processing.runalg("saga:convertlinestopoints", vectorLyr, False, 1, pointsPth)
    
    
  8. 最后,使用 SAGA 的另一个处理算法创建最终数据集,并将网格值分配给点:

    processing.runandload("saga:addgridvaluestopoints", pointsPth, rasterPth, 0, elvPointsPth)
    
    

工作原理...

以下是从 QGIS 保存的图像显示了数字高程模型(DEM)、路线线和带有高程标签的高程点,所有这些都在地图上显示,并应用了一些样式:

工作原理...

必须将线转换为点,因为线要素只能有一组属性。您也可以用多边形执行相同的操作。

更多...

我们可以构建一个处理脚本,将这两个算法组合到一个界面中,然后将其添加到工具箱中,而不是运行两个算法。在处理工具箱中,有一个名为 脚本 的类别,其中有一个名为 创建新脚本 的工具。双击此工具将打开一个编辑器,允许您构建自己的处理脚本。根据您的平台,您可能需要安装或配置 SAGA 以使用此算法。您可以在 sourceforge.net/p/saga-gis/wiki/Binary%20Packages/ 找到 Linux 的二进制包。

此外,在 Linux 上,您可能需要更改以下选项:

  1. 处理 菜单中,选择 选项…

  2. 选项 对话框中,打开 提供者 树菜单,然后打开 SAGA 树菜单。

  3. 取消选择 使用 2.0.8 语法 选项。

创建栅格的共同范围

如果你试图比较两个栅格图像,它们必须具有相同的范围和分辨率。大多数软件包甚至不允许你尝试比较没有相同范围的图像。有时,你会有重叠的图像,但它们没有共享一个共同的区域,或者具有不同的分辨率。以下插图是这种情况的一个例子:

为栅格创建共同范围

在这个菜谱中,我们将取两个重叠的图像并给它们相同的范围。

准备工作

你可以从geospatialpython.googlecode.com/svn/overlap.zip下载两个重叠的图像。

解压图像并将它们放在你的/qgis_data/rasters目录中。

你还需要从以下位置下载以下处理脚本:

geospatialpython.googlecode.com/svn/unify_extents.zip

解压内容并将脚本放在你的\.qgis2\processing\scripts目录中,该目录位于你的用户目录内。例如,在一个 Windows 64 位机器上,该目录将是C:\Users\<username>\.qgis2\processing\scripts,将<username>替换为你的用户名。

确保你重新启动 QGIS。此脚本是 Yury Ryabov 在他的博客ssrebelious.blogspot.com/2014/01/unifying-extent-and-resolution-of.html上创建的修改版本。

原始脚本使用了一个需要用户交互的确认对话框。修改后的脚本遵循处理工具箱编程约定,并允许你以编程方式使用它。

如何操作...

在 QGIS 中,唯一的步骤是运行新创建的处理命令。为此,我们需要执行以下步骤:

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. 导入processing模块:

    import processing
    
    
  4. 运行新添加的处理算法,指定算法名称、两个图像的路径、可选的无数据值、统一图像的输出目录以及一个布尔标志,用于将图像加载到 QGIS 中:

    processing.runalg("script:unifyextentandresolution","/qgis_data/rasters/Image2.tif;/qgis_data/rasters/Image1.tif",-9999,"/qgis_data/rasters",True)
    
    
  5. 在 QGIS 的目录中,请确认你有以下两个名为的图片:

    Image1_unified.tif
    Image2_unfied.tif
    
    

它是如何工作的...

以下截图显示了通过将Image1_unified.tif的透明度设置为像素0,0,0的栅格的常见范围:

它是如何工作的...

如果你没有使用透明度设置,你会看到两个图像都填充了非重叠区域,在两个范围的最小边界框内没有数据。这些无数据值,指定为-9999,将被其他处理算法忽略。

重采样栅格分辨率

重采样图像允许您将图像的当前分辨率更改为不同的分辨率。将分辨率降低,也称为下采样,需要您从图像中删除像素,同时保持数据集的地理空间参照完整性。在 QGIS 处理工具箱中,使用的是gdalogr:warpproject算法,这与用于重投影的算法相同。

准备工作

我们将再次使用可在geospatialpython.googlecode.com/files/SatImage.zip找到的 SatImage 栅格数据。

将此栅格放在您的/qgis_data/rasters目录中。

如何操作...

在此过程中有一个额外的步骤,我们将获取栅格当前像素分辨率作为参考来计算新的分辨率并将其传递给算法。为此,我们需要执行以下步骤:

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. 导入processing模块:

    import processing
    
    
  4. 加载并验证栅格图层:

    rasterLyr = QgsRasterLayer("/qgis_data/rasters/SatImage.tif", "Resample")
    rasterLyr.isValid()
    
    
  5. 算法需要投影信息。我们不会更改它,所以只需将当前投影分配给一个变量:

    epsg = rasterLyr.crs().postgisSrid()
    srs = "EPSG:%s" % epsg
    
    
  6. 获取当前像素的地面距离并将其乘以 2 以计算地面分辨率的一半。我们只使用 X 距离,因为在这种情况下,它与 Y 距离相同:

    res = rasterLyr.rasterUnitsPerPixelX() * 2
    
    
  7. 运行重采样算法,指定算法名称、图层引用、输入和输出空间参考系统、期望的分辨率、重采样算法(0是最近邻)、任何附加参数、0为输出栅格数据类型,以及输出文件名:

    processing.runalg("gdalogr:warpreproject", rasterLyr, srs, srs, res, 0, None, 0,  "/qgis_data/rasters/resampled.tif")
    
    
  8. 确认resampled.tif图像已创建在您的/qgis_data/rasters目录中。

它是如何工作的...

初始时,通过乘法降低分辨率似乎是不直观的。然而,通过增加每个像素的空间覆盖范围,覆盖栅格范围所需的像素更少。您可以在 QGIS 中通过加载两个图像并放大到有建筑物或其他详细结构的区域,然后关闭或打开一个图层来轻松比较两者之间的差异。

计算栅格中的唯一值

遥感图像不仅仅是图片;它们是数据。像素的值具有意义,可以由计算机自动分析。在数据集上运行统计算法是遥感的关键。此菜谱计算多个波段中像素唯一组合的数量。此菜谱的一个用例将是评估图像分类的结果,这是一个我们将在本章后面讨论的菜谱。此菜谱与典型的直方图函数相反,后者总计唯一值以及每个波段中每个值的频率。

准备工作

我们将使用可在geospatialpython.googlecode.com/files/SatImage.zip找到的 SatImage 栅格数据。

将此栅格放在您的/qgis_data/rasters目录中。

如何操作...

此算法完全依赖于numpy模块,该模块包含在 PyQGIS 中。Numpy 可以通过 GDAL 包的gdalnumeric模块访问。为此,我们需要执行以下步骤:

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. 首先,我们必须导入名为gdalnumeric的桥梁模块,该模块将 GDAL 与 Numpy 连接,以便在地理空间图像上执行数组数学:

    import gdalnumeric
    
    
  4. 现在,我们将直接将我们的栅格图像加载到多维数组中:

    a = gdalnumeric.LoadFile("/qgis_data/rasters/satimage.tif")
    
    
  5. 以下代码计算图像中的像素组合数量:

    b = a.T.ravel()
    c=b.reshape((b.size/3,3))
    order = gdalnumeric.numpy.lexsort(c.T)
    c = c[order]
    diff = gdalnumeric.numpy.diff(c, axis=0)
    ui = gdalnumeric.numpy.ones(len(c), 'bool')
    ui[1:] = (diff != 0).any(axis=1)
    u = c[ui]
    
    
  6. 现在,我们可以查看结果一维数组的大小以获取唯一值计数:

    u.size
    
    

最后,验证结果是否为16085631

它是如何工作的...

numpy模块是商业软件包Matlab的开源等效物。你可以在Numpy.org了解更多关于 Numpy 的信息。

当你使用 Numpy 加载一个图像时,它被加载为一个多维数组。Numpy 允许你使用操作符和专用函数在整个数组上进行数组数学,就像你在包含单个数值变量的变量上做的那样。

栅格镶嵌

栅格镶嵌是将具有相同分辨率和地图投影的多个地理空间图像融合成一个栅格的过程。在本菜谱中,我们将结合两个重叠的卫星图像形成一个单一的数据集。

准备工作

如果你之前没有从其他菜谱中下载,你需要从geospatialpython.googlecode.com/svn/overlap.zip下载重叠数据集。

将两个图像放置在您的/qgis_data/rasters/目录中。

如何操作...

此过程相对简单,在处理工具箱中有一个专门的算法。执行以下步骤:

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. 运行 gdalogr:merge 算法,指定进程名称、两个图像、一个布尔值以使用第一个图像的伪彩色调色板、一个布尔值以将每个图像堆叠到单独的波段中,以及输出文件名:

    processing.runalg("gdalogr:merge","C:/qgis_data/rasters/Image2.tif;C:/qgis_data/rasters/Image1.tif",False,False,"/qgis_data/rasters/merged.tif")
    
    
  4. 验证merged.tif图像是否已创建,并在 QGIS 中显示为单个栅格中的两个图像。

它是如何工作的...

合并处理算法是实际gdal_merge命令行工具的简化版本。此算法仅限于 GDAL 输出并汇总输入栅格的范围。它一次只能合并两个栅格。gdal_merge 工具具有更多选项,包括额外的输出格式、一次合并多个栅格的能力、控制范围的能力等。你还可以直接使用 GDAL API 来利用这些功能,但将需要比这个简单示例更多的代码。

将 TIFF 图像转换为 JPEG 图像

图像格式转换是几乎所有地理空间项目的一部分。栅格有多种不同的专用格式,将它们转换为更常见的格式是必要的。GDAL 工具包括一个名为gdal_translate的工具,专门用于格式转换。不幸的是,Processing 工具箱中的算法功能有限。对于格式转换,使用核心 GDAL API 更容易。

准备工作

我们将使用位于geospatialpython.googlecode.com/files/SatImage.zip的 SatImage 栅格。

将此栅格放置在您的/qgis_data/rasters目录中。

如何操作...

在这个配方中,我们将使用 GDAL 打开一个 TIFF 图像并将其复制到一个新的数据集作为 JPEG2000 图像,这允许您在保持地理空间信息的同时使用常见的 JPEG 格式。为此,我们需要执行以下步骤:

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. 导入 gdal 模块:

    from osgeo import gdal
    
    
  4. 获取我们所需格式的 GDAL 驱动程序:

    drv = gdal.GetDriverByName("JP2OpenJPEG")
    
    
  5. 打开源图像:

    src = gdal.Open("/qgis_data/rasters/satimage.tif")
    
    
  6. 将源数据集复制到新格式:

    tgt = drv.CreateCopy("/qgis_data/rasters/satimage.jp2", src)
    
    

它是如何工作的...

对于图像格式的直接格式转换,核心 GDAL 库非常快且简单。GDAL 支持创建超过 60 种栅格格式和读取超过 130 种栅格格式。

为栅格创建金字塔

金字塔,或概述图像,通过在文件中存储与全分辨率图像一起的重新采样、低分辨率图像版本来牺牲磁盘空间以换取地图渲染速度。一旦您最终确定了一个栅格,构建金字塔概述是一个好主意。

准备工作

对于这个配方,我们将使用一个假彩色图像,您可以从geospatialpython.googlecode.com/files/FalseColor.zip下载。

解压此TIF文件并将其放置在您的/qgis_data/rasters目录中。

如何操作...

Processing 工具箱有一个专门用于构建金字塔图像的算法。执行以下步骤以创建栅格金字塔:

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. 导入processing模块:

    import processing
    
    
  4. 运行gdalogr:overviews算法,指定进程名称、输入图像、概述级别、删除现有概述的选项、重采样方法(0是最近邻)和概述格式(1是内部):

    processing.runalg("gdalogr:overviews","/qgis_data/rasters/FalseColor.tif","2 4 8 16",True,0,1)
    
    
  5. 现在,通过从文件系统拖放到地图画布上,将栅格加载到 QGIS 中。

  6. 双击地图图例中的图层名称以打开图层属性对话框。

  7. 图层属性对话框中,点击金字塔选项卡并验证图层是否列出了多个分辨率。

它是如何工作的...

概览图像的概念相当简单。你多次重采样图像,然后查看器根据比例选择最合适的、最小的文件加载到地图上。概览可以存储在文件的头部,对于某些格式或作为外部文件格式。所需概览的级别在很大程度上取决于当前图像的文件大小和分辨率,但实际上是任意的。在这个例子中,我们通过 2 的倍数放大比例,这是一种常见的做法。大多数应用程序中的缩放工具在点击放大时都会将比例加倍。2 的倍数为你提供了足够的缩放级别,因此你通常不会缩放到没有金字塔图像的水平。如果你创建太多级别,因为金字塔会占用额外的磁盘空间,所以会有一个收益递减的点。通常 4 到 5 个级别是有效的。

将像素位置转换为地图坐标

在地理空间环境中查看栅格的能力依赖于将像素位置转换为地面坐标。迟早,当你使用 Python 编写地理空间程序时,你必须自己执行这种转换。

准备工作

我们将使用在以下位置可用的 SatImage 栅格:

geospatialpython.googlecode.com/files/SatImage.zip

将此栅格放置在您的/qgis_data/rasters目录中。

如何操作...

我们将使用 GDAL 提取将像素转换为坐标所需的信息,然后使用纯 Python 执行计算。我们将使用图像的中心像素作为转换的位置。

  1. 启动 QGIS。

  2. 插件菜单选择Python 控制台

  3. 我们需要导入gdal模块:

    from osgeo import gdal
    
    
  4. 然后,我们需要定义一个可重用的函数,该函数执行转换,接受包含栅格地理参考信息的 GDAL GeoTransform对象以及像素的xy值:

    def Pixel2world(geoMatrix, x, y):
     ulX = geoMatrix[0]
     ulY = geoMatrix[3]
     xDist = geoMatrix[1]
     yDist = geoMatrix[5]
     coorX = (ulX + (x * xDist))
     coorY = (ulY + (y * yDist))
     return (coorX, coorY)
    
    
  5. 现在,我们将使用 GDAL 打开图像

    src = gdal.Open("/qgis_data/rasters/Satimage.tif")
    
    
  6. 接下来,从图像中获取GeoTransform对象:

    geoTrans = src.GetGeoTransform()
    
    
  7. 现在,计算图像的中心像素:

    centerX = src.RasterXSize/2
    centerY = src.RasterYSize/2
    
    
  8. 最后,通过调用我们的函数来执行转换:

    Pixel2world(geoTrans, centerX, centerY)
    
    
  9. 验证返回的坐标是否接近以下输出:

    (-89.59486002580364, 30.510227817850406)
    
    

工作原理...

像素转换只是两个平面之间的缩放比例,即图像坐标系和地球坐标系。当处理大面积时,这种转换可能成为一个更复杂的投影,因为地球的曲率会发挥作用。GDAL 网站在以下 URL 有一个关于地理变换对象的优秀教程:www.gdal.org/gdal_tutorial.html

将地图坐标转换为像素位置

当你收到一个地图坐标作为用户输入或来自其他来源时,你必须能够将其转换回栅格上的适当像素位置。

准备工作

我们将使用在以下位置可用的 SatImage 栅格:

geospatialpython.googlecode.com/files/SatImage.zip

将此栅格文件放置在您的/qgis_data/rasters目录中。

如何操作...

与之前的菜谱类似,我们将定义一个函数,从我们的栅格中提取 GDAL GeoTransform对象,并用于转换。

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. 我们需要导入 gdal 模块:

    from osgeo import gdal
    
    
  4. 然后,我们需要定义一个可重用的函数,该函数执行坐标到像素的转换。我们获取包含栅格地理参考信息的 GDAL GeoTransform对象和地图xy坐标:

    def world2Pixel(geoMatrix, x, y):
     ulX = geoMatrix[0]
     ulY = geoMatrix[3]
     xDist = geoMatrix[1]
     yDist = geoMatrix[5]
     rtnX = geoMatrix[2]
     rtnY = geoMatrix[4]
     pixel = int((x - ulX) / xDist)
     line = int((y - ulY) / yDist)
     return (pixel, line)
    
    
  5. 接下来,我们打开源图像:

    src = gdal.Open("/qgis_data/rasters/satimage.tif")
    
    
  6. 现在,获取 GeoTransform 对象:

    geoTrans = src.GetGeoTransform()
    
    
  7. 最后,执行转换:

    world2Pixel(geoTrans, -89.59486002580364, 30.510227817850406)
    
    
  8. 验证您的输出如下:

    (1296, 1346)
    
    

工作原理...

这种转换在小范围内非常可靠,但随着感兴趣区域的扩大,您还必须考虑高程,这需要一个更复杂的转换,具体取决于图像是如何生成的。

注意

马萨诸塞大学提供的以下演示文稿出色地解释了地理参考数据的挑战:

UMass 大学课程资料

为栅格创建 KML 图像叠加

GoogleEarth是现有最广泛使用的地理空间查看器之一。GoogleEarth 用于地理空间数据的 XML 数据格式称为KML。开放地理空间联盟采用了 KML 作为数据标准。将栅格转换为压缩在KMZ存档文件中的KML叠加是一种非常流行的将数据提供给知道如何使用 GoogleEarth 的最终用户的方式。

准备工作

如果您尚未从之前的菜谱中下载,我们将再次使用以下 URL 中可用的 SatImage 栅格:

SatImage 下载链接

将此栅格文件放置在您的/qgis_data/rasters目录中。

如何操作...

在本菜谱中,我们将创建一个描述我们图像的KML文档。然后,我们将使用 GDAL 的专用虚拟文件系统将图像转换为内存中的JPEG,并使用 Python 的zipfile模块将所有内容直接写入KMZ文件。

  1. 启动 QGIS。

  2. 插件菜单中选择Python 控制台

  3. 我们需要导入gdal模块以及 Python 的zipfile模块:

    from osgeo import gdal
    import zipfile
    
    
  4. 接下来,我们将在gdal中打开我们的卫星图像:

    srcf = "/qgis_data/rasters/Satimage.tif"
    
    
  5. 现在,我们将使用以vismem开头的 GDAL 虚拟文件命名约定,创建一个包含虚拟文件名的变量:

    vfn = "/vsimem/satimage.jpg"
    
    
  6. 我们为输出格式创建 JPEG gdal 驱动程序对象:

    drv = gdal.GetDriverByName('JPEG')
    
    
  7. 现在,我们可以打开源文件:

    src = gdal.Open(srcf)
    
    
  8. 然后,我们可以将源文件复制到我们的虚拟 JPEG:

    tgt = drv.CreateCopy(vfn, src)
    
    
  9. 现在,我们将为我们的栅格创建一个 QGIS 栅格图层,仅为了计算图像的范围:

    rasterLyr = QgsRasterLayer(srcf, "SatImage")
    
    
  10. 接下来,我们获取图层的范围:

    e = rasterLyr.extent()
    
    
  11. 接下来,我们格式化 KML 文档模板并插入图像范围:

    kml = """<?xml version="1.0" encoding="UTF-8"?>
    <kml >
      <Document>
        <name>QGIS KML Example</name>
        <GroundOverlay>
            <name>SatImage</name>
            <drawOrder>30</drawOrder>
            <Icon>
              <href>SatImage.jpg</href>
            </Icon>
            <LatLonBox>
              <north>%s</north>
              <south>%s</south>
              <east>%s</east>
              <west>%s</west>
            </LatLonBox>
        </GroundOverlay>
      </Document>
    </kml>""" %(e.yMaximum(), e.yMinimum(), e.xMaximum(), e.xMinimum())
    
  12. 现在,我们打开我们的虚拟 JPEG 文件在 GDAL 中,并为其读取做准备:

    vsifile = gdal.VSIFOpenL(vfn,'r')
    gdal.VSIFSeekL(vsifile, 0, 2)
    vsileng = gdal.VSIFTellL(vsifile)
    gdal.VSIFSeekL(vsifile, 0, 0)
    
    
  13. 最后,我们将我们的 KML 文档和虚拟 JPEG 写入一个压缩的 KMZ 文件:

    z = zipfile.ZipFile("/qgis_data/rasters/satimage.kmz", "w", zipfile.ZIP_DEFLATED)
    z.writestr("doc.kml", kml)
    z.writestr("SatImage.jpg", gdal.VSIFReadL(1, vsileng, vsifile))
    z.close()
    
    
  14. 现在,在 GoogleEarth 中打开 KMZ 文件,并确认它看起来像以下截图:如何操作...

它是如何工作的...

KML 是一个简单的 XML 格式。在 Python 中,有专门的库用于读取和写入它,但对于简单的导出以共享一个或两个图像,PyQGIS 控制台已经足够了。虽然我们在这个脚本中运行 QGIS Python 解释器,但它也可以仅使用 GDAL 在 QGIS 之外运行。

更多...

Orfeo Toolbox有一个名为otb:imagetokmzexport的处理算法,它为图像提供了一个更复杂的 KMZ 导出工具。

栅格分类

图像分类是遥感中最复杂的方面之一。虽然 QGIS 能够根据值对像素进行着色以进行可视化,但它并没有进行太多的分类。它确实提供了一个栅格计算器工具,你可以在图像上执行任意数学公式,然而它并没有尝试实现任何常见算法。Orfeo Toolbox 完全致力于遥感,包括一个名为 K-means 聚类的自动分类算法,该算法将像素分组到任意数量的相似类别中,以创建一个新的图像。我们可以使用这个算法做一个很好的图像分类演示。

准备工作

对于这个食谱,我们将使用一个假彩色图像,你可以从这里下载:

geospatialpython.googlecode.com/files/FalseColor.zip

解压这个 TIFF 文件,并将其放置在你的/qgis_data/rasters目录中。

如何操作...

我们需要做的只是在我们输入的图像上运行算法。重要的参数是第二个、第三个、第六个和第十个参数。它们分别定义了输入图像名称、分配给任务的 RAM 量、类别数量和输出名称。

  1. 首先,在 QGIS Python 控制台中导入processing模块:

    import processing
    
    
  2. 接下来,使用processing.runandload()方法运行otb算法,以在 QGIS 中显示输出:

    processing.runandload("otb:unsupervisedkmeansimageclassification","/qgis_data/rasters/FalseColor.tif",768,None,10000,3,1000,0.95,"/qgis_data/rasters/classify.tif",None)
    
    
  3. 当图像在 QGIS 中加载时,双击内容表中的图层名称。

  4. 图层属性对话框中,选择样式

  5. 渲染类型菜单更改为单波段伪彩色

  6. 将右侧的颜色图菜单更改为光谱

  7. 点击分类按钮。

  8. 在窗口底部选择确定按钮。

  9. 确认你的图像看起来与以下图像相似,但不要有类别标签:如何操作...

它是如何工作的...

保持类别编号低,可以让自动化分类算法专注于图像中的主要特征,并在我们达到非常高的整体土地利用准确性时有所帮助。额外的自动化分类将需要带有训练数据集的监督分析和更深入的准备。但整体概念将保持不变。QGIS 有一个用于半自动分类的插件。您可以在以下 URL 了解更多信息:

plugins.qgis.org/plugins/SemiAutomaticClassificationPlugin/

将栅格转换为矢量

栅格数据集有效地表示现实世界的特征,但在地理空间分析中可能用途有限。一旦将图像分类为可管理的数据集,您可以将这些栅格类别转换为矢量数据集,以便进行更复杂的 GIS 分析。GDAL 有一个名为 polygonize 的函数用于此操作。

准备工作

您需要下载以下分类栅格并将其放置在您的 /qgis_data/rasters 目录中:

geospatialpython.googlecode.com/svn/landuse_bay.zip

如何操作...

通常,您会将此菜谱的输出保存为形状文件。我们不会指定输出文件名。处理工具箱将分配一个临时文件名并返回该文件名。我们只需将临时文件加载到 QGIS 中。该算法允许您通过将其指定为最后一个参数来写入形状文件。

  1. 在 QGIS Python 控制台中导入processing模块:

    import processing
    
    
  2. 接下来,运行算法,指定进程名称、输入图像、类别编号的字段名称,以及可选的输出形状文件:

    processing.runalg("gdalogr:polygonize","C:/qgis_data/rasters/landuse_bay.tif","DN",None)
    
    
  3. 您应该获得一个包含三个类别的矢量图层,定义为多边形,表示已开发区域。在下面的示例图像中,我们为每个类别分配了独特的颜色:开发区域(最暗),水域(中间色调),和陆地(最浅颜色):如何操作...

它是如何工作的...

GDAL 寻找像素簇并在其周围创建多边形。尽可能少地使用类别很重要。如果像素之间存在太多差异,那么 GDAL 将在图像中的每个像素周围创建一个多边形。您可以通过使用第一章中的菜谱 计算多边形面积 来将此菜谱转换为真正的分析产品,以量化每种土地利用类别。

从控制点进行栅格地理配准

有时,代表地球特征的栅格图可能只是一个没有地理参照信息的图像。历史扫描地图的情况就是这样。然而,你可以使用同一区域的参考数据集来创建连接点,或称为地面控制点,然后使用算法将图像扭曲以适应地球模型。数据收集系统通常只存储与栅格一起的地面控制点(GCP),以尽可能保持图像的原始格式。每次对图像的更改都可能导致数据丢失。因此,按需地理参照图像通常是最佳方法。

在这个菜谱中,我们将地理参照 1853 年路易斯安那州和密西西比州墨西哥湾沿岸的历史调查地图。控制点是使用 QGIS 地理参照插件手动创建并保存到标准控制点文件的。

准备工作

下载以下 zip 文件,解压内容,并将georef目录放在/qgis_data/rasters

geospatialpython.googlecode.com/svn/georef.zip

如何操作...

我们将使用处理 API 的低级模块来访问一些专门的 GDAL 实用工具函数。

  1. 在 QGIS Python 控制台中,导入GdalUtils模块:

    from processing.algs.gdal.GdalUtils import GdalUtils
    
    
  2. 现在,我们将为源数据和目标数据设置一些路径名,这些路径名将被多次使用:

    src = "/qgis_data/rasters/georef/1853survey.jpg"
    points = "/qgis_data/rasters/georef/1853Survey.points"
    trans = "/qgis_data/rasters/georef/1835survey_trans.tif"
    final = "/qgis_data/rasters/georef/1835survey_georef.tif"
    
    
  3. 接下来,我们将打开我们的 GCP 文件并读取标题行:

    gcp = open(points, "rb")
    hdr = gcp.readline()
    
    
  4. 然后,我们可以开始构建我们的第一个 gdal 实用工具命令:

    command = ["gdal_translate"]
    
    
  5. 遍历 GCP 文件并将点追加到命令参数中:

    for line in gcp:
     x,y,col,row,e = line.split(",")
     command.append("-gcp")
     command.append("%s" % col)
     command.append("%s" % abs(float(row)))
     command.append("%s" % x)
     command.append("%s" % y)
    
    
  6. 现在,将输入和输出文件添加到命令中:

    command.append(src)
    command.append(trans)
    
    
  7. 接下来,我们可以执行第一个命令:

    GdalUtils.runGdal(command, None)
    
    
  8. 接下来,我们将命令更改为扭曲图像:

    command = ["gdalwarp"]
    command.extend(["-r", "near", "-order", "3", "-co", "COMPRESS=NONE", "-dstalpha"])
    
    
  9. 将上一个命令的输出添加为输入,并使用最终图像路径作为输出:

    command.append(trans)
    command.append(final)
    
    
  10. 现在,运行扭曲命令以完成任务:

    GdalUtils.runGdal(command, None)
    
    

它是如何工作的...

GdalUtils API 公开了 Processing 工具箱算法使用的底层工具,同时提供了一个比其他传统方法更好的跨平台方法,这些传统方法是从 Python 访问外部程序。如果你将输出图像拖入 QGIS 并与 USGS 海岸线 shapefile 进行比较,你可以看到结果相当准确,并且可以通过添加更多的控制点和参考数据来改进。对于给定图像所需的 GCP 数量是一个试错的问题。添加更多的 GCP 并不一定会导致更好的结果。你可以在 QGIS 文档中了解更多关于创建 GCP 的信息:

docs.qgis.org/2.6/en/docs/user_manual/plugins/plugins_georeferencer.html

使用 shapefile 裁剪栅格

有时候,你可能需要使用图像的一部分,这部分覆盖了项目感兴趣的区域。实际上,图像中你感兴趣区域外的区域可能会分散听众对你试图传达的想法的注意力。将栅格裁剪到矢量边界可以让你只使用所需的栅格部分。这还可以通过消除你感兴趣区域外的区域来节省处理时间。

准备工作

如果您之前没有从之前的菜谱中下载,我们将再次使用以下 URL 中可用的 SatImage 栅格:

geospatialpython.googlecode.com/files/SatImage.zip

将此栅格放置在你的/qgis_data/rasters目录中。

如何操作...

裁剪是一种常见的操作,GDAL 非常适合进行这项操作。

  1. 首先,在 QGIS 的Python 控制台中运行导入处理模块:

    import processing
    
    
  2. 接下来,运行处理命令,将输入图像名称作为第二个参数,输出图像作为第七个参数:

    processing.runandload("gdalogr:cliprasterbymasklayer","/qgis_data/rasters/SatImage.tif","/qgis_data/hancock/hancock.shp","none",False,False,"","/qgis_data/rasters/clipped.tif")
    
    
  3. 验证你的输出栅格看起来像以下截图:如何操作...

它是如何工作的...

GDAL 在 shapefile 边界外创建了一个无数据掩码。原始图像的尺寸保持不变,但你不再可视化它,处理算法将忽略无数据值。

第五章:创建动态地图

在本章中,我们将介绍以下菜谱:

  • 访问地图画布

  • 更改地图单位

  • 遍历图层

  • 矢量图层符号化

  • 使用颜色渐变算法渲染单波段栅格

  • 创建复杂的矢量图层符号

  • 使用图标作为矢量图层符号

  • 创建渐变矢量图层符号

  • 创建分类矢量图层符号

  • 创建地图书签

  • 导航到地图书签

  • 设置图层的基于比例的可见性

  • 使用 SVG 作为图层符号

  • 使用饼图作为符号

  • 使用 OpenStreetMap 服务

  • 使用 Bing 航拍影像服务

  • 从 OpenWeatherMap 添加实时天气数据

  • 特征标注

  • 更改地图图层透明度

  • 将标准地图工具添加到画布上

  • 使用地图工具在画布上绘制点

  • 使用地图工具在画布上绘制多边形或线条

  • 构建自定义选择工具

  • 创建鼠标坐标跟踪工具

简介

在本章中,我们将使用 Python 编程创建动态地图,以控制 QGIS 地图画布的各个方面。我们将学习如何使用自定义符号、标签、地图书签,甚至实时数据。我们还将超越画布创建自定义地图工具。您将看到 Python 可以控制 QGIS 的各个方面,以编写您自己的应用程序。有时,PyQGIS API 可能不会直接支持您的应用程序目标,但几乎总是有办法使用 QGIS 完成您设定的目标。

访问地图画布

QGIS 中的地图通过地图画布进行控制。在本菜谱中,我们将访问画布,然后检查其属性之一以确保我们对对象有控制权。

准备工作

对于这个菜谱,您需要做的唯一事情是打开 QGIS 并从 插件 菜单中选择 Python 控制台

如何操作...

我们将把地图画布分配给一个名为 canvas 的变量。然后,我们将检查画布的 size 属性以获取其像素大小。为此,请执行以下步骤:

  1. 在 QGIS Python 控制台中输入以下行:

    canvas = qgis.utils.iface.mapCanvas()
    
    
  2. 现在,为了确保我们已经正确访问了画布,请使用以下代码行检查其像素大小:

    canvas.size()
    
    
  3. 验证 QGIS 是否返回一个包含画布像素大小的 QSize 对象,格式类似于以下内容:

    PyQt4.QtCore.QSize(698, 138)
    
    

它是如何工作的...

QGIS 中的所有内容都集中在画布上。画布是 QGIS 界面或 iface API 的一部分。使用 QGIS 时你在屏幕上看到的所有内容都是通过 iface API 生成的。请注意,iface 对象仅对脚本和插件可用。当你构建独立应用程序时,你必须初始化自己的 QgsMapCanvas 对象。

更改地图单位

根据您的地图目的或您组织或国家的标准,更改地图单位或地图单位是一个非常常见的操作。在本菜谱中,我们将读取 QGIS 使用的地图单位,然后更改它们以适应您的项目。

准备工作

为此菜谱,您唯一需要的准备是打开 QGIS 并从插件菜单中选择Python 控制台

如何操作...

在以下步骤中,我们将访问地图画布,检查地图单位类型,并将其更改为不同的设置。

  1. 首先,按照以下方式访问地图画布:

    canvas = iface.mapCanvas()
    
    
  2. 现在,获取地图单位类型。默认情况下,它应该是数字2

    canvas.mapUnits()
    
    
  3. 现在,让我们使用内置枚举器将地图单位设置为米:

    canvas.setMapUnits(QGis.Meters)
    
    

它是如何工作的...

QGIS 有七个不同的地图单位,按以下顺序列出:

0 米

1 英尺

2 度

3 未知单位

4 十进制度

5 度分秒

6 十进制度分

7 海里

重要的是要注意,更改地图单位仅更改测量工具和状态栏显示的测量单位;它不会更改底层地图投影。如果您尝试在依赖于未投影数据的米投影数据上运行 Processing 工具箱中的操作,您将注意到这种差异。更改地图单位的最常见用例是根据用户的偏好在英制和公制单位之间切换。

遍历层

对于许多 GIS 操作,您需要遍历地图层以查找特定信息或对所有层应用更改。在这个菜谱中,我们将遍历层并获取它们的信息。

准备工作

我们需要两个具有相同地图投影的层来执行此菜谱。您可以从geospatialpython.googlecode.com/files/MSCities_Geo_Pts.zip下载第一个层作为 ZIP 文件。

您可以从geospatialpython.googlecode.com/files/Mississippi.zip下载第二个压缩层。

将这两个层解压缩到您qgis_data目录下的名为ms的目录中。

如何操作...

我们将通过地图注册表将层添加到地图中。然后,我们将遍历地图层并打印每一层的标题。为此,执行以下步骤:

  1. 首先,让我们使用 QGIS Python 控制台打开多边形和点层:

    lyr_1 = QgsVectorLayer("/Users/joellawhead/qgis_data/ms/mississippi.shp", "Mississippi", "ogr")
    lyr_2 = QgsVectorLayer("/Users/joellawhead/qgis_data/ms/MSCities_Geo_Pts.shp", "Cities", "ogr")
    
    
  2. 接下来,获取地图层注册实例:

    registry = QgsMapLayerRegistry.instance()
    
    
  3. 现在,将矢量层添加到地图中:

    registry.addMapLayers([lyr_2, lyr_1])
    
    
  4. 然后,我们将层作为迭代器检索:

    layers = registry.mapLayers()
    
    
  5. 最后,我们遍历层并打印标题:

    for l in layers:
     printl.title()
    
    
  6. 确认您可以在Python 控制台中读取层标题,格式类似于以下:

    Cities20140904160234792
    Mississippi20140904160234635
    
    

它是如何工作的...

在 QGIS 中,层在您将它们添加到地图层注册表之前与地图画布独立。一旦创建,它们就有一个 ID。当添加到地图中时,它们成为画布的一部分,在那里它们获取标题、符号和许多其他属性。在这种情况下,您可以使用地图层注册表遍历它们并访问它们以更改它们的外观或添加和提取数据。

符号化矢量层

QGIS 地图上图层的外观由其符号控制。图层的符号包括渲染器和一个或多个符号。渲染器提供规则,以规定符号的外观。符号描述属性,包括颜色、形状、大小和线宽。在本食谱中,我们将加载一个矢量图层,更改其符号,并刷新地图。

准备工作

下载以下压缩的 shapefile 并将其提取到您的qgis_data目录中的ms文件夹中,从geospatialpython.googlecode.com/files/Mississippi.zip

如何操作...

我们将加载一个图层,将其添加到地图图层注册表中,更改图层的颜色,然后刷新地图。为此,执行以下步骤:

  1. 首先,使用 QGIS Python 控制台,我们必须导入QtGui库,以便访问用于在 PyQGIS API 中描述颜色的QColor对象:

    from PyQt4.QtGui import *
    
    
  2. 然后,我们创建我们的矢量图层,如下所示:

    lyr = QgsVectorLayer("/Users/joellawhead/qgis_data/ms/mississippi.shp", "Mississippi", "ogr")
    
    
  3. 然后,我们将它添加到地图图层注册表中:

    QgsMapLayerRegistry.instance().addMapLayer(lyr)
    
    
  4. 现在,我们通过图层的渲染器对象访问图层的符号列表:

    symbols = lyr.rendererV2().symbols()
    
    
  5. 接下来,我们引用第一个符号,在这种情况下,这是唯一的符号:

    sym = symbols[0]
    
    
  6. 一旦我们有了符号,我们就可以设置其颜色:

    sym.setColor(QColor.fromRgb(255,0,0))
    
    
  7. 我们必须记住重新绘制图层以强制更新:

    lyr.triggerRepaint()
    
    

它是如何工作的...

改变图层的颜色听起来很简单,但请记住,在 QGIS 中,您看到的任何内容都必须通过画布 API 进行更改。因此,我们将图层添加到地图中,并通过其渲染器访问图层的符号。地图画布被渲染为位图图像。渲染器负责将图层数据转换为位图图像,因此图层的表示信息与其渲染器一起存储。

使用颜色渐变算法渲染单波段栅格

颜色渐变允许您使用仅几种颜色来表示具有相似意义的单元格值的不同范围,以便将它们分组。本食谱中将使用的方法是渲染高程数据最常见的方式。

准备工作

您可以从geospatialpython.googlecode.com/files/dem.zip下载一个示例 DEM,您可以在qgis_data目录中的rasters目录中解压缩它。

如何操作...

在以下步骤中,我们将设置对象以着色栅格,创建一个建立颜色渐变范围的列表,将渐变应用于图层渲染器,并最终将图层添加到地图中。为此,我们需要执行以下步骤:

  1. 首先,我们在 QGIS Python 控制台中导入用于颜色对象的QtGui库:

    from PyQt4 import QtGui
    
    
  2. 接下来,我们加载栅格图层,如下所示:

    lyr = QgsRasterLayer("/Users/joellawhead/qgis_data/rasters/dem.asc", "DEM")
    
    
  3. 现在,我们创建一个通用的栅格着色器对象:

    s = QgsRasterShader()
    
    
  4. 然后,我们实例化专门的渐变着色器对象:

    c = QgsColorRampShader()
    
    
  5. 我们必须为渐变着色器命名一个类型。在这种情况下,我们使用一个INTERPOLATED着色器:

    c.setColorRampType(QgsColorRampShader.INTERPOLATED)
    
    
  6. 现在,我们将创建我们的颜色渐变定义列表:

    i = []
    
    
  7. 然后,我们填充颜色渐变值列表,这些值对应于高程值范围:

    i.append(QgsColorRampShader.ColorRampItem(400, QtGui.QColor('#d7191c'), '400'))
    i.append(QgsColorRampShader.ColorRampItem(900, QtGui.QColor('#fdae61'), '900'))
    i.append(QgsColorRampShader.ColorRampItem(1500, QtGui.QColor('#ffffbf'), '1500'))
    i.append(QgsColorRampShader.ColorRampItem(2000, QtGui.QColor('#abdda4'), '2000'))
    i.append(QgsColorRampShader.ColorRampItem(2500, QtGui.QColor('#2b83ba'), '2500'))
    
    
  8. 现在,我们将颜色渐变分配给我们的着色器:

    c.setColorRampItemList(i)
    
    
  9. 现在,我们告诉通用栅格着色器使用颜色渐变:

    s.setRasterShaderFunction(c)
    
    
  10. 接下来,我们使用着色器创建一个栅格渲染器对象:

    ps = QgsSingleBandPseudoColorRenderer(lyr.dataProvider(), 1,  s)
    
    
  11. 我们将渲染器分配给栅格图层:

    lyr.setRenderer(ps)
    
    
  12. 最后,我们将图层添加到画布上以便查看:

    QgsMapLayerRegistry.instance().addMapLayer(lyr)
    
    

它是如何工作的…

虽然创建颜色渐变需要四个对象,但这个菜谱展示了 PyQGIS API 的灵活性。通常,在 QGIS 中完成操作所需的对象越多,API 就越丰富,这为你提供了制作复杂地图的灵活性。

注意,在每一个ColorRampItem对象中,你指定一个起始高程值、颜色和作为字符串的标签。颜色渐变的范围在下一个项目之前的任何值结束。因此,在这种情况下,第一个颜色将被分配给值在 400 到 899 之间的单元格。以下截图显示了应用的颜色渐变。

它是如何工作的…

创建复杂矢量图层符号

QGIS 符号化的真正力量在于其能够堆叠多个符号以创建单个复杂符号的能力。这种能力使得创建几乎任何你可以想象到的地图符号成为可能。在这个菜谱中,我们将合并两个符号以创建单个符号,并开始解锁复杂符号的潜力。

准备工作

对于这个菜谱,我们需要一个线形状文件,你可以从geospatialpython.googlecode.com/svn/paths.zip下载并提取。

将此形状文件添加到qgis_data目录中名为shapes的目录。

如何做到这一点…

使用QGISPythonConsole,我们将通过在规则线符号上放置一系列短旋转线标记来创建一个经典的铁路线符号。为此,我们需要执行以下步骤:

  1. 首先,我们加载我们的线形状文件:

    lyr = QgsVectorLayer("/Users/joellawhead/qgis_data/shapes/paths.shp", "Route", "ogr")
    
    
  2. 接下来,我们获取符号列表并引用默认符号:

    symbolList = lyr.rendererV2().symbols()
    symbol = symbolList[0]
    
    
  3. 然后,我们为符号层创建一个更短的变量名:

    symLyrReg = QgsSymbolLayerV2Registry
    
    
  4. 现在,我们使用 Python 字典设置简单线的样式:

    lineStyle = {'width':'0.26', 'color':'0,0,0'}
    
    
  5. 然后,我们为简单线创建一个抽象符号层:

    symLyr1Meta = symLyrReg.instance().symbolLayerMetadata("SimpleLine")
    
    
  6. 我们使用线样式属性从抽象层实例化一个符号层:

    symLyr1 = symLyr1Meta.createSymbolLayer(lineStyle)
    
    
  7. 现在,我们将符号层添加到层的符号中:

    symbol.appendSymbolLayer(symLyr1)
    
    
  8. 现在,为了在铁路上创建轨道,我们开始使用另一个 Python 字典构建一个标记线样式,如下所示:

    markerStyle = {}
    markerStyle['width'] = '0.26'
    markerStyle['color'] = '0,0,0'
    markerStyle['interval'] = '3'
    markerStyle['interval_unit'] = 'MM'
    markerStyle['placement'] = 'interval'
    markerStyle['rotate'] = '1'
    
    
  9. 然后,我们为第二个符号创建标记线抽象符号层:

    symLyr2Meta = symLyrReg.instance().symbolLayerMetadata("MarkerLine")
    
    
  10. 我们创建符号层,如下所示:

    symLyr2 = symLyr2Meta.createSymbolLayer(markerStyle)
    
    
  11. 现在,我们必须与定义标记线上的标记的子符号一起工作:

    sybSym = symLyr2.subSymbol()
    
    
  12. 我们必须删除默认的子符号:

    sybSym.deleteSymbolLayer(0)
    
    
  13. 现在,我们使用字典设置我们的轨道标记的样式:

    railStyle = {'size':'2', 'color':'0,0,0', 'name':'line', 'angle':'0'}
    
    
  14. 现在,我们重复构建符号层并将其添加到子符号的过程:

    railMeta = symLyrReg.instance().symbolLayerMetadata("SimpleMarker")
    rail = railMeta.createSymbolLayer(railStyle) 
    sybSym.appendSymbolLayer(rail)
    
    
  15. 然后,我们将子符号添加到第二个符号层:

    symbol.appendSymbolLayer(symLyr2)
    
    
  16. 最后,我们将图层添加到地图上:

    QgsMapLayerRegistry.instance().addMapLayer(lyr)
    
    

它是如何工作的…

首先,我们必须创建一个简单的线符号。标记线本身将正确渲染,但底层的简单线将是一个随机选择的颜色。我们还必须更改标记线的子符号,因为默认的子符号是一个简单的圆圈。

使用图标作为矢量层符号

除了 QGIS 中可用的默认符号类型外,您还可以使用 TrueType 字体作为地图符号。TrueType 字体是可缩放矢量图形,可以用作点标记。在本例中,我们将创建此类符号。

准备工作

您可以从geospatialpython.googlecode.com/files/NYC_MUSEUMS_GEO.zip下载本例中使用的点形状文件。

将其提取到您的qgis_data目录中名为nyc的文件夹中。

如何操作...

我们将加载一个点形状文件作为图层,然后使用一个名为Webdings的免费字体中的字符G,这个字体可能已经存在于您的系统中,在每个点渲染建筑图标。为此,我们需要执行以下步骤:

  1. 首先,我们将定义点形状文件的路径:

    src = "/qgis_data/nyc/NYC_MUSEUMS_GEO.shp"
    
    
  2. 然后,我们将加载矢量层:

    lyr = QgsVectorLayer(src, "Museums", "ogr")
    
    
  3. 现在,我们将使用 Python 字典来定义字体属性:

    fontStyle = {}
    fontStyle['color'] = '#000000'
    fontStyle['font'] = 'Webdings'
    fontStyle['chr'] = 'G'
    fontStyle['size'] = '6'
    
    
  4. 现在,我们将创建一个字体符号层:

    symLyr1 = QgsFontMarkerSymbolLayerV2.create(fontStyle)
    
    
  5. 然后,我们将矢量层的默认符号层更改为我们的字体符号信息:

    lyr.rendererV2().symbols()[0].changeSymbolLayer(0, symLyr1)
    
    
  6. 最后,我们将图层添加到地图中:

    QgsMapLayerRegistry.instance().addMapLayer(lyr)
    
    

它是如何工作的...

字体标记符号层只是另一种标记层;然而,与 QGIS 内置字体相比,矢量字体的可能性范围更广。许多行业使用自定义字体作为标记来定义标准地图符号。

创建渐变向量层符号渲染器

毕业向量层符号渲染器是栅格颜色渐变的矢量等价物。您可以将要素分组到相似的范围内,并使用有限的颜色集来视觉上识别这些范围。在本例中,我们将使用多边形形状文件渲染渐变符号。

准备工作

您可以从geospatialpython.googlecode.com/files/MS_UrbanAnC10.zip下载包含一组城市区域多边形的形状文件。

将此文件提取到您的qgis_data目录中名为ms的目录中。

如何操作...

我们将使用渐变符号根据人口规模对每个城市区域进行分类,如下所示:

  1. 首先,我们导入QColor对象来构建我们的颜色范围。

    from PyQt4.QtGui import QColor
    
    
  2. 接下来,我们将加载我们的多边形形状文件作为矢量层:

    lyr = QgsVectorLayer("/qgis_data/ms/MS_UrbanAnC10.shp", "Urban Areas", "ogr")
    
    
  3. 现在,我们构建一些嵌套的 Python 元组,定义符号的渐变。元组中的每个项目包含一个范围标签、范围起始值、范围结束值和颜色名称,如下所示:

    population = (
    ("Village", 0.0, 3159.0, "cyan"), 
    ("Small town", 3160.0, 4388.0, "blue"),
    ("Town", 43889.0, 6105.0, "green"),
    ("City", 6106.0, 10481.0, "yellow"),
    ("Large City", 10482.0, 27165, "orange"),
    ("Metropolis", 27165.0, 1060061.0, "red"))
    
    
  4. 然后,我们创建一个 Python 列表来保存我们的 QGIS 渲染器对象:

    ranges = []
    
    
  5. 接下来,我们将遍历我们的范围列表,构建 QGIS 符号,并将它们添加到渲染器列表中:

    for label, lower, upper, color in population:
    sym = QgsSymbolV2.defaultSymbol(lyr.geometryType())
    sym.setColor(QColor(color))
    rng = QgsRendererRangeV2(lower, upper, sym, label)
    ranges.append(rng)
    
    
  6. 现在,引用包含形状文件属性中人口值的字段名称:

    field = "POP"
    
    
  7. 然后,我们创建渲染器:

    renderer = QgsGraduatedSymbolRendererV2(field, ranges)
    
    
  8. 我们将渲染器分配给图层:

    lyr.setRendererV2(renderer)
    
    
  9. 最后,我们将地图添加到图层:

    QgsMapLayerRegistry.instance().addMapLayer(lyr)
    
    

如何工作...

使用渐变符号为矢量图层的方法与栅格图层中的颜色渐变着色器非常相似。你可以通过扩展用于构建范围的 Python 元组来拥有你想要的任意多个范围。当然,你也可以通过首先以编程方式检查数据字段,然后以相等间隔或其他方案划分值来构建自己的算法。

创建分类矢量图层符号

分类矢量图层符号允许你为独特特征创建具有颜色和标签的不同类别。这种方法通常用于具有有限独特类型特征的集合。在这个食谱中,我们将矢量图层分类为三个不同的类别。

准备工作

对于这个食谱,我们将使用一个土地利用 shapefile,你可以从geospatialpython.googlecode.com/svn/landuse_shp.zip下载。

将其提取到你的qgis_data目录中名为hancock的目录。

如何做到这一点...

我们将加载矢量图层,创建三个土地利用类别,并将它们渲染为分类符号。为此,我们需要执行以下步骤:

  1. 首先,我们需要导入QColor对象以用于我们的分类颜色:

    from PyQt4.QtGui import QColor
    
    
  2. 然后,我们加载矢量图层:

    lyr = QgsVectorLayer("Users/joellawhead/qgis_data/hancock/landuse.shp", "Land Use", "ogr")
    
    
  3. 接下来,我们将使用 Python 字典创建我们的三个土地利用分类,其中字段值作为键,颜色名称和标签:

    landuse = {
     "0":("yellow", "Developed"),
     "1":("darkcyan", "Water"),
     "2":("green", "Land")}
    
    
  4. 现在,我们可以构建我们的分类渲染项:

    categories = []
    for terrain, (color, label) in landuse.items():
     sym = QgsSymbolV2.defaultSymbol(lyr.geometryType())
     sym.setColor(QColor(color))
     category = QgsRendererCategoryV2(terrain, sym, label)
     categories.append(category)
    
    
  5. 我们命名包含土地利用值的字段:

    field = "DN"
    
    
  6. 接下来,我们构建渲染器:

    renderer = QgsCategorizedSymbolRendererV2(field, categories)
    
    
  7. 我们将渲染器添加到图层:

    lyr.setRendererV2(renderer)
    
    
  8. 最后,我们将分类图层添加到地图:

    QgsMapLayerRegistry.instance().addMapLayer(lyr)
    
    

如何工作...

QGIS 中各种渲染器类型的配置只有细微差别。通过首先使用原生 Python 对象定义渲染器的属性来设置它们,可以使你的代码更容易阅读和管理。以下地图图像展示了本食谱中的分类符号:

如何工作...

创建地图书签

地图书签允许你在 QGIS 中保存地图上的位置,这样你可以快速跳转到需要重复查看的点,而无需手动平移和缩放地图。PyQGIS 不包含用于读取、写入和缩放到书签的 API 命令。幸运的是,QGIS 将书签存储在 SQLite 数据库中。Python 有一个内置的 SQLite 库,我们可以使用数据库 API 来操作书签。

准备工作

你可以从geospatialpython.googlecode.com/files/GIS_CensusTract.zip下载一个人口普查区多边形 shapefile,用于与这个食谱一起使用。

将其提取到你的qgis_data目录中。我们将创建一个使用此 shapefile 中感兴趣区域的书签,这样你可以手动加载书签以进行测试。

如何操作...

我们将访问 QGIS 配置变量以获取用户数据库的路径,该数据库存储书签。然后,我们将连接到这个数据库并执行一个插入书签的 SQL 查询。最后,我们将更改提交到数据库,如下所示:

  1. 首先,使用 QGIS 的Python 控制台,我们必须导入 Python 的内置 SQLite 库:

    import sqlite3
    
    
  2. 接下来,获取数据库的路径:

    dbPath = QgsApplication.qgisUserDbFilePath()
    
    
  3. 现在,我们连接到数据库:

    db = sqlite3.connect(dbPath)
    
    
  4. 然后,我们需要一个数据库游标来操作数据库:

    cursor = db.cursor()
    
    
  5. 现在,我们可以执行 SQL 查询,它是一个字符串。在查询的VALUES部分,我们将书签 ID 留为NULL但给它一个名称,然后我们将项目名称留为NULL并设置范围,如下所示:

    cursor.execute("""INSERT INTO tbl_bookmarks(
     bookmark_id, name, project_name,
     xmin, ymin, xmax, ymax, 
     projection_srid)
     VALUES(NULL, "BSL", NULL,
     -89.51715550010032,
     30.233838337125075,
     -89.27257255649518,
     30.381717490617945,
     4269)""")
    
    
  6. 然后,我们提交更改:

    db.commit()
    
    
  7. 为了测试地图书签,通过从您的文件系统拖放到 QGIS 中,将人口普查区层加载到地图上。

  8. 接下来,在 QGIS 中点击视图菜单并选择显示书签

  9. 然后,选择BSL 书签并点击ZoomTo按钮。

  10. 验证地图是否已捕捉到接近多边形的感兴趣区域,其 OBJECTID 从 4625 到 4627。

它是如何工作的...

即使 QGIS 没有提供高级 API,您几乎总是可以使用 Python 深入挖掘并访问您想要的信息。QGIS 建立在开源软件之上,因此程序中的任何部分都不是真正受限的。

导航到地图书签

地图书签在地图上存储重要位置,因此您可以快速找到它们。您可以使用 Python 的sqlite3库编程导航到书签,以便访问 QGIS 用户数据库中的书签数据库表,然后使用 PyQGIS 画布 API。

准备工作

我们将使用人口普查区层来测试书签导航。您可以从geospatialpython.googlecode.com/files/GIS_CensusTract.zip下载压缩的 shapefile。

在从 ZIP 文件中提取后,手动将此层加载到 QGIS 中。同时,请确保您已完成了之前的配方,创建地图书签。您将需要一个名为 BSL 的书签来定位此 shapefile 中的感兴趣区域。

如何操作...

我们将从 QGIS 用户数据库中检索一个书签,并将地图的范围设置为这个书签。为此,执行以下步骤:

  1. 首先,导入 Python 的sqlite3库:

    import sqlite3
    
    
  2. 接下来,从 QGIS 数据中获取用户数据库的位置:

    dbPath = QgsApplication.qgisUserDbFilePath()
    
    
  3. 现在,我们连接到数据库:

    db = sqlite3.connect(dbPath)
    
    
  4. 然后,我们需要一个数据库游标来运行查询:

    cursor = db.cursor()
    
    
  5. 现在,我们可以获取名为BSL的书签信息:

    cursor.execute("""SELECT * FROM tbl_bookmarks WHERE name='BSL'""")
    
    
  6. 现在,我们将从查询中获取完整的结果:

    row = cursor.fetchone()
    
    
  7. 然后,我们将结果值拆分为多个变量:

    id,mark_name,project,xmin,ymin,xmax,ymax,srid = row
    
    
  8. 现在,我们可以使用书签来创建一个 QGIS 范围矩形:

    rect = QgsRectangle(xmin, ymin, xmax, ymax)
    
    
  9. 接下来,我们引用地图画布:

    canvas = qgis.utils.iface.mapCanvas()
    
    
  10. 最后,我们将画布的范围设置为矩形并刷新画布:

    canvas.setExtent(rect)
    canvas.refresh()
    
    

它是如何工作的...

使用 SQLite 读取和写入书签非常简单,尽管它不是 PyQGIS 主 API 的一部分。请注意,书签有一个用于项目名称的占位符,您可以使用它根据需要按项目过滤书签。

为图层设置基于比例的可见性

有时,GIS 图层只有在特定比例下显示时才有意义,例如,复杂的道路网络。PyQGIS 支持基于比例的可见性,可以编程设置图层显示的比例范围。在本菜谱中,我们将研究基于比例的图层。

准备工作

您需要从geospatialpython.googlecode.com/files/GIS_CensusTract.zip提供的 ZIP 文件中获取样本人口普查区形状文件。

将压缩层提取到您的qgis_data目录下名为census的目录中。

如何做到...

我们将加载矢量层,切换基于比例的可见性,设置可见范围,然后将图层添加到地图中。为此,请执行以下步骤:

  1. 首先,我们加载图层:

    lyr = QgsVectorLayer("/Users/joellawhead/qgis_data/census/GIS_CensusTract_poly.shp", "Census", "ogr")
    
    
  2. 接下来,我们切换基于比例的可见性:

    lyr.toggleScaleBasedVisibility(True)
    
    
  3. 然后,我们设置图层可见的最小和最大地图比例:

    lyr.setMinimumScale(22945.0)
    lyr.setMaximumScale(1000000.0)
    
    
  4. 现在,我们将图层添加到地图中:

    QgsMapLayerRegistry.instance().addMapLayer(lyr)
    
    
  5. 最后,手动放大和缩小地图以确保图层在适当的比例下消失和重新出现。

它是如何工作的...

地图比例是地图单位与物理地图大小的比率,以浮点数表示。您必须记住切换基于比例的可见性,以便 QGIS 知道每次地图比例变化时都需要检查范围。

使用 SVG 作为图层符号

可缩放矢量图形SVG)是一个 XML 标准,它定义了可以按任何分辨率缩放的矢量图形。QGIS 可以使用 SVG 文件作为点的标记。在本菜谱中,我们将使用 Python 将 QGIS 附带的一个 SVG 符号应用到点图层上。

准备工作

对于这个菜谱,从geospatialpython.googlecode.com/files/NYC_MUSEUMS_GEO.zip下载以下压缩的点形状文件图层。

将其提取到您的qgis_data目录中。

如何做到...

在以下步骤中,我们将加载矢量层,构建符号层和渲染器,并将其添加到图层中,如下所示:

  1. 首先,我们将定义形状文件的路径:

    src = "/Users/joellawhead/qgis_data/NYC_MUSEUMS_GEO/NYC_MUSEUMS_GEO.shp"
    
    
  2. 接下来,我们将加载图层:

    lyr = QgsVectorLayer(src, "Museums", "ogr")
    
    
  3. 现在,我们定义符号的属性,包括将 SVG 文件位置作为 Python 字典:

    svgStyle = {}
    svgStyle['fill'] = '#0000ff'
    svgStyle['name'] = 'landmark/tourism=museum.svg'
    svgStyle['outline'] = '#000000'
    svgStyle['outline-width'] = '6.8'
    svgStyle['size'] = '6'
    
    
  4. 然后,我们创建一个 SVG 符号层:

    symLyr1 = QgsSvgMarkerSymbolLayerV2.create(svgStyle)
    
    
  5. 现在,我们更改图层渲染器的默认符号层:

    lyr.rendererV2().symbols()[0].changeSymbolLayer(0, symLyr1)
    
    
  6. 最后,我们将图层添加到地图中以查看 SVG 符号:

    QgsMapLayerRegistry.instance().addMapLayer(lyr)
    
    

它是如何工作的...

默认 SVG 图层存储在 QGIS 应用程序目录中。有许多图形可用于许多常见用途。您也可以添加自己的图形。以下地图图像显示了菜谱的输出:

如何工作...

使用饼图作为符号

QGIS 具有使用动态饼图作为符号描述给定区域统计信息的能力。在这个配方中,我们将使用 QGIS 中的多边形图层上的饼图符号。

准备工作

对于这个配方,请从geospatialpython.googlecode.com/svn/County10PopnHou.zip下载以下压缩的 shapefile 并将其提取到qgis_data目录中名为ms的目录中。

如何做...

与其他渲染器一样,我们将构建一个符号图层,将其添加到渲染器中,并在地图上显示该图层。饼图图示渲染器比其他渲染器更复杂,但具有更多选项。按照以下步骤创建饼图地图:

  1. 首先,我们导入 PyQt GUI 库:

    from PyQt4.QtGui import *
    
    
  2. 然后,我们加载图层:

    lyr = QgsVectorLayer("/Users/joellawhead/qgis_data/ms/County10PopnHou.shp", "Population", "ogr")
    
    
  3. 接下来,我们根据属性名称设置类别:

    categories = [u'PCT_WHT', u'PCT_BLK', u'PCT_AMIND', u'PCT_ASIAN', u'PCT_HAW', u'PCT_ORA', u'PCT_MR', u'PCT_HISP']
    
    
  4. 现在,我们为每个类别设置一个相应的颜色列表:

    colors = ['#3727fa','#01daae','#f849a6','#268605','#6810ff','#453990','#630f2f','#07dd45']
    
    
  5. 接下来,我们将十六进制颜色值转换为QColor对象:

    qcolors = []
    for c in colors:
     qcolors.append(QColor(c))
    
    
  6. 现在,我们引用地图画布:

    canvas = iface.mapCanvas()
    
    
  7. 然后,我们创建一个饼图对象:

    diagram = QgsPieDiagram()
    
    
  8. 然后,我们创建一个图示设置对象:

    ds = QgsDiagramSettings()
    
    
  9. 现在,我们定义所有将用于渲染器的图示设置:

    ds.font = QFont("Helvetica", 12)
    ds.transparency = 0
    ds.categoryColors = qcolors
    ds.categoryAttributes = categories
    ds.size = QSizeF(100.0, 100.0)
    ds.sizeType = 0 
    ds.labelPlacementMethod = 1 
    ds.scaleByArea = True 
    ds.minimumSize = 0 
    ds.BackgroundColor = QColor(255,255,255,0)
    ds.PenColor = QColor("black") 
    ds.penWidth = 0
    
    
  10. 现在,我们可以创建我们的图示渲染器:

    dr = QgsLinearlyInterpolatedDiagramRenderer()
    
    
  11. 我们必须为我们的图示设置一些尺寸参数:

    dr.setLowerValue(0.0)
    dr.setLowerSize(QSizeF(0.0, 0.0))
    dr.setUpperValue(2000000)
    dr.setUpperSize(QSizeF(40,40))
    dr.setClassificationAttribute(6)
    
    
  12. 然后,我们可以将我们的图示添加到渲染器中:

    dr.setDiagram(diagram)
    
    
  13. 接下来,我们将渲染器添加到图层中:

    lyr.setDiagramRenderer(dr)
    
    
  14. 现在,我们在图层级别应用一些额外的放置设置:

    dls = QgsDiagramLayerSettings() 
    dls.dist = 0
    dls.priority = 0
    dls.xPosColumn = -1 
    dls.yPosColumn = -1
    dls.placement = 0 
    lyr.setDiagramLayerSettings(dls)
    
    
  15. 在 QGIS 2.6 中,图示渲染器与新的 PAL 标签引擎绑定,因此我们需要激活此引擎:

    label = QgsPalLayerSettings() 
    label.readFromLayer(lyr) 
    label.enabled = True 
    label.writeToLayer(lyr)
    
    
  16. 接下来,我们删除任何已渲染的缓存图像并强制图层重新绘制:

    if hasattr(lyr, "setCacheImage"):
     lyr.setCacheImage(None)
    
    lyr.triggerRepaint()
    
    
  17. 最后,我们将我们的图示图层添加到地图中:

    QgsMapLayerRegistry.instance().addMapLayer(lyr)
    
    

如何工作...

饼图图示的基本符号简单易懂,其工作方式与其他类型的符号和渲染器类似。然而,当我们需要在三个不同级别应用设置时——图示级别、渲染级别和图层级别——事情就会变得有些复杂。实际上,它们相当复杂。大多数设置都没有很好地记录,如果有的话。幸运的是,大多数设置都是自我解释的。以下截图显示了完成的饼图图示地图示例:

如何工作...

还有更多...

要了解饼图图示符号的更多可能性,您可以在 Script Runner 插件中尝试此配方,在那里您可以更改或删除设置,并快速重新渲染地图。您还可以使用 QGIS 对话框手动更改设置,然后将样式导出为 XML 文件,查看使用了哪些设置。它们中的大多数都很好地映射到 Python API。

使用 OpenStreetMap 服务

云技术正越来越多地将数据移动到互联网上,GIS 也不例外。QGIS 可以使用 Open GIS Consortium 标准加载基于 Web 的数据,例如Web Map Service (WMS)。添加 WMS 图层最简单的方法是使用地理空间数据抽象库 (GDAL)及其虚拟文件系统功能来加载瓦片图层。

准备工作

除了在 QGIS 中打开 Python 控制台插件之外,您不需要为此菜谱做任何准备。

如何操作...

我们将创建一个描述我们想要导入的 OpenStreetMap 瓦片网络服务的 XML 模板。然后,我们将将其转换为 GDAL 虚拟文件,并在 QGIS 中将其加载为栅格图层。为此,我们需要执行以下步骤:

  1. 首先,我们导入 GDAL 库:

    from osgeo import gdal
    
    
  2. 接下来,我们将创建我们的 XML 模板,描述我们想要导入的 OpenStreetMap 瓦片网络服务:

    xml = """<GDAL_WMS>
    <Service name="TMS">
    <ServerUrl>http://tile.openstreetmap.org/${z}/${x}/${y}.png</ServerUrl>
    </Service>
    <DataWindow>
    <UpperLeftX>-20037508.34</UpperLeftX>
    <UpperLeftY>20037508.34</UpperLeftY>
    <LowerRightX>20037508.34</LowerRightX>
    <LowerRightY>-20037508.34</LowerRightY>
    <TileLevel>18</TileLevel>
    <TileCountX>1</TileCountX>
    <TileCountY>1</TileCountY>
    <YOrigin>top</YOrigin>
    </DataWindow>
    <Projection>EPSG:900913</Projection>
    <BlockSizeX>256</BlockSizeX>
    <BlockSizeY>256</BlockSizeY>
    <BandsCount>3</BandsCount>
    <Cache />
    </GDAL_WMS>"""
    
  3. 现在,我们将为我们的 GDAL 虚拟文件系统文件创建路径:

    vfn = "/vsimem/osm.xml"
    
    
  4. 接下来,我们使用 GDAL 通过路径和 XML 文档创建虚拟文件:

    gdal.FileFromMemBuffer(vfn, xml)
    
    
  5. 现在,我们可以从虚拟文件创建一个栅格图层:

    rasterLyr = QgsRasterLayer(vfn, "OSM")
    
    
  6. 在我们将图层添加到地图之前,我们将确保其有效性:

    rasterLyr.isValid()
    
    
  7. 最后,将图层添加到地图中:

    QgsMapLayerRegistry.instance().addMapLayers([rasterLyr])
    
    

它是如何工作的...

虽然有其他方法可以将如 OpenStreetMap 之类的瓦片地图服务以编程方式加载到 QGIS 中,但 GDAL 是最稳健的方法。前缀 vsimem 告诉 GDAL 使用虚拟文件来管理瓦片。这种方法让您免于直接在磁盘上管理下载的瓦片,并让您专注于应用程序的功能。

使用 Bing 空中影像服务

虽然有许多服务提供街道地图瓦片,但提供影像服务的服务却少得多。对于地图和更重要的是影像,Microsoft 的 Bing 地图服务是一个出色的免费服务。我们可以使用 GDAL 的 WMS 功能结合虚拟文件在 QGIS 中以编程方式访问 Bing 影像。

准备工作

除了在 QGIS 中打开 Python 控制台插件之外,您不需要为此菜谱做任何准备。

如何操作...

与之前用于 使用 OpenStreetMap 服务 的方法类似,我们将创建一个字符串形式的 XML 文件来描述服务,将其转换为 GDAL 虚拟文件,并在 QGIS 中将其加载为栅格。为此,我们需要执行以下步骤:

  1. 首先,我们导入 GDAL 库:

    from osgeo import gdal
    
    
  2. 接下来,我们创建 XML 文件,将 Bing 服务描述为一个字符串:

    xml = """<GDAL_WMS>
      <Service name="VirtualEarth">
        <ServerUrl>
          http://a${server_num}.ortho.tiles.virtualearth.net/tiles/a${quadkey}.jpeg?g=90
        </ServerUrl>
      </Service>
      <MaxConnections>4</MaxConnections>
      <Cache/>
    </GDAL_WMS>"""
    
  3. 现在,我们为 XML 文件创建虚拟文件路径:

    vfn = "/vsimem/bing.xml"
    
    
  4. 然后,我们将 XML 文件转换为 GDAL 虚拟文件:

    gdal.FileFromMemBuffer(vfn, xml)
    
    
  5. 现在,我们可以将文件作为 QGIS 栅格图层添加,并检查其有效性:

    rasterLyr = QgsRasterLayer(vfn, "BING")
    rasterLyr.isValid()
    
    
  6. 最后,我们将图层添加到地图中:

    QgsMapLayerRegistry.instance().addMapLayers([rasterLyr])
    
    

它是如何工作的...

GDAL 为各种地图服务提供了驱动程序。Bing 的服务名称为 VirtualEarth。服务器 URL 中的 ${} 子句提供了占位符,当 GDAL 下载样式时,这些占位符将被实例特定的数据替换。在使用这些数据时,您应该意识到它们有版权限制。请务必在线阅读 Bing 使用协议。

添加来自 OpenWeatherMap 的实时天气数据

实时数据是您可以添加到现代地图中最激动人心的数据类型之一。大多数数据生产者通过 Open GIS Consortium 标准提供数据。一个这样的例子是 OpenWeatherMap,它提供不同实时天气数据层的 OGC Web Map Service (WMS)。在本食谱中,我们将访问此服务以访问实时天气数据层。

准备工作

为了准备这个食谱,您只需通过点击 插件 菜单并选择 Python 控制台 来打开 QGIS Python 控制台

如何操作...

我们将按照以下方式将降水 WMS 数据层添加到 QGIS 地图中:

  1. 首先,我们指定 service 的参数:

    service = 'crs=EPSG:900913&dpiMode=7&featureCount=10&format=image/png&layers=precipitation&styles=&url=http://wms.openweathermap.org/service'
    
    
  2. 接下来,我们创建栅格图层,指定 wms 作为类型:

    rlayer = QgsRasterLayer(service, "precip", "wms")
    
    
  3. 最后,我们将降水层添加到地图中:

    QgsMapLayerRegistry.instance().addMapLayers([rlayer])
    
    

工作原理...

WMS 请求通常是一个带有所有参数作为 URL 部分的 HTTP GET 请求。在 PyQGIS 中,您使用 URL 编码格式,并从 URL 中单独指定参数。

以下地图图像显示了 QGIS 中降水层的输出:

工作原理...

标记要素

一旦您的地图图层被样式化,创建完整地图的下一步就是标记要素。在本食谱中,我们将探讨标记的基础知识。

准备工作

geospatialpython.googlecode.com/files/MSCities_Geo_Pts.zip 下载以下压缩的 shapefile。

将 shapefile 提取到名为 ms 的目录中,该目录位于您的 qgis_data shapefile 目录下。

如何操作...

我们将加载点 shapefile 图层,创建一个标签对象,设置其属性,将其应用于图层,然后将图层添加到地图中。为此,我们需要执行以下步骤:

  1. 首先,为了节省空间,我们将指定 shapefile 的路径:

    src = "/Users/joellawhead/qgis_data/ms/MSCities_Geo_Pts.shp"
    
    
  2. 接下来,我们将加载图层:

    lyr = QgsVectorLayer(src, "Museums", "ogr")
    
    
  3. 然后,我们将创建标记对象:

    label = QgsPalLayerSettings()
    
    
  4. 现在,我们将配置标签,从正在读取的当前图层设置开始:

    label.readFromLayer(lyr)
    label.enabled = True
    
    
  5. 然后,我们指定标签数据的属性:

    label.fieldName = 'NAME10' 
    
    
  6. 然后,我们可以设置放置和大小选项:

    label.placement= QgsPalLayerSettings.AroundPoint
    label.setDataDefinedProperty(QgsPalLayerSettings.Size,True,True,'8','')
    
    
  7. 接下来,我们将更改提交到图层:

    label.writeToLayer(lyr)
    
    
  8. 最后,我们可以将图层添加到地图中以查看标签:

    QgsMapLayerRegistry.instance().addMapLayers([lyr])
    
    

工作原理...

标记的一个有趣部分是往返读取和写入过程,用于访问图层数据和分配标记属性。标记可能相当复杂,但本食谱涵盖了开始所需的基本知识。

改变地图图层透明度

地图图层透明度允许您更改图层的透明度,以便在一定程度上可以看到其后的项目。一种常见的技术是将矢量图层多边形部分透明,以便允许底层图像或高程数据为数据添加纹理。

准备工作

在名为 ms 的目录中,在您的 qgis_data 目录下,从以下位置下载并提取以下 shapefile:

geospatialpython.googlecode.com/files/Mississippi.zip

如何操作...

这个过程极其简单。透明度只是一个方法:

  1. 首先,我们加载 shapefile 图层:

    lyr = QgsVectorLayer("/Users/joellawhead/qgis_data/ms/mississippi.shp", "Mississippi", "ogr")
    
    
  2. 接下来,我们将图层的透明度设置为 50 百分比:

    lyr.setLayerTransparency(50)
    
    
  3. 最后,我们将此图层添加到地图上:

    QgsMapLayerRegistry.instance().addMapLayer(lyr)
    
    

它是如何工作的...

如果您将透明度设置为 100%,则图层完全不透明。如果设置为 0,则图层完全不可见。

将标准地图工具添加到画布

在这个菜谱中,您将学习如何将标准地图导航工具添加到独立的地图画布。创建最简单的交互式应用程序提供了一个框架,以开始使用 QGIS 作为库来构建专业地理空间应用程序。

准备工作

下载以下压缩的 shapefile 并将其提取到您的 qgis_data 目录中的 ms 文件夹中,从 geospatialpython.googlecode.com/files/Mississippi.zip

如何操作...

我们将逐步介绍创建地图画布、向其添加图层以及添加缩放和平移地图的工具所需的步骤,如下所示:

  1. 首先,因为我们是在 QGIS Python 解释器外部工作,所以我们需要导入一些 QGIS 和 Qt 库:

    from qgis.gui import *
    from qgis.core import *
    from PyQt4.QtGui import *
    from PyQt4.QtCore import SIGNAL, Qt
    import sys, os
    
    
  2. 然后,我们必须设置主 QGIS 应用程序目录的位置。此设置取决于平台:

    # OSX:
    QgsApplication.setPrefixPath("/Applications/QGIS.app/Contents/MacOS/", True)
    # Windows:
    # app.setPrefixPath("C:/Program Files/QGIS Valmiera/apps/qgis", True)
    
    
  3. 接下来,我们开始初始化类:

    class MyWnd(QMainWindow):
     def __init__(self):
    
    
  4. 现在,我们可以初始化应用程序并创建地图画布:

    QMainWindow.__init__(self)
    QgsApplication.setPrefixPath("/Applications/QGIS.app/Contents/MacOS/", True)
    QgsApplication.initQgis()
    self.canvas = QgsMapCanvas()
    self.canvas.setCanvasColor(Qt.white)
    
    
  5. 然后,我们可以加载 shapefile 图层并将其添加到画布上:

    self.lyr = QgsVectorLayer("/Users/joellawhead/qgis_data/ms/mississippi.shp", "Mississippi", "ogr")
    QgsMapLayerRegistry.instance().addMapLayer(self.lyr)
    self.canvas.setExtent(self.lyr.extent())
    self.canvas.setLayerSet([QgsMapCanvasLayer(self.lyr)])
    self.setCentralWidget(self.canvas)
    
    
  6. 接下来,我们定义将在工具栏上可见的按钮:

    actionZoomIn = QAction("Zoom in", self)
    actionZoomOut = QAction("Zoom out", self)
    actionPan = QAction("Pan", self)
    actionZoomIn.setCheckable(True)
    actionZoomOut.setCheckable(True)
    actionPan.setCheckable(True)
    
    
  7. 现在,我们将按钮点击时创建的信号连接到提供每个工具功能的 Python 方法:

    actionZoomIn.triggered.connect(self.zoomIn)
    actionZoomOut.triggered.connect(self.zoomOut)
    actionPan.triggered.connect(self.pan)
    
    
  8. 接下来,我们创建工具栏并添加按钮:

    self.toolbar = self.addToolBar("Canvas actions")
    (actionZoomIn)
    self.toolbar.addAction(actionZoomOut)
    self.toolbar.addAction(actionPan)
    
    
  9. 然后,我们将按钮连接到应用程序的状态:

    self.toolPan = QgsMapToolPan(self.canvas)
    self.toolPan.setAction(actionPan)
    self.toolZoomIn = QgsMapToolZoom(self.canvas, False) # false = in
    self.toolZoomIn.setAction(actionZoomIn)
    self.toolZoomOut = QgsMapToolZoom(self.canvas, True) # true = out
    self.toolZoomOut.setAction(actionZoomOut)
    
    
  10. 然后,我们定义当应用程序加载时将选择的按钮:

    self.pan()
    
    
  11. 现在,我们定义控制应用程序行为的每个工具的 Python 方法:

    defzoomIn(self):
    self.canvas.setMapTool(self.toolZoomIn)
    defzoomOut(self):
    self.canvas.setMapTool(self.toolZoomOut)
    def pan(self):
    self.canvas.setMapTool(self.toolPan)
    
    
  12. 然后,我们创建一个使用我们的应用程序窗口类的 Qt 应用程序:

    class MainApp(QApplication):
    def __init__(self):
    QApplication.__init__(self,[],True)
    wdg = MyWnd()
    wdg.show()
    self.exec_()
    
    
  13. 最后,我们进入程序的主循环:

    if __name__ == "__main__":
    import sys
    app = MainApp()
    
    

它是如何工作的...

应用程序是一个持续运行的程序循环,只有当我们退出应用程序时才会结束。QGIS 基于 Qt 窗口库,因此我们的应用程序类继承自提供画布以及创建工具栏和对话框的主要窗口类。即使是对于极其简单的应用程序,这也有很多设置,但一旦应用程序的框架完成,扩展它就变得容易得多。

使用地图工具在画布上绘制点

QGIS 包含了在自定义应用程序中缩放和平移地图的内置功能。它还包含了构建您自己的交互式工具的基本钩子。在这个菜谱中,我们将创建一个交互式点工具,允许您通过点击点在地图上标记位置。

准备工作

我们将使用来自上一节“将标准地图工具添加到画布”的应用程序框架,因此首先完成该食谱。我们将通过添加新工具来扩展该应用程序。该应用程序的完整版本可在本书提供的代码示例中找到。

如何操作...

我们将设置按钮、信号触发和动作,就像我们处理所有地图工具一样。然而,因为我们正在构建一个新工具,我们必须定义一个类来精确地定义工具的功能。为此,我们需要执行以下操作:

  1. 首先,我们在应用程序的动作部分定义我们的点工具按钮。将此行放置在QAction("Pan")方法之后:

    actionPoint = QAction("Point", self)
    
    
  2. 在下一节中,我们确保点击按钮后它保持选中状态:

    actionPoint.setCheckable(True)
    
    
  3. 在下一节中,我们定义当按钮被触发时使用的方法:

    self.connect(actionPoint, SIGNAL("triggered()"), self.point)
    
    
  4. 现在,我们将按钮添加到工具栏中,与其他按钮一起:

    self.toolbar.addAction(actionPoint)
    
    
  5. 然后,我们将应用程序链接到我们的专用工具类:

    self.toolPoint = PointMapTool(self.canvas)
    self.toolPoint.setAction(actionPoint)
    
    
  6. 我们设置在应用程序加载时选择点工具:

    self.point()
    
    
  7. 现在,我们在主应用程序类中定义我们的工具方法:

    def point(self):
    self.canvas.setMapTool(self.toolPoint)
    
    
  8. 现在,我们创建一个类来描述我们拥有的工具类型及其提供的输出。输出是在canvasPressEvent方法中定义的画布上的一个点,它接收按钮点击事件。我们将从名为QgsMapToolEmitPoint的通用工具继承,以创建点:

    classPointMapTool(QgsMapToolEmitPoint):
    def __init__(self, canvas):
    self.canvas = canvas
    QgsMapToolEmitPoint.__init__(self, self.canvas)
    self.point = None
    
    defcanvasPressEvent(self, e):
    self.point = self.toMapCoordinates(e.pos())
    printself.point.x(), self.point.y()
    m = QgsVertexMarker(self.canvas)
    m.setCenter(self.point)
    m.setColor(QColor(0,255,0))
    m.setIconSize(5)
    m.setIconType(QgsVertexMarker.ICON_BOX) # or ICON_CROSS, ICON_X
    m.setPenWidth(3)
    
    

它是如何工作的...

对于自定义工具,PyQGIS 提供了一套通用工具,用于执行您可以扩展和组合的常见功能。在这种情况下,EmitPoint 工具处理您在地图上点击位置时事件和地图功能的相关细节。

使用地图工具在画布上绘制多边形或线条

在本食谱中,我们将创建一个在画布上绘制多边形的工具。这个工具是一个重要的工具,因为它打开了通往更多高级工具的大门。一旦你在画布上有一个多边形,你就可以执行所有涉及查询和几何的各种操作。

准备工作

我们将使用来自“将标准地图工具添加到画布”的Adding standard map tools to the canvas食谱的应用程序框架,因此首先完成该食谱。我们将通过添加新工具来扩展该应用程序。该应用程序的完整版本可在本书提供的代码示例中找到。

如何操作...

我们将添加一个新工具到工具栏,并创建一个描述我们的多边形工具的类,如下所示:

  1. 首先,我们在应用程序的动作部分定义我们的多边形工具按钮。将此行放置在QAction("Pan")方法之后:

    actionPoly = QAction("Polygon", self)
    
    
  2. 在下一节中,我们确保点击按钮后它保持选中状态:

    actionPoly.setCheckable(True)
    
    
  3. 在下一节中,我们定义当按钮被触发时使用的方法:

    self.connect(actionPoly, SIGNAL("triggered()"), self.poly)
    
    
  4. 现在,我们将按钮添加到工具栏中,与其他按钮一起:

    self.toolbar.addAction(actionPoly)
    
    
  5. 然后,我们将应用程序链接到我们的专用工具类:

    self.toolPoly = PolyMapTool(self.canvas)
    self.toolPoly.setAction(actionPoly)
    
    
  6. 我们设置在应用程序加载时选择点工具:

    self.poly()
    
    
  7. 现在,我们在主应用程序类中定义我们的工具方法:

    def poly(self):
    self.canvas.setMapTool(self.toolPoly)
    
    

现在,我们创建一个类来描述我们拥有的工具类型以及它提供的输出。输出是在canvasPressEvent方法中定义的画布上的一个点,该方法接收按钮点击事件和showPoly方法。我们将从通用的工具中继承以创建名为QgsMapToolEmitPoint的点;我们还将使用名为QgsRubberBand的对象来处理多边形:

classPolyMapTool(QgsMapToolEmitPoint):
def __init__(self, canvas):
self.canvas = canvas
QgsMapToolEmitPoint.__init__(self, self.canvas)
self.rubberband = QgsRubberBand(self.canvas, QGis.Polygon)
self.rubberband.setColor(Qt.red)
self.rubberband.setWidth(1)
self.point = None
self.points = []

defcanvasPressEvent(self, e):
self.point = self.toMapCoordinates(e.pos())
m = QgsVertexMarker(self.canvas)
m.setCenter(self.point)
m.setColor(QColor(0,255,0))
m.setIconSize(5)
m.setIconType(QgsVertexMarker.ICON_BOX)
m.setPenWidth(3) 
self.points.append(self.point)
self.isEmittingPoint = True
self.showPoly()

defshowPoly(self):
self.rubberband.reset(QGis.Polygon)
for point in self.points[:-1]:
self.rubberband.addPoint(point, False)
self.rubberband.addPoint(self.points[-1], True)
self.rubberband.show()

如何工作...

多边形的所有设置都包含在自定义类中。有一个关键属性,称为EmittingPoint,我们用它来检测我们是否仍在向多边形添加点。这个值最初是false。如果是这种情况,我们将重置我们的多边形对象并开始绘制一个新的。以下截图显示了使用此工具在地图上绘制的多边形:

如何工作...

构建自定义选择工具

在这个菜谱中,我们将构建一个自定义工具,该工具可以在地图上绘制形状并与地图上的其他功能交互。这两个基本功能是几乎所有你想要构建的地图工具的基础,无论是像这样一个独立的 QGIS 应用程序,还是通过插件扩展 QGIS 桌面应用程序。

准备工作

我们将使用来自将标准地图工具添加到画布菜谱的应用程序框架,所以首先完成那个菜谱。我们将通过添加新工具来扩展该应用程序。该应用程序的完整版本可在本书提供的代码示例中找到。研究其他两个与工具相关的菜谱,在画布上绘制多边形或线条的工具在画布上绘制点的工具也将大有裨益,因为这个菜谱也是基于它们的。

你还需要以下来自geospatialpython.googlecode.com/files/NYC_MUSEUMS_GEO.zip的压缩 shapefile。

下载并提取到你的qgis_data目录中。

如何做...

我们将在工具栏中添加一个新工具,并创建一个描述我们的选择工具的类,包括如何绘制选择多边形以及如何选择功能。为此,我们需要执行以下步骤:

  1. 首先,我们在应用程序的动作部分定义我们的多边形工具的按钮。将此行放置在QAction("Pan")方法之后:

    actionSelect = QAction("Select", self)
    
    
  2. 在下一节中,我们确保当我们点击按钮时,它保持选中状态:

    actionSelect.setCheckable(True)
    
    
  3. 在接下来的部分中,我们定义当按钮被触发时使用的方法:

    self.connect(actionSelect, SIGNAL("triggered()"), self.select)
    
    
  4. 现在,我们在工具栏中添加按钮以及其他按钮:

    self.toolbar.addAction(actionSelect)
    
    
  5. 然后,我们将应用程序链接到我们的专用工具类:

    self.toolSelect = SelectMapTool(self.canvas, self.lyr)
    self.toolSelect.setAction(actionSelect)
    
    
  6. 我们设置点工具在应用程序加载时被选中:

    self.select()
    
    
  7. 现在,我们在主应用程序类中为我们的工具定义方法:

    def select(self):
    self.canvas.setMapTool(self.toolSelect)
    
    
  8. 接下来,我们创建一个类来描述我们工具的类型及其工作方式。输出是在canvasPressEvent方法中定义的画布上的一个点,该方法接收按钮点击事件和selectPoly方法。我们将从名为QgsMapToolEmitPoint的通用工具继承以创建点;我们还将使用名为QgsRubberBand的对象来处理多边形。然而,我们还必须执行选取过程以突出显示落在我们的选取多边形内的特征:

    classSelectMapTool(QgsMapToolEmitPoint):
    def __init__(self, canvas, lyr):
    self.canvas = canvas
    self.lyr = lyr
    QgsMapToolEmitPoint.__init__(self, self.canvas)
    self.rubberband = QgsRubberBand(self.canvas, QGis.Polygon)
    self.rubberband.setColor(QColor(255,255,0,50))
    self.rubberband.setWidth(1)
    self.point = None
    self.points = []
    
    defcanvasPressEvent(self, e):
    self.point = self.toMapCoordinates(e.pos())
    m = QgsVertexMarker(self.canvas)
    m.setCenter(self.point)
    m.setColor(QColor(0,255,0))
    m.setIconSize(5)
    m.setIconType(QgsVertexMarker.ICON_BOX)
    m.setPenWidth(3) 
    self.points.append(self.point)
    self.isEmittingPoint = True
    self.selectPoly()
    
    defselectPoly(self):
    self.rubberband.reset(QGis.Polygon)
    for point in self.points[:-1]:
    self.rubberband.addPoint(point, False)
    self.rubberband.addPoint(self.points[-1], True)
    self.rubberband.show() 
    iflen(self.points) > 2:
    g = self.rubberband.asGeometry()
    featsPnt = self.lyr.getFeatures(QgsFeatureRequest().setFilterRect(g.boundingBox()))
    forfeatPnt in featsPnt:
    iffeatPnt.geometry().within(g):
    self.lyr.select(featPnt.id()) 
    
    

它是如何工作的...

QGIS 有一个用于突出显示特征的通用工具,但在这个案例中,我们可以使用标准的选取功能,这简化了我们的代码。除了加载新图层对话框和显示属性的能力之外,我们有一个非常基础但几乎完整的独立 GIS 应用程序。以下截图显示了选取工具的效果:

它是如何工作的...

创建鼠标坐标跟踪工具

在这个菜谱中,我们将构建一个实时跟踪和显示鼠标坐标的工具。此工具还将演示如何与 QGIS 应用程序的状态栏交互。

准备工作

我们将使用来自将标准地图工具添加到画布菜谱的应用程序框架,因此首先完成该菜谱。我们将使用坐标跟踪工具扩展该应用程序。此书的代码示例中提供了一个此应用程序的完整版本。研究本章中的其他两个与工具相关的菜谱,在画布上绘制多边形或线条的工具在画布上绘制点的工具也将有益,因为此菜谱也基于它们。

如何做...

我们将向基本独立 QGIS 应用程序添加一个事件过滤器,并使用它来获取当前鼠标坐标以及更新状态栏。为此,我们需要执行以下步骤:

  1. 作为我们应用程序__init__方法的最后一行,插入以下行以在应用程序加载时创建默认状态栏消息:

    self.statusBar().showMessage(u"x: --, y: --")
    
    
  2. 在应用程序的__init__方法之后立即,我们将添加以下事件过滤器方法:

    defeventFilter(self, source, event):
    ifevent.type() == QEvent.MouseMove:
    ifevent.buttons() == Qt.NoButton:
    pos = event.pos()
    x = pos.x()
    y = pos.y()
    p = self.canvas.getCoordinateTransform().toMapCoordinates(x, y)
    self.statusBar().showMessage(u"x: %s, y: %s" % (p.x(), p.y()))
    else:
    pass
    returnQMainWindow.eventFilter(self, source, event)
    
    
  3. MainApp类中,作为倒数第二行,我们必须使用以下代码安装事件过滤器:

    self.installEventFilter(wdg)
    
    

它是如何工作的...

在 Qt 框架中,为了监视鼠标事件,我们必须插入一个事件过滤器,允许我们监视应用程序中的所有事件。在默认事件过滤器方法中,然后我们可以处理我们想要的任何事件。在这种情况下,我们监视鼠标的任何移动。

第六章。创建静态地图

在本章中,我们将介绍以下食谱:

  • 创建最简单的地图渲染器

  • 使用地图编者

  • 为打印地图添加标签

  • 为地图添加比例尺

  • 为地图添加指向北方的箭头

  • 为地图添加徽标

  • 为地图添加图例

  • 为地图添加自定义形状

  • 为地图添加网格

  • 为地图添加表格

  • 将地图保存为 PNG 图像

  • 为地图图像添加世界文件

  • 将地图保存到项目中

  • 从项目中加载地图

简介

在本章中,我们将使用 PyQGIS、Qt 图像对象和 QGIS 地图编者创建地图布局,这些布局可以导出为文档或图像。QGIS 地图编者旨在创建具有装饰性和参考元素的静态地图布局,用于打印或包含在其他文档中。

创建最简单的地图渲染器

为了将动态 GIS 地图转换为静态地图图像或文档,您必须创建一个渲染器来冻结地图视图并创建其图形版本。在本食谱中,我们将渲染地图到 JPEG 图像并保存它。

准备工作

您需要下载以下压缩的 shapefile 并将其解压缩到您的qgis_data目录下的hancock子目录中:

geospatialpython.googlecode.com/svn/hancock.zip

您还需要在 QGIS 的插件菜单下打开Python 控制台。您可以在控制台中运行这些代码行。

如何做到这一点...

在本食谱中,我们将加载我们的 shapefile,将其添加到地图中,创建一个空白图像,设置地图视图,渲染地图图像,并将其保存。为此,我们需要执行以下步骤:

  1. 首先,我们需要导入用于图像处理的底层Qt库:

    from PyQt4.QtGui import *
    from PyQt4.QtCore import *
    
    
  2. 接下来,我们将加载图层并将其添加到地图中:

    lyr = QgsVectorLayer("/qgis_data/hancock/hancock.shp", "Hancock", "ogr")
    reg = QgsMapLayerRegistry.instance()
    reg.addMapLayer(lyr)
    
    
  3. 现在,我们创建一个空白图像以接受地图图像:

    i = QImage(QSize(600,600), QImage.Format_ARGB32_Premultiplied)
    c = QColor("white")
    i.fill(c.rgb())
    p = QPainter()
    p.begin(i)
    
    
  4. 然后,我们访问地图渲染器:

    r = QgsMapRenderer()
    
    
  5. 现在,我们获取地图图层的 ID:

    lyrs = reg.mapLayers().keys()
    
    
  6. 然后,我们使用地图中新初始化的渲染器图层:

    r.setLayerSet(lyrs)
    
    
  7. 现在,我们获取地图的全范围作为矩形:

    rect = QgsRectangle(r.fullExtent())
    
    
  8. 然后,我们为渲染器设置一个比例。较小的数字会产生较大的地图比例,而较大的数字会产生较小的地图比例。我们可以更改地图比例以在地图图像周围创建缓冲区:

    rect.scale(1.1)
    
    
  9. 接下来,我们将渲染器的范围设置为矩形:

    r.setExtent(rect)
    
    
  10. 现在我们设置图像的输出大小和分辨率。分辨率会自动计算:

    r.setOutputSize(i.size(), i.logicalDpiX())
    
    
  11. 现在,我们可以渲染地图并最终确定图像:

    r.render(p)
    p.end()
    
    
  12. 最后,我们保存地图图像:

    i.save("/qgis_data/map.jpg","jpg")
    
    
  13. 确认您在qgis_data目录中有一个地图图像,类似于在 QGIS 中显示的地图。

它是如何工作的...

QGIS 使用底层的 Qt GUI 库来创建常见的图像类型。我们没有使用任何 QGIS 编者对象来渲染图像;然而,这种渲染技术用于保存使用 QGIS 编者创建的地图。

还有更多...

QImage对象还支持其他图像格式。要将地图图像保存为 PNG,将如何操作…部分中的最后一步替换为以下代码:

i.save("/qgis_data/map.png","png")

使用地图组合器

QGIS 地图组合器允许您将地图与非空间元素结合,这些元素有助于增强我们对地图的理解。在这个配方中,我们将创建一个基本的地图组合。组合需要您定义物理纸张大小和输出格式。即使像这样的简单组合示例也有超过 20 行的配置选项。

准备工作

您需要下载以下压缩的 shapefile 并将其提取到您的qgis_data目录下的hancock子目录中:

geospatialpython.googlecode.com/svn/hancock.zip

您还需要在 QGIS 的插件菜单下打开Python 控制台。您可以在控制台中运行此配方,或者将其包装在脚本运行器插件的脚本中,使用插件提供的模板。

如何操作...

在这个配方中,主要步骤是将 shapefile 加载到地图中,构建地图组合,并将其渲染为图像,具体如下所述:

  1. 首先,我们需要导入用于图像处理的Qt库:

    from PyQt4.QtGui import *
    from PyQt4.QtCore import *
    
    
  2. 接下来,我们加载图层并将其添加到地图中:

    lyr = QgsVectorLayer("/qgis_data/hancock/hancock.shp", "Hancock", "ogr")
    reg = QgsMapLayerRegistry.instance()
    reg.addMapLayer(lyr)
    
    
  3. 现在,我们创建一个空白图像以接受地图图像:

    i = QImage(QSize(600,600), QImage.Format_ARGB32_Premultiplied)
    c = QColor("white")
    i.fill(c.rgb())
    p = QPainter()
    p.begin(i)
    
    
  4. 接下来,我们获取地图图层的 ID:

    lyrs = reg.mapLayers().keys()
    
    
  5. 然后,我们访问地图渲染器:

    mr = iface.mapCanvas().mapRenderer()
    
    
  6. 然后,我们使用地图中新初始化的渲染器图层:

    mr.setLayerSet(lyrs)
    
    
  7. 现在,我们获取地图的完整范围作为矩形:

    rect = QgsRectangle(lyr.extent())
    
    
  8. 然后,我们为渲染器设置比例尺。较小的数字会产生较大的地图比例尺,而较大的数字会产生较小的地图比例尺,以便在地图周围添加图像缓冲区:

    rect.scale(1.2)
    
    
  9. 现在,我们将地图渲染器的范围设置为整个地图的范围:

    mr.setExtent(rect)
    
    
  10. 接下来,我们通过创建一个新的组合并分配地图渲染器来开始使用 QGIS 组合器:

    c = QgsComposition(mr)
    
    
  11. 然后,我们设置组合样式。我们将将其定义为Print,这将允许我们创建 PDF 文档和图像。另一种选择是将它定义为 postscript,这通常用于直接输出到打印机设备:

    c.setPlotStyle(QgsComposition.Print)
    
    
  12. 现在,我们定义我们的纸张大小,以毫米为单位指定。在这种情况下,我们将使用相当于 8.5 x 11 英寸纸张大小的纸张,即美国信函大小:

    c.setPaperSize(215.9, 279.4)
    
    
  13. 接下来,我们将计算地图的尺寸,使其占据大约一半的页面并居中:

    w, h = c.paperWidth() * .50, c.paperHeight() * .50
    x = (c.paperWidth() - w) / 2
    y = ((c.paperHeight() - h)) / 2 
    
    
  14. 然后,我们创建地图组合器对象并设置其范围:

    composerMap = QgsComposerMap(c,x,y,w,h)
    composerMap.setNewExtent(rect)
    
    
  15. 接下来,我们在地图的边缘周围添加一个框架并将其添加到页面上:

    composerMap.setFrameEnabled(True)
    c.addItem(composerMap)
    
    
  16. 现在,我们确保组合的分辨率已设置。分辨率定义了输出包含多少细节。较低的分辨率包含较少的细节并创建较小的文件。较高的分辨率提供更多的图像细节但创建较大的文件:

    dpi = c.printResolution()
    c.setPrintResolution(dpi)
    
    
  17. 我们现在将每英寸点数分辨率转换为每毫米点数:

     mm_in_inch = 25.4
    dpmm = dpi / mm_in_inch
    width = int(dpmm * c.paperWidth())
    height = int(dpmm * c.paperHeight())
    
    
  18. 接下来,我们初始化图像:

    image = QImage(QSize(width, height), QImage.Format_ARGB32)
    image.setDotsPerMeterX(dpmm * 1000)
    image.setDotsPerMeterY(dpmm * 1000)
    image.fill(0)
    
    
  19. 现在,我们渲染组合:

    imagePainter = QPainter(image)
    sourceArea = QRectF(0, 0, c.paperWidth(), c.paperHeight())
    targetArea = QRectF(0, 0, width, height)
    c.render(imagePainter, targetArea, sourceArea)
    imagePainter.end()
    
    
  20. 最后,我们将组合保存为 JPEG 图像:

    image.save("/Users/joellawhead/qgis_data/map.jpg", "jpg")
    
    

确认输出图像类似于以下示例图像:

如何操作...

它是如何工作的...

地图组成非常强大,但它们也可能相当复杂。您正在管理代表虚拟纸张的组成。在这个组成中,您放置对象,例如地图。然后,您还必须管理将组成作为图像的渲染。所有这些项都是独立可配置的,这有时可能导致项目的大小或可见性出现意外结果。

还有更多...

在 QGIS 即将推出的版本中,地图组成类可能被重命名为print layout类。您可以在github.com/qgis/QGIS-Enhancement-Proposals/pull/9找到更多关于这个提议更改的信息。

为打印添加标签

QgsComposition对象允许您在组成中的任何位置放置任意文本。在这个菜谱中,我们将演示如何向地图组成中添加标签。

准备工作

您需要下载以下压缩的 shapefile 并将其解压到您的qgis_data目录下的hancock子目录中:

geospatialpython.googlecode.com/svn/hancock.zip

除了 shapefile 之外,您还需要MapComposer类。这个类以可重用的方式封装了模板化组成代码,以便更容易添加其他元素。您可以从geospatialpython.googlecode.com/svn/MapComposer.py下载它。

此文件必须可以通过确保它在 Python 路径目录中从 QGIS Python 控制台访问。将文件放置在您家目录中的.qgis2/python目录内。

如何操作...

要向组成中添加标签,我们首先构建地图组成,创建一个标签,然后将组成保存为图像。为此,我们需要执行以下步骤:

  1. 首先,我们需要导入 Qt GUI 库和MapComposer类:

    from PyQt4.QtGui import *
    from PyQt4.QtCore import *
    import MapComposer
    
    
  2. 接下来,我们创建一个带有 shapefile 的层,设置 shapefile 的路径以匹配您的系统:

    lyr = QgsVectorLayer("/Users/joellawhead/qgis_data/hancock/hancock.shp", "Hancock", "ogr")
    
    
  3. 现在,我们将这一层添加到地图中:

    reg = QgsMapLayerRegistry.instance()
    reg.addMapLayer(lyr)
    
    
  4. 接下来,我们访问地图渲染器:

    mr = iface.mapCanvas().mapRenderer()
    
    
  5. 然后,我们创建一个MapComposer对象,传入地图层注册表和地图渲染器:

    qc = MapComposer.MapComposer(qmlr=reg, qmr=mr)
    
    
  6. 现在,我们创建一个新的标签对象:

    qc.label = QgsComposerLabel(qc.c)
    
    
  7. 我们可以将标签文本设置为任何字符串:

    qc.label.setText("Hancock County")
    
    
  8. 我们可以自动设置标签容器的尺寸以适应我们使用的字符串:

    qc.label.adjustSizeToText()
    
    
  9. 现在,我们在标签框周围添加一个框架:

    qc.label.setFrameEnabled(True)
    
    
  10. 然后,我们在页面上设置标签的位置,它位于地图的左上角:

    qc.label.setItemPosition(qc.x,qc.y-10)
    
    
  11. 接下来,由于已经配置好了,我们现在将标签添加到地图组成中:

    qc.c.addItem(qc.label)
    
    
  12. 最后,我们保存组成图像:

    qc.output("/Users/joellawhead/qgis_data/map.jpg", "jpg")
    
    
  13. 确认您的输出图像在地图左上角有一个文本标签在框架内。

它是如何工作的...

在此情况下,我们基于默认设置创建了一个非常简单的标签。然而,标签可以被自定义以更改字体、大小、颜色和样式,以用于打印质量的组合。此外,请注意,用于在组合中放置项目的 xy 值从页面的左上角开始。当您将项目向下移动页面时,y 值会增加。

向地图添加比例尺条

比例尺条是地图组合中最重要的元素之一,因为它定义了地图的比例,以确定地图上的地面距离。QGIS 组合器允许您创建多种不同类型的比例尺条,从简单的文本比例尺比到图形的、双测量系统的双比例尺条。在本配方中,我们将创建一个以公里为单位的比例尺条。

准备工作

您需要下载以下压缩的 shapefile 并将其提取到您的 qgis_data 目录下的 ms 子目录中:

geospatialpython.googlecode.com/svn/mississippi.zip

除了 shapefile 之外,您还需要 MapComposer 类。这个类以可重用的方式封装了组合器代码的样板,以便更容易地添加其他元素。您可以从 geospatialpython.googlecode.com/svn/MapComposer.py 下载它。

此文件必须可以从 QGIS Python 控制台中访问;确保它位于 Python 路径目录中。将文件放置在您主目录中的 .qgis2/python 目录下。

为了正确显示比例尺条,您必须确保 QGIS 设置为自动在飞行中重新投影数据。在 QGIS 中,转到 设置 菜单并选择 选项。在 选项 对话框中,选择 CRS 面板。在 新项目默认 CRS 部分中,勾选 默认启用 '在飞行中' 重投影 单选按钮。单击 确定 按钮以确认设置。

如何操作...

首先,我们将生成地图,然后生成组合,最后创建比例尺条并将其放置在地图的右下角。为此,我们需要执行以下步骤:

  1. 首先,我们需要导入我们将需要的库:

    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    from qgis.core import *
    from qgis.gui import *
    import MapComposer
    
    
  2. 然后,我们将使用 shapefile 构建地图渲染器:

    lyr = QgsVectorLayer("/Users/joellawhead/qgis_data/ms/mmississippi.shp", "Mississippi", "ogr")
    reg = QgsMapLayerRegistry.instance()
    reg.addMapLayer(lyr)
    mr = iface.mapCanvas().mapRenderer()
    
    
  3. 接下来,我们将使用图层注册和地图渲染器创建 MapComposer 对象:

    qc = MapComposer.MapComposer(qmlr=reg, qmr=mr)
    
    
  4. 现在,我们将初始化比例尺条对象:

    qc.scalebar = QgsComposerScaleBar(qc.c)
    
    
  5. 然后,我们定义比例尺条的类型。默认是文本比例尺,但我们将创建一个更传统的框形比例尺条:

    qc.scalebar.setStyle('Single Box')
    
    
  6. 接下来,我们将比例尺条应用到地图上,并将比例尺条图形设置为默认大小:

    qc.scalebar.setComposerMap(qc.composerMap)
    qc.scalebar.applyDefaultSize()
    
    
  7. 我们使用比例尺条大小、地图大小和地图位置来计算比例尺条在地图右下角所需的位置:

    sbw = qc.scalebar.rect().width()
    sbh = qc.scalebar.rect().height()
    mcw = qc.composerMap.rect().width()
    mch = qc.composerMap.rect().height()
    sbx = qc.x + (mcw - sbw)
    sby = qc.y + mch
    
    
  8. 然后,我们设置比例尺条的计算位置并将其添加到组合中:

    qc.scalebar.setItemPosition(sbx, sby)
    qc.c.addItem(qc.scalebar)
    
    
  9. 最后,我们将组合保存为图像:

    qc.output("/Users/joellawhead/qgis_data/map.jpg", "jpg")
    
    

它是如何工作的...

如果地图投影设置正确,比例尺条将显示为公里,这就是为什么在 QGIS 设置中启用自动重投影很重要的原因。比例尺条在组合中的位置并不重要,只要将其应用于composerMap对象即可。

添加地图北向针

北向针是另一种常见的制图元素,甚至在古代地图中也能找到,它显示了地图相对于真北、网格北或磁北的方位。有时,这些符号可能相当复杂。然而,QGIS 提供了一个基本的线箭头元素,我们将与地图标签结合使用来制作基本的北向针。

准备工作

您需要下载以下压缩的 shapefile 并将其提取到您的qgis_data目录中,到名为ms的子目录中:

geospatialpython.googlecode.com/svn/Mississippi.zip

除了 shapefile 之外,您还需要MapComposer类来简化添加此单个元素所需的代码。如果您之前没有在配方中使用过它,您可以从geospatialpython.googlecode.com/svn/MapComposer.py下载。

此文件必须可以从 QGIS Python 控制台访问;为此,您需要确保它位于 Python 路径目录中。将文件放置在您家目录中的.qgis2/python目录内。

如何操作...

在本配方中,我们将创建一个地图组合,在地图右侧绘制一个箭头,然后在箭头下方放置一个带有大写字母N的标签。为此,我们需要执行以下步骤:

  1. 首先,我们导入QtMapComposerPython 库:

    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    from qgis.core import *
    from qgis.gui import *
    import MapComposer
    
    
  2. 接下来,我们创建地图组合对象:

    lyr = QgsVectorLayer("/qgis_data/ms/mississippi.shp", "Mississippi", "ogr")
    reg = QgsMapLayerRegistry.instance()
    reg.addMapLayer(lyr)
    mr = iface.mapCanvas().mapRenderer() 
    qc = MapComposer.MapComposer(qmlr=reg, qmr=mr)
    
    
  3. 现在,我们计算箭头在地图右侧的位置,设置其位置,然后将其添加到组合中:

    mcw = qc.composerMap.rect().width()
    mch = qc.composerMap.rect().height()
    ax =  qc.x + mcw + 10
    ay =  (qc.y + mch) - 10
    afy = ay - 20
    qc.arrow = QgsComposerArrow(QPointF(ax, ay), QPointF(ax,afy), qc.c)
    qc.c.addItem(qc.arrow)
    
    
  4. 然后,我们创建一个大写字母N标签并将其添加到箭头下方:

    f = QFont()
    f.setBold(True)
    f.setFamily("Times New Roman")
    f.setPointSize(30)
    qc.labelNorth = QgsComposerLabel(qc.c)
    qc.labelNorth.setText("N")
    qc.labelNorth.setFont(f)
    qc.labelNorth.adjustSizeToText()
    qc.labelNorth.setFrameEnabled(False)
    qc.labelNorth.setItemPosition(ax - 5, ay) 
    qc.c.addItem(qc.labelNorth)
    
    
  5. 最后,我们将组合保存为图像:

    qc.output("/qgis_data/map.jpg", "jpg") 
    
    

确认您的输出图像与以下类似:

如何操作...

工作原理...

QGIS 组合器没有专门的北向针或罗盘玫瑰对象。然而,正如前文所示,构建一个相当简单。箭头只是一个图形。箭头的方向由创建QgsComposerArrow对象时列出的起点和终点位置控制。

更多内容...

您可以将此示例扩展为使箭头指向多个罗盘方向。您还可以将更复杂的罗盘玫瑰图像添加到组合中。我们将在下一个配方中演示如何添加图像。请注意,箭头元素也可以用来指向带有相关标签的地图上的项目。

添加地图徽标

自定义地图的一个重要部分是向组合中添加您的标志或其他图形。在本菜谱中,我们将向地图添加一个简单的标志。

准备工作

您需要下载以下 zip 格式的 shapefile,并将其解压到您的qgis_data目录下的名为ms的子目录中:

geospatialpython.googlecode.com/svn/Mississippi.zip

您还需要一个标志图像,您可以从geospatialpython.googlecode.com/svn/trunk/logo.png下载。

将图像放置在您的qgis_data/rasters目录中。

如果您在先前的菜谱中还没有这样做,请从geospatialpython.googlecode.com/svn/MapComposer.py下载MapComposer库,以简化地图组合的创建。

将文件放置在您家目录中的.qgis2/python目录内。

如何操作...

在本菜谱中,我们将创建地图组合,添加标志图像,并将地图保存为图像。为此,我们需要执行以下步骤:

  1. 首先,我们需要导入 Qt GUI、核心 QGIS、QGIS GUI 和 MapComposer 库:

    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    from qgis.core import *
    from qgis.gui import *
    import MapComposer
    
    
  2. 接下来,我们将使用 shapefile 构建基本的地图组合:

    lyr = QgsVectorLayer("/qgis_data/ms/mississippi.shp", "Mississippi", "ogr")
    reg = QgsMapLayerRegistry.instance()
    reg.addMapLayer(lyr)
    mr = iface.mapCanvas().mapRenderer()
    qc = MapComposer.MapComposer(qmlr=reg, qmr=mr)
    
    
  3. 现在,我们初始化图片对象:

    qc.logo = QgsComposerPicture(qc.c)
    
    
  4. 然后,我们将图片的路径设置为我们的图像文件:

    qc.logo.setPictureFile("/qgis_data/rasters/logo.png")
    
    
  5. 我们必须设置框或场景矩形的大小,使其足够大,可以包含标志。否则,图片将出现裁剪:

    qc.logo.setSceneRect(QRectF(0,0,50,70))
    
    
  6. 接下来,我们将计算标志相对于地图图像的位置。我们将标志放置在地图的右上角:

    lx = qc.x + 50
    ly = qc.y – 120
    
    
  7. 现在,我们设置标志的位置并将其添加到地图组合中:

     mcw = qc.composerMap.rect().width()
     mch = qc.composerMap.rect().height()
     lx =  qc.x
    ly =  qc.y - 20
    
    
  8. 最后,我们将组合保存为图像:

    qc.output("/qgis_data/map.jpg", "jpg")
    
    

工作原理...

此菜谱非常直接,因为QgsComposerPicture是一个非常简单的对象。您可以使用 JPG、PNG 或 SVG 图像。此技术还可以用于添加自定义的北方箭头或其他制图元素。

向地图添加图例

地图图例解码了用于主题 GIS 地图的符号,供读者解读。图例与 QGIS 紧密集成,在本菜谱中,我们将地图的默认图例添加到打印组合中。

准备工作

geospatialpython.googlecode.com/svn/Mississippi.zip下载此地图的 shapefile,并将其解压到您的qgis_data目录下的名为ms的子目录中。

与本章中先前的菜谱一样,我们将使用geospatialpython.googlecode.com/svn/MapComposer.py中的MapComposer库来简化地图组合的创建。

将文件放置在您家目录中的.qgis2/python目录内。

如何操作...

这个菜谱就像创建地图、添加自动生成的图例并将输出保存到图像一样简单。为此,我们需要执行以下步骤:

  1. 首先,我们需要加载 Qt 和 QGIS GUI 库,然后是 MapComposer 库:

    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    from qgis.core import *
    from qgis.gui import *
    import MapComposer
    
    
  2. 接下来,我们将加载形状文件作为图层,并使用MapComposer库创建地图组合,传递地图图层注册表和地图渲染器:

    lyr = QgsVectorLayer("/qgis_data/ms/mississippi.shp", "Mississippi", "ogr")
    reg = QgsMapLayerRegistry.instance()
    reg.addMapLayer(lyr)
    mr = iface.mapCanvas().mapRenderer()
    qc = MapComposer.MapComposer(qmlr=reg, qmr=mr)
    
    
  3. 现在,我们初始化图例对象:

    qc.legend = QgsComposerLegend(qc.c)
    
    
  4. 我们现在告诉图例我们想要使用哪个图层集:

    qc.legend.model().setLayerSet(qc.qmr.layerSet())
    
    
  5. 然后,我们将图例的位置设置为地图的左侧,并将其添加到组合中:

    qc.legend.setItemPosition(5, qc.y)
    qc.c.addItem(qc.legend)
    
    
  6. 最后,我们将组合输出到地图上:

    qc.output("/qgis_data/map.jpg", "jpg")
    
    

它是如何工作的...

添加图例相当简单。QGIS 将在加载图层时自动生成样式,或者由用户手动设置。当然,您也可以保存图层样式,这些样式与图层一起加载,并由图例使用。然而,如果您在后台生成组合,例如在独立应用程序中,那么图例的每个方面都可以通过 PyQGIS API 进行自定义。

向地图添加自定义形状

QGIS 作曲器有一个用于绘制和样式化非空间形状的对象,包括矩形、椭圆和三角形。在这个菜谱中,我们将添加一些填充不同颜色的矩形,这将成为使用形状的一个简单条形图的例子。

准备工作

geospatialpython.googlecode.com/svn/Mississippi.zip下载此地图的压缩形状文件,并将其解压到您的qgis_data目录下的ms子目录中。

我们还将使用来自geospatialpython.googlecode.com/svn/MapComposer.pyMapComposer库来简化地图组合的创建。

将文件放置在您家目录中的.qgis2/python目录下。

如何做到这一点...

首先,我们将使用形状文件创建一个简单的地图组合。然后,我们将定义矩形的样式属性。接下来,我们将绘制矩形,应用符号,并渲染组合。为此,我们需要执行以下步骤:

  1. 首先,我们必须导入 PyQGIS 和 Qt GUI 库以及 MapComposer 库,如下所示:

    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    from qgis.core import *
    from qgis.gui import *
    import MapComposer
    
    
  2. 接下来,我们使用形状文件创建地图组合:

    lyr = QgsVectorLayer("/qgis_data/ms/mississippi.shp", "Mississippi", "ogr")
    reg = QgsMapLayerRegistry.instance()
    reg.addMapLayer(lyr)
    mr = iface.mapCanvas().mapRenderer()
    qc = MapComposer.MapComposer(qmlr=reg, qmr=mr)
    
    
  3. 现在,我们通过构建具有颜色属性的 Python 字典来创建三个基本的填充符号,并用这些字典初始化符号:

    red = {'color':'255,0,0,255','color_border':'0,0,0,255'}
    redsym = QgsFillSymbolV2.createSimple(red)
    blue = {'color':'0,0,255,255','color_border':'0,0,0,255'}
    bluesym = QgsFillSymbolV2.createSimple(blue)
    yellow = {'color':'255,255,0,255','color_border':'0,0,0,255'}
    yellowsym = QgsFillSymbolV2.createSimple(yellow)
    
    
  4. 然后,我们计算第一个形状相对于地图的 y 位置:

    mch = qc.composerMap.rect().height()
    sy = qc.y + mch
    
    
  5. 我们创建第一个形状并将其设置为类型 1,这是一个矩形:

    qc.shape1 = QgsComposerShape(10,sy-25,10,25,qc.c)
    qc.shape1.setShapeType(1)
    
    
  6. 接下来,我们告诉形状使用一个符号,设置一个符号为我们三个填充符号中的一个,并将形状添加到组合中:

    qc.shape1.setUseSymbolV2(True)
    qc.shape1.setShapeStyleSymbol(redsym)
    qc.c.addItem(qc.shape1)
    
    
  7. 我们用另外两种形状重复这个过程,改变它们的位置、大小和符号,使它们看起来不同:

    qc.shape2 = QgsComposerShape(22,sy-18,10,18,qc.c)
    qc.shape2.setShapeType(1)
    qc.shape2.setUseSymbolV2(True)
    qc.shape2.setShapeStyleSymbol(bluesym)
    qc.c.addItem(qc.shape2)
    qc.shape3 = QgsComposerShape(34,sy-12,10,12,qc.c)
    qc.shape3.setShapeType(1)
    qc.shape3.setUseSymbolV2(True)
    qc.shape3.setShapeStyleSymbol(yellowsym)
    qc.c.addItem(qc.shape3)
    
    
  8. 最后,我们将组合输出为图像:

    qc.output("/qgis_data/map.jpg", "jpg")
    
    

确认您的输出图像与以下类似:

如何做到这一点...

它是如何工作的...

这种简单的图形输出几乎有 40 行代码。虽然这些形状可能有一些有限的用途来处理,但在大多数情况下,更简单的方法是直接导入图像。然而,这为更丰富的图形 API 提供了一个良好的基础,因为 QGIS 持续发展。

还有更多...

如果您在 QGIS 版本低于 2.6 的 Python 插件中使用填充符号,您必须确保符号在全局范围内定义,否则 QGIS 会因为一个错误而崩溃。在全局范围内包含变量的最简单方法是立即在导入语句之后定义它们。这也影响了在 Script Runner 插件中运行的脚本。这个错误在 2.6 版及以后的版本中已修复。

向地图添加网格

一个带注释的参考网格对于用于定位特征的地图产品很有用。这个配方教你如何在地图上添加参考线以及地图边缘的注释。

准备工作

您需要从这个地图中获取一个 shapefile,来自geospatialpython.googlecode.com/svn/Mississippi.zip,并且您需要将其提取到您的qgis_data目录下的一个名为ms的子目录中。

与本章中之前的配方一样,我们将使用来自geospatialpython.googlecode.com/svn/MapComposer.pyMapComposer库来简化地图组合的创建。

将文件放在您家目录中的.qgis2/python目录中。

如何做到这一点...

在这个配方中,一般的步骤是创建地图组合,建立整体网格参数,定义网格线位置,然后样式网格和注释。为此,我们需要执行以下步骤:

  1. 首先,我们需要导入所有 GUI 库和 MapComposer 库:

    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    from qgis.core import *
    from qgis.gui import *
    import MapComposer
    
    
  2. 接下来,我们使用 shapefile 创建地图组合:

    lyr = QgsVectorLayer("/qgis_data/ms/mmississippi.shp", "Mississippi", "ogr")
    reg = QgsMapLayerRegistry.instance()
    reg.addMapLayer(lyr)
    mr = iface.mapCanvas().mapRenderer()
    qc = MapComposer.MapComposer(qmlr=reg, qmr=mr)
    
    
  3. 现在,我们将创建一些变量来缩短一些异常长的方法和对象名称:

    setGridAnnoPos = qc.composerMap.setGridAnnotationPosition
    setGridAnnoDir = qc.composerMap.setGridAnnotationDirection
    qcm = QgsComposerMap
    
    
  4. 然后,我们启用网格,设置行间距,并使用实线作为网格:

    qc.composerMap.setGridEnabled(True)
    qc.composerMap.setGridIntervalX(.75)
    qc.composerMap.setGridIntervalY(.75)
    qc.composerMap.setGridStyle(qcm.Solid)
    
    
  5. 接下来,我们启用坐标的注释数字,并将整数的小数精度设置为 0:

    qc.composerMap.setShowGridAnnotation(True)
    qc.composerMap.setGridAnnotationPrecision(0)
    
    
  6. 现在,我们围绕地图组合框架移动,并使用上一步中较短的变量名定义每一组网格线的位置和方向:

    setGridAnnoPos(qcm.OutsideMapFrame, qcm.Top)
    setGridAnnoDir(qcm.Horizontal, qcm.Top)
    setGridAnnoPos(qcm.OutsideMapFrame, qcm.Bottom)
    setGridAnnoDir(qcm.Horizontal, qcm.Bottom)
    setGridAnnoPos(qcm.OutsideMapFrame, qcm.Left)
    setGridAnnoDir(qcm.Vertical, qcm.Left)
    setGridAnnoPos(qcm.OutsideMapFrame, qcm.Right)
    setGridAnnoDir(qcm.Vertical, qcm.Right)
    
    
  7. 最后,在将整个地图添加到整体组合之前,我们为网格线和注释设置了一些额外的样式:

    qc.composerMap.setAnnotationFrameDistance(1)
    qc.composerMap.setGridPenWidth(.2)
    qc.composerMap.setGridPenColor(QColor(0, 0, 0))
    qc.composerMap.setAnnotationFontColor(QColor(0, 0, 0))
    qc.c.addComposerMap(qc.composerMap)
    
    
  8. 我们将组合输出到图像中:

    qc.output("/qgis_data/map.jpg", "jpg")
    
    

确认您的输出图像与以下类似:

如何做到这一点...

它是如何工作的...

这个菜谱有多个步骤,因为网格是可定制的。操作顺序也很重要。请注意,我们直到最后才将地图添加到编排中。通常,您会做出看似微小的更改,网格可能无法渲染。因此,请仔细修改此菜谱。

将表格添加到地图中

QGIS 编排器提供了一个对象,可以将表格添加到编排中,表示矢量层的属性或您创建的任意文本表格。在本菜谱中,我们将使用地图层形状文件的属性将表格添加到编排中。

准备工作

geospatialpython.googlecode.com/svn/Mississippi.zip 下载此地图的形状文件,并将其提取到您的 qgis_data 目录下的 ms 子目录中。

与本章中之前的菜谱一样,我们将使用 geospatialpython.googlecode.com/svn/MapComposer.py 中的 MapComposer 库来简化地图编排的创建。

将文件放置在您家目录中的 .qgis2/python 目录下。

如何操作...

以下步骤将创建地图编排,添加表格,并将编排输出为图像:

  1. 首先,我们导入我们的 GUI 库和 MapComposer 库:

    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    from qgisfromqgis.core import *
    from qgisfromqgis.gui import *
    import MapComposer
    
    
  2. 接下来,我们创建地图编排:

    lyr = QgsVectorLayer("/qgis_data/ms/mississippi.shp", "Mississippi", "ogr")
    reg = QgsMapLayerRegistry.instance()
    reg.addMapLayer(lyr)
    mr = iface.mapCanvas().mapRenderer()
    qc = MapComposer.MapComposer(qmlr=reg, qmr=mr)
    
    
  3. 现在,我们可以初始化表格对象:

    qc.table = QgsComposerAttributeTable(qc.c)
    
    
  4. 然后,我们引用相关的地图:

    qc.table.setComposerMap(qc.composerMap)
    
    
  5. 接下来,我们可以指定要显示在表格中的属性所在的层:

    qc.table.setVectorLayer(lyr)
    
    
  6. 现在,我们可以将表格放置在地图下方并将其添加到编排中:

    mch = qc.composerMap.rect().height() 
    qc.table.setItemPosition(qc.x, qc.y + mch + 20)
    qc.c.addItem(qc.table)
    
    
  7. 最后,我们将编排输出为图像:

    qc.output("/qgis_data/map.jpg", "jpg")
    
    

它是如何工作的...

表格对象非常直观。使用矢量层的属性是自动的。如果您想显示自定义信息,也可以逐个单元格构建表格。

将世界文件添加到地图图像中

将地图导出为图像会移除其所有空间信息。然而,您可以创建一个名为 world file 的外部文本文件,它为栅格图像提供地理参考信息,因此它可以由包括 QGIS 在内的 GIS 软件用作栅格层。在本菜谱中,我们将导出地图编排为图像,并创建与其相关的 world file。

准备工作

您需要从 geospatialpython.googlecode.com/svn/Mississippi.zip 下载压缩的形状文件,并将其提取到您的 qgis_data 目录下的 ms 子目录中。

除了形状文件外,您还需要 MapComposer 类来简化添加此单个元素所需的代码。如果您之前没有在菜谱中使用它,您可以从 geospatialpython.googlecode.com/svn/MapComposer.py 下载它。

此文件必须可以从 QGIS Python 控制台访问;为此,您需要确保它位于 python 路径目录中。将文件放置在您家目录中的 .qgis2/python 目录内。

如何操作...

首先,我们将创建地图组合,然后将其保存为图像,最后生成世界文件。为此,我们需要执行以下步骤:

  1. 首先,我们需要导入 GUI 和 MapComposer 库:

    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    from qgisfromqgis.core import *
    from qgisfromqgis.gui import *
    import MapComposer
    
    
  2. 接下来,我们将使用 MapComposer 库创建地图的组合:

    lyr = QgsVectorLayer("/qgis_data/ms/mississippi.shp", "Mississippi", "ogr")
    reg = QgsMapLayerRegistry.instance()
    reg.addMapLayer(lyr)
    mr = iface.mapCanvas().mapRenderer()
    qc = MapComposer.MapComposer(qmlr=reg, qmr=mr)
    
    
  3. 现在,我们将定义输出文件的名称:

    output =  "/qgis_data/map"
    
    
  4. 然后,我们可以将组合导出为图像:

    qc.output(output + ".jpg", "jpg")
    
    
  5. 现在,我们将创建一个包含世界文件信息的对象:

    qc.c.setWorldFileMap(qc.composerMap)
    qc.c.setGenerateWorldFile(True)
    wf = qc.c.computeWorldFileParameters()
    
    
  6. 最后,我们将打开一个文本文件并写入文本文件的每一行:

    with open(output + ".jgw", "w") as f:
     f.write("%s\n" % wf[0])
     f.write("%s\n" % wf[1])
     f.write("%s\n" % wf[3])
     f.write("%s\n" % wf[4])
     f.write("%s\n" % wf[2])
     f.write("%s\n" % wf[5])
    
    

它是如何工作的...

世界文件包含每像素的地面距离和地图图像的左上角坐标。QGIS 组合器会自动根据引用的地图生成这些信息。世界文件的名称必须与图像相同,扩展名使用图像文件扩展名的第一个和最后一个字母加上字母 w。例如,.TIFF 图像文件将有一个扩展名为 .TFW 的世界文件。您可以在 en.wikipedia.org/wiki/World_file 上了解更多关于每行中世界文件变量含义的信息。

将地图保存到项目中

自动保存项目可以用于自动保存功能或作为从动态更新的数据自动生成项目的流程的一部分。在这个配方中,我们将保存一个 QGIS 项目到 .qgs 项目文件。

准备工作

您需要下载以下压缩的 shapefile 并将其解压到您的 qgis_data 目录下的 ms 子目录中:

geospatialpython.googlecode.com/svn/Mississippi.zip

如何操作...

我们将通过加载 shapefile 层创建一个简单的 QGIS 项目,然后访问项目对象,并将地图项目保存到文件中,如下所示:

  1. 首先,我们需要在 QGIS Python 控制台中安装 Qt 核心库:

    from PyQt4.QtCore import *
    
    
  2. 接下来,我们加载 shapefile 并将其添加到地图中:

    lyr = QgsVectorLayer("/Users/joellawhead/qgis_data/ms/mississippi.shp", "Mississippi", "ogr")
    reg = QgsMapLayerRegistry.instance()
    reg.addMapLayer(lyr)
    
    
  3. 然后,我们创建一个 file 对象来保存我们的项目:

    f = QFileInfo("/Users/joellawhead/qgis_data/myProject.qgs")
    
    
  4. 现在,我们可以访问 QGIS 项目对象实例:

    p = QgsProject.instance()
    
    
  5. 最后,我们可以通过将其写入文件对象来保存项目:

    p.write(f)
    
    

它是如何工作的...

QGIS 简单地创建了一个包含所有项目设置和 GIS 地图设置的 XML 文档。您可以手动读取甚至修改 XML 输出。

从项目中加载地图

这个配方演示了如何从 .qgs XML 文件中加载项目。加载项目将在 QGIS 中设置之前保存的项目的地图和项目设置。

准备工作

您需要完成之前的配方,将地图保存到项目中,以便您在 qgis_data 文件夹中有一个名为 myProject.qgs 的项目。

如何操作...

对于这个菜谱,你需要设置一个文件对象,设置资源路径,然后读取引用项目文件的文件对象。为此,你需要执行以下步骤:

  1. 首先,我们导入核心 Qt 库用于文件对象:

    from PyQt4.QtCore import *
    
    
  2. 接下来,我们使用项目文件的路径初始化文件对象:

    f = QFileInfo("/Users/joellawhead/qgis_data/myProject.qgs")
    
    
  3. 现在,我们访问项目对象:

    p = QgsProject.instance()
    
    
  4. 然后,我们为 QGIS 设置资源路径以便找到数据和其它文件,以防项目是以相对路径而不是绝对路径保存的:

    p.readPath("/Users/joellawhead/qgis_data/")
    
    
  5. 最后,我们告诉项目对象读取项目文件以加载地图:

    p.read(f)
    
    

它是如何工作的...

QGIS 有一个设置,可以将对数据和其它文件的引用保存为相对路径,这些路径相对于项目文件,或者绝对路径,这些路径包含完整路径。如果保存的路径是绝对路径,PyQGIS 将无法定位数据源。将读取路径设置为项目文件的完整系统路径确保 QGIS 可以找到项目文件中所有引用的文件,如果它们是以相对路径保存的。

第七章. 与用户交互

在本章中,我们将介绍以下配方:

  • 使用日志文件

  • 创建一个简单的消息对话框

  • 创建一个警告对话框

  • 创建一个错误对话框

  • 显示进度条

  • 创建一个简单的文本输入对话框

  • 创建一个文件输入对话框

  • 创建一个组合框

  • 创建单选按钮

  • 创建复选框

  • 创建标签页

  • 引导用户通过向导

  • 保持对话框在最前面

简介

QGIS 是使用名为 Qt 的综合图形用户界面框架构建的。QGIS 和 Qt 都有 Python API。在本章中,我们将学习如何与用户交互,以便在默认 QGIS 界面之外收集和显示信息。Qt 有自己出色的文档,由于 QGIS 是基于 Qt 构建的,因此所有这些文档都适用于 QGIS。您可以在qt-project.org找到 Qt 文档。

使用日志文件

日志文件提供了一种跟踪 Python 插件或脚本中确切发生情况的方法,即使脚本或 QGIS 崩溃,这些消息仍然可用。这些日志消息使故障排除变得更容易。在本配方中,我们将演示两种用于日志记录的方法。一种方法是在文件系统上使用实际的日志文件,另一种方法是使用 QGIS 日志消息窗口,可以通过点击 QGIS 应用程序窗口右下角的黄色感叹号三角形或通过选择视图菜单,然后点击面板,然后勾选日志消息来实现。

准备工作

要使用日志文件,我们必须通过执行以下步骤来配置QGIS_LOG_FILE环境变量,以便 QGIS 知道在哪里写入日志消息:

  1. 从 QGIS 设置菜单中选择选项

  2. 选项对话框中,选择系统面板。

  3. 系统面板中,向下滚动到环境部分。

  4. 环境部分,勾选使用自定义变量复选框。

  5. 点击添加按钮。

  6. 变量字段中,输入QGIS_LOG_FILE

  7. 字段中,输入/qgis_data/log.txt或您有写权限的另一个目录的路径。

  8. 点击确定按钮关闭选项对话框。

  9. 重新启动 QGIS 以使环境变量生效。

如何做到这一点...

我们将向之前配置的定制日志文件写入一条消息,然后向标签页式的 QGIS 日志消息窗口写入一条消息。为此,我们需要执行以下步骤:

  1. 首先,在 QGIS 中打开Python 控制台

  2. 接下来,我们将编写以下日志文件消息:

    QgsLogger.logMessageToFile("This is a message to a log file.")
    
    
  3. 然后,我们将向 QGIS 日志消息窗口写入一条消息,指定消息作为第一个参数,以及消息将出现的标签页名称:

    QgsMessageLog.logMessage("This is a message from the Python Console", "Python Console")
    
    
  4. 现在,打开日志文件并检查消息是否出现。

  5. 最后,打开 QGIS 日志消息窗口,点击Python 控制台标签,并验证第二个日志消息是否出现。

它是如何工作的...

传统的日志文件提供了使用 Python 从 QGIS 记录信息的简单且便携的方式。日志消息窗口是一种更结构化的方式来查看来自许多不同来源的信息,具有标签式界面和每条消息的方便时间戳。在大多数情况下,你可能想使用 日志消息窗口,因为 QGIS 用户对此很熟悉。然而,请谨慎使用。在测试代码时记录大量消息是可以的,但将插件或应用程序的日志记录限制在严重错误上。大量的日志记录——例如,在遍历图层中的每个要素时记录消息——可能会减慢 QGIS 的速度,甚至导致其崩溃。

创建一个简单的消息对话框

消息对话框弹出以吸引用户的注意力并显示重要信息。在本例中,我们将创建一个简单的信息对话框。

准备工作

通过转到 插件 菜单并选择 Python 控制台 来打开 QGIS Python 控制台

如何做...

我们将创建一个消息对话框并在其中显示一些文本,如下所示:

  1. 首先,我们需要导入 GUI 库:

    from PyQt4.QtGui import *
    
    
  2. 然后,我们将创建消息对话框:

    msg = QMessageBox()
    
    
  3. 接下来,我们将设置要显示的消息:

    msg.setText("This is a simple information message.")
    
    
  4. 最后,我们调用执行方法来显示消息对话框:

    msg.show()
    
    

工作原理...

注意,我们直接使用构建 QGIS 的底层 Qt 框架。QGIS API 的对象以 Qgs 开头,而 Qt 对象仅以字母 Q 开头。

还有更多...

消息对话框也应该谨慎使用,因为它是一个弹出窗口,可能会让用户感到烦恼,或者可能会在用户桌面上的打开窗口和对话框中丢失。QGIS 信息消息的首选方法是使用 QgsMessageBar() 方法,这在位于 docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/communicating.html 的 PyQGIS 开发者手册中有详细说明。

创建一个警告对话框

有时,当检测到问题可能会引起问题,如果用户继续操作,就需要通知用户。这种情况需要警告对话框,我们将在本例中演示。

准备工作

通过转到 插件 菜单并选择 Python 控制台 来打开 QGIS Python 控制台

如何做...

在本例中,我们将创建一个对话框,设置警告信息和警告图标,并显示对话框,如下所示:

  1. 首先,我们导入 GUI 库:

    from PyQt4.QtGui import *
    
    
  2. 接下来,我们初始化警告对话框:

    msg = QMessageBox()
    
    
  3. 然后,我们设置警告信息:

    msg.setText("This is a warning...")
    
    
  4. 现在,向对话框添加一个具有枚举索引 2 的警告图标:

    msg.setIcon(QMessageBox.Warning)
    
    
  5. 最后,我们调用执行方法来显示对话框:

    msg.show()
    
    

工作原理...

消息对话框应该谨慎使用,因为它们会打断用户体验,并可能很快变得令人烦恼。然而,有时防止用户执行可能导致数据损坏或程序崩溃的操作是很重要的。

创建一个错误对话框

当你需要因为严重错误而结束一个进程时,你可以发出一个错误对话框。在这个菜谱中,我们将创建一个错误对话框的示例。

准备工作

通过选择插件菜单然后点击Python 控制台来打开 QGIS 的Python 控制台

如何做...

在这个菜谱中,我们将创建一个对话框,分配一个错误信息,设置一个错误图标,并显示对话框,如下所示:

  1. 首先,我们需要导入 GUI 库:

    from PyQt4.QtGui import *
    
    
  2. 接下来,我们初始化对话框:

    msg = QMessageBox()
    
    
  3. 然后,我们设置错误信息:

    msg.setText("This is an error!")
    
    
  4. 然后,我们为错误图标设置一个图标编号:

    msg.setIcon(QMessageBox.Critical)
    
    
  5. 最后,我们执行错误对话框:

    msg.show()
    
    

它是如何工作的...

模态窗口的一个重要特性是它们总是保持在应用程序的最前面,无论用户是否更改窗口的焦点。这个特性确保用户在继续之前先处理对话框。

显示进度条

进度条是一个动态对话框,显示用户必须等待的运行进程的完成百分比。进度条比简单对话框更高级,因为它需要不断更新。在这个菜谱中,我们将基于计时器创建一个简单的进度对话框。

准备工作

这个菜谱不需要做任何准备工作。

如何做...

这个菜谱的步骤包括基于QProgressBar创建一个自定义类,初始化对话框并设置其大小和标题,创建一个计时器,将进度条连接到计时器,开始计时,并显示进度。为此,我们需要执行以下步骤:

  1. 首先,我们必须导入 GUI 和 QGIS 核心库:

    from PyQt4.QtGui import *
    from PyQt4.QtCore import *
    
    
  2. 接下来,我们为我们的进度条创建一个自定义类,包括一个用于增加进度条值的increaseValue方法:

    class Bar(QProgressBar):
     value = 0
     def increaseValue(self):
     self.setValue(self.value)
     self.value = self.value+1
    
    
  3. 现在,我们设置进度条:

    bar = Bar()
    
    
  4. 接下来,我们设置进度条的大小和标题:

    bar.resize(300,40)
    bar.setWindowTitle('Working...')
    
    
  5. 然后,我们初始化计时器,它将作为我们监控的进程:

    timer = QTimer()
    
    
  6. 现在,将计时器的timeout信号连接到我们之前创建的increaseValue方法。每当计时器完成倒计时,它将发出timeout信号并通知increaseValue方法。

    timer.timeout.connect(bar.increaseValue)
    
    
  7. 现在,我们将开始计时器,指定一个 500 毫秒的间隔。计时器将每 0.5 秒调用其timeout()信号:

    timer.start(500)
    
    
  8. 最后,我们显示进度条并开始进度计:

    bar.show()
    
    

它是如何工作的...

当进度条值达到 100 时,进度条将停止,但我们的计时器将继续运行,直到调用stop()方法。在更现实的实现中,你需要一种方法来确定被监控的过程是否完成。指示器可能是文件的创建,或者更好的是,一个信号。Qt 框架使用信号和槽的概念来连接 GUI 元素。GUI 是事件驱动的,在多个不同时间发生多个事件,包括用户操作和其他触发器。信号/槽系统允许你在事件发生时定义反应,而无需编写代码来持续监控变化。在这个菜谱中,我们使用计时器预定义的信号并创建我们自己的槽。槽只是一个通过传递给信号connect()方法而被标识为槽的方法。以下截图显示了进度条的示例:

如何工作...

还有更多...

在像 QGIS 这样的复杂 GUI 应用程序中,你将会有多个同时触发多个槽的信号。你必须注意,快速更新的元素,如进度条,不会减慢应用程序的速度。仅当确实有变化时使用线程来更新进度条会更有效。关于这种技术的示例,请参阅snorf.net/blog/2013/12/07/multithreading-in-qgis-python-plugins/

使用QgsMessageBar对象来显示信息性消息是首选的,但它也可以接受进度条等小部件。PyQGIS 开发者手册有一个示例,展示了如何将进度条放置在QgsMessageBar对象中(docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/communicating.html

创建一个简单的文本输入对话框

在这个菜谱中,我们将演示用于从用户那里接受输入的最简单方法之一,即文本输入对话框。

准备工作

通过选择插件菜单然后点击Python 控制台来打开 QGIS Python 控制台

如何做到这一点...

在这个菜谱中,我们将初始化对话框并配置其标题和标签。我们将设置编辑模式和默认文本。当你点击确定按钮时,文本将被打印到Python 控制台。为此,我们需要执行以下步骤:

  1. 首先,我们需要导入 GUI 库:

    from PyQt4.QtGui import *
    
    
  2. 接下来,我们初始化对话框:

    qid = QInputDialog()
    
    
  3. 现在,我们设置窗口标题、标签文本、编辑模式和默认文本:

    title = "Enter Your Name"
    label = "Name: "
    mode = QLineEdit.Normal
    default = "<your name here>"
    
    
  4. 我们在捕获用户输入和返回代码的同时配置对话框:

    text, ok = QInputDialog.getText(qid, title, label, mode, default)
    
    
  5. 当对话框出现时,输入一些文本并点击确定按钮。

  6. 现在,我们将用户输入打印到控制台:

    print text
    
    
  7. 最后,验证是否正确地将文本打印到了Python 控制台

如何工作...

编辑模式区分正常,这是我们在这里使用的,和密码,以隐藏输入的密码。尽管我们没有在这个例子中使用它,但返回码是一个布尔值,可以用来验证用户输入是否发生。

创建文件输入对话框

从用户那里获取文件名的最佳方式是让他们使用对话框浏览文件。您可以让用户使用文本输入对话框输入文件名,但这种方法容易出错。在这个菜谱中,我们将创建一个文件对话框并将选定的文件名打印到控制台。

准备工作

通过选择插件菜单然后点击Python 控制台来打开 QGIS Python 控制台

如何做...

在这个菜谱中,我们将创建和配置对话框,浏览文件,并打印选定的文件名,如下所示:

  1. 首先,我们导入 GUI 库:

    from PyQt4.QtGui import *
    
    
  2. 接下来,我们初始化文件对话框并指定其窗口标题:

    qfd = QFileDialog()
    title = 'Open File'
    
    
  3. 现在,我们指定文件对话框开始时的目录路径:

    path = "/Users/joellawhead/qgis_data"
    
    
  4. 然后,我们使用前面的参数配置文件对话框并将输出分配给一个变量:

    f = QFileDialog.getOpenFileName(qfd, title, path)
    
    
  5. 当对话框出现时,浏览文件,选择它,然后点击确定按钮。

  6. 最后,我们将选定的文件名打印到控制台:

    print f
    
    

它是如何工作的...

文件对话框仅提供文件名。在用户选择文件后,您必须打开它或对其执行其他操作。如果用户取消文件对话框,文件变量将是一个空字符串。您可以使用QFileInfo对象来获取所选文件的路径:

from PyQt4.QtCore import *path = QFileInfo(f).path()

然后,您可以将此路径保存到项目设置中,如第一章中所示,自动化 QGIS。这样,下次打开文件对话框时,您将开始于与上次文件相同的目录位置,这通常更方便。

更多...

您也可以使用QFileDialog()方法获取要保存的文件名。您可以使用FileMode枚举来限制用户选择目录。

创建组合框

组合框提供了一个下拉列表,以限制用户的选项为定义好的选择集。在这个菜谱中,我们将创建一个简单的组合框。

准备工作

通过选择插件菜单然后点击Python 控制台来打开 QGIS Python 控制台

如何做...

在这个菜谱中,我们将初始化组合框小部件,向其中添加选项,调整其大小,显示它,然后捕获用户输入以打印到控制台。为此,我们需要执行以下步骤:

  1. 首先,我们导入 GUI 库:

    from PyQt4.QtGui import *
    
    
  2. 现在,我们创建我们的组合框对象:

    cb = QComboBox()
    
    
  3. 接下来,我们添加用户可以选择的项目:

    cb.addItems(["North", "South", "West", "East"])
    
    
  4. 然后,我们调整小部件的大小:

    cb.resize(200,35)
    
    
  5. 现在,我们可以向用户显示小部件:

    cb.show()
    
    
  6. 接下来,我们需要从列表中选择一个项目。

  7. 现在,我们将用户的选项设置为一个变量:

    text = cb.currentText()
    
    
  8. 最后,我们可以打印选择:

    print text
    
    
  9. 确认选择已打印到控制台。

它是如何工作的...

添加到组合框中的项是一个 Python 列表。此功能使得使用 Python 作为数据库查询或其他动态数据的结果动态生成选项变得容易。您可能还希望访问对象在列表中的索引,您可以通过currentIndex属性来访问它。

创建单选按钮

当您希望用户从选项列表中选择一个唯一的选择时,单选按钮非常适合用户输入,与允许用户选择多个或所有可用选项的复选框相比。对于更长的选项列表,组合框是一个更好的选择。一旦选中单选按钮,您只能通过选择另一个单选按钮来取消选中它。

准备工作

通过选择插件菜单并然后点击Python 控制台来打开 QGIS Python 控制台

如何实现...

单选按钮作为类的一部分更容易管理,因此我们将创建一个自定义类,该类还包括一个文本框来查看哪个单选按钮被选中。为此,请执行以下步骤:

  1. 首先,我们将导入 GUI 和核心 QGIS 库:

    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    
    
  2. 接下来,我们将创建RadioButton类并设置单选按钮和文本框:

    class RadioButton(QWidget):
     def __init__(self, parent=None):
     QWidget.__init__(self, parent)
    
    
  3. 我们还必须定义一个布局来管理小部件的位置,如下所示:

    self.layout = QVBoxLayout()
    self.rb1 = QRadioButton('Option 1')
    self.rb2 = QRadioButton('Option 2')
    self.rb3 = QRadioButton('Option 3')
    self.textbox = QLineEdit()
    
    
  4. 现在,我们将每个单选按钮的切换信号连接到您即将定义的方法,以便检测何时选中单选按钮:

    self.rb1.toggled.connect(self.rb1_active)
    self.rb2.toggled.connect(self.rb2_active)
    self.rb3.toggled.connect(self.rb3_active)
    
    
  5. 然后,我们将单选按钮和文本框添加到布局中:

    self.layout.addWidget(self.rb1)
    self.layout.addWidget(self.rb2)
    self.layout.addWidget(self.rb3)
    self.layout.addWidget(self.textbox)
    
    
  6. 现在,我们可以定义我们正在构建的自定义小部件的布局:

     self.setLayout(self.layout)
    
    
  7. 接下来,我们可以定义表示哪个单选按钮被选中的方法。您也可以在单个方法中定义这些选项,但为了更好的理解,三个方法更容易:

    def rb1_active(self, on):
     if on:
     self.textbox.setText('Option 1 selected')
    def rb2_active(self, on):
     if on:
     self.textbox.setText('Option 2 selected')
    def rb3_active(self, on):
     if on:
     self.textbox.setText('Option 3 selected')
    
    
  8. 我们现在可以初始化我们的类并显示单选按钮:

    buttons = RadioButton()
    buttons.show()
    
    
  9. 最后,点击每个单选按钮并验证文本框中的文本是否更改以指示您点击的单选按钮已被选中。

它是如何工作的...

单选按钮几乎总是作为一个单独的对象分组,因为它们是相关的选项。许多 GUI 框架在 API 中将它们暴露为单个对象;然而,Qt 将它们作为单独的对象来保持最大的控制。

创建复选框

复选框与单选按钮密切相关,因为它们围绕一个主题提供选项。然而,与单选按钮不同,复选框可以被选中或取消选中。您还可以同时选中多个复选框。在本例中,我们将创建一个带有复选框和一些文本框的对话框,以编程方式跟踪哪些复选框被选中。

准备工作

通过选择插件菜单并然后点击Python 控制台来打开 QGIS Python 控制台

如何实现...

在本例中,我们将使用一个类来管理复选框和文本框小部件,如下所示:

  1. 首先,我们导入 GUI 和 QGIS 核心库:

    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    
    
  2. 接下来,我们创建我们的自定义类用于复选框和文本框:

    class CheckBox(QWidget):
     def __init__(self, parent=None):
     QWidget.__init__(self, parent)
    
    
  3. 接下来,我们需要一个layout对象来管理小部件的位置:

    self.layout = QVBoxLayout()
    
    
  4. 现在,我们将添加三个复选框和三个文本框:

     self.cb1 = QCheckBox('Option 1')
     self.cb2 = QCheckBox('Option 2')
     self.cb3 = QCheckBox('Option 3')
     self.textbox1 = QLineEdit()
     self.textbox2 = QLineEdit()
     self.textbox3 = QLineEdit()
    
    
  5. 然后,我们将复选框的状态信号连接到我们将定义的方法:

     self.cb1.toggled.connect(self.cb1_active)
     self.cb2.toggled.connect(self.cb2_active)
     self.cb3.toggled.connect(self.cb3_active)
    
    
  6. 接下来,我们必须将小部件添加到布局中:

     self.layout.addWidget(self.cb1)
     self.layout.addWidget(self.cb2)
     self.layout.addWidget(self.cb3)
     self.layout.addWidget(self.textbox1)
     self.layout.addWidget(self.textbox2)
     self.layout.addWidget(self.textbox3)
    
    
  7. 现在,我们将我们的自定义类的布局设置为创建的布局:

     self.setLayout(self.layout)
    
    
  8. 然后,我们创建在每次复选框切换时更改文本框的方法:

     # First checkbox
     def cb1_active(self, on):
     if on:
     self.textbox1.setText('Option 1 selected')
     else: self.textbox1.setText('') 
     # Second checkbox 
     def cb2_active(self, on):
     if on:
     self.textbox2.setText('Option 2 selected')
     else: self.textbox2.setText('') 
     # Third checkbox 
     def cb3_active(self, on):
     if on:
     self.textbox3.setText('Option 3 selected')
     else: self.textbox3.setText('')
    
    
  9. 现在,我们已准备好初始化我们的自定义类并显示对话框:

    buttons = CheckBox()
    buttons.show()
    
    
  10. 分别和同时切换复选框,然后验证文本框是否反映了这些变化。

如何工作...

文本框允许你验证当复选框切换时,你是否以编程方式捕获了复选框的信号。你也可以使用单个复选框作为只有两个选择的选项的布尔值。当你运行这个菜谱时,结果应该类似于以下截图:

如何工作...

创建标签

标签允许你将多个屏幕的信息压缩到相对较小的空间。标签在窗口顶部提供标题,当点击时,每个标题都呈现一个单独的小部件布局。在这个菜谱中,我们将创建一个简单的标签界面。

准备工作

通过选择插件菜单然后点击Python 控制台来打开 QGIS Python 控制台

如何做到这一点...

我们将创建一个总体的标签小部件。然后,我们将创建三个通用小部件来表示我们的标签。我们将设置包含三个不同 GUI 小部件的布局,并将每个布局分配给我们的标签小部件。最后,我们将添加我们的标签到标签小部件并显示它。为此,我们需要执行以下步骤:

  1. 首先,我们需要导入 GUI 和 QGIS 核心库:

    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    
    
  2. 接下来,我们创建我们的标签并配置其标题和大小:

    qtw = QTabWidget()
    qtw.setWindowTitle("PyQGIS Tab Example")
    qtw.resize(400,300)
    
    
  3. 现在,我们初始化我们的标签小部件:

    tab1 = QWidget()
    tab2 = QWidget()
    tab3 = QWidget()
    
    
  4. 然后,我们将设置一个包含丰富文本输入框的小部件和布局,使用 HTML 标签为我们的第一个标签加粗文本:

    layout1 = QVBoxLayout()
    layout1.addWidget(QTextEdit("<b>Type text here</b>"))
    tab1.setLayout(layout1)
    
    
  5. 现在,我们将为第二个标签页设置一个简单的按钮,遵循与第一个标签页相同的格式:

    layout2 = QVBoxLayout()
    layout2.addWidget(QPushButton("Button"))
    tab2.setLayout(layout2)
    
    
  6. 接下来,我们将为第三个标签创建小部件和布局,其中包含一个简单的文本标签:

    layout3 = QVBoxLayout()
    layout3.addWidget(QLabel("Label text example"))
    tab3.setLayout(layout3)
    
    
  7. 然后,我们将标签添加到标签窗口中:

    qtw.addTab(tab1, "First Tab")
    qtw.addTab(tab2, "Second Tab")
    qtw.addTab(tab3, "Third Tab")
    
    
  8. 最后,我们将显示标签窗口:

    qtw.show()
    
    
  9. 确认你可以点击每个标签并与小部件交互。

如何工作...

这个菜谱的关键是 QTabWidget() 方法。其他所有内容只是任意布局和小部件,这些最终都包含在标签小部件中。

注意

标签的一般规则是保持其中信息独立。

无法预测用户如何与标签界面交互,如果标签之间的信息相互依赖,则会出现问题。

引导用户通过向导

向导是一系列对话框,引导用户通过一系列步骤。向导每一页的信息可能以某种方式与其他页面的信息相关。在这个菜谱中,我们将创建一个简单的三页向导来收集用户的一些信息并将其显示给他们。

准备工作

通过选择插件菜单并点击Python 控制台来打开 QGIS Python 控制台

如何操作...

我们将创建三个类,每个类代表我们的向导页面。前两个页面将收集信息,第三个页面将向用户展示信息。我们将创建一个QWizard对象来将这些页面类连接起来。我们还将使用向导字段的概念在页面之间传递信息。

要做到这一点,我们需要执行以下步骤:

  1. 首先,我们导入 GUI 和 QGIS 核心库:

    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    
    
  2. 接下来,我们为向导的第一个页面创建类,并添加一个文本框以收集用户的uname变量:

    class Page1(QWizardPage):
     def __init__(self, parent=None):
     super(Page1, self).__init__(parent)
     self.setTitle("What's Your Name?")
     self.setSubTitle("Please enter your name.")
     self.label = QLabel("Name:")
     self.uname = QLineEdit("<enter your name>")
    
    
  3. 现在,我们注册uname字段,以便我们可以在稍后访问输入的值,而无需跟踪变量本身:

     self.registerField("uname", self.uname)
    
    
  4. 然后,我们设置页面的布局:

     layout = QVBoxLayout()
     layout.addWidget(self.label)
     layout.addWidget(self.uname)
     self.setLayout(layout)
    
    
  5. 接下来,我们将设置第二个页面的类:

    class Page2(QWizardPage):
     def __init__(self, parent=None):
     super(Page2, self).__init__(parent)
     self.setTitle("When's Your Birthday?")
     self.setSubTitle("Select Your Birthday.")
    
    
  6. 然后,我们将添加一个日历小部件以获取用户的生日:

     self.cal = QCalendarWidget()
    
    
  7. 我们将注册选定的日期作为一个field,以便稍后访问:

     self.registerField("cal", self.cal, "selectedDate")
    
    
  8. 然后,我们将为此页面设置布局:

     layout = QVBoxLayout()
     layout.addWidget(self.cal)
     self.setLayout(layout)
    
    
  9. 现在,我们已准备好设置第三个页面,该页面将展示用户信息。我们将使用简单的标签,这些标签将在下一步动态填充:

    class Page3(QWizardPage):
     def __init__(self, parent=None):
     super(Page3, self).__init__(parent)
     self.setTitle("About You")
     self.setSubTitle("Here is Your Information:")
     self.name_lbl = QLabel()
     self.date_lbl = QLabel()
     layout = QVBoxLayout()
     layout.addWidget(self.name_lbl)
     layout.addWidget(self.date_lbl)
     self.setLayout(layout)
    
    
  10. 现在,我们设置页面的初始化。我们首先访问从前一页注册的字段以获取用户输入:

     def initializePage(self):
     uname = self.field("uname")
     date = self.field("cal").toString()
    
    
  11. 然后,我们只需将这些值设置为标签的文本,使用 Python 字符串格式化即可:

     self.name_lbl.setText("Your name is %s" % uname)
     self.date_lbl.setText("Your birthday is %s" % date)
    
    
  12. 最后,我们创建我们的向导小部件,添加页面,并显示向导:

    wiz = QWizard()
    wiz.addPage(Page1())
    wiz.addPage(Page2())
    wiz.addPage(Page3())
    wiz.show() 
    
    

如何操作...

向导界面与标签页控件有许多相似之处,但也有一些重要区别。向导只允许用户根据页面顺序进行线性前进和后退。如果信息被注册为字段,它可以跨页面共享信息,这使得页面在向导的作用域内全局。然而,field()方法是一个受保护的方法,因此您的页面必须定义为从QWizardPage对象继承的类,以便注册的字段能够按预期工作。以下截图显示了向导的日历屏幕:

如何操作...

保持对话框在顶部

很容易失去 QGIS 前面的弹出窗口的跟踪。一旦用户将焦点转移到移动主 QGIS 应用程序窗口,您的对话框可能会消失在它后面,迫使用户重新排列整个桌面以再次找到较小的窗口。幸运的是,Qt 有一个窗口设置称为 hint,它允许您强制窗口保持在顶部。这种类型的对话框称为模式对话框。在这个菜谱中,我们将创建一个消息对话框并使用 hint 强制它保持在顶部。

准备工作

通过选择插件菜单并点击Python 控制台来打开 QGIS Python 控制台

如何操作...

在这个菜谱中,我们将创建一个简单的消息对话框并将其设置为保持在顶部,如下所示:

  1. 首先,我们导入 Qt GUI 和 QGIS 核心库:

    from PyQt4.QtGui import *
    from PyQt4.QtCore import *
    
    
  2. 接下来,我们创建消息的文本:

    msg = "   This window will always stay on top."
    
    
  3. 现在,我们创建我们的对话框并指定消息和提示:

    lbl = QLabel(msg, None, Qt.WindowStaysOnTopHint)
    
    
  4. 我们可以调整对话框的大小并显示它:

    lbl.resize(400,400)
    lbl.show()
    
    
  5. 点击主 QGIS 应用程序窗口以更改窗口焦点并验证对话框始终位于 QGIS 之上。

它是如何工作的...

这种简单技术可以帮助确保用户在继续之前先处理重要的对话框。

第八章:QGIS 工作流程

在本章中,我们将介绍以下食谱:

  • 创建 NDVI

  • 地理编码地址

  • 创建栅格足迹

  • 执行网络分析

  • 沿街道进行路由

  • 跟踪 GPS

  • 创建图集

  • 寻找最低成本路径

  • 执行最近邻分析

  • 创建热图

  • 创建点密度图

  • 收集现场数据

  • 使用高程数据计算道路坡度

  • 在地图上定位照片

  • 图像变化检测

简介

在本章中,我们将使用 Python 在 QGIS 中执行各种常见的地理空间任务,这些任务可能是完整的流程,或者是更大流程的关键部分。

创建 NDVI

归一化植被指数NDVI)是用于检测感兴趣区域内绿色植被的最古老的遥感算法之一,它使用图像的红光和近红外波段。植物中的叶绿素吸收可见光,包括红光波段,而植物细胞的结构反射近红外光。NDVI 公式提供了近红外光与总入射辐射的比率,这作为植被密度的指标。本食谱将使用 Python 控制 QGIS 栅格计算器,以使用农田的多光谱图像创建 NDVI。

准备工作

geospatialpython.googlecode.com/svn/farm-field.tif下载图像并将其放置在您的qgis_data目录下的rasters文件夹中。

如何操作...

我们将加载栅格作为 QGIS 栅格图层,执行 NDVI 算法,并最终将颜色渐变应用于栅格,以便我们能够轻松地可视化图像中的绿色植被。为此,我们需要执行以下步骤:

  1. 在 QGIS Python 控制台中,导入以下库:

    from PyQt4.QtGui import *
    from PyQt4.QtCore import *
    from qgis.analysis import *
    
    
  2. 现在,使用以下代码将栅格图像作为图层加载:

    rasterName = "farm"
    raster = QgsRasterLayer("/Users/joellawhead/qgis_data/\
    rasters/farm-field.tif", rasterName)
    
    
  3. 然后,使用以下代码在 QGIS 栅格计算器中为两个波段创建条目:

    ir = QgsRasterCalculatorEntry()
    r = QgsRasterCalculatorEntry()
    
    
  4. 现在,使用以下代码行,将栅格图层分配为每个计算器条目的栅格组件:

    ir.raster = raster
    r.raster = raster
    
    
  5. 为每个条目选择适当的波段,以便计算器使用我们需要的 NDVI 数据。红光和红外波段编号通常列在栅格的元数据中:

    ir.bandNumber = 2
    r.bandNumber = 1
    
    
  6. 接下来,使用特殊的 QGIS 命名约定为每个条目分配一个参考 ID,如以下示例所示,以图层的名称作为前缀,后跟一个@符号和波段编号作为后缀:

    ir.ref = rasterName + "@2"
    r.ref = rasterName + "@1"
    
    
  7. 使用以下代码构建栅格计算器表达式:

    references = (ir.ref, r.ref, ir.ref, r.ref)
    exp = "1.0 * (%s - %s) / 1.0 + (%s + %s)" % references
    
    
  8. 然后,指定 NDVI 图像的输出名称:

    output = "/Users/joellawhead/qgis_data/rasters/ndvi.tif"
    
    
  9. 通过定义栅格的范围、其列和行的宽度和高度以及我们在上一步中定义的栅格条目来设置其余栅格计算器调用的变量:

    e = raster.extent()
    w = raster.width()
    h = raster.height()
    entries = [ir,r]
    
    
  10. 现在,使用我们的表达式创建 NDVI:

    ndvi =  QgsRasterCalculator(exp, output, "GTiff", e, w, h, entries)
    ndvi.processCalculation()
    
    
  11. 接下来,将 NDVI 输出作为栅格图层加载:

    lyr = QgsRasterLayer(output, "NDVI")
    
    
  12. 我们必须对图像执行直方图拉伸,否则值之间的差异将难以看到。拉伸是通过 QGIS 对比增强算法来执行的:

    algorithm = QgsContrastEnhancement.StretchToMinimumMaximum
    limits = QgsRaster.ContrastEnhancementMinMax
    lyr.setContrastEnhancement(algorithm, limits)
    
    
  13. 接下来,构建一个颜色渐变着色器来着色 NDVI,如下所示:

    s = QgsRasterShader()
    c = QgsColorRampShader()
    c.setColorRampType(QgsColorRampShader.INTERPOLATED)
    
    
  14. 然后,为图像中的每种颜色添加条目。每个条目由一个下限值范围、一个颜色和一个标签组成。条目中的颜色将从下限值开始,直到遇到一个更高的值或最大值。请注意,我们将使用变量别名来表示 QGIS ColorRampItem 对象的极长名称:

    i = []
    qri = QgsColorRampShader.ColorRampItem
    i.append(qri(0, QColor(0,0,0,0), 'NODATA')) 
    i.append(qri(214, QColor(120,69,25,255), 'Lowest Biomass'))
    i.append(qri(236, QColor(255,178,74,255), 'Lower Biomass'))
    i.append(qri(258, QColor(255,237,166,255), 'Low Biomass'))
    i.append(qri(280, QColor(173,232,94,255), 'Moderate Biomass'))
    i.append(qri(303, QColor(135,181,64,255), 'High Biomass'))
    i.append(qri(325, QColor(3,156,0,255), 'Higher Biomass'))
    i.append(qri(400, QColor(1,100,0,255), 'Highest Biomass'))
    
    
  15. 现在,我们可以将条目添加到着色器并将其应用于图像:

    c.setColorRampItemList(i)
    s.setRasterShaderFunction(c)
    ps = QgsSingleBandPseudoColorRenderer(lyr.dataProvider(), 1, s)
    lyr.setRenderer(ps)
    
    
  16. 最后,将分类的 NDVI 图像添加到地图中以可视化它:

    QgsMapLayerRegistry.instance().addMapLayer(lyr)
    
    

它是如何工作的...

QGIS 栅格计算器正如其名所示。它允许你在图像上执行数组数学。QGIS 栅格菜单和处理工具箱都有几个栅格处理工具,但栅格计算器可以执行可以由单个数学方程定义的自定义分析。NDVI 算法是红外波段减去红波段除以红外波段加上红波段,即 (IR-R)/(IR+R)。在我们的计算器表达式中,我们将方程的两边都乘以 1.0 以避免除以零错误。如果你将结果加载到 QGIS 中,你的输出应该看起来类似于以下图像。在这个屏幕截图中,NODATA 值用黑色表示;然而,你的 QGIS 安装可能默认使用白色:

它是如何工作的...

地理编码地址

地理编码是将地址转换为地球坐标的过程。地理编码需要一个综合的数据集,将邮政编码、城市、街道和街道号码(或街道号码范围)与坐标关联起来。为了拥有适用于世界上任何地址且精度合理的地理编码器,你需要使用云服务,因为地理编码数据集非常密集,可能相当大。为超过几平方英里的任何区域创建地理编码数据集需要大量的资源。有几个服务可用,包括 Google 和 MapQuest。在 QGIS 中,通过 QGIS Python 地理编码插件访问这些服务是最简单的方法。在这个菜谱中,我们将使用此插件以编程方式对地址进行地理编码。

准备工作

为了进行这个练习,你需要安装由 Alessandro Pasotti 开发的 QGIS Python 地理编码插件,如下所示:

  1. 从 QGIS 插件 菜单中选择 管理并安装插件…

  2. 插件 对话框的搜索框中,搜索 地理编码

  3. 选择 地理编码 插件并点击 安装插件 按钮。

如何操作...

在这个菜谱中,我们将使用 Python 访问地理编码插件方法,向插件提供一个地址,并打印出结果坐标。为此,我们需要执行以下步骤:

  1. 在 QGIS Python 控制台 中,使用以下代码导入 OpenStreetMap 的 geoCoding 对象:

    from GeoCoding.geopy.geocoders import Nominatim
    
    
  2. 接下来,我们将创建我们的地理编码器:

    geocoder = Nominatim()
    
    
  3. 然后,使用以下代码,我们将对地址进行地理编码:

    location = geocoder.geocode("The Ugly Pirate, Bay Saint Louis, MS 39520")
    
    
  4. 最后,我们将打印结果以查看坐标:

    print location
    
    
  5. 检查是否在控制台打印出以下输出:

    (u'The Ugly Pirate, 144, Demontluzin Street, Bay St. Louis, Hancock County, Mississippi, 39520, United States of America', (30.3124059, -89.3281418))
    
    

它是如何工作的...

地理编码插件旨在与 QGIS GUI 界面一起使用。然而,像大多数 QGIS 插件一样,它是用 Python 编写的,我们可以通过 Python 控制台访问它。

小贴士

这个技巧并不适用于每个插件。有时,用户界面与插件的 GUI 结合得太紧密,以至于你无法在不触发 GUI 的情况下程序化地使用插件的方法。

然而,在大多数情况下,你可以使用插件不仅扩展 QGIS,还可以利用其强大的 Python API。如果你自己编写插件,考虑使其可访问 QGIS Python 控制台,以便使其更有用。

更多内容...

地理编码插件还提供 Google 地理编码引擎作为一项服务。请注意,Google 地图 API,包括地理编码,附带一些限制,可以在developers.google.com/maps-engine/documentation/limits找到。

创建栅格足迹

对由大量文件组成的栅格数据集进行编目的一种常见方法是通过创建一个包含每个栅格文件范围的多边形足迹的矢量数据集。矢量足迹文件可以轻松地加载到 QGIS 中或在网络上提供服务。本菜谱演示了一种从充满栅格文件目录创建足迹矢量的方法。我们将把这个程序作为一个处理工具箱脚本构建,这比构建 QGIS 插件更容易,并且提供了一个 GUI 和一个干净的编程 API。

准备工作

geospatialpython.googlecode.com/svn/scenes.zip下载示例栅格图像场景。将scenes目录解压缩到您的qgis_data目录下的名为rasters的目录中。

对于这个菜谱,我们将按照以下步骤创建一个新的处理工具箱脚本:

  1. 在 QGIS 处理工具箱中,展开脚本树菜单。

  2. 接下来,展开工具树菜单。

  3. 最后,双击创建新脚本项以打开处理脚本编辑器。

如何操作...

首先,我们将使用处理工具箱的命名约定,这将同时定义我们的 GUI 以及输入和输出变量。然后,我们将创建逻辑,该逻辑处理栅格目录并计算图像范围,最后我们将创建矢量文件。为此,我们需要执行以下步骤:

  1. 首先,我们使用注释来定义我们的输入变量,告诉 Processing Toolbox 在脚本被用户调用时将这些添加到 GUI 中。第一个条目定义了脚本的分组菜单,将我们的脚本放置在工具箱中,第二个条目定义了包含栅格的目录,第三个条目是我们 shapefile 的输出名称。脚本必须以这些注释开始。每个条目还声明了 Processing Toolbox API 允许的类型。这些注释中变量的名称对脚本可用:

    ##Vector=group
    ##Input_Raster_Directory=folder
    ##Output_Footprints_Vector=output vector
    
    
  2. 接下来,我们导入我们将需要的 Python 库,使用以下命令:

    import os
    from qgis.core import *
    
    
  3. 现在,我们获取栅格目录中的文件列表。以下脚本没有尝试根据类型过滤文件。如果目录中有其他类型的非栅格文件,它们也将被包括在内:

    files = os.listdir(Input_Raster_Directory)
    
    
  4. 然后,我们声明几个变量,它们将保存我们的栅格范围和坐标参考字符串,如下所示:

    footprints = []
    crs = ""
    
    
  5. 现在,我们遍历栅格,将它们作为栅格图层加载以获取它们的范围,将它们作为 Python 字典中的点数据存储,并将它们添加到我们的临时存储的足迹列表中。如果栅格无法处理,将使用 Processing Toolbox 进度对象发出警告:

    for f in files:
     try:
     fn = os.path.join(Input_Raster_Directory, f)
     lyr = QgsRasterLayer(fn, "Input Raster")
     crs = lyr.crs()
     e = lyr.extent()
     ulx = e.xMinimum()
     uly = e.yMaximum()
     lrx = e.xMaximum()
     lry = e.yMinimum()
     ul = (ulx, uly)
     ur = (lrx, uly)
     lr = (lrx, lry)
     ll = (ulx, lry)
     fp = {}
     points = []
     points.append(QgsPoint(*ul))
     points.append(QgsPoint(*ur))
     points.append(QgsPoint(*lr)) 
     points.append(QgsPoint(*ll)) 
     points.append(QgsPoint(*ul))
     fp["points"] = points
     fp["raster"] = fn
     footprints.append(fp)
     except:
     progress.setInfo("Warning: The file %s does not appear to be a \
    valid raster file." % f)
    
    
  6. 使用以下代码,我们将创建一个内存矢量图层来在写入 shapefile 之前构建足迹矢量:

    vectorLyr =  QgsVectorLayer("Polygon?crs=%s&field=raster:string(100)" \
    % crs, "Footprints" , "memory")
    vpr = vectorLyr.dataProvider()
    
    
  7. 现在,我们将我们的范围列表转换为要素:

    features = []
    for fp in footprints:
     poly = QgsGeometry.fromPolygon([fp["points"]])
     f = QgsFeature()
     f.setGeometry(poly)
     f.setAttributes([fp["raster"]])
     features.append(f)
    vpr.addFeatures(features)
    vectorLyr.updateExtents()
    
    
  8. 然后,我们将设置 shapefile 的文件驱动程序和 CRS:

    driver = "Esri Shapefile"
    epsg = crs.postgisSrid()
    srs = "EPSG:%s" % epsg
    
    
  9. 最后,我们将写入选定的输出文件,指定我们保存到磁盘的图层;输出文件的名称;文件编码,这可能会根据输入而变化;坐标参考系统;以及输出文件类型的驱动程序,在这种情况下是 shapefile:

    error = QgsVectorFileWriter.writeAsVectorFormat\ (vectorLyr, Output_Footprints_Vector, \"utf-8", srs, driver)
    if error == QgsVectorFileWriter.NoError:
     pass
    else:
     progress.setInfo("Unable to output footprints.")
    
    

它是如何工作的...

记住这一点很重要:Processing Toolbox 脚本可以在几个不同的环境中运行:作为一个 GUI 进程,例如插件,作为一个来自 Python 控制台的程序化脚本,一个 Python 插件,或者图形模型器框架。因此,遵循文档化的 Processing Toolbox API 非常重要,以确保它在这些所有环境中都能按预期工作。这包括定义清晰的输入和输出,并使用进度对象。进度对象是提供进度条和消息反馈给用户的正确方式。尽管 API 允许你定义让用户选择不同 OGR 和 GDAL 输出的输出,但目前似乎只支持 shapefile 和 GeoTiff。

还有更多...

Processing Toolbox 内的图形模型器工具允许你将不同的处理算法可视化地链接起来,以创建复杂的工作流程。另一个有趣的插件是 Processing Workflows 插件,它不仅允许你链接算法,还提供了一个带有用户说明的漂亮的选项卡界面,以帮助初学者通过复杂的地理空间工作流程。

下面的截图显示了在 OpenStreetMap 底图上的栅格足迹:

还有更多...

执行网络分析

网络分析允许您在定义的网络中找到两点之间的最有效路线。这些线可能代表街道、水系统中的管道、互联网或任何数量的连接系统。网络分析抽象出这个常见问题,以便相同的技术和算法可以应用于广泛的各类应用。在本菜谱中,我们将使用通用线网络,通过 Dijkstra 算法进行分析,这是用于找到最短路径的最古老算法之一。QGIS 内置了所有这些功能。

准备工作

首先,从以下链接下载矢量数据集,其中包含两个 shapefiles,并将其解压缩到qgis_data目录下的名为shapes的目录中:

geospatialpython.googlecode.com/svn/network.zip

如何操作...

我们将通过定义线网络的起点和终点来创建一个网络图,然后使用这个图来确定两点之间沿线网络的最短路线。为此,我们需要执行以下步骤:

  1. 在 QGIS Python 控制台中,我们首先导入所需的库,包括 QGIS 网络分析器:

    from qgis.core import *
    from qgis.gui import *
    from qgis.networkanalysis import *
    from PyQt4.QtCore import *
    
    
  2. 接下来,我们将加载我们的线网络 shapefile 以及包含我们想要网络分析器在选择路线时考虑的网络上的点的 shapefile:

    network = QgsVectorLayer("/Users/joellawhead/qgis_data/shapes/\Network.shp", "Network Layer", "ogr")
    waypoints = QgsVectorLayer("/Users/joellawhead/qgis_data/shapes/\ NetworkPoints.shp", "Waypoints", "ogr")
    
    
  3. 现在,我们将创建一个图导演来定义图的属性。director对象接受我们的线 shapefile、一个用于方向信息的字段 ID 以及一些其他涉及网络方向属性的文档化整数代码。在我们的例子中,我们将告诉导演忽略方向。properter对象是一种基本的路由策略算法,它被添加到网络图中,并考虑线长度:

    director = QgsLineVectorLayerDirector(network, -1, '', '', '', 3)
    properter = QgsDistanceArcProperter()
    director.addProperter(properter)
    crs = network.crs()
    
    
  4. 现在,我们创建GraphBuilder对象,将线网络实际转换为图:

    builder = QgsGraphBuilder(crs)
    
    
  5. 我们定义了我们的路线的起点和终点:

    ptStart = QgsPoint(-0.8095638694, -0.1578175511)
    ptStop = QgsPoint(0.8907435677, 0.4430834924)
    
    
  6. 然后,我们告诉导演将我们的点层转换为网络中的连接点,这些点定义了网络中的航路点,也可以选择提供阻力值:

    tiePoints = director.makeGraph(builder, [ptStart, ptStop])
    
    
  7. 现在,我们可以使用以下代码来构建图:

    graph = builder.graph()
    
    
  8. 我们现在将起点和终点定位为图中的连接点:

    tStart = tiePoints[0]
    tStop = tiePoints[1]
    idStart = graph.findVertex(tStart)
    idStop = graph.findVertex(tStop)
    
    
  9. 然后,我们可以告诉分析器使用我们的起点来找到网络中的最短路线:

    (tree, cost) = QgsGraphAnalyzer.dijkstra(graph, idStart, 0)
    
    
  10. 接下来,我们将遍历生成的树并获取输出路线上的点:

    p = []
    curPos = idStop
    while curPos != idStart:
    p.append(graph.vertex(graph.arc(tree[curPos]).inVertex()).point())
    curPos = graph.arc(tree[curPos]).outVertex()
    p.append(tStart)
    
    
  11. 现在,我们将加载我们的两个输入 shapefiles 到地图上,并创建一个橡皮筋来可视化路线:

    QgsMapLayerRegistry.instance().addMapLayers([network,waypoints])
    rb = QgsRubberBand(iface.mapCanvas())
    rb.setColor(Qt.red)
    
    
  12. 最后,我们将路线点添加到橡皮筋上,以便查看网络分析器的输出:

    for pnt in p:
     rb.addPoint(pnt)
    
    

它是如何工作的...

这个食谱是一个非常简单的例子,用作调查一个非常复杂且强大的工具的起点。线网络形状文件可以有一个字段定义每条线在某个方向上为单向或双向。点形状文件提供网络上的航点,以及阻力值,这些值可能代表海拔、交通密度或其他因素,这些因素会使路线不那么理想。输出将类似于以下图像:

如何工作...

更多信息和网络分析工具的示例可以在 QGIS 文档中找到,网址为 docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/network_analysis.html

沿街道进行路由

有时候,你可能想要找到两个地址之间的最佳驾驶路线。街道导航现在已经变得如此普遍,以至于我们把它视为理所当然。然而,如果你探索这本书中关于地理编码和网络分析的食谱,你将开始看到街道导航实际上是一个多么复杂的挑战。为了在 QGIS 中执行路由操作,我们将使用用 Python 编写的 QGIS GeoSearch 插件,这样我们就可以从控制台访问它。

准备工作

为了进行这个练习中的路由,你需要安装 QGIS Python GeoSearch 插件,以及 QGIS OpenLayers 插件来在 Google 地图上叠加结果,如下所示:

  1. 从 QGIS 插件菜单中选择 管理并安装插件…

  2. 如果你已经安装了 QGIS GeoCoding 插件,那么你必须卸载它,因为有时它与 GeoSearch 插件冲突。因此,在插件列表中选择此选项,并点击 卸载插件按钮。

  3. 插件对话框的搜索框中,搜索 GeoSearch

  4. 选择 GeoSearch 插件并点击 安装插件按钮。

  5. 接下来,在 插件搜索对话框中,搜索 OpenLayers

  6. 选择 OpenLayers 插件并点击 安装插件按钮。

如何操作...

我们将调用 GeoSearch 插件的路线功能,该功能使用 Google 的路线引擎,并通过 OpenLayers 插件在 Google 地图上显示结果。为此,我们需要执行以下步骤:

  1. QGIS Python 控制台中,我们首先导入 QGIS utils 库以及 GeoSearch 插件所需的部分:

    import qgis.utils
    from GeoSearch import geosearchdialog, GoogleMapsApi
    
    
  2. 接下来,我们将使用 QGIS utils 库来访问 OpenLayers 插件

    openLyrs = qgis.utils.plugins['openlayers_plugin']
    
    
  3. GeoSearch 插件并不是真正为程序化使用而设计的,因此为了调用此插件,我们必须通过 GUI 界面调用它,但然后我们需要传递空白值,这样它就不会触发 GUI 插件界面:

    g = geosearchdialog.GeoSearchDialog(iface)
    g.SearchRoute([])
    
    
  4. 现在,使用以下代码,我们可以安全地创建我们的路由引擎对象:

    d = GoogleMapsApi.directions.Directions()
    
    
  5. 接下来,我们创建我们的起点和终点地址:

    origin = "Boston, MA"
    dest = "2517 Main Rd, Dedham, ME 04429"
    
    
  6. 然后,我们可以使用最简单的选项来计算路线,如下所示:

    route = d.GetDirections(origin, dest, mode = "driving", \ waypoints=None, avoid=None, units="imperial")
    
    
  7. 现在,我们使用OpenLayers 插件将谷歌地图基础地图添加到 QGIS 地图中:

    layerType = openLyrs._olLayerTypeRegistry.getById(4)
    openLyrs.addLayer(layerType)
    
    
  8. 最后,我们使用GeoSearch 插件在基础地图上创建我们的路线图层:

    g.CreateVectorLayerGeoSearch_Route(route)
    
    

它是如何工作的...

尽管它们是用 Python 构建的,但 GeoSearch 和 OpenLayers 插件都不是为程序员使用 Python 而设计的。然而,我们仍然可以在脚本中不费太多麻烦地使用这些工具。为了利用 GeoSearch 插件提供的某些路由选项,您可以使用其 GUI 查看可用选项,然后将这些选项添加到您的脚本中。请注意,大多数插件没有真正的 API,因此插件在未来版本中的微小更改可能会破坏您的脚本。

跟踪 GPS

QGIS 能够连接到使用 NMEA 标准的 GPS。QGIS 可以通过串行连接到 GPS 或通过 QGIS GPS 信息面板使用名为 gpsd 的开源软件与之通信。GPS 的位置信息可以在 QGIS 地图上显示,QGIS 甚至可以自动平移地图以跟随 GPS 点。在这个菜谱中,我们将使用 QGIS API 处理 NMEA 语句并更新全球地图上的一个点。连接到不同 GPS 设备所需的信息可能会有很大差异,因此我们将使用在线 NMEA 语句生成器来获取一些模拟的 GPS 信息。

准备工作

这个菜谱不需要任何准备。

如何操作...

我们将从免费的在线生成器中抓取一批 NMEA GPS 语句,使用在线 geojson 数据创建一个全球基础地图,创建一个矢量点图层来表示 GPS,并最终遍历语句,使我们的轨迹点在地图上移动。

要做到这一点,我们需要执行以下步骤:

  1. 首先,我们需要使用 QGIS Python 控制台导入一些标准 Python 库:

    import urllib
    import urllib2
    import time
    
    
  2. 接下来,我们将连接到在线 NMEA 生成器,下载一批语句,并将它们转换成列表,如下所示:

    url = 'http://freenmea.net/api/emitnmea'
    values = {'types' : 'default'}
    data = urllib.urlencode(values)
    req = urllib2.Request(url, data)
    response = urllib2.urlopen(req)
    results = response.read().split("\n")
    
    
  3. 接下来,我们可以添加我们的世界国家基础地图,使用 geojson 服务:

    wb = "https://raw.githubusercontent.com/johan/world.geo.json/master/countries.geo.json"
    basemap = QgsVectorLayer(wb, "Countries", "ogr")
    qmr = QgsMapLayerRegistry.instance()
    qmr.addMapLayer(basemap)
    
    
  4. 现在,我们可以创建我们的 GPS 点图层并访问其数据提供者:

    vectorLyr = QgsVectorLayer('Point?crs=epsg:4326', \'GPS Point' , "memory")
    vpr = vectorLyr.dataProvider()
    
    
  5. 然后,我们需要一些变量来存储我们在遍历位置时的当前坐标,我们还将访问mapCanvas对象:

    cLat = None
    cLon = None
    canvas = iface.mapCanvas()
    
    
  6. 接下来,我们将创建一个 GPS 连接对象用于数据处理。如果我们使用的是实时 GPS 对象,我们将使用此行输入设备信息:

    c = QgsNMEAConnection(None)
    
    
  7. 现在,我们设置一个标志来决定我们是否正在处理第一个点:

    firstPt = True
    
    
  8. 我们现在可以遍历 NMEA 语句了,但我们必须检查语句类型,以确定我们正在使用哪种类型的信息。在实时 GPS 连接中,QGIS 会自动处理这部分,因此这部分代码将不再必要:

    for r in results:
     l = len(r)
     if "GGA" in r:
     c.processGGASentence(r,l)
     elif "RMC" in r:
     c.processRMCSentence(r,l)
     elif "GSV" in r:
     c.processGSVSentence(r,l)
     elif "VTG" in r:
     c.processVTGSentence(r,l)
     elif "GSA" in r:
     c.processGSASentence(r,l)
    
    
  9. 然后,我们可以获取当前的 GPS 信息:

     i=c.currentGPSInformation()
    
    
  10. 现在,在我们尝试更新地图之前,我们将检查这些信息,以确保 GPS 位置确实自上次循环以来发生了变化:

     if i.latitude and i.longitude:
     lat = i.latitude
     lon = i.longitude
     if lat==cLat and lon==cLon:
     continue
     cLat = lat
     cLon = lon
     pnt = QgsGeometry.fromPoint(QgsPoint(lon,lat))
    
    
  11. 现在我们有一个新点,我们检查这是否是第一个点,如果是,就将整个图层添加到地图中。否则,我们编辑图层并添加一个新功能,如下所示:

     if firstPt:
     firstPt = False
     f = QgsFeature()
     f.setGeometry(pnt)
     vpr.addFeatures([f])
     qmr.addMapLayer(vectorLyr)
     else:
     print lon, lat
     vectorLyr.startEditing()
     vectorLyr.changeGeometry(1,pnt)
     vectorLyr.commitChanges()
    
    
  12. 最后,我们刷新地图并观察跟踪点跳到新的位置:

     vectorLyr.setCacheImage(None)
     vectorLyr.updateExtents()
     vectorLyr.triggerRepaint()
     time.sleep(1)
    
    

它是如何工作的...

一个实时 GPS 将在地图上沿线性、增量路径移动。在这个配方中,我们使用了随机生成的点在世界各地跳跃,但概念是相同的。要连接实时 GPS,您需要首先使用 QGIS 的 GPS 信息 GUI 建立连接,或者至少获取正确的连接信息,然后使用 Python 来自动化后续操作。一旦您有了位置信息,您就可以轻松地使用 Python 操作 QGIS 地图。

还有更多...

NMEA 标准虽然老旧且广泛使用,但按照现代标准来说,它是一个设计不佳的协议。现在几乎每部智能手机都有 GPS,但它们并不使用 NMEA 协议。然而,几乎每个智能手机平台都有几个应用程序可以将手机的 GPS 输出为 NMEA 语句,这些语句可以被 QGIS 使用。在本章的后面部分,在收集现场数据的配方中,我们将演示另一种跟踪手机、GPS 或甚至数字设备的估计位置的方法,这种方法更简单,也更现代。

创建地图册

地图册是一个自动生成的文档,也可以称为地图集。地图册将数据集分解成更小的、更详细的地图,这些地图基于覆盖层,将大地图缩放到覆盖层中的每个特征,以便制作地图册的一页。覆盖层可能与地图册每页上展示的地图层相同,也可能不同。在这个配方中,我们将创建一个展示世界上所有国家的地图册。

准备工作

对于这个配方,您需要从geospatialpython.googlecode.com/svn/countries.zip下载世界国家数据集,并将其放入qgis_data目录中名为shapes的文件夹内。

接下来,您需要安装PyPDF2库。在 Linux 或 OS X 上,只需打开控制台并运行以下命令:

sudo easy_install PyPDF2

在 Windows 上,从开始菜单打开 OSGEO4W 控制台并运行以下命令:

easy_install PyPDF2

最后,在您的qgis_data目录中,创建一个名为atlas的文件夹来存储地图册的输出。

如何做到这一点...

我们将构建一个 QGIS 组合,并将其设置为地图册模式。然后,我们将添加一个组合地图,其中每个国家都将被展示,以及一个概述地图。接下来,我们将运行地图册过程,以生成地图册的每一页作为单独的 PDF 文件。最后,我们将单个 PDF 文件合并成一个 PDF 文件。为此,我们需要执行以下步骤:

  1. 首先,导入所有需要的库:

    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    from qgis.core import *
    import PyPDF2
    import os
    
    
  2. 接下来,创建与输出文件相关的变量,包括地图册的名称、覆盖层以及单个 PDF 文件的命名模式:

    filenames = []
    mapbook = "/Users/joellawhead/qgis_data/atlas/mapbook.pdf"
    coverage = "/Users/joellawhead/qgis_data/shapes/countries.shp"
    atlasPattern = "/Users/joellawhead/qgis_data/atlas/output_"
    
    
  3. 现在,使用以下代码将覆盖层添加到地图中:

    vlyr = QgsVectorLayer(coverage, "Countries", "ogr")
    QgsMapLayerRegistry.instance().addMapLayer(vlyr)
    
    
  4. 接下来,建立地图渲染器:

    mr = QgsMapRenderer()
    mr.setLayerSet([vlyr.id()])
    mr.setProjectionsEnabled(True)
    mr.setMapUnits(QGis.DecimalDegrees)
    crs = QgsCoordinateReferenceSystem()
    crs.createFromSrid(4326)
    mr.setDestinationCrs(crs)
    
    
  5. 然后,设置组合:

    c = QgsComposition(mr)
    c.setPaperSize(297, 210)
    
    
  6. 为覆盖层创建一个符号:

    gray = {"color": "155,155,155"}
    mapSym = QgsFillSymbolV2.createSimple(gray)
    renderer = QgsSingleSymbolRendererV2(mapSym)
    vlyr.setRendererV2(renderer)
    
    
  7. 现在,将第一个作曲地图添加到组合中,如下所示:

    atlasMap = QgsComposerMap(c, 20, 20, 130, 130)
    atlasMap.setFrameEnabled(True)
    c.addComposerMap(atlasMap)
    
    
  8. 然后,创建图集框架:

    atlas = c.atlasComposition()
    atlas.setCoverageLayer(vlyr)
    atlas.setHideCoverage(False)
    atlas.setEnabled(True)
    c.setAtlasMode(QgsComposition.ExportAtlas)
    
    
  9. 接下来,建立概述地图:

    ov = QgsComposerMap(c, 180, 20, 50, 50)
    ov.setFrameEnabled(True)
    ov.setOverviewFrameMap(atlasMap.id())
    c.addComposerMap(ov)
    rect = QgsRectangle(vlyr.extent())
    ov.setNewExtent(rect)
    
    
  10. 然后,创建概述地图符号:

    yellow = {"color": "255,255,0,255"}
    ovSym = QgsFillSymbolV2.createSimple(yellow)
    ov.setOverviewFrameMapSymbol(ovSym)
    
    
  11. 接下来,你需要用国家的名称标记每一页,这个名称存储在 shapefile 的CNTRY_NAME字段中:

    lbl = QgsComposerLabel(c)
    c.addComposerLabel(lbl)
    lbl.setText('[% "CNTRY_NAME" %]')
    lbl.setFont(QgsFontUtils.getStandardTestFont())
    lbl.adjustSizeToText()
    lbl.setSceneRect(QRectF(150, 5, 60, 15))
    
    
  12. 现在,我们将告诉图集为每个国家使用自动缩放,以便最佳地适应每个地图窗口:

    atlasMap.setAtlasDriven(True)
    atlasMap.setAtlasScalingMode(QgsComposerMap.Auto)
    atlasMap.setAtlasMargin(0.10)
    
    
  13. 现在,我们告诉图集遍历所有特征并创建 PDF 地图,如下所示:

    atlas.setFilenamePattern("'%s' || $feature" % atlasPattern)
    atlas.beginRender()
    for i in range(0, atlas.numFeatures()):
     atlas.prepareForFeature(i)
     filename = atlas.currentFilename() + ".pdf"
     print "Writing file %s" % filename
     filenames.append(filename)
     c.exportAsPDF(filename)
    atlas.endRender()
    
    
  14. 最后,我们将使用 PyPDF2 库将单个 PDF 文件合并成一个 PDF 文件,如下所示:

    output = PyPDF2.PdfFileWriter()
    for f in filenames:
     pdf = open(f, "rb")
     page = PyPDF2.PdfFileReader(pdf)
     output.addPage(page.getPage(0))
     os.remove(f)
    print "Writing final mapbook..."
    book = open(mapbook, "wb")
    output.write(book)
    with open(mapbook, 'wb') as book:
     output.write(book)
    
    

它是如何工作的...

你可以根据需要自定义创建单个页面的模板。GUI 图集工具可以将图集导出为单个文件,但这个功能在 PyQIS 中不可用,所以我们使用纯 Python 的 PyPDF2 库。你还可以在 GUI 中创建模板,保存它,然后用 Python 加载它,但如果你在代码中已经有了布局,通常更容易进行更改。你还应该知道,PDF 页面只是图像。地图以栅格形式导出,因此图集将不可搜索,文件大小可能很大。

寻找最低成本路径

最低成本路径LCP)分析是网络分析的栅格等效,用于在栅格中找到两点之间的最优路径。在这个菜谱中,我们将对数字高程模型(DEM)执行 LCP 分析。

准备工作

你需要下载以下 DEM 并将 ZIP 文件解压到你的qgis_data/rasters目录:geospatialpython.googlecode.com/svn/lcp.zip

如何做到这一点...

我们将加载我们的 DEM 和包含起点和终点的两个 shapefile。然后,我们将通过处理工具箱使用 GRASS 来创建累积成本层,该层根据栅格中每个单元格的高度、周围其他单元格的值以及其到和从终点的距离为每个栅格单元格分配一个成本。

然后,我们将使用 SAGA 处理算法在两点之间找到最低成本路径。最后,我们将输出加载到地图上。为此,我们需要执行以下步骤:

  1. 首先,我们将导入 QGIS 处理 Python 库:

    import processing
    
    
  2. 现在,我们将设置层的路径,如下所示:

    path = "/Users/joellawhead/qgis_data/rasters"/"
    dem = path + "dem.asc"
    start = path + "start-point.shp"
    finish = path + "end-point.shp"
    
    
  3. 我们需要将 DEM 的范围作为字符串用于算法:

    demLyr = QgsRasterLayer(dem, "DEM")
    ext = demLyr.extent()
    xmin = ext.xMinimum()
    ymin = ext.yMinimum()
    xmax = ext.xMaximum()
    ymax = ext.xMaximum()
    box = "%s,%s,%s,%s" % (xmin,xmax,ymin,ymax)
    
    
  4. 使用以下代码,我们将端点作为层来建立:

    a = QgsVectorLayer(start, "Start", "ogr")
    b = QgsVectorLayer(finish, "End", "ogr")
    
    
  5. 然后,我们将创建累积成本栅格,指定算法名称、成本层(DEM)、起点层、终点层、速度或精度选项、保留空值选项、感兴趣的范围、单元格大小(默认为 0)和一些其他默认值:

    tmpCost = processing.runalg("grass:r.cost",dem,a,b,\
    False,False,box,0,-1,0.0001,None)
    cost = tmpCost["output"]
    
    
  6. 我们还需要将点合并成一个单独的层,以便 SAGA 算法使用:

    tmpMerge = processing.runalg("saga:mergeshapeslayers",\start,finish,None)
    merge = tmpMerge["OUT"]
    
    
  7. 接下来,我们为 LCP 算法设置输入和输出:

    vLyr = QgsVectorLayer(merge, "Destination Points", "ogr")
    rLyr = QgsRasterLayer(cost, "Accumulated Cost")
    line = path + "path.shp"
    
    
  8. 然后,我们使用以下代码运行 LCP 分析:

    results = processing.runalg("saga:leastcostpaths",\lyr,rLyr,demLyr,None,line)
    
    
  9. 最后,我们可以加载路径来查看它:

    path = QgsVectorLayer(line, "Least Cost Path", "ogr")
    QgsMapLayerRegistry.instance().addMapLayers([demLyr, \ vLyr, path])
    
    

它是如何工作的...

GRASS 也有 LCP 算法,但 SAGA 算法更容易使用。GRASS 在创建成本网格方面做得很好。处理工具箱算法允许您创建在 QGIS 关闭时删除的临时文件。因此,我们使用临时文件来处理中间产品,包括成本网格和合并后的形状文件。

执行邻近邻分析

邻近邻分析将一个点与一个或多个数据集中的最近点相关联。在这个菜谱中,我们将一组点与另一个数据集中的最近点相关联。在这种情况下,我们将找到国家不明飞行物(UFO)报告中心目录中每个不明飞行物目击事件的最近大城市。这项分析将告诉您哪些大城市有最多的不明飞行物活动。不明飞行物目录数据仅包含经纬度点,因此我们将使用邻近邻分析来为地点分配名称。

准备工作

下载以下 ZIP 文件并将其解压到 qgis_data 目录下的名为 ufo 的目录中:

geospatialpython.googlecode.com/svn/ufo.zip

您还需要 MMQGIS 插件:

  1. 从 QGIS 插件 菜单中选择 管理并安装插件…

  2. 插件 对话框的搜索框中,搜索 mmqgis

  3. 选择 MMQGIS 插件 并点击 安装插件 按钮。

如何操作...

这个菜谱很简单。在这里,我们将在 MMQGIS 插件中加载图层并运行邻近邻算法,如下所示:

  1. 首先,我们将导入 MMQGIS 插件:

    from mmqgis import mmqgis_library as mmqgis
    
    
  2. 接下来,如图所示,我们将加载所有数据集:

    srcPath = "/qgis_data/ufo/ufo-sightings.shp"
    dstPath = "/qgis_data/ufo/major-cities.shp"
    usPth = "/qgis_data/ufo/continental-us.shp"
    output = "/qgis_data/ufo/alien_invasion.shp"
    srcName = "UFO Sightings"
    dstName = "Major Cities"
    usName = "Continental US"
    source = QgsVector(srcPath, srcName, "ogr")
    dest = QgsVector(dstPath, dstName, "ogr")
    us = QgsVector(usPath, usName, "ogr")
    
    
  3. 最后,我们将运行并加载算法,该算法将从每个不明飞行物目击点绘制到最近城市的线条:

    mmqgis.mmqgis_hub_distance(iface, srcName, dstName, \"NAME", "Miles", True, output, True)
    
    

它是如何工作的...

QGIS 中有几个不同的邻近邻算法,但 MMQGIS 版本是一个优秀的实现,并且具有最好的可视化。与其他章节中的其他菜谱一样,插件没有故意设计的 Python API,因此探索其功能的一个好方法是使用 GUI 界面,然后再查看 Python 代码。以下图像显示了输出,其中不明飞行物目击点由较小的点表示,而连接到城市的枢纽线则由较大、较暗的点表示。

它是如何工作的...

创建热力图

热力图用于使用显示密度的栅格图像来显示数据的地理聚类。聚类也可以使用数据中的一个字段进行加权,不仅显示地理密度,还可以显示强度因子。在这个菜谱中,我们将使用地震点数据来创建地震影响的热力图,并通过地震的震级来加权聚类。

准备工作

这个菜谱不需要准备。

如何操作...

我们将使用 GeoJSON 格式创建一个包含全球国家边界和地震位置的地图,接下来,我们将运行 SAGA 的核密度估计算法来生成热力图图像。我们将从输出中创建一个图层,为其添加颜色着色器,并将其添加到地图中。

要执行此操作,我们需要执行以下步骤:

  1. 首先,我们将导入 Python 控制台中需要的 Python 库:

    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    import processing
    
    
  2. 接下来,使用以下代码,我们将定义我们的地图图层和输出栅格名称:

    countries = "https://raw.githubusercontent.com/johan/\world.geo.json/master/countries.geo.json"
    quakes = "https://geospatialpython.googlecode.com/\svn/quakes2014.geojson"
    output = "/Users/joellawhead/qgis_data/rasters/heat.tif"
    
    
  3. 现在我们将图层添加到地图中:

    basemap = QgsVectorLayer(countries, "World", "ogr")
    quakeLyr = QgsVectorLayer(quakes, "Earthquakes", "ogr")
    QgsMapLayerRegistry.instance().addMapLayers([quakeLyr, basemap])
    
    
  4. 我们需要获取地震图层的范围,以便 Processing 工具箱算法可以使用:

    ext = quakeLyr.extent()
    xmin = ext.xMinimum()
    ymin = ext.yMinimum()
    xmax = ext.xMaximum()
    ymax = ext.xMaximum()
    box = "%s,%s,%s,%s" % (xmin,xmax,ymin,ymax)
    
    
  5. 现在,我们可以通过指定 mag 或震级字段作为权重因子来运行核密度估计算法:

    processing.runalg("saga:kerneldensityestimation",quakeLyr,"mag",10,0,0,box,1,output)
    
    
  6. 接下来,我们将输出加载为图层:

    heat = QgsRasterLayer(output, "Earthquake Heatmap")
    
    
  7. 然后,我们创建颜色渐变着色器并将其应用于图层:

    algorithm = QgsContrastEnhancement.StretchToMinimumMaximum
    limits = QgsRaster.ContrastEnhancementMinMax
    heat.setContrastEnhancement(algorithm, limits)
    s = QgsRasterShader() 
    c = QgsColorRampShader() 
    c.setColorRampType(QgsColorRampShader.INTERPOLATED) 
    i = [] 
    qri = QgsColorRampShader.ColorRampItem
    i.append(qri(0, QColor(255,255,178,255), \
    'Lowest Earthquake Impact')) 
    i.append(qri(0.106023, QColor(254,204,92,255), \
    'Lower Earthquake Impact')) 
    i.append(qri(0.212045, QColor(253,141,60,255), \
    'Moderate Earthquake Impact')) 
    i.append(qri(0.318068, QColor(240,59,32,255), \
    'Higher Earthquake Impact'))
    i.append(qri(0.42409, QColor(189,0,38,255), \
    'Highest Earthquake Impact')) 
    c.setColorRampItemList(i) 
    s.setRasterShaderFunction(c) 
    ps = QgsSingleBandPseudoColorRenderer(heat.dataProvider(),\ 1,  s) 
    heat.setRenderer(ps) 
    
    
  8. 最后,我们将 Heatmap 添加到我们的地图中:

    QgsMapLayerRegistry.instance().addMapLayers([heat])
    
    

如何工作...

核密度估计算法会查看点数据集并形成簇。值越高,簇的密度就越大。然后,算法根据权重因子(即地震的震级)增加值。输出图像当然是灰度 GeoTIFF,但我们使用颜色渐变着色器来使可视化更容易理解。以下截图显示了预期的输出:

如何工作...

更多内容...

QGIS 提供了一个功能强大的插件,名为热力图,它能够自动在各种数据上良好地工作。然而,它是用 C++ 编写的,并且没有 Python API。

创建点密度图

点密度图使用点密度来表示多边形内的字段值。我们将使用这项技术来表示美国人口普查局的一些区域的人口密度。

准备工作

您需要从 geospatialpython.googlecode.com/files/GIS_CensusTract.zip 下载人口区域图层并将其提取到 qgis_data 目录下的 census 目录中。

如何操作...

我们将加载人口图层,创建一个内存图层,遍历人口图层的要素,为每 100 人计算一个特征内的随机点,并将点添加到内存图层中。为此,我们需要执行以下步骤:

  1. 在 QGIS 的 Python 控制台 中,我们将导入 random 模块:

    import random
    
    
  2. 接下来,我们将加载人口图层:

    src = "/Users/joellawhead/qgis_data/census/\
    GIS_CensusTract_poly.shp"
    tractLyr = QgsVectorLayer(src, "Census Tracts", "ogr")
    
    
  3. 然后,我们将创建我们的内存图层:

    popLyr =  QgsVectorLayer('Point?crs=epsg:4326', "Population" , "memory")
    
    
  4. 我们需要人口值的索引:

    i = tractLyr.fieldNameIndex('POPULAT11')
    
    
  5. 现在,我们以迭代器的方式获取人口图层的特点:

    features = tractLyr.getFeatures()
    
    
  6. 我们需要一个数据提供者来为内存图层提供支持,以便我们可以编辑它:

    vpr = popLyr.dataProvider()
    
    
  7. 我们将创建一个列表来存储我们的随机点:

    dotFeatures = []
    
    
  8. 然后,我们可以遍历要素并计算密度点:

    for feature in features:
     pop = feature.attributes()[i]
     density = pop / 100
     found = 0
     dots = []
     g = feature.geometry()
     minx =  g.boundingBox().xMinimum()
     miny =  g.boundingBox().yMinimum()
     maxx =  g.boundingBox().xMaximum()
     maxy =  g.boundingBox().yMaximum()
     while found < density:
     x = random.uniform(minx,maxx)
     y = random.uniform(miny,maxy)
     pnt = QgsPoint(x,y)
     if g.contains(pnt):
     dots.append(pnt)
     found += 1
     geom = QgsGeometry.fromMultiPoint(dots)
     f = QgsFeature()
     f.setGeometry(geom)
     dotFeatures.append(f)
    
    
  9. 现在,我们可以使用以下代码将我们的要素添加到内存图层中,并将它们添加到地图中以查看结果:

    vpr.addFeatures(dotFeatures)
    popLyr.updateExtents()
    QgsMapLayerRegistry.instance().addMapLayers(\ [popLyr,tractLyr])
    
    

如何工作...

这种方法稍微有些低效;它使用了一种暴力方法,可能会将随机生成的点放置在不规则的多边形之外。我们使用要素的范围来尽可能包含随机点,然后使用几何包含方法来验证点是否在多边形内部。以下截图显示了输出样本:

工作原理...

收集现场数据

几十年来,从现场收集观测数据并将其输入 GIS 系统需要花费数小时的手动录入,或者在最好情况下,在旅行结束后加载数据。智能手机和具有蜂窝连接的笔记本电脑彻底改变了这一过程。在本教程中,我们将使用一个简单但有趣的基于 geojson 的框架,通过任何具有网络浏览器的互联网连接设备输入信息和地图位置,并在 QGIS 中更新地图。

准备工作

本教程不需要任何准备工作。

如何操作...

我们将在 QGIS 地图上加载世界边界图层和现场数据图层,访问现场数据移动网站创建一个条目,然后刷新 QGIS 地图以查看更新。为此,我们需要执行以下步骤:

  1. 在 QGIS 的Python 控制台中,添加以下 geojson 图层:

    wb = "https://raw.githubusercontent.com/johan/\ world.geo.json/master/countries.geo.json"
    basemap = QgsVectorLayer(wb, "Countries", "ogr")
    observations = \
    QgsVectorLayer("http://bit.ly/QGISFieldApp", \
    "Field Observations", "ogr")
    QgsMapLayerRegistry.instance().addMapLayers(\ [basemap, observations])
    
    
  2. 现在,在您的计算机上的浏览器中,或者在具有数据连接的移动设备上,访问geospatialpython.github.io/qgis/fieldwork.html。应用程序将请求您允许使用您的位置,您应该暂时允许程序运行。

  3. 在表单中输入信息,然后点击发送按钮。

  4. 验证您是否可以在api.myjson.com/bins/3ztvz看到 geojson 数据,包括您的提交。

  5. 最后,通过缩放或平移更新 QGIS 中的地图,并定位您的记录。

工作原理...

 code for the mobile page on GitHub.com (https://github.com/GeospatialPython/qgis).

以下图像显示了 iPhone 上的移动现场应用程序:

工作原理...

此图像显示了在 QGIS 中对应数据的显示方式:

工作原理...

使用高程数据计算道路坡度

常见的地理空间工作流程是将栅格值分配给相应的矢量图层,以便可以对矢量图层进行样式设置或进行进一步分析。本教程将使用这一概念来展示如何使用坡度栅格将值映射到道路矢量图层,以表示道路的陡峭程度。

准备工作

您需要从geospatialpython.googlecode.com/svn/road.zip下载一个压缩目录,并将名为road的目录放置在您的qgis_data目录中。

如何操作...

我们将从 DEM 开始,计算其坡度。然后,我们将加载一个道路矢量层,并将其分割成 500 米的区间长度。接下来,我们将加载该层,并使用每个段落的绿色、黄色和红色值对其进行样式化,以显示坡度的范围。我们将在 DEM 的阴影图上叠加这个层,以获得良好的可视化效果。为此,我们需要执行以下步骤:

  1. 首先,我们需要在 QGIS Python 控制台中导入 QGIS 处理模块、QGIS 常量模块、Qt GUI 模块和 os 模块:

    from qgis.core import *
    from PyQt4.QtGui import *
    import processing
    
    
  2. 现在,我们需要将我们项目的坐标参考系统(CRS)设置为我们的数字高程模型(DEM)的 CRS,即 EPSG 代码 26910,这样我们就可以以米为单位而不是十进制度数来处理数据:

    myCrs = QgsCoordinateReferenceSystem(26910, QgsCoordinateReferenceSystem.EpsgCrsId)
    iface.mapCanvas().mapRenderer().setDestinationCrs(myCrs)
    iface.mapCanvas().setMapUnits(QGis.Meters)
    iface.mapCanvas().refresh()
    
    
  3. 现在,我们将设置所有层的路径。为此,我们将使用我们创建的中间层,这样如果需要,我们可以在一个地方更改它们:

    src_dir = "/Users/joellawhead/qgis_data/road/" 
    dem = os.path.join(src_dir, "dem.asc")
    road = os.path.join(src_dir, "road.shp")
    slope = os.path.join(src_dir, "slope.tif")
    segRoad = os.path.join(src_dir, "segRoad.shp")
    steepness = os.path.join(src_dir, "steepness.shp")
    hillshade = os.path.join(src_dir, "hillshade.tif") 
    
    
  4. 我们将加载 DEM 和道路层,以便我们可以获取处理算法的范围:

    demLyr = QgsRasterLayer(dem, "DEM")
    roadLyr = QgsVectorLayer(road, "Road", "ogr")
    
    
  5. 现在,使用以下代码构建一个包含 DEM 范围的字符串:

    ext = demLyr.extent()
    xmin = ext.xMinimum()
    ymin = ext.yMinimum()
    xmax = ext.xMaximum()
    ymax = ext.yMaximum()
    demBox = "%s,%s,%s,%s" % (xmin,xmax,ymin,ymax)
    
    
  6. 接下来,计算坡度网格:

    processing.runalg("grass:r.slope",dem,0,0,1,0,True,\ demBox,0,slope)
    
    
  7. 然后,我们可以将道路层的范围作为一个字符串获取:

    ext = roadLyr.extent()
    xmin = ext.xMinimum()
    ymin = ext.yMinimum()
    xmax = ext.xMaximum()
    ymax = ext.yMaximum()
    roadBox = "%s,%s,%s,%s" % (xmin,xmax,ymin,ymax)
    
    
  8. 现在,我们将道路层分割成 500 米的段落,以便为坡度评估提供一个有意义的长度:

    processing.runalg("grass:v.split.length",road,500,\
    roadBox,-1,0.0001,0,segRoad)
    
    
  9. 接下来,我们将坡度和分割层添加到地图界面中,以便进行下一个算法,但我们将使用addMapLayers方法中的布尔False选项将它们隐藏起来:

    slopeLyr = QgsRasterLayer(slope, "Slope")
    segRoadLyr = QgsVectorLayer(segRoad, \
    "Segmented Road", "ogr")
    QgsMapLayerRegistry
    .instance().addMapLayers([\ segRoadLyr,slopeLyr], False)
    
    
  10. 现在,我们可以将坡度值传输到分割道路层,以创建坡度层:

    processing.runalg("saga:addgridvaluestoshapes",\ segRoad,slope,0,steepness)
    
    
  11. 现在,我们可以加载坡度层:

    steepLyr = QgsVectorLayer(steepness, \ "Road Gradient", "ogr")
    
    
  12. 我们将样式化坡度层,使用交通信号灯的红色、黄色和绿色值,其中红色表示最陡:

    roadGrade = ( ("Rolling Hill", 0.0, 20.0, "green"), 
    ("Steep", 20.0, 40.0, "yellow"),
    ("Very Steep", 40.0, 90.0, "red"))
    ranges = []
    for label, lower, upper, color in roadGrade:
     sym = QgsSymbolV2.defaultSymbol(steepLyr.geometryType())
     sym.setColor(QColor(color))
     sym.setWidth(3.0)
     rng = QgsRendererRangeV2(lower, upper, sym, label)
     ranges.append(rng)
    
    field = "slope"
    renderer = QgsGraduatedSymbolRendererV2(field, ranges)
    steepLyr.setRendererV2(renderer)
    
    
  13. 接下来,我们将从 DEM 创建阴影图以进行可视化,并将所有内容加载到地图上:

    processing.runalg("saga:analyticalhillshading",dem,\
    0,315,45,4,hillshade)
    hs = QgsRasterLayer(hillshade, "Terrain")
    QgsMapLayerRegistry.instance().addMapLayers([steepLyr, hs])
    
    

它是如何工作的...

对于我们每个 500 米的线段,算法会平均其下方的坡度值。这个工作流程相当简单,同时也为你提供了一个更复杂版本所需的所有构建块。在执行涉及相对较小区域测量的计算时,使用投影数据是最佳选择。以下图像显示了输出效果:

它是如何工作的...

在地图上定位照片

配备 GPS 的相机拍摄的照片,包括智能手机,在文件的头部存储位置信息,这种格式称为 EXIF 标签。这些标签在很大程度上基于 TIFF 图像标准使用的相同头部标签。在这个菜谱中,我们将使用这些标签为一些照片创建地图上的位置,并提供链接以打开它们。

准备工作

您需要从github.com/GeospatialPython/qgis/blob/gh-pages/photos.zip?raw=true下载一些带有地理标签的样本照片,并将它们放置在qgis_data目录中名为photos的目录中。

如何操作...

QGIS 需要Python 图像库PIL),它应该已经包含在您的安装中。PIL 可以解析 EXIF 标签。我们将收集照片的文件名,解析位置信息,将其转换为十进制度,创建点矢量图层,添加照片位置,并将操作链接添加到属性中。为此,我们需要执行以下步骤:

  1. 在 QGIS Python 控制台中,导入我们将需要的库,包括用于解析图像数据的 k 库和用于执行通配符文件搜索的glob模块:

    import glob
    import Image
    from ExifTags import TAGS
    
    
  2. 接下来,我们将创建一个可以解析标题数据的函数:

    def exif(img):
     exif_data = {}
     try: 
     i = Image.open(img)
     tags = i._getexif()
     for tag, value in tags.items():
     decoded = TAGS.get(tag, tag)
     exif_data[decoded] = value
     except:
     pass
     return exif_data
    
    
  3. 现在,我们将创建一个可以将度-分-秒转换为十进制度数的函数,这是 JPEG 图像中存储坐标的方式:

    def dms2dd(d, m, s, i):
     sec = float((m * 60) + s)
     dec = float(sec / 3600)
     deg = float(d + dec)
     if i.upper() == 'W':
     deg = deg * -1
     elif i.upper() == 'S':
     deg = deg * -1
     return float(deg)
    
    
  4. 接下来,我们将定义一个函数来解析标题数据中的位置数据:

    def gps(exif):
     lat = None
     lon = None
     if exif['GPSInfo']: 
     # Lat
     coords = exif['GPSInfo']
     i = coords[1]
     d = coords[2][0][0]
     m = coords[2][1][0]
     s = coords[2][2][0]
     lat = dms2dd(d, m ,s, i)
     # Lon
     i = coords[3]
     d = coords[4][0][0]
     m = coords[4][1][0]
     s = coords[4][2][0]
     lon = dms2dd(d, m ,s, i)
     return lat, lon
    
    
  5. 接下来,我们将遍历photos目录,获取文件名,解析位置信息,并构建一个简单的字典来存储信息,如下所示:

    photos = {}
    photo_dir = "/Users/joellawhead/qgis_data/photos/"
    files = glob.glob(photo_dir + "*.jpg")
    for f in files:
     e = exif(f)
     lat, lon = gps(e)
     photos[f] = [lon, lat]
    
    
  6. 现在,我们将设置用于编辑的矢量图层:

    lyr_info = "Point?crs=epsg:4326&field=photo:string(75)" 
    vectorLyr =  QgsVectorLayer(lyr_info, \"Geotagged Photos" , "memory")
    vpr = vectorLyr.dataProvider()
    
    
  7. 我们将向矢量图层添加照片详细信息:

    features = []
    for pth, p in photos.items():
     lon, lat = p
     pnt = QgsGeometry.fromPoint(QgsPoint(lon,lat))
     f = QgsFeature()
     f.setGeometry(pnt)
     f.setAttributes([pth])
     features.append(f)
    vpr.addFeatures(features)
    vectorLyr.updateExtents()
    
    
  8. 现在,我们可以将图层添加到地图并将它设置为活动图层:

    QgsMapLayerRegistry.instance().addMapLayer(vectorLyr)
    iface.setActiveLayer(vectorLyr)
    activeLyr = iface.activeLayer()
    
    
  9. 最后,我们将添加一个允许您点击并打开照片的操作:

    actions = activeLyr.actions() 
    actions.addAction(QgsAction.OpenUrl, "Photos", \'[% "photo" %]')
    
    

如何工作...

使用包含的 PIL EXIF 解析器,获取位置信息并将其添加到矢量图层相对简单。此菜谱的有趣部分是 QGIS 打开照片的操作。此操作是打开 URL 的默认选项。但是,您也可以使用 Python 表达式作为操作来执行各种任务。以下截图显示了数据可视化和照片弹出窗口的示例:

如何工作...

还有更多...

另一个名为 Photo2Shape 的插件可用,但它需要您安装外部 EXIF 标签解析器。

图像变化检测

变化检测允许您在两个图像正确正射校正的同一区域自动突出显示它们之间的差异。在此菜谱中,我们将对两个相隔数年的图像进行简单的差异变化检测,以查看城市发展和自然环境的变化。

准备工作

您可以从github.com/GeospatialPython/qgis/blob/gh-pages/change-detection.zip?raw=true下载此菜谱的两个图像,并将它们放入qgis_data目录下的rasters目录中名为change-detection的目录中。请注意,该文件大小为 55 兆字节,因此下载可能需要几分钟。

如何操作...

我们将使用 QGIS 栅格计算器来减去图像以获取差异,这将突出显示显著的变化。我们还将向输出添加颜色渐变着色器以可视化变化。为此,我们需要执行以下步骤:

  1. 首先,我们需要将所需的库导入到 QGIS 控制台:

    from PyQt4.QtGui import *
    from PyQt4.QtCore import *
    from qgis.analysis import *
    
    
  2. 现在,我们将为我们的图像设置路径名和光栅名:

    before = "/Users/joellawhead/qgis_data/rasters/change-detection/before.tif"
    after = "/Users/joellawhead/qgis_data/rasters/change-detection/after.tif"
    beforeName = "Before"
    afterName = "After"
    
    
  3. 接下来,我们将图像设置为光栅图层:

    beforeRaster = QgsRasterLayer(before, beforeName)
    afterRaster = QgsRasterLayer(after, afterName)
    
    
  4. 然后,我们可以构建计算器条目:

    beforeEntry = QgsRasterCalculatorEntry()
    afterEntry = QgsRasterCalculatorEntry()
    beforeEntry.raster = beforeRaster
    afterEntry.raster = afterRaster
    beforeEntry.bandNumber = 1
    afterEntry.bandNumber = 2
    beforeEntry.ref = beforeName + "@1"
    afterEntry.ref = afterName + "@2"
    entries = [afterEntry, beforeEntry]
    
    
  5. 现在,我们将设置一个简单的表达式,用于进行遥感计算:

    exp = "%s - %s" % (afterEntry.ref, beforeEntry.ref)
    
    
  6. 然后,我们可以设置输出文件路径、光栅范围以及像素宽度和高度:

    output = "/Users/joellawhead/qgis_data/rasters/change-detection/change.tif"
    e = beforeRaster.extent()
    w = beforeRaster.width()
    h = beforeRaster.height()
    
    
  7. 现在,我们进行计算:

    change = QgsRasterCalculator(exp, output, "GTiff", e, w, h, entries)
    change.processCalculation()
    
    
  8. 最后,我们将输出加载为图层,创建颜色渐变着色器,将其应用于图层,并将其添加到地图中,如图所示:

    lyr = QgsRasterLayer(output, "Change")
    algorithm = QgsContrastEnhancement.StretchToMinimumMaximum
    limits = QgsRaster.ContrastEnhancementMinMax
    lyr.setContrastEnhancement(algorithm, limits)
    s = QgsRasterShader() 
    c = QgsColorRampShader() 
    c.setColorRampType(QgsColorRampShader.INTERPOLATED) 
    i = [] 
    qri = QgsColorRampShader.ColorRampItem
    i.append(qri(0, QColor(0,0,0,0), 'NODATA')) 
    i.append(qri(-101, QColor(123,50,148,255), 'Significant Itensity Decrease')) 
    i.append(qri(-42.2395, QColor(194,165,207,255), 'Minor Itensity Decrease')) 
    i.append(qri(16.649, QColor(247,247,247,0), 'No Change'))
    i.append(qri(75.5375, QColor(166,219,160,255), 'Minor Itensity Increase')) 
    i.append(qri(135, QColor(0,136,55,255), 'Significant Itensity Increase'))
    c.setColorRampItemList(i) 
    s.setRasterShaderFunction(c) 
    ps = QgsSingleBandPseudoColorRenderer(lyr.dataProvider(), 1,  s) 
    lyr.setRenderer(ps) 
    QgsMapLayerRegistry.instance().addMapLayer(lyr)
    
    

它是如何工作的...

概念很简单。我们从新图像数据中减去旧图像数据。专注于城市区域往往具有较高的反射性,导致图像像素值较高。如果在新图像中添加了一座建筑,它将比周围环境更亮。如果移除了一座建筑,新图像在该区域将变暗。这在一定程度上也适用于植被。

第九章。其他技巧和窍门

在本章中,我们将介绍以下配方:

  • 从 QGIS 地图中创建瓦片

  • 将图层添加到 geojson.io

  • 根据规则渲染地图图层

  • 创建图层样式文件

  • 在 PyQGIS 中使用 NULL 值

  • 使用生成器进行图层查询

  • 使用 alpha 值显示数据密度

  • 使用 __geo_interface__ 协议

  • 沿着线生成点

  • 使用基于表达式的标签

  • 在 QGIS 中创建动态表单

  • 计算所有选中线的长度

  • 使用与地图不同的状态栏 CRS

  • 在 QGIS 中创建 HTML 标签

  • 在 QGIS 中使用 OpenStreetMap 的兴趣点

  • 使用 WebGL 在 3D 中可视化数据

  • 在地球上可视化数据

简介

本章提供了有趣的、有价值的 QGIS Python 技巧,这些技巧没有包含在其他章节的任何主题中。每个配方都有一个特定的目的,但在许多情况下,一个配方可能展示了多个您在其他程序中会发现有用的概念。本章中的所有配方都在 QGIS Python 控制台中运行。

从 QGIS 地图中创建瓦片

此配方从您的 QGIS 地图中创建一组互联网网络地图瓦片。这个配方的有趣之处在于,一旦生成了静态地图瓦片,您可以在本地或从任何可访问的 Web 目录使用客户端浏览器的 JavaScript 提供它们,而无需地图服务器,或者您可以将它们(例如,在可移动的 USB 驱动器上分发)。

准备工作

您需要从 geospatialpython.googlecode.com/svn/countries.zip 下载压缩的 shapefile。

将 shapefile 解压缩到 qgis_data 目录中名为 shapes 的目录。接下来,在 qgis_data 目录中创建一个名为 tilecache 的目录。您还需要使用 QGIS 插件管理器 安装 QTiles 插件。此插件是实验性的,所以请确保在 QGIS 插件管理器的 设置 选项卡中勾选了 显示实验性插件 复选框。

如何做到这一点...

我们将加载 shapefile 并随机着色每个国家。然后,我们将使用 Python 操作 QTiles 插件以生成 5 个缩放级别的地图瓦片。为此,我们需要执行以下步骤:

  1. 首先,我们需要导入所有必要的 Python 库,包括 QTiles 插件:

    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    import qtiles
    import random
    
    
  2. 现在,我们创建一个可以产生随机颜色的颜色函数。此函数接受混合颜色,默认为白色,以改变整个颜色调的基调:

    def randomColor(mix=(255,255,255)):
     red = random.randrange(0,256)
     green = random.randrange(0,256)
     blue = random.randrange(0,256)
     r,g,b = mix
     red = (red + r) / 2
     green = (green + g) / 2
     blue = (blue + b) / 2
     return (red, green, blue)
    
    
  3. 接下来,我们将创建一个简单的回调函数,用于在瓦片生成完成后通知。此函数通常用于创建消息栏或其他通知,但我们将保持简单:

    def done():
     print "FINISHED!!"
    
    
  4. 现在,我们设置 shapefile 的路径和瓦片的输出方向:

    shp = "/qgis_data/shapes/countries.shp"
    dir = "/qgis_data/tilecache"
    
    
  5. 然后,我们加载 shapefile:

    layer = QgsVectorLayer(shp, "Countries", "ogr")
    
    
  6. 之后,我们定义用于着色国家的字段:

    field = 'CNTRY_NAME'
    
    
  7. 现在,我们需要获取所有要素,以便我们可以遍历它们:

    features = layer.getFeatures()
    
    
  8. 我们将构建我们的颜色渲染器:

    categories = []
    for feature in features:
     country = feature[field]
     sym = QgsSymbolV2.defaultSymbol(layer.geometryType())
     r,g,b = randomColor()
     sym.setColor(QColor(r,g,b,255))
     category = QgsRendererCategoryV2(country, sym, country)
     categories.append(category) 
    
    
  9. 然后,我们将设置图层渲染器并将其添加到地图中:

    renderer = QgsCategorizedSymbolRendererV2(field, categories)
    layer.setRendererV2(renderer)
    QgsMapLayerRegistry.instance().addMapLayer(layer)
    
    
  10. 现在,我们将设置图像瓦片所需的所有属性,包括地图元素和图像属性:

    canvas = iface.mapCanvas()
    layers = canvas.mapSettings().layers()
    extent = canvas.extent()
    minZoom = 0
    maxZoom = 5
    width = 256
    height = 256
    transp = 255
    quality = 70
    format = "PNG"
    outputPath = QFileInfo(dir)
    rootDir = "countries"
    antialiasing = False
    tmsConvention = True
    mapUrl = False
    viewer = True
    
    
  11. 我们准备好使用QTiles插件的效率线程系统生成瓦片。我们将创建一个线程对象,并传递之前提到的所有瓦片设置:

    tt = qtiles.tilingthread.TilingThread(layers, extent, minZoom, maxZoom, width, height, transp,
    quality, format, outputPath, rootDir, antialiasing, tmsConvention,
    mapUrl, viewer)
    
    
  12. 然后,我们可以将完成信号连接到我们的简单回调函数:

    tt.processFinished.connect(done)
    
    
  13. 最后,我们开始瓦片处理过程:

    tt.start()
    
    
  14. 一旦收到完成消息,检查输出目录并验证是否存在名为countries.html的 HTML 文件和一个名为countries的目录。

  15. 双击countries.html页面,在浏览器中打开它。

  16. 一旦地图加载,点击左上角的加号符号(+)两次以放大地图。

  17. 接下来,四处移动以查看您的地图瓦片版本加载。

它是如何工作的...

使用此插件,您可以生成多达 16 个缩放级别。在八个缩放级别之后,瓦片生成过程会花费很长时间,瓦片集在文件系统上变得相当大,总计数百兆字节。避免创建大量文件的一种方法是用mbtiles格式,它将所有数据存储在一个文件中。但是,您需要一个使用 GDAL 的 Web 应用程序来访问它。

注意

您可以在存储在github.io网络目录中的输出菜谱的示例中看到工作效果,网址为geospatialpython.github.io/qgis/tiles/countries.html

以下图像显示了浏览器中的输出:

它是如何工作的...

将图层添加到 geojson.io

云服务已成为常态,地理空间地图也不例外。这个菜谱使用一个名为 geojson.io 的服务,该服务提供在线矢量图层,您可以使用 Python 从 QGIS 上传。

准备工作

对于这个菜谱,您需要使用QGIS 插件管理器安装qgisio插件。

您还需要从geospatialpython.googlecode.com/svn/union.zip获取一个在测地坐标系(WGS84)中的 shapefile。

解压缩 ZIP 文件并将其放置在您的qgis_data目录中,命名为shapes

如何操作...

我们将使用临时文件将我们的 shapefile 转换为 GeoJSON。然后,我们将使用 Python 调用qgisio插件,以便将数据上传到在线显示。为此,我们需要执行以下步骤:

  1. 首先,我们需要导入所有相关的 Python 库:

    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    from qgis.core import *
    from tempfile import mkstemp
    import os
    from qgisio import geojsonio
    
    
  2. 现在,我们设置图层并获取图层的名称:

    layer = QgsVectorLayer("/qgis_data/shapes/building.shp", "Building", "ogr")
    name = layer.name()
    
    
  3. 接下来,我们使用 Python 的 tempfile 模块创建一个用于 GeoJSON 转换的临时文件:

    handle, tmpfile = mkstemp(suffix='.geojson')
    os.close(handle)
    
    
  4. 现在,我们将建立转换所需的坐标系,这必须是 WGS84 地理坐标系,以便与云服务协同工作:

    crs = QgsCoordinateReferenceSystem(4326,
    QgsCoordinateReferenceSystem.PostgisCrsId)
    
    
  5. 接下来,我们可以将图层以 GeoJSON 格式输出:

    error = QgsVectorFileWriter.writeAsVectorFormat(layer, tmpfile,
     "utf-8", crs, "GeoJSON", onlySelected=False)
    
    
  6. 然后,我们可以确保转换没有出现任何问题:

    if error != QgsVectorFileWriter.NoError:
     print "Unable to write geoJSON!"
    
    
  7. 现在,我们可以读取 GeoJSON 内容:

    with open(str(tmpfile), 'r') as f:
     contents = f.read()
    
    
  8. 然后,我们需要删除临时文件:

    os.remove(tmpfile)
    
    
  9. 我们现在可以使用qgisio模块将我们的 GeoJSON 上传到 geojson.io:

    url = geojsonio._create_gist(contents, "Layer exported from QGIS", name + ".geojson")
    
    
  10. 然后,我们可以使用 Qt 库在浏览器中打开地图:

    QDesktopServices.openUrl(QUrl(url))
    
    

如何工作...

这个配方实际上使用了两个云服务。GeoJSON 数据存储在名为 Gist 的github.com服务上,该服务允许您存储代码片段,如JSON。geojson.io 服务可以读取 Gist 中的数据。

注意

注意,有时生成可用的 URL 可能需要几秒钟到几分钟。

这张截图显示了 geojson.io 上 OSM 地图上的建筑图层,GeoJSON 显示在地图旁边:

如何工作...

还有更多...

有一些额外的先进服务可以用于 QGIS 地图,包括www.QGISCloud.comwww.CartoDB.com,它们也可以显示栅格地图。这两个服务都有免费选项和 QGIS 插件。然而,如果您尝试将地图发布到 Web 作为工作流程的一部分进行自动化,那么从 Python 脚本化这些服务会困难得多。

根据规则渲染地图图层

渲染规则提供了一种强大的方式来控制图层相对于其他图层或图层本身的属性如何以及何时显示。使用基于规则的渲染器,本配方演示了如何根据属性对图层进行着色编码。

准备工作

您需要从geospatialpython.googlecode.com/svn/ms_rails_mstm.zip下载一个压缩的 shapefile。

解压它并将其放置在您的qgis_data目录下名为ms的目录中。

在同一目录中,下载并解压以下 shapefile:

geospatialpython.googlecode.com/files/Mississippi.zip

最后,还需要将此 shapefile 添加到目录中:

geospatialpython.googlecode.com/svn/jackson.zip

如何做到这一点...

我们将设置一个铁路图层,然后我们将设置我们的规则作为 Python 元组,根据使用频率对其进行着色编码。最后,我们将添加一些其他图层作为参考。为此,我们需要执行以下步骤:

  1. 首先,我们需要导入 QTGui 库来处理颜色:

    from PyQt4.QtGui import *
    
    
  2. 接下来,我们将设置我们的数据路径以避免重复输入。将此字符串替换为您的qgis_data目录路径:

    prefix = "/Users/joellawhead/qgis_data/ms/"
    
    
  3. 现在,我们可以加载我们的铁路图层:

    rails = QgsVectorLayer(prefix + "ms_rails_mstm.shp", "Railways", "ogr")
    
    
  4. 然后,我们可以将我们的规则定义为一系列元组。每个规则定义了一个标签和一个表达式,详细说明了哪些属性值构成了该规则,一个颜色名称,以及描述的特征在最小/最大地图比例值上可见:

    rules = (
     ('Heavily Used', '"DEN09CODE" > 3', 'red', (0, 6000000)),
     ('Moderately Used', '"DEN09CODE" < 4 AND "DEN09CODE" > 1', 'orange', (0, 1500000)),
     ('Lightly Used', '"DEN09CODE" < 2', 'grey', (0, 250000)),
    )
    
    
  5. 接下来,我们创建一个基于规则的渲染器和基础符号,以便开始应用我们的规则:

    sym_rails = QgsSymbolV2.defaultSymbol(rails.geometryType())
    rend_rails = QgsRuleBasedRendererV2(sym_rails)
    
    
  6. 规则基于根规则分层,因此我们首先必须访问根规则:

    root_rule = rend_rails.rootRule()
    
  7. 现在,我们将遍历我们的规则,克隆默认规则,并将我们的自定义规则追加到树中:

    for label, exp, color, scale in rules:
     # create a clone (i.e. a copy) of the default rule
     rule = root_rule.children()[0].clone()
     # set the label, exp and color
     rule.setLabel(label)
     rule.setFilterExpression(exp)
     rule.symbol().setColor(QColor(color))
     # set the scale limits if they have been specified
     if scale is not None:
     rule.setScaleMinDenom(scale[0])
     rule.setScaleMaxDenom(scale[1])
    # append the rule to the list of rules
     root_rule.appendChild(rule)
    
    
  8. 我们现在可以删除默认规则,该规则不属于我们的渲染方案:

    root_rule.removeChildAt(0)
    
    
  9. 现在,我们将渲染器应用到我们的rails图层上:

    rails.setRendererV2(rend_rails)
    
    
  10. 我们将建立一个城市图层,它将提供一个焦点以便我们放大,这样我们就可以轻松地看到基于比例的渲染效果:

    jax = QgsVectorLayer(prefix + "jackson.shp", "Jackson", "ogr")
    jax_style = {}
    jax_style['color'] = "#ffff00"
    jax_style['name'] = 'regular_star'
    jax_style['outline'] = '#000000'
    jax_style['outline-width'] = '1'
    jax_style['size'] = '8'
    sym_jax = QgsSimpleMarkerSymbolLayerV2.create(jax_style)
    jax.rendererV2().symbols()[0].changeSymbolLayer(0, sym_jax)
    
    
  11. 然后,我们将为两个数据集周围设置并样式化一个边框层:

    ms = QgsVectorLayer(prefix + "mississippi.shp", "Mississippi", "ogr")
    ms_style = {}yea
    ms_style['color'] = "#F7F5EB"
    sym_ms = QgsSimpleFillSymbolLayerV2.create(ms_style)
    ms.rendererV2().symbols()[0].changeSymbolLayer(0, sym_ms)
    
    
  12. 最后,我们将所有内容添加到地图中:

    QgsMapLayerRegistry.instance().addMapLayers([jax, rails, ms])
    
    

如何工作...

规则是符号和表达式的分层集合。符号是符号层的集合。这个配方相对简单,但包含超过 50 行代码。渲染是 QGIS 中编码最复杂的特性之一。然而,规则也有它们自己的属性集,与图层和符号分开。请注意,在这个配方中,我们能够为规则设置标签和过滤器,这些属性通常被分配给图层。将规则视为单独图层的一种方式。我们可以通过将我们的铁路图层作为每个规则的新图层来加载,以实现相同的功能。规则是分割单个图层渲染的更紧凑方式。

此图像显示了在所有规则输出都可见的比例尺下的渲染效果:

如何工作...

创建图层样式文件

图层样式是 QGIS Python API 中最复杂的方面之一。一旦为图层开发出样式,通常将样式保存到 XML 格式的QGIS 标记语言QML)中是非常有用的。

准备工作

您需要下载名为saveqml的压缩目录,并将其解压缩到您的qgis_data/rasters目录中,链接为geospatialpython.googlecode.com/svn/saveqml.zip

如何做到这一点...

我们将为 DEM 创建一个颜色渐变,并将其设置为半透明,以叠加 DEM 的阴影 tif 图像。我们将创建的样式保存到 QML 文件中。为此,我们需要执行以下步骤:

  1. 首先,我们需要以下 Python Qt 库:

    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    
    
  2. 接下来,我们将加载我们的两个栅格图层:

    hs = QgsRasterLayer("/qgis_data/saveqml/hillshade.tif", "Hillshade")
    dem = QgsRasterLayer("/qgis_data/saveqml/dem.asc", "DEM")
    
    
  3. 接下来,我们将对 DEM 执行直方图拉伸以获得更好的可视化效果:

    algorithm = QgsContrastEnhancement.StretchToMinimumMaximum
    limits = QgsRaster.ContrastEnhancementMinMax
    dem.setContrastEnhancement(algorithm, limits)
    
    
  4. 现在,我们将创建一个基于 DEM 高程值的视觉上令人愉悦的颜色渐变,并将其作为渲染器应用到图层上:

    s = QgsRasterShader() 
    c = QgsColorRampShader() 
    c.setColorRampType(QgsColorRampShader.INTERPOLATED) 
    i = [] 
    qri = QgsColorRampShader.ColorRampItem
    i.append(qri(356.334, QColor(63,159,152,255), '356.334')) 
    i.append(qri(649.292, QColor(96,235,155,255), '649.292')) 
    i.append(qri(942.25, QColor(100,246,174,255), '942.25')) 
    i.append(qri(1235.21, QColor(248,251,155,255), '1235.21'))
    i.append(qri(1528.17, QColor(246,190,39,255), '1528.17')) 
    i.append(qri(1821.13, QColor(242,155,39,255), '1821.13'))
    i.append(qri(2114.08, QColor(165,84,26,255), '2114.08'))
    i.append(qri(2300, QColor(236,119,83,255), '2300'))
    i.append(qri(2700, QColor(203,203,203,255), '2700'))
    c.setColorRampItemList(i) 
    s.setRasterShaderFunction(c) 
    ps = QgsSingleBandPseudoColorRenderer(dem.dataProvider(), 1, s)
    ps.setOpacity(0.5) 
    dem.setRenderer(ps)
    
    
  5. 现在,我们可以将图层添加到地图中:

    QgsMapLayerRegistry.instance().addMapLayers([dem, hs])
    
    
  6. 最后,通过这一行,我们可以将 DEM 的样式保存到可重用的 QML 文件中:

    dem.saveNamedStyle("/qgis_data/saveqml/dem.qml")
    
    

如何工作...

QML 格式易于阅读,并且可以手动编辑。saveNamedStyle()方法在矢量图层上以完全相同的方式工作。您无需对前面的代码进行样式化,只需使用loadNamedStyle()方法引用 QML 文件即可:

dem.loadNamedStyle("/qgis_data/saveqml/dem.qml")

如果您将 QML 文件与 shapefile 一起保存,并使用相同的文件名(带有.qml扩展名),那么当加载 shapefile 时,QGIS 将自动加载样式:

在 PyQGIS 中使用 NULL 值

QGIS 可以使用 NULL 值作为字段值。Python 没有 NULL 值的概念。它最接近的类型是None类型。当在 QGIS 中使用 Python 时,您必须注意这一点。在这个菜谱中,我们将探讨 QGIS 的 NULL 值在 Python 中的影响。NULL 值的计算涉及一个指针,该指针是一个未初始化、未定义、空或无意义的值。

准备工作

在您的qgis_data/shapes目录中,从geospatialpython.googlecode.com/svn/NullExample.zip下载 shapefile,该 shapefile 包含一些 NULL 字段值,并将其解压缩。

如何操作...

我们将加载 shapefile 并获取其第一个要素。然后,我们将访问其一个 NULL 字段值。接下来,我们将运行一些测试,让您看到 NULL 值在 Python 中的行为。为此,我们需要执行以下步骤:

  1. 首先,我们将加载 shapefile 并访问其第一个要素:

    lyrPth = "/qgis_data/shapes/NullExample.shp"
    lyr = QgsVectorLayer(lyrPth, "Null Field Example", "ogr")
    features = lyr.getFeatures()
    f = features.next()
    
    
  2. 接下来,我们将获取一个 NULL 字段值:

    value = f["SAMPLE"]
    
    
  3. 现在,我们将检查 NULL 值的类型:

    print "Check python value type:"
    print type(value)
    
    
  4. 然后,我们将查看该值是否是 Python 的None类型:

    print "Check if value is None:"
    print value is None
    
    
  5. 现在,我们将查看它是否等同于None

    print "Check if value == None:"
    print value == None
    
    
  6. 接下来,我们将查看该值是否与 QGIS 的 NULL 类型匹配:

    print "Check if value == NULL:"
    print value == NULL
    
    
  7. 然后,我们将查看它是否实际上是 NULL:

    print "Check if value is NULL:"
    print value is NULL
    
    
  8. 最后,我们将与 QGIS 的 NULL 进行类型匹配:

    print "Check type(value) is type(NULL):"
    print type(value) is type(NULL)
    
    

它是如何工作的...

如您所见,NULL 值的类型是PyQt4.QtCore.QPyNullVariant。这个类是注入到 PyQt 框架中的一个特殊类型。需要注意的是,使用is运算符和==运算符比较时,返回的值可能不同。您应该了解这些差异,以避免代码中出现意外结果。

使用生成器进行图层查询

Python 生成器提供了一种高效处理大数据集的方法。一位名为 Nathan Woodrow 的 QGIS 开发者创建了一个简单的 Python QGIS 查询引擎,该引擎使用生成器轻松地从 QGIS 图层中检索特征。我们将在这个菜谱中使用这个引擎来查询图层。

准备工作

您需要使用easy_install或下载并添加到您的 QGIS Python 安装中安装查询引擎。要使用easy_install,请在控制台中运行以下命令,这将下载原始代码的克隆,包括 Python 设置文件:

easy_install 

github.com/GeospatialPython/qquery/archive/master.zip

您也可以从github.com/NathanW2/qquery/archive/master.zip下载 ZIP 文件,并将其内容复制到您的工作目录或 QGIS Python 安装的site-packages目录中。

您还需要从以下位置下载压缩的 shapefile 并将其解压缩到qgis_data目录下的ms目录中:

geospatialpython.googlecode.com/files/MS_UrbanAnC10.zip

如何操作...

我们将加载包含人口数据的图层。然后,我们将使用查询引擎执行一个针对人口少于 50,000 的城市区域的简单查询。我们将过滤结果,只提供三个列,地点名称、人口水平和土地面积。为此,我们需要执行以下步骤:

  1. 首先,我们导入查询引擎模块:

    from query import query
    
    
  2. 然后,我们设置到我们的 shapefile 的路径并将其加载为一个矢量图层:

    pth = "/Users/joellawhead/qgis_data/ms/MS_UrbanAnC10.shp"
    layer = QgsVectorLayer(pth, "Urban Areas", "ogr")
    
    
  3. 现在,我们可以运行查询,该查询使用 Python 的点表示法执行 where 子句搜索,然后使用 select 语句进行过滤。这一行将返回一个包含结果的生成器:

    q = (query(layer).where("POP > 50000").select('NAME10', "POP", "AREALAND", "POPDEN"))
    
    
  4. 最后,我们将使用查询生成器迭代到第一个结果:

    q().next()
    
    

它是如何工作的...

如您所见,这个模块非常实用。要使用默认的 PyQGIS API 执行此相同查询,代码量将几乎是现在的四倍。

使用 alpha 值来显示数据密度

主题地图通常使用基于单一颜色的颜色渐变来显示数据密度。较暗的颜色表示对象浓度较高,而较浅的颜色表示浓度较低。您也可以使用透明度渐变来显示密度。如果您想在影像或其他矢量图层上叠加密度层,这种技术很有用。在这个配方中,我们将使用一些熊目击数据来显示一个区域内熊的浓度。我们将使用 alpha 值来显示密度。我们将使用不寻常的六边形网格来划分区域,并使用基于规则的渲染器来构建显示。

准备工作

您需要安装 MMQGIS 插件,该插件用于使用 QGIS 插件管理器构建六边形网格。

您还需要从 geospatialpython.googlecode.com/svn/bear-data.zip 下载熊的数据,解压缩 shapefile,并将其放入 qgis_data 目录下的 ms 目录。

如何做到这一点...

我们将加载熊的数据。然后,我们将使用 MMQGIS 插件生成六边形网格。然后,我们将使用处理工具箱将六边形裁剪到熊 shapefile,并将 shapefile 属性数据连接到六边形网格。最后,我们将使用基于规则的渲染器根据熊目击密度应用 alpha 值并将结果添加到地图上。为此,我们需要执行以下步骤:

  1. 首先,我们导入所有需要的库,包括处理引擎、用于颜色管理的 PyQt GUI 库和 MMQGIS 插件:

    import processing
    from PyQt4.QtGui import *
    from mmqgis import mmqgis_library as mmqgis
    
    
  2. 接下来,我们将设置所有输入和输出 shapefile 的路径:

    dir = "/qgis_data/ms/"
    source = dir + "bear-data.shp"
    grid = dir + "grid.shp"
    clipped_grid = dir + "clipped_grid.shp"
    output = dir + "ms-bear-sightings.shp"
    
    
  3. 现在,我们可以设置输入 shapefile 作为图层:

    layer = QgsVectorLayer(source, "bear data", "ogr")
    
    
  4. 我们需要 shapefile 的范围来创建网格以及地图单位中的宽度和高度:

    e = layer.extent()
    llx = e.xMinimum()
    lly = e.yMinimum()
    w = e.width()
    h = e.height()
    
    
  5. 现在,我们可以使用 MMQGIS 插件在整个 shapefile 范围内生成网格。我们将使用十分之一的度(大约 6 英里)的网格单元大小:

    mmqgis.mmqgis_grid(iface, grid, .1, .1, w, h, llx, lly, "Hexagon (polygon)", False)
    
    
  6. 然后,我们可以使用处理工具箱将网格裁剪到源数据的形状:

    processing.runalg("qgis:clip",grid,source,clipped_grid)
    
    
  7. 接下来,我们需要进行空间连接,以便根据县匹配源数据的属性到每个网格单元:

    processing.runalg("qgis:joinbylocation",source,clipped_grid,0,"sum,mean,min,max,median",0,0,output)
    
    
  8. 现在,我们可以将此输出作为一个图层添加:

    bears = QgsVectorLayer(output, "Bear Sightings", "ogr")
    
    
  9. 接下来,我们将创建我们的渲染规则集作为 Python 元组,指定符号之间的标签、值表达式、颜色和 alpha 级别(介于 0 和 1):

    rules = (
     ('RARE', '"BEARS" < 5', (227,26,28,255), .2),
     ('UNCOMMON', '"BEARS" > 5 AND "BEARS" < 15', (227,26,28,255), .4),
     ('OCCASIONAL', '"BEARS" > 14 AND "BEARS" < 50', (227,26,28,255), .6),
     ('FREQUENT', '"BEARS" > 50', (227,26,28,255), 1),
    )
    
    
  10. 我们然后创建默认符号规则渲染器并将规则添加到渲染器中:

    sym_bears = QgsFillSymbolV2.createSimple({"outline_color":"white","outline_width":".26"}) 
    rend_bears = QgsRuleBasedRendererV2(sym_bears)
    root_rule = rend_bears.rootRule()
    for label, exp, color, alpha in rules:
     # create a clone (i.e. a copy) of the default rule
    rule = root_rule.children()[0].clone()
     # set the label, exp and color
    rule.setLabel(label)
    rule.setFilterExpression(exp)
    r,g,b,a = color
    rule.symbol().setColor(QColor(r,g,b,a))
     # set the transparency level
    rule.symbol().setAlpha(alpha)
     # append the rule to the list of rules
    root_rule.appendChild(rule)
    
    
  11. 我们移除了默认规则:

    root_rule.removeChildAt(0)
    
    
  12. 我们将渲染器应用到图层上:

    bears.setRendererV2(rend_bears)
    
    
  13. 最后,我们将完成的密度图层添加到地图上:

    QgsMapLayerRegistry.instance().addMapLayer(bears)
    
    

它是如何工作的...

规则渲染器是这个菜谱的核心。然而,六边形网格提供了一种更有趣的方式来可视化统计数据。就像基于点的密度图一样,六边形在空间上并不完全准确或精确,但它使得理解数据的整体趋势变得非常容易。六边形的一个有趣特性是它们的质心,它到每个邻居的距离相等,而正方形网格中,对角邻居则更远。

这张图片显示了最终地图的样式:

如何工作...

使用 geo_interface 协议

__geo_interface__ method.

准备工作

这个菜谱不需要准备。

如何操作...

我们将创建两个函数:一个用于特征,一个用于几何。然后我们将使用 Python 的动态能力,通过 __geo_interface__ 内置 方法来修补 QGIS 对象。为此,我们需要执行以下步骤:

  1. 首先,我们需要 Python 的 json 模块:

    import json
    
    
  2. 接下来,我们将创建一个用于特征的函数,该函数接受一个特征作为输入并返回一个类似 GeoJSON 的对象:

    def mapping_feature(feature):
    geom = feature.geometry()
     properties = {}
     fields = [field.name() for field in feature.fields()]
     properties = dict(zip(fields, feature.attributes()))
     return { 'type' : 'Feature',
     'properties' : properties,
     'geometry' : geom.__geo_interface__}
    
    
  3. 现在,我们将创建 geometry 函数:

    def mapping_geometry(geometry):
    geo = geometry.exportToGeoJSON()
    return json.loads(geo)
    
    
  4. 最后,我们将使用我们的自定义内置函数修补 QGIS 特征和几何对象,以便在访问内置函数时调用我们的函数:

    QgsFeature.__geo_interface__ = property(lambda self: mapping_feature(self))
    QgsGeometry.__geo_interface__ = property(lambda self: mapping_geometry(self))
    
    

它是如何工作的...

这个菜谱出奇地简单,但利用了 Python 的一些最有趣的功能。首先,请注意,特征函数实际上在其输出部分调用了几何函数。另外,请注意,添加 __geo_interface__ 内置函数就像使用双下划线命名约定和 Python 的内置属性方法来声明 lambda 函数作为对象的内部一样简单。另一个有趣的 Python 功能是 QGIS 对象能够使用 self 关键字将自身传递给我们的自定义函数。

沿着线生成点

您可以通过使用点在多边形内的方法以相当简单的方式在多边形内生成点。然而,有时您可能想要在一条线上生成点。您可以在多边形范围内部随机放置点——这本质上只是一个矩形多边形——或者您可以在线上随机距离处随机放置点。在这个菜谱中,我们将演示这两种方法。

准备工作

您需要下载压缩的 shapefile 并将其放置在 qgis_data 目录中名为 shapes 的目录中,如下所示:

geospatialpython.googlecode.com/svn/path.zip

如何操作...

首先,我们将使用处理工具箱中的grass()函数在一条线上生成随机点。然后,我们将使用 QGIS 的本地处理函数在线的范围内生成点。为此,我们需要执行以下步骤:

  1. 首先,我们需要导入处理模块:

    import processing
    
    
  2. 然后,我们将线图层加载到地图上:

    line = QgsVectorLayer("/qgis_data/shapes/path.shp", "Line", "ogr")
    QgsMapLayerRegistry.instance().addMapLayer(line)
    
    
  3. 接下来,我们将通过指定 shapefile 的路径、点之间的最大地图单位距离(米)、我们想要输出的特征类型(顶点)、范围、捕捉容差选项、点之间的最小距离、输出类型和输出名称来生成线上的点。我们不会指定名称,并告诉 QGIS 自动加载输出:

    processing.runandload("grass:v.to.points",line,"1000",False, False,True,"435727.015026,458285.819185,5566442.32879,5591754.78979",-1,0.0001,0,None)
    
    
  4. 最后,我们将在线的范围内创建一些点并将它们也加载进来:

    processing.runandload("qgis:randompointsinextent","435727.015026,458285.819185,5566442.32879,5591754.78979",100,100,None)
    
    

它是如何工作的...

第一个算法将点放在线上。第二个算法将它们放在附近。这两种方法有不同的使用场景。

更多内容...

另一个选项是在线周围创建一个指定距离的缓冲区,并剪切第二个算法的输出,以便点不会靠近线范围的角落。QgsGeometry类还有一个 interpolate 方法,允许您在指定距离处创建线上的点。这已在qgis.org/api/classQgsGeometry.html#a8c3bb1b01d941219f2321e6c6c3db7e1中记录。

使用基于表达式的标签

表达式是一种迷你编程语言或类似 SQL 的语言,在 QGIS 的不同功能中都可以找到,用于选择特征。表达式的一个重要用途是控制标签。如果为每个特征都添加标签,地图很容易变得杂乱。表达式可以轻松地将标签限制在重要特征上。您可以使用 Python 中的表达式从内部过滤标签,就像我们在这个菜谱中所做的那样。

准备工作

您需要从以下链接下载压缩的 shapefile 并将其解压缩到您的qgis_data目录中名为ms的目录:

geospatialpython.googlecode.com/files/MS_UrbanAnC10.zip

如何操作...

我们将使用 QGIS PAL 标签引擎根据字段名过滤标签。在加载图层后,我们将创建我们的 PAL 设置并将它们写入图层。最后,我们将图层添加到地图上。为此,我们需要执行以下步骤:

  1. 首先,我们将设置 shapefile 的路径:

    pth = "/Users/joellawhead/qgis_data/ms/MS_UrbanAnC10.shp"
    
    
  2. 接下来,我们将加载我们的图层:

    lyr = QgsVectorLayer(pth, "Urban Areas", "ogr")
    
    
  3. 现在,我们创建一个标签对象并读取图层的当前标签设置:

    palyr = QgsPalLayerSettings()
    palyr.readFromLayer(lyr)
    
    
  4. 我们创建一个表达式,仅对人口字段大于 50,000 的特征进行标签:

    palyr.fieldName = 'CASE WHEN "POP" > 50000 THEN NAME10 END'
    
    
  5. 然后,我们将启用以下设置:

    palyr.enabled = True
    
    
  6. 最后,我们将标签过滤器应用于图层并将其添加到地图上:

    palyr.writeToLayer(lyr)
    QgsMapLayerRegistry.instance().addMapLayer(lyr)
    
    

它是如何工作的...

当标签是图层的功能时,标签引擎的设置由外部对象控制,然后应用于图层。

在 QGIS 中创建动态表单

当您在 QGIS 中编辑图层的字段时,您可以选择使用类似电子表格的表格视图或使用数据库样式的表单视图。表单很有用,因为您可以更改表单的设计并添加交互式功能,这些功能会根据用户输入做出反应,以便更好地控制数据编辑。在本菜谱中,我们将向表单添加一些自定义验证,以检查用户输入的有效值。

准备工作

您需要从以下链接下载压缩的 shapefile 并将其解压缩到您的qgis_data目录中名为ms的目录中:

geospatialpython.googlecode.com/files/MS_UrbanAnC10.zip

您还需要创建一个名为validate.py的空 Python 文件,您将按照以下步骤进行编辑。将validate.py文件放在与 shapefile 相同的qgis_data目录的ms目录中。

如何操作...

我们将创建我们验证引擎所需的两个函数。然后,我们将使用 QGIS 界面将操作附加到图层上。确保您将以下代码添加到与 shapefile 同一目录下的validate.py文件中,如下所示:

  1. 首先,我们将导入 Qt 库:

    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    
    
  2. 接下来,我们将为我们将要验证的属性和表单对话框创建一些全局变量:

    popFld = None
    dynamicDialog = None
    
    
  3. 现在,我们将开始构建更改对话框行为的函数,并为我们要验证的字段和提交按钮创建变量:

    def dynamicForm(dialog,lyrId,featId):
     globaldynamicDialog
     dynamicDialog = dialog
     globalpopFld = dialog.findChild(QLineEdit,"POP")
     buttonBox=\
     dialog.findChild(QDialogButtonBox,"buttonBox")
    
    
  4. 我们必须断开对话框与控制表单接受的操作的连接:

    buttonBox.accepted.disconnect(dynamicDialog.accept)
    
    
  5. 接下来,我们将重新连接对话框,将操作重新连接到我们的自定义操作:

    buttonBox.accepted.connect(validate)
    buttonBox.rejected.connect(dynamicDialog.reject)
    
    
  6. 现在,我们将创建一个验证函数,如果人口字段值小于 1,则拒绝表单:

    def validate():
    if not float(popFld.text()) > 0:
     msg = QMessageBox(f)
     msg.setText("Population must be \ 
     greater than zero.")
     msg.exec_()
     else:
     dynamicDialog.accept()
    
    
  7. 接下来,打开 QGIS 并将 shapefile 从您的文件系统拖放到地图画布上。

  8. 保存项目并在与validate.py文件相同的目录中为其命名。

  9. 在 QGIS 图例中双击图层名称。

  10. 图层属性对话框的左侧选择字段选项卡。

  11. 在屏幕右上角的字段选项卡中,将以下行输入到PythonInit 函数字段中:

    validate.dynamicForm
    
    
  12. 点击确定按钮,位于图层属性对话框的右下角。

  13. 现在,使用识别工具选择一个要素。

  14. 要素属性对话框中,点击图像左上角的表单图标。

  15. 一旦要素表单打开,切换回QGIS 图例,右键单击图层名称,并选择切换编辑

  16. 切换回功能表单,向下滚动到POP字段,并将值更改为0

  17. 现在,点击确定按钮并验证您是否收到了警告对话框,该对话框要求值大于0

作用原理...

validate.py文件必须在您的 Python 路径中。将此文件放在与项目相同的目录中可以使函数可用。验证是您可以实现的简单函数之一。

这张截图显示了当人口设置为0时的拒绝信息:

工作原理...

计算所有选中线的长度

如果您需要计算给定数据集属性的总和,例如长度,最简单的方法是使用 Python。在这个菜谱中,我们将计算数据集中铁路的总长度。

准备工作

您需要从geospatialpython.googlecode.com/svn/ms_rails_mstm.zip下载一个压缩的 shapefile。

解压它,并将其放置在qgis_data目录中名为ms的目录中。

如何做到这一点...

我们将加载图层,在遍历特征的同时保持线长度的累计总和,最后将结果转换为公里。为此,我们需要执行以下步骤:

  1. 首先,我们将设置 shapefile 的路径:

    pth = "/Users/joellawhead/qgis_data/ms/ms_rails_mstm.shp"
    
    
  2. 然后,我们将加载图层:

    lyr = QgsVectorLayer(pth, "Railroads", "ogr")
    
    
  3. 接下来,我们需要一个变量来累计线长度:

    total = 0
    
    
  4. 现在,我们遍历图层,获取每条线的长度:

    for f in lyr.getFeatures():
     geom = f.geometry()
     total += geom.length()
    
    
  5. 最后,我们将打印转换成公里的总长度,并将字符串格式化为只显示两位小数:

    print "%0.2f total kilometers of rails." % (total / 1000)
    
    

工作原理...

这个函数很简单,但它不是直接在 QGIS API 中可用的。您可以使用类似的技术来计算一组多边形的面积或执行条件计数。

使用与地图不同的状态栏 CRS

有时,您可能希望在状态栏中显示与源数据不同的鼠标坐标坐标系统。使用这个菜谱,您可以在不更改数据坐标参考系统或地图 CRS 的情况下设置不同的坐标系统。

准备工作

从以下链接下载压缩的 shapefile 并将其解压到您的qgis_data/ms目录:

geospatialpython.googlecode.com/files/MSCities_Geo.zip

如何做到这一点...

我们将加载我们的图层,在状态栏中建立一条信息,创建一个特殊的事件监听器,将鼠标位置的地图坐标转换为我们的备用 CRS,然后将地图的鼠标坐标信号连接到我们的监听函数。为此,我们需要执行以下步骤:

  1. 首先,我们需要导入 Qt 核心库:

    from PyQt4.QtCore import *
    
    
  2. 然后,我们将设置 shapefile 的路径并将其作为图层加载:

    pth = "/qgis_data/ms/MSCities_Geo_Pts.shp"
    lyr = QgsVectorLayer(pth, "Cities", "ogr")
    
    
  3. 现在,我们将图层添加到地图:

    QgsMapLayerRegistry.instance().addMapLayer(lyr)
    
    
  4. 接下来,我们创建一个默认消息,该消息将在状态栏中显示,并在事件监听器激活后用备用坐标替换:

    msg = "Alternate CRS ( x: %s, y: %s )"
    
    
  5. 然后,我们在状态栏的左侧显示默认消息作为占位符:

    iface.mainWindow().statusBar().showMessage(msg % ("--", "--"))
    
    
  6. 现在,我们创建一个自定义的事件监听器函数,将鼠标的地图位置转换为我们的自定义坐标参考系统(CRS),在这个例子中是EPSG 3815

    def listen_xyCoordinates(point):
     crsSrc = iface.mapCanvas().mapRenderer().destinationCrs()
     crsDest = QgsCoordinateReferenceSystem(3815) 
     xform = QgsCoordinateTransform(crsSrc, crsDest)
     xpoint = xform.transform(point)
     iface.mainWindow().statusBar().showMessage(msg % (xpoint.x(), xpoint.y()))
    
    
  7. 接下来,我们将连接地图画布信号,该信号在鼠标坐标更新时发出,并将其连接到我们的自定义事件监听器:

    QObject.connect(iface.mapCanvas(), SIGNAL("xyCoordinates(const QgsPoint &)"), listen_xyCoordinates)
    
    
  8. 最后,验证当你移动鼠标在地图上时,状态栏会更新为转换后的坐标。

它是如何工作的...

QGIS 中的坐标转换引擎非常快。通常,QGIS 会尝试将所有内容转换为 WGS84 地理坐标,但有时你需要以不同的参考系查看坐标。

在 QGIS 中创建 HTML 标签

QGIS 地图提示允许你将鼠标光标悬停在要素上以创建一个弹出窗口,显示信息。这些信息通常是数据字段,但你也可以使用 HTML 标签的子集显示其他类型的信息。在这个菜谱中,我们将创建一个 HTML 地图提示,在要素的位置显示 Google 街景图像。

准备工作

在你的 qgis_data 目录中,创建一个名为 tmp 的目录。

你还需要下载以下压缩的 shapefile 并将其放置在 qgis_data/nyc 目录中:

geospatialpython.googlecode.com/files/NYC_MUSEUMS_GEO.zip

如何做到这一点...

我们将创建一个处理 Google 数据的函数并将其注册为 QGIS 函数。然后,我们将加载图层并设置其地图提示显示字段。为此,我们需要执行以下步骤:

  1. 首先,我们需要导入我们将需要的 Python 库:

    from qgis.utils import qgsfunction
    from qgis.core import QGis
    import urllib 
    
    
  2. 接下来,我们将设置一个特殊的 QGIS Python 装饰器,将我们的函数注册为 QGIS 函数。第一个参数 0 表示该函数本身不接受任何参数。第二个参数 Python 定义了当使用表达式构建器时函数将出现的组:

    @qgsfunction(0, "Python")
    
    
  3. 我们将创建一个接受要素并使用其几何形状来拉取 Google 街景图像的函数。我们必须在本地缓存图像,因为显示地图提示的 Qt 小部件只允许你使用本地图像:

    def googleStreetView(values, feature, parent):
    x,y = feature.geometry().asPoint()
    baseurl = "https://maps.googleapis.com/maps/api/streetview?"
    w = 400
    h = 400
    fov = 90
    heading = 235
    pitch = 10
    params = "size=%sx%s&" % (w,h)
    params += "location=%s,%s&" % (y,x)
    params += "fov=%s&heading=%s&pitch=%s" % (fov, heading, pitch) 
    url = baseurl + params
    tmpdir = "/qgis_data/tmp/"
    img = tmpdir + str(feature.id()) + ".jpg"
    urllib.urlretrieve(url, img)
    return img
    
    
  4. 现在,我们可以加载图层:

    pth = "/qgis_data/nyc/nyc_museums_geo.shp"
    lyr = QgsVectorLayer(pth, "New York City Museums", "ogr")
    
    
  5. 接下来,我们可以使用一个特殊的 QGIS 标签来设置显示字段,该标签的名称就是我们的函数名:

    lyr.setDisplayField('<img src="img/[% $googleStreetView %]"/>')
    
    
  6. 最后,我们将它添加到地图中:

    QgsMapLayerRegistry.instance().addMapLayer(lyr)
    
    
  7. 选择地图提示工具,并将鼠标悬停在不同的点上以查看 Google 街景图像。

它是如何工作的...

这个菜谱的关键是 @qgsfunction 装饰器。当你以这种方式注册函数时,它会在表达式中以 Python 函数的形式出现在菜单中。该函数还必须具有父级和值参数,但在这个例子中我们不需要它们。

以下截图显示了一个 Google 街景地图提示:

它是如何工作的...

还有更多...

如果你不再需要该函数,你必须取消注册它,以便函数消失。unregister 命令使用以下约定,使用美元符号引用函数名:

QgsExpression.unregisterFunction("$googleStreetView")

在 QGIS 中使用 OpenStreetMap 的兴趣点

OpenStreetMap 有一个名为 Overpass 的 API,允许您动态访问 OSM 数据。在这个菜谱中,我们将添加一些 OSM 旅游兴趣点到一个地图上。

准备工作

您需要使用 QGIS 插件管理器 安装 Quick OSM 插件。

您还需要下载以下 shapefile 并将其解压到您的 qgis_data/ms 目录中:

geospatialpython.googlecode.com/svn/MSCoast_geo.zip

如何操作...

我们将加载定义感兴趣区域的基本图层。然后,我们将使用 Processing 工具箱构建 OSM 查询,下载数据并将其添加到地图中。为此,我们需要执行以下步骤:

  1. 首先,我们需要导入 processing 模块:

    import processing
    
    
  2. 接下来,我们需要加载基本图层:

    lyr = QgsVectorLayer("/qgis_data/ms/MSCoast_geo.shp", "MS Coast", "ogr")
    
    
  3. 然后,我们需要处理算法的图层范围:

    ext = lyr.extent()
    w =  ext.xMinimum()
    s =  ext.yMinimum()
    e =  ext.xMaximum()
    n =  ext.yMaximum()
    
    
  4. 接下来,我们创建查询:

    factory = processing.runalg("quickosm:queryfactory",\
    "tourism","","%s,%s,%s,%s" % (w,e,s,n),"",25)
    q = factory["OUTPUT_QUERY"]
    
    
  5. Quick OSM 算法在其输出中有一个错误,因此我们将创建一个正确格式的 XML 标签并执行字符串替换:

    bbox_query = """<bbox-query e="%s" n="%s" s="%s" \ w="%s"/>""" % (e,n,s,w)
    bad_xml = """<bbox-query %s,%s,%s,%s/>""" % (w,e,s,n)
    good_query = q.replace(bad_xml, bbox_query)
    
    
  6. 现在,我们使用我们的查询下载 OSM 数据:

    results = processing.runalg("quickosm:queryoverpassapiwithastring",\"http://overpass-api.de/api/",good_query,"0,0,0,0","",None)
    osm = results["OUTPUT_FILE"]
    
    
  7. 我们定义了从 OSM 输出创建的 shapefile 的名称:

    poly = "/qgis_data/ms/tourism_poly.shp"
    multiline = "/qgis_data/ms/tourism_multil.shp"
    line = "/qgis_data/ms/tourism_lines.shp"
    points = "/qgis_data/ms/tourism_points.shp"
    
    
  8. 现在,我们将 OSM 数据转换为 shapefile:

    processing.runalg("quickosm:ogrdefault",osm,"","","","",poly,multiline,line,points)
    
    
  9. 我们将点作为一个图层放置:

    tourism_points = QgsVectorLayer(points, "Points of Interest", "ogr")
    
    
  10. 最后,我们可以将它们添加到地图中:

    QgsMapLayerRegistry.instance().addMapLayers([tourism_points, lyr]
    
    

工作原理...

Quick OSM 插件管理 Overpass API。这个插件有趣的地方在于它除了提供图形用户界面外,还提供了处理算法。不幸的是,创建查询的处理算法格式化 bbox-query 标签不正确,因此我们需要通过字符串替换来解决这个问题。API 返回一个 OSM XML 文件,我们必须将其转换为 shapefile 以在 QGIS 中使用。

使用 WebGL 在 3D 中可视化数据

QGIS 以二维显示数据,即使数据是三维的。然而,大多数现代浏览器可以使用 WebGL 标准显示 3D 数据。在这个菜谱中,我们将使用 Qgis2threejs 插件在浏览器中显示 QGIS 数据的 3D 版本。

准备工作

您需要从以下链接下载一些栅格高程数据并将其放置在您的 qgis_data 目录中:

geospatialpython.googlecode.com/svn/saveqml.zip

您还需要使用 QGIS 插件管理器 安装 Qgis2threejs 插件。

如何操作...

我们将为覆盖在阴影图像上的 DEM 设置颜色渐变,并使用插件创建一个 WebGL 页面以显示数据。为此,我们需要执行以下步骤:

  1. 首先,我们需要导入相关的库和 Qgis2threejs 插件:

    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    import Qgis2threejs as q23js
    
    
  2. 接下来,我们将禁用 QGIS 自动重投影以保持数据显示在米为单位:

    iface.mapCanvas().setCrsTransformEnabled(False)
    iface.mapCanvas().setMapUnits(0)
    
    
  3. 现在,我们可以加载我们的栅格图层:

    demPth = "/Users/joellawhead/qgis_data/saveqml/dem.asc"
    hillshadePth = "/Users/joellawhead/qgis_data/saveqml/hillshade.tif"
    dem = QgsRasterLayer(demPth, "DEM")
    hillshade = QgsRasterLayer(hillshadePth, "Hillshade")
    
    
  4. 然后,我们可以为 DEM 图层创建颜色渐变渲染器:

    algorithm = QgsContrastEnhancement.StretchToMinimumMaximum
    limits = QgsRaster.ContrastEnhancementMinMax
    dem.setContrastEnhancement(algorithm, limits)
    s = QgsRasterShader() 
    c = QgsColorRampShader() 
    c.setColorRampType(QgsColorRampShader.INTERPOLATED) 
    i = [] 
    qri = QgsColorRampShader.ColorRampItem
    i.append(qri(356.334, QColor(63,159,152,255), '356.334')) 
    i.append(qri(649.292, QColor(96,235,155,255), '649.292')) 
    i.append(qri(942.25, QColor(100,246,174,255), '942.25')) 
    i.append(qri(1235.21, QColor(248,251,155,255), '1235.21'))
    i.append(qri(1528.17, QColor(246,190,39,255), '1528.17')) 
    i.append(qri(1821.13, QColor(242,155,39,255), '1821.13'))
    i.append(qri(2114.08, QColor(165,84,26,255), '2114.08'))
    i.append(qri(2300, QColor(236,119,83,255), '2300'))
    i.append(qri(2700, QColor(203,203,203,255), '2700'))
    c.setColorRampItemList(i) 
    s.setRasterShaderFunction(c) 
    ps = QgsSingleBandPseudoColorRenderer(dem.dataProvider(), 1,  s)
    ps.setOpacity(0.5) 
    dem.setRenderer(ps) 
    
    
  5. 现在,我们准备将栅格图层添加到地图中:

    QgsMapLayerRegistry.instance().addMapLayers([dem, hillshade])
    
    
  6. 要创建 WebGL 界面,我们需要控制插件的 GUI 对话框,但我们将保持其隐藏:

    d = q23js.qgis2threejsdialog.Qgis2threejsDialog(iface)
    
    
  7. 接下来,我们必须创建一个字典,其中包含插件所需的属性。最重要的是 DEM 图层的图层 ID:

    props = [None,
     None,
     {u'spinBox_Roughening': 4,
    u'checkBox_Surroundings': False,
    u'horizontalSlider_Resolution': 2,
    u'lineEdit_Color': u'',
     'visible': False,
     'dem_Height': 163,
    u'checkBox_Frame': False,
    u'lineEdit_ImageFile': u'',
    u'spinBox_Size': 5,
    u'spinBox_sidetransp': 0,
    u'lineEdit_xmax': u'',
    u'radioButton_MapCanvas': True,
     'dem_Width': 173,
    u'radioButton_Simple': True,
    u'lineEdit_xmin': u'',
    u'checkBox_Sides': True,
    u'comboBox_DEMLayer': dem.id(),
    u'spinBox_demtransp': 0,
    u'checkBox_Shading': False,
    u'lineEdit_ymax': u'',
    u'lineEdit_ymin': u'',
    u'spinBox_Height': {5},{},{},{},{}]}
    
    
  8. 现在,我们将将这些属性应用到插件上:

    d.properties = props
    
    
  9. 我们必须设置 HTML 页面的输出文件:

    d.ui.lineEdit_OutputFilename.setText('/qgis_data/3D/3d.html')
    
    
  10. 在下一步中,我们必须覆盖保存属性的方法,否则它会覆盖我们设置的属性:

    def sp(a,b):
    return
    d.saveProperties = sp
    
    
  11. 现在,我们已准备好运行插件:

    d.run()
    
    
  12. 在您的文件系统中,导航到 HTML 输出页面并在浏览器中打开它。

  13. 按照帮助说明移动 3D 高程显示。

工作原理...

此插件绝对不是为脚本级访问而设计的。然而,Python 非常灵活,我们甚至可以在 GUI 级别脚本化插件并避免显示 GUI,因此对用户来说无缝。这种方法中唯一的缺陷是保存方法会覆盖我们设置的属性,因此我们必须插入一个虚拟函数来防止这种覆盖。

以下图像显示了 WebGL 查看器的实际效果:

工作原理...

在球体上可视化数据

自从 Google Earth 发布以来,旋转地球应用程序已成为地理探索的有用且受欢迎的方法。QGIS 有一个名为QGIS Globe的实验性插件,它与 Google Earth 类似;然而,它极其不稳定。在本教程中,我们将展示如何在 Google Earth 中显示一个图层。

准备工作

您需要使用 QGIS 插件管理器来安装MMQGIS插件。

确保您已从www.google.com/earth/安装了 Google Earth。

您还需要从之前的教程中获取以下数据集。它是一个名为ufo的压缩目录,您应该将其解压缩到您的qgis_data目录中:

geospatialpython.googlecode.com/svn/ufo.zip

如何操作...

我们将加载我们的图层并设置我们想要用于 Google Earth KML 输出的属性作为描述符。我们将使用 MMQIGS 插件将我们的图层输出为 KML。最后,我们将使用跨平台技术打开文件,这将触发它在 Google Earth 中打开。为此,我们需要执行以下步骤:

  1. 首先,我们将导入包括插件在内的相关 Python 库。我们将使用 Python 的webbrowser模块来启动 Google Earth:

    from mmqgis import mmqgis_library as mmqgis
    import webbrowser
    import os
    
    
  2. 现在,我们将加载图层:

    pth = "/Users/joellawhead/qgis_data/continental-us"
    lyrName = "continental-us"
    lyr = QgsVectorLayer(pth, lyrName, "ogr")
    
    
  3. 接下来,我们将设置 KML 的输出路径:

    output = "/Users/joellawhead/qgis_data/us.kml"
    
    
  4. 然后,我们将设置插件用于 KML 输出的所需变量,这些变量组成图层标识符:

    nameAttr = "FIPS_CNTRY"
    desc = ["CNTRY_NAME",]
    sep = "Paragraph"
    
    
  5. 现在,我们可以使用此插件创建 KML:

    mmqgis.mmqgis_kml_export(iface, lyrName, nameAttr, desc, \
    sep, output, False)
    
    
  6. 最后,我们将使用webbrowser模块打开 KML 文件,它将默认在 Google Earth 中打开。我们需要在输出中添加file协议,以便webbrowser模块能够工作:

    webbrowser.open("file://" + output)
    
    

工作原理...

MMQGIS 插件在自定义脚本和易于使用的功能方面做得很好。虽然我们的自动启动 Google Earth 的方法可能不适用于所有可能的情况,但它几乎是完美的。

posted @ 2025-10-24 09:52  绝不原创的飞龙  阅读(0)  评论(0)    收藏  举报