空间向量数据#

GeoPandas 简介#

GeoPandas 的目标是使 python 中的空间数据更容易使用。它结合了 pandasshapely 的功能,提供了 pandas 中的空间运算和多个几何图形的高级接口。GeoPandas 使您能够轻松地用 python 执行运算,否则将需要诸如 PostGIS 这样的空间数据库。

GeoPandas 数据结构#

GeoPandas 实现了两个主要的数据结构,GeoSeries 和 GeoDataFrame。它们分别是 pandas Series 和 DataFrame 的子类。

GeoSeries#

GeoSeries 本质上是向量,其中向量中的每个条目都是一组对应于观察结果的形状。一个条目可能只包含一个形状(如单个多边形)或多个被认为是一个观察结果的形状(如组成夏威夷州或印度尼西亚等国家的多个多边形)。

Geopandas 有三种基本的几何对象(实际上是 shapely 的对象):

  • Points / Multi-Points

  • Lines / Multi-Lines

  • Polygons / Multi-Polygons

from matplotlib import pyplot as plt
import geopandas as gpd

plt.style.use('bmh') # 更适合绘制几何图形。
from shapely.geometry import Point
s = gpd.GeoSeries([Point(1, 1), Point(2, 2), Point(3, 3)])
s
0    POINT (1.00000 1.00000)
1    POINT (2.00000 2.00000)
2    POINT (3.00000 3.00000)
dtype: geometry
fig, ax = plt.subplots(figsize=(12, 6))
s.plot(ax=ax);
<Figure size 1200x600 with 1 Axes>
from shapely.geometry import LineString
l= gpd.GeoSeries([LineString([Point(-77.036873,38.907192), 
                              Point(-76.612190,39.290386,), 
                              Point(-77.408456,39.412006)])])
l
0    LINESTRING (-77.03687 38.90719, -76.61219 39.2...
dtype: geometry
fig, ax = plt.subplots(figsize=(12, 6))
l.plot(ax=ax);
<Figure size 1200x600 with 1 Axes>
from shapely.geometry import Polygon
p= gpd.GeoSeries([Polygon([Point(-77.036873,38.907192),
                           Point(-76.612190,39.290386),
                           Point(-77.408456,39.412006)])])
p
0    POLYGON ((-77.03687 38.90719, -76.61219 39.290...
dtype: geometry
fig, ax = plt.subplots(figsize=(12, 6))
p.plot(ax=ax);
<Figure size 1200x600 with 1 Axes>

注意,GeoSeries 中的所有条目不需要具有相同的几何类型,但如果不是这样,某些导出操作将失败。

GeoDataFrame#

GeoDataFrame 是包含 GeoSeries 的表格式数据结构。

GeoDataFrame 最重要的属性是它总是有一个 GeoSeries 列,它持有一个特殊的状态。这个 GeoSeries 被称为 GeoDataFrame 的“几何”。当将空间方法应用到 GeoDataFrame (或调用类似 area 的空间属性)时,此命令将始终作用于“几何”列。

“几何”列——不管它的名字是什么——都可以通过几何属性(gdf.geometry)访问,并且可以通过键入 gdf.geometry.name 找到几何列的名称。

备注

GeoDataFrame 还可以包含具有几何(形状)对象的其他列,但一次只能有一列是活动几何。要更改哪个列是活动几何列,请使用 GeoDataFrame.set_geometry() 方法。

使用世界 GeoDataFrame 的例子:

world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
world.head()
/tmp/ipykernel_1610/913829029.py:1: FutureWarning: The geopandas.dataset module is deprecated and will be removed in GeoPandas 1.0. You can get the original 'naturalearth_lowres' data from https://www.naturalearthdata.com/downloads/110m-cultural-vectors/.
  world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
       pop_est      continent                      name iso_a3  gdp_md_est  \
0     889953.0        Oceania                      Fiji    FJI        5496   
1   58005463.0         Africa                  Tanzania    TZA       63177   
2     603253.0         Africa                 W. Sahara    ESH         907   
3   37589262.0  North America                    Canada    CAN     1736425   
4  328239523.0  North America  United States of America    USA    21433226   

                                            geometry  
0  MULTIPOLYGON (((180.00000 -16.06713, 180.00000...  
1  POLYGON ((33.90371 -0.95000, 34.07262 -1.05982...  
2  POLYGON ((-8.66559 27.65643, -8.66512 27.58948...  
3  MULTIPOLYGON (((-122.84000 49.00000, -122.9742...  
4  MULTIPOLYGON (((-122.84000 49.00000, -120.0000...  
world.plot();
<Figure size 640x480 with 1 Axes>

空间点线多边形#

经常发现处于这样一种情况:需要从头生成新的空间数据,或者需要更好地理解数据是如何构造的。这节课将带您了解一些最常见的数据生成形式。

import pandas as pd
import geopandas as gpd
from shapely.geometry import Point, LineString, Polygon
# import fiona
import matplotlib.pyplot as plt
plt.style.use('bmh') # 更适合绘制几何图形。

创建 GeoDataFrame 几何#

GeoDataFrame 对象是 pandas.DataFrame,具有几何列的数据帧。空的 GeoDataFrame 就是空的,本质上就像 pandas 的那个一样。

创建空的 GeoDataFrame,并创建名为 geometry 的新列,它将包含 Shapely 对象:

# 创建空白的 geopandas GeoDataFrame
newdata = gpd.GeoDataFrame()
print(newdata)
Empty GeoDataFrame
Columns: []
Index: []

为了有工作的空间数据框架,需要定义一些东西:

GeoDataFrame 组件#

  • data:包含所需属性数据的 pandas.DataFrame、字典或空列表 []。如果没有数据,则使用 []

  • crs:几何对象的坐标参考系(Coordinate Reference System)。可以是 pyproj.CRS.from_user_input() 所接受的任何内容,例如权限字符串(例如 “EPSG:4326”)或 WKT 字符串。

  • geometry:DataFrame 中的列名称,用作几何或 Shapely 点、线或多边形对象。

因为 geopandas 利用了 Shapely 的几何对象,所以可以通过将 Shapely 的几何对象传递到 GeoDataFrame 来从头创建 Shapefile。这是非常有用的,因为它可以很容易地将包含坐标的文本文件转换为 Shapefile。

现在在 GeoDataFrame 中有一个几何列,但是还没有任何数据。

从坐标列表中创建点#

创建 geopandas 点对象是一个快照!所需要的是坐标对,从中生成 Shapely 点几何对象,然后创建字典,保存该几何和想要的任何属性,以及坐标参考系统。

# GW department of geography 的十进制坐标
coordinate = [-77.04639494419096,  38.89934963421794]

# 从坐标对中创建 Shapely
point_coord = Point(coordinate)

# 创建具有所需属性和所需几何列的数据帧
df = {'GWU': ['Dept Geography'], 'geometry': [point_coord]}

# 将 shapely 对象转换为  geodataframe 
point = gpd.GeoDataFrame(df, geometry='geometry', crs ="EPSG:4326")

# 渲染
point.plot();
<Figure size 640x480 with 1 Axes>

可以对存储在 pandas 数据框架中的点集应用相同的过程。

# 属性和坐标列表
df = pd.DataFrame(
    {'City': ['Buenos Aires', 'Brasilia', 'Santiago', 'Bogota', 'Caracas'],
     'Country': ['Argentina', 'Brazil', 'Chile', 'Colombia', 'Venezuela'],
     'lat': [-34.58, -15.78, -33.45, 4.60, 10.48],
     'lon': [-58.66, -47.91, -70.66, -74.08, -66.86]})

# 从坐标元组列表中创建 Shapely points
ply_coord = [Point(x, y) for x, y in zip(df.lat, df.lon)]

# 用 crs 转换 shapely 对象到 geodataframe
poly = gpd.GeoDataFrame(df, geometry=ply_coord, crs ="EPSG:4326")
poly.plot();
<Figure size 640x480 with 1 Axes>

从经纬度(lat, lon) CSV 创建点#

最常见的数据创建任务之一是从点列表或 .csv 文件创建 shapefile。幸运的是,将这些数据转换成可用的格式非常容易。

首先,必须创建示例 .csv 数据集来工作:

import pandas as pd
# 创建 Washington DC 的轮廓,并写入 csv
path_to_csv = 'points.csv'
points = {'Corner':['N','E','S','W'],
          'lon': [-77.0412826538086, -77.11681365966797, -77.01896667480469, -77.0412826538086], 
          'lat': [38.99570671505043, 38.936713143230044, 38.807610542357594, 38.99570671505043]}
points = pd.DataFrame.from_dict(points)
points.to_csv(path_to_csv)              

要从我们的数据创建 geodataframe,您只需要将其读入,并使用 points_from_xy 将其指向 df 的正确列,即 df 指定几何列值 df.londf.lat

# 读取点数据
df = pd.read_csv(path_to_csv)

# 从使用 'EPSG' 代码的数据创建 geodataframe,以分配 WGS84 坐标系
points= gpd.GeoDataFrame(df, geometry=gpd.points_from_xy(x=df.lon, y=df.lat), crs = 'EPSG:4326')
points
   Unnamed: 0 Corner        lon        lat                    geometry
0           0      N -77.041283  38.995707  POINT (-77.04128 38.99571)
1           1      E -77.116814  38.936713  POINT (-77.11681 38.93671)
2           2      S -77.018967  38.807611  POINT (-77.01897 38.80761)
3           3      W -77.041283  38.995707  POINT (-77.04128 38.99571)

在本例中,points_from_xy() 用于将 latlon 转换为 shapely.Point 对象列表。这将被用作 GeoDataFrame 的几何图形。(points_from_xy() 只是 [Point(x, y) for x, y in zip(df.lon, df.lat)] 的增强包装器。

小技巧

  • 虽然我们说 “lat lon”,但 python 使用 “lon lat” 代替,这遵循了使用 x, y 作为符号的偏好。

  • 通常,像上面的数据一样,这些数据存储在 WGS84 经纬度中,但一定要检查这一点,另一种常见的格式是 UTM 坐标(寻找从东到西约 \(500\,000\) 的值,以米为单位)。

创建空间线条#

按照上面的例子,可以很容易地指定线条。在这种情况下,假设有追踪三个骑自行车穿过城镇的人的线。跟踪他们的唯一 id ID,他们的位置 X, Y 和他们的速度,并读取以下数据:

from io import StringIO 
data = """
ID,X,Y,Speed
1,  -87.789,  41.976,  16
1,  -87.482,  41.677,  17
2,  -87.739,  41.876,  16
2,  -87.681,  41.798,  16
2,  -87.599,  41.708,  16
3,  -87.599,  41.908,  17
3,  -87.598,  41.708,  17
3,  -87.643,  41.675,  17
"""
# 使用 StringIO 读取文本块
df = pd.read_table(StringIO(data), sep=',')

让我们把这些转换成点来看看。注意,在本例中点不能很好地替代线,我们有三个个体,它们需要分别处理。

# zip 坐标到点对象,并转换为 GeoDataFrame
points = [Point(xy) for xy in zip(df.X, df.Y)]
geo_df = gpd.GeoDataFrame(df, geometry=points, crs = 'EPSG:4326')
geo_df.plot();
<Figure size 640x480 with 1 Axes>

现在让将这些数据作为线处理,可以利用列 ID 到 .groupby。幸运的是,geopanda .groupby 与 pandas 的用法一致。所以这里 .groupby(['ID']),对于每个 ID 组,将值转换为一个列表,并将其存储在 Fiona LineString 对象中。

# 把每个点的 `ID` 组看作一条线
lines = geo_df.groupby(['ID'])['geometry'].apply(lambda x:  LineString(x.tolist()))

# 存储为 GeodataFrame 并添加 'ID' 作为列(当前存储为 'index')
lines = gpd.GeoDataFrame(lines, geometry='geometry', crs="EPSG:4326") 
lines.reset_index(inplace=True)
lines.plot(column='ID');
<Figure size 640x480 with 1 Axes>

现可以看到,每条线都是由 ID 单独处理的,并使用 .plot(column='ID') 绘制它们。

创建空间多边形#

在 geopandas 中创建多边形与其他练习非常相似。首先,从坐标中创建 Fiona 几何对象,将其添加到具有任何属性的数据帧中,然后使用指定的坐标参考系统(coordinate reference system,简称 CRS)创建 GeoDataFrame。

# list of coordindate pairs
coordinates = [[ -77.0412826538086, 38.99570671505043 ], [ -77.11681365966797, 38.936713143230044 ], [ -77.01896667480469, 38.807610542357594],
               [-76.90910339355469,  38.892636142310295]]           

# Create a Shapely polygon from the coordinate-tuple list
ply_coord = Polygon(coordinates)

# create a dictionary with needed attributes and required geometry column
df = {'Attribute': ['name1'], 'geometry': ply_coord}

# Convert shapely object to a geodataframe 
poly = gpd.GeoDataFrame(df, geometry='geometry', crs ="EPSG:4326")

# Let's see what we have
poly.plot();
<Figure size 640x480 with 1 Axes>

创建空间点(必须承认这是漫长的过程)#

因为 geopandas 利用了 Shapely 的几何对象,所以可以通过将 Shapely 的几何对象传递到 GeoDataFrame 来从头创建 Shapefile。这是非常有用的,因为它可以很容易地将包含坐标的文本文件转换为 Shapefile。

让我们创建空的 GeoDataFrame,并创建名为 geometr 的新列,它将包含我们的 Shapely 对象:

# Create an empty geopandas GeoDataFrame
newdata = gpd.GeoDataFrame()
# Create a new column called 'geometry' to the GeoDataFrame
newdata['geometry'] = None

print(newdata)
Empty GeoDataFrame
Columns: [geometry]
Index: []
/tmp/ipykernel_1610/2510346371.py:4: FutureWarning: You are adding a column named 'geometry' to a GeoDataFrame constructed without an active geometry column. Currently, this automatically sets the active geometry column to 'geometry' but in the future that will no longer happen. Instead, either provide geometry to the GeoDataFrame constructor (GeoDataFrame(... geometry=GeoSeries()) or use `set_geometry('geometry')` to explicitly set the active geometry column.
  newdata['geometry'] = None

创建表示 GWU 地理学系的 Shapely Point,可以将它插入到 GeoDataFrame 中:

# Coordinates of the GW department of geography in Decimal Degrees
coordinates = (-77.04639494419096,  38.89934963421794)

# Create a Shapely polygon from the coordinate-tuple list
point = Point(coordinates)

# Let's see what we have
point
<POINT (-77.046 38.899)>

现在有了合适的 Polygon -object。

将多边形插入到 GeoDataFrame 的“geometry”列中:

# Insert the polygon into 'geometry' -column at index 0
newdata.loc[0, 'geometry'] = point

# Let's see what we have now
newdata
                     geometry
0  POINT (-77.04639 38.89935)

现在有了带 Point 的 GeoDataFrame,可以将它导出到 Shapefile中。在 GeoDataFrame 中添加另一列,名为 Location,文本为 GWU Geography。

# Add a new column and insert data
newdata.loc[0, 'Location'] = 'GWU Geography'

# Let's check the data
newdata
                     geometry       Location
0  POINT (-77.04639 38.89935)  GWU Geography

好了,现在有了额外的有用的信息能够识别特征代表什么。

在导出数据之前,确定 GeoDataFrame 的坐标参考系统(CRS,“投影”)是很有用的。

GeoDataFrame 有叫做 .crs 的属性,它显示了在例子中为空(None)的数据坐标系统,因为是从头创建数据(例如 newdata.crs 返回 None)。

为 GeoDataFrame 添加 crs。Python 模块 fiona 有很好的 from_epsg() 函数,用于传递 GeoDataFrame 的坐标系统。接下来,将使用它,并确定投影到 WGS84 (epsg code:4326),这是最常见的选择,lat lon CRSs:

# Import specific function 'from_epsg' from fiona module
from fiona.crs import from_epsg

# Set the GeoDataFrame's coordinate system to WGS84
newdata.crs = from_epsg(code = 4326)

# Let's see how the crs definition looks like
newdata.crs
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[24], line 5
      2 from fiona.crs import from_epsg
      4 # Set the GeoDataFrame's coordinate system to WGS84
----> 5 newdata.crs = from_epsg(code = 4326)
      7 # Let's see how the crs definition looks like
      8 newdata.crs

File fiona/crs.pyx:1252, in fiona.crs.from_epsg()

TypeError: from_epsg() takes exactly 1 positional argument (0 given)

最后,可以使用 GeoDataFrames.to_file() 导出数据。该函数的工作原理与 numpy 或 pandas 类似,但这里只需要为 Shapefile 提供输出路径。

# Determine the output path for the Shapefile
outfp = r"../temp/gwu_geog.shp"

# Write the data into that Shapefile
newdata.to_file(outfp)