当前位置:   article > 正文

Python绘制数据地图-GeoPandas使用要点

geopandas

本文主要介绍GeoPandas的使用要点。GeoPandas是一个Python开源项目,旨在提供丰富而简单的地理空间数据处理接口。GeoPandas扩展了Pandas的数据类型,并使用matplotlib进行绘图。GeoPandas官方仓库地址为:GeoPandas[1]。GeoPandas的官方文档地址为:GeoPandas-doc[2]。本文主要参考GeoPandas Examples Gallery[3]。GeoPandas的基础使用见Python绘制数据地图1-GeoPandas入门指北[4]。GeoPandas的可视化入门见Python绘制数据地图2-GeoPandas地图可视化[5]

GeoPandas推荐使用Python3.7版本及以上,运行环境最好是linux系统。GeoPandas安装命令如下:

pip install geopandas

如果上述命令安装出问题,则推荐使用conda安装GeoPandas,命令如下:

conda install geopandas

或:

conda install --channel conda-forge geopandas

除了GeoPandas需要安装,以下第三方库也需要安装:

  1. pip install mapclassify
  2. pip install matplotlib_scalebar
  3. pip install rtree
  4. pip install contextily
  1. # jupyter notebook环境去除warning
  2. import warnings
  3. warnings.filterwarnings("ignore")
  4. # 查看geopandas版本
  5. import geopandas as gpd
  6. gpd.__version__
'0.13.2'

1 分级统计图Choropleth

分级统计图Choropleth是一种表示地理区域内数据分布的可视化图表。它将地图划分为不同的区域,并使用颜色或阴影的不同程度来显示该区域的数据值。通常,分级统计图用于显示人口统计、自然资源分布等数据。分级统计图以帮助观察者更容易地理解数据在地理空间上的分布情况和变化趋势,有助于制定决策和规划相关工作。

  1. import geopandas as gpd
  2. from geopandas import read_file
  1. # pip install mapclassify
  2. import mapclassify
  3. mapclassify.__version__
'2.5.0'
  1. # 读取四川地图数据,数据来自DataV.GeoAtlas,将其投影到EPSG:4573
  2. data = gpd.read_file('https://geo.datav.aliyun.com/areas_v3/bound/510000_full.json').to_crs('EPSG:4573')
  3. data.head()

adcodenamechildrenNumlevelparentsubFeatureIndexgeometry
0510100成都市20city{'adcode': 510000}0MULTIPOLYGON (((18399902.859 3356187.915, 1840...
1510300自贡市6city{'adcode': 510000}1MULTIPOLYGON (((18419941.941 3231303.167, 1842...
2510400攀枝花市5city{'adcode': 510000}2MULTIPOLYGON (((18183734.470 2889855.327, 1818...
3510500泸州市7city{'adcode': 510000}3MULTIPOLYGON (((18540813.879 3244247.734, 1853...
4510600德阳市6city{'adcode': 510000}4MULTIPOLYGON (((18516163.207 3398495.768, 1851...

简单分级统计

以下代码通过scheme分级统计四川省各地级市所包含区县数。

  1. ax = data.plot(
  2. column="childrenNum",
  3. scheme="QUANTILES", # 设置分层设色标准
  4. edgecolor='lightgrey',
  5. k=7, # 分级数量
  6. cmap="Blues",
  7. legend=True,
  8. # 通过fmt设置位数
  9. legend_kwds={"loc": "center left", "bbox_to_anchor": (1, 0.5),"fmt": "{:.2f}"}
  10. )
  11. # 显示各地级市包含区县数量
  12. for index in data.index:
  13. x = data.iloc[index].geometry.centroid.x
  14. y = data.iloc[index].geometry.centroid.y
  15. name = data.iloc[index]["childrenNum"]
  16. ax.text(x, y, name, ha="center", va="center",color='red')
  1. # 查看label,也就是分级区间
  2. labels = [t.get_text() for t in ax.get_legend().get_texts()]
  3. labels
  1. [' 3.00, 5.00',
  2. ' 5.00, 6.00',
  3. ' 6.00, 6.57',
  4. ' 6.57, 7.43',
  5. ' 7.43, 9.29',
  6. ' 9.29, 13.57',
  7. '13.57, 20.00']
  1. # 查看各个分级标准和区间数量,一般是左开右闭
  2. res = mapclassify.Quantiles(data["childrenNum"], k=7)
  3. res
  1. Quantiles
  2. Interval Count
  3. ----------------------
  4. [ 3.00, 5.00] | 5
  5. ( 5.00, 6.00] | 4
  6. ( 6.00, 6.57] | 0
  7. ( 6.57, 7.43] | 3
  8. ( 7.43, 9.29] | 3
  9. ( 9.29, 13.57] | 3
  10. (13.57, 20.00] | 3

间隔展示

  1. ax = data.plot(
  2. column="childrenNum",
  3. scheme="BoxPlot",
  4. edgecolor='k',
  5. cmap="OrRd", # 设置分层设色标准
  6. legend=True,
  7. # 通过interval设置是否展示区间间隔
  8. legend_kwds={"loc": "center left", "bbox_to_anchor": (1, 0.5), "interval": True}
  9. )
  10. # 显示各地级市包含区县数量
  11. for index in data.index:
  12. x = data.iloc[index].geometry.centroid.x
  13. y = data.iloc[index].geometry.centroid.y
  14. name = data.iloc[index]["childrenNum"]
  15. ax.text(x, y, name, ha="center", va="center",color='red')
c89f6742f815612ac981aeaa632817a6.png
png

分类展示

以数值分类的方式展示数据,其中区县数量为20的地级市为成都市。

  1. ax = data.plot(
  2. column="childrenNum",
  3. categorical=True, # 以数值分类的方式展示
  4. legend=True,
  5. cmap="tab20",
  6. # 对于分类数据,fmt设置无用
  7. legend_kwds={"loc": "center left", "bbox_to_anchor": (1, 0.5), "fmt": "{:.0f}"},
  8. )
  9. # 显示各地级市包含区县数量
  10. for index in data.index:
  11. x = data.iloc[index].geometry.centroid.x
  12. y = data.iloc[index].geometry.centroid.y
  13. name = data.iloc[index]["childrenNum"]
  14. ax.text(x, y, name, ha="center", va="center",color='red')
e128bb526aa8bce2a5b7d86a6bf43874.png
png

自定义分级

  1. # 自定义分级标准
  2. def custom(value):
  3. # 设置ABC三个等级
  4. level = None
  5. if value > 15:
  6. level = 'A'
  7. elif value > 8:
  8. level = 'B'
  9. else:
  10. level = 'C'
  11. return level
  12. # 根据自定义函数映射为新的列
  13. data['level'] = data['childrenNum'].apply(custom)
  14. data.head()

adcodenamechildrenNumlevelparentsubFeatureIndexgeometry
0510100成都市20A{'adcode': 510000}0MULTIPOLYGON (((18399902.859 3356187.915, 1840...
1510300自贡市6C{'adcode': 510000}1MULTIPOLYGON (((18419941.941 3231303.167, 1842...
2510400攀枝花市5C{'adcode': 510000}2MULTIPOLYGON (((18183734.470 2889855.327, 1818...
3510500泸州市7C{'adcode': 510000}3MULTIPOLYGON (((18540813.879 3244247.734, 1853...
4510600德阳市6C{'adcode': 510000}4MULTIPOLYGON (((18516163.207 3398495.768, 1851...
  1. ax = data.plot(
  2. column="level",
  3. categorical=True, # 以数值分类的方式展示
  4. legend=True,
  5. cmap="coolwarm",
  6. # 对于分类数据,fmt设置无用
  7. legend_kwds={"loc": "center left", "bbox_to_anchor": (1, 0.5), "fmt": "{:.0f}"},
  8. )
  9. # 显示各地级市包含区县数量
  10. for index in data.index:
  11. x = data.iloc[index].geometry.centroid.x
  12. y = data.iloc[index].geometry.centroid.y
  13. name = data.iloc[index]["childrenNum"]
  14. ax.text(x, y, name, ha="center", va="center",color='red')
d039a485c5b76e997e31a35d00d3656a.png
png

2 通过DataFrame创建GeoDataFrame

基于经纬度数据

GeoDataFrame有一个geometry列,我们可以通过经纬度数据Latitude和Longitude创建该列。

  1. import pandas as pd
  2. # 生成关于南美城市的dataframe数据
  3. df = pd.DataFrame(
  4. {
  5. "City": ["Buenos Aires", "Brasilia", "Santiago", "Bogota", "Caracas"],
  6. "Country": ["Argentina", "Brazil", "Chile", "Colombia", "Venezuela"],
  7. "Latitude": [-34.58, -15.78, -33.45, 4.60, 10.48],
  8. "Longitude": [-58.66, -47.91, -70.66, -74.08, -66.86],
  9. }
  10. )
  11. df

CityCountryLatitudeLongitude
0Buenos AiresArgentina-34.58-58.66
1BrasiliaBrazil-15.78-47.91
2SantiagoChile-33.45-70.66
3BogotaColombia4.60-74.08
4CaracasVenezuela10.48-66.86
  1. # 将dataframe转换为GeoDataFrame
  2. import geopandas as gpd
  3. gdf = gpd.GeoDataFrame(
  4. df, geometry=gpd.points_from_xy(df.Longitude, df.Latitude), crs="EPSG:4326"
  5. )
  6. gdf

CityCountryLatitudeLongitudegeometry
0Buenos AiresArgentina-34.58-58.66POINT (-58.66000 -34.58000)
1BrasiliaBrazil-15.78-47.91POINT (-47.91000 -15.78000)
2SantiagoChile-33.45-70.66POINT (-70.66000 -33.45000)
3BogotaColombia4.60-74.08POINT (-74.08000 4.60000)
4CaracasVenezuela10.48-66.86POINT (-66.86000 10.48000)
  1. # 在南美地图上展示
  2. world = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres"))
  3. # 定位到南美
  4. ax = world.cx[-90:-55, -25:15].plot(color="white", edgecolor="black")
  5. # 在ax区域上绘制地图
  6. gdf.plot(ax=ax, color="red")
<matplotlib.axes._subplots.AxesSubplot at 0x7f75ed57a460>
df6cf6398edff0c827001f99da385f7b.png
png

基于WTK格式数据

WKT (Well-Known Text) 是一种用于描述地理位置的数据格式。WTK格式的数据包含点、线、多边形等地理位置信息。WTK格式的数据可以被许多GIS软件和地理位置分析工具所读取和处理。我们可以将带有WKT数据的DataFrame转换为GeoDataframe。

  1. df = pd.DataFrame(
  2. {
  3. "City": ["Buenos Aires", "Brasilia", "Santiago", "Bogota", "Caracas"],
  4. "Country": ["Argentina", "Brazil", "Chile", "Colombia", "Venezuela"],
  5. "Coordinates": [
  6. "POINT(-58.66 -34.58)",
  7. "POINT(-47.91 -15.78)",
  8. "POINT(-70.66 -33.45)",
  9. "POINT(-74.08 4.60)",
  10. "POINT(-66.86 10.48)",
  11. ],
  12. }
  13. )
  14. df

CityCountryCoordinates
0Buenos AiresArgentinaPOINT(-58.66 -34.58)
1BrasiliaBrazilPOINT(-47.91 -15.78)
2SantiagoChilePOINT(-70.66 -33.45)
3BogotaColombiaPOINT(-74.08 4.60)
4CaracasVenezuelaPOINT(-66.86 10.48)
  1. # 创建新列然后数据转换
  2. df["Coordinates"] = gpd.GeoSeries.from_wkt(df["Coordinates"])
  3. gdf = gpd.GeoDataFrame(df, geometry="Coordinates", crs="EPSG:4326")
  4. print(gdf.head())
  1. City Country Coordinates
  2. 0 Buenos Aires Argentina POINT (-58.66000 -34.58000)
  3. 1 Brasilia Brazil POINT (-47.91000 -15.78000)
  4. 2 Santiago Chile POINT (-70.66000 -33.45000)
  5. 3 Bogota Colombia POINT (-74.08000 4.60000)
  6. 4 Caracas Venezuela POINT (-66.86000 10.48000)
  1. # 在南美地图上展示
  2. world = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres"))
  3. # 定位到南美
  4. ax = world.cx[-90:-55, -25:15].plot(color="white", edgecolor="black")
  5. # 在ax区域上绘制地图
  6. gdf.plot(ax=ax, color="red")
<matplotlib.axes._subplots.AxesSubplot at 0x7f75ed445100>
981c4ae82f591ec409dd4732124caa49.png
png

3 添加比例尺

在地理空间数据分析和可视化过程中,比例尺可以帮助我们了解地图上的距离和大小关系。基于matplotlib进行可视化时,可以利用matplotlib-scalebar[6]库添加比例尺。

简单比例尺

  1. import geopandas as gpd
  2. # pip install matplotlib_scalebar安装
  3. from matplotlib_scalebar.scalebar import ScaleBar
  1. # nybb为纽约五大地图
  2. nybb = gpd.read_file(gpd.datasets.get_path("nybb"))
  3. # 北美地区常见坐标系统,坐标以米为单位
  4. nybb = nybb.to_crs(32619)
  5. nybb.head()

BoroCodeBoroNameShape_LengShape_Areageometry
05Staten Island330470.0103321.623820e+09MULTIPOLYGON (((72387.313 4502901.349, 72390.3...
14Queens896344.0477633.045213e+09MULTIPOLYGON (((90672.492 4505050.592, 90663.5...
23Brooklyn741080.5231661.937479e+09MULTIPOLYGON (((88021.476 4503764.521, 87967.7...
31Manhattan359299.0964716.364715e+08MULTIPOLYGON (((76488.408 4515823.054, 76399.6...
42Bronx464392.9918241.186925e+09MULTIPOLYGON (((86828.383 4527641.247, 86816.3...

如下所示,创建ScaleBar对象所需的唯一参数是dx。dx表示输入图片每一个像素代表的长度,units为dx的单位。此参数的值取决于坐标参考系的单位。在前面nybb数据集已经使用epsg:32619坐标系统,该坐标系以单位米为单位,如下所示,可以看到nybb.crs输出结果中Axis Info项标识了该参考系以metre米为单位。

nybb.crs
  1. <Projected CRS: EPSG:32619>
  2. Name: WGS 84 / UTM zone 19N
  3. Axis Info [cartesian]:
  4. - E[east]: Easting (metre)
  5. - N[north]: Northing (metre)
  6. Area of Use:
  7. - name: Between 72°W and 66°W, northern hemisphere between equator and 84°N, onshore and offshore. Aruba. Bahamas. Brazil. Canada - New Brunswick (NB); Labrador; Nunavut; Nova Scotia (NS); Quebec. Colombia. Dominican Republic. Greenland. Netherlands Antilles. Puerto Rico. Turks and Caicos Islands. United States. Venezuela.
  8. - bounds: (-72.0, 0.0, -66.0, 84.0)
  9. Coordinate Operation:
  10. - name: UTM zone 19N
  11. - method: Transverse Mercator
  12. Datum: World Geodetic System 1984 ensemble
  13. - Ellipsoid: WGS 84
  14. - Prime Meridian: Greenwich

在下面代码中添加了比例尺和像素尺寸,该比例尺采用的是线段式表示方式,即在地图上绘制一条线段并注明该地图上该线段所代表的实际距离。

  1. ax = nybb.plot()
  2. # 在地图中添加比例尺和像素尺寸
  3. scalebar =ScaleBar(dx=1,units="m")
  4. ax.add_artist(scalebar)
<matplotlib_scalebar.scalebar.ScaleBar at 0x7f75ed426a30>
91af4630f6a6045e218f6eb3a80399fe.png
png

确定比例尺基准长度

如下所示,以经纬度为单位的epsg:4326坐标系,其单位尺度为度(经纬度)。

  1. # nybb为纽约五大区地图
  2. nybb = gpd.read_file(gpd.datasets.get_path("nybb"))
  3. nybb = nybb.to_crs(4326)
  4. nybb.plot()
<matplotlib.axes._subplots.AxesSubplot at 0x7f75ed3a9cd0>
5fe627ea75c8f3355710aa47009147d7.png
png
nybb.crs
  1. <Geographic 2D CRS: EPSG:4326>
  2. Name: WGS 84
  3. Axis Info [ellipsoidal]:
  4. - Lat[north]: Geodetic latitude (degree)
  5. - Lon[east]: Geodetic longitude (degree)
  6. Area of Use:
  7. - name: World.
  8. - bounds: (-180.0, -90.0, 180.0, 90.0)
  9. Datum: World Geodetic System 1984 ensemble
  10. - Ellipsoid: WGS 84
  11. - Prime Meridian: Greenwich

可以通过计算该地图中相邻两点之间的距离长度来确定比例尺基准长度,要注意的是这两点应该位于待绘制的地图中。

  1. from shapely.geometry.point import Point
  2. points = gpd.GeoSeries(
  3. [Point(-73.9, 40.7), Point(-74.9, 40.7)], crs=4326
  4. )
  5. # 将两点转换到以米为单位的坐标系
  6. points = points.to_crs(32619)
  7. # 计算点之间的距离,距离单位为坐标系的单位
  8. distance_meters = points[0].distance(points[1])
  9. distance_meters
84698.53985065906
  1. nybb = nybb.to_crs(4326)
  2. ax = nybb.plot()
  3. ax.add_artist(ScaleBar(distance_meters,"m"))
<matplotlib_scalebar.scalebar.ScaleBar at 0x7f75ed113400>
0894c7b32525c15245288e56cb474c7b.png
png

比例尺自定义

通过更改 ScaleBar 参数能够调整比例尺的显示效果,ScaleBar具体参数如下所示。这些参数的使用可以自行尝试。

  1. scalebar = ScaleBar(
  2. dx, # 像素和长度之间的比例尺。例如,如果一个像素代表1毫米,则dx=0.001
  3. units="m", # 长度单位
  4. dimension="si-length", # 维度
  5. label=None, # 刻度尺标签
  6. length_fraction=None, # 刻度尺长度占比
  7. height_fraction=None, # 刻度尺高度占比
  8. width_fraction=None, # 刻度尺宽度占比
  9. location=None, # 刻度尺的位置
  10. pad=None, # 刻度尺和边框之间的间距
  11. border_pad=None, # 刻度尺和边框之间的边距
  12. sep=None, # 刻度尺标签和刻度之间的距离
  13. frameon=None, # 是否显示边框
  14. color=None, # 刻度尺和标签的颜色
  15. box_color=None, # 边框的颜色
  16. box_alpha=None, # 边框的透明度
  17. scale_loc=None, # 刻度线的位置
  18. label_loc=None, # 刻度尺标签的位置
  19. font_properties=None, # 标签和刻度线的字体属性
  20. label_formatter=None, # 标签的格式化函数
  21. scale_formatter=None, # 刻度线的格式化函数
  22. fixed_value=None, # 固定的数值
  23. fixed_units=None, # 固定的单位
  24. animated=False, # 是否允许动画
  25. rotation=None, # 刻度尺的旋转角度
  26. bbox_to_anchor=None, # bbox的锚点
  27. bbox_transform=None, # bbox的变换
  28. )

此外,也可以更改一像素代表的长度单位,如ScaleBar(2, dimension="si-length", units="km")表示图中1像素代表实际si-length(国际单位制)中的2km。所支持的长度单位参数如下表所示:

dimensionunits
si-lengthkm, m, cm, um
imperial-lengthin, ft, yd, mi
si-length-reciprocal1/m, 1/cm
angledeg

一些比例尺参数调整的示例如下

  1. nybb = gpd.read_file(gpd.datasets.get_path("nybb")).to_crs(32619)
  2. ax = nybb.plot()
  3. # 改变位置和方向
  4. scale1 = ScaleBar(
  5. dx=1,
  6. label="Scale 1",
  7. location="lower left", # 位置
  8. label_loc="left",
  9. scale_loc="top", # 注释文字相对于横线方向位置
  10. )
  11. # 改变颜色
  12. scale2 = ScaleBar(
  13. dx=1,
  14. label="Scale 2",
  15. location="center",
  16. color="#b32400",
  17. box_color="yellow",
  18. box_alpha=0.8, # 透明度
  19. )
  20. # 改变文字
  21. scale3 = ScaleBar(
  22. dx=1,
  23. label="Scale 3",
  24. font_properties={
  25. "size": "large",
  26. },
  27. location="lower right", # 位置
  28. scale_formatter=lambda value, unit: f"> {value} {unit} <",
  29. )
  30. # 改变长度
  31. scale4 = ScaleBar(
  32. dx=1,
  33. label="Scale 4",
  34. length_fraction=0.5, # 表示刻度线占绘图区域的50%
  35. scale_loc="top",
  36. label_loc="left",
  37. border_pad=1,
  38. pad=0.25,
  39. )
  40. ax.add_artist(scale1)
  41. ax.add_artist(scale2)
  42. ax.add_artist(scale3)
  43. ax.add_artist(scale4)
<matplotlib_scalebar.scalebar.ScaleBar at 0x7f753de7d850>
c7b1b872e5ca909ba739d6450ce35d0c.png
png

4 图层操作与几何运算

4.1 图层叠加

在geopandas中,overlay()函数是用于将两个地理图层进行叠加分析的函数。它可以进行求交集、并集、差集和对称差集等操作。overlay()函数的基本语法如下:

geopandas.overlay(layer1, layer2, how)

其中,layer1和layer2是两个geopandas地理图层对象,how是一个字符串,指定要进行的叠加操作。how参数有以下取值:

  • intersection:交集

  • union:并集

  • difference:差集

  • symmetric_difference:对称差集

  • identity

在下面的示例中展示overlay函数的使用方式。

准备数据

  1. import geopandas as gpd
  2. import pandas as pd
  3. from shapely.geometry import Polygon, Point
  1. # 画一个圆
  2. center = Point(2, 2) # 圆心坐标
  3. radius = 1 # 圆的半径
  4. circle = center.buffer(radius)
  5. gdf1 = gpd.GeoDataFrame({'geometry': circle, 'circle':[0]})
  6. gdf1.plot()
<matplotlib.axes._subplots.AxesSubplot at 0x7f74a2e0fc10>
3c4c98fefd01f760572c3a91bd683dc7.png
png
gdf1

geometrycircle
0POLYGON ((3.00000 2.00000, 2.99518 1.90198, 2....0
  1. # 画两个正方形
  2. square = gpd.GeoSeries([Polygon([(0, 0), (2, 0), (2, 2), (0, 2)]),
  3. Polygon([(2, 2), (4, 2), (4, 4), (2, 4)])])
  4. gdf2 = gpd.GeoDataFrame({'geometry': square, 'square':[0,1]})
  5. gdf2.plot()
<matplotlib.axes._subplots.AxesSubplot at 0x7f753db6ab20>
c98775db6610d0759a19e854010b260f.png
png
gdf2

geometrysquare
0POLYGON ((0.00000 0.00000, 2.00000 0.00000, 2....0
1POLYGON ((2.00000 2.00000, 4.00000 2.00000, 4....1
  1. # 展示共同绘图结果
  2. ax = gdf1.plot()
  3. gdf2.plot(ax=ax)
<matplotlib.axes._subplots.AxesSubplot at 0x7f75ed2480a0>
0d4674ed5c6f25c4285e2760bdc6ed49.png
png

交集intersection

  1. # 需要pip install rtree
  2. gdf = gpd.overlay(gdf1, gdf2, how='intersection')
  3. gdf

circlesquaregeometry
000POLYGON ((1.90198 1.00482, 1.80491 1.01921, 1....
101POLYGON ((2.09802 2.99518, 2.19509 2.98079, 2....
gdf.plot(cmap="tab10")
<matplotlib.axes._subplots.AxesSubplot at 0x7f75ed10acd0>
43eb5ad7ce64e04f9298a90e492065db.png
png

并集union

  1. # 需要pip install rtree
  2. gdf = gpd.overlay(gdf1, gdf2, how='union')
  3. gdf

circlesquaregeometry
00.00.0POLYGON ((1.90198 1.00482, 1.80491 1.01921, 1....
10.01.0POLYGON ((2.09802 2.99518, 2.19509 2.98079, 2....
20.0NaNMULTIPOLYGON (((1.00000 2.00000, 1.00482 2.098...
3NaN0.0POLYGON ((2.00000 0.00000, 0.00000 0.00000, 0....
4NaN1.0POLYGON ((2.00000 4.00000, 4.00000 4.00000, 4....
gdf.plot(cmap="tab10")
<matplotlib.axes._subplots.AxesSubplot at 0x7f75ed15deb0>
35797451cac108310c163d7392740f34.png
png

差集difference

  1. # 需要pip install rtree
  2. # 提取在gdf1中,但不在gdf2中的区域
  3. gdf = gpd.overlay(gdf1, gdf2, how='difference')
  4. # 也可以用以下写法更加直观
  5. # gdf = gdf1.overlay(gdf2, how='difference')
  6. gdf

geometrycircle
0MULTIPOLYGON (((1.00000 2.00000, 1.00482 2.098...0
gdf.plot(cmap="tab10")
<matplotlib.axes._subplots.AxesSubplot at 0x7f75ed0d27f0>
194b961479a2ee42a30539a5961460a0.png
png

对称差集symmetric_difference

  1. # 需要pip install rtree
  2. # 提取不在gdf1和pdf2交集的区域
  3. gdf = gpd.overlay(gdf1, gdf2, how='symmetric_difference')
  4. gdf

circlesquaregeometry
00.0NaNMULTIPOLYGON (((1.00000 2.00000, 1.00482 2.098...
1NaN0.0POLYGON ((2.00000 0.00000, 0.00000 0.00000, 0....
2NaN1.0POLYGON ((2.00000 4.00000, 4.00000 4.00000, 4....
gdf.plot(cmap="tab10")
<matplotlib.axes._subplots.AxesSubplot at 0x7f75ed259070>
13eaca5bb8beb7d1fa9701698ca9fafb.png
png

identity

identity是ArcGIS中常用的操作。意思是将源地理图层与参考图层进行比较,以在源图层中标识与参考图层中相交的区域。使用identity的一个典型场景是当需要分析两个图层交集的时候。例如,可能有一个图层包含了所有的道路,另一个图层包含了所有的建筑。通过使用identity可以找到所有的建筑物位于哪些道路上。

  1. # 需要pip install rtree
  2. gdf = gpd.overlay(gdf1, gdf2, how='identity')
  3. gdf

circlesquaregeometry
00.00.0POLYGON ((1.90198 1.00482, 1.80491 1.01921, 1....
10.01.0POLYGON ((2.09802 2.99518, 2.19509 2.98079, 2....
20.0NaNMULTIPOLYGON (((1.00000 2.00000, 1.00482 2.098...
gdf.plot(cmap="tab10")
<matplotlib.axes._subplots.AxesSubplot at 0x7f75ed256d60>
219d0cec3bb25893890271a416078711.png
png

4.2 空间连接

空间连接允许将两个或多个空间数据集合并成一个新的数据集。例如,我们有两个数据集,一个包含所有城市的边界,另一个包含所有的人口数据。通过空间连接,我们可以将这两个数据集合并成一个新的数据集,其中每个城市都会有相应的人口数据。GeoPandas提供sjoin函数将两个GeoDataFrame数据集基于空间关系进行连接。sjoin函数常用参数如下:

sjoin(left_df, right_df, how='inner', op='intersects', lsuffix='left', rsuffix='right')

其中,参数含义如下:

  • left_df:左侧的GeoDataFrame数据集。

  • right_df:右侧的GeoDataFrame数据集。

  • how:连接方式,可选项如下:

    • inner (默认选项):返回两个GeoDataFrame中具有共同空间索引的几何体的交集。

    • left:返回左侧GeoDataFrame中的所有几何体,以及右侧GeoDataFrame中与之相交的几何体。如果右侧GeoDataFrame中没有与左侧相交的几何体,则右侧数据中的所有列都将为null。

    • right:与left相反,返回右侧GeoDataFrame中的所有几何体,以及左侧GeoDataFrame中与之相交的几何体。如果左侧GeoDataFrame中没有与右侧相交的几何体,则左侧数据中的所有列都将为null。

  • predicate:连接的空间关系,常用选项如下:

    • intersects (默认选项):返回两个几何体相交的所有几何体。

    • contains:返回左侧GeoDataFrame中包含于右侧GeoDataFrame中的所有几何体。

    • within:返回右侧GeoDataFrame中包含于左侧GeoDataFrame中的所有几何体。

    • touches:返回两个几何体相切的所有几何体。

    • crosses:返回两个几何体相交但不相切的所有几何体。

    • overlaps:返回两个几何体部分重叠的所有几何体。

  • lsuffix:组合后左侧数据集中几何对象列的后缀,默认为left

  • rsuffix:组合后右侧数据集中几何对象列的后缀,默认为right

以下示例展示了如何使用sjoin函数进行空间连接。

准备数据

  1. # 创建点 GeoDataFrame
  2. points = gpd.GeoDataFrame(
  3. [
  4. {'id': 'p1', 'geometry': Point(0, 0)},
  5. {'id': 'p2', 'geometry': Point(1, 1)},
  6. {'id': 'p3', 'geometry': Point(2, 2)},
  7. {'id': 'p4', 'geometry': Point(3, 3)}
  8. ],
  9. crs='EPSG:4326'
  10. )
  11. points

idgeometry
0p1POINT (0.00000 0.00000)
1p2POINT (1.00000 1.00000)
2p3POINT (2.00000 2.00000)
3p4POINT (3.00000 3.00000)
points.plot()
<matplotlib.axes._subplots.AxesSubplot at 0x7f75ed360bb0>
f7819d248a62803620a7987f7f143807.png
png
  1. # 创建多边形 GeoDataFrame
  2. polygons = gpd.GeoDataFrame(
  3. [
  4. {'id': 'P1', 'geometry': Polygon([(0, 0), (0, 2), (2, 2), (2, 0)])},
  5. {'id': 'P2', 'geometry': Polygon([(1, 1), (1, 3), (3, 3), (3, 1)])}
  6. ],
  7. crs='EPSG:4326'
  8. )
  9. polygons

idgeometry
0P1POLYGON ((0.00000 0.00000, 0.00000 2.00000, 2....
1P2POLYGON ((1.00000 1.00000, 1.00000 3.00000, 3....
polygons.plot()
<matplotlib.axes._subplots.AxesSubplot at 0x7f75ed49deb0>
41ea21aeda6b7bc08b7e55bbe12333ad.png
png

sjoin函数

  1. # 左连接
  2. join_left_df = points.sjoin(polygons, how="left")
  3. # 输出结果中的每一行都表示左侧GeoDataFrame中的一个几何对象与右侧GeoDataFrame中的一个几何对象进行了连接后得到的结果。
  4. # index_right表示右侧GeoDataFrame中的行索引
  5. # id_left:表示左侧GeoDataFrame中的几何对象的ID
  6. # id_right:表示右侧GeoDataFrame中的几何对象的ID
  7. # geometry:表示连接后的几何对象
  8. join_left_df

id_leftgeometryindex_rightid_right
0p1POINT (0.00000 0.00000)0P1
1p2POINT (1.00000 1.00000)0P1
1p2POINT (1.00000 1.00000)1P2
2p3POINT (2.00000 2.00000)0P1
2p3POINT (2.00000 2.00000)1P2
3p4POINT (3.00000 3.00000)1P2
  1. # 右连接
  2. join_right_df = points.sjoin(polygons, how="right")
  3. join_right_df

index_leftid_leftid_rightgeometry
00p1P1POLYGON ((0.00000 0.00000, 0.00000 2.00000, 2....
01p2P1POLYGON ((0.00000 0.00000, 0.00000 2.00000, 2....
02p3P1POLYGON ((0.00000 0.00000, 0.00000 2.00000, 2....
11p2P2POLYGON ((1.00000 1.00000, 1.00000 3.00000, 3....
12p3P2POLYGON ((1.00000 1.00000, 1.00000 3.00000, 3....
13p4P2POLYGON ((1.00000 1.00000, 1.00000 3.00000, 3....
  1. # 设置predicate
  2. join_right_within_df = points.sjoin(polygons, how="left", predicate="contains")
  3. join_right_within_df

id_leftgeometryindex_rightid_right
0p1POINT (0.00000 0.00000)NaNNaN
1p2POINT (1.00000 1.00000)NaNNaN
2p3POINT (2.00000 2.00000)NaNNaN
3p4POINT (3.00000 3.00000)NaNNaN

4.3 几何操作

GeoPandas提供了多种用于几何操作的函数,具体如下:

  • 构造方法

    • buffer(distance, resolution=16):返回一个GeoSeries,其中包含与每个几何对象距离在给定距离内的所有点的几何形状。

    • boundary:返回一个GeoSeries,其中包含每个几何形状的集合理论边界的低维对象。

    • centroid:返回一个GeoSeries,其中包含每个几何质心的点。

    • convex_hull:返回一个GeoSeries,其中包含表示包含每个对象中所有点的最小凸多边形的几何形状,除非对象中的点数小于三个。对于两个点,凸包会折叠成一个线串;对于一个点,凸包是一个点。

    • envelope:返回一个GeoSeries,其中包含包含每个对象的点或最小矩形多边形(其边与坐标轴平行)的几何形状。

    • simplify(tolerance, preserve_topology=True):返回一个GeoSeries,其中包含每个对象的简化表示。在geopandas中,simplify函数可以用来简化多边形的形状,以减少地图数据的大小,同时也可以提高绘图的效率。当绘图数据特别大时,该函数很有用。tolerance:简化容差值,代表简化几何对象的形状后的最大允许误差。当 tolerance 值越小时,简化后的几何对象的形状越接近原始几何对象的形状。preserve_topology:是否保持拓扑结构,默认值为True,表示保持拓扑结构。

    • unary_union:返回一个几何形状,其中包含GeoSeries中所有几何形状的联合。

  • 几何变化方法

    • affine_transform(self, matrix):使用仿射变换矩阵来变换 GeoSeries 的几何形状。matrix 为一个包含6、12个元素的列表或元组(2d情况、3d情况)的仿射变换矩阵。关于 matrix 参数的使用需要有仿射变换的知识。

    • rotate(ngle, origin='center', use_radians=False):旋转 GeoSeries 的坐标系。

    • scale(xfact=1.0, yfact=1.0, zfact=1.0, origin='center'):沿着(x, y, z)三个方向缩放 GeoSeries 的几何形状。

    • skew(xs=0.0, ys=0.0, origin='center', use_radians=False):基于原点origin,沿着 x 和 y 两个方向倾斜/扭曲 GeoSeries 的几何形状。

    • translate(xoff=0.0, yoff=0.0, zoff=0.0):平移 GeoSeries 的坐标系。

构造方法使用示例

  1. import geopandas as gpd
  2. # 加载数据集
  3. world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
  4. # 展示结果
  5. world.plot()
<matplotlib.axes._subplots.AxesSubplot at 0x7f75ed587670>
7361cc19450a3d73d8ec30146d154458.png
png
  1. # buffer函数
  2. buffered = world.geometry.buffer(distance=5)
  3. # 显示结果
  4. buffered.plot()
<matplotlib.axes._subplots.AxesSubplot at 0x7f74a32e7a30>
6f98b00e34383cf1a5cd1f4b91c8987c.png
png
  1. # 获取几何形状边界
  2. boundary = world.geometry.boundary
  3. # 显示结果
  4. boundary.plot()
<matplotlib.axes._subplots.AxesSubplot at 0x7f74a3183fd0>
3f3a17af0f150b41ff95460875369a87.png
png
  1. # 获取几何质心
  2. centroids = world.geometry.centroid
  3. # 显示结果
  4. centroids.plot(marker='*', color='green', markersize=5)
<matplotlib.axes._subplots.AxesSubplot at 0x7f74a31560d0>
e011c5e410ff1694975d1df8d038c537.png
png
  1. # 获取几何形状的凸包
  2. convex_hulls = world.geometry.convex_hull
  3. # 显示结果
  4. convex_hulls.plot(alpha=0.5, edgecolor='k')
<matplotlib.axes._subplots.AxesSubplot at 0x7f75ed012eb0>
1f05f1c1fb70246d15f51f90968ea61e.png
png
  1. # 获取几何形状的外接矩形
  2. envelopes = world.geometry.envelope
  3. # 显示结果
  4. envelopes.plot(alpha=0.5, edgecolor='k')
<matplotlib.axes._subplots.AxesSubplot at 0x7f753dee4940>
d5ef692e3ef5295e48d4b00c92e15815.png
png
  1. # 对几何对象进行简化处理
  2. simplified = world.geometry.simplify(tolerance=0.1)
  3. # 显示结果
  4. simplified.plot(alpha=0.5, edgecolor='k')
<matplotlib.axes._subplots.AxesSubplot at 0x7f753dd89a60>
d1004b6af6420784a31c1dca47a1211a.png
png
  1. merged = world.geometry.unary_union
  2. # 将合并后的几何对象转换为GeoDataFrame
  3. gdf_merged = gpd.GeoDataFrame(geometry=[merged])
  4. # 打印后只有一行
  5. print(gdf_merged)
  6. gdf_merged.plot()
  1. geometry
  2. 0 MULTIPOLYGON (((-162.440 -79.281, -163.027 -78...
  3. <matplotlib.axes._subplots.AxesSubplot at 0x7f753dd36d60>
c31b4d7bb95e705bf0a386859dc2f31f.png
png

几何变化方法使用示例

  1. # 读取数据集
  2. import geopandas as gpd
  3. nybb = gpd.read_file(gpd.datasets.get_path('nybb'))
  4. ax = nybb.plot()
  5. # 启用科学计数法
  6. ax.ticklabel_format(style='sci', axis='both', scilimits=(0,0))
aab13d5daeb504f8b4d5a77c9ad8ebaf.png
png
  1. from shapely.affinity import affine_transform
  2. # 仿射变换
  3. # 定义仿射变换参数
  4. a, b, d, e, xoff, yoff = 1.5, 0.5, 0, 0.5, 1.5, 0
  5. tmp = nybb.copy()
  6. # 对nybb数据集中的几何对象进行仿射变换
  7. tmp['geometry'] = tmp['geometry'].apply(lambda x: affine_transform(x, [a, b, d, e, xoff, yoff]))
  8. # 显示变换后的nybb数据集
  9. tmp.plot()
<matplotlib.axes._subplots.AxesSubplot at 0x7f753dee4f10>
e30f4721c900be2204a49ae171a94cf6.png
png
  1. # 旋转
  2. nybb_rotate = nybb.geometry.rotate(angle=45)
  3. nybb_rotate.plot()
<matplotlib.axes._subplots.AxesSubplot at 0x7f753d983d60>
06f8bfb428e6e13e8c63d58db0c2d573.png
png
  1. # 缩放
  2. nybb_scale = nybb.geometry.scale(xfact=2, yfact=2, zfact=1)
  3. nybb_scale.plot()
<matplotlib.axes._subplots.AxesSubplot at 0x7f753b71aee0>
02220771be624f596256d4892b7def64.png
png
  1. # 倾斜/扭曲
  2. nybb_skew = nybb.geometry.skew(xs=0.1, ys=0.2, use_radians=True)
  3. ax = nybb_skew.plot()
  4. # 启用科学计数法
  5. ax.ticklabel_format(style='sci', axis='both', scilimits=(0,0))
b542dadb5dd0567217a2c0233f8bc15a.png
png
  1. # 平移
  2. nybb_translated = nybb.geometry.translate(xoff=100000, yoff=100000, zoff=0)
  3. ax = nybb_translated.plot()
  4. # 启用科学计数法
  5. ax.ticklabel_format(style='sci', axis='both', scilimits=(0,0))
3ef61c2718ea2971a3cd89ff4df35853.png
png

4.4 汇总

在geopandas中,dissolve函数可以对具有相同属性值的几何对象进行合并,从而生成新的几何对象。在汇总过程中,可以选择保留某些字段的信息,也可以对其他字段进行统计计算。dissolve函数如下:

geopandas.GeoDataFrame.dissolve(by=None, aggfunc='first', as_index=True, **kwargs)

函数参数介绍:

  • by: 可以是一个字段名,也可以是一列字段名的列表。表示按照哪些字段进行汇总。默认为None,即将所有要素合并成一个要素。

  • aggfunc: 统计函数,用于对其他字段进行计算,可以是以下函数之一:

    • 'first': 返回第一个非空值。

    • 'last': 返回最后一个非空值。

    • 'mean': 返回平均值。

    • 'sum': 返回总和。

    • 'min': 返回最小值。

    • 'max': 返回最大值。

    • 自定义函数:可以传入自定义的聚合函数。

  • as_index: 是否将by参数指定的字段作为行索引,默认为True

  • *kwargs: 其他参数。

下面示例代码演示了dissolve函数的使用。

  1. import geopandas as gpd
  2. # 读取湖北省地图数据
  3. data = gpd.read_file('https://geo.datav.aliyun.com/areas_v3/bound/420000_full.json')
  4. data.head()

adcodenamechildrenNumlevelparentsubFeatureIndexgeometry
0420100武汉市13city{'adcode': 420000}0MULTIPOLYGON (((113.71000 30.38892, 113.70961 ...
1420200黄石市6city{'adcode': 420000}1MULTIPOLYGON (((114.54626 30.06280, 114.54502 ...
2420300十堰市8city{'adcode': 420000}2MULTIPOLYGON (((111.04672 33.20292, 111.03242 ...
3420500宜昌市13city{'adcode': 420000}3MULTIPOLYGON (((112.07982 30.65932, 112.08643 ...
4420600襄阳市9city{'adcode': 420000}4MULTIPOLYGON (((111.58304 32.59654, 111.58514 ...
data.plot(cmap='tab20')
<matplotlib.axes._subplots.AxesSubplot at 0x7f753d7ad850>
7aa91328152ba1743f1ac65b75f581d9.png
png
  1. # 使用dissolve函数合并几何体,根据地级市的区县数分组
  2. dissolve_data = data.dissolve(by='childrenNum')
  3. dissolve_data.head()

geometryadcodenamelevelparentsubFeatureIndex
childrenNum





0MULTIPOLYGON (((113.02499 30.18293, 113.02826 ...429004仙桃市city{'adcode': 420000}13
3MULTIPOLYGON (((115.06176 30.26142, 115.05617 ...420700鄂州市city{'adcode': 420000}5
5MULTIPOLYGON (((112.06071 30.68840, 112.06988 ...420800荆门市city{'adcode': 420000}6
6MULTIPOLYGON (((114.94866 29.52531, 114.96668 ...420200黄石市city{'adcode': 420000}1
7MULTIPOLYGON (((113.43656 30.49471, 113.44782 ...420900孝感市city{'adcode': 420000}7
dissolve_data.plot(cmap='tab20')
<matplotlib.axes._subplots.AxesSubplot at 0x7f753da46ee0>
4b968cd031f184e95aeb19b370c49bfc.png
png
  1. # 使用dissolve函数合并几何体,根据地级市的区县数分组,其他列求均值
  2. dissolve_data = data.dissolve(by='childrenNum', aggfunc='mean')
  3. dissolve_data.head()

geometryadcodesubFeatureIndex
childrenNum


0MULTIPOLYGON (((113.02499 30.18293, 113.02826 ...429009.014.5
3MULTIPOLYGON (((115.06176 30.26142, 115.05617 ...421000.08.0
5MULTIPOLYGON (((112.06071 30.68840, 112.06988 ...420800.06.0
6MULTIPOLYGON (((114.94866 29.52531, 114.96668 ...420700.05.5
7MULTIPOLYGON (((113.43656 30.49471, 113.44782 ...420900.07.0

4.5  缺失值与空值处理

在使用geopandas处理地理空间数据时,经常会遇到NoneEmpty这两个概念。虽然它们都表示缺失值,但它们之间有着一些区别。

  • None:表示属性或者列的值不存在,或者没有被填充。在geopandas中,如果一个geometry列的值为None,那意味着这个几何对象不存在。

  • Empty:表示属性或者列的值存在,但是值为空。在geopandas中,如果一个geometry列的值为空,那意味着这个几何对象是存在的,但是它没有任何形状或者坐标信息。

以下为具有一个多边形、一个缺失值和一个空多边形的GeoSeries示例:

  1. from shapely.geometry import Polygon
  2. s = gpd.GeoSeries([Polygon([(0, 0), (1, 1), (0, 1)]), None, Polygon([])])
  3. s
  1. 0 POLYGON ((0.00000 0.00000, 1.00000 1.00000, 0....
  2. 1 None
  3. 2 POLYGON EMPTY
  4. dtype: geometry

在geopandas空间运算中,缺失的几何图形通常会传播。在结果中,这些缺失的几何图形也会缺失。另一方面,空的几何图形被视为几何图形。结果将取决于所进行的运算。如下所示:

s.area
  1. 0 0.5
  2. 1 NaN
  3. 2 0.0
  4. dtype: float64

我们可以通过isna函数和is_empty属性判断是否为缺失值或者空值:

  1. # 判断缺失值
  2. s.isna()
  1. 0 False
  2. 1 True
  3. 2 False
  4. dtype: bool
  1. # 判断空值
  2. s.is_empty
  1. 0 False
  2. 1 False
  3. 2 True
  4. dtype: bool
  1. # 判断缺失或为空
  2. s.is_empty | s.isna()
  1. 0 False
  2. 1 True
  3. 2 True
  4. dtype: bool
  1. # 提取既不缺失也不为空的值
  2. s[~(s.is_empty | s.isna())]
  1. 0 POLYGON ((0.00000 0.00000, 1.00000 1.00000, 0....
  2. dtype: geometry

5 背景地图叠加

contextily[7]是一个Python库,它提供了一种简单的方法将背景地图(通常是Web瓦片地图,如OpenStreetMap、Stamen Maps、Mapbox等)添加到地理空间数据可视化中。使用contextily库可以使地理空间数据可视化更加生动、直观,同时可以提供更多的地理信息。瓦片地图是一种基于网格的地图显示方式,将地图划分为多个小块,每个小块称为“瓦片”,每个瓦片都有自己的坐标和编号。这些瓦片可以按需加载,使用户能够快速地浏览地图,同时减少了加载时间和资源消耗。瓦片地图常用于在线地图应用程序,例如谷歌地图和百度地图。 contextily支持使用WGS84 (EPSG:4326)和Spheric Mercator (EPSG:3857)坐标系,在Web地图应用程序中,一般使用EPSG:3857(以米为单位)来显示瓦片地图,并使用EPSG:4326(以经纬度为单位)来标记瓦片地图上的位置。

contextily库的主要功能包括:

  • 从Web地图提供商获取地图图层

  • 将地图图层与地理空间数据集合并

  • 使用Matplotlib或Bokeh绘制地图

本文主要介绍contextily简单使用,contextily具体使用可参考其官方文档:contextily-doc[8]。contextily库中基于add_basemap函数在地图上添加背景地图。下面是该函数常用可用参数的介绍:

  • ax: matplotlib axes对象,用于绘制地图

  • crs: 输出地图的坐标系,默认为'EPSG:3857'

  • source: 底图的来源,支持多种来源,如OpenStreetMap、Stamen Terrain、Stamen Toner等等,默认为OpenStreetMap

  • zoom: 底图的缩放级别,默认为None,自动根据ax的extent和crs计算。zoom值越高,底图的缩放级别就越大,地图显示的范围也就越小,细节也会越来越清晰。

  • url: 底图的url地址,默认为None,自动根据source和zoom计算。

  • attribution: 底图的版权信息,默认为None

  • alpha: 底图的透明度,默认为1.0

  • *kwargs: 其他matplotlib.image()函数的可选参数,如cmapvminvmax等等

⚠⚠source参数选择不同底图的来源,可能需要大量时间或者特定网络,如果失败多重试运行代码。

5.1 简单背景地图叠加

  1. import geopandas as gpd
  2. # 读取深圳市地图数据
  3. data = gpd.read_file("https://geo.datav.aliyun.com/areas_v3/bound/440300_full.json")
  4. # 简单绘图
  5. ax = data.plot(alpha=0.5, edgecolor="k")
ea59fc438fab7bd1d48a8ac18f2b662a.png
png
  1. # 确定数据所使用的坐标系
  2. data.crs
  3. # 将数据集所使用坐标系转为EPSG:3857
  4. data_wm = data.to_crs(epsg=3857)
  1. import contextily as cx
  2. import matplotlib.pyplot as plt
  3. fig, ax = plt.subplots(figsize=(10, 10))
  4. ax = data_wm.plot(ax = ax, alpha=0.5, edgecolor="k")
  5. # 将自动下载瓦片地图
  6. cx.add_basemap(ax)
  7. # 保存地图
  8. fig.savefig('save.jpg', pad_inches=0.5, bbox_inches='tight', dpi=300)
cbb2c63326ec323592f6ade19dbe6130.png
png

在上面的代码中,如果仅使用经纬度数据叠加瓦片地图,需要在add_basemap函数中设置crs参数,如下所示:

  1. import contextily as cx
  2. import matplotlib.pyplot as plt
  3. fig, ax = plt.subplots(figsize=(10, 10))
  4. ax = data.plot(ax = ax, alpha=0.5, edgecolor="k")
  5. # 将自动下载瓦片地图
  6. cx.add_basemap(ax, crs=data.crs)
98a99a4334f4dbc6536e07b903d5e087.png
png

可以通过调整zoom参数改变背景瓦片地图的细节程度,建议zoom值不要过大,下载速度太慢。此外可以通过设置attribution=""去除绘图水印。

  1. ax = data_wm.plot(figsize=(10, 10), alpha=0.5, edgecolor="k")
  2. cx.add_basemap(ax, zoom=12, attribution="")
91eba662f4091188e9d26149f79e971d.png
png

5.2 定制化背景地图

通过设置add_basemap的source参数能够指定不同的数据源,以在地图上添加不同类型的底图。如下所示:

  1. ax = data_wm.plot(figsize=(10, 10), alpha=0.5, edgecolor="k")
  2. cx.add_basemap(ax, source=cx.providers.Stamen.TonerLite)
  3. ax.set_axis_off()
84e8d19cb0412553ce29d2337a47ce6b.png
png

当然也可以叠加多个背景地图,如下所示:

  1. ax = data_wm.plot(figsize=(10, 10), alpha=0.5, edgecolor="k")
  2. cx.add_basemap(ax, source=cx.providers.Stamen.TonerLite)
  3. cx.add_basemap(ax, source=cx.providers.Stamen.TonerLabels)
de29fa0f84a27257c9f7d3f210842a3b.png
png

此外也可以叠加多个不同来源的背景图层。如下所示:

  1. ax = data_wm.plot(figsize=(10, 10), alpha=0.5, edgecolor="k")
  2. cx.add_basemap(ax, source=cx.providers.Stamen.Watercolor, zoom=12)
  3. cx.add_basemap(ax, source=cx.providers.Stamen.TonerLabels, zoom=10)
c43014263bc00d48505287acf16c963c.png
png

从上面的案例我们可以看到,contextily通过Provider预置提供商的名称来获取相应的Web瓦片地图。contextily所有预置的地图提供商可以通过以下cx.providers命令获取。可以尝试根据这些提供商定制瓦片地图格式。

# cx.providers

除了使用contextily预置的地图提供商,可以通过source设置给定瓦片地图地址来指定需要添加的底图。例如可以添加天地图,高德地图,腾讯地图的瓦片地图的地址。一些示例的瓦片地图地址可见:高德谷歌腾讯天地图地图瓦片url[9]和在geopandas中叠加在线地图[10]。 一般地图服务提供XYZ瓦片地图链接,其中的xyz代表了地图的坐标系。如下所示:

  • x:表示在地图水平方向上的位置,从左到右递增,即经度值。

  • y:表示在地图竖直方向上的位置,从上到下递增,即纬度值。

  • z:表示地图的缩放级别,从0开始递增,数值越大,地图显示的范围越小,细节越丰富。

在瓦片地图中,地图被分成了许多小块,每个小块都有一个唯一的编号,也就是xyz坐标系。当我们使用地图服务时,通过改变xyz的值,就可以获取到不同位置、不同缩放级别下的地图瓦片,从而达到展示不同地图的目的。直接通过url设置瓦片地图示例如下:

  1. fig, ax = plt.subplots(figsize=(10, 10))
  2. ax = data_wm.plot(ax=ax, alpha=0.5, edgecolor="k")
  3. cx.add_basemap(ax,
  4. source='https://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}',
  5. zoom=12)
  6. fig.savefig('save.jpg', pad_inches=0, bbox_inches='tight', dpi=300)
bf2ceaa585e777f2316ddda48aa24d36.png
png
  1. fig, ax = plt.subplots(figsize=(10, 10))
  2. ax = data_wm.plot(ax=ax, alpha=0.5, edgecolor="k")
  3. cx.add_basemap(ax,
  4. source='https://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}',
  5. zoom=12)
  6. fig.savefig('save.jpg', pad_inches=0, bbox_inches='tight', dpi=300)
  7. ax.set_xlim(data_wm.total_bounds[0], data_wm.total_bounds[2])
  8. ax.set_ylim(data_wm.total_bounds[1], data_wm.total_bounds[3])
(2559177.946084248, 2615308.057854809)
7721fd55cdf6bd124497a937c49fc2dc.png
png

5.3 离线背景地图

在有些时候我们需要离线使用背景瓦片地图,contextly提供bounds2raster函数用于根据给定的空间范围和地图缩放级别,将在线地图服务中的栅格数据下载为本地文件。bounds2raster函数常用参数如下:

  • w:float类型,表示空间范围的最小值。

  • s:float类型,表示空间范围的最小值。

  • e:float类型,表示空间范围的最大值。

  • n:float类型,表示空间范围的最大值。

  • path:str类型,表示下载的栅格数据文件的保存路径。

  • zoom:int或者字符串类型,表示地图缩放级别。如果为字符串类型,可以设置为'auto',表示自动确定最佳的缩放级别。

  • source:str类型,表示地图服务的地址。

  • ll:bool类型,表示w、s、e、n是否使用经纬度坐标系,默认为False。

  • wait:int类型,表示两次下载之间的等待时间,单位为秒。默认为0。

  • max_retries:int类型,表示下载失败后最大的重试次数。默认为2次。

bounds2raster函数返回RGB图像数组和瓦片图像边界框[minX,maxX,minY,maxY],此外由于网络地图总是基于WGS84 Web Mercator(EPSG:3857)坐标系,因此bounds2raster函数返回和保存的图片都是基于EPSG:3857坐标系。

  1. import geopandas as gpd
  2. # 读取郑州市地图数据
  3. data = gpd.read_file("https://geo.datav.aliyun.com/areas_v3/bound/410100_full.json")
  4. # 简单绘图
  5. ax = data.plot(alpha=0.5, edgecolor="k")
c826b928e7e530c20410c2e797b63966.png
png
  1. # 叠加地图
  2. ax = data.plot(alpha=0.5, edgecolor="k")
  3. # crs告诉数据集用的坐标系统,这里data.crs为WGS 84(经纬度)
  4. cx.add_basemap(ax,
  5. crs=data.crs,
  6. source=cx.providers.Stamen.Watercolor
  7. )
b71fd5df134bc41893d388f88570fc7a.png
png

提取待绘图区域的边界信息

  1. west, south, east, north = bbox = data.total_bounds
  2. bbox
array([112.721178,  34.262109, 114.220962,  34.989506])

根据边界信息下载数据

  1. import contextily as cx
  2. import matplotlib.pyplot as plt
  3. img, ext = cx.bounds2raster(west,
  4. south,
  5. east,
  6. north,
  7. "demo.tif",
  8. source=cx.providers.Stamen.Watercolor,
  9. ll=True
  10. )
  1. # 展示下载的数据
  2. plt.axis('off')
  3. plt.imshow(img)
  4. # 边界范围
  5. ext
(12523442.714243276, 12719121.506653327, 4030983.1236470537, 4187526.157575096)
9dc83690e2b730f03c05b71fb586981e.png
png

有了背景地图,add_basemap中的source函数设置文件路径地址就可以离线叠加地图。

  1. ax = data.plot(alpha=0.5, edgecolor="k")
  2. # crs告诉数据集用的坐标系统,这里data.crs为WGS 84(经纬度)
  3. cx.add_basemap(ax,
  4. crs=data.crs,
  5. source="demo.tif",
  6. )
3661db3371985b22749e5c6fd0b611bf.png
png

欢迎关注我的公众号“彭彭加油鸭”,原创技术文章第一时间推送。

fc94c9e3aaba1c6fb0f50745f570736b.gif

参考资料

[1]

GeoPandas: https://github.com/geopandas/geopandas

[2]

GeoPandas-doc: https://geopandas.org/en/stable/docs.html

[3]

GeoPandas Examples Gallery: https://geopandas.org/en/stable/gallery/index.html

[4]

Python绘制数据地图1-GeoPandas入门指北: https://www.cnblogs.com/luohenyueji/p/17222011.html

[5]

Python绘制数据地图2-GeoPandas地图可视化: https://www.cnblogs.com/luohenyueji/p/17299870.html

[6]

matplotlib-scalebar: https://github.com/ppinard/matplotlib-scalebar

[7]

contextily: https://github.com/geopandas/contextily

[8]

contextily-doc: https://contextily.readthedocs.io/en/latest/

[9]

高德谷歌腾讯天地图地图瓦片url: https://zhuanlan.zhihu.com/p/138591824

[10]

在geopandas中叠加在线地图: https://www.cnblogs.com/feffery/p/13763601.html

-------- End --------

推荐

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/煮酒与君饮/article/detail/904940
推荐阅读
相关标签