8.16 地理數據和 Basemap
原文:Geographic Data with Basemap
譯者:飛龍
本節是《Python 數據科學手冊》(Python Data Science Handbook)的摘錄。
數據科學中一種常見的可視化類型是地理數據。Matplotlib 用于此類可視化的主要工具是 Basemap 工具包,它是位于mpl_toolkits
命名空間下的幾個 Matplotlib 工具包之一。不可否認,Basemap 使用時有點笨拙,甚至簡單的可視化渲染也要花費更長的時間,超出你的想象。
更傳統的解決方案(如 leaflet 或 Google Maps API)可能是更加密集的地圖可視化的更好選擇。盡管如此,Basemap 仍然是 Python 用戶在其虛擬工具欄中擁有的有用工具。在本節中,我們將展示使用此工具包可以實現的地圖可視化類型的幾個示例。
Basemap 的安裝很簡單;如果你正在使用 conda,你可以輸入這個,然后下載包:
$ conda install basemap
我們只在標準樣板中添加一個新的導入:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
一旦安裝并導入了 Basemap 工具包,地理繪圖就能在幾行之內實現(下面的圖形也需要 Python 2 中的PIL
包,或者 Python 3 中的pillow
包):
plt.figure(figsize=(8, 8))
m = Basemap(projection='ortho', resolution=None, lat_0=50, lon_0=-100)
m.bluemarble(scale=0.5);
Basemap
參數的含義將立即討論。
有用的是這里顯示的地球不僅僅是一個圖像; 它是一個功能齊全的 Matplotlib 軸域,它可以理解球面坐標,這使我們可以輕松地在地圖上繪制數據!例如,我們可以使用不同的地圖投影,放大到北美并繪制西雅圖的位置。我們將使用 etopo 圖像(顯示陸地和海底的地形特征)作為地圖背景:
fig = plt.figure(figsize=(8, 8))
m = Basemap(projection='lcc', resolution=None,
width=8E6, height=8E6,
lat_0=45, lon_0=-100,)
m.etopo(scale=0.5, alpha=0.5)
# 將 (long, lat) 映射為 (x, y) 以便繪圖
x, y = m(-122.3, 47.6)
plt.plot(x, y, 'ok', markersize=5)
plt.text(x, y, ' Seattle', fontsize=12);
通過幾行 Python,你可以輕松了解可能的地理可視化類型。我們現在將更深入地討論 Basemap 的功能,并提供幾個可視化地圖數據的示例。使用這些簡短的示例作為積木,你應該能夠創建幾乎任何你想要的地圖可視化。
地圖投影
使用地圖時要決定的第一件事,是要使用什么投影。你可能已經熟悉這樣一個事實:不可能將球形地圖(例如地球的地圖)投影到平坦的表面上,而不會以某種方式扭曲或破壞其連續性。這些預測是在人類歷史進程中發展起來的,有很多選擇!取決于地圖投影的預期用途,有一些地圖特征,保留它們很有用(例如,方向,區域,距離,形狀或其他考慮因素)。
Basemap 包實現了幾十個這樣的投影,全部由短格式代碼引用。在這里,我們將簡要介紹一些比較常見的。我們首先定義一個便利例程來繪制我們的世界地圖以及經緯線:
from itertools import chain
def draw_map(m, scale=0.2):
# 繪制陰影浮雕圖像
m.shadedrelief(scale=scale)
# 將 lats 和 lons 作為字典返回
lats = m.drawparallels(np.linspace(-90, 90, 13))
lons = m.drawmeridians(np.linspace(-180, 180, 13))
# 鍵包含 plt.Line2D 實例
lat_lines = chain(*(tup[1][0] for tup in lats.items()))
lon_lines = chain(*(tup[1][0] for tup in lons.items()))
all_lines = chain(lat_lines, lon_lines)
# 遍歷這些線并設置所需的樣式
for line in all_lines:
line.set(linestyle='-', alpha=0.3, color='w')
圓柱投影
最簡單的地圖投影是圓柱投影,其中恒定緯度和經度的線分別映射到水平線和垂直線。這種類型的映射很好地代表了赤道區域,但產生了極點附近的極端扭曲。緯線的間距在不同的圓柱投影之間變化,產生不同的保留特征,并且在極點附近的不同的變形。在下圖中,我們展示了等距圓柱投影的示例,它選擇了沿子午線保留距離的緯度縮放。其他圓柱投影是墨卡托(projection='merc'
)和圓柱等積(projection='cea'
)投影。
fig = plt.figure(figsize=(8, 6), edgecolor='w')
m = Basemap(projection='cyl', resolution=None,
llcrnrlat=-90, urcrnrlat=90,
llcrnrlon=-180, urcrnrlon=180, )
draw_map(m)
此視圖的 Basemap 的附加參數,為所需的地圖指定左下角(llcrnr
)和右上角(urcrnr
)的緯度(lat
)和經度(lon
),以度為單位。
偽圓柱投影
偽圓柱投影放松了子午線(恒定經線)保持垂直的要求; 這可以在投影的極點附近提供更好的特性。墨卡托投影(projection ='moll'
)是這方面的一個常見例子,其中所有經線都是橢圓弧。它的構造是為了保留地圖上的區域:盡管兩極附近存在扭曲,但小塊的區域反映了真實區域。其他偽圓柱投影是正弦曲線(projection='sinu'
)和羅賓遜(projection='robin'
)投影。
fig = plt.figure(figsize=(8, 6), edgecolor='w')
m = Basemap(projection='moll', resolution=None,
lat_0=0, lon_0=0)
draw_map(m)
這里 Basemap 的額外參數是指所需映射的中心緯度(lat_0
)和經度(lon_0
)。
透視投影
透視投影使用透視點的特定選擇構建,類似于你從空間中的特定點拍攝地球(對于某些投影,技術上點位于地球內部!)。一個常見的例子是正交投影(projection='ortho'
),它顯示了遠處的觀察者看到的地球的一側。因此,它一次只能顯示全球的一半。
其他基于視角的投影包括 gnomonic 投影(projection='gnom'
)和立體投影(projection='stere'
)。這些通常對于顯示地圖的一小部分最有用。
以下是正交投影的示例:
fig = plt.figure(figsize=(8, 8))
m = Basemap(projection='ortho', resolution=None,
lat_0=50, lon_0=0)
draw_map(m);
圓錐投影
圓錐投影將地圖投影到單個圓錐上,然后展開。這可以產生非常好的局部特性,但是遠離圓錐焦點的區域可能變得非常扭曲。其中一個例子是 Lambert Conformal 圓錐投影(projection='lcc'
),我們之前在北美地圖中看到過。
它將地圖投影到一個圓錐上,這個圓錐的排列方式使得兩個標準平行線(在 Basemap 中由lat_1
和lat_2
規定)的距離是良好表示的,比例在它們之間減小并且在它們之外增加。其他有用的圓錐投影是等距圓錐投影(projection='eqdc'
)和 Albers 等面投影(projection='aea'
)。圓錐投影,就像透視投影,往往是表示地球中小塊區域的良好選擇。
fig = plt.figure(figsize=(8, 8))
m = Basemap(projection='lcc', resolution=None,
lon_0=0, lat_0=50, lat_1=45, lat_2=55,
width=1.6E7, height=1.2E7)
draw_map(m)
其它投影
如果你要對基于地圖的可視化做很多事情,我建議你了解其他可用的投影,以及它們的屬性,優點和缺點。
最有可能的是,它們可以在 Basemap 包中找到。
如果你深入研究這個主題,你會發現一個令人難以置信的極客的亞文化 geo-viz,他們為熱烈爭論做好了準備,來為任何給定應用支持他們最喜歡的投影!
繪制地圖背景
之前我們看過bluemarble()
和shadedrelief()
方法,用于在地圖上投影全球圖像,以及drawparallels()
和drawmeridians()
方法用于繪制恒定經緯度的線。Basemap 包包含一系列有用的函數,用于繪制物理特征的邊界,例如大陸,海洋,湖泊和河流等,以及政治邊界,例如國家地區,以及美國各州和縣。以下是一些可用繪圖功能,你可能希望使用 IPython 幫助特性來探索:
-
物理邊界和水體
-
drawcoastlines()
:繪制大陸海岸線 -
drawlsmask()
:繪制陸地和海洋之間的掩碼,用于在一個或另一個上投射圖像 -
drawmapboundary()
:繪制地圖邊界,包括海洋的填充顏色。 -
drawrivers()
:在地圖上繪制河流 -
fillcontinents()
:用給定的顏色填充大陸;可選擇用另一種顏色填充湖泊
-
-
政治邊界
-
drawcountries()
:繪制國界 -
drawstates()
:繪制美國國界 -
drawcounties()
:繪制美國縣界
-
-
地圖功能
-
drawgreatcircle()
:在兩點之間繪制大圓圈 -
drawparallels()
:繪制恒定緯度的線條 -
drawmeridians()
:繪制恒定經度的線條 -
drawmapscale()
:在地圖上繪制線性刻度
-
-
全球圖像
-
bluemarble()
:將 NASA 的藍色大理石圖像投影到地圖上 -
shadedrelief()
:將陰影浮雕圖像投影到地圖上 -
etopo()
:在地圖上繪制一個 etopo 浮雕圖像 -
warpimage()
:將用戶提供的圖像投影到地圖上
-
對于基于邊界的特性,必須在創建 Basemap 圖像時設置所需的分辨率。Basemap
類的resolution
參數設置邊界中的細節級別,他們是'c'
(原始),'l'
(低),'i'`(中),
'h'(高),
'f'``(完整)或None
(如果沒有使用邊界)。這個選項很重要:例如,在全局地圖上設置高分辨率邊界可能非常慢。
這是繪制陸地/海洋邊界,以及分辨率參數的效果的示例。我們將創建蘇格蘭的美麗的斯凱島的低分辨率和高分辨率地圖。它位于北緯 57.3°,6.2°W,90,000×120,000 公里的地圖很好顯示它:
fig, ax = plt.subplots(1, 2, figsize=(12, 8))
for i, res in enumerate(['l', 'h']):
m = Basemap(projection='gnom', lat_0=57.3, lon_0=-6.2,
width=90000, height=120000, resolution=res, ax=ax[i])
m.fillcontinents(color="#FFDDCC", lake_color='#DDEEFF')
m.drawmapboundary(fill_color="#DDEEFF")
m.drawcoastlines()
ax[i].set_title("resolution='{0}'".format(res));
請注意,低分辨率海岸線不適合此級別的縮放,而高分辨率的工作正常。然而,低水平對于全局視圖來說效果會很好,并且比加載整個地球的高分辨率邊界數據要快得多!可能需要進行一些實驗,才能找到給定視圖的正確分辨率參數:最佳路徑是從快速低分辨率的繪圖開始,并根據需要增加分辨率。
在 Basemap 上繪制數據
也許 Basemap 工具包中最有用的部分,是將各種數據繪制到地圖背景上的能力。對于簡單的繪圖和文本,任何plt
函數都可以在地圖上執行;你可以使用Basemap
實例將緯度和經度坐標投影到(x, y)
坐標,用于plt
的繪圖,正如我們在西雅圖示例中所見。
除此之外,還有許多特定于地圖的函數,可用作Basemap
實例的方法。這些東西與它們的標準 Matplotlib 對應物非常相似,但是有一個額外的布爾參數latlon
,如果設置為True
,它允許你將原始緯度和經度傳遞給方法,而不是投影(x, y)
坐標。
其中一些特定于地圖的方法是:
-
contour()/contourf()
:繪制等高線或填充的等高線 -
imshow()
:繪制圖像 -
pcolor()/pcolormesh()
:為不規則/規則網格繪制偽彩色圖 -
plot()
:繪制線條和/或標記。 -
scatter()
:繪制帶標記的點。 -
quiver()
:繪制向量。 -
barbs()
:繪制風向。 -
drawgreatcircle()
:繪制大圓圈。
我們將繼續并看到其中一些例子。這些函數的更多信息,包括幾個示例圖,請參閱在線 Basemap 文檔。
示例:加利福尼亞的城市
回想一下,在“自定義圖例”中,我們演示了在散點圖中使用大小和顏色,來傳達加州城市的位置,大小和人口的信息。在這里,我們將再次創建此繪圖,但使用 Basemap 將數據放在上下文中。
我們開始加載數據,就像我們之前做的那樣:
import pandas as pd
cities = pd.read_csv('data/california_cities.csv')
# 提取我們感興趣的數據
lat = cities['latd'].values
lon = cities['longd'].values
population = cities['population_total'].values
area = cities['area_total_km2'].values
接下來,我們設置地圖投影,繪制數據的散點圖,然后創建顏色條和圖例:
# 1. 繪制地圖北京
fig = plt.figure(figsize=(8, 8))
m = Basemap(projection='lcc', resolution='h',
lat_0=37.5, lon_0=-119,
width=1E6, height=1.2E6)
m.shadedrelief()
m.drawcoastlines(color='gray')
m.drawcountries(color='gray')
m.drawstates(color='gray')
# 2. 繪制城市數據的散點圖,其中顏色反映人口
# 尺寸反映面積
m.scatter(lon, lat, latlon=True,
c=np.log10(population), s=area,
cmap='Reds', alpha=0.5)
# 3. 創建顏色條和圖例
plt.colorbar(label=r'$\log_{10}({\rm population})$')
plt.clim(3, 7)
# 使用虛擬的點生成圖例
for a in [100, 300, 500]:
plt.scatter([], [], c='k', alpha=0.5, s=a,
label=str(a) + ' km$^2$')
plt.legend(scatterpoints=1, frameon=False,
labelspacing=1, loc='lower left');
這向我們展示了大量人口在加利福尼亞定居的地方:它們聚集在洛杉磯和舊金山地區的海岸附近,沿著平坦的中央山谷中的高速公路延伸,幾乎完全避開了沿著國界的山區。
示例:表面溫度數據
作為一些更連續的地理數據的可視化示例,讓我們考慮一下 2014 年 1 月襲擊美國東部的“極渦”。任何氣候數據的重要來源是美國宇航局 Goddard 空間研究所。這里我們將使用 GIS 250 溫度數據,我們可以使用 shell 命令下載(在 Windows 機器上,這些命令可能必須修改)。此處使用的數據下載于 2016 年 6 月 12 日,文件大小約為 9MB:
# !curl -O http://data.giss.nasa.gov/pub/gistemp/gistemp250.nc.gz
# !gunzip gistemp250.nc.gz
數據采用NetCDF格式,可以通過netCDF4
庫在 Python 中讀取。你可以像此處所示安裝此庫:
$ conda install netcdf4
我們這樣讀取數據:
from netCDF4 import Dataset
data = Dataset('gistemp250.nc')
該文件包含不同日期的許多全球溫度讀數;我們需要選擇我們感興趣的日期的索引 - 這里是 2014 年 1 月 15 日:
from netCDF4 import date2index
from datetime import datetime
timeindex = date2index(datetime(2014, 1, 15),
data.variables['time'])
現在我們可以加載經緯度數據,以及這個索引的溫度異常:
lat = data.variables['lat'][:]
lon = data.variables['lon'][:]
lon, lat = np.meshgrid(lon, lat)
temp_anomaly = data.variables['tempanomaly'][timeindex]
最后,我們將使用pcolormesh()
方法繪制數據的顏色網格。我們將看看北美,并在背景中使用陰影浮雕地圖。請注意,對于此數據,我們專門選擇了一個離散顏色表,其中零處為中性色,負值和正值為兩個對比色。我們還會在顏色上輕輕劃出海岸線以供參考:
fig = plt.figure(figsize=(10, 8))
m = Basemap(projection='lcc', resolution='c',
width=8E6, height=8E6,
lat_0=45, lon_0=-100,)
m.shadedrelief(scale=0.5)
m.pcolormesh(lon, lat, temp_anomaly,
latlon=True, cmap='RdBu_r')
plt.clim(-8, 8)
m.drawcoastlines(color='lightgray')
plt.title('January 2014 Temperature Anomaly')
plt.colorbar(label='temperature anomaly (°C)');
該數據描繪了該月發生的局部極端溫度異常。美國東部比正常情況要冷得多,而西部和阿拉斯加的溫度要高得多。沒有記錄溫度的區域顯示地圖背景。