Python-地理空间数据分析-全-

Python 地理空间数据分析(全)

原文:zh.annas-archive.org/md5/1e5dd3cb52782f4aa8fc65ed40d9831a

译者:飞龙

协议:CC BY-NC-SA 4.0

序言

Python 地理空间数据分析是一本介绍使用一系列 Python 库和软件包进行地理空间数据分析的书籍,优化用于探索和发现。近年来,随着物联网的出现和位置数据在本地计算机上的可用性,各行各业的专业人士一直在探索具有内置分析功能的地理空间平台,这些专业人士包括地理空间专业人员、数据科学家、业务分析师、地理学家、地质学家、研究人员、分析师、计算机科学家和统计专业人士等等。随着他们的探索,这些学习者经常试图更深入地了解嵌入在他们工具中的技术。图形用户界面非常强大,但通过编写自己的 Python 代码来完全自定义或自动化结果,并更好地了解大型平台和系统的工作方式,你可以真正提升你的技能水平。

开源项目希望吸引各种各样的用户;如今,大多数行业的专业人员可能都可以访问位置数据和公开可用的数据集。随着云计算的发展,不再需要将大量数据下载到本地计算机上,这意味着任何拥有互联网设备的人都可以获得使用权。本书旨在成为一个资源,满足专业人士的需求,并作为实现目标和理解的指南。本书鼓励所有背景的读者参与地理空间分析,帮助决策城市规划、气候变化研究和许多其他领域。

即使对经验丰富的数据分析师来说,技术通常是分隔的。你可能了解一点 Python,并可以随时随地进入和退出 QGIS 或 ArcGIS,而不需要真正了解操作化的工作流程。当然,你可以与嵌入式系统一起工作,而不需要了解栅格、矢量或数学模型,但了解这些基础元素将为你的探索性和解释性数据能力带来新的严谨性和探究精神。

我是如何写一本关于地理空间分析和 Python 集成的书的?我有一个问题要解决。在对电子健康记录进行分析时,我发现它们包含大量的医疗数据,但只有模糊的有用人口统计信息。审查医疗保健建设基础设施的这一方面向我展示了地点在预测健康结果中的重要性。如果不知道以下问题的答案,我们就无法做出关于公共卫生的政策决策:

  • 患者的社区是否提供步行距离内的新鲜市场或沿着可达交通线路的市场?

  • 患者的社区是否适合步行且安全?

  • 患者是否可以进入绿地空间?

  • 例如,邻近高速公路、电力线和废水处理厂会如何影响附近社区的健康?

  • 我们如何将地理位置数据引入到对这些环境的研究中呢?

将这些非空间数据与空间信息整合,可以让你探索周围环境——或者地球表面上任何感兴趣的地方——并生成地图和其他可视化,帮助你深入思考复杂问题。

为什么选择 Python?

Python 是一种流行且灵活的脚本语言。其易于访问的编码语法使学习比大多数编程语言更容易,因此你可以相对快速地开始使用它。Python 非常适合地理空间数据分析,因为它已经被整合到许多地理信息系统(GIS)应用程序中,包括 ArcGIS 和 QGIS。它还得到了一个充满活力的开源社区的支持,拥有大量的库和包。

这不是一个 Python 101 的入门书。如果你是初学者,并希望对这门语言有基本的掌握,那么有许多资源可以供你选择。你可能希望从 Matt Harrison 和 Theodore Petrou 的《Pandas 1.x Cookbook》(Packt Publishing)第二版开始,或者任何 Dunder Data 的面对面或在线课程,比如掌握 Python 基础

也许你没有倾向或时间来学习整个编程语言。尽管我建议你至少掌握一门计算机语言,但没关系。本书的目的是让你开始使用丰富的公开地理空间数据生态系统,并不要求具备全面的编程能力。我会提供逐步指导,并在全书中提供代码片段。我会引导你了解一些本书未涵盖的其他功能的资源,这样你可以利用所学内容进行探索。当你能够在一个大型库或包中熟练使用一两个函数时,你将逐渐增加进入新领域的信心。

本书的运作方式

我们将从一个快速的水平设定开始,介绍一些关键的地理信息系统(GIS)概念。随着我们的深入,我会逐步融入 Python 学习。我并不假设读者在编程语言或地理空间分析方面有专业知识。

这里提供的资源是开源的:它们的源代码由开发者自由分发,通常也包含社区成员的贡献。大多数资源使用 Python。在我所能做到的范围内,我确保本书展示了可以免费获取而不需要繁琐订阅服务的资源。任何成本,尽管可能不高,都会被突出显示,以便你做出知情决策。我的开源重点并不意味着我不支持企业解决方案;它意味着我希望降低围绕重大问题进行有意义分析的门槛。

这本书涵盖了广泛的开源工具和数据,并查看了多种数据集,其中一些您可能在当前的职业角色中无法访问。Python 地理空间数据分析不像典型的技术书籍(或者说,关于 Python 的书籍)那样线性。有多种方法可以探索数据问题。也许你可以从第一次在集成开发环境(IDE)中工作中得到灵感。也许你对在终端或控制台中工作感到好奇。

在一本书中不可能详细介绍每个 Python 包或库的细节。如果您熟悉特定工具或库,您可能有一些我在这里没有包含的喜爱功能。没问题,我只是想让您对每个工具有所了解。从那里,您可以继续探索它们的功能。

这本书适合谁?

Python 地理空间数据分析的构想给我提出了一个难题:如何为新近获得的地理空间专业人员编写一本既懂 Python 又精通地理空间分析的书籍?我决定简单地使其有趣。我的目标不是让您在任何一个领域专业化,而是将我们所有人汇聚一起学习工具和最佳实践。

在本书结束时,我希望所有的读者都能够感到熟练和自信,足以独立探索地理空间分析。因此,在我教授每个工具和技术时,请您跟随,并根据需要安装工具,并使用 Jupyter 或 Google Colab 笔记本来运行代码。但我不希望您止步于此,所以我还提供了大量不同的体验,邀请您继续探索。

有关工具的几个提示

没有绝对无错的代码。您将需要学习如何排除故障。这就是为什么数据女神发明了Stack Overflow。一个快速警告——您并不总能从建议网站获得最佳或最正确的解决方案,所以请谨慎研究。为了帮助,我选择了具有坚实 GitHub 存在的 Python 包,以确保始终有支持选项。话虽如此,我将提供一些您在安装并熟悉本书工具时可能会发现有用的提示:

  • 尽可能创建 Python 环境,以便您可以控制版本和依赖关系。

  • 如果您不在 Conda 或 Mamba 环境中,请始终检查您的 Python 版本和任何其他软件或包的版本。版本问题是错误代码的主要生成器。

  • 不确定您的 pip 安装是否成功?在一个单元格中写下**!pip list**并运行它。会列出该会话中安装的所有包。

  • 不要害怕包文档!阅读说明材料是获取新技能的重要部分,也是疑难解答的关键。

寻找您的方向

前两章介绍了一些你在深入研究地理空间分析时需要的一般概念和能力。从那里开始,我们将进入具体的工具,使用实践活动帮助你熟悉它们。第三章介绍了 QGIS,第四章介绍了 Google Earth Engine 和其他基于云的分析工具。第五章向你介绍了 OpenStreetMap,第六章向你展示了 ArcGIS Python API,第七章探索了使用 GeoPandas 库进行空间统计。第八章暂停一下,看看数据清理:这是一个将有用数据与常常伴随的噪音分离的过程。第九章随后介绍了一个非常有用的资源:Geospatial Data Abstraction Library (GDAL)。最后,在第十章,三个实践项目将你所有的学习内容结合起来,展示了你的地理空间分析技能在研究一个紧迫的全球问题——气候变化方面的用处。

在本书的末尾,你将找到丰富的链接到工具、软件包和数据资源,以及文本中使用的来源引用和推荐阅读,帮助你继续学习之旅。

本书使用的约定

本书使用以下排版约定:

斜体

指示新术语、URL、电子邮件地址、文件名和文件扩展名。

等宽字体

用于程序清单,以及在段落内用于引用程序元素,如变量或函数名称、数据库、数据类型、环境变量、语句和关键字。

等宽字体粗体

显示用户应该按字面意思键入的命令或其他文本。

等宽字体斜体

显示应该用用户提供的值或上下文确定的值替换的文本。

提示

此元素表示提示或建议。

注意

此元素表示一般注释。

警告

此元素表示警告或注意。

使用代码示例

附加材料(代码示例、练习等)可在https://github.com/datamongerbonny/geopy-notebooks.git下载。

如果你有技术问题或者在使用代码示例时遇到问题,请发送电子邮件至bookquestions@oreilly.com

本书旨在帮助您完成工作。一般情况下,如果本书提供了示例代码,您可以在您的程序和文档中使用它。除非您复制了代码的大部分,否则您无需联系我们以获取许可。例如,编写一个使用本书中多个代码片段的程序无需许可。出售或分发奥莱利图书中的示例需要许可。通过引用本书回答问题并引用示例代码无需许可。将本书中大量示例代码整合到您产品的文档中需要许可。

我们感谢但通常不要求署名。署名通常包括标题、作者、出版商和 ISBN。例如:“Python for Geospatial Data Analysis by Bonny P. McClain (O’Reilly)。版权所有 2023 年 Grapheme Consulting, Inc.,978-0-098-10479-5。”

如果您认为您使用的代码示例超出了公平使用范围或上述许可,请随时联系我们,邮箱为permissions@oreilly.com

奥莱利在线学习

注意

过去 40 多年来,奥莱利传媒为技术和商业培训提供了知识和深刻的洞察,帮助企业取得成功。

我们独特的专家和创新者网络通过书籍、文章和我们的在线学习平台分享他们的知识和专业知识。奥莱利的在线学习平台为您提供按需访问的实时培训课程、深度学习路径、交互式编码环境,以及来自奥莱利和其他 200 多家出版商的大量文本和视频。更多信息,请访问https://oreilly.com

如何联系我们

请将有关本书的评论和问题发送至出版社:

  • O’Reilly Media, Inc.

  • 1005 Gravenstein Highway North

  • Sebastopol, CA 95472

  • 800-998-9938(美国或加拿大)

  • 707-829-0515(国际或本地)

  • 707-829-0104(传真)

我们为本书创建了一个网页,列出勘误、示例和任何其他信息。您可以访问https://oreil.ly/python-for-geo-data

电子邮件bookquestions@oreilly.com以评论或询问有关本书的技术问题。

有关我们的图书和课程的新闻和信息,请访问https://oreilly.com

在 LinkedIn 上找到我们:https://linkedin.com/company/oreilly-media

在 Twitter 上关注我们:https://twitter.com/oreillymedia

在 YouTube 上观看我们:https://www.youtube.com/oreillymedia

致谢

我要感谢许多地理空间学生和专业人士的指导、建议和问题,这些都是我处理这个主题的动力来源。对于一本既是入门指南又是经验指导的书籍,大家的热情让我感到非常自豪。

我要感谢田纳西大学地理系助理教授吴秋生博士提供的巨大工作和支持。吴博士在地理空间社区的贡献无与伦比,正是他在 GeoPython Conference 2021 上的演讲,Google Earth Engine 和 geemap 工作坊,让我第一次了解到 Google Earth Engine 和 Python 的重要整合。一年后,我也在同一会议上做了演讲。

我还要感谢泰德·佩特罗,Python 和数据探索的大师,在Dunder Data提供及时资源和实惠的工作坊,弥补了学习 Python 的理论知识和实际工作应用之间的差距。

SpatialThoughts 的乌贾瓦尔·甘地在向在校学生和独立学习者介绍地理空间平台和技能方面发挥了重要作用。我在多个场合受益于他的专业知识和慷慨帮助。

衷心感谢和赞赏我所属的地理空间连接社区的管理员们:Bruce Buxton、Juliana McMillan-Wilhoit、Tim Nolan 和 Kendrick Faison。在这个空间里和同事们的交流非常精彩和富有信息。

我感谢我的丈夫史蒂夫,他给予我无穷的幽默、无尽的支持、丰盛的沙拉和取之不尽的软糖熊。对于我的儿子哈里森和莱兰,感谢他们是我的北极星,是无穷的智慧和魅力的源泉,是我的一切理由。

第一章:地理空间分析导论

你是地理学家、地质学家还是计算机科学家吗?如果是,那太棒了!我不是这些中的任何一个:我是一个空间数据分析师,对探索数据和将位置信息整合到数据分析中感兴趣。

地理空间数据无处不在。在数据分析中理解“位置”引入了一个新维度:理解各种特征对特定观察或结果的影响。例如,我花费大量时间研究公共卫生和医疗保健领域的大型开源数据集。一旦你熟悉了地理编码和空间文件,你不仅可以跨多个领域策划见解,还可以识别和针对存在深刻社会和经济差距的地区。

在我作为数据分析师的早期阶段,我开始意识到自己需要考虑更大更复杂的问题,并且需要更多的资源。为了能够处理美国人口普查数据,我报名参加了一门应用分析课程。我之前使用过 R 语言,但这门课程是用 Python 教授的。我虽然顺利完成了,但在接下来的几个月里,我发现了很多我希望早些学到的东西,比如 Python 基础。这本书的目的是分享我希望自己早些被教到的内容。

我希望在这里分享的不是完整的 Python 编程范式,也不是 Python 101 课程。相反,它旨在通过展示如何编写可操作的代码来补充你的 Python 学习,让你通过实践学习。本书包含简单的例子,深入探讨关键概念。前几章的图形将使你熟悉地图的外观以及不同工具如何展示关系。后面的章节将探讨代码和各种平台,让你能够利用 Python 来回答地理空间问题。如果你希望在开源和专有系统中灵活操作数据,Python 可能是拼图中缺失的一块。它相对容易学习,并且有多种库可以进行数据透视、重塑表格、合并数据和生成图表。

将 Python 集成到空间分析中是本书的重点。像开放街道地图(OSM)这样的开源平台允许我们放大并添加属性。OSMnx 是一个 Python 包,它允许您从 OSM 下载空间数据,并在 Jupyter Notebook 中建模、投影、可视化和分析真实世界的街道网络和景观,独立于任何特定的应用程序或工具。^1 您可以使用一行 Python 代码下载和建模可步行、可驾驶或可骑行的城市网络,然后轻松分析和可视化它们。您还可以下载并处理其他类型的基础设施、设施和兴趣点、建筑物轮廓、海拔数据、街道方向和方位、速度和行程时间。本书的第五章将提供一个更深入了解 OSM 的机会。

运用空间数据分析原则,您可以考虑在本地、区域和全球层面面临的挑战。这些可能涉及环境、医疗、生物学、地理学、经济学、历史学、工程学、地方政府、城市规划和供应链管理等多个领域。即使是看似局部或区域性的问题也会跨越物理和政治界限、生态区域、市政单位以及流域,并具有空间组成部分。由于地图是我们首次感知数据可视化的方式之一,因此在审视数据后对位置感到好奇是很自然的事情。本章将向您介绍空间数据分析的一些广泛目标,并探讨地理空间信息如何影响我们的思维。

数据民主化

开放源数据工具和大规模在线开放课程(MOOC)的可及性使一批新的公民科学家得以自主。如今,公众可以访问位置数据和地理空间数据集,许多人都变得“数据好奇”,不论他们的专业头衔或学习领域如何。也许您是一名鸟类观察者,对某种鸟类(比如蓝鹭)感兴趣。您可能希望访问空间数据以了解其栖息地。您可能会提出研究问题,比如:蓝鹭在哪里筑巢?它们迁徙到哪里?它们的哪些栖息地支持最多的物种,以及这种情况如何随时间变化?您可能会创建自己的观测地图或其他感兴趣的变量。

除了个人或专业爱好外,地理空间分析师还研究社区的社会经济状况及其随时间地理变化。环境种族主义的研究旨在分析建成基础设施如何阻碍或影响边缘化社区的健康结果。稍后,我们将探讨这个想法,当我们提出一个探索性数据问题时。

Map Warper 项目是一个开源的历史地图和当前位置的收集项目。挑战在于旧地图由于过时的测量技术而包含许多错误。Map Warper 项目是一个地图校正项目:旨在通过搜索现代匹配的地面控制点并相应地扭曲图像来纠正这些错误,以使其与今天的精确地图相匹配。这些控制点是已知坐标,在感兴趣的区域内分布,用作精确已知位置。您可以使用校正后的地图来探索不同城市随时间的发展,或者更准确地重新想象历史位置。例如,基础设施和工业发展的投资如何随时间影响社区?Figure 1-1 显示了一张校正后的地图。您可以浏览已经校正的地图,或者通过自行校准地图来帮助纽约公共图书馆。欢迎每个人参与!

在多个行业的专业人士中,有许多机会可以在其分析中包含位置智能。位置智能是通过探索地理空间关系而得出的可操作信息——也就是制定数据问题和评估假设。由于开源工具欢迎新的最终用户,我们需要一个适合具有多样化兴趣、资源和学习背景的人的词汇表。我希望您能够探索本书讨论的所有工具。

虽然有功能强大的基于订阅的应用程序和工具可用,但它们大多作为企业解决方案定价,不适合个人用户,这限制了未与大型机构有关联的任何人的访问。以地理信息系统为例:有许多选择,各有利弊。其中两个最突出的选择是航空侦察覆盖地理信息系统,现在称为 ArcGIS,在第六章中讨论,以及量子地理信息系统,或者 QGIS,在第三章中讨论。在我的专业工作中,我两者都使用,但在教学时,我更喜欢让 QGIS 主角,因为它真正是开源的:您不需要支付不同许可级别的费用来访问其工具。

1870 年曼哈顿地图(顶部)和当代曼哈顿校正地图(底部)

图 1-1. 1870 年曼哈顿地图(顶部)和当代曼哈顿校正地图(底部)
警告

我亲身经历过基于订阅的工具有多么昂贵。ArcGIS 使用信用系统,当我开始使用它时,并没有意识到上传带有位置数据的 CSV 文件会自动启动付费服务。不要犯这种错误!另一方面,QGIS 提供两个选择——都是免费的。

提问数据问题

国家人口普查局是数据的丰富来源,尤其是关于人口统计学的数据。我使用美国人口普查数据在 ArcGIS 中生成了所示的美国地图,显示在图 1-2 中:具体来说,使用了来自美国社区调查(ACS)的县级种族变量。^(2) 公开可用的资源如美国人口普查局提供了一个应用程序接口(API):这是一个数据传输接口,让任何人只需几行 Python 代码就可以检索人口统计数据。你可以获取你想要的具体数据,而不是下载整个庞大的数据集。

在美国大陆上展示县级种族变量的 ArcGIS 地图

图 1-2. 在美国大陆上展示县级种族变量的 ArcGIS 地图

地图上的多边形用不同颜色着色,对应于人口普查中使用的种族类别,并显示每个群体在当地人口中占多数的地区。乍一看,你可以看到分类变量的集群,但你可能还想知道其他什么?这就是地理空间数据如此宝贵的地方。你能用你手头的数据回答你的问题吗?如果面对缺失数据或无法访问的资源,你可能需要重新制定你的问题。如果你同时检查其他属性,你对这些集群可能会得出什么结论?

在第七章中,你将有机会添加非空间数据作为额外的图层,当我们使用人口普查数据 API 探索美国人口普查数据。现在,我给你一个提示:细节决定成败——或者我应该说,图层决定成败。图层是你可以根据特定查询选择的地理数据集合。例如,在图 1-2 中的图层中,多边形根据特定种族在人口中占多数的地区而着色。

这是介绍一个表达式的好地方:默认设置不应是终点。默认设置是你开始分析的地方。在你的数据中总结了哪些信息可能通过更详细的粒度探索而得出最佳结果?对 GIS 的深入理解将使你能够远离默认设置,并创造独特而深刻的见解。

你将探索如何通过专门针对空间和非空间属性定制你的变量选择,来增强你所提出的问题和你收集到的见解。

空间数据分析的第一个规则是任何分析都需要明确定义的问题。你想知道什么?你是否有一个想要测试的假设?一旦你明确了研究问题,你就可以寻找可以帮助回答问题的数据。

在制定数据问题时,我喜欢参考托布勒地理学的第一法则:“万物互相关联,但相近的物体比遥远的物体更相关。”^(3) 直接应用于地理空间概念时,当你思考“相近”的对象时,这个概念涵盖了时间和空间,而不仅仅是邻近性。例如,海滨住宅可能直接受到海平面上升和风暴加剧的直接影响,但随之而来的洪水可能会破坏一个更大的区域。在考虑空间配置和“近距离”描述时,你必须学会思考空间连接性。

明确地说,并不意味着挑选数据以匹配你想要的答案。你需要查看所有相关数据来形成假设或产生见解。在思考如何理解美国的种族和种族主义时,你可能希望揭示政策漏洞,解决未满足的健康需求,或促进同理心。从你的目标反向思考,你可以使用地理空间数据来研究种族如何与住房、就业、交通和教育等领域相交。当你熟悉人口普查数据时,你开始理解种族所要承担的重任。这是我在关于贫困、种族不平等、健康结构性决定因素和各种新兴问题的讲座中引入 ArcGIS 和 QGIS 等地理空间应用的研究类型。

如果你依赖没有空间数据的电子表格或表格,可能会错过关键的见解。非空间数据描述了价值如何分布,你可以依赖描述性统计。但如果你对空间关系可能对这些值产生的影响感到好奇呢?静态指标,如道路的位置或特定事件,以及动态度量,如传染病的传播,在与位置智能集成后变得更加强大。空间分析研究了地理边界内确定的要素之间的关系。

空间数据科学的概念框架

地理空间问题复杂且随时空变化。不用看其他地方,看看当前的头条新闻就能找到例子:种族不平等,气候变化,健康结构性决定因素,刑事司法,水污染,不可持续的农业实践,海洋酸化,贫困,物种濒危和灭绝,以及经济困境。一个人的位置如何影响他们的健康、幸福或经济机会?通过 GIS,你可以回答这些问题,通过发现和展示现象的空间模式,比如疾病扩散速率,患者到最近医院的距离,以及道路、水路、树木覆盖和城市可步行性的位置。

空间思维包括考虑接近度、重叠包含、邻近性、测量地理空间的方式,以及地理要素和现象如何相互关联等。它是空间素养的一部分,这种素养从内容知识开始,涵盖了对地球系统的理解,以及它们如何与人类影响的领域相互作用,以及地理空间如何被表现。空间素养使你能够使用地图和其他空间工具进行推理,并就空间概念做出关键决策。^(4) 例如,几何可视化是一种空间素养技能,包括计算要素之间的距离、计算缓冲区(例如一个要素距离另一个要素有多远)以及识别区域或周长。

阿斯彭全球变化研究所确定了地球的六个系统:大气层、冰层、水圈、生物圈、地圈和人圈(或地球上的人类存在)。地理空间数据使我们能够理解所有这些系统的相互关联性,并且我们可以使用大数据——大量数据——来回答精心制定的数据问题。

您不必成为专家也能保持重要的空间素养技能以回答更大的问题。如果您在地理空间数据和技术的基础上了解事物如何运作,您已经在向构建更复杂的想法迈进。您将学会提出数据问题,并确定开发您的新应用或解决方案的可行步骤。对于用 Python 编写的工具,源代码是可用的,我鼓励您从中学习、修改、扩展和分享这些及其他分析工具。

让我们来看一个例子。您在图 1-3 中看到的地图是作为经济不稳定性探索而创建的。

Risk Index Summer Meals(ArcGIS),扩展美国农业部夏季膳食计划的定位

图 1-3. 风险指数夏季餐饮(ArcGIS),目标是扩展美国农业部夏季膳食计划

红色方块表示华盛顿特区的人口,这些家庭平均无法负担几百美元以支付意外支出。这些红色方块显示了最容易无法支付家庭开支的家庭,并且更大的方块反映了更多的家庭数量。绿色方块是 2020 年夏季膳食服务计划(SFSP)为低收入儿童提供免费餐的地点。您对 SFSP 如何分配这些位置有何想法吗?

在地图上一起查看这些地理空间数据的层次,图 1-3 中,你可以看到非空间数据的相关性。如果没有理解像受影响家庭的大小和位置以及这些特征如何影响风险指数等人口特征的理解,你在解释表格数据时将受到限制。

地理信息系统分析数据并在各种行业中显示实时地理信息。虽然空间分析与非空间分析之间存在相似之处,但空间统计学是专门用于地理数据的。两者都与地理特征相关,但空间统计学专门关注地理编码的地理空间数据。也就是说,它们直接将空间(包括接近性、区域、连接性和其他空间关系)融入其数学计算中。例如,想想机场产生的各种数据类型。有关区域、使用(军事或民用/公共)、准点到达和出发列表等变量的非空间统计数据。还有一些空间组成部分,例如跑道高程和地理坐标。

复杂问题是具有空间性质的。这些问题发生在哪里,我们如何规划未来的更好结果?

地图投影

我们在查看地图时的舒适水平掩盖了其复杂性。大多数地图包含多个信息层。我们可以制作交互地图,将多个数据集层叠并尝试如何传达发现。但我们也需要谨慎。地图可能很熟悉,但熟悉并不等同于准确性或能力。投影就是一个很好的例子。

地球并非完全球形。当你考虑到行星的化学特性以及在太空中旋转时由于离心力作用而倾向于推出中间部分时,这就说得通了,导致其呈现一个扁球体形状。从技术上讲,地球的形状是一个椭球体:它在极地周围的周长比赤道周围的周长要短,几乎像是从上到下被挤压了一样。当我们尝试将行星表面映射到平面上创建二维地图时,我们使用了一个地理坐标系统(图 1-4)来投影这个不完美的球体、这个坐标系统到一个平面表面上。简化的投影考虑了复杂因素,如地球重力场如何随地形变化而变化。我们称之为大地水准面。重要的是要意识到整个世界是无法完全放在一张纸或者计算机屏幕上的——至少不是以一种可见、易于解释的方式。

地理坐标系统

图 1-4. 地理坐标系统

这些不同的投影包括圆锥投影方位角投影柱面投影。如果你使用过 OpenStreetMap 或 Google 地图,你一定熟悉 Web Mercator 坐标系。每种投影都有其优缺点,包括面积、距离、方向和大小上的扭曲。你会很高兴地知道,我们不需要单独解决这些妥协问题——软件可以处理大部分复杂的数学计算。

了解这些地图的变化(图 1-5)将帮助你为你的目的选择最佳投影。你总是需要做出妥协和权衡,选择优化的方面,并接受其他方面的一些扭曲。流行的墨卡托投影(图 1-6)对导航很有用,但会使接近极地的区域发生面积扭曲——这就是为什么格陵兰岛看起来巨大的原因。你可以看到在图 1-5 中显示的等面积投影的可视化有多么不同,但在所有投影中,格陵兰岛的比例都是适当的。在图 1-6 的墨卡托投影中,虽然南美洲实际上比格陵兰岛大八倍,但它们看起来大小相似。

一些等面积投影

图 1-5. 一些等面积投影

在我从事人口健康工作时,面积是最关键的方面。在投影中,必须尽可能准确地维护它。当我在地图上绘制百分比或原始数字时,我希望尽可能公正;如果一个小地方看起来比其他地方大得多,那里存在一种固有的偏见,会影响我对地图的解读。我将尽力研究投影的优缺点,并说:“我会选择一个保持面积的投影。” 保持面积的地图称为等面积投影

墨卡托投影

图 1-6. 墨卡托投影

如果我确保选择的坐标系能够捕捉到我可视化中最相关的测量数据,那么我已经完成了大部分工作。当然,我希望我的数值能尽可能地与现实世界中的实际数值匹配。

矢量数据:地点作为对象

在我们深入讨论如何在 Python 中探索矢量数据之前,我需要介绍一些概念,这些概念将在我们继续阅读本书时非常有用。我们将使用矢量数据,它使用点、线和多边形来传达数据。我们将使用 Python 脚本和 QGIS 集成来加载数据集到地图中,并检查矢量数据的结构。我还将向你展示如何使用不同的工具来定制地图,使用颜色和符号来提高清晰度和准确性。

图 1-7 是 ArcGIS 渲染的纽约市中央公园及周边建筑的图示。你可以看到每个要素的几何形状以点、线或多边形表示。要素的几何形状决定了其渲染方式:作为点、线或多边形。有关要素的额外信息可能会显示结构类型、建造年份、建筑尺寸及其他在属性表中可访问的属性。

ArcGIS 渲染的矢量数据显示纽约市的建筑类型,附带属性表

图 1-7. ArcGIS 渲染的矢量数据显示纽约市的建筑类型,附带属性表

地理信息系统可以处理多种类型的数据。你可能会看到一个指定为 shapefile(扩展名为 .shp)或 geodatabase(.gdb)的矢量数据文件。激光雷达(Light Detection and Ranging,LiDAR)测量通常以矢量数据形式收集,但通常会以栅格化的栅格数据格式创建和存储。

将地理信息系统文件格式与诸如 Microsoft Word(.docx)之类的文字处理程序所用的格式进行比较,可以看出文本文件(.txt)的复杂度和精密程度显然较低。尽管两种文件中的内容(页面上的文字)可能相同,但文本文件的复杂性肯定较低。如果要分享一篇写作作品,为什么会选择文本文件呢?如果希望所有人都能读取文档,无论使用何种软件,或者希望文档具备易于可移植性或更小的存储格式,又该如何选择呢?

地理信息系统文件格式也是如此:尽管内容相同,但功能各异。Shapefile 文件没有拓扑或空间层,而 geodatabase 则可以选择包含这些层。地理信息系统文件格式在简易性、冗余性、错误检测和存储大小方面也有所不同。人口普查地理数据使用 TIGER/Line 抽取数据或 shapefile。它们被分组为一组,其中包括数字文件(带有 .shp 扩展名的矢量坐标)、索引(.shx)和 dBase 属性数据(.dbf)。

最熟悉的坐标投影系统是经度和纬度。这些坐标准确描述了地球表面上特定地点的位置。无论你身处世界的哪个角落,只要你知道自己的经度(X)和纬度(Y),你就知道自己的位置。这种精确的位置被称为点要素点属性也有 X 和 Y 值,但属性可以是定量或定性描述。点属性描述了要素。可选地,Z 值可用于表示三维空间中的值,其中 Z 指的是海拔高度。当位置数据出现在电子表格中时,你可以使用纬度和经度列来找到一个点。图 1-8 中显示的空气质量电子表格包括地理数据(纬度和经度)和非地理数据(值列中的空气质量测量),允许 GIS 应用程序添加与特定地理位置相关的信息。

包括空间和非空间数据的空气质量测量数据集

图 1-8. 包括空间和非空间数据的空气质量测量数据集

光栅数据:理解空间关系

矢量数据侧重于特定位置可见的内容。地图上的特定边界或区域存在或不存在数据或对象。你不会期望一个建筑物,甚至是代表城市的多边形对象,在特定边界内的每个位置都存在。

另一方面,光栅数据是连续的数据,缺乏具体的边界,但存在于整个地图视图中,例如图像、地表温度和数字高程。光栅是作为像素化图像矩阵显示的数据,例如在图 1-9 中显示的那样,而不是矢量数据的点、线和多边形。每个像素对应于特定的地理位置。如果现在这些概念看起来有些抽象,不要担心:一旦我们开始处理它们,这两种类型的数据将更容易理解。或许稍微直观一些,考虑地表高程、降水量或地表温度。你可以记录这些测量值在研究区域内的每个位置,而不管你的研究视角如何。

旧金山作为光栅图像显示(QGIS)

图 1-9. 旧金山作为光栅图像显示(QGIS)

到目前为止,我只是在描述点、多边形或线在我们研究区域内的分布。光栅数据表示为数值数组,分为网格单元。术语单元像素描述空间分辨率,并经常互换使用。单元或像素的维度表示正在覆盖的区域。空间数据模型获取这些真实世界客观和/或现场视图的抽象表示,并探索数学关系以模拟或预测关系。

照片和光栅图像的主要区别在于光栅图像包含扩展波长的数据。这种增强数据不仅包括红、绿和蓝波长,还允许机器学习模型区分各种对象。这是因为不同的物体以不同的方式反射红外光,从而在多光谱图像中提供额外信息。全球许多空间机构免费提供其地球观测卫星的数据集,这些数据对科学家、研究人员、政府和企业非常宝贵。

如图 1-9 中所示的阴影光栅图,利用光和阴影创建视图中区域的三维效果。

在审查 GIS 系统级方法时,有必要同时考虑多个概念。在较大系统中运作的较小组件动态交互,以揭示系统中的模式。例如,几何可视化包括计算特征之间的距离,计算缓冲区(例如一个特征离另一个有多远),以及识别区域或周长。如果您将这些主题视为整体的一部分,我们的讨论将更加简化。了解这些入门概念将简化您在后续章节中的学习。开始时具备基础的空间素养非常重要。

评估和选择数据集

有许多数据集可用于学习新技能的教程,参与新应用的学习,甚至启动您自己的独立地理空间项目。本书中的数据集经过审核,在各种应用程序和工作流程中可行。

在选择数据集之前,您需要评估您的选择。关于数据集的信息称为元数据。通常还有一个描述字段标题等属性的附加数据文件,这称为数据字典。您可以探索一个示例:Landsat 数据字典,由美国地质调查局(USGS)发布。

您可以通过查看元数据了解很多信息。您可以将元数据想象成罐头汤上的标签:您阅读它是因为您想知道其中的成分以及这种汤是否适合您。图 1-10 显示了一个元数据示例。您希望检查的最重要信息应包括显示的地理区域、列出的属性、数据集使用的地图投影、其比例尺以及是否有使用费用。

我建议首先尝试按照本书中列出的建议数据资源进行操作。一旦您感到自信,可以探索与您兴趣相关的数据集,看看您能发现什么。

元数据示例

图 1-10. 元数据示例

摘要

地理空间分析领域广阔,而 Python 则是一个涉及大量文献的广泛主题。很难想象任何一本书能够全面且权威地介绍这两个主题中的任何一个,更不用说两者了。因此,本书不会尝试这样做;相反,本书的目标是解释重要的基础元素,并向您介绍开源工具和数据集,以便您能够回答地理空间问题。

本章概述了重要的地理空间概念,如坐标系统、投影以及两种主要类型的地理空间数据:矢量和栅格。您还开始学习如何进行空间思维,并通过介绍数据集及其如何选择工作数据来结束。如果这一切看起来有些多,不要惊慌!我现在只是想通过分享使用 Python 处理开源地理空间数据的可能性来激发您的好奇心。

^(1) Boeing, G. 2017. “OSMnx: New Methods for Acquiring, Constructing, Analyzing, and Visualizing Complex Street Networks.” Computers, Environment and Urban Systems 65: 126–139. https://doi.org/10.1016/j.compenvurbsys.2017.05.004.

^(2) 每年的美国社区调查(ACS)自 2005 年起取代了十年一次的长表人口普查。它提出各种各样的问题,以识别人口变化和收集关于当地社区的信息。来自世界各地的地理普查数据也可以从集成公共使用微数据系列(IPUMS)下载。IPUMS 的整合和文档使得研究变化、进行比较研究、跨数据类型合并信息以及分析家庭和社区背景中的个体变得更加容易。其数据和服务均免费提供。

^(3) Tobler, W. 1970. “A Computer Movie Simulating Urban Growth in the Detroit Region.” Economic Geography 46 (Supplement): 234–240. https://doi.org/10.2307/143141.

^(4) 要了解更多关于空间素养的信息,请参阅国家研究委员会。2006. 学会空间思维。华盛顿:国家学院出版社。https://oreil.ly/i3olt

^(5) Gott, J. Richard, III, Goldberg, David M., and Vanderbei, Robert J. 2021. “Flat Maps that Improve on the Winkel Tripel.” arXiv preprint arXiv:2102.08176.

第二章:空间分析的基本设施

第一章 讨论了如何将三维地球球体映射到二维坐标系统的复杂性。这通常需要了解如何根据你希望查看或分析的区域选择适当的地图投影。你学会了如何将地球表面上的三维坐标转换为二维坐标。为此,介绍了大地水准面椭球体的概念。你还看到了多种地图投影方法在第一章中的应用,并讨论了如何选择适合特定区域的投影方法,例如选择一个在你查看的区域上最小化失真的投影方法。

那些位于地球表面以下或以上的地形特征会发生什么?地理空间工具也能够分析这些特征以及时间序列数据,这通常被描述为第四维

当学习空间素养时,你可能注意到GIS这个术语在系统级别上做了很多工作,但实际上有多个集成概念贡献了系统的水平。我讨论了空间数据框架,在这里地理位置被确定在地球表面上。这对于建立全面(和可靠)的地理编码和地图数据的参考系统至关重要。

在本章中,你将继续通过 QGIS 中的三个简短示例项目来增强你的空间素养技能。首先,你将下载并自定义 QGIS 仪表板。接着我会介绍简单的 Python 脚本,当你探索来自纽约市开放数据门户的数据时。Mapping Inequality 项目将提供一个机会来探索历史数据,并下载数据以在 QGIS 中进行分析。这将帮助你熟悉 QGIS,并介绍重要的概念,包括上传矢量数据图层,过滤数据集以及在控制台中编写 Python 脚本。

探索 QGIS 中的空间数据

我在前一章提到了 QGIS,并且尽管你将在第三章中广泛使用它,但在本章中,你将使用 QGIS 和 Python 探索公开可用的数据集,并讨论空间素养的一些关键概念。

QGIS 是一个免费且开源的 GIS。GIS,你可能记得是“地理信息系统”的缩写,是一个完整的应用程序或工具系统,用于处理地理空间数据。一旦你下载了软件,你就可以使用这些工具。Python 可作为插件使用,并且很容易安装。

安装 QGIS

要进行操作,请安装 QGIS。您可以在QGIS 项目网站上下载并安装软件。(请寻找标识为“LTR”的长期存储库以获得稳定版。详细的安装说明在用户指南中。)一旦您添加了几个面板到视图中,您的仪表板将类似于图 2-1 所示的样子。

QGIS 仪表板显示浏览器(A)、图层(B)、处理工具箱(C)和图层样式(D)面板

图 2-1. QGIS 仪表板显示(A)浏览器、(B)图层、(C)处理工具箱和(D)图层样式面板

图 2-1 显示(a)浏览器、(b)图层、(c)处理工具箱和(d)图层样式面板(右下角未选择)。浏览器允许您查看计算机文件系统,并将数据上传到 QGIS 图层面板或直接拖放文件到画布上。处理工具箱有几个工具可以探索;在本章后续的 Python 脚本工具练习中将使用。最后,图层样式面板提供了地图上点的定制选项,透明级别和颜色方案(在其众多功能中)。在本章的最后一个练习中,您将为地图图例分配一个描述社区感知可取性等级的颜色方案。

自定义您的仪表板相对直观。从屏幕顶部的菜单中选择“视图”,然后向下滚动到“面板”。图 2-2 显示了已添加到控制台的选项。您可以通过逐个选择它们并将其移动到所需位置来按照您的偏好移动它们。

使用显示选项(如面板)自定义您的仪表板

图 2-2. 使用显示选项(如面板)自定义您的仪表板

添加底图到 QGIS

添加额外的底图是最后一项定制工作,将有助于增强您的工作环境。底图是参考地图,可为数据图层增添背景信息。您的 XYZ Tiles 文件夹(在左侧浏览器面板中向下滚动)目前为空。要添加底图,请访问田纳西大学助理教授吴秋生的GitHub 页面。单击原始文件以直接下载到下载文件夹。您可以选择不同的位置,但需要记住其位置以便将其上传到 Python 控制台。图 2-3 是您将看到的屏幕片段。

下载底图到 QGIS 的可执行文件

图 2-3. 下载底图到 QGIS 的可执行文件

在文本中右键单击,将文件保存到您的下载文件夹作为 .py 脚本(参见图 2-4](#saving_the_python_script_to_your_downlo))。

将 Python 脚本保存到您的下载文件夹

图 2-4. 将 Python 脚本保存到您的下载文件夹

您的脚本现在已保存到您的本地计算机。在 QGIS 中有多个位置可以打开 Python 控制台,这些位置由 Python 图标指示。图 2-5](#installing_the_python_plug_in_to_qgis) 强调了插件菜单中控制台图标位于仪表板顶部的位置。

将 Python 插件安装到 QGIS

图 2-5. 安装 Python 插件到 QGIS

Python 控制台是您可以逐行输入几个命令或单击菜单上的“显示编辑器”按钮输入 Python 脚本或代码片段的地方。代码片段是从编辑器运行的。Python 代码编辑器具有保存选项,如果您编写较长的脚本,这将是一个选择原因。这也是您决定在编辑器中运行代码而不仅仅是将图层拖放到画布上的原因之一。

单击带有铅笔和纸的图标以打开脚本窗口。当您选择绿色 + 符号时,您可以导航到下载脚本的位置。点击运行图标(一个小绿箭头符号),代码将在控制台中运行(参见图 2-6](#run_the_script_and_the_basemaps_will_po))。现在,您将看到可用的底图正在填充 XYZ Tile 文件夹。将它们直接拖放到画布上查看。确保在添加图层到画布时重新排序底图。您希望数据图层位于图层面板的顶部。

运行脚本,基础地图将填充 XYZ Tiles 文件夹

图 2-6. 运行脚本,基础地图将填充 XYZ Tiles 文件夹

QGIS 具有强大的编程接口,允许您扩展其核心功能并编写 Python 脚本来自动化地理空间数据工作。即使您是新手,学习一点 Python 并导航此接口将使您在地理空间数据工作中变得更加高效。

探索数据资源

本书主要使用我在培训或项目中首次使用的数据资源,并更新为最新报告的数据。您可以直接从我的GitHub 页面访问这些资源。我确保您无论使用何种操作系统都能访问这些数据集。QGIS 具有多种工具,使这一过程变得简单明了。

提示

如果您是高级用户,并希望用本书中的数据替换本地数据或反映您个人或专业兴趣的数据,以下是一些建议:

  • 您希望在不同类型的道路、边界、水体和地形的城市和乡村位置之间有一个健康的混合。

  • 在有选择时,我更喜欢最新的数据,但出于教学目的,我倾向于使用具有最广泛属性的来源。

  • 不要忘记将数据保存为 GeoPackage 文件格式,以便您能够继续探索。

  • 如果您正在创建您区域的栅格文件,请找到最大图层的范围坐标。

在纽约市可视化环境投诉

您可以在 图 2-7 中看到 QGIS 中地理信息的表示示例。点表示纽约市卫生和心理卫生部门(DOHMH)收到的室内环境投诉。该图像中的数据来自NYC Open Data,这是一个提供来自市政机构和其他合作伙伴的免费公共数据资源。这个特定的数据集可以从附带本书的 GitHub 存储库中下载。

显示在 QGIS 界面中的 DOHMH 室内环境投诉数据集;左侧面板是浏览器,右侧面板是画布

图 2-7. 显示在 QGIS 界面中的 DOHMH 室内环境投诉数据集;左侧面板是浏览器,右侧面板是画布

将数据上传到 QGIS

您有几种选项可以将数据上传到 QGIS。使用浏览器面板,选择主文件夹(在 图 2-7 中用圈圈圈出来),导航到您的下载文件夹,并选择您下载的数据集文件。您可以直接从浏览器将 shapefiles (.shp) 拖到画布(右侧面板)上。

您在 图 2-7 中观察到的是纽约市在某个时间段内的 311 信息热线呼叫。最初,您不知道有关呼叫性质或如何过滤数据以获得更好洞察的信息。

使用数据源管理器上传文件

数据源管理器(在 图 2-8 中用圈圈圈出来)是上传 .csv 文件的第一个选项,也是我首选的选项。如果您的数据有一个几何列,就像这个例子中所示(在 图 2-8 中的示例数据窗口中查看纬度和经度列),您可以在 X 字段(经度)和 Y 字段(纬度)中选择它们,并将数据添加到控制台中的图层面板中。

在确认或编辑具有几何属性的分隔文件之前,它将不被视为空间数据。对于 .csv 文件,此选项更受欢迎,因为虽然 QGIS 经常本能地识别几何属性,但您可能需要在列标题中手动更改数据类型。

使用数据源管理器上传分隔文本文件

图 2-8. 使用数据源管理器上传分隔文本文件

将数据添加为矢量图层

您还可以选择将数据集添加为矢量图层,如图 2-9 所示。对于 GeoJSON 和多种其他数据格式,这种方法非常有效。QGIS 可以检测大多数类型的几何体。在使用 PostgreSQL 数据库时,我更喜欢此方法,因为它简化了在 QGIS 之外更新文件的过程。

添加数据作为矢量图层

图 2-9. 添加数据作为矢量图层

在图层面板中右键单击数据文件,您将看到查看属性表的选项(显示为图 2-10 中的快照)。属性表对于查看可用列以及几何格式(纬度和经度)非常有用。一个小但重要的细节是在编写代码时匹配列标题的大小写,以避免运行脚本时出现错误。

检查属性表以确定几何列

图 2-10. 检查属性表以确定几何列

现在您已经将额外的底图上传到 QGIS(如图 2-11 所示),您还可以自定义您的底图。

添加数据图层和来自 XYZ 瓦片的 OpenStreetMap 底图

图 2-11. 添加数据图层和来自 XYZ 瓦片的 OpenStreetMap 底图

设置项目 CRS

QGIS 将尝试分配项目坐标参考系统(CRS),如图 2-12 所示。您可以在点击图层面板中的数据时看到“图层 CRS”窗口,从而进行更改。

设置 CRS 图层 EPSG:4326—WGS84

图 2-12. 设置 CRS 图层 EPSG:4326—WGS84

现在选择了 CRS 图层后,您可以在地图上的点上悬停以获取更多信息。选择标识要素图标(蓝色圆圈内的小“i”),然后选择地图上的点。

稍后在 Python 控制台开始工作时,您将进一步探索许多这些地理空间和非地理空间功能。

使用查询编辑器过滤数据

让我们假设这里的研究问题是:曼哈顿居民在哪里报告有关石棉的问题? 石棉水平的监测非常重要,因为石棉一旦释放到大气中就是已知的致癌物质。QGIS 具有内置的查询构建器,可以帮助您熟悉数据集。在图层面板中右键单击数据集,然后选择过滤器。

当您点击字段时,它将填充在提供者特定过滤表达式控制台中,如图 2-13 所示。在值下选择示例选项以查看示例数据。添加一个操作符并点击控制台底部的测试按钮。您将看到返回的行数。您可以向数据添加更多过滤器,或者点击确定并将更新后的数据集加载到您的画布中。通过浏览字段并选择示例,可以提供在控制台中探索的信息。

根据事件类型和位置过滤数据

图 2-13. 根据事件类型和位置过滤数据

图 2-14 中的画布已过滤,仅显示石棉投诉。如果您想要探索,还有基于投诉状态和日期的其他过滤选项!

接下来是关于 QGIS 中 Python 控制台的简要介绍。您将运行一些代码,甚至可以在处理工具箱中生成一个模板脚本。

更新后的画布,显示石棉投诉

图 2-14. 更新后的画布,显示石棉投诉的新查询

可视化人口数据

无论您是专业制作地图还是个人使用,寻找优质的数据资源都可能是一个挑战。Natural Earth是一个公共领域数据集,通过志愿者和北美地图学信息协会(NACIS)的支持和协作可用。

它的免费可用矢量和栅格数据——分别以 shapefile 和 TIFF 格式提供,都使用 EPSG:4326 投影,也被称为 WGS84 投影。此外,您可以选择下载数据的详细程度。更详细的数据,1:10 米^(1)将消耗最多资源,而 1:50 米和 1:110 米则提供中等和粗略级别的详细信息。这种关系被描述为代表性分数比例尺:地图上的 1 单位(如英寸或厘米)表示地面上的等价单位。在我们的示例中,1:50,000,000 意味着 1 厘米=50 公里或 1 英寸=790 英里。在图 2-15 的左侧面板中,您可以看到浏览器窗口。下载数据集后,可以从浏览器窗口上传并直接将其放置在画布上(在右侧面板)。将 shapefile(.shp)拖入视图中。点表示世界上的有人居住的地方。与每个符号相关联的属性包括首都、大城市和城镇等。

Python 控制台窗口中的人口数据(QGIS)

图 2-15. Python 控制台窗口中的人口数据(QGIS)

当数据在画布上时,您可以在 Python 控制台中进行查询。要展开控制台,请选择左侧的图标(位于左侧的红色箭头下方)。选择右侧红色箭头下方的绿色按钮后,代码将运行。当运行单行代码时,>>> 控制台很好用。当我需要迭代多行代码时,我使用编辑器(位于右下角的面板)。

QGIS Python 控制台

您还可以使用 Python 控制台将数据上传到画布。将矢量图层添加到本地计算机:选择图层 >> 添加图层 >> 添加矢量图层。从那里复制路径,或者如果您知道您的目录和文件名,则可以直接输入。

创建一个名为 uri 的变量(或任何您喜欢的名字;我喜欢 uri,它代表统一资源标识符—用于标识资源的字符字符串)。您正在存储描述数据路径或源的字符串。这将是 iface.addVectorLayer 函数代码片段中看到的三个参数之一。第二个参数是您选择的图层名称,ogr 是提供者关键字(代表 OGR 简单要素库,今天仅以其首字母缩写称呼)。其他提供者关键字的示例包括postgres分隔文本

将以下代码写入 Python 控制台,用您的用户名替换您在文件路径中的出现位置:

uri="Users/yourfilepathusername/Downloads/50m_cultural/ne_50m_populated_places.shp" vlayer = iface.addVectorLayer(uri, "places", "ogr")

运行脚本,画布将填充数据点。您还可以在编辑器中查询人口:

for feature in vlayer.getFeatures():
    print("{pop:.2f} mio people live in {name}".format(name=feature['ADMIN'],
	pop=feature['POP_EST']/1000000))

您代码中的缩进对于标识作为函数一部分的代码非常重要;如果在必要时忘记包含它们,将会出现错误。您可以在 图 2-15 中的编辑器中查看代码示例,在 Python 文档 中查看,也可以在线参考 PyQGIS 开发者手册 中的代码。代码结果显示在左窗口中。当您成功运行代码时,您将看到可供滚动查看的输出。

在编辑器中编写代码时,请注释掉不想再次运行的任何代码(以#开头的行),因为每次运行单元格时都会生成输出。记得使用您自己的文件路径。如果点的样式不符合您的口味,您可以使用图层样式面板或在控制台中编写 Python 代码。

渲染器修改数据层中数据的显示方式。接下来,运行以下代码,并根据您的喜好调整 setSize 参数:

vlayer.renderer().symbol().setSize(6)
vlayer.triggerRepaint()

triggerRepaint 函数将更新地图。如果不调用此函数,地图在再次运行画布之前将不会更新。

QgsSimpleMarkerSymbolLayerBase 修改参数的形状、大小、角度和比例方法,提供多种形状选项(列在文档中)。输入以下代码以将点的形状从圆形更改为星形:

vlayer.renderer().symbol().symbolLayer(0).setShape
(QgsSimpleMarkerSymbolLayerBase.Star)
vlayer.triggerRepaint()

更高级的脚本选项可在“处理工具箱”中找到。如果你向下滚动到 Python 脚本图标,你会看到任何保存的脚本都列在“示例脚本”下面。如果你在“处理工具箱”的顶部选择 Python 图标,你会看到四个选项:创建新脚本,从模板创建新脚本,打开现有脚本和将脚本添加到工具箱。

选择从模板创建新脚本。运行脚本(见图 2-16 中的日志),输出图层将显示在图层面板中作为一个新图层。

Python 脚本窗口渲染地图

图 2-16。Python 脚本窗口渲染地图

加载栅格图层

你正在使用的数据来自QGIS 样本数据仓库。加载栅格图层的步骤与加载矢量图层的步骤类似。最大的区别在于我们现在使用的是一个.tif文件,而且提供者参数现在是gdal(来自地理空间数据抽象库,我们将在第九章中讨论)而不是ogr

uri="Users/bonnymcclain/Downloads/qgis_sample_data/raster/SR_50M_alaska_nad.tif" rlayer = iface.addRasterLayer(uri, "SR raster","gdal")

使用if语句来判断代码运行是否成功可能会很有帮助。当特定条件满足时,print语句会缩进并运行:

if rlayer.isValid():
        print("This is a valid raster layer!")
**else**:
       print("This raster layer is invalid!")

以下是你可以在控制台中运行的栅格统计示例,还有更多示例可在QGIS 文档中找到:

print("Width: {}px".format(rlayer.width())
print("Height: {}px".format(rlayer.height()))
print("Extent: {}".format(rlayer.extent().toString()))

输出显示在图 2-17 以及这里:

Width: 1754px
Height: 1394px
Extent: -6232946.6726976688951254,-735684.6617672089487314 : 
6363148.4376372583210468,9275122.9686814155429602

这表明了我们正在查看的图层的像素值和范围,或者实际尺寸。

向画布添加栅格图层

图 2-17。向画布添加栅格图层

红线:不平等映射

我将以一个强有力的可视化结束本章,该可视化展示了在考虑数据问题时地点和位置的价值。Mapping Inequality: Redlining in New Deal America研究项目呈现了来自 1933 年作为富兰克林·德拉诺·罗斯福总统的新政一部分引入的联邦避免抵押程序——房主贷款公司(HOLC)的数据。以下是该网站介绍数据的方式:

HOLC 工作人员利用当地房地产专业人士(放贷人、开发商和房地产评估师)组织的数据和评估,为每个城市的居民区分配了反映其“抵押贷款安全性”的等级,然后将其可视化为彩色地图。在地图上被着以绿色表示的“A”等级的社区被认为对银行和其他抵押贷款放贷机构是最小的风险,当他们确定谁应该获得贷款以及城市哪些地区是安全的投资时。那些收到“D”等级,也就是被标记为红色的社区,被认为是“有危险的”。

这种做法称为红线划定,将现有的土地利用系统锁定在原地。最佳分类(绿色或蓝色)之外的地区更有可能发生洪水(由于更多的沥青覆盖地面)、拥有较少的公园和有限的绿化带,更靠近垃圾堆和州际高速公路。

该项目的网站允许您选择一个城市并浏览地图。我在纽约市花了很多时间,所以在图 2-18 的截图中选择了曼哈顿。多年来,我在纽约市的各个社区漫步,目睹了曾被广泛认为太危险的社区的经济投资。1930 年代的红线地图将围绕中央公园的社区突出为“仍然理想”和“最佳”——只要它们不靠近哈林和其他主要黑人社区即可。^(2)尽管抵押证券红线政策不再是政府支持的做法,但许多这些地区的土地利用系统仍然受到限制,并且常常被探讨为环境种族主义研究的基础。

图 2-18. 红线划定:1930 年代曼哈顿的 HOLC 社区等级地图,由 Mapping Inequality 项目的在线工具生成

可以下载数据并上传到 QGIS。通过将 shapefile 从浏览器面板拖放到画布上添加 shapefile。要根据 HOLC 等级更新彩色多边形,请使用图层样式面板,如图 2-19,选择分类格式,并将holc_grade作为值,然后每个颜色作为符号(通过单击每个方块单独选择)。

根据 HOLC 等级进行 shapefile 样式设定

图 2-19. 根据 HOLC 等级进行 shapefile 样式设定

图 2-20 显示了在 QGIS 中显示的纽约市地图和属性表。将图层保存为文件,可用于其他地图,您可以检查红线的持久影响。

包括、、、和等属性的 QGIS 地图属性表

图 2-20. QGIS 地图属性表,包括statecityholc_gradeneighborhoodarea等属性。

红线区域在过去和现在更可能成为城市热岛:人工或自然的位置比城市平均温度更高。拥有更多绿色植被、较少覆盖地面的不透水表面或较少工业或大型建筑的社区往往比充斥混凝土的社区更容易吸收阳光,因此在夏季更凉爽。在图 2-21 中,较凉爽的社区以黄色显示,较暖的区域以橙色和红色表示。图 2-21 中的红色圆圈显示了映射到 1930 年代 HOLC 红色和黄色等级的高表面温度集中区域。卫星图像经常捕捉地球上的城市集群,检查 LandScan 城市扩展及相关温度。

城市热岛,计算从 2003 年到 2018 年的平均值。数据来源:Earth Engine Data Catalog

图 2-21. 城市热岛,计算从 2003 年到 2018 年的平均值。数据来源:Earth Engine Data Catalog

概要

在本章中,您已经了解了 QGIS 及其 Python 控制台。您学习了如何加载 .csv 和分隔文件、shapefile 和栅格数据。QGIS 拥有强大的查询编辑器,但通常您可以在 Python 控制台或编辑器中执行相同的任务。您还尝试了如何将数据整合到地图中,以帮助您探索复杂的研究问题。公开可用的地理空间数据集可以成为探索社会伤害和利益的强大决策资源工具,也是评估公共和私营部门潜在风险框架的基础。本书将为您展示可用资源的规模,并指导您如何交互和探索位置智能,以获取更深层次的洞察和更大的问题。

在下一章中,您将更多地了解 PyQGIS,即 QGIS 的 Python 语言。

^(1) 地图比例尺是地图上某一距离与地面对应距离的比率。作为参考,1:10 m 表示您地图上的 1 单位(屏幕上)等于 10,000,000 单位。地图上的 1 米 = 10,000,000 米。更多关于地图比例尺的信息请参见美国地质调查信息中心。

^(2) 要了解更多信息,请参阅 Mitchell, Bruce, 和 Franco, Juan. 2018. HOLC ‘Redlining’ Maps: The Persistent Structure of Segregation and Economic Inequality. National Community Reinvestment Coalition. https://ncrc.org/holc.

第三章:QGIS:探索 PyQGIS 和本地算法进行空间分析

在本章中,我们将继续专注于 QGIS,随着您通过 Python 和 QGIS 提升技能。这些是基础概念,学习它们对未来的地理空间分析技能至关重要,包括空间算法、数据工程、预测建模和机器学习。

QGIS 的 Python 集成称为PyQGIS,这是一个使用定义的协议和定制来自动化工作流程的 Python API。在运行大型脚本或构建应用程序时,自动化非常重要。通过 API 与集成,您可以访问各种数据集进行探索和分析。您可以创建、修改和查询表示现实世界中特征的数字对象。

PyQGIS 是基于底层 C++库的一个包装器。所有由 C++版本的QgisInterface实现的方法和类变量都通过 Python 包装器提供。PyQGIS 的专注于 QGIS 内的功能。您无需单独安装 Python,因为它与 QGIS 一起直接安装到您的系统中。

Python 是一种面向对象的编程语言。您可以将对象视为数据块(属性)和行为(方法)。在 Python 中,对象还包括函数。属性可以是数据或函数。方法也是属性,这意味着您可以像任何其他属性一样将它们存储在变量中。Python 类描述如何改变对象状态和对象属性的指令。

我将在本章稍后简要探讨 Python 脚本模板时再次讨论这些概念,但在我们使用由QgisInterface类定义的方法或函数时,我还将突出它们。我们将首先使用 PyQGIS 导航一个示例项目。您将上传数据层并学习如何使用 Python 控制台与它们交互。

探索 QGIS 工作空间:旧金山的树木覆盖和不平等

你在第二章中学习了城市热岛现象,我们在这里将进一步展开讨论。众所周知,树木覆盖较少的社区往往更炎热,这常常导致健康风险增加。我们将在本章的第一个示例中探索这个想法,看看在美国的一个城市,旧金山,有哪些社区可能树木覆盖较少。

图 3-1 中的地图有四个数据层叠加在上面:一个描绘社区边界的层,一个提供旧金山树木覆盖数据的层,一个提供收入水平和种族数据的层,以及一个提供位置背景的 OpenStreetMap。紫色线条表示以摘要要素类表示的社区边界。

我们还需要最后一件事情,那就是代理或代替,用于低收入社区。是否有已经在使用的措施,可以告诉我们需要知道的信息?事实上,有:Equity Strategy Neighborhoods。旧金山市交通局(SFMTA)在应用旨在解决交通性能不平等的政策时使用此措施。这些社区根据其低收入家庭的比例、公共住房以及居民对私人车辆的访问、种族/族裔和残疾进行了识别。这可以作为我们需要的低收入社区的代理。

有了这些图层,我们可以考虑树木覆盖充足的区域位于何处,并将它们与不同收入水平和种族组成的社区进行比较。

旧金山的城市热岛,由 ArcGIS Online 生成

图 3-1. 旧金山的城市热岛,由 ArcGIS Online 生成

你将会查看来自DataSF的样本数据(如图 3-2 所示)。从以下三个数据资源链接下载 shapefile 文件:Equity Strategy NeighborhoodsSF Urban Tree Canopy,以及SF_neighborhoods。一旦设置好工作空间,你将返回这些文件。

在 QGIS 地图画布中可见的样本数据

图 3-2. 在 QGIS 地图画布中可见的样本数据

Python 插件

打开 Python 控制台,就像你在第二章中所做的那样,可以通过在窗口顶部的工具栏中选择图标或从菜单栏中打开 Plugins >> Python Console 来执行。你可以点击控制台,插件将添加到你的工作空间中,就像图 3-3 中那样。

安装 Python 插件

图 3-3. 安装 Python 插件

QgisInterface类提供了与 QGIS 环境交互的方法。当 QGIS 运行时,设置一个名为iface的变量,以提供一个QgisInterface类的对象,用于与运行中的 QGIS 环境交互。该接口允许访问地图画布、菜单、工具栏和 QGIS 应用程序的其他部分。Python 控制台和插件都可以使用iface访问 QGIS 界面的各个部分。

在 QGIS 桌面应用程序中,iface.activeLayer()可以访问图例中当前选择的图层。iface类最常见的用法是获取显示地图的画布的引用。

在 图 3-4 左下角的提示是您将输入短代码片段的位置。您的查询结果将显示在上部控制台中。右侧的代码编辑器可以容纳更长的代码行,并允许您在运行代码之前对其进行一些处理。

Python 控制台(左)和 Python 代码编辑器(右)

图 3-4. Python 控制台(左)和 Python 代码编辑器(右)

在 图 3-4 中从左到右阅读工具栏图标,您将看到 Clear Console(清除控制台内容),Run Command,Show Editor,Options 和 Help。

在工具栏中选择选项,可以设置字体并执行其他自定义操作。代码编辑器还列出了函数图标,您可以在直接在编辑器窗口中创建脚本时使用它们。完整列表在 表 3-1 中给出。

表 3-1. QGIS 中的功能图标

内联 清除控制台
内联 运行命令/运行脚本
内联 显示编辑器/在外部编辑器中打开
内联 选项
内联 Python 控制台帮助
内联 打开脚本
内联 保存
内联 另存为
内联 剪切
内联 复制
内联 粘贴
内联 查找文本
内联 注释
内联 取消注释
内联 对象检查器

在 >>> 提示符下在控制台中键入:

print("Save the Planet!")

按 Enter 键,您将得到输出:拯救地球!

我还想指出,当您在 GQIS 控制台中键入此代码时,“拯救地球!” 字符串以红色显示,而其余部分以黑色显示。这称为 语法高亮,是一个有用的工具,使您更容易发现代码中是否有任何输入错误。括号也将被突出显示,因为意外地留下所需的开放或闭合括号是常见错误。

访问数据

在我们探索多层示例之前,让我们使用 Python 加载一个矢量图层。这是来自 图 3-1 的邻域图层。您需要知道从哪里下载了您的文件的 URL。如果您进入浏览器面板并找到已下载的文件,可以从图层属性中检索到 URL,如 图 3-5 所示。

从浏览器加载数据

图 3-5. 从浏览器加载数据

您需要告诉 iface 添加矢量图层。在接下来的代码片段中,ogr 是您在 第 2 章 中看到的提供者键名称。在本章中,您将使用几个不同的提供者键。将 *Path to your shape file.shp* 替换为从图层属性中检索到的 URL。您还将创建一个变量 SF 用于旧金山,并将位置信息存储在其中,这样您以后可以不必重新输入字符串来引用它:

for layer in QgsProject.instance().mapLayers().values():
    print(layer.name())
SF = iface.addVectorLayer('Path to your shape file.shp','SF_neighborhoods','ogr')
QgsProject.instance().addMapLayer(SF)
if SF.isValid():
    QgsProject.instance().addMapLayer(SF)

此处的代码正在获取我们称为 SF_neighborhoods 的图层中邻里的多边形形状。如果矢量有效,此代码将将图层添加到画布上。.isValid``(): 是一个程序检查,用于验证输入是否正确。

您现在从 Python 控制台将一个图层加载到了画布上。矢量图层的颜色是随机的,但您可以修改属性:

renderer = SF.renderer()
symbol = renderer.symbol()
symbol.setColor(QColor('pink'))

接下来,您将要包括邻里的名称。选择图层属性并格式化标签以更新地图上的名称。在 图 3-6 中,您可以看到如何调整字体、样式(粗体)、大小、颜色和不透明度。

图层属性:向地图添加标签

图 3-6. 图层属性:向地图添加标签

当您选择应用并点击确定后,标签将被添加到画布上(图 3-7)。

加载其余的图层。最快的方法是使用 addVectorLayer() 方法如下:

vlayer = iface.addVectorLayer("Path to SF Urban Tree Canopy.shp", "SF_TREES",
"ogr")
**if not** vlayer:
  print("Layer failed to load!")

vlayer = iface.addVectorLayer("Path to Equity Strategy Neighborhoods.shp", 
"ESN layer", "ogr")
**if not** vlayer:
  print("Layer failed to load!")

QGIS 中的旧金山邻里图层,带有标签

图 3-7. QGIS 中的旧金山邻里图层,带有标签

早些时候,您使用了方法 QgsProject.instance().addMapLayer(SF) 来设置活动图层。在这里,您正在添加矢量图层,iface.addVectorLayer

使用图层面板

在我们查看最终地图之前,我想回溯一下,向您展示我们是如何到达这里的。在您的画布上,您有三个矢量图层。

点击菜单栏中的“视图”,滚动到面板(图 3-8)以查看面板选项的嵌套菜单。您可以将这些选项停靠到画布上,以便在构建可视化(地图)并与功能和底层数据交互时访问它们。

QGIS 中可用的面板类型

图 3-8. QGIS 中可用的面板类型

要停靠面板,您可以将它们拖放在彼此上方以节省空间,并根据需要选择面板在它们之间移动。如果您决定更喜欢取消停靠,点击 X 旁边的堆叠图像,然后逐个取消停靠。我建议停靠图层 >> 图层样式 >> 浏览器 >> 处理工具箱。当您开始工作时,您可以简单地返回到画布,或者点击菜单中的“视图”,滚动到面板,然后选中或取消选中选项来删除或添加面板。

面板是一种小部件。你可以使用它们提供输入和可见性,数字化坐标,执行统计分析和添加数据源等等。有关所有不同选项的详细信息,请参阅QGIS 用户指南。我将向你展示我在创建几乎所有地图项目中使用的面板。

在图 3-9 中,你可以看到左上窗口中的图层已经移动到了一个允许每个图层都可见的层次结构中。例如,社区的标签位于最高层级,因此它们不会被其他要素或多边形掩盖。

编辑地图画布上的图层

图 3-9. 编辑地图画布上的图层

在图层面板中,你还可以调整不透明度和颜色。你要确保“Equity Strategy Neighborhoods”数据可见,但不要遮盖 SF Urban Tree Canopy 下面的数据。选择面板并在画布上排列它们。

小贴士

现在你已经开始在控制台中编写 Python 代码了,QGIS PyQGIS 速查表列出了一些代码片段供你参考。请随意尝试它们,并决定哪些可以提高工作效率。

解答研究问题

现在让我们来解答我们最初的研究问题。通过查看地图,你能感受到树木覆盖数据与低收入社区的联系吗?

乍一看,我们识别出的许多低收入地区的树木覆盖似乎稀疏。进一步计算树木覆盖密度将是量化我们在地图中观察到的内容的下一步。选择其中一个社区会显示底层数据(图 3-10)。我们不会在这里得出任何正式结论,但我鼓励你深入挖掘。

选择社区并探索功能

图 3-10. 选择社区并探索功能

这个练习帮助你熟悉了 QGIS 工作空间和 Python 插件。你学会了如何处理图层和添加标签。你还学到了如何查找现有数据集,这些数据集可以作为你想要测量的东西的代理,你也能感受到在地图上对齐两个数据集如何帮助解决研究问题。

Web Feature Service: 马萨诸塞州的环境威胁识别

在下一个例子中,你将探索另一个开放数据集。马萨诸塞州政府的马萨诸塞州地理信息局提供了一个名为MassMapper的 GIS 工具。该工具中提供的许多数据层之一称为关键环境关注区(ACECs)。该图层由马萨诸塞州保护与娱乐部门(DCR)开发和维护。根据该网站,这些数据提供了有关麻省被选为“其自然和文化资源的质量、独特性和重要性”的地点信息。利用 ACECs 数据,我们可以构建地图,查看这些区域可能受到当地特征(如附近道路密度、湿地或机场的接近度)的影响。

访问数据

有许多连接到 QGIS 数据的方法,所以这里我们将探索一个新的方法。

Web Feature Service(WFS)规范是一种允许访问地理要素的提供者关键字,其中包括几何和属性可用于您的查询和分析。通过选择“图层” >> “数据源管理器”或单击工具栏中的图标来连接数据。选择 WFS/OGC API - Features。您还可以在浏览器面板中垂直滚动。单击 WFS/OGC API,然后 图 3-11 中的弹出窗口将变为可见。

创建一个新的 WFS 连接

图 3-11. 创建一个新的 WFS 连接

通过将 URL http://giswebservices.massgis.state.ma.us/geoserver/wfs 输入到对话框字段中(图 3-12),连接到服务。您将提示创建连接的名称:使用 MASS_Sample 并单击“确定”。默认设置很好。

WFS 连接详细信息

图 3-12. WFS 连接详细信息

新的连接出现在服务器连接中。点击连接,你将看到可用图层的列表,如图 3-13 所示。

滚动到底部找到并选择以下图层,然后单击添加:

  • GISDATA.AIRPORTS_PT(机场位置)

  • GISDATA.BM2_CH_BIOMAP2_WETLANDS(湿地位置)

  • GISDATA.ACECS_POLY(关键环境关注区域)

  • GISDATA.CENSUS2010TIGERROADS_ARC(道路位置)

数据源管理器 WFS 连接

图 3-13. 数据源管理器 WFS 连接

发现属性

在控制台中输入以下代码并点击运行(绿色箭头):

active_layer = iface.activeLayer()
iface.showAttributeTable(active_layer)
iface.showLayerProperties(active_layer)

这将创建对活动图层的引用,在本例中为 GISDATA.AIRPORTS_PT。一旦创建了此引用,您可以访问图层的属性。

现在您可以探索一些选项或直接在控制台中访问它们。首先,选择“打开属性表”以查看与图层相关的属性。您可以对任何要探索的图层执行相同操作。

在您输入代码时,可能会注意到出现的建议,如图 3-14 所示。

QGIS Python 控制台中的自动建议

图 3-14. QGIS Python 控制台中的自动建议

让我们看看打开属性表时出现的片段:

showAttributeTable(QgsVectorLayer, QString filterExpression='') -> QDialog

黄色框包含您可以提供给showAttributeTable方法的参数及其输出内容。在这里,您正在调用QgsVectorLayer对象和表示过滤表达式的字符串,可以用它来通过识别特定字段来过滤数据,如图 3-15 所示。filterExpression不是必需的,这由空字符串表示:=''。如果留空,则提供默认值,在本例中为空字符串。输出的QDialog构造实际的属性表。这只是一个简短的示例;要了解此特定类的全部功能,请参阅QgisInterface文档

图层和属性表

图 3-15. 图层和属性表

使用迭代器进行工作

接下来,让我们找到机场。直接在控制台中编写以下代码可以识别出所在地的城镇图层和特征计数:

layer = iface.activeLayer()
print(type(layer))

<class 'qgis._core.QgsVectorLayer'>

print(layer.sourceName())
massgis:GISDATA.AIRPORTS_PT
layer.featureCount()
42

这里是输出的一部分:

BEDFORD
BEVERLY
BOSTON
HANSON
HOPEDALE
...

迭代器是一种 Python 对象类型,包含可迭代的项。迭代器可以逐个元素地查看较大的数据集。与列表等其他对象类似,但有一个关键区别:创建迭代器时,不会将所有项存储在内存中。迭代器每次加载一个项,然后在需要时获取下一个项。这使得迭代器非常高效。它们可以在不读取整个数据集的情况下读取大量数据。QGIS 为许多不同的对象类型实现了迭代器。

在以下代码中,调用layer.getFeatures()的结果是一个迭代器;next()函数是手动迭代器;列表、元组和字符串是可迭代的:

layer = iface.activeLayer()
features = layer.getFeatures()
f = next(features)
print(f.attributes())
f = next(features)
print(f.attributes())

这将输出:

['BED', 'BEDFORD', 'HANSCOM FIELD', 'MassPort', 'PUBLIC OWNED AP', 'TRAN', 
2, '42 28 12', '71 17 22', 133, 7001.0, 5106, 150, 150, 'ASPHALT-GROOVED', 
'ASPHALT', 'HIRLS', 'MIRLS', 'FAA', 'N', ' ', 'RELIEVER W/ COMMERCIAL SERVICE', 
'Y']
['BVY', 'BEVERLY', 'BEVERLY MUNICIPAL AIRPORT', 'MAC', 'PUBLIC OWNED AP', 'GU-2', 
2, '42 35 03', '70 55 03', 108, 5001.0, 4637, 150, 100, 'ASPHALT', 'ASPHALT', 
'MIRLS', 'MIRLS', 'CONTRACT', 'Y', ' ', 'RELIEVER GA', 'N']

如果您想要地图中所有图层的列表,请使用以下代码:

for layer in QgsProject.instance().mapLayers().values():
    print(layer.name())

输出如下:

OpenStreetMap
massgis:GISDATA.ACECS_POLY
massgis:GISDATA.AIRPORTS_PT
massgis:GISDATA.BM2_CH_BIOMAP2_WETLANDS
massgis:GISDATA.CENSUS2010TIGERROADS_ARC

选择“图层属性”还将打开表格,如图 3-16 所示。早些时候运行的代码layer.featureCount()显示了总共 42 个特征,本例中为机场(活动图层)加上图层属性面板中列出的 23 个字段(列)。

所选图层 GISDATA.AIRPORTS_PT 的属性和属性

图 3-16. 所选图层 GISDATA.AIRPORTS_PT 的属性和属性

向 Python 控制台添加样本代码将生成地图图层的信息。当逐行运行代码时,您可以使用控制台并在>>>提示符下输入代码。当运行多行脚本时,您将需要使用编辑器:右侧的面板在图 3-17 中。代码运行时会显示行号。您将在本章的最后一个示例中看到这一点。

在控制台中运行 Python 代码以添加数据到画布

图 3-17. 在控制台中运行 Python 代码以添加数据到画布

图层样式

现在可以修改图层使特征可见和清晰,以便您的受众能够理解。生成的颜色通常是随机的,所以我喜欢使用图层样式进行定制。

现在您可以查看地图(图 3-18)并探索机场位置、道路以及湿地与 ACECs 区域的关系。您认为这些特征可能影响这些环境敏感区域的健康状况?在回答这个研究问题时,您可能还想寻找哪些其他特征?

马萨诸塞州 ACECs 区域地图

图 3-18. 马萨诸塞州 ACECs 区域地图

这个练习让您进一步练习了 QGIS 图层,并介绍了图层样式。您还学习了如何使用迭代器和发现属性。

在 Python 控制台中使用处理算法

到目前为止,您已经了解了 Python 控制台和 QGIS 在地理空间分析中的能力。您正在熟悉一个信息系统,它将地理数据与强大的软件结合起来,用于管理、分析和可视化这些数据。到现在为止,您可能已经发现了处理工具箱中的更多插件和强大工具。您已经很好地开始使用 Python 与 QGIS 这样的系统进行交互。

从这里开始,您可以继续利用帮助函数、QGIS 文档和其他资源来建立您的专业知识。您将在以下练习中进行实践。对于这个练习,我们的研究问题将是:亚马逊河沿岸有哪些城市?也许您希望专注于洪水或农业径流如何影响当地社区。这个练习是尝试过滤和分析大型开源数据集的机会。探索属性表并定制查询!

Python 脚本的最大优势之一是您的任务可以灵活且易于复制。在本章的最后一部分,您将学习如何使用处理算法:这些算法允许您保存脚本并将它们链接在一起。调整现有脚本是学习 Python 操作的最佳方式之一。您将使用从 Anita Graser^(1)调整的脚本来构建 Python 控制台中的工作流程。这个工作流程将使用三个处理算法:

  • native:extractbyexpression

  • native:buffer

  • native:extractbylocation

就像我们在第二章中所做的那样,我们将使用来自 Natural Earth Data 的数据。在图 3-19 中的画布显示了巴西的两种数据:人口聚集地和河流与湖泊。如果尚未下载,请下载GeoPackage。从数据源管理器选择 GeoPackage 以上传文件。

在地图画布中探索感兴趣的区域

图 3-19. 在地图画布中探索感兴趣的区域

使用算法

在我们开始之前,您需要了解这些算法的来源以及如何处理不同的参数。在您的 Python 控制台中写入以下代码来开始:

from qgis import processing

在处理函数时,重要的是按名称调用算法,以确保它们可靠执行。QgsProcessingRegistry将正确列出算法。注册表是您可以访问算法、参数和不同输出的地方。您可以通过编写以下代码访问注册表:

for alg in QgsApplication.processingRegistry().algorithms():
        print(alg.id(), "->", alg.displayName())

native开头的算法是处理算法,仍然可以移植到 C++,并具有速度优势。

在输出的长列表中(下面摘录了一部分),您可以滚动查看其他内容,但也可以使用一小段代码直接访问它们:

native:atlaslayouttomultiplepdf -> Export atlas layout as PDF (multiple files)
native:atlaslayouttopdf -> Export atlas layout as PDF (single file)
native:batchnominatimgeocoder -> Batch Nominatim geocoder
native:bookmarkstolayer -> Convert spatial bookmarks to layer
native:boundary -> Boundary
native:boundingboxes -> Bounding boxes
native:buffer -> Buffer
…

当我运行此代码时,我计算了超过两千个算法。每个算法都需要特定的参数来成功执行其代码。使用processing.algorithmHelp("algorithmID")和算法名称输出语法的特性将有助于您熟悉编写代码。

首先,让我们来看看我们将使用的脚本。但不要运行它!在您运行之前,我将逐个解释每个部分:

buffered_amazonas = processing.run("native:buffer", 
    {'INPUT':amazonas,'DISTANCE':buffer_distance,'SEGMENTS':5,'END_CAP_STYLE':0,
    'JOIN_STYLE':0,'MITER_LIMIT':2,'DISSOLVE':**False**,'OUTPUT':'memory:'}
    )['OUTPUT']my_gpkg = "/Users/bonnymcclain/Downloads/natural_earth_vector/
	packages/natural_earth_vector.gpkg" rivers = '{}|layername=ne_110m_rivers_lake_centerlines'.format(my_gpkg)
places ='{}|layername=ne_110m_populated_places'.format(my_gpkg)
expression = "name = 'Amazonas'" amazonas = processing.run("native:extractbyexpression", 
    {'INPUT':rivers,'EXPRESSION':expression,'OUTPUT':'memory:'}
    )['OUTPUT'] 
buffer_distance = 0.1 #degrees  
places_along_amazonas = processing.run("native:extractbylocation", 
    {'INPUT':places,'PREDICATE':[0],'INTERSECT':buffered_amazonas,'OUTPUT':
	'memory:'}
    )['OUTPUT'] 
QgsProject.instance().addMapLayer(places_along_amazonas) 
**for** feature **in** places_along_amazonas.getFeatures():
    print(feature["name"])

注意,它按顺序运行您的三个算法。首先,您将按表达式提取。结果是一个存储在amazonas变量中并作为输入传递到下一个算法的图层。缓冲算法创建与缓冲河流中心线相交的缓冲区。例如,从河流的距离应该在城市附近的一定距离内。缓冲区设置了此距离。接下来,extractbylocation提取沿河的城市列表。

根据表达式提取

下面是algorithmHelp()函数如何描述 native:extractbyexpression:

此算法创建一个仅包含来自输入图层的匹配要素的新向量图层。将要素添加到结果图层的标准基于 QGIS 表达式。

暂停片刻,运行processing.algorithmHelp("native:extractbyexpression")并现在阅读。它将提供重要细节并描述您可以预期的输出。如果不列出参数,将应用默认值并将输出发送到内存。

这是使用此算法的脚本片段,所以你可以更详细地查看它:

 amazonas = processing.run("native:extractbyexpression", 
    {'INPUT':rivers,'EXPRESSION':expression,'OUTPUT':'memory:'}
    )['OUTPUT']
buffer_distance = 0.1 #degrees

缓冲区

接下来运行:

processing.algorithmHelp("native:buffer")

让我们看看 帮助文档 告诉我们有关 native:buffer 的信息:

该算法为输入图层中的所有要素计算缓冲区,使用固定或动态距离。

段参数控制创建圆角偏移时使用的线段数目。

端点样式参数控制缓冲区中如何处理线段的结束。

连接样式参数指定在线段角点偏移时应使用圆角、斜接或斜角连接。

斜接限制参数仅适用于斜接连接样式,并控制创建斜接连接时偏移曲线使用的最大距离。

阅读完整输出以获取理解算法及所需输入和输出的信息。特别是,你需要理解这些参数,以便包含在你的查询中。你将看到INPUTDISTANCESEGMENTSEND_CAP_STYLEJOIN_STYLEMITER_LIMITDISSOLVEOUTPUT的信息。

这是包含此算法的脚本段落:

buffered_amazonas = processing.run("native:buffer", 
    {'INPUT':amazonas,'DISTANCE':buffer_distance,'SEGMENTS':5,'END_CAP_STYLE':0,
    'JOIN_STYLE':0,'MITER_LIMIT':2,'DISSOLVE':**False**,'OUTPUT':
   'memory:'}
    )['OUTPUT']

这段代码在做什么?从这里的语法中你能得出什么结论?从algorithmHelp函数的输出中,你可以看到位置向量现在是 native.buffer 的输入。缓冲区只关心缓冲范围内的数据。例如,河流和城市应该在 10 公里范围内。

根据位置提取

我们的第三个算法是 native:extractbylocation。接下来,我们将 交叉 Amazonas 缓冲数据——也就是说,比较我们现在拥有的两组数据——将河流数据与地点数据结合起来。这将包括我们感兴趣的河流沿岸的城市。

首先运行:

processing.algorithmHelp("native:extractbylocation")

帮助文档再次包含关于参数的重要信息。

这里是我们脚本中使用 extract by location 的部分:

places_along_amazonas = processing.run("native:extractbylocation", 
    {'INPUT':places,'PREDICATE':[0],'INTERSECT':buffered_amazonas,'OUTPUT':
    'memory:'}
    )['OUTPUT']

QGIS GUI 在 Processing Toolbox 功能中包含类似的工具(图 3-20),但你需要分别运行这些过程,并找到输出中合适的输入和输出参数(这与 图 3-21 中的类似)。

processing.algorithmHelp("native:extractbylocation")

QGIS GUI 中的算法

图 3-20. QGIS GUI 中的算法

QGIS GUI 输出中埋藏的输入和输出参数

图 3-21. QGIS GUI 输出中埋藏的输入和输出参数

现在你已经检查过了,请继续在控制台中运行脚本。输出结果如图 3-22,展示了巴西亚马逊河沿岸的城市地图。这个练习展示了如何创建算法以在大型数据集上运行,从而加强高效和可重复的工作流程。现在你可以根据自己的想法查询数据。使用每个数据集提供的属性表来注意变量或列标题的列出情况。保存你的 Python 脚本将允许你创建模板,随着你的好奇心增长而进行练习和更新!

亚马逊河沿线的城市地图

图 3-22. 亚马逊河沿线的城市地图

摘要

本章向你展示了如何下载 QGIS,自定义你的工作空间,并从不同的提供商键中上传数据,既可以在 Python 控制台内部,也可以直接从浏览器中进行。你还学会了如何自定义你的地图以提高其可读性,并开始熟悉画布和图层样式。你被介绍了处理工具。这些工具很复杂,但将代码分成小节并学习语法通常是获得更高级技能的绝佳实践。你学会了如何将三个算法链接在一起并将结果存储在一个变量中,以供后续使用。

在接下来的章节中,你将继续通过与 Google Earth Engine 和 ArcGIS 等附加工具互动来构建你的 Python 技能。

^(1) 这个工作流程改编自 Anita Graser 未标日期的博文“PyQGIS 101: Chaining Processing Tools”,一个处理工具的使用教程。

第四章:云中的地理空间分析:谷歌地球引擎及其他工具

如何访问地理空间数据?尽管拥有企业账户的数据专业人士可能不会考虑个人计算机的限制和依赖于开源数据,但我们其他人通常会在限制条件下工作。云中的地理空间分析已经缩小了这一差距,因为这意味着我们不再需要在本地存储大量数据。一般公众从未如此全球化地获得过开源地理空间数据。本章将向您展示如何找到用于探索和学习的数据。

多年来,美国及全球的空间计划一直从卫星和传感器中收集数据,但直到最近我们才有能力实时操作这些数据进行分析。美国地质调查局托管的EarthExplorer(Landsat),以及Copernicus 开放获取中心,提供来自欧洲空间局(ESA)哨兵卫星的数据。Landsat 高分辨率卫星图像使我们能够评估和测量环境变化,理解气候科学和农业实践的影响,并在时间和空间上应对自然灾害等等。免费卫星图像的出现使得全球经济挑战区域的决策者能够获得见解并专注于解决方案。

空间分析包括应用于位置数据的方法和工具,其结果因位置或分析对象的框架而异。它本质上是“位置特定”的分析。这可以简单到查找最近的地铁站,或询问社区中有多少绿地或公园,也可以复杂到揭示交通可达性或健康结果中的模式。空间算法是通过列出和执行与地理属性集成的顺序指令来解决问题的方法,用于分析、建模和预测。

GIS 解决依赖于位置信息(如纬度、经度和投影)的空间问题。空间信息回答“在地球表面的哪里发生了某事?”

例如,想象一下,您走出曼哈顿的第 41 街和麦迪逊大道的酒店。由于天气比您预期的寒冷得多,您在地图应用中搜索可能购买外套的地方。瞬间,服装店的位置出现在您的屏幕上。

或者从市场营销的角度来说,比如您在一家户外用品公司工作,为高品质的户外服装制造商。您可以使用地理空间信息来回答诸如:您潜在客户居住在哪里,访问哪里或旅行到哪里?附近的潜在零售位置是否是盈利的营销决策?潜在客户愿意走多远?在您正在考虑的每个位置内的平均收入是多少?这些何处组成部分存在于零售和商业环境中,军事、气候科学和医疗保健等领域,仅举几例。

属性是空间参考数据的另一个重要组成部分。空间属性在空间上有界限;这些可以包括社区边界或基础设施,如道路或地铁站,通常用多边形表示。空间参考数据还可以具有非空间属性,例如特定位置居民的收入,并为位置智能提供背景。

GIS 中的I越来越多地存储在云端。今天,您的笔记本可以访问由云端地理空间分析处理服务提供的宠字节信息。本章将探讨其中一种服务:谷歌地球引擎(GEE)

2007 年,微软计算机科学家 Jim Gray,在同年失踪前曾发表过相当有远见的言论:“对于数据分析,一种可能是将数据移动到您的位置,但另一种可能性是将您的查询移动到数据。您可以移动您的问题,也可以移动数据。通常情况下,移动问题比移动数据更有效。”这是在云端进行地理空间分析的基本原则。

在本章中,您将使用 GEE 来执行与空间环境中地理属性相关的各种任务。我们还将简要介绍另一个与 Python 集成的工具:Leafmap。通过本章末尾,您将对这些接口有足够的熟悉度,以便跟随后续章节,并最终启动自己的独立项目。

谷歌地球引擎设置

但首先,您需要创建您的工作环境。本章的 Jupyter 笔记本可以在GitHub上找到。您可以打开它们并跟随操作,或者在有空时分别进行代码实验和探索。安装必要的软件包和资源的说明也将涵盖在内。

GEE 数据库包含超过 60 拍字节 的卫星图像和遥感以及地理空间数据,所有这些数据都是免费提供的,经过预处理,易于访问。想象一下试图将所有这些数据下载到您的笔记本电脑上!GEE 的算法允许公众在云端创建交互式应用程序或数据产品。您只需申请一个免费的 Google Earth Engine 账户(配备 250 千兆字节的存储空间),并在获得访问权限后在终端或笔记本中进行身份验证。按照以下步骤进行:

To authorize access needed by Earth Engine, open the following URL 
in a web browser and follow the instructions:
https://accounts.google.com/o/oauth2/auth?client_id=xxx
The authorization workflow will generate a code, which you should paste 
in the box below
Enter verification code: code will be here

Successfully saved authorization token.

GEE 将向您发送一个唯一的链接和验证代码。将代码粘贴到框中并按 Enter 键。

使用 GEE 控制台和 geemap

GEE 控制台是快速定位图像并运行代码的资源。但是 Python 并非 GEE 的本机语言:GEE 代码编辑器专为在 JavaScript 中编写和执行脚本而设计。其 JavaScript API 具有强大的 IDE、广泛的文档和交互式可视化功能,而这些在 Python 环境中都不是本机可用的。要在 Python 环境中使用完整的交互性,您需要使用 geemap,这是由 吴秋生博士 创建的用于与 GEE 交互的 Python 包。

幸运的是,您可以使用广泛的 GEE 目录,通过单击一个按钮快速定位和可视化数据,即使没有或只有有限的 JavaScript 专业知识。您可以通过浏览“脚本”选项卡轻松找到界面并生成地图。每个代码脚本都允许您运行 JavaScript 代码并生成地图。但是,如果您希望自主构建自己的地图并进行交互操作,您将需要使用 geemap。GEE 目录(如 图 4-1 所示)包含您在决定如何与 geemap 中的数据交互时所需的有用信息。

GEE 控制台

图 4-1. GEE 目录

浏览 Earth Engine 数据目录,找到数据集 集合,并向下滚动页面。在页面底部,您会注意到提供了 JavaScript 代码。只需将其复制并粘贴到控制台中,如 图 4-2 所示。

图 4-2 展示了当您将代码粘贴到控制台并从中心面板的选项列表中选择“运行”时生成的内容。例如,来自 图 4-1 的数据生成了 USGS Landsat 8 Level 2, Collection 2,被标识为 ee.ImageCollection("LANDSAT/LC08/C02/T1_L2")

Google Earth Engine IDE

图 4-2. Google Earth Engine IDE

让我们学习如何在 Jupyter Notebook 中使用 Python 脚本生成 GEE 图像。geemap 甚至有一个工具,可以直接在您的 Jupyter Notebook 中将 JavaScript 代码转换为 Python。

Jupyter Notebook 是独立于您的 Python 环境的实体。最初它是以其与三种不同编程语言 Julia、Python 和 R 交互的能力而命名的,但它已经远远超出了最初的能力范围。您必须告诉系统您想要使用的 Python 版本。内核 是 Notebook 和 Python 之间通信的方式。

安装 geemap 将在 Notebook 环境中创建一个类似于 GEE 控制台的控制台,但使用的是 Python API 而不是 JavaScript。一旦您设置了 Conda 环境,您就可以在 Jupyter Notebook 中与 GEE 进行交互。首先,您需要下载所需的软件包和库。

创建 Conda 环境

Anaconda 是一个流行的跨平台 Python 和其他编程语言的分发管理器,用于安装和管理 Conda 软件包。您可以将 Anaconda 视为所有数据科学工具的存储库。Conda 管理您的软件包和工具,允许您根据需要上传新工具并自定义您的工作环境。

Conda 软件包存储在 Anaconda 仓库或云中,因此您无需额外的工具进行安装。Conda 允许您根据需要使用您喜欢的 Python 版本创建任意数量的环境。您也可以选择下载一个更精简的 Anaconda 版本,称为Miniconda,无论您使用的是哪种操作系统,我都推荐它。两者都是简单的安装。我建议查看Ted Petrou 的这篇教程中的 Miniconda 安装说明。

打开 Jupyter Notebook

Jupyter Notebook 是开源的互动式 Web 工具。它们在您的浏览器中运行,不需要额外的下载。您可以在GitHub上访问本章的 Jupyter Notebook:文件名为4 Geospatial Analytics in the Cloud。您可以在 Notebook 的文件菜单中找到并配置已安装的nbextensions。这些是方便的插件,为您的 Jupyter Notebook 环境增加更多功能。

安装 geemap 和其他软件包

安装完成 Conda 环境后,您可以打开终端或命令提示符安装 geemap。逐行执行以下代码以激活您的工作环境。在这里,我将我的地理空间环境命名为gee

conda create -n gee python=3.9
conda activate gee
conda install geemap -c conda-forge
conda install cartopy -c conda-forge
conda install jupyter_contrib_nbextensions -c conda-forge
jupyter contrib nbextension install --user

请注意,我指定了要包含在环境中的 Python 版本。我这样做是因为仍有一些依赖项尚未准备好支持最新版本的 Python。这是环境很有用的一个重要原因:如果存在兼容性冲突,您将收到警告,并且可以使用避免这些冲突的版本创建环境。

这次安装还包括了 Cartopy,一个用于地理空间数据处理的 Python 包;jupyter_contrib_nbextensions,一个用于扩展功能的包;以及 contrib_nbextensions,它将向 Jupyter 配置中添加样式。

现在你已经将包安装到了你的环境中,每当你打开一个新的会话时,你只需要在一个代码单元中运行 import geemap。环境现在在你激活时可见,如下所示 (gee)

(gee) MacBook-Pro-8:~ bonnymcclain$ conda list

此环境将包含所有相关的包以及它们的依赖关系。你可以创建不同的环境(简称为 env),其中包含每个项目特有的依赖关系和包。

conda list 命令会显示当前环境中安装了哪些包。下面是我执行该命令时加载的部分内容:

# packages in environment at /Users/bonnymcclain/opt/miniconda3/envs/geo:
#
# Name                         Version                 Build    Channel
aiohttp                        3.7.4          py38h96a0964_0    conda-forge
anyio                          3.1.0          py38h50d1736_0    conda-forge
appnope                        0.1.2          py38h50d1736_1    conda-forge
argon2-cffi                    20.1.0         py38h5406a74_2    conda-forge
async-timeout                  3.0.1                 py_1000    conda-forge
async_generator                1.10                     py_0    conda-forge
attrs                          21.2.0           pyhd8ed1ab_0    conda-forge
backcall                       0.2.0            pyh9f0ad1d_0    conda-forge
backports                      1.0                      py_2    conda-forge
backports.functools_lru_cache  1.6.4            pyhd8ed1ab_0    conda-forge
beautifulsoup4                 4.9.3            pyhb0f4dca_0    conda-forge
bleach                         3.3.0            pyh44b312d_0    conda-forge
bqplot                         0.12.27          pyhd8ed1ab_0    conda-forge
branca                         0.4.2            pyhd8ed1ab_0    conda-forge
brotlipy                       0.7.0       py38h5406a74_1001    conda-forge
bzip2                          1.0.8              h0d85af4_4    conda-forge
c-ares                         1.17.1             h0d85af4_1    conda-forge
ca-certificates                2020.12.5          h033912b_0    conda-forge
cachetools                     4.2.2            pyhd8ed1ab_0    conda-forge
cartopy                        0.19.0.post1   py38h4be4431_0    conda-forge
certifi                        2020.12.5      py38h50d1736_1    conda-forge
cffi                           1.14.5         py38ha97d567_0    conda-forge
chardet                        4.0.0          py38h50d1736_1    conda-forge
click                          8.0.1          py38h50d1736_0    conda-forge
colour                         0.1.5                    py_0    conda-forge
geemap                         0.8.16           pyhd8ed1ab_0    conda-forge
...

这对于你的代码由于缺少依赖而抛出错误很有帮助。运行 conda list;你应该也会看到列出的版本。运行 conda env list 将显示你已经安装的任何环境。

我为每个激活的环境安装一个内核(运行在你的环境中的操作系统的一部分):

conda install ipykernel

现在你可以将内核添加到你的环境中——在这种情况下,*<你的环境名称>*gee

python -m ipykernel install --user --name myenv --display-name 
"<your environment name>"

你的本地计算机必须访问文件。import 语句将包添加为 Python 对象(即,一组数据和方法)到当前正在运行的程序实例中。

打开你的终端并在控制台中写入 **jupyter notebook**。一个 Notebook 应该会在你的浏览器中打开。你需要将所需的库导入到 Notebook 中。你可以在代码 shell 中看到它们列出。请注意,os 允许你访问运行 Python 的操作系统,ee 是 Earth Engine 库,而 geemap 允许你通过 Python 进行接口操作。你将使用 import 函数导入这些库:

import os
import ee
import geemap
#geemap.update_package()

计算机操作系统的中心组件是内核。内核针对每种编程语言具体化,并且默认内核取决于你在 Notebook 中运行的 Python 版本。

你需要重新启动内核以使更新生效。从菜单中选择 Kernel,滚动到重新运行选项。你现在可以开始在 Notebook 中工作了。

请注意上一个代码块的最后一行中的井号(#)。在 Python 代码中,井号表示注释,或者一行代码,不会运行。当你想要运行该行代码时,删除井号。为了确保你使用的是更新的 geemap 包,取消注释最后一行(即,删除最后一行的井号)然后再运行代码。更新 geemap 后,你可以再次插入井号,因为你不需要每次运行代码时都更新该包。你还可以添加注释文本以包含任何澄清细节。你将在本书的许多代码块中看到这种做法。

导航 geemap

我之前提到了对象。Python 专注于对象,而不是您在其他编程语言中熟悉的函数,因此被称为面向对象的编程语言。

您可能还记得早期章节中提到的就像建筑物的蓝图一样。建筑物是对象,但是可以根据一套蓝图建造许多建筑物,对吧?代码中的对象是类的实例,就像建筑物是那些蓝图的实例一样。创建对象称为实例化

下一个代码块是声明一个对象实例,我称之为m``ap,并在geemap.Map()中定义属性和方法。您可以将您的变量设置为任何您喜欢的内容,但要保持一致。我建议保持简单但信息丰富和实用。您将使用对象名称map访问对象的属性。截至本文撰写时,geemap 默认显示世界地图。如果您希望将地图中心定位到特定位置,可以使用纬度/经度(lat/lon)坐标和缩放级别来指定位置。以下将使您的地图以美国为中心:

map = geemap.Map(center=(40, -100), zoom=4)
map

图层与工具

图 4-3 显示了地图右侧的图层和工具菜单。地图的每一层实际上都是自己的数据库,其中包含地理数据的集合。这些可能包括道路、建筑物、河流或湖泊,都以点、线和/或多边形的集合形式表示,可以是矢量数据或来自栅格数据的影像。图层图标将显示地图中的不同图层。您可以更改它们的透明度,切换图层的显示/隐藏,并检查其他属性。输入以下代码以在您的笔记本中渲染地图:

map = geemap.Map(center =(40, -100), zoom=4)
map.basemap()
map

图层与工具菜单

图 4-3. 图层与工具菜单

单击不同的选项以查看它们如何自定义地图。

球形图标(在图 4-4 中的最左侧)是搜索位置/数据功能。在这里,您可以通过输入名称和地址或一组纬度/经度坐标或搜索和导入数据来找到要加载到地图上的数据。随着我们构建几个地图图层,我们将探索更多这些选项,并向您展示一些快捷方式,帮助您浏览地图画布。

在导入窗口中使用 GEE 资产 ID(Landsat/LC08/C01)搜索位置数据

图 4-4. 使用 GEE 资产 ID(Landsat/LC08/C01)在导入窗口中搜索位置数据

您可以通过在 geemap 中输入搜索参数来访问 USGS Landsat 地图。选择“数据”并在搜索栏中输入几个词条。再次探索 GEE 可能有助于识别感兴趣的地图。您可以按名称将它们调用到您自己的项目中。一旦选择了一个 Landsat 集合并选择了资产 ID,点击导入按钮(在图 4-4 中显示为蓝色)。

要查看可用参数和其他自定义选项,请将光标置于geemap.Map()括号内,然后按 Shift + Tab。现在可以在 Figue 4-5 下的地图下方看到文本。在这里,您可以阅读有关可用参数和进一步自定义地图的信息。

在 GEE 中带有文档字符串的底图

图 4-5. 在 GEE 中带有文档字符串的底图

底图

请回顾 Figue 4-3 一刻,看看最右边的下拉菜单。此菜单包含可用底图的字典:这些基础地图是您数据探索的基础。Jupyter Notebook 允许您在不编写代码的情况下滚动查看可用的底图。

根据您的数据问题或数据的性质,您可能希望显示不同的地理空间信息。例如,如果您有兴趣显示水文(水体的物理特性和可航性),您可能不会选择展示主要道路和高速公路的底图。为了迅速和高效,底图存储为栅格或矢量瓦片。底图字典与TileLayer交互,允许与地图服务如 NASA 的全球影像浏览服务(GIBS)OpenStreetMap进行连接。

geemap 软件包将 GEE 的所有分析功能带入ipyleaflet,这是一个交互式库,可以将地图引入到您的笔记本中,允许您在更新位置和缩放级别时看到地图的动态更新。geemap 的默认地图是 Google Maps 的全球视图。接下来,您将使用 OSM 作为您的底图,请运行:

add_google_map = False

高级用户可以选择创建自己的 TileLayer,但ipyleaflet 地图和底图资源中也提供多种默认底图。

现在您知道如何将地图加载到您的笔记本中,请大胆开始尝试吧。您的目标是让自己变得好奇,并且感到舒适地在 Jupyter Notebook 中导航和选择不同的工具。

探索 Landsat 9 图像收集

我们一直在使用 Landsat 数据,所以让我们看看 Landsat 9 数据,它于 2022 年初首次发布,并且在本文撰写时仍在发布中。要查看数据集的可用部分,请运行以下代码:

collection = ee.ImageCollection('LANDSAT/LC09/C02/T1_L2')
print(collection.size().getInfo())

这输出:106919

这一收藏包括 106,919 张图像,并且仍在增加中!

作为比较,Landsat/LC08/C02/T1_L2 收集包含 1,351,632 张图像。到本书出版时,Landsat 9 的图像数量将大大增加。您可以计算所有匹配波段的中位值来减少图像收集的大小:

median = collection.median()

使用光谱带进行工作

正如您在第一章学到的,光谱波段 就像不同类型光的容器。反射光被捕捉为一系列不同波长或颜色的光能波段。想象电磁光谱。光谱的每个部分实际上是一个波段。本节中关于波段的信息旨在突出数据的位置,以便输入到代码中以访问正确的信息。Landsat 8 收集的波段适用于 Landsat 9。您将需要这些数据来应用缩放因子,或线性距离的比较,以调整基于地图投影的面积和角度的扭曲(也在第一章中涵盖)。请记住,地球是椭圆形的,而不是完美的球体!我们从比例和偏移中推导出缩放因子,如图 4-6 所示。

Landsat 8/9 数据的波段特征

图 4-6。Landsat 8/9 数据的波段特征

USGS 提供了关于哪些光谱波段最适合不同类型研究的指导。您可以了解更多关于科学,并探索常见的 Landsat 波段组合

您将在 Jupyter Notebook 中导入ee.ImageCollection并将其添加为数据层到您的地图中。然后,您将从所有图像创建一个复合图像。这将产生光谱波段的中位数值。

在 Python 中,我们使用关键字def定义函数。在下面的代码中,函数名为apply_scale_factors,参数为(image)

def apply_scale_factors(image):
   opticalBands = image.select('SR_B.*').multiply(0.0000275).add(-0.2)
   thermalBands = image.select('ST_B.*').multiply(0.00341802).add(149.0)
   return image.addBands(opticalBands, None, True).addBands(thermalBands, None, 
   True)

星号(*)告诉函数您要选择满足定义的搜索要求的多个波段。Landsat 的传感器是操作陆地成像仪(OLI)和热红外传感器(TIRS)。OLI 产生光谱波段 1 到 9,而 TIRS 包括两个热波段:SR_B 和 ST_B。

冒号(:)标志着函数体的开始。在缩进的函数体内,return 语句确定要返回的值。函数定义完成后,使用参数调用函数将返回一个值:

dataset = apply_scale_factors(median)

要理解为什么您会希望选择某些波段,请将它们视为具有光谱特征签名。自然彩色波段使用 SR_B4 代表红色,SR_B3 代表绿色,SR_B2 代表蓝色。绿色表示健康的植被,棕色表示不那么健康,白灰色通常表示城市特征,水域将显示为深蓝或黑色。

近红外(NIR)复合物使用 NIR(SR_B5)、红色(SR_B4)和绿色(SR_B3)。红色区域具有更好的植被健康状况,暗区域是水域,城市区域是白色。因此,请包括这些作为您的可视化参数:

vis_natural = {
   'bands': ['SR_B4', 'SR_B3', 'SR_B2'],
   'min': 0.0,
   'max': 0.3,
}

vis_nir = {
   'bands': ['SR_B5', 'SR_B4', 'SR_B3'],
   'min': 0.0,
   'max': 0.3,
}

现在将它们作为数据层添加到您的地图中:

Map.addLayer(dataset, vis_natural, 'True color (432)')
Map.addLayer(dataset, vis_nir, 'Color infrared (543)')
Map

在 图 4-7 中,我将红外层关闭,这样您可以更清楚地看到其他波段。还似乎有云层存在。Landsat 9 每 16 天重采样一次,因此在查看时会有所不同。

Landsat 8/9 不同波段组合

图 4-7. Landsat 8/9 不同波段组合

如果将鼠标悬停在工具栏图标上,将会看到图层菜单出现。您可以更改任何地图的不透明度,并取消选择您不想在图层菜单中查看的图层。您还可以单击齿轮图标以探索属性。您还可以指定要显示的最小和最大值。拉伸数据会展开像素值,您可以尝试不同的值。您的数据将显示波段的范围,您可以决定要显示哪些值:

vis_params = [
   {'bands': ['SR_B4', 'SR_B3', 'SR_B2'], 'min': 0, 'max': 0.3},
   {'bands': ['SR_B5', 'SR_B4', 'SR_B3'], 'min': 0, 'max': 0.3},
   {'bands': ['SR_B7', 'SR_B6', 'SR_B4'], 'min': 0, 'max': 0.3},
   {'bands': ['SR_B6', 'SR_B5', 'SR_B2'], 'min': 0, 'max': 0.3},
]

要为这些图层添加标签,请创建标签列表:

labels = [
   'Natural Color (4, 3, 2)',
   'Color Infrared (5, 4, 3)',
   'Short-Wave Infrared (7, 6 4)',
   'Agriculture (6, 5, 2)',
]

然后为每一层分配一个标签:

geemap.linked_maps(
   rows=2,
   cols=2,
   height="400px",
   center=[-3.4653, -62.2159],
   zoom=4,
   ee_objects=[dataset],
   vis_params=vis_params,
   labels=labels,
   label_position="topright",
)

在 图 4-8 中检查另外两个参数,您还可以看到短波红外线。这里,深绿色表示密集植被,蓝色显示城市区域,健康植被为绿色,裸地为品红色。

Landsat 波段组合

图 4-8. Landsat 波段组合

让我们将您对 GEE 和 geemap 的介绍应用到开始探索。

国家土地覆盖数据库底图

National Land Cover Database (NLCD) 跟踪美国的土地覆盖情况。它在 Earth Engine 数据目录 中免费提供,并每五年更新一次。土地覆盖 数据包括空间参考和地表特征,例如树冠覆盖(我们在上一章节中探索过的),不透水表面以及生物多样性和气候变化的额外模式。不透水土地覆盖 意味着非自然表面,例如沥青、混凝土或其他人造层,限制雨水渗入土壤的自然能力。这些信息可以帮助预测在暴雨期间哪些区域可能更容易发生洪水。

在本节中,您将使用 NLCD 数据对几个 Landsat 基础上的不透水数据层(用于城市类)和决策树分类(用于其他类)进行检查。我们不会在这里进行完整的活动,只是快速导向,但我鼓励您进一步探索。

访问数据

转到 Earth Engine 数据目录 并滚动到 NLCD_Releases/2019_REL/NLCD 或国家土地覆盖数据库,如 图 4-9 所示。早些时候我们注意到,您只需将此数据添加到地图上,但这里还有几个选项我想向您展示。复制 JavaScript 代码并将其放在剪贴板上。

Earth Engine 数据目录

图 4-9. Earth Engine 数据目录

NLCD 目录提供了丰富的信息,包括采集的日期范围、数据源、图像片段、数据描述、多光谱波段信息和图像属性。

在 geemap 中,生成世界地图的默认设置:

map = geemap.Map()  
map

接下来,选择转换 JavaScript 图标。将显示 图 4-9 中的框。将 JavaScript 代码从目录粘贴到框中。按照弹出窗口中的代码注释中显示的说明,继续执行 图 4-10 中显示的操作:

// Import the NLCD collection.
var dataset = ee.ImageCollection('USGS/NLCD_RELEASES/2019_REL/NLCD');

// The collection contains images for multiple years and regions in the USA.
print('Products:', dataset.aggregate_array('system:index'));

// Filter the collection to the 2016 product.
var nlcd2016 = dataset.filter(ee.Filter.eq('system:index', '2016')).first();

// Each product has multiple bands for describing aspects of land cover.
print('Bands:', nlcd2016.bandNames());

// Select the land cover band.
var landcover = nlcd2016.select('landcover');

// Display land cover on the map.
Map.setCenter(-95, 38, 5);
Map.addLayer(landcover, null, 'Landcover');

点击“转换”后,您将看到代码从 JavaScript 更新为 Python,如 图 4-10 所示。

使用 geemap 将脚本从 JavaScript 转换为 Python

图 4-10. 使用 geemap 将脚本从 JavaScript 转换为 Python

如果代码没有更新到 Jupyter Notebook 的新单元格中,您可以剪切并粘贴到新单元格中运行该单元格。地图现在将显示为您的地图。

现在让我们包括默认的 NLCD 图例。选择地表覆盖层。要发现可用作默认选项的图例,请运行 builtin_legend 函数:

legends = geemap.builtin_legends
for legend in legends:
    print(legend)

NLCD 的图例将作为一个选项列出。选择它将其添加到您的地图中。

创建自定义图例

虽然 NLCD 提供了内置的图例选项,但许多数据集没有——即使有,这些图例也不总是完全符合您的需求。因此,能够创建自己的地图图例是很有帮助的。现在让我们看看如何做到这一点。

数据集中的类通常对应于您在图例中希望显示的类别。幸运的是,您可以将类表转换为图例。

如果您的数据来自 GEE 数据目录,您可以在那里找到一个类表。然后使用以下代码(或在 Jupyter Notebook 中找到此代码单元格)并将类表中的文本复制到其中:

map = geemap.Map()

legend_dict = {
    '11 Open Water': '466b9f',
    '12 Perennial Ice/Snow': 'd1def8',
    '21 Developed, Open Space': 'dec5c5',
    '22 Developed, Low Intensity': 'd99282',
    '23 Developed, Medium Intensity': 'eb0000',
    '24 Developed High Intensity': 'ab0000',
    '31 Barren Land (Rock/Sand/Clay)': 'b3ac9f',
    '41 Deciduous Forest': '68ab5f',
    '42 Evergreen Forest': '1c5f2c',
    '43 Mixed Forest': 'b5c58f',
    '51 Dwarf Scrub': 'af963c',
    '52 Shrub/Scrub': 'ccb879',
    '71 Grassland/Herbaceous': 'dfdfc2',
    '72 Sedge/Herbaceous': 'd1d182',
    '73 Lichens': 'a3cc51',
    '74 Moss': '82ba9e',
    '81 Pasture/Hay': 'dcd939',
    '82 Cultivated Crops': 'ab6c28',
    '90 Woody Wetlands': 'b8d9eb',
    '95 Emergent Herbaceous Wetlands': '6c9fb8' }

landcover = ee.Image('USGS/NLCD/NLCD2019').select('landcover')
Map.addLayer(landcover, {}, 'NLCD Land Cover')

Map.add_legend(legend_title="NLCD Land Cover Classification", 
   legend_dict=legend_dict)
Map

您可以在 geemap 文档 中找到更多关于手动构建和自定义图例的信息。

现在您可以探索地图并深入研究您感兴趣的区域。您想要从这张地图中提出什么问题?花些时间来探索。有许多不同的工具可以用来自定义您的地图!

GEE 目录 很广泛。当您在此学习到的技能探索不同的数据库和数据集时,您将能够处理栅格和矢量数据,并上传自己的数据源。有关在 geemap 上的有用附加功能列表,请参见 geemap GitHub 页面。不过,我也想向您介绍 GEE 的一个替代方案。

Leafmap: Google Earth Engine 的替代方案

在不依赖于 GEE 的情况下可视化地理空间数据并不受限制!如果您没有访问 GEE 帐户或不感兴趣使用 GEE,可以考虑使用 Leafmap。Leafmap 是一个 Python 包,允许您在 Jupyter Notebook 环境中可视化交互式地理空间数据。它基于您已经使用过的 geemap 包,但正如您在本节中将看到的,Leafmap 提供了在 GEE 平台之外访问地理空间数据的功能。其图形用户界面减少了您需要编写的代码量。它的核心是各种开源包。

Leafmap 适用于许多不同的绘图后端,包括 ipyleaflet(在此上下文中,后端 是在服务器上运行并接收客户端请求的内部代码)。用户看不到后端,但它们仍在运行。

您可以通过GitHub 链接访问 Jupyter Notebook Leafmap。根据您的 Python 版本,请参阅Leafmap 文档获取特定的安装说明。(如果您不确定您安装了哪个版本,请在终端输入**python**,它将输出您已安装的版本号。这对于遇到包安装问题时非常重要。)

可以设置一个新的环境来使用 Leafmap。我最初使用 Python 3.8 创建了下面代码中显示的 Conda 环境,但后续版本也可能适用。我将这个环境命名为geo,因为它在不同版本的 Python 中运行:

conda create -n geo python=3.8
conda activate geo
conda install geopandas
conda install leafmap -c conda-forge
conda install mamba -c conda-forge
mamba install leafmap xarray_leaflet -c conda-forge
conda install jupyter_contrib_nbextensions -c conda-forge
pip install keplergl

与之前一样,要打开 Notebook,请输入**jupyter notebook**并按 Enter 键。现在将以下代码输入到 Notebook 中,类似于显示在图 4-11 中的内容:

from ipyleaflet import *
m = Map(center=[48.8566, 2.3522], zoom=10, height=600, widescreen=False,
basemaps=basemaps.Stamen.Terrain)
m

Leafmap 中安装底图

图 4-11. Leafmap 中安装底图

更改底图就像将光标放在底图括号内并在键盘上选择 Tab 键一样简单。图 4-12 展示了可用的选项。这里选择了 Esri 作为底图,但您可以向上或向下滚动直到找到合适的选项。一定要探索。一旦输入**Esri**,选项将显示。

Leafmap 中更改底图

图 4-12. Leafmap 中更改底图

另一个有用的工具是能够预设您的缩放级别。当您在 Notebook 中运行单元格时,您将可以在不同的缩放级别之间滑动选择:

m.interact(zoom=(5,10,1))

图 4-13 显示了输出。

Leafmap 中的缩放级别

图 4-13. Leafmap 中的缩放级别

您还可以通过将小地图插入到较大地图中提供参考,如图 4-14 所示。要执行此操作,请输入以下代码:

minimap = Map(
    zoom_control=False, attribution_control=False, 
    zoom=5, center=m.center, basemap=basemaps.Stamen.Terrain
)
minimap.layout.width = '200px'
minimap.layout.height = '200px'
link((minimap, 'center'), (m, 'center'))
minimap_control = WidgetControl(widget=minimap, position='bottomleft')
m.add_control(minimap_control)

显示在图 4-14 中的小地图将帮助用户在更大的背景下保持方向感。

地图中的地图:Leafmap 中的小地图功能

图 4-14. 地图中的地图:Leafmap 中的小地图功能

总结

本章探讨了 Google Earth Engine 及其相关工具、库和包,这些工具可用于回答地理空间问题,并介绍了另一种工具 Leafmap。这一章及其相关笔记本将成为你在下一章项目中的便捷参考。你已在画布上呈现了可视化效果并创建了地图。接下来,你将开始分析这些关系并探索工具,以进行地理空间数据的高级分析。

第五章:使用 OSMnx 访问地理空间数据

开放街图(OSM)是一个由志愿者建立的可编辑的全球地理数据库,其宏伟目标是为我们所有人创建地理数据并免费分发。如果您使用 GPS 进行导航或任何启用位置的设备,您已经在智能手机上与 OSM 互动过了。Python 提供了一个名为OSMnx的包,允许城市规划者和其他各种用户创建街道网络,并与和分析通常“隐藏”的地理信息互动。您可以找到适合步行、驾驶或骑行的城市网络,用于个人使用或研究,比如研究城市环境的特征。强大的分析揭示了基础设施框架,例如在分析道路网络和道路相互关联的性质时揭示了低效率。

就我个人而言,我认为街道网络是艺术作品。但它们真正的用途可能被低估了,即为您的建筑基础设施添加几何形状。您可以添加建筑物(例如医院、学校和杂货店)、公园和其他数据框架,分类为边缘、建筑物和区域。术语建筑物定义宽泛,有一个关于建筑标签的维基。查找地名的好资源是OSM Nominatim。您还可以添加兴趣点、海拔高度等等。

乍一看,OSMnx 可能看起来有些技术性和复杂。但随着您在本章中使用 OSM 建立街道网络,您将学会应用和互动这个功能强大、可定制的包。

开放街图的概念模型

OSM 数据库包含超过 80,000 个标签键和 600 种关系类型,因此您可以为地图定义适当的粒度。在本章中,您将学习如何访问这些功能,自定义它们,将它们添加到您的地图中,并进行分析。例如,您可以识别一个位置并探索农业、商业或住宅用地的分布。(您可以通过查询数据库获取的不同土地使用价值的快照显示在本节后面的图 5-2 中。)了解一个区域的土地利用分布可能很重要,如果您对该地区发生的洪水量或如何计算暴雨径流感兴趣的话。截至撰写本文时,数据库中最流行的标签包括关于建筑物、高速公路、地表和水路的数据。

OSM 的结构有一些规则,但它们相对来说很简单。例如,节点可以是任意(可散列)的 Python 对象,节点和边缘都可以是可选的键/值属性。要查看这些可选值的全部内容,请前往 OSM 的地图功能标签信息(或者在文本中跟随)。

你可以将可变对象(如列表)放在元组中。元组仍然是不可变的,但是您可以更改其中的列表,因此它不可哈希。

标签

OSM 应用一个由键-值对组成的标签。您将在我们将编写的代码单元中看到这些定义。它们的格式是key=value,例如,如果您的位置键设置为highway,值设置为residential,则可以知道这是一条人们居住的道路。

这里有几个标签的示例:

surface=asphalt
highway=residential
building=commercial 

根据你的需求,这种粒度可能在数据探索中很有用。例如,我曾使用这些标签来探索城市环境中的不透水表面。不透水表面往往会吸热,而且这些地方往往有较高的洪水发生率——这在比较不同社区特征时是重要信息。举个例子,你可能只是想要一个某社区内所有建筑物的地图。然后默认值buildings="all"将被上传。我们将探讨包含标签在本章代码示例和示例笔记本中的实用性。

OSMnx 提取 OSM 数据并创建一个可用于处理复杂网络的可路由的Python NetworkX对象。这是将来自 OSM 的边缘和节点转换为具有路由特性的链接和交叉点所需的。这些可路由的地图是具有诸如行驶时间、速度限制和位置之间的最短距离等功能的网络,允许进行路径规划。

曼哈顿切尔西社区可行走街道网络地图

图 5-1. 曼哈顿切尔西社区的可行走街道网络地图

OSMnx 自动化了多个地图功能,包括下载像州、城市、社区、建筑物轮廓、定制街道网络和拓扑结构等几何图形。此外,OSMnx 还具有基于图论基础的分析功能。简单来说,图论 表示空间网络中元素及其位置之间的连接,包括节点和边缘。NetworkX 集成允许您自定义节点和边缘以容纳各种数据、图算法和用于分析的网络结构。除了地理坐标外,每个位置还存储大量信息。我们将很快开始探索这些数据类型。首先,这里是引擎盖下的简要介绍。

多重图

图 5-1 显示节点之间的边缘。这是本章稍后将要构建的一个预览。节点是通过街道段连接的角落,例如本例中按长度加权。你也可以按行驶时间加权。这是计算两地之间最短路径的一种方式。

您将在下一节中请求的数据将以 NetworkX 多重有向图的格式呈现。多重有向图是对具有多条边的对象或元素的抽象表示。它们是有向的,反映了例如特定城市街道上的交通是单向的还是双向的。有向图的边从节点指向节点,但不一定是双向的。一旦您有了多个平行的边,您就有了一个多重有向图。

OSM 是一个维基,是一个开源的可编辑地理数据库。其概念模型包括节点定义空间中的点,道路定义线性特征和边界,以及关系解释节点和道路如何共同工作。将节点看作是由纬度和经度定义的坐标,道路代表节点列表(称为折线)或多边形的边界。

您可能最初将这些空间网络视为简单的平面,但实际上有很多非平面结构,比如桥梁、隧道以及各种等级分隔的结构,如高速公路、上下匝道和过街天桥。您可以使用拓扑测量和度量结构或以空间单位(如米)描述的长度和面积来评估这些结构。维基描述了土地利用标签与可以分配给关键的值之间的关系,如图 5-2 所示。

地理数据问题通常基于特定位置进行定义,并且通常具有现实世界的影响。它们经常涉及土地利用、道路表面、建筑物的数量和类型,或者社区设施的位置,比如博物馆、酒吧或互联网接入点。例如,您的数据问题可能是这样的:

  • 华盛顿特区第七选区有多少个超市?

  • 特定社区有多少个绿地或公园?

  • 芝加哥伊利诺伊州有多少步行街?

OSM 数据库中可用于描述土地利用的一些标签

图 5-2。OSM 数据库中可用于描述土地利用的一些标签

首先,让我们安装 OSM。

安装 OSMnx

在您的终端中为 OSMnx 安装一个独特的环境。正如我之前所指出的,我创建不同的环境是为了简单地解决依赖关系和更新问题。另一个从艰难经验中学到的教训是:如果您需要更新软件包,请将其删除并重新安装到您的环境中。我经常在特定项目之后删除环境,因为如果您随意创建并让它们长时间停留在那里,它们可能会占用您硬盘上的大量空间。如果您以具体的方式命名它们,这将更加容易。这似乎是确保您所有依赖项也已更新的唯一方法。如果您在代码中收到一个函数已被弃用的消息,请返回至用户参考文件以获取澄清。

在你的终端中输入:

conda create -name OSM
conda activate OSM
conda install -c conda-forge osmnx
conda install -c conda-forge matplotlib
conda install -c conda-forge descartes 
conda install -c conda-forge shapely 
conda install -c conda-forge jupyter
conda install -c conda-forge jupyter_contrib_nbextensions

你将使用 OSMnx 检索 OSM 数据和 matplotlib 库与数据进行交互和可视化。

如果你想查看与 OSMnx 一起安装的软件包,进入conda activate env后,在终端中输入**conda list**。如果忘记了环境的名称,可以写**conda env list**并查看所有环境。在这里,我创建了一个名为OSM的唯一环境:

(ox) MacBook-Pro-8:~ bonnymcclain$ conda list
# packages in environment at /Users/bonnymcclain/opt/miniconda3/envs/OSM:

当你准备启动一个笔记本时,在你的终端中键入**jupyter notebook**,一个笔记本将会打开。

选择一个位置

为了传递place_name,OSMnx 使用 OSM Nominatim API。Nominatim 文档包含一个 API 参考指南,用于搜索地理编码(参见图 5-3)。如果输入不正确的信息(例如,如果你按名称寻找但拼写错误),你可能会收到错误。打开Nominatim,你将进入一个用于搜索所请求位置的调试界面。我从那里开始确保我查询正确。

使用 Nominatim 查询的 API 请求

图 5-3. 使用 Nominatim 查询place_name的 API 请求

虽然可以将地名作为字符串添加,但最好先检查 Nominatim,并查看位置如何列出,以避免冲突或不正确的数据。例如,我想查找加利福尼亚州的 Brentwood,但出现了错误,所以我通过 Nominatim 运行了它。结果发现 Brentwood 作为一个社区的边界与 Brentwood 的行政边界不同,还有 Brentwood Heights。我需要稍微调整如何称呼这个社区。

当我们开始处理这些关系时,这将更加清晰。查阅OSMnx 文档获取更多信息,但你也可以直接在你的代码单元格中查询。

假设你想要洛杉矶的详细街道地图。在以下代码中,你将从导入所需的包开始:osmnx 和 matplotlib。包括place_name并将其设置为所需位置(在代码中用引号括起来)。如果你看的不仅仅是城市和州,你包含的细节越多越好。运行以下代码(可能需要一些时间):

import osmnx as ox
import networkx as nx
import matplotlib.pyplot as plt #installs with osmnx

# download/model a street network for some city then visualize it
G = ox.graph_from_place("Los Angeles,California,United States", 
network_type="drive")
fig, ax = ox.plot_graph(G,figsize=(20,20),bgcolor='#FFFFFF',
    node_color='black', node_size=0)

函数以ox.module_name.function_name()的格式编写。之前的代码片段引用了osmnx.graph模块和graph_from_place函数。大多数函数只需调用ox.function_name()即可。这将获取洛杉矶,加利福尼亚州,美国内的地理编码信息,以及边界内的可驾驶街道网络数据(参见图 5-4)。

如果你喜欢,可以选择一个较小的位置。较大的城市需要大量资源,可能需要几分钟来加载。本章后面我们将探索加利福尼亚州卡尔弗城。

将你的光标放在你的代码中指向的☞图标指向的括号内。这里是为了清晰起见而隔离的片段:

G = ox.graph_from_place(☞"Los Angeles,California")

当你的光标位于括号内时,选择键盘上的 Tab + Shift。

由 OSMnx 生成的洛杉矶市街道网络

图 5-4. 由 OSMnx 生成的洛杉矶市街道网络

让我们看看可能的参数!

例如,图 5-5 显示了签名的特征。这是参数列表,它们需要与函数签名中列出的参数匹配。尝试不同的选项,看看地图如何变化。

发现函数内可用的参数

图 5-5. 在函数内发现可用参数

函数选项的network_type如图 5-5 所示:

drive

可行驶的公共街道(不包括服务道)

drive_service

可行驶的公共街道,包括服务道

walk

所有可供行人使用的街道和路径(忽略单向性,始终使用相邻节点之间的相互连接有向边)

bike

所有可供自行车使用的街道和路径

all

所有(非私有)OSM 街道和路径

all_private

所有 OSM 街道和路径,包括私人通道

理解参数和参数

你听过这句话吗,“没有人会来救你”?我确信这是由数据科学家首次说出的。当你的代码无法执行(相信我,这不是“如果”,而是“何时”),你需要阅读用户文档。这可能是关于软件包本身的文档,或者可能是一个 GitHub 存储库。功能和参数已经过时并且用新版本进行更新。知道如何找到它们是一个重要的技能。

如果你在参数下滚动,你会注意到这些变量在代码片段中的定制。我不能在这里包含每个函数和相关的参数,但我们来看一些例子。选择右上角的倒置尖角以显示框的内容(如图 5-5 所示)。向下滚动,你会看到一个名为“文档字符串”的部分。

文档字符串提供了有关类、函数或方法的快速信息。我在这里寻找如何构建我的查询的提示。当你需要关于函数的额外信息时,在括号内返回你的光标并选择 Tab + Shift。这里有一个可用文档的示例:

Docstring:
Create graph from OSM within the boundaries of some geocodable place(s).

The query must be geocodable and OSM must have polygon boundaries for the geocode 
result. If OSM does not have a polygon for this place, you can instead get its 
street network using the graph_from_address function, which geocodes the place 
name to a point and gets the network within some distance of that point.

If OSM does have polygon boundaries for this place but you’re not finding it, 
try to vary the query string, pass in a structured query dict, or vary the 
which_result argument to use a different geocode result. If you know the OSM ID 
of the place, you can retrieve its boundary polygon using the geocode_to_gdf 
function, then pass it to the graph_from_polygon function.

当您在界面中继续向下滚动时,您将看到一个“参数”部分。该部分提供关于数据类型的信息以及如何完成查询的说明。根据您的查询,不是所有参数都是必需的。如果您省略了一些参数,则会显示默认值。

再看一下用于生成 图 5-4 中洛杉矶地图的代码片段。此地图需要为network_type参数输入信息:在本例中,network_type="drive"。该函数接受一个字符串(因此要用引号括起来),选项在括号内列出并且必须在括号内。这些选项如文档中所列:

Parameters
----------
query : string or dict or list
    the query or queries to geocode to get place boundary polygon(s)
network_type : string {"all_private", "all", "bike", "drive", "drive_service", 
"walk"}
    what type of street network to get if custom_filter is None
simplify : bool
    if True, simplify graph topology with the `simplify_graph` function
retain_all : bool
    if True, return the entire graph even if it is not connected.
    otherwise, retain only the largest weakly connected component.
truncate_by_edge : bool
    if True, retain nodes outside boundary polygon if at least one of
    node's neighbors is within the polygon
which_result : int
    which geocoding result to use. if None, auto-select the first
    (Multi)Polygon or raise an error if OSM doesn't return one.
buffer_dist : float
    distance to buffer around the place geometry, in meters
clean_periphery : bool
    if True, buffer 500m to get a graph larger than requested, then
    simplify, then truncate it to requested spatial boundaries
custom_filter : string
    a custom ways filter to be used instead of the network_type 
    presets  e.g., '["power"~"line"]' or 
    '["highway"~"motorway|trunk"]'. Also pass in a network_type that 
    is in settings.bidirectional_network_types if you want graph 
    to be fully bi-directional.

计算旅行时间

一种受欢迎的数据问题类型涉及到旅行时间:从点 A 到点 B 需要多长时间?

要回答这类问题,您可以绘制网络(其中边交叉)并计算自由流行驶时间,使用在特定公路类型上允许的最大速度。换句话说,当考虑到道路上的最大速度时,计算驾驶距离所需的时间定义为自由流行osmnx.speed 模块通过提供每小时公里数作为 speed_kph 边属性来计算速度和旅行时间。复杂的网络由节点和边之间的连接形成。使用 NetworkX Python 包分析这些结构允许您将节点视为元素,并将节点之间的连接视为边以研究它们的关系。

让我们尝试运行一个计算旅行时间的函数:

ox.speed.add_edge_speeds(G, hwy_speeds=None, fallback=None, precision=1)
ox.speed.add_edge_travel_times(G, precision=1)

请注意,在此处您有四个输入。让我们对此进行分解,并查看 OSM 文档如何定义它们:

G

输入图

hwy_speeds

公路类型中所有边的平均速度值(如果为空)

f``allback

如果道路类型没有预设值,则备用速度

p``recision

舍入到所需的小数精度

只传递G,即 NetworkX MultiGraph,到函数中。如果同时指定了起点(orig)和终点(dest),您将得到从源到目标的最短路径的单个节点列表。这些路线是不同的,用于展示如何绘制travel_timesedge_speeds以找到目的地之间的最短路径。

您正在将权重设置为等于w,而不是默认的空值。看起来是这样的:

G = ox.add_edge_speeds(G)
G = ox.add_edge_travel_times(G)

w = 'travel_time'
orig, dest = list(G)[10], list(G)[-10]
route1 = nx.shortest_path(G, orig, dest, weight=w)
orig, dest = list(G)[0], list(G)[-1]
route2 = nx.shortest_path(G, orig, dest, weight=w)
orig, dest = list(G)[-100], list(G)[100]
route3 = nx.shortest_path(G, orig, dest, weight=w)

routes = [route1, route2, route3]
rc = ['r', 'y', 'c']
fig, ax = ox.plot_graph_routes(G, routes, route_colors=rc, route_linewidth=6, 
figsize=(30, 30),node_size=0,bgcolor='#FFFFFF')

图 5-6 也使用了 OSMnx 的颜色选项。颜色选项 可以描述作为路径中每个边的属性值。属性值 可以表示属性,例如有关如何构造图的数据、在绘制图时顶点的颜色或仅是加权图中边的权重。在 图 5-6 中,路线用不同的颜色表示。

计算洛杉矶点之间的最短距离,通过计算加权旅行时间

图 5-6. 计算洛杉矶点之间的最短距离,通过计算加权旅行时间

完整的街道网络确实会消耗资源,因此让我们选择一个特定的社区来获取一个更可管理的可视化查询。在随后的代码片段中,我指定了加州卡尔弗城(Culver City),包括城市边界内的所有私人道路(请参见 图 5-7):

place_name = 'Culver City, California, United States'
G = ox.graph_from_place(place_name, clean_periphery=False,
network_type="all_private")
fig, ax = ox.plot_graph(G,figsize=(7,7),bgcolor='#FFFFFF',
     node_color="b",node_size=3)

加州卡尔弗城(Culver City)的街道网络地图

图 5-7. 加州卡尔弗城的街道网络地图(邻里级别)

如果您对节点或边缘的着色感兴趣,可以在 OSM wiki 页面或用户参考文档中探索 key:colour 选项。图 5-8 显示了最常见的颜色代码,但并非穷尽所有。颜色变化在某种程度上是不言自明的,但您可以在 OSM wiki 上查看确切的颜色。我发现这个图表对获取我经常使用的颜色代码非常有用。

OSM 中的关键颜色选项(非穷尽,但是一个方便的参考)

图 5-8. OSM 中的关键颜色选项(非穷尽,但是一个方便的参考)

您已经在 OSMnx 中构建了几个街道网络和地图。现在让我们看看您可以提取哪些额外信息。

OSMnx 中的基本统计量

可视化街道网络是有意义的,但 OSMnx 框架还允许您生成描述性的几何和拓扑测量。地形测量可以提供像每条街道平均节点数这样的见解。

迂回度

为了介绍迂回度的概念,让我们计算曼哈顿的平均迂回度,并将其与斯塔滕岛进行比较。迂回度 是网络距离与直线距离的比率。迂回度是城市网络中的重要指标;它显示了交通效率,并可能显示出不同地理区域和人口之间的不平等。不同社区、不同地铁路线以及旅行的长度和时间的迂回度平均值不同。研究人员在研究中使用迂回度作为发现纽约市地铁闸机进入次数与 COVID-19 病例和死亡之间关联的一部分,该研究跨越了 2020 年 3 月至 5 月。

您从点 A 到点 B 的迂回程度与其他旅程相比,依赖于多种因素,例如土地利用系统、交通选项设计、枢纽和成本。诸如迂回度这样的网络距离是基于城市的实际网格和布局计算的,而不是“鸟儿飞行”——也就是说,两点之间的简单直线。您可以始终假设到达位置的实际距离要远得多。

如果您在不同的章节之间移动,我将重复导入的详细信息。代码中有一个新行:

 %matplotlib inline

百分号表示这是 Python 的“魔法函数”。参数是接下来的行:

ox.__version__

这些魔法函数(或者双下划线的方法)提供了额外的功能。在这个例子中,matplotlib 将在 Jupyter Notebook 中的代码正下方显示绘图命令。

圆括号不是必需的。这将把绘图输出放在 Jupyter Notebook 的编码单元格下面:

import osmnx as ox
import networkx as nx
import matplotlib.pyplot as plt #installs with osmnx
import pandas as pd
%matplotlib inline
ox.__version__

这里是曼哈顿旅行的一个值,它有一个健全的交通框架:

# get the network for Manhattan, calculate its basic stats and show the average 
circuity stats = ox.basic_stats(ox.graph_from_place("Manhattan,New York,United States"))
stats["circuity_avg"]

输出为 1.0366712806100773。

您现在可以将这个值与其他行政区进行比较。较低的回路度值表示更高的效率。纽约市的其他四个行政区提出了不同的交通挑战。例如,在 Staten Island 内部旅行,比在曼哈顿内部旅行要间接多 4%,正如以下代码的输出所示:

# get the network for Staten Island, calculate its basic stats and show the 
average circuity stats = ox.basic_stats(ox.graph_from_place("Staten Island,New York,United States"))
stats["circuity_avg"]

输出为 1.0732034499369911。

网络分析:巴黎,法国的回路度

让我们来玩一点,探索巴黎,具体来说是它的城市基础设施的一部分——道路。首先,让我们计算回路度:

stats = ox.basic_stats(ox.graph_from_place("Paris, France"))
stats["circuity_avg"]

输出结果为:1.0377408863844562。

查看一个地方的网络统计数据就像更新地方名称(根据 OSM 的 Nominatim 标准)并运行单元格一样简单。然后将加载街道网络以及以平方米为单位的区域。让我们获取巴黎的街道网络和区域:

# get street network, and area in square meters
place = "Paris, France"
gdf = ox.geocode_to_gdf(place)
area = ox.project_gdf(gdf).unary_union.area
G = ox.graph_from_place(place, network_type="drive")

现在让我们计算并合并统计数据:

# calculate basic stats, merge them, and display
stats = ox.basic_stats(G, area=area)
pd.Series(stats)

街道和网络的计数存储为统计字典中的嵌套字典。以下代码展开了这些嵌套字典,然后将它们转换为一个 pandas 数据框架:

# unpack dicts into individual keys:values
stats = ox.basic_stats(G, area=area)
for k, count in stats["streets_per_node_counts"].items():
    stats["{}way_int_count".format(k)] = count
for k, proportion in stats["streets_per_node_proportions"].items():
    stats["{}way_int_prop".format(k)] = proportion
# delete the no longer needed dict elements
del stats["streets_per_node_counts"]
del stats["streets_per_node_proportions"]

# load as a pandas dataframe
pd.DataFrame(pd.Series(stats, name="value")).round(3)

这个例子可能比大多数情况都更详细,但我希望你能看到用来计算诸如最短距离之类的测量数据。^(1)

一目了然,您可以确定从每个节点出来的街道数。如果您对密度感兴趣,您可以计算交叉口的数量或线性街道距离的米数。具体而言,线性街道距离表示网络的无向表示中总街道长度的总和,意味着未捕获方向性(单向和双向街道)。这些信息对于驾驶指南很重要,但对于步行指南则不那么重要。

中介中心度

让我们问一个数据问题:巴黎街道的平均长度是多少?平均街道长度是块大小的一个很好的线性代理。这些测量数据提供了关于步行便利性和房屋价格的城市规划信息;例如,较小的块大小转化为更高的步行便利性和更高的房价。图 5-9 展示了我们统计数据的总体输出。

介数中心性 衡量了一个位置或节点在更大网络或邻域中的中心性。您可以看到在巴黎,介数中心性最高的区域有所有最短路径的 11% 通过其边界。这告诉我们什么?较短的道路段通常围绕中央商务区或历史区聚集,或者作为导航城市的导管。

巴黎市的网络统计数据

图 5-9. 巴黎市的网络统计数据

节点有多重要?想象一下信息必须通过一个人才能传递给组织的其余部分的命令链。如果只有 11% 的信息必须通过它传递,那么通常它对网络流动并不重要。您可以在 图 5-10 的左侧图像 (A) 中看到最大节点作为一个小红点。在下面的代码单元格中,计算节点之间的最短路径。参数权重考虑了边属性长度。该函数将返回介数中心性:

# calculate betweenness with a digraph of G (ie, no parallel edges)
bc = nx.betweenness_centrality(ox.get_digraph(G), weight="length")
max_node, max_bc = max(bc.items(), key=lambda x: x[1])
max_node, max_bc

输出为 (332476877, 0.1128095261389006)

(A) 巴黎介数图中介数最大节点的有向图; (B) 可视化图中的每个节点

图 5-10. (A) 巴黎介数图中介数最大节点的有向图; (B) 可视化图中的每个节点

节点颜色 (nc) 将检索出具有最高中心性的节点,并在图对象中观察结果,如 图 5-10 所示。节点大小 (ns) 也已计算。您可以根据您想要的可视化调整这些值:

nc = ["r" if node == max_node else "grey" for node in G.nodes]
ns = [100 if node == max_node else 15 for node in G.nodes]
fig, ax = ox.plot_graph(G, node_size=ns, node_color=nc, node_zorder=5)
plt.show()

接下来,尝试为每个节点添加颜色,以可视化相对于 图 5-10 (B) 中所有节点的所有最短路径的 11%:

# add the betweenness centrality values as new node attributes, then plot
nx.set_node_attributes(G, bc, "bc")
nc = ox.plot.get_node_colors_by_attr(G, "bc", cmap="plasma")
fig, ax = ox.plot_graph(
    G,
    node_color=nc,
    node_size=30,
    node_zorder=2,
    edge_linewidth=0.2,
    edge_color="w",
)

在视觉上观察街道网络时,识别重要节点和中心度量可能是一项挑战。绘制节点属性为您提供了一个易于识别的区域进行深入审查。附近还有哪些其他特征?这如何影响我们在这个节点上观察到的值?

网络类型

我们在法国再逗留一会儿怎么样?在 Jupyter 笔记本中,我经常重复导入函数以简化操作,避免在运行或重新运行部分时上下滚动。我在这里也会做同样的事情:

import osmnx as ox
import networkx as nx
import geopandas as gpd
import matplotlib.pyplot as plt
plt.style.use('default')
import pandas as pd

有几种网络类型可以探索。最常见的是 walkbikedrive,或者您甚至可以探索 all。在像巴黎这样的城市中,一些选项可能非常拥挤。让我们试试 drive,并探索它的测量结果。在您的笔记本中生成该图:

place_name = "Paris, France"
graph = ox.graph_from_place(place_name, network_type='drive')
fig, ax = ox.plot_graph(graph)

您想要边缘(街道),因此您需要创建一个变量:

edges = ox.graph_to_gdfs(graph, nodes=False, edges=True)

列类型是什么?投影是什么?以下代码单元格提供了此信息:

edges.columns

输出为:

Index(['osmid', 'name', 'highway', 'maxspeed', 'oneway', 'reversed', 'length',
       'lanes', 'geometry', 'junction', 'width', 'bridge', 'tunnel', 'access',
       'ref'],
      dtype='object')

您还需要了解 CRS 以识别地理空间中的位置:

edges.crs

并且输出如下:

<Geographic 2D CRS: EPSG:4326>
Name: WGS 84
Axis Info [ellipsoidal]:
- Lat[north]: Geodetic latitude (degree)
- Lon[east]: Geodetic longitude (degree)
Area of Use:
- name: World.
- bounds: (-180.0, -90.0, 180.0, 90.0)
Datum: World Geodetic System 1984 ensemble
- Ellipsoid: WGS 84
- Prime Meridian: Greenwich

我是一个视觉人士,所以我喜欢看到带有数据样本的列标题。接下来设置好这个:

edges.head()

现在,您可以运行以下代码,以了解各种道路类型及其分类的摘要:

print(edges['highway'].value_counts())

这是输出:

residential                                  8901
primary                                      2984
tertiary                                     2548
secondary                                    2542
unclassified                                  648
living_street                                 458
trunk_link                                    194
trunk                                         146
primary_link                                  118
[residential, living_street]                   41
secondary_link                                 37
[unclassified, residential]                    30
tertiary_link                                  20
motorway_link                                  16
…

OSM 包含关于道路的信息,您可以为各种用途访问这些信息。任何位置或城市的基础设施都可以提供关于如何轻松进出该位置的交通信息,这可能会影响社区、社区以及更大的人口网络的社交性质。

自定义您的社区地图

让我们看看如何创建、自定义、自动下载和比较街道网络地图。对于此练习,我们将回到库尔弗城并生成一个社区地图。

地点的几何图形

以下代码生成了加利福尼亚库尔弗城的地图,见图 5-11;包括标签 building 将在地图上添加建筑物轮廓:

place = "Culver City, California"
tags = {"building": True}
gdf = ox.geometries_from_place(place, tags={'building':True})
gdf.shape

我默认使用 #FFFFFF(白色)作为背景色,以便更容易打印地图:

fig, ax = ox.plot_geometries(gdf, figsize=(10, 10),bgcolor='#FFFFFF')

请继续看看你能创造出什么。图 5-11 展示了具有建筑物轮廓的城市几何图形。

使用地点调用特定地理位置的几何图形,来自加利福尼亚库尔弗城的 OSMnx

图 5-11. 使用地点调用加利福尼亚库尔弗城的 OSMnx 中特定几何图形

来自地址的几何图形

也许您有一个地理坐标,您可以通过将这些坐标传递给 geometries_from_address 函数来探索该位置(见图 5-12)。所有几何图形都将转换为您选择的 CRS。例如,荷兰的投影坐标系统是 EPSG:28992。

这是生成图 5-12 中地图的代码:

gdf=ox.geometries.geometries_from_address((52.3716,4.9005),dist=15000, 
tags={'natural':'water','highway':''})
gdf.to_crs(epsg=28992, inplace=True)
gdf.plot(figsize=(16,16))
gdf.plot(figsize=(16,16))

通过地理坐标和 EPSG 投影查看荷兰阿姆斯特丹

图 5-12. 通过地理坐标和 EPSG 投影查看荷兰阿姆斯特丹

您也可以更改参数来探索其他地理区域。确保更改投影以获得最高级别的准确性。(友情提醒:地球不是一个完美的球体,因此您需要选择最适合地理区域的投影以最小化失真。)以下代码包含额外的标签:

gdf=ox.geometries.geometries_from_address(('Manhattan, NY'),dist=15000, 
    tags={'natural':'water','building':'commercial','landuse':'commercial',
    'landuse':'residential','highway':'unclassified','highway':'primary'})
gdf.to_crs(epsg=2263,inplace=**True**)
gdf.plot(figsize=(20,29))

图 5-13 展示了此代码的输出。多亏了这些标签,该地图显示了水域、商业建筑、商业和住宅用地以及高速公路。这些标签是探索性的。看看使用不同组合会发生什么。

使用标签为地图添加特征

图 5-13. 使用标签为地图添加特征

在下一个示例中,你将使用 custom_filter 查询要包含在图中的特定路径,例如 motorway。使用 custom_filter 指定你想要的内容将大大减少组装图所需的资源。

术语 motorway_linktrunk_link 在 OSM 中具有特定含义(如 OpenStreetMap Wiki 所解释)。它们描述了高速公路如何连接:例如匝道(查看 图 5-14)。

输入以下代码:

cf = '["highway"~"motorway|motorway_link|trunk|trunk_link"]'
G = ox.graph_from_place("Madrid", network_type="drive", custom_filter=cf)
fig, ax = ox.plot_graph(G, node_size=3,node_color='black',edge_linewidth=3,
bgcolor='#FFFFFF')

custom_filter (cf) 需要像 "highway"~"motorway" 这样的 network_type 预设。我们想要同时是高速公路和主干道的高速公路,以及 Python 中的二进制 OR (|) 运算符。该参数同时涵盖了高速公路和主干道(进出高速公路的匝道)的值。

输出 (图 5-14) 显示了一张地图,仅显示了马德里,西班牙的高速公路。

使用自定义过滤器查询马德里高速公路

图 5-14. 使用自定义过滤器 (cf) 查询马德里高速公路的高速公路

为了全面了解高速公路地图,你可以轻松地使用以下代码调出马德里更详细的地图,其中包括建筑轮廓 (图 5-15):

place = "Madrid, Spain"
tags = {"building": True}
gdf = ox.geometries_from_place(place, tags)
fig, ax = ox.plot_footprints(gdf, figsize=(20, 20),alpha=1,color='black',
bgcolor='#FFFFFF',save=True,filepath='settings.imgs_folder/image.png',show=True,
dpi=600)

注意,save 设置为 True。保存文件很重要。我正在使用默认的 filepath,但你可以指定一个精确的文件夹或位置,比如你的下载文件夹。

马德里,西班牙的建筑轮廓

图 5-15. 马德里,西班牙的建筑轮廓

图 5-16 是下载整个网络 (network_type="drive") 的示例,在没有过滤器或自定义的情况下。这些渲染可以是非常艺术的,并经常被整合到制图和地图制作或商业产品中。运行此代码以生成地图:

import osmnx as ox
import networkx as nx
import matplotlib.pyplot as plt
%matplotlib inline 
place_name = 'Madrid,Spain'
G = ox.graph_from_place(place_name,network_type="drive")
fig, ax = ox.plot_graph(G,figsize=(20,20),bgcolor='#FFFFFF',
    node_color='black', node_size=0)

networkx 函数将所有网络带入图中

图 5-16. networkx 函数将所有网络带入图中。

本章的最后一个图是使用 network_type="all" 的不同值创建的。图 5-17 显示了如果通过交换字符串 (all_privateallbikedrivedrive_servicewalk) 更改地理编码查询的输出会发生什么。

G = ox.graph_from_place(
    "Madrid, Spain",network_type="all",
    retain_all=False,
    truncate_by_edge=True,
    simplify=True,

)

fig, ax = ox.plot_graph(G, figsize=(20, 20),node_size=0, edge_color="#111111", 
edge_linewidth=0.7,bgcolor='#FFFFFF')

在 OSMnx 中观察到的马德里,西班牙,网络类型为“all”

图 5-17. 在 OSMnx 中以 network_type="all" 观察到的马德里,西班牙。

你可以通过选择其他 network_type 选项进行实验,并观察输出如何变化。

在 QGIS 中使用 QuickOSM

您在第三章中熟悉了 QGIS,我想简要指出,您也可以使用 QGIS 与 OSM 数据进行交互。在 QGIS 中,QuickOSM 与 GUI 类似工作。打开 QGIS,选择工具栏中的矢量,您将看到“快速查询”选项用于 QuickOSM(图 5-18)。QuickOSM 允许您在控制台中运行这些快速查询,并直接在画布上显示结果。

获取 QGIS 格式中的 GIS 数据访问权限,允许您选择多种设施并包含在单个地图图层中。当您的数据问题在范围上增长时,这是探索key:value关系的机会,可以超出笔记本之外。

在 QGIS 中集成 OSM key:value 对

图 5-18. 在 QGIS 中集成 OSM key:value

摘要

在本章中,您探索了 OpenStreetMap,并学习了如何生成全球街道网络,以及使用函数计算旅行时间、回路性和其他重要指标。

OSM 包含丰富的开源地理空间数据,您可以访问、建模、投影、可视化,并用于探索现实世界的街道网络、兴趣点、建筑位置、高程等。希望您继续使用它进行探索。现在我想向您展示如何将这种新技能与另一种工具,即 ArcGIS Python API 集成。

^(1) 如果您希望了解这些计算背后的详细信息和案例研究,请参阅 Boeing,G. 2017. “OSMnx: New Methods for Acquiring, Constructing, Analyzing, and Visualizing Complex Street Networks.” Computers, Environment and Urban Systems, 65: 126–139. https://doi.org/10.1016/j.compenvurbsys.2017.05.004。用户参考还提供了包含属性的字典摘要。

第六章:ArcGIS Python API

由 Esri 开发的客户端软件和 GIS 组成的套件被称为 ArcGIS。它不仅是一个 API,还是一个 Python 包,允许用户查询存储在 ArcGIS Online 或 ArcGIS Enterprise 中的信息。它不是开源的,但 Esri 在行业中的领导地位已经产生了大量免费内容和教程,供你访问和探索。我将分享一些学习资源、可访问的工具和信息,你可以通过 ArcGIS Python API 访问它们。你需要使用 Jupyter Notebook 和 ArcGIS Online 来跟进。

设置

ArcGIS Pro 是一个桌面应用程序,还有一个基于浏览器的平台叫做 ArcGIS Online;在本章中,你将使用 ArcGIS Online 和 ArcGIS Python API 进行工作。

ArcGIS Python API 中可用的模块

为了扩展标准 Python 库,ArcGIS Python API 允许通过点符号访问其他模块。你可以在 文档 中探索它们,随着我们在编码中使用它们,我会提供更多信息。

Arcgis.gis

将你连接到 ArcGIS Online

Arcgis.features

提供用于处理地理元素组的功能

Arcgis.geometry

允许输入和输出不同的几何体(例如点或多边形)

Arcgis.geocoding

为地图可视化分配位置坐标

Arcgis.geoenrichment

为区域添加属性

在教授地理空间技能时,我使用开源或低成本选项,以便让尽可能多的人获得这些知识。由于大多数 Esri 用户都是专业或企业订阅者,很少有人了解个人使用的成本负担。无论你选择什么,请在使用之前仔细阅读文档,以避免不必要的费用。

ArcGIS API 是作为 arcgis 包分发的。在下面的示例中,你将通过 Conda 安装它,这是建议的协议,就像你在本书的几个章节中已经做过的那样。

安装 ArcGIS Pro

虽然 ArcGIS Pro 只适用于 Windows,但 API 允许非 Windows 用户访问 ArcGIS 功能,而无需打开 Windows 环境。我假设如果你已经有完整的 ArcGIS Pro 许可证,你可以按照文档安装 Python Package Manager or Python Command Prompt。要在桌面环境中使用 Pro 许可证并与 API 一起工作,你需要在运行命令的同一台计算机上安装 ArcGIS。

提示

如果你和我一样拥有 ArcGIS Pro 账户,但更喜欢在 MacOS 上工作,你可以离线使用 ArcGIS Pro。为此,请登录你的 ArcGIS Pro 账户,在“设置 >> 许可证”下,选中允许你离线工作的复选框。

即使没有许可证,也有访问公开资源的选项,因此让我们探索一下。

设置你的环境

接下来,打开您的终端。我建议为下载 ArcGIS 创建一个环境。(您可能记得,环境允许您安装兼容版本的软件包。)在这里,我称我的环境为 esriENV

提示

虽然可以在环境中更新单个软件包,但如果在环境中工作时生成持久错误,我会将整个环境删除并从头开始创建。这是一个新的 Python 地理空间软件包,其中包含您可能希望考虑添加到任何创建的空间中的常用软件包:

mamba install -c conda-forge geospatial

您可能对这里列出的 Mamba 安装感兴趣。自本书编写以来,mambaforge 已成为 Conda-forge 社区项目。在操作上,Mamba 承诺速度和与 Conda 软件包的兼容性。在您的实验中,您将找到自己喜欢的工作流程。我已将 Mamba 安装到我的基础环境中,并在自己的项目中使用它,但本文是在长期可靠性的基础上编写的 Conda 环境。

让我们回顾一下如何创建您的环境并安装本笔记本所需的软件包。在终端中输入以下代码,但是在 *myenv* 的地方插入 的环境名称:

conda create --name myenv

要创建一个环境并包含一个特定版本的 Python 来解决软件包依赖关系,请输入以下代码:

#conda create --name myenv python=x.x
conda create --name esriENV python=3.9

现在,您必须激活您的新环境。目前我们只有一个频道或地方可以托管安装的软件包,即 conda-forge,因此我们希望所有需要安装软件包的依赖都来自 conda-forge 频道,除非它们只存在于默认频道上。

或者,您可能希望在列表中的任何频道上拥有最新版本的软件包。如果是这种情况,请使用conda config --set channel_priority strict

conda activate esriENV
#conda config --set channel_priority strict

安装软件包

您已准备好安装您的软件包:

conda install -c esri arcgis

接下来,您将需要安装 Jupyter Notebook;IPython 内核,以在 Conda 环境中执行 Jupyter 的 Python 后端;以及 Jupyter Notebook 扩展,以简化工具(如自动完成)的编码任务:

conda install -c conda-forge notebook
conda install -c conda-forge nb_conda_kernels
conda install -c conda-forge jupyter_contrib_nbextensions
 conda install -c conda-forge bokeh

在您的终端中,使用您给它起的名称激活 ArcGIS 环境:

Conda activate esriENV

下载完成后,您可以再次从终端打开 Jupyter Notebook,以便逐步记录您的工作并评估输出结果:

jupyter notebook

笔记本将与 API 一起安装,并在新窗口中打开。

连接到 ArcGIS Python API

安装完成后,现在是时候登录了。首先,我将向您展示如何匿名登录;随后的部分将解释如何使用开发者或个人账户登录。

作为匿名用户连接到 ArcGIS Online

有许多方法可以免费或几乎不收费地访问 ArcGIS 的部分内容;即使没有 ArcGIS 账户,您也可以使用资源、创建地图并共享它们,但功能有限。您可以通过运行代码或查阅文档在 Jupyter Notebook 中探索这些选项。公共账户的另一个好处是它允许匿名访问。

您将首先在您的 Jupyter Notebook 中导入 Python API。在这个示例中,您将使用一个公共账户,因此只显示免费资源。没问题——这里有很多东西可以让您探索。要从gis模块导入 GIS,运行:

from arcgis.gis import GIS

gis = GIS()

使用凭据连接到 ArcGIS 用户账户

如果您有用户账户,可以通过两种方式之一登录:使用内置登录或使用 API 密钥。登录的优势在于您可以将创建的任何地图保存到您的账户中。

无论哪种方式,您首先需要使用终端导入 GIS:

from arcgis.gis import GIS

要使用内置登录,运行:

   gis = GIS(username="someuser", password="secret1234")

要使用 API 密钥登录,请使用以下代码(我已经缩短了令牌):

gis = GIS(api_key="Request YOUR KEY AND INSERT IT HERE",
              referer="https")

当您连接凭据并运行单元格时,输出将是您组织的 ArcGIS Online 用户名。我的账户用户名是“datalchemy”;如果您创建一个账户,您需要用您自己的账户凭据替换这些内容:

gis = GIS(username="datalchemy", password="xxx")
gis

如果您拥有账户和登录 URL(列在您组织的设置中),可以通过密码保护方式登录,再次用您自己的凭据替换:

import arcgis
import getpass
username = input ("Username:")
password = getpass.getpass ("Password:")

gis = arcgis.gis.GIS("https://datalchemy.maps.arcgis.com", username, password)

运行单元格后,将弹出一个框,提示您输入用户名和密码。

现在,您可以开始探索一些图像层次。

探索图像层:城市热岛地图

在我们开始之前,先说一下命名约定。图 6-1 是一幅芝加哥地图,我称之为map1

这个指定是任意的:您可以根据自己的喜好为地图命名,但重要的是在涉及多个地图的代码编写时避免混淆。通常会指定一个带有整数后缀的地图变量,或者简单地使用m。选择一种方法并保持一致。它应与map()函数有所区别。您可以通过在 Jupyter Notebook 中运行以下代码生成map1

map1 =gis.map("Chicago,Illinois")
map1

这将使您的地图居中显示在伊利诺伊州的芝加哥。您可以通过缩放到特定位置或绘制多边形来设置地图覆盖的区域,称为其范围

芝加哥地图

图 6-1. 芝加哥地图
注意

要访问 ArcGIS Online 的帮助功能,请输入:

gis?

如果您键入一个点并按 Tab 键,将显示一个下拉菜单,其中包含您可以点击以获取更多信息的附加属性:

gis = GIS.

现在您可以尝试将一些属性层叠到您的地图上。图像层显示来自图像服务的数据。这些资源允许您应用规则来显示图像。您可以通过将项目类型标识为Imagery Layer来搜索它们。

您可以使用gis.content.search()中的搜索词来描述您想在地图中包含的要素。创建一个分配给gis.content.search()的变量可以帮助您查找可用的要素图层或其他item_type数据。使用以下代码片段,您可以探索可用的 Landsat 9 卫星视图,以用作影像层。不同的影像层具有不同的属性。在这里,我已将搜索限制为图 6-2 中显示的两个选项,这些选项是带有指定术语的公开可用要素图层:

from IPython.display import display

items = gis.content.search("Landsat 9 Views", item_type="Imagery Layer", 
max_items=2)
for item in items:
    display(item)

使用 gis.content.search()搜索 Landsat 9 视图

图 6-2. 使用gis.content.search()搜索 Landsat 9 视图

代码块中的item_type包括 Web 场景、要素图层、地理空间数据、底图和具有地形的交互式 3D 环境。您可以在文档中了解更多关于项目和项目类型的信息。

Web 场景允许您可视化和分析包含所有配置设置的地理空间内容,例如底图、样式和范围。要探索这些内容,您可以用web scene替换item_type

我们将使用 Web 场景来探索芝加哥的热岛。根据美国环保局(EPA),热岛是:

城市化区域的温度比外围区域高。建筑物、道路和其他基础设施等结构比森林和水体等自然景观更多地吸收并重新发射太阳的热量。在这些结构高度集中且绿化有限的城市区域中,相对于外围区域,成为更高温度的“岛屿”。

城市热岛严重性测量城市基础设施如何吸收并重新发射这些热量。

要定位图像服务器的 URL 并开始在地图上探索热岛,请单击搜索结果中的标题(“城市热岛”),或通过在下一个单元格中调用img_svc_url来列出:

img_svc_url = 'https://server6.tplgis.org/arcgis6/rest/services/
heat_severity_2019/ImageServer'

这输出:

[<Item title:"Urban Heat Island Severity for U.S. cities - 2019" type:
Imagery Layer owner:TPL_GIS_Support>,
 <Item title:"Multispectral Landsat" type:Imagery Layer owner:esri>]

您也可以使用此代码尝试栅格函数。更多关于这些内容的信息在下一节中,但现在,请尝试这个:

from arcgis.raster import ImageryLayer
landsat_urbanheat = ImageryLayer(img_svc_url)
landsat_urbanheat.properties.name

这输出:

‘Heat_severity_2019’

您已经访问了城市热岛严重性影像层。现在将其添加到您的芝加哥地图中:

map = gis.map('Chicago', zoomlevel=13)
map

map.add_layer(landsat_urbanheat)

landsat_urbanheat图层现在已添加到您的地图中,这应与图 6-3 类似。

添加到地图范围的地图图层

图 6-3. 添加到地图范围的地图图层

现在,您将从多光谱 Landsat 图层的 URL 中添加影像层,并将其分配给变量landsat_ms

img_svc_url ='*https://landsat2.arcgis.com/arcgis/rest/services/Landsat/MS/
	ImageServe*'
from arcgis.raster import ImageryLayer
landsat_ms = ImageryLayer(img_svc_url)
landsat_ms.properties.name

最后一行代码仅确认了图层的名称。

这将生成以下输出:

'Landsat/MS'

图像层的描述也可通过一行代码获取:

landsat_ms.properties['description']

输出是详细的:

Multispectral Landsat image service covering the landmass of the World. This 
service includes scenes from Landsat 8 and Global Land Survey (GLS) data from 
epochs 1990, 2000, 2005 and 2010 at 30 meter resolution as well as GLS 1975 
at 60 meter resolution. GLS datasets are created by the United States 
Geological Survey (USGS) and the National Aeronautics and Space Administration 
(NASA) using Landsat images. This service can be used for mapping and change 
detection of urban growth, change of natural resources and comparing Landsat 
8 imagery with GLS data.  Using on-the-fly processing, the raw DN values are 
transformed to scaled (0 - 10000) apparent reflectance values and then 
different service based renderings for band combinations and indices are applied. 
The band names are in line with Landsat 8 bands; GLS data band names are mapped 
along the same lines.

要探索更多的栅格函数对象,请访问常见数据类型文档。在学习栅格函数和图像图层的过程中,我还建议您随时查阅ArcGIS Python API 参考指南

栅格函数

栅格函数处理来自图像的像素以进行一项或多项光栅处理,无需下载或创建中间文件进行分析。访问栅格函数受到限制,需要额外的凭据,但您仍然有机会探索 Landsat 多光谱图像。

这次,我们将探索洛杉矶地图,逐一了解其中的一些亮点:

from arcgis.gis import GIS
from arcgis.geocoding import geocode
from arcgis.raster.functions import *
from arcgis import geometry
import ipywidgets as widgets

import pandas as pd

您将在 gis.content.search() 中再次运行搜索。这次,您要查找组织外的图像(如果您已创建组织),并设置 outside_org=True。这是在访问公开可用数据集时的设置:

landsat_item = gis.content.search("Landsat Multispectral tags:'Landsat on AWS',
'landsat 9', 'Multispectral', 'Multitemporal', 'imagery', 'temporal', 'MS'", 
'Imagery Layer', outside_org=**True**)[0]
landsat = landsat_item.layers[0]
df = **None**

您的搜索结果应包括多光谱 Landsat,如图 6-4 所示。要查看 landsat_item,请在单元格中调用它:

landsat_item

显示 Esri 的多光谱 Landsat 成像图层的搜索结果

图 6-4. 显示 Esri 的多光谱 Landsat 成像图层的搜索结果

欲了解更多信息,请单击链接或运行以下代码:

from IPython.display import HTML
HTML(landsat_item.description)

本图展示了多光谱光带,这些光带由比人眼更敏感的仪器检测到。正如您在第一章 中学到的,多光谱光带通过电磁波谱中特定波长范围内的图像数据来突出显示不同的地表覆盖特征。您可以检查每个波段的名称、最小和最大波长以及其他属性:

pd.DataFrame(landsat.key_properties()['BandProperties'])

这些波段指示了与陆地覆盖、植被及其他类型地表有关的详细信息。表 6-1 是输出的摘录,以表格形式呈现以便阅读。

表 6-1. 多光谱和热红外传感器波段

波段 波长(微米) 分辨率(米)
第一波段—近岸气溶胶 0.43–0.45 30
第二波段—蓝色 0.45–0.51 30
第三波段—绿色 0.53–0.59 30
第四波段—红色 0.64–0.67 30
第五波段—近红外(NIR) 0.85–0.88 30
第六波段—短波红外(SWIR)1 1.57–1.65 30
第七波段—短波红外(SWIR)2 2.11–2.29 30
第八波段—全色 0.50–0.68 15
第九波段—卷云 1.36–1.38 30
第十波段—热红外(TIRS)1 10.6–11.19 100
第十一波段—热红外(TIRS)2 11.50–12.51 100

使用您的 Jupyter Notebook 创建地图:

landsat = landsat_item.layers[0]
landsat

m = gis.map('los angeles')
m

现在添加要素图层:

m.add_layer(landsat)

您现在可以查看您的地图(图 6-5)。您还可以开始使用栅格函数。

使用多光谱 Landsat 数据创建的洛杉矶地图

图 6-5. 使用多光谱 Landsat 数据创建的洛杉矶地图

在这里,您将使用一个栅格函数来突出显示地图中的彩色可视化效果。例如,color infrared显示鲜艳的红色条带来指示健康的植被,而natural color显示我们通常看到的地形:绿色的植被,蓝色的水域,棕色的土壤。

许多选项可通过动态范围调整(DRA)来进行调整,以增强细节并在匹配您的计算机或其他设备的范围时提高可见性。您可以使用以下代码生成可用栅格函数的列表,如随后显示的输出所示:

for rasterfunc in landsat.properties.rasterFunctionInfos:
    print(rasterfunc.name)

这将输出一个列表:

Agriculture with DRA
Bathymetric with DRA
Color Infrared with DRA
Geology with DRA
Natural Color with DRA
Short-wave Infrared with DRA
Agriculture
Bathymetric
Color Infrared
Geology
Natural Color
Short-wave Infrared
NDVI Colorized
Normalized Difference Moisture Index Colorized
NDVI Raw
NBR Raw
None

熟悉其中几个函数应该为您在独立探索中深入了解其他选项铺平了道路。

让我们尝试查看color_infrared。您将使用apply函数:

From arcgis.raster.functions import apply
Color_infrared = apply (landsat, 'Color Infrared with DRA')

要可视化地图,请调用map函数:

m = gis.map('los angeles')
m.add_layer(color_infrared)
m

健康的植被现在以鲜艳的红色显示(图 6-6)。

用彩色红外线查看的洛杉矶

图 6-6. 用color_infrared查看的洛杉矶

用于查看植被的最佳栅格函数通常是归一化差异植被指数(NDVI)。图 6-7 展示了NDVI_colorized函数,显示绿色的植被:

ndvi_colorized = apply(landsat, 'NDVI Colorized')
ndvi_colorized

Los Angeles NDVI

图 6-7. 用于 NDVI 的洛杉矶

您已经学会了如何搜索图像和要素图层,以及如何对特定位置应用栅格函数来突出显示特定功能。接下来,我们将探讨属性,并查看arcgis.geometry模块。

探索图像属性

在这一部分中,您将使用 ArcGIS 栅格模块get_samples来探索您的洛杉矶地图的几何形状。该模块允许您查看所选几何形状的样本点位置、空间分辨率和像素值。它是ArcGIS REST API的一个示例,该 API 提供有关 Web 应用程序架构的信息。与主要关注访问点的一般 ArcGIS API 文档不同,ArcGIS REST API 文档提供了地理空间功能的全部范围。它还描述了get_samples操作可以调用的参数:

get_samples(geometry, geometry_type=None, sample_distance=None, sample_count=None, 
mosaic_rule=None, pixel_size=None, return_first_value_only=None, 
interpolation=None, out_fields=None, slice_id=None)

在这里,您指定地图的几何形状和样本计数,并根据您定义的范围返回有关其图像属性的信息―在本例中是洛杉矶的范围:

import arcgis
g = arcgis.geometry.Geometry(area['extent'])

为什么要使用几何形状?当您创建 3D 模型时,像高度和太阳方位角这样的数据点对于计算阴影坡度非常有用。在使用hillshade函数时,基本上是将 2D 表面渲染为逼真的 3D 地形。

接下来,指定几何形状和样本计数:

samples = landsat.get_samples(g, sample_count=50,
                                 out_fields='AcquisitionDate,OBJECTID,GroupName,
                                 Category,SunAzimuth,SunElevation,CloudCover')

您可以指定要查看的样本列表中的项目,或查看所有项目:

samples[10]

输出包括位置、对象 ID、云层覆盖计算和像素值等信息:

{'location': {'x': -13150297.20625444,
  'y': 4059732.8562727477,
  'spatialReference': {'wkid': 102100, 'latestWkid': 3857}},
 'locationId': 10,
 'value': '1158 991 843 769 2850 1675 1030 44 21824 22410 22691',
 'rasterId': 3508198,
 'resolution': 30,
 'attributes': {'AcquisitionDate': 1647023302000,
  'OBJECTID': 3508198,
  'GroupName': 'LC09_L1TP_041036_20220311_20220311_02_T1_MTL',
  'Category': 1,
  'SunAzimuth': 144.81874084,
  'SunElevation': 45.83943939,
  'CloudCover': 0.0024},
 'values': [1158.0,
  991.0,
  843.0,
  769.0,
  2850.0,
  1675.0,
  1030.0,
  44.0,
  21824.0,
  22410.0,
  22691.0]}

在 Python 中,我们使用datetime对象进行时间序列数据或在不同时间点收集的数据。再次,在 Python 中,您正在对datetime类进行采样,并可以通过运行以下代码渲染获取日期。(我将在本章后面提供更多详细信息,但这是我们在示例数据上使用该功能的方式。)

由于 Python 基于零索引访问数据,使用[0]请求列表中的第一个索引或示例中的第一个值:

import datetime
value = samples[0]['attributes']['AcquisitionDate']
datetime.datetime.fromtimestamp(value /1000).strftime("Acquisition Date: %d %b, 
	%Y")

这将输出:

'Acquisition Date: 11 Mar, 2022'

通过在 Los Angeles 的特定位置采样值,您可以估算地图上特定点的光谱配置。

m = gis.map('los angeles')
m

m.add_layer(landsat)

如果选择特定像素,光谱配置将绘制在该位置反射的所有波段,如图 6-8 所示。

下一个代码允许您在 Jupyter Notebook 中的画布上选择一个点以识别光谱配置。请注意,如果您从 ArcGIS Python API 指南运行此示例并请求 Landsat 9,则会生成错误,因为添加了额外的波段。

点击生成光谱配置在#a_spectral_profile_visualized_with_boke

图 6-8. 点击生成光谱配置在图 6-9

get_samples()方法将收集样本数据中包含的像素值。像素值是数字。为了正确计算它们,您需要先将其转换为浮点数,然后转换为整数。数字记录像素的电磁强度。

您已将Bokeh 包安装到 Conda 环境中以允许交互式绘图和制图。Landset 规格基于 Landsat 8 数据,并仅包括了八个波段。这导致了 Bokeh 的错误。为解决此问题,请使用包含新波段的运行:

from bokeh.models import Range1d
from bokeh.plotting import figure, show, output_notebook
from IPython.display import clear_output
output_notebook()

def get_samples(mw, g):
    clear_output()
    m.draw(g)
    samples = landsat.get_samples(g, pixel_size=30)
    values = samples[0]['value']
    vals = [float(int(s)/100000) for s in values.split(' ')]

    x = ['1','2', '3', '4', '5', '6', '7', '8','9','10','11']
    y = vals
    p = figure(title="Spectral Profile", x_axis_label='Spectral Bands', 
	y_axis_label='Data Values', width=600, height=300)
    p.line(x, y, legend_label="Selected Point", line_color="red", line_width=2)
    p.circle(x, y, line_color="red", fill_color="white", size=8)
    p.y_range=Range1d(0, 1.0)

    show(p)

print('Click anywhere on the map to plot the spectral profile for that location.')
m.on_click(get_samples)

您应该收到 Bokeh 2.4.2(或您的版本号)已成功加载的消息。现在返回地图并选择您的点。点击生成图 6-8 中显示的地图(您的地图将根据您选择的点有不同的值)。

您生成的地图(图 6-8)现在是交互式的。在新地图上任意点击以绘制该位置的光谱配置。现在您可以选择不同的点并查看生成的值。图 6-9 展示了在我选择的位置上用于探测卷云的第 9 波段的光谱配置。

使用 Bokeh 可视化的光谱配置

图 6-9. 使用 Bokeh 可视化的光谱配置

根据点击 Landsat 9 图像图层时选择的独特位置,您的光谱配置文件将不同。请参阅表 6-1 以识别包含的波段。这些代表了 ArcGIS Python API 中get_samples方法的几个应用。

改进图像

光栅函数允许您执行诸如提取特定波段以检查土地利用、植被或火灾;连续数据(如温度);扫描图像;以及卫星图像等操作。拉伸函数允许您调整地图上的亮度和对比度。以下代码从可见光谱中选择 3、2 和 1 波段(红、绿和蓝),并导入stretch raster.function

from arcgis.raster.functions import stretch, extract_band
naturalcolor = stretch(extract_band(landsat, [3,2,1]), 
                    stretch_type='percentclip', min_percent=0.1, max_percent=0.1, 
					gamma=[1, 1, 1], dra=True)

naturalcolor

此代码设置的百分比剪切最小值(percentclip)将排除拉伸应用于栅格的最低 10%值。如果大多数像素在特定范围内,这是非常有用的。设置stretch_type会修剪掉异常值,重新分布值的直方图,从而生成图 6-10 中的图像。

自然色波段展示了肉眼在没有增强的情况下所看到的图像

图 6-10. 自然色波段展示了肉眼在没有增强的情况下所看到的图像

图 6-10 中的图像是肉眼在没有任何增强的情况下所看到的。根据您要查找的特征,可能有其他波段可以探索。我们将在接下来的部分查看其他波段。

比较同一位置的多个时间点

ArcGIS API 允许您使用称为时间滑块的地图小部件比较同一位置在不同时间点的图像,该小部件使您可以使用可配置的属性(如开始和结束时间及其间隔)对地图进行动画化。对于此示例,我选择了一个城市和一个卫星底图。

地图缩放小部件接受 0 到 23 之间的值。级别 9 提供了全球概览;级别 10 介于大型都市区域和城市之间;级别 20 是个体建筑物的级别。我从未缩放到 23,但这样做会呈现出最详细的视图。然而,根据可视化参数的规模,缩放的限制程度有所不同(转换率可在文档中查看)。您可以更改缩放级别并处理数据,但分辨率将保持不变。例如,所有 Landsat 图像的分辨率为 15 米。这意味着卫星图像捕捉到的地面细节为 15 米或更大。(作为比较,洛杉矶的好莱坞标志和标准半挂拖车均长约 15 米。)

尝试将缩放级别调整到 10:

map = gis.map("los angeles")
map.basemap = "satellite"
map.zoom = 10
map

item title的名称将作为输出打印以供参考:

landsat_item = gis.content.search("Landsat Multispectral tags:'Landsat on AWS',
'landsat 9', 'Multispectral', 'Multitemporal', 'imagery', 'temporal', 'MS'", 
'Imagery Layer', outside_org=**True**)[0]
print(landsat_item)
<Item title:"Multispectral Landsat" type:Imagery Layer owner:esri>

您已搜索到一个 Landsat 项,并且通过其标题在输出中确认了该项。不同的子层将提供不同的详细信息。

将图层添加到地图中:

map.add_layer(landsat_item)

请求确认map.time_slider是否可用:

map.time_slider

它应该返回输出True

您可以将日期调整为特定间隔。以下代码测量所选开始和结束时间之间的 10 天间隔:

from datetime import datetime
map.time_slider = True
map.set_time_extent(start_time=datetime(2021, 12, 12), end_time=datetime(2022, 4, 
12), interval=10, unit='days')

此代码将在 Figure 6-11 中输出地图,其中包括时间滑块小部件。

时间滑块小部件

图 6-11. 时间滑块小部件

在代码单元中选择map.draw(polygon)选项,然后使用鼠标绘制一个多边形。如果您有感兴趣的特定位置,请输入其坐标:

from arcgis.geometry import Point

pt = Point({"x" : 34.092809, "y" : -118.328661, 
            "spatialReference" : {"wkid" : 3309}})

空间参考WKID(Well-Known ID)定义了您选择的位置的公差和分辨率(参见空间参考列表)。

map.draw选项包括选择点(pt)、polylinepolygon

map.draw(pt)
map.draw('polyline')
map.draw('polygon')

当您选择多边形并运行代码单元时,只需返回到地图,您就可以跟踪一个多边形。时间滑块小部件允许在感兴趣的区域跨时间序列进行比较。在处理图层时,通常需要筛选出出现在画布上的内容。

过滤图层

您可能希望过滤您的图层:例如,也许您只想看到具有少于 10%云覆盖率的特定集合中的 Landsat 图层。为此,熟悉 Landsat 的数据字典有助于获取关于数据库或系统中数据元素、属性和名称的完整信息。

在以下代码片段中,WRS_Row提供了有关拍摄图像的卫星轨道路径的信息:

selected = landsat.filter_by(where="(Category = 1) AND (CloudCover <=0.10) AND 
	(WRS_Row = 36)", 
                   geometry=arcgis.geometry.filters.intersects(area['extent']))

36表示北半球。(您可以在 Landsat 数据字典、集合背景或其他 Landsat 信息中找到这些详细信息。)

您可以访问的信息显示在您之前生成的 HTML 项目描述中。现在您可以在数据帧(df)中查看刚刚创建的表格:

fs = selected.query(out_fields="AcquisitionDate, GroupName, Best, CloudCover, 
	WRS_Row, Month, Name", 
              return_geometry=True,
              return_distinct_values=False,
              order_by_fields="AcquisitionDate")

因为您正在比较日期,您将希望看到最旧的获取日期以及最近的获取日期。要清楚地查看输出,请在代码单元中运行它。重要的是看列标题以及在哪里找到标识信息(见 Figure 6-12)。您可以通过调用df.head()来查看前五行:

df = fs.sdf
df.head()

采集日期输出

图 6-12. 采集日期输出

现在运行df.tail()并选择最近的获取日期。您可以在 Jupyter Notebook 中运行代码:

df = fs.sdf
df.tail()

以下命令给出了数据的形状:

df.shape

输出告诉我们,数据有 9 列和 193 行:(193,9)

如果您只想要获取日期,可以运行此数据,或从数据帧中的任何其他列中获取:

df['Time'] = pd.to_datetime(df['AcquisitionDate'], unit='ms')
df['Time'].head(10)

这个输出:

0   1977-05-30
1   1979-06-08
2   1989-06-28
3   1990-05-07
4   2000-04-24
5   2000-05-01
6   2005-05-15
7   2005-05-24
8   2009-04-16
9   2009-05-11
Name: Time, dtype: datetime64[ns]

波段具有特定波长,用于统计计算。在图像图层中要避免像素重叠,因为这可能会扭曲计算结果。您可以使用默认方法,其中像素值是从最后一个数据集计算的(见 图 6-13):

m3 = gis.map('los angeles', 7)
display(m3)
m3.add_layer(selected.last())

使用  方法重叠像素

图 6-13. 使用 last() 方法重叠像素

或者,您可以请求 first() 方法,并从第一个光栅数据集计算像素值(输出显示在 图 6-14 中):

m3 = gis.map('los angeles', 7)
display(m3)
m3.add_layer(selected.first())

使用  方法重叠像素

图 6-14. 使用 first() 方法重叠像素

通过 OBJECTID 查询数据,辅以 df(head)df(tail) 数据,您可以比较不同时期捕获的图像:

old = landsat.filter_by('OBJECTID=3106827')

new = landsat.filter_by('OBJECTID=3558253')

from arcgis.raster.functions import *

stretch 函数 通过扩展像素值来提高可见性。有不同类型的拉伸,但在这里,您只需通过计算标准偏差 (stddev) 删除任何极端值:

diff = stretch(composite_band([ndvi(old, '5 4'),
                               ndvi(new, '5 4'),
                               ndvi(old, '5 4')]), 
                               stretch_type='stddev',  num_stddev=2.5, min=0, 
                               max=255, dra=True, astype='u8')
diff

这将输出 图 6-15。绿色条带表示植被密度增加,洋红色显示所选 OBJECTID 时间范围内的减少。

ArcGIS 函数显示随时间图像差异

图 6-15. ArcGIS 函数显示随时间图像差异

或许有您想要捕获的特定阈值。尝试仅测量阈值变化高于 10%的区域。使用以下代码:

threshold_val = 0.1
masked = colormap(remap(ndvi_diff, 
                        input_ranges=[threshold_val, 1], 
                        output_values=[1], 
                        no_data_ranges=[-1, threshold_val], astype='u8'), 
                  colormap=[[1, 124, 252, 0]], astype='u8')

Image(masked.export_image(bbox=area['extent'], size=[1200,450], f='image'))

输出(图 6-16)将这些区域呈现为绿色。

植被指数(NDVI)变化的掩码阈值

图 6-16. 植被指数(NDVI)变化的掩码阈值

让我们再次渲染地图:

m = gis.map('los angeles')
m

最终图像显示在 图 6-17 中。您现在可以通过向地图添加图层来查看掩码阈值:

m.add_layer(diff)
m.add_layer(masked)

在请求的阈值处显示的组合图像,展示了掩码阈值

图 6-17. 在请求的阈值处显示的组合图像,展示了掩码阈值

摘要

现在,您已经根据 ArcGIS API for Python 定制并探索了一些公开可用资源,应该对栅格函数和图像图层有所了解。继续学习时,请尝试独立使用这些函数和图层,看看您可以访问哪些其他查询。

第七章:GeoPandas 和空间统计

地图很美丽。它们讲述的故事可能如此迷人,以至于很容易无意中忽略其中的地理空间统计数据。但地理空间地图不仅仅是静态图像。其中嵌入了信息,例如与 GIS 图层中特定要素相关联的属性或在光栅图像中观察到的像素密度。

Python 有多种用于处理地理空间数据的包。如果您熟悉 Python,您可能已经了解 pandas,这是一个专为 Python 构建的数据分析工具。Pandas 允许我们将各种数据类型读入数据框中:一组包含行(表示记录)和列(表示属性)的表格。GeoPandas 是 pandas 的扩展,允许您使用其称为 GeoDataFrame 的方式操作几何和地理空间数据框架:每行是空间要素,如点、线或多边形。

本章将向您展示如何使用 GeoPandas 和 GeoDataFrame 分析数据并创建地图,以及一些其他重要的包,如 matplotlib 用于可视化数据和人口普查数据 API。您还将学习如何访问地理空间文件,并通过创建和比较人口地图深入了解人口统计数据。

安装 GeoPandas

要安装 GeoPandas,您将在终端中使用 condaconda-forge。与前几章一样,我还将向您展示如何为本章中所有需要处理的数据创建环境。

安装 GeoPandas 可能有点棘手。可以把它想象成一个情绪化的青少年。它通常想要先行(导入)并喜欢最新的趋势(注意依赖项的版本)。它还喜欢一个响亮的昵称:通常情况下,GeoPandas 被称为 gpd,但有时代码会写出 geopandas。请注意并调整您的变量。

要设置您的环境,请从这里开始:

conda create --name geop_env python = 3.X 

您可以在此处添加您的 Python 版本。

MacBook-Pro-8:~ USERNAME$ conda activate geop_env

现在您可以开始将文件安装到 geop_env 中:

conda install -c conda-forge geopandas

现在您可以将任何想要访问的包添加到 geop_env 中。如果您希望获取一组用于地理空间分析的包,我建议下载地理空间包,但现在如果您愿意,也可以逐个添加包:

conda install -c conda-forge geospatial

GeoPandas 简化了在 Python 中处理地理空间数据的工作。让我们探索扩展操作,使您能够在几何数据上执行空间操作。

处理 GeoJSON 文件

GeoPandas 让您能够处理 GeoJSON 文件。您可能已经听说过 JSON,这是一种常见的数据交换格式。GeoJSON 是一种基于 JSON 的数据交换格式,专门设计用于表示地理数据结构及其非空间属性。使用 GeoJSON 文件工作使得识别地图中的坐标和位置变得相当简单。

图 7-1 显示了来自geojson.io的地图。Geojson.io 是一个开源工具,简化了创建、查看和分享地图。你可以在互联网上执行各种功能,而无需将任何数据下载到你的计算机。

缩放到巴黎第一区并选择边界以创建 GeoJSON 文件

图 7-1. 缩放到巴黎第一区并选择边界以创建 GeoJSON 文件

点击geojson.io链接并放大到一个国家。我选择了法国,因为,嗯,它是法国。查找特定位置的坐标的简单方法是选择你感兴趣的区域并拖动多边形周围的区域。选择多边形的工具可见于地图仪表板的右边缘。尝试在地图上找到法国巴黎。找到杜乐丽花园并在其周围绘制一个多边形。保存多边形后,你可以通过点击边界查看格式化选项和样式属性。

顶部菜单中的 Meta 标签(显示在图 7-1 中)允许你生成一个边界框(bbox),其中包含你想要映射的坐标和点集。它定义了感兴趣对象的空间位置和其坐标。你还可以加载以 well-known text(WKT)标记语言表示的字符串,用于矢量几何对象。

左下角的按钮,在图 7-2 中可见,允许你在 Mapbox(矢量瓦片)、卫星(光栅数据)和 OSM(OpenStreetMap,在第五章中介绍过)之间切换。

地图查看器选项

图 7-2. 地图查看器选项

如果你想要保存这个文件,你可以以几种不同的格式保存,包括.csv.txt和 shapefile。然后你可以将其导入到 QGIS、ArcGIS 或你选择的平台进行进一步的探索和分析。电子表格数据存储为.csv.txt,通常包含位置信息,如经度和纬度的坐标数据、地址或邮政编码。Shapefile 是 Esri 的矢量文件,捕捉位置、形状和有关位置的相关文件。

创建一个 GeoDataFrame

GeoDataFrame 类似于带有额外几何数据列的 pandas 数据帧。许多类型的矢量数据描述了具有固定位置的离散数据,而连续数据通常由栅格数据表示(尽管两者都可以是任意类型)。

GeoPandas 提供了一些数据集示例供探索。你将访问缩写为nybb的 NYC Boroughs 示例:

geopandas.datasets.available = ['naturalearth_cities', 'naturalearth_lowres',
'nybb']

这些文件代表城市的位置、不同国家的轮廓以及纽约市行政区的边界。要访问它们,你需要将数据集名称作为参数包含进去:

geopandas.datasets.get_path("nybb")

您可以从终端打开 Jupyter Notebook:

(geop_env) MacBook-Pro-8:~ USERNAME$ jupyter notebook

现在,笔记本已打开,您可以创建一个数据帧。首先,将您需要的软件包导入到您的环境中:

%matplotlib inline

import requests
import pandas as pd
import geopandas as gpd

from scipy.spatial.distance import cdist

接下来,您需要检索数据集并调用活动几何列(在本例中为“geometry”)来创建一个名为world.geometry.name的数据帧。当您调用head()时,将返回数据集中的前五行和列:

boros_world = gpd.read_file(gpd.datasets.get_path('nybb'))
print(f"{type(boros_world)}, {boros_world.geometry.name}")
print(boros_world.head())
print(boros_world.geometry.geom_type.value_counts())

您应该得到以下输出:

<class 'geopandas.geodataframe.GeoDataFrame'>, geometry
 BoroCode  BoroName  Shape_Leng Shape_Area \
0   5   Staten Island 330470.010332 1.623820e+09 
1   4   Queens 896344.047763 3.045213e+09 
2   3   Brooklyn 741080.523166 1.937479e+09 
3   1   Manhattan 359299.096471 6.364715e+08 
4   2   Bronx 464392.991824 1.186925e+09 

           geometry 
0 MULTIPOLYGON (((970217.022 145643.332, 970227.... 
1 MULTIPOLYGON (((1029606.077 156073.814, 102957... 
2 MULTIPOLYGON (((1021176.479 151374.797, 102100... 
3 MULTIPOLYGON (((981219.056 188655.316, 980940.... 
4 MULTIPOLYGON (((1012821.806 229228.265, 101278... 
MultiPolygon 5
dtype: int64

注意第一列中的索引分配。通过引用其索引位置,您将能够隔离单个区域。BoroCodeBoroName是您可以指定的附加标识符,但是您可能会注意到在随后的代码片段中通过索引调用的简易性。

您现在正在使用一个 GeoDataFrame。您可以在 Jupyter Notebook 中运行以下代码生成图 7-3 的绘图:

boros = gpd.read_file(gpd.datasets.get_path('nybb'))
ax = boros.plot(figsize=(10, 10), alpha=0.5, edgecolor='k')

纽约市行政区域的地理数据框中的位置

图 7-3。纽约市行政区域的位置

使用figsize参数可以更改图像的大小。在示例中,alpha 值小于 1 可调整透明度。边缘颜色也可以自定义。你可以在matplotlib 文档中找到详细信息。

在大多数情况下,我更喜欢没有可见轴的地图。要删除框架,只需添加:

ax.set_axis_off()

您将通过交互地图进一步探索数据,因此不需要区域坐标。这将生成图 7-4 中的图像。

没有轴框架的纽约市行政区域的位置

图 7-4。没有轴框架的纽约市行政区域的位置

通过调用行号可以请求单个区域。斯泰顿岛在您几页前生成的 GeoDataFrame 中的第 0 行。您可以使用可调用函数DataFrame.loc根据其索引号访问位置。我认为loc是“列标签”的缩写,因为它需要输入标签,如“索引”或“几何”。缩写iloc用于整数位置索引/列的整数。替换您创建的数据帧名称:

boros.loc[0,'geometry']

这将输出显示在图 7-5 中的斯泰顿岛的图像。

使用.loc 函数调用单个行政区域

图 7-5。使用.loc函数调用单个区域

您可以在图 7-5 中看到斯泰顿岛区域的边界,但是现在您知道如何访问 GeoDataFrame 后,获取更多信息会更有帮助。您还可以找到区域的面积:

gdf = gdf.set_index("BoroName")
gdf["area"] = gdf.area
gdf["area"]

这将输出:

BoroName
Staten Island   1.623822e+09
Queens          3.045214e+09
Brooklyn        1.937478e+09
Manhattan       6.364712e+08
Bronx           1.186926e+09
Name: area, dtype: float64

要查看一个行政区域的边界,请运行:

gdf['boundary'] = gdf.boundary
gdf['boundary']

这将输出:

BoroName
Staten Island   MULTILINESTRING ((970217.022 145643.332, 97022...
Queens          MULTILINESTRING ((1029606.077 156073.814, 1029...
Brooklyn        MULTILINESTRING ((1021176.479 151374.797, 1021...
Manhattan       MULTILINESTRING ((981219.056 188655.316, 98094...
Bronx           MULTILINESTRING ((1012821.806 229228.265, 1012...
Name: boundary, dtype: geometry

在交互式地图 (Figure 7-6) 中,您可以通过悬停在市区上来检索信息。参考距离是 0,因为史泰登岛的索引号是 0. 点击任何其他市区将告诉您与史泰登岛的距离。gdf.explore 函数生成一个交互式地图:

gdf.explore("area", legend=True)

纽约市行政区交互地图

图 7-6. 纽约市行政区交互地图

将像几何形状这样的地理空间能力添加到 pandas 数据框中仅仅是个开始。人口普查数据是开放数据的宝库,了解如何使用 GeoPandas 将帮助您扩展访问和查询这一重要数据集的能力。让我们再看看更多人口普查数据。

使用美国人口普查数据:洛杉矶人口密度地图

在这个练习中,您将计算加利福尼亚州洛杉矶县各个小区的人口密度。

警告

当使用 Python 人口普查包时要小心。如果开发者失去了维护它们的兴趣或者被其他任务压倒,您的地图和图形将不再工作。在写这一章节时,我就是这样学到的。好消息是,它提醒我始终可靠的备用基础知识。

如果您需要一个专业服务或研究的人口普查数据问题的高质量地图,您可以在像 QGIS 这样的开源平台上或使用像 ArcGIS 这样的专有软件构建一个。这个例子将带您了解基础知识。一旦您理解了人口普查 API 的结构并能够在一个不花哨的 Python 包之外编写代码,您就可以轻松访问这个最强大的公开可用数据集之一。

第一步是在 census.gov 上找到数据。根据您的兴趣领域,2020 年人口普查的数据已经可以用于分析。我依赖于 Census Reporter 来识别和下载数据集。大多数仓库都有 ACS 数据(通常称为“长表人口普查”),每年从全国一部分收集,并以年度或五年时段的形式捆绑。API 默认获取最近五年的 ACS 时段。这是一个易于导航的第一站,全面了解捕获数据类型的方法。

Census Reporter 为许多大都市区提供即用的社区数据,这些示例中您将看到这些数据。

小贴士

对于更高级的用户,IPUMS(前身为集成公共使用微数据系列,现以其首字母缩写命名)提供了来自人口普查和调查数据的全球协调数据集,并附有支持文件。您可以使用它将 ACS 和人口普查数据与其他来源的合并数据集集成。

通过人口普查 API 和 FTP 访问小区和人口数据

通过 FTP 站点或直接访问人口普查 API 是能够在人口普查 GUI 外工作的基础。这将帮助您解决更大的问题,并且数据已准备好在笔记本或 QGIS 中进行分析。

FTP 站点提供了快速简便访问大文件的途径,这些文件可能难以导航。如果您在 MacOS 环境下工作,可能需要调整一些设置,但我更喜欢通过 FTP 访问人口普查数据的可靠性和无杂乱。

选择“系统偏好设置 >> 共享”并选中“远程登录”。选择允许访问的用户。

接下来,导航到您的 Finder 窗口并选择 Go。在下拉菜单中,您将看到一个连接到服务器的选项(图 7-7)。

连接到美国人口普查局 FTP 站点

图 7-7. 连接到美国人口普查局 FTP 站点

FTP 站点 可通过链接访问。您将能够浏览可用的文件。您还可以通过服务器站点获得访问权限。总结文件是大多数感兴趣的文件所在之处。

您将要探索美国人口普查的十年人口数据。这里包含的地理数据来自 2019 年 ACS 一年调查。人口普查表来自 2010 年人口普查。^(1) Google Colab 在这项练习中表现良好,因为文件结构是直接可用的,并且会话结束后,文件会被移除。

您需要下载以下在 Colab 中尚未包含的包:

!pip install geopandas #install separately to avoid errors

import requests # accessing from the CensusAPI **import** sys # checking python version **import** pandas **as** pd # working with dataframes **import** geopandas **as** gpd # creating maps **import** folium **as** fm # creating HTML map complete with OSM basemap

当处理人口普查数据时,我学到了一条重要的经验教训:始终了解您的包版本,以防存在依赖关系(或冲突!)。这是一个有用的实践方法,可以添加到您的笔记本中。使用以下代码显示版本:

# Display package versions
print("Python Version", sys.version)
print("requests version:", requests.__version__)
print("pandas version:", pd.__version__)
print("geopandas version:", gpd.__version__)
print("folium version:", fm.__version__)

现在您可以从 FTP 服务器请求人口普查区信息。您正在寻找人口普查区的形状文件。这些是 .zip 文件;请注意,您需要的文件可能捆绑在一个较大的文件中。下载加利福尼亚州的拓扑集成地理编码和参照(TIGER)文件:FIPS 06.(联邦信息处理标准出版物,或 FIPS 文件指定州和县的等效物)。TIGER 形状文件包含地理信息但不包括人口统计信息,因此我们将很快添加该信息。链接标识了美国人口普查局 FTP 站点,并指定了加利福尼亚州 2019 年按区划级别的地理文件:

'ftp://ftp2.census.gov/geo/tiger/TIGER2019/TRACT/tl_2019_06_tract.zip'

一旦您在 FTP 中确定文件,要检索它,您需要 !wget 调用。wget 实用程序从 Web 服务器检索 FTP 文件:

!wget ftp://ftp2.census.gov/geo/tiger/TIGER2019/TRACT/tl_2019_06_tract.zip

对于 !wget 调用,.zip 文件路径上的引号是不需要的,并且文件将解压缩到您的 Colab 笔记本中。您可以在笔记本中直接观察解压缩进度,直到完成到 100%。您的输出应如下所示:

--2022-08-22 15:52:00--  ftp://ftp2.census.gov/geo/tiger/TIGER2019/TRACT/
tl_2019_06_tract.zip
           => 'tl_2019_06_tract.zip'
Resolving ftp2.census.gov (ftp2.census.gov)... 148.129.75.35, 2610:20:2010:a09:
1000:0:9481:4b23
Connecting to ftp2.census.gov (ftp2.census.gov)|148.129.75.35|:21... connected.
Logging in as anonymous ... Logged in!
==> SYST ... done.    ==> PWD ... done.
==> TYPE I ... done.  ==> CWD (1) /geo/tiger/TIGER2019/TRACT ... done.
==> SIZE tl_2019_06_tract.zip ... 29388806
==> PASV ... done.    ==> RETR tl_2019_06_tract.zip ... done.
Length: 29388806 (28M) (unauthoritative)

tl_2019_06_tract.zi 100%[===================>]  28.03M   262KB/s    in 69s     

2022-08-22 15:53:10 (416 KB/s) - 'tl_2019_06_tract.zip' saved [29388806]

图 7-8 显示文件在解压缩到文件夹层次结构后的情况。导航到县级数据,使用与查找加利福尼亚州小区相同的步骤。

Google Colab 文件结构

图 7-8。Google Colab 文件结构

现在文件解压缩后,我们可以从 Web 服务器检索 FTP 文件:

'ftp://ftp2.census.gov/geo/tiger/TIGER2019/COUNTY/tl_2019_us_county.zip'
!wget ftp://ftp2.census.gov/geo/tiger/TIGER2019/COUNTY/tl_2019_us_county.zip

现在您可以使用 GeoPandas 绘制加利福尼亚小区(ca_tracts),从您的 Colab 窗口读取文件。每个文件名旁边有三个点。右键单击点并选择复制路径以复制文件链接:

ca_tracts = gpd.read_file('/content/tl_2019_06_tract.shp')
ca_tracts.plot()

输出显示在图 7-9 中。

加利福尼亚人口普查小区

图 7-9。加利福尼亚人口普查小区

让我们花点时间弄清楚表 ID 的含义,以帮助您找到正确的数据。

在浏览器中访问人口普查 API 的数据

接下来,您需要打开美国人口普查局网站。在搜索窗口中输入**DP03**,这是人口普查数据的表 ID(图 7-10)。该 ID 可能看起来很随机,但实际上它提供了重要信息。从结果中可以立即看出,DP 指示一个数据概况表。表 ID 描述此表包含“广泛的社会、经济、住房和人口统计信息”。ID 中的后续数字 03 还涉及选定的经济特征。

人口普查 API:数据

图 7-10。人口普查 API:数据

选择 Enter 以查看可用表格列表。图 7-11 显示来自此搜索可用的不同年份和类型的 ACS 调查。这些被称为数据概况,稍后我们将再次提及它们。

来自美国人口普查局的数据概况表

图 7-11。来自美国人口普查局的数据概况表

census.gov 上的可用 API 页面(图 7-12)是一个重要资源,包含广泛主题的公开可用数据集。从这里选择美国社区调查(ACS)。

可用 API 及其他美国人口普查局数据集

图 7-12。可用 API 及其他美国人口普查局数据集

在美国社区调查(ACS)下,选择 1 年估计,并滚动到 2019 年。滚动表格列表直至看到数据概况。复制第一个链接并粘贴到浏览器中。您将编辑此链接并生成一个.csv文件。

提示

当你在“可用 API”页面时,你可以请求一个用于运行美国人口普查局服务请求的密钥。对于这个练习,你不需要一个,但我建议请求一个并将其保存在一个可靠的位置。浏览列出的其他数据集,并熟悉公开可用数据的范围。另一个有用的美国人口普查局资源是开发者论坛,这是一个接受数据集和咨询请求的活跃社区。

使用数据概况

在数据概况部分,你有将要编辑的示例调用以及关于数据概况表的其他详细信息:

点击链接以收集更多信息。你的第一个编辑将包括替换你感兴趣的表格 DP03,并从 URL 中删除占位符 &key=YOUR_KEY_GOES_HERE

api.census.gov/data/2019/acs/acs1/profile?get=group(DP03)&for=us:1

然后将 URL 粘贴到浏览器中并按 Enter 键。现在你将想要定制 URL 的地理部分,包括所有都市区域。从前一页选择示例和支持的地理位置,并滚动到地理层次结构图 7-13 中所示的都市区域。

地理层次结构

图 7-13. 地理层次结构

注意到 *(通配符选项),复制 URL,确保再次删除 YOUR_KEY_GOES_HERE 部分。另一种选项将为你提供一个单一都市区域的数据:俄亥俄州阿克伦都会区。

一旦你熟悉了 URL 和数据表,只需进行一些快速编辑即可获得所需的人口或经济数据的可用文件。按如下修改 URL:

https://api.census.gov/data/2019/acs/acs1/profile?get=NAME,DP03_0001E&for=metropolitan statistical area/micropolitan statistical area:*

现在将其粘贴到浏览器中并按 Enter 键。该文件以 JSON 格式保存在我的 Mac 上。与其将其保存为 .csv 文件,这会迫使你在 Microsoft Excel 中进行编辑,不如运行以下代码行将其从 .json 转换为 .csv

df = pd.read_json(‘link to json file’)

df.to_csv()
df

这将输出一个格式化的 .csv 文件。

创建地图

要完成我们创建人口密度项目,你将返回到 2010 年人口普查的 URL。

让我们从查看 2010 年人口普查的参考 URL 开始:

https://api.census.gov/data/2010/dec/sf1?get=LSAD_NAME,P001001&for=tract:&in=state:06*

现在您已经熟悉了人口普查 API 的调用,您可以在 Python 中分解链接,计算密度并自定义表格。在加利福尼亚州人口普查区段的 URL 调用中包括表P001001sf1指的是十年一次的人口普查:

# The built out request for the URL  https://api.census.gov/data/2010/dec/
sf1?get=LSAD_NAME,P001001&for=tract:*&in=state:06 HOST = "https://api.census.gov/data" year = "2010" dataset = "dec/sf1" base_url = "/".join([HOST, year, dataset])
predicates = {}
get_vars = ["LSAD_NAME", "P001001"]
predicates["get"] = ",".join(get_vars)
predicates["for"] = "tract:*" predicates["in"] = "state:06" r = requests.get(base_url, params=predicates)

现在您已经发出了请求,让我们查看数据的列名称:

print(r.json()[0])
['LSAD_NAME', 'P001001', 'state', 'county', 'tract']

记住index函数。您只需读取数据的第一行(r)来查看标题。您还可以创建新的名称:

# Create user friendly column names tract_name = ["tract_name", "tract_pop", "state_fips", "county_fips", 
"tract_fips"]
# Reading the json into pandas df tractdf = pd.DataFrame(columns=tract_name, data=r.json()[1:])
# Changing data types to integer tractdf["tract_pop"] = tractdf["tract_pop"].astype(int)

tractdf.head()

在笔记本中跟随操作。以下代码将选择所有洛杉矶县的属性,删除任何空值,并创建一个新的geoid列。运行代码,并验证确实已添加了geoid列。

首先,我们需要从洛杉矶县中选择属性:

onecounty_tractdf = tractdf.loc[tractdf['county_fips'] == 
'037'].copy()Onecounty_tractdf

然后,我们创建包含新geoid列的新数据框架:

onecounty_tractdf['tract_fips'] = onecounty_tractdf['tract_fips'].str.ljust(6,'0')
onecounty_tractdf['geoid'] = onecounty_tractdf['state_fips'] + 
onecounty_tractdf['county_fips'] + onecounty_tractdf['tract_fips']
onecounty_tractdf

数据框中有 2,346 条记录。您还可以通过一行代码快速计算记录数量:

onecounty_tractdf.count()

要将区段级别和县级文件连接起来,您需要选择一个连接属性。要回顾可用的选择,请使用以下代码检查列标题:

ca_tracts.info

该连接将合并来自两个数据集的列标题GEOIDgeoid

attr_joined = pd.merge(ca_tracts, onecounty_tractdf, left_on='GEOID', 
right_on='geoid')
# Check that all 2345 Census Tracts joined
attr_joined.count()

双重检查坐标参考系统,确保地理文件和人口普查数据匹配。ALAND 是面积,以平方米计。现在您可以将分组块添加到您的地图上:

map = fm.Map(location=[center_y, center_x], zoom_start=10)

# Add Study Area Block Groups to Map
fm.Choropleth(
   geo_data = ca_prj,
   data=ca_prj,
   columns=['tract_pop','ALAND'],
   key_on= 'feature.properties.tract_pop',
   fill_color='YlGnBu',
   name = 'Population Density',
   legend_name='Population Density'
).add_to(map)
map

人口密度衡量在给定区域内居住的人数,通常以平方公里计算。您可以在代码单元格中直接将研究区域转换为平方公里:

# Create a new column for Census Tract area in square Kilometers ca_prj['AreaLandKM2'] = (ca_prj['ALAND'] * .000001)

ca_prj[['geoid','TRACTCE','ALAND','AWATER','AreaLandKM2']].head()

ca_prj['ppl_perKM2']=(ca_prj['tract_pop']/ca_prj['AreaLandKM2'])
ca_prj[['geoid','TRACTCE','tract_pop','AreaLandKM2','ppl_perKM2']].head(16)

接下来,选择您希望地图中心的位置:

center_x = (ca_prj.bounds.minx.mean() + ca_prj.bounds.maxx.mean())/2
center_y = (ca_prj.bounds.miny.mean() + ca_prj.bounds.maxy.mean())/2
print(f'The center of the data file is located at {center_x} {center_y}')

现在您可以通过运行以下代码查看地图和图例(自动生成),显示洛杉矶县内的人口密度:

map = fm.Map(location=[center_y, center_x], zoom_start=10)

# Add Study Area Block Groups to Map
fm.Choropleth(
   geo_data = ca_prj,
   data=ca_prj,
   columns=['TRACTCE','ppl_perKM2'],
   key_on= 'feature.properties.TRACTCE',
   fill_color='YlGnBu',
   name = 'Population Density',
   legend_name='Population Density'
).add_to(map)
map

输出结果,您完成的地图显示在图 7-14 中。

洛杉矶县人口普查区段密度

图 7-14. 洛杉矶县人口普查区段密度

从人口普查 API FTP 站点导入 TIGER 文件可以保证单一的位置精度源。在这里,您查看了人口普查区段级别,但通过简单编辑 URL 调用,您可以更改到县级、国会选区或市政区等级。虽然这些文件类型不包括人口统计或经济信息,但您学会了如何发现数据资源并在笔记本中查看它们。将 JSON 文件保存为.csv格式还意味着您可以将其上传到如 QGIS 等 GUI 进行进一步探索。

总结

GeoPandas 是一个功能强大的开源 Python 项目,允许用户将地理数据与 pandas 对象集成,并进行空间操作。它在地理空间计算中默默地发挥作用。在本章中,您已经学习了文件的读取和写入、数据的选择、地图和图表的制作、交互式地图制作以及几个有用的包,以便将其包含在您未来的地理空间分析中。

^(1) 截至本文撰写时,2020 年人口普查的最终数据尚未公布。一旦数据公布,您只需在代码单元格中更新链接,即可获得最新信息。

第八章:数据清洗

处理数据时的一个通用问题是了解数据的完整性。数据工程依赖于清理、处理和可视化数据的能力。现在你已经熟悉了将数据与基于笔记本的代码编辑器集成的基本功能,无论是在本地使用 Jupyter Notebook 还是在云端使用 Google Colab,现在是学习如何清理你的数据的时候了。数据经常是不完整的(缺失),格式不一致,或者其他方面不准确——通常称为杂乱的数据。数据清洗是解决这些问题并准备数据进行分析的过程。

在这一章中,我们将探索一些公开可用的数据集,使用一些可以加载到 Colab 笔记本中的包来找到并清理杂乱的数据。你将使用来自纽约市开放数据门户NYC Open Data,更新于 2021 年 7 月 7 日的 NYPD_Complaint_Data_Historic 数据集。我筛选了 2020 年的数据,以便更容易查看和操作。你可以根据你的数据问题筛选数据并导出为 CSV 文件。本章将向你展示如何管理、删除、更新和 consoldate 数据,并用一些有用的 Python 包来处理数据。数据分析的准确性取决于数据集或数据库的质量,这一章将提供工具来评估和解决常见的不一致性问题。

检查缺失数据

如果你曾经参加过数据竞赛,比如在Kaggle上提供的那些竞赛,你可能会注意到数据集被设计成帮助你集中注意力解决特定任务,比如构建可视化。通常情况下,数据已经被清理过了。现实生活要复杂一些,缺失数据是一个持续存在的问题。让我们看看你如何让你的数据世界变得更加整洁。

使用电子表格和表格数据格式,评估数据的形状是一项直截了当的任务。稍微滚动一下就可以轻松地发现缺少数据的行和/或列。当你审查数据集以查找缺失数据的模式时,实际上是在评估它们的空值,或者说空数据(缺失值)的存在。地理空间分析,像一般的分析一样,通常涉及多个表格,因此学会如何识别这些表格数据之间的数据模式非常重要。

上传至 Colab

我在这个例子中使用了Google Colab,但你可以使用任何你喜欢的环境。将NYPD Complaint Data Historic CSV 文件上传到你的笔记本上,并点击文件名旁边的点。这将为你提供包含在笔记本中的路径(参见图 8-1)。

上传文件至 Colab

图 8-1 上传文件至 Colab

Missingno是一个 Python 库,用于检测数据集中的缺失值,并可视化它们在数据框架中的分布,这样可以更容易地发现模式。^(1) Missingno 在底层运行时依赖 NumPy、pandas、SciPy、matplotlib 和 seaborn,这使得底层代码片段更加熟悉。安装 missingno:

pip install missingno

根据数据集大小的不同,您可以调整样本大小。这里我设置了样本为 1000。导入 Python 库 pandas 将帮助您进行一些数据清洗和准备工作:

import pandas as pd
NYPD = pd.read_csv("/content/NYPD_Complaint_Data_Historic.csv")
import missingno as msno
%matplotlib inline
msno.matrix(NYPD.sample(1000))

一旦您上传数据集,您将需要查看数据字典,了解列标题表示的内容。

我们数据框架中列出的列显示在表 8-1 中。CMPLNT_NUM 似乎是每行的关键标识符。这一点很重要,因为每行都是数据集中一个事件的记录。

表 8-1. 纽约市警察局投诉数据集数据字典

Field name 描述
CMPLNT_NUM 每个投诉的随机生成的持久标识符
CMPLNT_FR_DT 报告事件发生的确切日期(或者如果存在 CMPLNT_TO_DT,则为事件开始日期)
CMPLNT_FR_TM 报告事件发生的确切时间(或者如果存在 CMPLNT_TO_TM,则为事件开始时间)
CMPLNT_TO_DT 如果确切时间不明确,则报告事件结束日期
CMPLNT_TO_TM 如果确切时间不明确,则报告事件结束时间
ADDR_PCT_CD 发生事件的警区
RPT_DT 向警察报告事件的日期
KY_CD 三位数的犯罪分类代码
OFNS_DESC 与关键代码对应的犯罪描述
PD_CD 三位数的内部分类代码(比关键代码更详细)
PD_DESC 与 PD 代码对应的内部分类描述(比犯罪描述更详细)
CRM_ATPT_CPTD_CD 犯罪行为是否成功完成、尝试但失败、或过早中断的指示符
LAW_CAT_CD 犯罪级别:重罪、轻罪或违规行为
BORO_NM 事件发生的市区名称
LOC_OF_OCCUR_DESC 发生地点具体描述:内部、相对于、前部或后部
PREM_TYP_DESC 场所的具体描述:杂货店、住宅、街道等
JURIS_DESC 管辖区代码的描述
JURISDICTION_CODE 事件责任管辖区:可以是内部机构,如警察(0)、运输(1)和住房(2),也可以是外部(3),如矫正局、港务局等。
PARKS_NM 如果适用,纽约市公园、游乐场或绿地发生事件的名称(不包括州立公园)
HADEVELOPT 如果适用,纽约市住房管理局发生事件的住房开发项目名称
HOUSING_PSA 发展级别代码
X_COORD_CD 纽约州平面坐标系统的x坐标,长岛区,NAD 83,单位为英尺(FIPS 3104)
Y_COORD_CD 纽约州平面坐标系统的y坐标,长岛区,NAD 83,单位为英尺(FIPS 3104)
SUSP_AGE_GROUP 嫌疑人年龄组
SUSP_RACE 嫌疑人种族描述
SUSP_SEX 嫌疑人性别描述
TRANSIT_DISTRICT 事件发生的运输区
Latitude 全球坐标系统的中部纬度坐标,WGS 1984,十进制度数(EPSG 4326)
Longitude 全球坐标系统的中部经度坐标,WGS 1984,十进制度数(EPSG 4326)
Lat_Lon 地理空间位置点(纬度和经度的组合)
PATROL_BORO 事件发生的巡逻区名称
STATION_NAME 运输站名称
VIC_AGE_GROUP 受害人年龄组
VIC_RACE 受害人种族描述
VIC_SEX 受害人性别描述

数据集中的变量需要确切地按照数据框中呈现的方式命名。如果它们全部大写或用下划线分隔,您需要确保复制它们(或者可以类似地重命名它们,就像您在第七章中重命名人口普查数据列一样)。

接下来,还有其他需要在数据集中考虑的问题。

空值和非空值

您首先需要检查数据集中的缺失值。通过使用像NYPD.info()NYPD.describe()这样的 pandas 功能快速查看数据(参见图表 8-2 和 8-4)。

使用.info()可以显示列名、非空值数和数据类型(Dtype):

NYPD.info()

在这个数据集中,范围索引有 1115 条记录;因此,任何具有少于 1115 个值的列都存在缺失数据。列的非空计数可以帮助您查看缺失的数据,并决定哪些列未捕捉到足够的数据以提供洞察。

该数据集的文档指出,空值可能归因于部门表格的变更和数据收集的不一致性。此外,如果 2020 年(我选择用来过滤数据的年份)的信息不可用或在收集时不明确,则将其分类为未知/不可用/未报告。重要的是要阅读支持文档,以了解数据的限制,这可能会限制您能够有用地提出的问题。

数据类型

数据清洗中的首要任务之一应该是检查数据集中每一列的数据类型。最常见的数据类型包括布尔值、整数、浮点数、对象和日期时间,您应该对这些类型非常熟悉。

这些数据类型中最棘手的是对象。Python 对象可以包含各种数据类型。它们通常是字符串,但您应该知道它们还可以包括整数、浮点数、列表和字典。在 pandas 库中,不同的数据类型被检测并分类为 NaN(默认标记为“不是数字”)或 NaT 表示缺少日期时间数据。NYPD.info的输出包括数据类型(Dtype),或者您可以使用dtypes DataFrame 属性,通过在其后添加点号来使用NYPD.dtypes

元数据

您还可以返回有关数据形状的信息。这与数据类型信息一起被称为元数据,或者称为数据的数据。要查看 NYPD 数据的形状,请运行:

NYPD.shape
(7375993, 35)

简化起见,在此示例中,我在源数据中筛选了仅包含 2020 年数据,以便我们处理较少的记录:1,115 条记录(图 8-2)。

NYPD.info()的数据框摘要

图 8-2. NYPD.info()的数据框摘要

每次投诉发生的确切时间,或者 CMPLNT_FR_TM,在图 8-2 中列出为对象或字符串。当 datetime 或其他数据类型被分配为字符串或对象时,Pandas 通常会猜测列中的数据类型。这通常会由于low_memory=True等默认设置而变得复杂。大文件会被“分块”,并且该文件通常会生成包含多个数据类型的列。我建议您不要简单地将参数切换为False。手动更改dtype更为高效,如下面的代码单元格所示:

NYPD = pd.read_csv('/content/drive/MyDrive/NYPD_Complaint_Data_Historic.csv',
                   parse_dates=['CMPLNT_FR_TM'])
NYPD.dtypes.head()

CMPLNT_NUM               int64
CMPLNT_FR_DT            object
CMPLNT_FR_TM    datetime64[ns]

NYPD = NYPD.drop(columns =['CMPLNT_TO_DT','CMPLNT_FR_TM','PARKS_NM','HADEVELOPT', 'HOUSING_PSA'])

NYPD.head()将更新您保留的列,并确认您的“删除”列不再显示在数据帧中。

如果不与您感兴趣的探索相关,您可以删除具有不完整数据的列。但我强烈建议您不要删除数据。通常,数据缺失的原因和选择包含的数据一样引人注目或重要。

让我们通过识别几个样本列来尝试这个功能从数据帧中删除。

摘要统计

您可以使用.describe()查看摘要统计,或者汇总您的样本数据的统计信息。

查看行计数是识别数据帧中不同特征缺失数据的另一个机会。在df.describe()的输出中,排除了 NaN 值,因此您可以看到数据的实际分布以及每列中值的计数。

中心趋势测量

正态分布是指平均值、众数和中位数都是相同的值。概率分布或中心趋势测量描述了分布中的中心值,当分布不正常时,如图 8-3 所示。

中心趋势测量回顾

Figure 8-3. 中心趋势测量的回顾

还有一些指标可以表明数据分布的偏斜程度和异常值的存在。 图 8-4 是2021 年能源和水数据披露法律 84 号(2020 年日历年数据)的一个快照,这是一份详细考察纽约市私人建筑物能源和水使用情况的大型数据集。表中的中位数或第二四分位数(Q2),即表中的 50%指标,表示中位数值,即一半数值在其上方,一半数值在其下方。

df.describe() 函数提供了数值数据列的描述性统计信息

Figure 8-4. df.describe() 函数提供了数值数据列的描述性统计信息。

快速查看数据形状的一种方法是观察 Q2 或中位数的位置。如果查看 图 8-4 中的第二列,您可以确定中位数的值更接近 25%还是 75%。如果更接近 25%或 Q1,则数据右偏,意味着有 25%的值低于 Q1 值。将 Q1 视为最小值和中位数之间的值。Q3 的值位于中位数和最大值之间,指示 75%的报告值将低于 Q3。这将导致数据具有左偏。

您可以通过 df.describe() 函数快速查看数据集中的数值值。将以下代码段输入到您的笔记本中以查看:

df.describe()

绘制直方图有助于更好地可视化数据的形状。如 图 8-5 所示,存在右偏尾或正偏态。这表明均值大于中位数,中位数大于众数。

数据分布的可视化

Figure 8-5. 数据分布的可视化

让我们回到 NYPD 数据。我们可以可视化数据并查看其分布:

size, scale = 1000, 10
Complaints = pd.Series(np.random.gamma(scale, size=size) ** 1.5)

Complaints.plot.hist(grid=True, bins=20, rwidth=0.9,
                 color='#607c8e')
plt.title('Distribution of Complaint Types')
plt.xlabel('CMPLNT_NUM')
plt.ylabel('OFNS_DESC')
plt.grid(axis='y', alpha=0.75)size, scale = 1000, 10
Complaints = pd.Series(np.random.gamma(scale, size=size) ** 1.5)

Complaints.plot.hist(grid=True, bins=20, rwidth=0.9,
                 color='#607c8e')
plt.title('Distribution of Complaint Types')
plt.xlabel('PATROL_BORO')
plt.ylabel('OFNS_DESC')
plt.grid(axis='y', alpha=0.75)

可视化还显示了投诉类型数据的右偏分布(见 图 8-5)。

图 8-6 简单统计了数据集中每一列缺失的数值。在分析数据之前了解记录缺失的时机提供了透明度,并且在分享数据可视化时,传达这些信息至关重要。

NYPD.isna().sum() 列出了数据集中每一列的缺失值统计

Figure 8-6. NYPD.isna().sum() 列出了数据集中每一列的缺失值统计。

NYPD.isna() 返回带有布尔值指示缺失值的数据框,如 图 8-7 所示。在计算中,布尔值只有两个可能的值:TrueFalse,通常表示为 10。在这种情况下,True 表示缺失数据。一目了然,您可以看到数据缺失的位置。

数据框 NYPD.isna()

图 8-7. 数据框 NYPD.isna()

这些只是探索数据框并了解数据完整性或空值范围的几个选项。

现在您已经找到了缺失数据,接下来该怎么办?

替换缺失值

检查数据框的另一个原因是查看缺失值并确定它们的特征。一旦您知道表中如何记录缺失值,您就可以使用 na_values 参数将它们替换为您选择的变量,例如 NaNunknown 或其他感兴趣的值:

df_test = pd.read_csv("/content/NYPD_Complaint_Data_Historic.csv", 
na_values = 'NaN')
print(df)

探索数据集通常会发现数据表中缺失值报告的不一致性。代码片段将查询替代变量,如 ?999

使用 Missingno 可视化数据

在本节中,我们将再次使用 Missingno。Missingno 可以将表格数据矩阵转换为布尔掩码,因为 掩码 基于底层操作数据的某些标准返回数据。数据根据指定标准返回 True/False 布尔值。在计算中,布尔值只有两个可能的值。Missingno 将包含数据的单元格标记为 True,空单元格标记为 False。它可以将这些数据可视化为 空值矩阵(图 8-8),这种图表使用空白空间来直观表示缺失数据并显示模式。数据存在的地方将被阴影化。

纽约警察局投诉数据集的空值矩阵

图 8-8. 纽约警察局投诉数据集的空值矩阵

在 图 8-8 中,每列代表数据中的一个特征,每行代表一个观察结果。矩阵右侧的小火花线显示了每个记录中缺失数据的量。

您在这里看到了任何缺失数据模式吗?我注意到第四和第五栏,即投诉结束日期和时间,具有相同的模式:当这两个值中的一个缺失时,另一个也缺失。您可以使用 msno.bar 将相同信息显示为列,而不是简化的矩阵。

msno.bar(NYPD.sample(1000))

柱的高度等于空值或缺失数据的级别(图 8-9)。较高的柱表示值完整(没有缺失数据)或几乎完整。

通过列显示的空值

图 8-9. 通过列显示的空值

评估 msno.heatmap(NYPD) 可以检查 空值相关性,即变量的存在或缺失以及这如何强烈地影响另一个变量的存在。相关性的度量范围为 –1 到 1,如图 8-10 所示。零相关 意味着一个变量的存在似乎与另一个变量的存在无关;相关分数为 1 表示一个变量在另一个存在时也存在。负值表示 负相关:一个变量的存在意味着另一个变量不会存在。

空值数据热图(NYPD)

图 8-10. 空值数据热图(NYPD)

始终存在或从不存在的值不会在热图中可视化。例如,有两列——RPT_DT(报告给警察的日期)和 JURIS_DESC(辖区描述)——报告没有缺失数据,它们不会在热图中可视化。其他似乎不测量任何内容的值可能是由错误数据生成的。对数据的更详细检查可能会发现一些有趣的东西。请注意小于 1 的值:例如,图 8-10 中的 –0.3 值值得更仔细地观察。例如,小于 –1 的值表示相关性几乎是 100% 的负相关。

树状图,或称树状图,是一种分支图,用于直观地表示不同组之间的关系。分支称为 类群,末端称为 叶子。类群的排列告诉我们哪些叶子最相似,基于它们的接近程度。分支点的高度指示它们之间的相似性或差异性:高度越大,差异性越大。树状图中分组的越接近 0,表示一列中的空值出现与其他列的空值出现或缺失有更密切的关系。

scipy.cluster.hierarchy.dendrogram 通过绘制连接类群的 U 形链接来组成聚类。如果列数少于 50,则树状图的方向默认为自上而下;如果列数超过 50,则默认为左右方向。

在图 8-11 中,表示嫌疑人种族和性别的列(SUSP_RACE 和 SUSP_SEX)比表示嫌疑人年龄组和转运区(SUSP_AGE_GROUP 和 TRANSIT_DISTRICT)的列更相似。

NYPD 数据集中的树状图关系

图 8-11. NYPD 数据集中的树状图关系

要查看层次聚类,请将以下代码片段写入代码单元格中:

msno.dendrogram(NYPD)

在关注数据集内数据分布时,树形图是非常有用的。层次聚类定量估计每个数据点与数据集中的每个其他点的关系。聚类距离在垂直轴上,不同的数据点水平显示。数据样本的最高粒度在数据样本水平上,树形图是一种快速可视化的方法。

映射模式

QGIS 是查看数据和查找数据模式的另一种方式。图 8-12 中的数据被筛选,仅显示入室盗窃案,以帮助根据位置指导额外洞察。让我们看看如何生成这样的地图。

在地图上可视化 NYPD 筛选后的犯罪数据

图 8-12. 在地图上可视化 NYPD 筛选后的犯罪数据

纬度和经度

数据集有纬度和经度列。你可以将它们转换为指出特征的点,然后使用 GeoPandas 创建地图。让我们使用相同的文件导入数据并添加一个 shapefile。首先,导入 pandas:

import pandas as pd

接下来,read_csv 将数据文件读入你的笔记本:

df = pd.read_csv('/content/drive/MyDrive/NYPD_Complaint_Data_Historic.csv')

如果你使用的是 Google Colab,可以直接上传文件。你可以将 df 变量修改为任何你喜欢的内容。df.head() 函数将返回每列的前几行:

df.head()

作为最佳实践,请确保精确捕获数据中出现的拼写和大写,编写代码时始终将经度列在纬度列之前。

Shapefiles

你将使用 Python 库 Shapely 进行计算几何,或处理诸如点、曲线和曲面等几何对象。上传 shapefile 时,请确保将与 shapefile 相关的所有文件(它们将具有 .dbf.shx.prj 扩展名)上传到同一目录中。图 8-13 中的 shapefile 来自NYC 开放数据区界

NYC 开放数据区界

图 8-13. NYC 开放数据区界

首先,导入你的库和包:

!pip install geopandas
import geopandas as gpd
from shapely.geometry import Point, Polygon
import matplotlib.pyplot as plt
street_map = gpd.read_file('/content/tl_2021_36_place.shp')
提示

Python 库在代码中通常被缩写。你已经看到 pandas 被缩写为 pd,而 GeoPandas 被缩写为 gpd。请注意,plt 用于 matplotlib —— 不要误以为它是函数或参数的一部分。

现在 Python 只需要知道如何在定义的空间中应用坐标 —— 所以你需要定义它:

# designate coordinate system
crs = {'init': 'epsg:4326'}
# zip x and y coordinates into single feature
geometry = [Point(xy) for xy in zip(df['Longitude'], df['Latitude'])]
# create GeoPandas dataframe
geo_df = gpd.GeoDataFrame(df,
crs = crs,
geometry = geometry)

GeoPandas 是笛卡尔坐标参考系统,这意味着每个点由一对数字坐标定义,比如我们示例中的纬度和经度。它将地理数据分配给 pandas 对象。GeoPandas 数据框从定义我们的 CRS 和组合我们的经度和纬度列而创建。Shapely 中的 Point 函数使用经度和纬度列创建一个名为几何的新列,如图 8-14 所示。

添加新列,几何图形

图 8-14. 添加新列,几何图形

在前述代码单元格中,我们定义了geo_df,现在我们可以查看新创建的几何列:

geo_df.head()

这里我扩大了figsize以便更好地查看,但在较小的尺寸下加载速度会更快。默认单位是英寸,但也可以进行厘米和像素的转换street_map被分配给轴,因为这是我们分配形状文件的位置:

# create figure and define axes, assign to subplot (matplotlib) fig, ax = plt.subplots(figsize=(30,30))
# add .shp mapfile to axes street_map.plot(ax=ax, alpha=0.4,color='grey')
# add geodataframe to axes
# assign 'OFNS_DESC' variable to represent coordinates on graph
# add legend
# make data points transparent using alpha
# assign size of points using markersize geo_df.plot(column='OFNS_DESC',ax=ax,alpha=0.5, legend=**True**,markersize=10)
# add title to graph plt.title('Reported Offenses', fontsize=15,fontweight='bold')
# set Latitude and Longitude boundaries for map display plt.xlim(-74.02,-73.925)
plt.ylim( 40.7,40.8)
# show map plt.show()

图 8-15 是从 GeoPandas 数据框创建的地图。您可以过滤和编辑报告的事件,以创建更具针对性的地图。(参考Jupyter Notebook获取更多选项。)plt.xlimplt.ylim 命令可让您选择特定的边界来进一步编辑您的投影。

如果您想从数据集中选择一种犯罪类型,请使用df.loc函数来定位所有实例。以下是一个显示入室盗窃的示例:

fig, ax = plt.subplots(figsize=(15,15))
street_map.plot(ax=ax, alpha=0.4,color='grey',legend=True,markersize=20)
geo_df.loc[df['OFNS_DESC'] == 'BURGLARY']
geo_df.plot(ax=ax, alpha=0.5,)
plt.xlim(-74.02,-73.925)
plt.ylim( 40.7,40.8)
plt.show()

或者您可能希望列出几种不同的违法行为:

df.loc[df['OFNS_DESC'].isin([value1, value2, value3, ...])]

或者一组不同的参数:

df.loc[(df['OFNS_DESC'] == value) & (df['TRANSIT_DISTRICT'] == value)]

GeoPandas 在纽约市各区地图上可视化的数据框

图 8-15. GeoPandas 在纽约市各区地图上可视化的数据框

尝试提出不同的问题!

摘要

您已学习了一系列在清理数据时非常有用的方法。这一点尤为重要,特别是在处理开放源码数据时。例如,社区级别的数据通常是手动输入的,拼写错误、缺少日期和几何变量的缺失可能会限制这些宝贵资源的实用性。

^(1) Bilogur, Aleksey. 2018. “Missingno: A Missing Data Visualization Suite.” Journal of Open Source Software 3 (22): 547. https://doi.org/10.21105/joss.00547

第九章:探索地理空间数据抽象库(GDAL)

讨论开源平台和库,如果没有介绍Geospatial Data Abstraction Library (GDAL),将是不完整的。这是一个有效处理栅格和矢量地理空间数据的资源。处理栅格和矢量数据需要一系列工具,而 GDAL 在本书中使用的许多程序的内部工作中起着关键作用,包括 ArcGIS、QGIS 和 Google Earth。当依赖图形用户界面的程序化便利时,很容易忘记将多种数据类型和格式结合起来,以便以统一的数据模型高效地工作。这就是为什么 GDAL 是一个重要工具。它简化了在各种格式和空间参考系统中处理地理空间数据的工作。

在本章中,我们将处理栅格数据,并看看如何使用 Spyder IDE 处理 GDAL,包括如何使用warp函数更改地图投影,处理栅格波段,转换文件并创建二进制掩模。我们还将快速介绍另外三个有用的数据集资源:EarthExplorer、Copernicus 开放访问中心和 Google Earth Engine(GEE)。

首先,我会向你展示如何使用命令行界面,也称为终端,快速读取、转换和重新投影你的地理空间数据。为什么要使用命令行?如果你正在处理多个需要相同功能的文件,你不必手动逐个操作,试图在 QGIS 或 ArcGIS 中回忆必要的步骤和过程。使用命令行,你可以将一行简单的代码保存为脚本,用来处理各种文件和数据集。正如本章稍后将看到的那样,这些脚本也可以在 QGIS 中运行。在命令行工作时,我建议在 QGIS 中查看结果,或者在你对终端工作感到舒适后,可以参考 Spyder IDE 部分。

其次,我会向你展示如何在 IDE 中使用 GDAL,这是一种集成了编程和应用开发的工具。许多初学者 Python 课程都是在 IDE 中教授的,所以如果你有一些 Python 编程背景,可能对至少一个 IDE 比较熟悉。Python 有很多 IDE,但在本章中,我们将使用一个叫做 Spyder 的 IDE。像 Spyder 这样的 IDE 通过实时代码内省(允许你直接查看函数和类是如何执行以及它们包含的信息)、与 matplotlib 一起显示的内联图形以及我最喜欢的变量资源管理器(提供了编辑和可视化代码中对象的内置支持)等功能,简化了与代码的工作和编写。不管你使用哪个操作系统,IDE 看起来都是一样的,这在你的最终用户访问不同的工作流程和资源时非常方便。

配置 GDAL

从历史上看,这个库被称为GDAL/OGR,其中 GDAL 是栅格库的一部分,而 OpenGIS 简单要素参考实现(OGR)是库的矢量部分。现在,这些组合库通常被称为GDAL。我知道有许多数据科学家几乎完全在命令行上工作,所以我想快速演示一下如何实现这一点。对我来说,在命令行工作使我更容易熟悉调用 GDAL 函数的语法。它的类和函数与你从 Python 中了解到的有些不同,尽管在使用 Python API 时,语法会很熟悉。GDAL 保持对 Python 的绑定,但这与你迄今为止使用 Python 库的方式有所不同。即使使用 Python 绑定,你也需要知道如何调用 GDAL 函数并理解它们的功能。^(1) 在使用 IDE 工作的部分中,我们将回到 Python。

安装 Spyder

Spyder IDE 完全由 Python 编写。它以被数据科学家、数据分析师和数据工程师使用而闻名。其功能包括自动补全和实时分析。让我们首先安装它。

我建议为 Spyder 安装创建一个新环境。也可以将 GDAL 安装到现有环境中;你可以输入**conda env list**查看已有的环境。你还需要添加 NumPy 和 matplotlib。

在相同的环境和目录中安装 Spyder 和 GDAL,用你自己的文件路径替换我的:

spyder-env/Users/bonnymcclain/opt/miniconda3/envs/spyder-env

conda install -c conda-forge spyder

安装完 Spyder 后,可以通过在终端中输入**spyder**来启动它。

安装 GDAL

你还将在命令行安装 GDAL。打开终端并在提示符下运行以下命令:

Conda install -c conda-forge gdal

要检查安装是否成功,请运行:

gdalinfo —-version

可能需要设置安装文件夹的路径以从终端访问它。接下来将介绍如何完成这些步骤。我还建议查阅GDAL 文档

在命令行中使用 GDAL

如今,普通计算机用户主要在 GUI 中工作:这是一个图形层,使得可视化文件和执行任务变得更加容易。但命令行也有其优点,包括更快的处理速度。不需要加载大量文件,如果想在不同数据集上重复分析,可以保存小型 shell 脚本执行重复操作。

激活你首选的环境,conda activate minimal_ds,或者你刚创建的用于处理 GDAL 的 Spyder 环境。我想要在我的 TIFF 文件夹中操作文件,所以我已经输入cd来将其设置为当前目录。现在你可以看到 TIFF 文件被添加为我的工作目录:

(minimal_ds) MacBook-Pro-8:~ bonnymcclain$ cd TIFFs
(minimal_ds) MacBook-Pro-8:TIFFs bonnymcclain$ ls

要在不同目录之间移动,请使用ls/深入了解您的目录结构。例如,如果您想要处理 Downloads 文件夹(假设它不是您当前的目录),请使用以下代码来浏览 Downloads 目录中的文件夹:

ls/Downloads/another folder

(minimal_ds) MacBook-Pro-8:~ bonnymcclain$ cd TIFFs
(minimal_ds) MacBook-Pro-8:TIFFs bonnymcclain$ ls

花点时间在不同目录之间移动,因为理解结构是在终端中使用 GDAL 包进行工作时最重要的一课。再次强调,访问 Spyder IDE 中的包、库和文件时,您需要理解结构。

如果您在正确的目录中,冒号后面和您的用户名之前将会看到目录名。这将输出该目录中的文件列表。

TIFFs bonnymcclain$ ls

我已经包括了一个关于使用GEE创建您自己文件资源的部分,以便您可以跟进。Copernicus 公开访问中心EarthExplorer也是获取栅格数据的来源,但由于较大文件的大小通常渲染速度较慢。

现在您的安装已经完成,您可以学习一些命令来帮助您入门。这里是GDAL 文档,可以帮助您解决下载和安装中的任何问题。

使用 GDAL 编辑您的数据

gdalinfo命令行参数展示了关于您的栅格数据集的可用信息。您可以使用gdalinfo命令并提供活动目录中文件的名称来查看这些参数。在终端中输入以下代码:

gdalinfo [--help-general] [-json] [-mm] [-stats | -approx_stats] [-hist] [-nogcp] 
[-nomd]
         [-norat] [-noct] [-nofl] [-checksum] [-proj4]
         [-listmdd] [-mdd domain|`all`]* [-wkt_format WKT1|WKT2|...]
         [-sd subdataset] [-oo NAME=VALUE]* [-if format]* datasetname

在这个示例中,我从 GEE 数据片段中选择了一个保存的文件。您可以在文档中了解更多关于命令行参数的信息。

输入以下代码,用您目录中.tif文件的名称替换(注意不需要括号):

(minimal_ds) MacBook-Pro-8:TIFFs bonnymcclain$ gdalinfo Sentinel2_RGB20200501.tif

这里是输出的一部分片段:

(minimal_ds) MacBook-Pro-8:TIFFs bonnymcclain$ gdalinfo Sentinel2_RGB20200501.tif
Driver: GTiff/GeoTIFF  This is the format of the saved file
Files: Sentinel2_RGB20200501.tif
Size is 5579, 4151
Coordinate System is:
PROJCRS["WGS 84 / UTM zone 29N",
    BASEGEOGCRS["WGS 84",
        …
Pixel Size = (10.000000000000000,-10.000000000000000)
Metadata:
  AREA_OR_POINT=Area
Image Structure Metadata:
  COMPRESSION=LZW
  INTERLEAVE=PIXEL
Corner Coordinates:
Upper Left  (566320.000, 4133200.000) (8d15' 4.53"W, 37d20'35.24"N)
Lower Left  (566320.000, 4091690.000) (8d15'17.78"W, 36d58' 8.31"N)
Upper Right (622110.000, 4133200.000) (7d37'17.50"W, 37d20'14.86"N)
Lower Right (622110.000, 4091690.000) (7d37'41.89"W, 36d57'48.20"N)
Center      (594215.000, 4112445.000) (7d56'20.39"W, 37d 9'13.16"N)

您可以从第二行看出保存文件的格式是 GTiff/GeoTIFF。

小贴士

要查看其他格式的列表,请在命令行中输入gdal_translate --formats。您还可以查看文件大小、坐标系、像素大小和坐标。

这里是显示关于颜色波段信息的输出部分:

Band 1 Block=256x256 Type=Float32, ColorInterp=Gray
  Description = B2
Band 2 Block=256x256 Type=Float32, ColorInterp=Undefined
  Description = B3
Band 3 Block=256x256 Type=Float32, ColorInterp=Undefined
  Description = B4

注意 Sentinel 卫星数据波段的颜色解释。它们被设置为Gray用于 B2 波段,对于剩余的两个波段则是ColorInterp=Undefined。由于未下载元数据,您需要帮助 GDAL 解释这些波段。

由于这是Sentinel 2 数据,您知道波段是蓝色(B2)、绿色(B3)和红色(B4)。您将直接在原地编辑数据集,使用命令gdal_edit.py,接着是选项(如colorinterp),最后是值(这里是波段的颜色值)。最后一步是提供输入文件,Sentinel2_RGB20200501.tif

gdal_edit.py -colorinterp_1 blue -colorinterp_2 green -colorinterp_3 red 
Sentinel2_RGB20200501.tif

如果您的功能需要输出文件,则还需要添加output.tif。这里只需要输入文件。

当您重新运行 gdalinfo Sentinel2_RGB20200501.tif 时,您会看到颜色波段已更新,现在显示为蓝色、绿色和红色。

变形功能

您还可以通过学习使用 gdal_warp 函数来更改栅格投影。(一旦熟悉 warp 函数,您可以用它来学习其他常见函数;要探索它们,建议从 Python API 的 GDAL 文档开始。)

在以下代码中,参数 -t_srs 指定您要定位的坐标系统。例如,每个地理坐标系统都分配了唯一的 EPSG 代码。*xxxxx* 的位置,请输入您希望更改为的 EPSG 代码。输入是栅格文件,输出是重命名的修改文件,如 output_rd.tif 所示:

gdalwarp -t_srs EPSG: xxxxx  Sentinel2_RGB20200501.tif output_rd.tif
gdalinfo Sentinel2_RGB20200501.tif

您的输出将展示新的投影。

捕获输入栅格波段

接下来,您将使用 GDAL 命令行捕获投影波段的图像统计信息:

(minimal_ds) MacBook-Pro-8:TIFFs bonnymcclain$ gdalinfo -stats srtm_41_19_4326.tif

输出摘录:

    STATISTICS_MAXIMUM=640
    STATISTICS_MEAN=256.70790240722
    STATISTICS_MINIMUM=27
    STATISTICS_STDDEV=119.8746675927
    STATISTICS_VALID_PERCENT=100

为什么要捕获输入栅格波段进行统计分析?主要用于分类:在查看不同位置时识别群集。例如,波段特征有助于确定您是否观察到植被、水体或者住宅区。

我在本章前面提到,GDAL 在 QGIS 中以及多种其他程序中运行。 图 9-1 展示了代码在 QGIS 平台上的运行,上传您的 .tif 文件,并选择栅格信息。

在 QGIS 中显示的 gdalinfo

图 9-1. 在 QGIS 中显示 gdalinfo

现在您已经体验了在命令行中使用 GDAL,我鼓励您进一步探索更多可用的任务。这些技能对处理和编辑地理空间数据和应用程序的核心算法非常有用。在处理各种矢量和栅格数据格式时,跨平台和多样化格式转换的能力尤为重要。

接下来,我们将与 Python 集成的 GDAL 进行操作。

使用 Python 处理 GDAL 库

当您启动 Spyder(再次在终端中键入 **spyder**)时,Spyder 控制台将在新的浏览器窗口中打开,如 图 9-2 所示,但是会是空白的。

Spyder 控制台

图 9-2. Spyder 控制台

在 Spyder 中的初始定位

图 9-2 的左侧是 Spyder 的脚本编辑器,您可以在其中创建和打开文件。(您可以自由排列面板和控制台。)右侧的两个控制台是您可以探索生成的变量和定位文件的地方。显示图像的面板是绘图窗格。您可以在底部控制台中编写简单的代码。如需帮助,请选择上窗口的帮助选项卡。

您可以在 Python 控制台或编辑器中运行代码。您还可以使用 Python 控制台控制 Spyder 的调试器。您生成的图像和图表将显示在绘图面板中或嵌入到控制台中。

变量资源管理器显示在图 9-2 右上窗格中,是我在集成开发环境中喜欢工作的一个原因。这些对象是在运行代码时生成的。点击一个变量以深入探索其详细信息。

每个控制台窗口还有一个“汉堡包”图标,或☰,展开成一个菜单(图 9-3),您可以在其中找到有关窗格的其他信息。

在控制台目录中工作

图 9-3. 在控制台目录中工作

这些选项是可定制的。我建议访问Spyder 网站获取界面详细信息以及探索所有可用窗格。如果您想在安装之前尝试 Spyder,还有一个Binder 选项,可以在浏览器中运行。

虽然这并非必须,但我喜欢在 Spyder 中启动一个新项目,就像在图 9-4 中演示的那样。这简化了返回到之前文件的过程,而不管当前工作目录如何。项目窗格也将变得可见。这让我想起 QGIS 的便利,因为文件很容易到手。

在 Spyder 中创建新项目

图 9-4. 在 Spyder 中创建新项目

项目窗格还可以与 Git 集成,用于版本控制。查阅Spyder 文件文档以了解如何在您的存储库中设置此功能。

在 Spyder 中探索您的数据

您距离在集成开发环境中探索您的栅格数据只有几行代码。第一步是导入必要的包:GDAL、NumPY 和 matplotlib:

from osgeo import gdal
import numpy as np #to manipulate the array from raster
import matplotlib.pyplot as plt #to view figure

NumPY 将操作从栅格文件创建的数组,而 matplotlib 将允许您查看文件。您可以探索的栅格程序包含在GDAL 文档中。

实用脚本属于实用类,这是一个跨应用程序可用的相关方法集合。Python 实用脚本位于osgeo_utils.samples子模块中,如下面的代码所示:

ds = gdal.Open("slope.tif")
gt = ds.GetGeoTransform()
proj =ds.GetProjection()
band = ds.GetRasterBand(1)
array = band.ReadAsArray()

如果您不确定如何找到.tif文件,请跳转到“探索开源栅格文件”,我将介绍如何查找栅格文件以进行探索。这些是简单的数字高程模型(DEMs),其中每个点或像素都具有高程值。通常,它们表示为 DEM .tif文件。如果您在阅读本书时一直在探索,那么您的下载文件夹可能已经拥有不少文件了。

你将写入编辑器的代码脚本在文本中有解释;更多信息可从GDAL Python API获取。

你可以随意给你的变量取任何名字,但为了简单起见,我使用 ds 来表示数据集。导入你的 .tif 文件并创建变量 ds 后,你会看到变量出现在变量资源管理器中。ds 文件的格式是 gdal.Dataset

GDAL 中的文件转换

你需要将你的 .tif 文件从图 9-6 中所见的表格格式转换为地理坐标,因此接下来你将定义 gt,地理转换。

图中图 9-5 中的六个系数,从上到下依次对应:

0

左上角像素的x坐标(262846.525725)

1

像素分辨率,从西到东(25.0)

2

行旋转(通常为 0)

3

左上角的像素的y坐标(4464275.0)

4

列旋转(再次,通常为 0)

5

北西像素分辨率和高度(通常为负值表示北向图像),-25.0

地理转换为地理参考坐标

图 9-5. 地理转换为地理参考坐标

投影信息在变量资源管理器中可见(图 9-2),但在图 9-6 中显示更大。在通用横轴墨卡托投影(UTM)中,投影是 UTM 30 北区;EPSG 代码是 EPSG:32630。

GDAL 读取的波段的投影

图 9-6. GDAL 读取的波段的投影

GetRasterBand 将波段提取到数据集中。要确定你有多少个波段,写入控制台:

ds.RasterCount

这会输出:1

输入波段数到函数中:

band = ds.GetRasterBand(1)

array = band.ReadAsArray()

探索数组变量,你会看到 Python 已经将 GDAL 文件读入 NumPY 数组。数组共享我们 DEM 中的高程,如图 9-7 所示。

DEM .tif 文件的 NumPY 对象数组

图 9-7. DEM .tif 文件的 NumPY 对象数组

在 GDAL 中使用二进制掩码

掩模 对于裁剪边界或值到特定范围非常有用。 二进制掩模(通常称为binmask) 是一个过滤掉高于或低于某个数字的高度值的工具(例如,如果你只想查看低于海平面的位置)。如果你想保存一切大于或等于平均值的内容,你将把它赋值为 1;否则,它将是 0. 输出显示在图 9-8 中。暗值处于较低高度。

代码中的 binmask 变量指的是基于条件(如 mean)返回数组中元素的 NumPy (np) 函数:

binmask = np.where((array >=np.mean(array)),1,0)
plt.figure()
plt.imshow(binmask)

要将这些数据保存为 GeoTIFF 文件,您将需要一个 GDAL 驱动程序来支持您选择的栅格文件格式。您可以查看不同驱动程序的长列表,包括 GeoTIFF,在GDAL 文档中找到。

binmask.shape

这输出为(410,601)。

二进制掩模输出

图 9-8. 二进制掩模输出

回顾如何在 Python 中使用index函数。binmask 的形状为 410 行 601 列。在下面的代码片段中,xsize指的是列数。您通过索引调用列,在本例中为[1],以获取计数(在本例中为 601)。您可以用ysize和索引[0]来获取行数(在本例中为 410):

#Now we want to save as GeoTiff so we need a GDAL driver
driver = gdal.GetDriverByName("GTiff")
driver.Register()
outds = driver.Create("binmask.tif", xsize = binmask.shape[1],
                      ysize = binmask.shape[0], bands =1,
                      eType=gdal.GDT_Int16)

gdalconst定义了图像中的数据类型。由于您正在使用01定义 binmask 的形状,您需要将值设置为整数,这将使用gdal.GDT_Int16完成。现在,您只需要打印数据类型属性。

最后一步是另一个地理转换。由于您没有更改任何内容,设置可以保持不变。一旦关闭文件,它们将可供您使用。如果您忘记了,您将无法使用它们。您也不需要读取数组,因为您还没有将其存储在任何地方。WriteArray(binmask)将提供输出:

#need geotransform to be complete
outds.SetGeoTransform(gt)
outds.SetProjection(proj)
outband = outds.GetRasterBand(1)
outband.WriteArray(binmask)
outband.SetNoDataValue(np.nan)
outband.FlushCache()

#close
outband = None
outds = None

总结一下,您已经从 DEM 识别并生成了一幅图像,并将输出转换为 GeoTIFF 文件。栅格图像与关于图像在地球表面像素级位置的任何信息或元数据一起保存。

完整脚本

这是完整的代码;当您想查看图像时,请取消注释打印选项:

from osgeo import gdal
import numpy as np
import matplotlib.pyplot as plt

ds = gdal.Open("slope.tif")
gt = ds.GetGeoTransform()
proj=ds.GetProjection()
band = ds.GetRasterBand(1)
array = band.ReadAsArray()

#plt.figure()
#plt.imshow(array)

binmask = np.where((array >=np.mean(array)),1,0)
plt.figure()
plt.imshow(binmask)

driver = gdal.GetDriverByName("GTiff")
driver.Register()
outds = driver.Create("binmask.tif", xsize = binmask.shape[1],
                      ysize = binmask.shape[0], bands =1,
                      eType=gdal.GDT_Int16)

outds.SetGeoTransform(gt)
outds.SetProjection(proj)
outband = outds.GetRasterBand(1)
outband.WriteArray(binmask)
outband.SetNoDataValue(np.nan)
outband.FlushCache()

#DON’T FORGET TO CLOSE FILE

outband = None
outds = None

探索开源栅格文件

开源地理空间社区拥有大量的公开数据集,供您继续学习并支持当前和未来的项目。这本书旨在成为一个不断发展的资源,邀请您进行额外的学习和技能发展。因此,在接下来的几节中,我将提供一些基础知识,让您开始探索这些资源,而不是进行完整的练习。目标是让您能够开始自主探索。

USGS EarthExplorer

美国地质调查局(USGS)托管了最大的免费卫星和航空图像数据库之一,名为 EarthExplorer。

您需要注册一个免费帐户在EarthExplorer。如果您有一个包含 shapefile(.shp)的压缩 ZIP 文件,您可以使用左上角的 KML/Shapefile 上传按钮上传它。免费的开源数据资源通常提供下载压缩的 shapefile 的选项。必须上传 shapefile 的所有附带文件。

要使用 EarthExplorer,你可以上传一个你想处理的 shapefile。图 9-9 展示了我上传的名为“Bodega 海洋实验室和保护区”的文件。当我在我想查看的区域绘制一个多边形(显示为红色)时,我可以下载 GeoTiff 文件。你可以更改坐标来重新定义多边形的形状。

上传 shapefile 到 EarthExplorer

图 9-9. 上传 shapefile 到 EarthExplorer

除了上传一个 shapefile 外,还有几种创建图像的方式:

  • 缩放到你希望探索的区域并绘制多边形或圆圈

  • 搜索地址(图 9-10)

  • 双击地图并选择使用地图按钮

  • 选择一个日期范围

你还可以在菜单中输入日期范围或云覆盖范围,如图 9-10 所示。在这个练习中,使用你选择的方法跟随我来到 Bodega 海洋实验室和保护区。将位置输入到搜索条件中。

搜索感兴趣区域的卫星图像

图 9-10. 搜索感兴趣区域的卫星图像

一旦你到达 Bodega 海洋实验室和保护区,选择数字高程数据,然后选择 SRTM Void Filled,如图 9-11 所示。SRTM 是空中雷达地形测量任务的缩写。Void Filled SRTMs 经过额外处理以填补缺失数据。

图 9-11. 地球测量数字高程 SRTM 数据

如果有符合你条件的图像,它们将作为搜索结果加载(图 9-12)。一旦调整了参数,你将看到数据集的缩略图。找到最适合你需求的数据集并下载 GeoTIFF。将其保存到工作目录中的文件夹或使用绝对路径(完整路径)将其上传到 Spyder 控制台中。^(2)

图 9-12. EarthExplorer 中的搜索结果

哥白尼开放获取中心

我要向你展示的下一个数据资源是哥白尼开放获取中心。你可以通过仪表板中的设置来访问它,类似于你访问 EarthExplorer 的方式。虽然哥白尼的界面可能不如 EarthExplorer 直观,但它提供了一些很棒的数据。尝试搜索 Sentinel 卫星数据(图 9-13)。

哥白尼开放获取中心 Sentinel 数据

图 9-13. 哥白尼开放获取中心 Sentinel 数据

谷歌地球引擎

你在第八章中学习了 GEE,所以这里我只简要提一下如何与 GDAL 结合使用它。GEE 数据以全球范围在云端提供,因此无需下载。你甚至可以使用诸如裁剪卫星数据之类的函数。

运行搜索 GEE DEM 文件。找到 Earth Engine 代码片段 ee.Image(“USGS/sDEP/10m”),如 图 9-14 所示。您可以复制 JavaScript 代码并粘贴到 GEE 控制台中。

GEE DEM 文件目录

图 9-14. GEE DEM 文件目录

在 GEE 控制台中,选择运行。这将生成图中显示的地图(见 图 9-15)。您可以选择一个多边形并创建几何导入。图层面板允许您更改图层的不透明度或切换图层的显示和隐藏。只需将文件保存为 GeoTIFF,您就有了另一个 DEM 文件选项。

Google Earth Engine DEM .tif 文件

图 9-15. Google Earth Engine DEM .tif 文件

GDAL 可能很复杂,但学习如何使用这个资源库来扩展您的地理空间技能是值得的。

总结

您已经了解了如何在终端和集成开发环境中工作,这两者都根据个人喜好具有各自的用途和通常的可访问性。这些技能突显了这两个选项的效用,以及跨平台应用程序中企业(Esri)和开源社区依赖的强大界面。

在处理地理空间数据和广泛的可用工具时,特别是在开源社区中,重要的是阅读用户文档,以提升您的技能和与数据交互的能力。不要犹豫,如果有问题或见解,请向社区寻求帮助。

^(1) 您可能还记得 绑定 是连接两种编程语言的库,使得一个语言编写的库可以在另一种语言中使用。

^(2) 相对文件路径 是相对于您当前工作目录的路径;绝对文件路径 是从根目录提供的路径。

第十章:使用 Python 测量气候数据

开发技术技能和学习 Python 和地理空间分析的途径很重要,但除非提供上下文或创建共享叙述,否则这只是架上的数据。

在本章中,您将探索三种探索时间序列数据的方法,通过访问来自Landsat中巴地球资源卫星(CBERS)哨兵的卫星图像层。您将利用您的地理空间分析技能来研究关于气候变化和森林砍伐的问题。

空间建模是预测、预测和监测全球温度增加和森林砍伐实时状态的关键工具,从而帮助我们预测这些现象可能带来的后果,并可能干预或为其做好准备。

展示了三个示例,以突出一些强大的 Python 包:Xarray、Web 时间序列服务(WTSS)和森林危险(FAR)。虽然这些工具可能看起来是新的,但在早期章节中已经介绍了它们的许多依赖项。最后一个示例深入探讨了用于预测建模的包的统计能力,这将在分析森林砍伐时使用。您可以在附带的笔记本中运行代码,因为本书的范围无法完全解释其中的所有内容。

示例 1:使用降水数据检查气候预测

空间分析通常依赖于多维数据分析。将网格化数据集想象为一个立方体。在 Python(以及计算机编程一般情况下),数组存储数据列表。列表中的对象可以单独或集体引用。这在调用 Python 数组时很重要,因为可以通过索引号访问每个项目。

NumPy 的多维和 N 维数组,或称为tensor,显示在 NumPy 的 ndarrays 中。将张量视为数据或信息的容器。在 Python 中,NumPy提供了用于处理原始 ndarrays 的基本数据结构和 API。

您将使用真实世界的数据集,这些数据集编码了有关数组值如何映射到位置的信息。您处理的数据带有时间戳、坐标、海拔、土地覆盖和降水等编码信息。

目标

美国政府的国家海洋和大气管理局(NOAA)的使命是“预测气候、天气、海洋和海岸的变化”,并通知和应对极端天气事件带来的紧急社会和环境影响。在这个练习中,您将使用公开可用的数据集来分析每日降水量。比较 2015 年和 2021 年美国大陆的数据,您将观察数据中的模式,以确定是否存在明显的可观察差异。

首先,我将向您介绍 Xarray,这是一个与 NumPy、SciPy 和 matplotlib 互操作的开源项目,扩展了 NumPy ndarrays 和 pandas 数据帧之外的功能。

与前几章一样,在您完成对一个包及其支持文档的介绍后,我强烈建议您尝试不同数据集和 Python 包和库的应用。

下载您的数据

首先,导航到 网格化气候数据集:降水 并选择 Climate Prediction Center (CPC) 的 全球降水数据集。返回给定经度/纬度的天气数据,与请求的经度/纬度对齐的网格单元。

下载感兴趣的年份,2015 年和 2021 年,并将文件上传到您的 Google 云硬盘或直接上传到您的计算机。图 10-1 显示了文件夹和文件层次结构。

Google Colab 中的文件

图 10-1. Google Colab 中的文件

在 Xarray 中工作

Xarray 是一个 Python 库,托管了许多您现在应该熟悉的依赖项,如 NumPy 和 pandas,以及一些您需要处理 CPC 全球降水数据集的可选依赖项。最初由 Climate Corporation 开发,Xarray 已成为用于分析气候变化数据文件以进行绘图和分析的有用开源资源。它对于探索天气、水文和气候极端及其影响非常有用。

美国国家海洋和大气管理局物理科学实验室依赖于 Xarray 的 Network Common Data Form(netCDF)格式,这是一个基于 NumPy 数组的数组导向数据接口,包含维度、变量和属性。这些维度通常是时间和纬度或经度,因此该格式直接适用于空间观测和分析。

您将下载每日降水数据并对其应用函数,包括 groupby(用于分组)、concat(用于合并文件,也称为串联)和 sel & isel(用于选择特定日期或特定位置的数据)。此外,您还将学习如何处理闰年。您将将所需输出保存为 netCDF 文件。

网格化数据将点数据(如来自个别气象站的数据)与其他数据源结合起来,保持空间和时间上的一致性方法,以考虑因位置或海拔引起的温度变化和降水等因素。天气数据来自包含超过 2,500 个月度网格化数据点的数据分布,代表了整个地球。

Xarray 的数据结构是 N 维 的,这意味着它们具有可变数量的维度。这使得 Xarray 适合处理多维科学数据。由于它使用维度名称而不是轴标签(dim='time' 而不是轴=0),这些数组比 NumPy ndarrays 更易管理。使用 Xarray,您无需跟踪维度的顺序或插入大小为 1 的占位维度来对齐数组。

您将使用几个可用函数来分析地理空间数据。运行 open_mfdataset 可以一次打开多个文件。您将使用这个功能来比较不同的时间点。您将在 Google Colab 中运行这些示例。我总是连接到我的谷歌云端硬盘,因为我将大部分数据集存储在云上,而不是本地计算机上。将您的驱动器连接到 Colab 并选择您托管数据集的目录非常简单:

from google.colab import drive
drive.mount('/content/drive')
提示

不是每个人都有谷歌云端硬盘。您也可以通过将数据简单上传到谷歌 Colab 来访问数据。我实际上升级到了谷歌 Colab Pro 以提高运行时性能。通常连接到您的谷歌云端硬盘是最佳选择,但效果可能因人而异。

您将需要通过选择和批准来建立连接。挂载驱动器会花费一些时间,但之后文件将显示在您的可用文件中。

谷歌 Colab 预装了 Xarray 的版本,但我建议使用 pip install 确保所有依赖项都包含在内:

!pip install xarray[complete]

我习惯于单独运行安装,但捆绑导入功能;这样可以隔离并解决任何错误。

让我们来看看您将要导入的较不熟悉的模块。glob 模块(全局模块的简称)返回您指定的文件或文件夹。这也适用于目录/文件内的路径和子目录/子文件。您应该认识 matplotlib 作为我们的绘图库,基于 NumPy 构建,以及 urllib.request 作为用于检索 URL 的模块。您可以下载数据以上传到 Colab,或者如果您的数据以 URL 形式可用,则将其导入到您的代码中:

import glob
import matplotlib.pyplot as plt
**import** urllib.request
import xarray as xr

结合您的 2015 和 2021 年数据集

Xarray 数据集是带有标签数组的容器。它类似于数据框架,但是它是多维的,并与 netCDF 数据集表示相对应,如 图 10-2 所示。

NetCDF 数据数组

图 10-2. NetCDF 数据数组

输入代码以创建您的两个变量,ds2015 用于 2015 年和 ds2021 用于 2021 年:

ds2015 = xr.open_dataset('/content/precip.V1.0.2015.nc')

ds2021 = xr.open_dataset('/content/precip.V1.0.2021.nc')

图 10-3 显示了 Xarray 数据集的关键属性,包括维度、坐标、数据变量和属性。

Xarray 数据集的属性

图 10-3. Xarray 数据集的属性

单击数据库图标(看起来像堆叠的光盘)以查看更多详细信息。您将看到单元格展开,如 图 10-4 所示。扩展的元数据显示了数组和其他信息。

接下来,您将会沿 time 维度进行 连接 (合并)Xarray 对象。

ds2015_2021 = xr.concat([ds2015,ds2021], dim='time')
ds2015_2021

扩展数据集详细信息

图 10-4. 扩展数据集详细信息

输出(图 10-5)将确认数据集已被合并。时间坐标现在列出了您选择的两个年份。

时间坐标串联

图 10-5. 时间坐标串联

函数 dataarray.groupby 返回一个用于分组操作的对象。您将按月份 (time.month 在代码中) 进行分组操作。有一个跨越几年的每日数据集的 dataarray 对象。该对象有一个变量和三个维度:纬度、经度和时间(每日)。您可以为 2015 年和 2021 年生成一个图形(图 10-6)。

2015 年美国大陆月降水量(左)和 2021 年(右)

图 10-6. 2015 年美国大陆月降水量(左)和 2021 年(右)

生成图像

要生成 2015 年图像的绘图,请在第一个项(0)处指定下限,使用默认的上限和步长。月降水量是一个要切片的对象,如 mon.precip 所示。我们正在跨三个维度进行操作:

ds2015_mon = ds2015.groupby('time.month').sum()
ds2015_mon.precip[0,:,:].plot(cmap='jet', vmax=300)

您可以使用相同的方法生成 2021 年的地图。两者的输出在 图 10-6 中,可以看到不同的降水模式。

让我们扩展到单个月份以比较不同年份。Python 有一个日历模块,用于显示整个年份进行比较。使用 matplotlib 和 calendar 模块迭代每个月份的代码如下:

import calendar

现在,您将沿 time 维度应用总和。您正在创建的 landmask 将“掩盖”某些观测而不是返回 NaN 值:

landmask = ds2015.precip.sum(dim='time')>0

Matplotlib 允许您格式化和绘制一系列地图。这些参数有默认值,但当您想要自定义它们时,可以为图像提供宽度和高度(figsize),以及背景颜色(facecolor)。参数 subplots_adjust 允许您调整子图或坐标轴的位置。首先我们来处理 2015 年:

fig = plt.figure(figsize=[12,8], facecolor='w')
plt.subplots_adjust(bottom=0.15, top=0.96, left=0.04, right=0.99,
                   wspace=0.2, hspace=0.27)
nrows = 3
ncols = 4
for i in range(1, 13):
#the python data index starts at 0, but the subplot starts at 1\.  
plt.subplot(nrows, ncols, i)
   dataplot = ds2015_mon.precip[i-1, :].where(landmask)
   p = plt.pcolormesh(ds2015_mon.lon, ds2015_mon.lat, dataplot,
                  vmax = 400, vmin = 0, cmap = 'nipy_spectral_r',
                  )
   plt.xlim([233,295])
   plt.ylim([25,50])
   plt.title(calendar.month_name[dataplot.month.values], fontsize = 13,
             fontweight = 'bold', color = 'b')
   plt.xticks(fontsize = 11)
   plt.yticks(fontsize = 11)
   if i % ncols == 1: # Add ylabel for the very left subplots
       plt.ylabel('Latitude', fontsize = 11, fontweight = 'bold')
   if i > ncols*(nrows-1): # Add xlabel for the bottom row subplots
       plt.xlabel('Longitude', fontsize = 11, fontweight = 'bold')

# Add a colorbar at the bottom:
cax = fig.add_axes([0.25, 0.06, 0.5, 0.018])
cb = plt.colorbar(cax=cax, orientation='horizontal', extend = 'max',)
cb.ax.tick_params(labelsize=11)
cb.set_label(label='Precipitation (mm)', color = 'k', size=14)

# Now we can save a high resolution (300dpi) version of the figure:
plt.savefig('Fig_prec_cpc_mon_2015.png', format = 'png', dpi = 300)

尝试不同的值以查看图像如何变化。通过调整宽度空间 (wspace) 或高度空间 (hspace) 来微调子图的边缘。

您可以在 matplotlib 使用指南 中探索不同的绘图类型。输出显示在 图 10-7 中。

2015 年美国大陆按月年降水量,Xarray

图 10-7. 2015 年美国大陆每月的年降水量,使用 Xarray

接下来,更新代码单元以运行 2021 年的数据(图 10-8),并直观地比较两年间降水量的差异。

2021 年的年降水量

图 10-8. 2021 年的年降水量

更多探索

如果你想看到图像下的数据,还有其他查询可以尝试。例如,ds2021.precip.dims 将列出数据的维度,ds2021.precip.attrs 将列出属性,而 ds2021.precip.data 将显示每日降水量。

如果按季节分组降水数据可能会更有意义,所以让我们试一试:

ds2021.groupby("time.season")
DatasetGroupBy, grouped over 'season'
4 groups with labels 'DJF', 'JJA', 'MAM', 'SON'.

季节以包含的月份命名:DJF 表示冬季,JJA 表示夏季,MAM 表示春季,SON 表示秋季。让我们按直觉的方式对它们进行排序:

seasonal_mean = seasonal_mean.reindex(season=["DJF", "MAM", "JJA", "SON"])
seasonal_mean

季节现在按照它们在一年中的顺序显示。可视化降水水平使您能够观察降水如何因季节和位置的不同而变化(图 10-9)。

按季节排列的平均降水量

图 10-9. 按季节排列的平均降水量

降水水平的观察显示数据中的模式。你可能已经注意到温度数据集也以网格格式提供。现在您已经了解了 Xarray 及其如何处理多维数组,尝试探索最高温度、最低温度和平均温度,以扩展您的见解。通过绘制这些数据,您可以学到什么?

示例 2:使用 WTSS 系列探索亚马逊雨林的砍伐和碳排放

亚马逊雨林正在缩小。森林中的道路建设,人类与动物的接触,以及游客与居住在雨林中的土著人之间的接触都导致持续的森林砍伐和森林退化。退化 指的是森林密度下降,虽然未达到砍伐的程度,但通常在 10%到 30%之间,并且土地正在转为替代用途,如木材采伐或农业。当森林覆盖受到干扰时,生物多样性——亚马逊独有的生物和生态系统混合——受到威胁。砍伐也对碳排放产生影响。

Web 时间序列服务(WTSS)是一个处理遥感图像采集的时间序列数据的网络服务。^(1) 你将再次使用一个具有空间(纬度和经度)和时间参考的三维数组。WTSS 是一个 Python 客户端库,因此你可以获取特定位置随时间记录的实际数值列表。

设置

在本练习中,你将探索巴西国家空间研究院(INPE)的研究项目数据,巴西数据立方体(BDC)。这个数据立方体是从 CBERS-4 和 Advanced Wide Field Imager(AWFI)的表面反射数据中得出的时间合成图像,AWFI 是一种捕捉高分辨率陆地和植被图像的仪器。

获取数据

首先在网站上注册。你需要创建一个个人资料,然后在菜单中生成并保存访问令牌,如图 10-10 所示。

BDC 访问令牌

图 10-10. BDC 访问令牌

BDC 项目收集了土地覆盖的持续数据(例如图 10-11 中的示例)。^(2) 为了识别感兴趣的区域,选择 CBERS-4-AWFI 立方体堆栈中的 CBB4_64_16D_STK-1 集合。信息图标(“i”)将提供背景信息。

巴西数据立方体

图 10-11. 巴西数据立方体

通过计算你在第六章学到的 NDVI 来调查亚马逊植被密度是否随时间变化。回想一下,NDVI 计算近红外(被植被反射)和红光(被植被吸收)之间的差异。这些波段突出了有植被的区域,使你能观察到植被生长的密度。你将选择亚马逊雨林内一个植被密集区域的位置。输入坐标:纬度 = –16.350000,经度 = –56.666668。

小贴士

在完成本练习后,你可以自行探索 BDC。尝试输入你自己选择的纬度和经度坐标。作为另一种选择,你还可以使用边界框(bbox)信息来定位感兴趣的区域。

创建你的环境

打开一个 Jupyter Notebook 并使用pip install安装库。(我知道,我知道 —— 通常我推荐使用 Conda 安装,但 WTSS 只稳定支持 pip。我们经常需要做出调整!)

!pip install wtss
from wtss import WTSS

要访问 WTSS,请使用以下代码中的链接和创建账户时生成的 BDC 访问令牌:

service = WTSS('https://brazildatacube.dpi.inpe.br/', 
    access_token='Your access token')

接下来运行:

service.coverages

这将输出链接中可用的服务列表:

['LANDSAT-MOZ_30_1M_STK-1',
 'MOD13Q1-6',
 'LC8_30_6M_MEDSTK-1',
 'MYD13Q1-6',
 'S2-SEN2COR_10_16D_STK-1',
 'CB4_64_16D_STK-1',
 'LC8_30_16D_STK-1',
 'CB4MUX_20_1M_STK-1']

创建你的地图

选择 CBERS 覆盖在代码单元格中:

cbers4_coverage = service['CB4_64_16D_STK-1']
cbers4_coverage

输出(图 10-12)提供了 CB4_64 立方体的属性。

CB4_64 带有描述的数据立方体

图 10-12. CB4_64 带有描述的数据立方体

注意,当你分配变量时,要使用表中的名称完全如其所示。我们对近红外和红波段感兴趣,所以让我们为它们分配变量:

red_band = 'BAND15'
nir_band = 'BAND16'

使用ts方法检索所列位置和日期的时间序列数据:

time_series = cbers4_coverage.ts(attributes=(red_band, nir_band),
                                 latitude=-16.350000,
                                 longitude= -56.666668 ,
                                 start_date="2016-01-01",
                                 end_date="2022-03-21")

使用plot方法绘制时间序列的静态可视化图:

time_series.plot()

输出显示在图 10-13 中。

CB4_64_16D_STK-1 数据立方体时间序列的静态可视化

图 10-13. CB4_64_16D_STK-1 数据立方体时间序列的静态可视化

分析

如您在图 10-13 中所见,亚马逊雨林此区域的 NDVI 在 2016 年至 2022 年间呈上升趋势。这可能表明大气二氧化碳(CO[2])水平增加,尽管降水和温度更重要,用于解释年际 NDVI 变异性。确实,生态系统模型表明,由于二氧化碳施肥效应,表现为近几十年来植物生长的季节振幅增加,大气 CO[2]浓度增加的观察主要归因于 CO[2]施肥效应,而气候和土地利用变化则起次要作用。基于这些数据,我们无法做出任何明确的声明,但捕获地表反射率并观测 NDVI 有助于提出假设。^(3)

优化

让我们看看改进地图的几种方法。

使您的地图交互式

接下来,请尝试使用 pandas 数据框创建交互式展示您的发现:

cbers_df = pd.DataFrame({ 'BAND15': time_series.BAND15, 
'BAND16': time_series.BAND16 }, 
                        index = pd.to_datetime(time_series.timeline))
cbers_df

您可以在笔记本中悬停以观看信息显示(图 10-14)。选择不同的波段和不同年份以获取更多信息。

CBERS-4 交互图形

图 10-14. CBERS-4 交互图形

px.line()调用 plotly 函数。dataframe参数允许您绘制数据。x参数指定x轴的变量,y参数做同样的事情。标题也有详细描述:

fig = px.line(cbers_df, x=cbers_df.index, y=['BAND15', 'BAND16'], 
title='CBERS-4/AWFI (BAND 15 and 16)', labels={
    'index': 'Date',
    'value': 'Spectral Reflectance (scaled)'
})

fig.update_xaxes(rangeslider_visible=True)
fig.show()

使用遮罩减少云层覆盖

时间序列观测的卫星图像通常显示出由云层引起的噪音或扭曲,这会改变被分析地区的光谱行为。云层及其阴影可能降低反射率。

插值是替换被云层扭曲的像素值或点的一种方式。插值过程在已知数据点的离散值范围内构建新的数据点。数据立方体包含掩码,逐像素捕捉云层的影响。

小贴士

Jupyter Notebook 包含使用 BDC 进行遮罩和插值的额外练习。

遥感技术依赖于云层遮罩来处理数据图像,并提高其质量。通过分析遮罩中的云百分比,云层掩码可以让用户识别有云和无云的像素。

此部分中的代码将首先检测云层,然后获取一个遮罩以“隐藏”任何低质量数据,以防止它影响结果。

运行代码时,它将输出元数据,其中包含三个值中的一个:

  • 值为0表示像素不包含数据。

  • 值为127表示像素清晰,没有云层。

  • 值为255表示像素被云层遮挡。

让我们看一个稍微不同的位置:

cb4_timeseries_cmask = cbers4_coverage.ts(

    attributes = ('CMASK'),

    latitude = -12.0,
    longitude = -53.989,

    start_date = "2017-01-01",
    end_date = "2021-12-31"
)

cb4_timeseries_cmask

这将给出输出:

CMASK: [127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 
127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 
127.0, 127.0, 255.0, 127.0, 127.0, 127.0, 255.0, 127.0, 127.0, 127.0, 127.0, 
127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 
127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 
127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 
127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 255.0, 127.0, 127.0, 127.0, 
127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 
127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0, 255.0]

你可以看到大多数像素是清晰的,但少数云观测记录值为255.0set 函数也显示了这些值:

set(cb4_timeseries_cmask.values('CMASK'))

输出是 {127.0, 255.0}

以下代码将显示所选时间范围内的云观测:

cb4_timeseries = cbers4_coverage.ts(

    attributes = ('NDVI', 'CMASK'),

    latitude = -12.0,
    longitude = -53.989,

    start_date = "2017-01-01",
    end_date = "2019-12-31"

)

时间线数据存储为日期列表,但你需要将其转换为 datetime 对象。为此,请运行以下代码:

ndvi_timeline = pd.to_datetime(cb4_timeseries.timeline)
ndvi_timeline

现在,你将把转换后的数据存储在变量 ndvi_datacmask_data 中:

import numpy as np

ndvi_data = np.array(cb4_timeseries.NDVI)
ndvi_data

cmask_data = np.array(cb4_timeseries.CMASK)
cmask_data

下一步是去除云层。 NumPy 数组(np)将所有127值(无云)转换为1255(多云)的值现在显示为 NaN(非数值):

cmask_data = np.where(cmask_data == 255, np.nan, 1)
cmask_data

现在将 NaN 值乘以 NDVI 数组以插值时间序列数据:

ndvi_data * cmask_data

这为 NaN 值创建了一个新的数据框:

ndvi_masked_data = pd.DataFrame({ 'data': ndvi_data * cmask_data }, 
index = pd.to_datetime(ndvi_timeline))
ndvi_masked_data[ndvi_masked_data['data'].isna()]

确实,当你运行代码时,它们会出现在输出中(Table 10-1)。

表 10-1. 插值云掩模数据中的 NaN 值

2018-01-01 NaN
2018-03-06 NaN
2020-02-18 NaN
2021-03-06 NaN

现在我们可以提取所有 NaN 数据并查看插值数据:

ndvi_masked_data_interpolated = ndvi_masked_data.interpolate()
ndvi_masked_data_interpolated[ndvi_masked_data_interpolated['data'].isna()]

让我们可视化插值数据!运行这段代码:

plt.figure(dpi = 120)

plt.plot(ndvi_data, color='gray', linestyle='dashed', label = 'Original')
plt.plot(ndvi_masked_data_interpolated['data'].values, color='blue', 
label = 'Interpolated')

plt.title('Comparison of Time Series with and without interpolation')
plt.legend()
plt.grid(True)
plt.show()

你的输出应该类似于 Figure 10-15。去除云观测可以清晰地显示像素和表面反射率的无遮挡值,更准确地描绘植被密度。

插值后时间序列数据的比较

图 10-15. 插值后时间序列数据的比较

这个练习向你展示了如何使用 WTSS 访问和分析远程感知数据,使用 Python 和 API,并向你介绍了 BDC 资源。你也看到,数据分析不仅仅是支持你的论点;事实上,使用这些工具探索数据可以成为 生成 假设过程的重要组成部分,并可能引导你的调查走向新的和意想不到的地方。

示例 3:使用 Forest at Risk 对瓜德罗普岛进行砍伐建模和预测

除了建模森林破坏的空间概率的益处外,本节还是建模大型地理参考栅格文件的一个很好的例子。我们将使用一个分析森林破坏的 Python 包:Forest at Risk (FAR)。这个包中的函数处理大规模地理尺度上模拟的数据块,并以高分辨率展示。

研究区域是瓜德罗普岛,这是法国的一个群岛和海外省份,位于东加勒比海。根据全球森林观察,仅在 2021 年,瓜德罗普的湿润原始森林就失去了超过 240 英亩。树木覆盖损失导致约 1.07 公吨(2358.95 磅)CO[2]的排放。

使用 FAR 的目标是对瓜德罗普的森林破坏进行空间建模,基于一组变量预测森林破坏的风险,并预测未来的森林密度。您将使用的空间变量包括地形(海拔、坡度和坡向)、可访问性(距离道路、城镇和森林边缘的距离)、以及距离先前的砍伐区域或受保护区的距离。

具有.predict()方法的任何统计模型类都可以与函数forestatrisk.predict_raster()一起用于预测森林破坏的空间风险。使用 NumPy 和 GDAL 在数组上执行计算可以提高效率,而无需高计算能力。您将使用这些数据定义地理区域,并测量森林砍伐、森林退化和森林再生。让我们看看是否可以依赖于地理空间工具来可视化表面反射变化和底层树木覆盖的变化。

我们将使用欧洲委员会联合研究中心的国际森林资源和碳排放(IFORCE)数据,该中心负责提供独立的科学技术支持。该数据测量了热带湿润森林(TMF)中的 Landsat 时间序列森林覆盖变化。它收集了 30 年来从赤道向远离赤道的季节性雨林的数据,从 1990 年到 2020 年。

设置

尽管在此示例中我使用 Google Colab 工作,但在可复制性方面存在挑战,我发现创建 Conda 环境是一个更稳定的选择。图 10-16 中的仪表板显示的输出文件不会自动显示在您的笔记本中。您可以从 Colab 的文件层次结构或您的 Jupyter Notebook 文件结构中选择它们。

在 Google Colab Pro 中打开 FAR 数据集

图 10-16. 在 Google Colab Pro 中打开 FAR 数据集

根据您的个人设置,Google Colab 通常运行良好。我默认使用 Colab 进行指导,因为它在突出显示您的 Google Drive 内的文件夹结构(或者如果您选择上传文件,则在导入路径中)方面非常简单。有时您可能无法使其正常工作。在这些情况下,我发现在终端中创建环境是解决方案。

创建您的环境

我正在使用 Python 3.10,在本文写作时它是当前版本,但在运行以下代码时,您将输入您自己的版本。一切应该工作正常,但请注意,当您构建环境并调整版本时,细节可能需要稍微调整。(在 Python 旅程中,您已经足够熟悉其中一些选项,但如果需要提醒,请返回前言部分。)

现在您可以使用 Conda 创建您的环境:

conda create --name conda-far -c conda-forge python=3.7 gdal numpy matplotlib 
pandas patsy pip statsmodels earthengine-api --yes
conda activate conda-far
pip install pywdpa sklearn # Packages not available with conda
pip install forestatrisk # For PyPI version
conda install -c conda-forge python-dotenv rclone
提示

如果您收到一个错误消息,指出未识别到 forestatrisk,则可以在终端内的 FAR 环境中创建一个内核:

conda install ipykernel
python -m ipykernel install --user --name myenv 
--display-name "FAR"

输入 **jupyter notebook** 并选择 FAR 内核。现在应该可以运行您的程序包了。

下载和导入包

导入必要的文件和包是第一步。 os 模块允许您与操作系统上的文件夹交互。shutil 和 copy2 包保留您导入的文件的元数据,包括权限。(在 MacOS 上工作时存在一些限制,请参阅Python 文档。)urllib 包收集了几个用于打开和读取 URL 的模块。您还将使用 ZIP 文件,并且 ZipFile 模块提供了必要的工具。

继续导入您的包:

# Imports
import os
from shutil import copy2
import urllib.request
from zipfile import ZipFile

import forestatrisk as far

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from patsy import dmatrices
import pickle
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import log_loss

下载和导入数据

接下来,您将创建一个输出目录,在笔记本环境中特别方便。运行脚本时,生成的输出文件将位于指定位置,包括您生成的图像:

# Make output directory
far.make_dir("output")

ZIP 文件 data_GLP.zip 位于以下代码块中的链接处,z.extractall 将文件解压缩到您的 Colab 或 Jupyter Notebook 目录中,如图 10-13 所示:

# Source of the data
url = "https://github.com/ghislainv/forestatrisk/raw/master/docsrc/notebooks/
data_GLP.zip"

if os.path.exists("data_GLP.zip") is False:
    urllib.request.urlretrieve(url, "data_GLP.zip")

with ZipFile**(**"data_GLP.zip", "r") as z**:**
    z.extractall("data")

.zip 文件包含跨时间跨度的环境变量和地图的选择:这是您在 FAR 教程的简要演练中所需的所有数据,您将在其中对特定地理位置的森林砍伐的空间概率进行建模。

当您下载数据时,请花些时间探索关于 IFORCE 站点的符号学、值和标签的详细信息。

绘制数据

运行以下代码以绘制数据:

# Plot forest
fig_fcc23 = far.plot.fcc(
    input_fcc_raster="data/fcc23.tif",
    maxpixels=1e8,
    output_file="output/fcc23.png",
    borders="data/ctry_PROJ.shp",
    linewidth=0.3, dpi=500)

运行代码单元格时,此代码将生成一个地图作为输出,但不会自动填充到笔记本中。要在 Google Colab 中找到输出文件,请使用仪表板导航到您的输出文件夹的文件层次结构。在 Jupyter Notebook 中,查看位于左上角标签页中的文件列表(图 10-17)。您下载的数据文件也将可见。

图 10-17. 在 Jupyter Notebook 文件结构中定位您的输出

当您绘制数据(图 10-18)时,森林显示为绿色,而砍伐区域显示为红色。您能看到砍伐更可能发生的周边吗?

图 10-18. 在瓜德罗普岛上以 GeoTiff 栅格文件形式绘制的 FAR 数据,展示了森林覆盖变化

数据采样

接下来,是时候对您的数据进行采样了。您将使用far.sample函数。帮助文档可以通过运行help(far.sample)来访问,告诉我们这个函数“在砍伐和保留森林区域中随机绘制空间点…提取每个空间点的环境变量值”。换句话说,它在被标识为已砍伐或仍然是森林的区域中随机抽样 10,000 个像素中心(点)。

让我们花点时间来看看这个函数接受的参数。首先,seed告诉函数从前一步的随机抽样数据中复制数据。接下来,每个样本的空间单元由csize参数分组。每个空间单元都被归属到一个参数,因此它们需要足够大的尺寸以减轻内存和大数据集的负担。这些分组的观测结果是森林砍伐中空间自相关的估算。

记住托布勒的第一定律,介绍见第一章:“一切事物都与其他事物相关,但接近的事物比远处的事物更相关。”空间自相关是衡量相邻观测内的空间变异的指标,在这里,可能会出现附近事物并不相似的情况!相邻观测可以具有相似的值(正空间自相关)或具有对比的值(负自相关)。在空间统计中,测量之间的像素值的存在或缺失具有重要意义。有许多可能的原因,但其中一种可能是,来自更大数据集的小样本简单地不代表更大的样本。

在以下代码单元中,您还将看到参数var_dir。这指向存放所有 GeoTIFF 文件的目录,并且指向fcc23.tif作为森林覆盖变化栅格文件。再次注意输出文件,并留意其出现在您的目录中:

# Sample points
dataset = far.sample(nsamp=10000, adapt=True, seed=1234, csize=10,
                     var_dir="data",
                     input_forest_raster="fcc23.tif",
                     output_file="output/sample.txt",
                     blk_rows=0)

我在这里展示输出,以演示计算的进行方式:

Sample 2x 10000 pixels (deforested vs. forest)
Divide region in 168 blocks
Compute number of deforested and forest pixels per block
100%
Draw blocks at random
Draw pixels at random in blocks
100%
Compute center of pixel coordinates
Compute number of 10 x 10 km spatial cells
... 99 cells (9 x 11)
Identify cell number from XY coordinates
Make virtual raster with variables as raster bands
Extract raster values for selected pixels
100%
Export results to file output/sample.txt

以下代码从数据集中删除“不可用”(NA)值,然后计算每个空间单元的邻居数量。它输出一个 pandas 数据框:

# Remove NA from dataset 
dataset = dataset.dropna**(**axis=0**)**
# Set number of trials to one for far.model_binomial_iCAR()
dataset**[**"trial"**]** = 1
# Print the first five rows
print**(**dataset.head**(**5**))**

如果您不删除 NA 值,您将无法在接下来的步骤中对数据建模——请记住,逻辑回归模型正在报告测量之间的像素值的存在或不存在。

这里显示的输出是前五行的样本。变量代表用于建模森林砍伐影响的空间解释变量,包括高程和坡度表示的地形;可达性,作为距离最近的道路、城镇、河流和森林边缘的距离;过去砍伐历史,计算为距离过去砍伐的距离;以及作为保护区(pa)的土地保护状态:

altitude  dist_defor  dist_edge  dist_river  dist_road  dist_town  fcc23  \
0      30.0       642.0       30.0      8448.0     1485.0     6364.0    0.0   
1      37.0       765.0       30.0      8583.0     1697.0     6576.0    0.0   
2      78.0       216.0       30.0      7722.0      949.0     5743.0    0.0   
3      80.0       277.0       30.0      8168.0     1172.0     6047.0    0.0   
4      46.0        30.0       30.0      6179.0      541.0     6690.0    0.0   

    pa  slope          X          Y               cell  trial  
0  0.0    8.0 -6842295.0  1851975.0   4.0      1  
1  0.0    7.0 -6842235.0  1852095.0   4.0      1  
2  0.0    5.0 -6842535.0  1851195.0   4.0      1  
3  0.0    2.0 -6842445.0  1851615.0   4.0      1  
4  0.0    1.0 -6840465.0  1849755.0   4.0      1 

森林覆盖变化地图的样本大小计算——当森林存在时,像素值设置为1,当没有森林时设置为0

# Sample size
ndefor = sum(dataset.fcc23 == 0)
nfor = sum(dataset.fcc23 == 1)
with open("output/sample_size.csv", "w") as f:
    f.write("var, n\n")
    f.write("ndefor, " + str(ndefor) + "\n")
    f.write("nfor, " + str(nfor) + "\n")
print("ndefor = {}, nfor = {}".format(ndefor, nfor))

相关性图

下一步是相关所有这些数据。这意味着实际上比较森林区域的位置与其他变量,如道路、河流、城镇和其他已砍伐的区域,以绘制问题点可能遭受砍伐的可能性有多大。

运行代码以进行相关性分析,并绘制结果:

# Correlation formula formula_corr = "fcc23 ~ dist_road + dist_town + dist_river + \
dist_defor + dist_edge + altitude + slope - 1"  
# Output file of = "output/correlation.pdf"
# Data y, data = dmatrices(formula_corr, data=dataset,
                    return_type="dataframe")
# Plots figs = far.plot.correlation(
    y=y, data=data,
    plots_per_page=3,
    figsize=(7, 8),
    dpi=80,
    output_file=of)

这将输出在图 10-19 中显示的相关性图

通过首先查看数据分布可以读取砍伐的概率。注意概率曲线的形状。查看一个像素离砍伐状态有多远。在图 10-16 中,您可以看到样本中的大多数像素与已砍伐区域相对接近—正如您所知,随着距离的增加,砍伐的概率会下降。

查看图 10-19 中的参数估计,您可以看到砍伐的概率随着海拔、坡度、距离以前砍伐的距离和森林边缘而减少。道路、城镇和河流的距离都“穿过零”,意味着这些值与零没有显著差异。置信区间范围包括零。

图 10-19:选定变量的相关性图

图 10-19:选定变量的相关性图

使用 iCAR 模型建模砍伐概率

你不需要理解底层统计学就能呈现输出。统计模型model_binomial_iCAR通过检查每个像素并评估一组环境变量(如海拔、距离森林边缘的距离、距离道路的距离和距离最近的城镇)来估计森林砍伐的概率。这些变量告诉我们森林对人类的可访问性。简单来说,我们需要这些数据是因为固定的环境变量并不能完全解释砍伐过程,至少在像国家或地理区域这样的大尺度上是如此。

在任意给定点,砍伐的概率取决于其相邻单元的砍伐概率。最近邻方法寻找接近给定点的训练样本,以指示相邻实体的数量。简而言之,这是观察数据模式并使用它们来预测新观察结果。

本示例中使用的许多方法背后的统计学,如马尔可夫链蒙特卡罗(MCMC)方法,涉及超出本书重点的高级概念,但如果您有兴趣了解更多,请参阅附录。

对于一些了解统计学的人来说,简而言之,内在条件自回归(iCAR)模型允许估计砍伐概率的变化,而不考虑不可测性的性质。例如,不可能测量每棵树及其对树冠的影响。我们使用二元结果的 Logistic 回归模型:如果森林像素被砍伐(像素较少),则为 1,否则为 0。

如果您对模型准备或变量选择模型感兴趣,请在笔记本中运行代码。深入的统计学内容超出本书的范围,但理解过程非常有趣。^(4)

让我们看看模型总结:

Binomial logistic regression with iCAR process
  Model: I(1 - fcc23) + trial ~ 1 + scale(altitude) + scale(slope) 
  + scale(dist_defor) + scale(dist_edge) + scale(dist_road) + scale(dist_town) 
  + scale(dist_river) + cell
  Posteriors:
                        Mean        Std     CI_low    CI_high
        Intercept      -3.84      0.224      -4.23      -3.27
  scale(altitude)       -0.5      0.105     -0.679     -0.293
     scale(slope)    -0.0159     0.0545     -0.117     0.0906
scale(dist_defor)      -2.06      0.274      -2.51      -1.51
 scale(dist_edge)      -6.89       0.44      -7.78       -6.2
 scale(dist_road)    -0.0408     0.0573     -0.159     0.0702
 scale(dist_town)    -0.0916     0.0444     -0.175     0.0032
scale(dist_river)    -0.0122     0.0347    -0.0838     0.0607
             Vrho       3.12      0.852       1.83       5.07
         Deviance   1.52e+04         48   1.52e+04   1.54e+04

您观察到的是,当系数为负时,该变量对您测量的影响是负面的。例如,当距离道路的距离较小时,砍伐的可能性较高。

MCMC 距离矩阵

首先,我们对数据进行抽样,然后准备、运行和测试模型。模型总结在图 10-20 中显示。许多方法背后的统计学涵盖了本书关注之外的高级概念,但在“21 世纪热带砍伐和碳排放的空间情景”一文中由 Ghislain Vieilledent 等人进行了强调。简言之,MCMC 方法用于贝叶斯推断,基于生成随机采样序列的技术来近似概率分布。

简而言之,MCMC 工具验证算法生成的样本是否足以作为整体分布的近似。你的样本大小足够大吗?简化关于跟踪和后验的讨论,MCMC 从后验分布中抽取样本,这些样本总结了不确定性,你可以根据新信息进行更新。简言之,一旦你看到数据,你可能会更新你的知识。

主要目的是确保样本的有效大小不会太小,并且样本与目标人群相似。比较跟踪与样本直方图形状与图 10-20 中的正态分布。你注意到任何潜在的偏斜吗?

MCMC 跟踪

图 10-20. MCMC 跟踪

使用predict_raster_binomial_iCAR模型建模森林砍伐概率。

使用predict_raster_binomial_iCAR,您可以通过在大地理区块上逐块计算来预测来自model_binomial_iCAR模型的砍伐空间概率。选择仅 10 行应有助于限制内存使用。

在笔记本中运行代码,让我们在这里看生成的图形。

图 10-21 中显示的输出将砍伐区域显示为白色,森林区域显示为黑色。

概率 TIFF

图 10-21. 概率 TIFF

该模型预测,2010 年至 2020 年间的年度砍伐面积为 498.375 公顷。作为比较,这相当于超过 11,000 个 NBA 篮球场或 930 个足球场(包括端区)。本笔记本包含了分析未来森林覆盖变化预测的代码。图 10-22 显示了 2000 年至 2020 年间历史森林覆盖变化。

数据建模和生成概率以及预测是开源社区在监测我们的雨林受到威胁及其对生态系统和气候的有害影响时强大的工具。运行代码,尝试将数据模型推进到 2050 年和 2100 年:

# Projected forest cover change (2020-2050)
fcc_2050 = far.plot.fcc("output/fcc_2050.tif",
                        maxpixels=1e8,
                        borders="data/ctry_PROJ.shp",
                        linewidth=0.2,
                        output_file="output/fcc_2050.png",
                        figsize=(5, 4), dpi=800)

2000、2010 和 2020 年森林覆盖变化

图 10-22. 2000、2010 和 2020 年的森林覆盖变化

结果显示在图 10-23 中:

// Palette for fcc_2050 and fcc_2100
var pal_fcc_proj = [
rgb(227, 26, 28), // 0\. Deforestation, red
rgb(34, 139, 34), // 1\. Remaining forest, green
];

2050 年未来的森林覆盖

图 10-23. 未来的森林覆盖,2050 年

持续的森林砍伐影响可以通过跟踪碳排放来监测。

碳排放

当前和未来砍伐的碳排放可以从地上生物量(AGB.tif)文件和函数.emissions()中计算出来:

# Create dataframe
dpast = ["2020"]
dpast.extend(dates_fut)
C_df = pd.DataFrame({"date": dpast, "C": np.repeat(-99, ndates_fut + 1)},
                    columns=["date","C"])
# Loop on date
for i in range(ndates_fut):
    carbon = far.emissions(input_stocks="data/emissions/AGB.tif",
                           input_forest="output/fcc_" + dates_fut[i] + ".tif")
    C_df.loc[C_df["date"]==dates_fut[i], ["C"]] = carbon
# Past emissions
carbon = far.emissions(input_stocks="data/emissions/AGB.tif",
                       input_forest="data/fcc23.tif")
C_df.loc[C_df["date"]==dpast[0], ["C"]] = carbon
# Save dataframe
C_df.to_csv("output/C_emissions.csv", header=True, index=False)

若要查看砍伐率估算的数据框:

print(C_df)

预计砍伐率的持续变化速率,年碳排放将继续增加,如代码单元格输出所示:

    date  C
0   2020   85954
1   2030   99463
2   2035  152047
3   2040  202883
4   2050  290920
5   2055  335097
6   2060  382887
7   2070  481913
8   2080  587963
9   2085  645071
10  2090  705914
11  2100  844066

模型预测,在该地区若没有强有力的保护或保育措施,碳排放将继续迅速增加。

分析

本节向您展示了一种使用 Python 和地理空间分析技能在国家级别建模和预测热带森林砍伐的方法。该方法使我们能够估计几种不同环境因素对砍伐概率的单独影响。

研究结果易于解释:随着森林土地的更易接近,砍伐变得更有可能。在受保护区内,砍伐的可能性较低。我们还表明,在砍伐过程中存在很大的空间变异性,我们需要考虑这一点,以便能够在国家尺度上以现实方式预测砍伐。

总结

本章向您展示了可以使用的可用包,以探索和分析使用位置智能和地理空间工具的气候变化及其潜在风险。

在本书中,您已经介绍了地理空间分析和重要的 Python 库、包和工具。在开始阶段,介绍了像 pandas 和 GeoPandas 这样的入门包,使您能够处理数据框架和地理数据框架。随着更复杂包的出现,像 NumPy、matplotlib 和 Plotly 等工具成为了重要的依赖项。

  • 我希望这段长途旅程对你的时间是值得的,也希望你会继续探索这些功能、工具、软件包等等。数据科学的工作是迭代的。你通过实践获得信心——我敢说,也是通过应对途中的波折。

  • 对你的旅程表示祝贺。

^(1) 欲了解更多信息,我推荐这篇论文,介绍了 WTSS 如何从巴西雨林的遥感数据中创建时间序列:Vinhas, L., Queiroz, G. R., Ferreira, K. R., 和 Camara, G. 2017. “Web Services for Big Earth Observation Data.” Revista Brasileira de Cartografia 69: 5. 英文翻译.

^(2) Earth Observation Data Cubes for Brazil: Requirements, Methodology and Products, Earth Observation and Geoinformatics Division, National Institute for Space Research (INPE), Avenida dos Astronautas, 1758, Jardim da Granja, Sao Jose dos Campos, SP 12227-010, Brazil.

^(3) Ito, Akihiko. 2019. “Disequilibrium of Terrestrial Ecosystem CO[2] Budget Caused by Disturbance-Induced Emissions and Non-CO[2] Carbon Export Flows: A Global Model Assessment.” Earth System Dynamics 10: 685–709. https://doi.org/10.5194/esd-10-685-2019.

^(4) 有关此处所使用的统计方法的深入探讨,请参阅:Vieilledent, Ghislain, Vancutsem, Christelle, Bourgoin, Clément, Ploton, Pierre, Verley, Philippe 和 Achard, Frédéric. 2022. “Spatial Scenario of Tropical Deforestation and Carbon Emissions for the 21st Century.” BioRxiv 预印本。https://www.biorxiv.org/content/biorxiv/early/2022/07/23/2022.03.22.485306.full.pdf

附录. 额外资源

除了本书的 GitHub 存储库之外,以下是您可能也会发现有用的一些库、课程、网站和组织。

Python 地理空间分析库

进一步探索的资源

第十一章:参考文献

  • Bilogur, Aleksey. “Missingno:缺失数据可视化套件。” 开源软件期刊 3,编号 22(2018 年 2 月):547。https://doi.org/10.21105/joss.00547

  • Boeing, Geoff. “OSMnx: 获取、构建、分析和可视化复杂街道网络的新方法。” 计算机、环境和城市系统 65 (2017 年 9 月):126–139。https://doi.org/10.1016/j.compenvurbsys.2017.05.004

  • Chakraborty, T., & Lee, X. “一种简化的城市范围算法,用于表征全球范围内的地表城市热岛并研究植被对其时空变化的控制。” 国际应用地球观测和地球信息学杂志 74 (2019 年 2 月):269–280。https://doi.org/10.1016/j.jag.2018.09.015

  • Goodman, Cooper, Nathanael Rosenheim, Wayne Day, Donghwan Gu, 和 Jayasaree Korukonda. 使用 Jupyter Notebook 中的人口分布工作流程:KY 布恩县 2000 年人口普查区动态地图。2020 年 7 月。政治和社会研究的跨大学联盟。https://doi.org/10.3886/E120382V1

  • Gott, J. Richard III, David M. Goldberg, 和 Robert J. Vanderbei. “改进 Winkel Tripel 投影的平面地图。” 预印本,提交于 2021 年 2 月 15 日。https://doi.org/10.48550/arXiv.2102.08176

  • Graser, Anita. “PyQGIS 101: 链接处理工具。” 免费和开源 GIS Ramblings(博客)。https://anitagraser.com/pyqgis-101-introduction-to-qgis-python-programming-for-non-programmers/pyqgis101-chaining-processing-tools

  • Gray, Jim. “电子科学:一种转变了的科学方法。” 呈现给国家资源委员会计算机科学电信委员会,加利福尼亚州山景城,2007 年 1 月 11 日。http://itre.cis.upenn.edu/myl/JimGrayOnE-Science.pdf

  • Hagberg, Aric A., Daniel A. Schult, 和 Pieter J. Swart. “使用 NetworkX 探索网络结构、动态和功能。” 在第 7 届 Python 在科学会议上展示,加利福尼亚州帕萨迪纳,2008 年 8 月。https://conference.scipy.org/proceedings/SciPy2008/paper_2

  • Mitchell, Bruce, 和 Juan Franco. HOLC “红线” 地图:隔离的持久结构和经济不平等。National Community Reinvestment Coalition。2018 年 3 月 20 日。https://ncrc.org/holc

  • National Research Council. 学会空间思维(华盛顿特区:国家学院出版社,2006 年)。doi.org/10.17226/11019

  • Open Source Geospatial Foundation. GDAL/OGR 地理空间数据抽象软件库. https://doi.org/10.5281/zenodo.5884351.

  • Tobler, W. R. “一部模拟底特律地区城市增长的计算机电影。” Economic Geography 46 (June 1970): 234–240. https://doi.org/10.2307/143141.

  • Vancutsem, C., F. Achard, J.-F. Pekel, G. Vieilledent, S. Carboni, D. Simonetti, J. Gallego, L.E.O.C. Aragão, and R. Nasi. “长期(1990–2019 年)监测热带湿地森林覆盖变化。” Science Advances 7, no. 10 (March 2021): 1–22. https://doi.org/10.1126/sciadv.abe1603.

  • Vieilledent, Ghislain. “forestatrisk: 用于模拟和预测热带地区森林砍伐的 Python 包。” Journal of Open Source Software 6, no. 59 (March 2021): 2975. https://doi.org/10.21105/joss.02975.

  • Wu, Qiusheng. “geemap: 一个用于与 Google Earth Engine 交互地图的 Python 包。” Journal of Open Source Software 5, no. 51 (July 2020): 2305. https://doi.org/10.21105/joss.02305.

  • Wu, Qiusheng, Charles R. Lane, Xuecao Li, Kaiguang Zhao, Yuyu Zhou, Nicolas Clinton, Ben DeVries, Heather E. Golden, and Megan W. Lang. “结合 LiDAR 数据和多时相航空影像利用 Google Earth Engine 绘制湿地入侵动态。” Remote Sensing of Environment 228 (2019): 1-13. https://doi.org/10.1016/j.rse.2019.04.015.

posted @ 2024-06-17 17:11  绝不原创的飞龙  阅读(19)  评论(0编辑  收藏  举报