基于Python的地理空间分析(四):矢量数据分析
引言
本文主要介绍矢量数据的处理和地理空间分析,主要用到Shapely、OGR和GeoPandas三个库,展示如何用这三个库执行基本的地理空间分析,以及更加复杂的数据科学任务,主要内容包括:
- 读写、创建、操作矢量数据
- 矢量数据的地图可视化
- 使用地图投影、执行空间操作
- 矢量几何和属性数据分析
OGR简单特征库
OGR简单特征库(OGR Simple Features Library)是GDAL的一部分,提供了一套处理矢量数据的工具。尽管OGR(矢量部分)和GDA(山各部分)L已经集成在一起,但是两者在使用上还是比较清晰的。虽然OGR是C编写的(文档也是C的),但是Python bindings使得我们可以通过Python访问所有GDAL的功能。接下来,介绍三部分内容:
- OGR批处理命令与Python脚本
- OGR库与Python bindings
OGR批处理命令
OGR提供了一系列的批处理命令,包括我们前面已经用过的ogrinfo和ogr2ogr命令,这些命令支持SQL查询的语法。此外,ogrindex和ogr2vrt可以用来创建矢量数据文件,ogrmerge能够将多个数据文件合并,并且创建指定格式的数据文件。
GDAL在安装的过程中,已经提供了一组用于地理空间分析的Python脚本,这些脚本的路径类似于:C:\ProgramData\Anaconda3\pkgs\gdal-2.4.0-py37hdf5ee75_1002\Scripts
,可以直接从Jupyter Notebook(使用魔法命令%run
)或者终端运行。不过,从这个文件夹中的Python脚本列表中可以看到,几乎所有这些脚本都是针对GDAL而不是OGR。
需要注意的是,如果要运行这些脚本,需要正确引用这些脚本的路径,或者添加到系统变量当中。
OGR库与Python bindings
OGR库与其Python绑定相结合,构成了在Python中处理矢量数据的最重要部分。利用OGR库,我们可以创建点,线和多边形,并对这些元素执行空间操作。 例如,您可以计算几何体的面积,将不同的数据叠加在一起,并使用接近工具(如缓冲区)。 此外,与ogrinfo和ogr2ogr一样,OGR库提供了读取矢量数据文件,迭代单个元素以及选择和重新投影数据的工具。
OGR主要模块和类
OGR主要包括两个模块:ogr
模块主要处理矢量几何,和osr
模块,这两个模块都是osgeo
的子模块。ogr
模块主要处理矢量几何,osr
则是处理投影。OGR库提供了以下七个类:
- Geometry
- Spatial Reference
- Feature
- Feature Class Definition
- Layer
- Dataset
- Drivers
这些库的名字直观地说明了类的功能以及OGR的结构。OGR的模块、类和函数能够在GDAL网站(www.gdal.org/python )上找到文档,但是没有Python的代码示例。好在其他的Python库例如Fiona、GeoPandas提供了更加用户友好的方式。
创建几何多边形
我们可以用OGR编写点、线、多边形等矢量几何,并且可以通过投影给出坐标或者米等单位。并且可以用well-known binary(WKB)进行编码,并将最终的段变形转换为well-known text(WKT):
1 | from osgeo import ogr |
POLYGON ((1 1 0,5 1 0,5 5 0,1 5 0,1 1 0))
从GeoJSON创建多边形
除了直接创建多边形以外,我们还可以通过传入GeoJSON创建,具体代码如下:
1 | geojson = """{"type":"Polygon","coordinates": |
POLYGON ((1 1,5 1,5 5,1 5,1 1))
基本几何操作
下面是一些对多边形的基本操作,包括创建区域,获得中心、边界、凸包、缓冲(buffer),以及判断是否有某个点:
1 | # 1. 创建区域 |
The area of our polygon is 16.0
The centroid of our polygon is POINT (3 3)
The boundary of our polygon is LINESTRING (1 1,5 1,5 5,1 5,1 1)
As our polygon is a square, the convex hull is the same:POLYGON ((1 1,1 5,5 5,5 1,1 1))
Set a buffer of 0 for our polygon:POLYGON ((1 1,1 5,5 5,5 1,1 1)), which is the same.
False
创建shapefile并写入数据
利用OGR还可以将刚才创建的polygon数据写入到shapefile中,需要用到ogr和osr两个库,具体过程如下:
1 | from osgeo import osr |
0
接下来,可以用ogrinfo查看刚才创建的shapefile:
1 | !ogrinfo my_polygon.shp |
INFO: Open of `my_polygon.shp'
using driver `ESRI Shapefile' successful.
1: my_polygon (Polygon)
使用filter选择features
我们还是使用前面用过的Natural Earth数据集,使用经纬度坐标以边界框的形式创建filter,通过选择边界框以内的数据,达到选择数据子集的效果。我们将使用OGR的SpatialFilterRec方法,该方法采用四个值-minx,miny,maxx和maxy来创建边界框。在实例中,选择我们的边界框中的城市(北京周边)。 为了进一步过滤我们的结果,我们只想要中国的城市。 这意味着我们必须在for循环中使用额外的if / else语句过滤搜索结果。 具体代码如下:
1 | import os |
Zhuozhou
Hengshui
Xuanhua
Beipiao
Wafangdian
...
Shapely和Fiona库
之前已经介绍过Shapeply库和Fiona库,之所以要放在一起,是因为Shapely依赖其他库实现文件读写,而Fiona刚好可以胜任。接下来,Fiona读取文件,然后由Shapely进行分析。
Shapely对象和类
Shapely库用于创建和操作2D矢量数据,而无需空间数据库,此外它也不关心投影和数据格式,而只专注于几何形状本身。 Shapely的优势在于它使用易于阅读的语法来创建可用于几何操作的各种几何形状。借助其他Python包,可以将这些几何和几何操作的结果写入矢量文件格式并在必要时进行投影。我们将介绍将pyproj和Fiona与Shapely的功能结合起来的示例。例如,我们可以使用Fiona从shapefile中读取矢量几何图形,然后使用Shapely来简化或清理现有几何图形(类似数据清洗)。清理后的几何图形可用作其他工作流程的输入,例如,用于创建专题图或执行数据科学任务。
Shapely库使用一组类,这些类是三种基本类型的几何对象(点,曲线和曲面)的实现,具体包括:
地理对象 | Shapely类名 |
---|---|
点 | Point |
线 | LineString, Linear Ring |
面 | Polygon |
点集合 | MultiPoint |
线集合 | MultiLineString |
面集合 | MultiPolygon |
Shapely的地理空间分析方法
在Shapley中,拓扑关系(例如包含、相邻等)作为集合对象的方法加以实现,Shapely还提供产生新的几何对象的方法(例如交叉、联合等)的实现。通过使用buffering方法可以使得形状更加清晰。此外,借助WKB和WKT、数组、Bumpy以及Python Geo接口,Shapely实现了与其他软件的互操作。
Fiona数据模型
尽管Fiona是OGR是Python的封装,但FIona与OGR的数据模型并不同。OGR使用数据源(data source)、图层(layer)和要素(feature),Fiona基于GeoJSON使用条目记录(term record)访问向量数据中的地理要素。一条记录具有ID、几何形状和属性键,可以用Python字典对象通过键引用记录。
创建几何形状
如同OGR一样,Shapely也可以用来创建形状,并且可以绘制出来,而不需要其他命令。
1 | from shapely.geometry import Polygon |
1 | p2 |
1 | # Point takes tuples as well as positional coordinate values |
1 | # line geometry |
1 | # linear rings |
1 | # collections of points |
1 | # collection of lines |
1 | # cooection of polygons |
使用Shapely的几何方法
1 | dir(p1)[-20:] |
['length',
'minimum_rotated_rectangle',
'overlaps',
'project',
'relate',
'relate_pattern',
'representative_point',
'simplify',
'svg',
'symmetric_difference',
'to_wkb',
'to_wkt',
'touches',
'type',
'union',
'within',
'wkb',
'wkb_hex',
'wkt',
'xy']
1 | print(p1.area) |
22.0
(1.0, 2.0, 5.0, 9.0)
POLYGON ((1.0000000000000000 2.0000000000000000, 5.0000000000000000 3.0000000000000000, 5.0000000000000000 7.0000000000000000, 1.0000000000000000 9.0000000000000000, 1.0000000000000000 2.0000000000000000))
读取JSON数据
尽管Shapely不能读写数据文件,我们可以用Shapely读取json数据:
1 | import json |
{"type": "Polygon", "coordinates": [[[1.0, 1.0], [1.0, 3.0], [3.0, 3.0], [1.0, 1.0]]]}
2.0
Fiona库的使用
读取数据
1 | import fiona |
dict_keys(['type', 'id', 'properties', 'geometry'])
使用Python的ppprint(pretty-print),打印数据集中第一个要素的各个键值如下:
1 | import pprint |
'Feature'
'0'
OrderedDict([('featurecla', 'Admin-1 scale rank'),
('scalerank', 3),
('adm1_code', 'ARG-1309'),
('diss_me', 1309),
('iso_3166_2', 'AR-E'),
('wikipedia', None),
('iso_a2', 'AR'),
('adm0_sr', 1),
('name', 'Entre RÃ\xados'),
('name_alt', 'Entre-Rios'),
('name_local', None),
('type', 'Provincia'),
('type_en', 'Province'),
('code_local', None),
('code_hasc', 'AR.ER'),
('note', None),
('hasc_maybe', None),
('region', None),
('region_cod', None),
('provnum_ne', 10),
('gadm_level', 1),
('check_me', 20),
('datarank', 3),
...
<built-in method keys of dict object at 0x0000025406F075E8>
1 | # print total amount of features |
4594
ESRI Shapefile
{'init': 'epsg:4326'}
联合使用Shapely和Fiona
前面已经提到,我们可以用Fiona打开shapefile,然后用Shapely操作分析其中的要素数据。
1 | src = fiona.open(r"data/10m_cultural" |
Heilongjiang
从上面结果可知,第1001个feature的名字是Heilongjiang,那我们可以用shapely的shape方法进行操作:
1 | heilongjiang = shape(src[1000]['geometry']) |
Shapefile作图
正如我们前面所做的,引用单独的地理要素并使用Python作图并不是那么简单。幸运的是,已经有许多人为我们造好了许多轮子,如果不将shapefile转为GeoJSON,而是直接做图的话,可以采用一下方式:
- 使用Numpy 数组和matplotlib:将shapefile的所有坐标装入到数组当中,然后可以用各种包作图。
- 使用Shapely,以使用地理区域名称作为键,地理要素数据作为值,从已有的shapefile创建字典,然后用shapefly作图。
- 使用pyshp和matplotlib:类似地,pyshp库也是用来读取地理信息。
- 使用GeoPandas和matplotlib:GeoPandas的好处在于可以将属性表存储为pandas的dataframe。
GeoPandas的使用
GeoPandas的好处是创建类似pandas的数据结构,同时还可以实现pandas所不能的地理空间功能(几何运算、数据叠加、地理编码等)。接下来,我们看一下如何使用GeoPandas实现数据处理、地理空间分析以及绘图等功能。
选择几何数据并作图
1 | import geopandas as gpd |
17 Xinjiang
18 Xizang
118 Inner Mongol
124 Gansu
261 Yunnan
1000 Heilongjiang
1005 Jilin
1010 Liaoning
1270 Guangxi
1670 Guangdong
2014 Hainan
2023 Fujian
2113 Zhejiang
2114 Shanghai
2116 Jiangsu
2220 Shandong
2221 Hebei
2222 Tianjin
3075 Paracel Islands
3786 Beijing
3789 Sichuan
3790 Chongqing
3791 Guizhou
3792 Hunan
3793 Ningxia
3794 Shaanxi
3795 Qinghai
3796 Shanxi
3797 Jiangxi
3798 Henan
3799 Hubei
3800 Anhui
Name: name, dtype: object
如上所示,'iso_a2’为’CN’的共有32个,是中国大陆的省、直辖市及自治区(不含港澳台)。
1 | beijing = df.loc[df['name']=='Shandong'] |
1 | china = df.loc[df['iso_a2']=='CN'] |
上面绘制的是中国大陆的省级行政区(注意:西藏等地区的边界线并非我国主张的边境线)。此外,我们还可以绘制指定经纬度范围内的地图:
1 | exp = df.cx[80:130, 15:50] |
绘制美国山火(wildfire)地图
近两年来,美国尤其是加州山火肆虐、经久不熄,给当地造成了严重的人身和财产损失,也导致了保险行业的巨大损失,接下来我们就用Geopandas对美国的山火情况进行简单的分析。
我们所使用的数据是MTBS提供的数据,MTBS,全称Monitoring Trends in Burn Severity,是一个由多个机构联合实施的项目,旨在为1984年以来美国的火灾严重程度和波及范围提供一致性的度量。
1 | import matplotlib |
(-180, -60)
1 | # read wildfire data |
1 | print(states.crs) |
{'init': 'epsg:4326'}
{'init': 'epsg:4269'}
Index(['Fire_ID', 'Fire_Name', 'Asmnt_Type', 'Pre_ID', 'Post_ID', 'Fire_Type',
'ND_T', 'IG_T', 'Low_T', 'Mod_T', 'High_T', 'Ig_Date', 'Lat', 'Long',
'Acres', 'geometry'],
dtype='object')
通过以上代码,我们发现了两个问题:
- states与fires采用的是不同的投影坐标系;
- fires中的山火信息不包括其发生在哪个州。
针对第一个问题,我们可以通过重新投影解决:
1 | fires = fires.to_crs({'init': 'epsg:4326'}) |
针对第二个问题,利用sjoin
方法,通过空间联合,指示fires的经纬度是否位于某一个州的经纬度范围内:
1 | state_fires = gpd.sjoin(fires, states[['name', 'geometry']].copy(), |
接下来,我们就可以按照州为单位统计发生的山火次数:
1 | counts_per_state = state_fires.groupby('name').size() |
name
Florida 3710
California 1630
Kansas 1422
Idaho 1316
...
dtype: int64
可以看到,Florida、California和Kansas在1984-2016年发生的山火次数高居全美前三位。但是,数字毕竟没有图像直观,接下来,我们将各州山火情况可视化:
首先,我们可以将计算的数据作为原来shapefile的一个属性:
1 | states = states.merge(counts_per_state.reset_index(name='number_of_fires')) |
然后画出等值图(chorpleth map):
1 | import matplotlib.pyplot as plt |
1 | import plotly.graph_objs |
1 | data = [dict( |
在我们真正进入分析之前,首先最好对数据进行检查,有一个初步的认识 例如,列出有关数据集的统计信息,以显示有多少元素,以及是否存在任何缺失值。 在进行分析之前,通常需要清理数据。 以我们前面使用的山火数据为例,统计山火的发生次数与各州山火的总数,发现分州汇总之后的数据少61个。这可能意味着:
- 有些山火的数据是缺失(无效的);
- 山火的经纬度坐标超出了Natrual Earth所给的坐标范围;
- …
这就需要我们重新去检查数据的有效性,并做相应的处理。
1 | print(len(fires.index)) |
21673
21612
False
False
小结
本文介绍了三个用于处理矢量数据-OGR,Shapely和GeoPandas的Python库以及如何使用这三者进行地理空间分析和处理。 每个库都分别包含其类,方法,数据结构和常用功能。接下来,我们将探讨栅格数据的分析处理以及GDAL和Rasterio库的具体使用。