數據科學 IPython 筆記本 8.16 地理數據和 Basemap

8.16 地理數據和 Basemap

原文:Geographic Data with Basemap

譯者:飛龍

協議:CC BY-NC-SA 4.0

本節是《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);
png

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);
png

通過幾行 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)
png

此視圖的 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)
png

這里 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);
png

圓錐投影

圓錐投影將地圖投影到單個圓錐上,然后展開。這可以產生非常好的局部特性,但是遠離圓錐焦點的區域可能變得非常扭曲。其中一個例子是 Lambert Conformal 圓錐投影(projection='lcc'),我們之前在北美地圖中看到過。

它將地圖投影到一個圓錐上,這個圓錐的排列方式使得兩個標準平行線(在 Basemap 中由lat_1lat_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)
png

其它投影

如果你要對基于地圖的可視化做很多事情,我建議你了解其他可用的投影,以及它們的屬性,優點和缺點。
最有可能的是,它們可以在 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));
png

請注意,低分辨率海岸線不適合此級別的縮放,而高分辨率的工作正常。然而,低水平對于全局視圖來說效果會很好,并且比加載整個地球的高分辨率邊界數據要快得多!可能需要進行一些實驗,才能找到給定視圖的正確分辨率參數:最佳路徑是從快速低分辨率的繪圖開始,并根據需要增加分辨率。

在 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');
png

這向我們展示了大量人口在加利福尼亞定居的地方:它們聚集在洛杉磯和舊金山地區的海岸附近,沿著平坦的中央山谷中的高速公路延伸,幾乎完全避開了沿著國界的山區。

示例:表面溫度數據

作為一些更連續的地理數據的可視化示例,讓我們考慮一下 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)');
png

該數據描繪了該月發生的局部極端溫度異常。美國東部比正常情況要冷得多,而西部和阿拉斯加的溫度要高得多。沒有記錄溫度的區域顯示地圖背景。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容