Python(pandasやNumPy)でデータサイエンスをしたい場合、描画ライブラリとしてはmatplotlibを使うのが現在のデファクトスタンダードで、地理空間データや地図の描画にはcartopyを使うのがよいです。そのcartopyですが、Googleクラウド上のJupyter notebookであるGoogle Colaboratoryで使おうとすると、2020-04現在は、公式の方法ではバグで悩まされ、実質的に使えません。回避策とその説明をこちらにまとめてみました。
結論
以下のどちらかでインストールしましょう。
パターン1
!grep '^deb ' /etc/apt/sources.list | \
sed 's/^deb /deb-src /g' | \
tee /etc/apt/sources.list.d/deb-src.list
!apt-get -qq update
!apt-get -qq build-dep python3-cartopy
!pip uninstall -y shapely
!pip install --no-binary cartopy cartopy==0.17.0
パターン2
!grep '^deb ' /etc/apt/sources.list | \
sed 's/^deb /deb-src /g' | \
tee /etc/apt/sources.list.d/deb-src.list
!apt-get update
!apt-get -qq build-dep python3-cartopy
!apt-get -qq remove python-shapely python3-shapely
!pip install --no-binary shapely shapely --force
!pip install --no-binary cartopy cartopy==0.17.0
公式の方法だとバグで悩む
Google Colaboratory公式のノートブックにもcartopyのインストール方法は書かれており、下記のように、OSのパッケージングシステムaptを使ってできることになっています。
!apt-get -qq install python-cartopy python3-cartopy
import cartopy
しかし、こうしてインストールしたcartopyにはバグがあり、次のような簡単なコードで落ちます。
問題1:gridlines()
がエラーになる
gridlines()
でエラーになるので、グリッドが引けません。
サンプルコード:
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
ax = plt.axes(projection=ccrs.PlateCarree())
ax.gridlines()
ax.coastlines()
エラー:
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-4-3e1ebed7d096> in <module>()
3
4 ax = plt.axes(projection=ccrs.PlateCarree())
----> 5 ax.gridlines()
6 ax.coastlines()
4 frames
/usr/local/lib/python3.6/dist-packages/matplotlib/ticker.py in _validate_steps(steps)
2108 steps = np.asarray(steps)
2109 if np.any(np.diff(steps) <= 0) or steps[-1] > 10 or steps[0] < 1:
-> 2110 raise ValueError('steps argument must be an increasing sequence '
2111 'of numbers between 1 and 10 inclusive')
2112 if steps[0] != 1:
ValueError: steps argument must be an increasing sequence of numbers between 1 and 10 inclusive
問題2:coastlines()
で落ちる
coastlines()
を用いた海岸線の描画が、次のようなランタイムログとともに、「不明な理由により、セッションがクラッシュしました」となります。
サンプルコード:
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
ax = plt.axes(projection=ccrs.PlateCarree())
ax.coastlines()
エラーログ(ランタイムログ):
Apr 14, 2020, 6:18:57 PM WARNING warn("IPython.utils.traitlets has moved to a top-level traitlets package.")
Apr 14, 2020, 6:18:57 PM WARNING /usr/local/lib/python3.6/dist-packages/IPython/utils/traitlets.py:5: UserWarning: IPython.utils.traitlets has moved to a top-level traitlets package.
Apr 14, 2020, 6:18:57 PM WARNING WARNING:root:kernel e5263554-c566-4983-aea5-418e4cb6441a restarted
Apr 14, 2020, 6:18:57 PM INFO KernelRestarter: restarting kernel (1/5), keep random ports
Apr 14, 2020, 6:18:54 PM WARNING python3: geos_ts_c.cpp:3991: int GEOSCoordSeq_getSize_r(GEOSContextHandle_t, const geos::geom::CoordinateSequence*, unsigned int*): Assertion `0 != cs' failed.
それぞれの問題の原因
問題1
問題1の原因は、インストールされているcartopyがv0.14.2と古いことです。cartopyのIssue #1374などでも報告されていますが、バグ自体はPR #773で修正され、2018年2月のv0.16.0以降は直っています。
ちなみに、v0.14.2は2016年4月にリリースされたバージョンで、そのように古いものがインストールされてしまうのは、OSがUbuntu 18.04.3 LTSであるためです。LTSとあるとおりlong-term support(長期サポート)されているバージョンで、最初のリリースの18.04は2018年4月にリリースされています。長期サポート版なので長期間安心して使えるのですが、マイナーなソフトウェアだったりまだ安定していないソフトウェアだったりすると、バグが入ったバージョンがパッケージ化されてずっと使われることになってしまうのは少し難点です(パッケージ情報参照)。
In [1]: cartopy.__version__
Out[1]: '0.14.2'
In [2]: !cat /etc/os-release
Out[2]:
NAME="Ubuntu"
VERSION="18.04.3 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.3 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic
問題2
問題2は、cartopyとcartopyが使っているShapelyが、異なるバージョンのGEOSを使っており、非互換が発生しているためです。cartopyのIssue #871やIssue #1490として報告されており、Issue #805で根本的な対応が議論されていますが、まだ解決していません。
DebianやUbuntuにおいては、ライブラリパッケージのバージョンが上がればそれに依存するパッケージはリビルドされるため、異なるバージョンのライブラリにリンクされることはないはずです。実際、aptでcartopyを入れると、下記のとおり、OS提供のdebパッケージ版Shapely 1.6.4が、それにリンクされたcartopyと一緒に入ります。そのShapelyが使われれば問題は起きません。
冒頭のapt-get install
で入るdebパッケージたち:
python-pkg-resources (39.0.1-2 Ubuntu:18.04/bionic [all])
python-pyshp (1.2.12+ds-1 Ubuntu:18.04/bionic [all])
python-shapely (1.6.4-1 Ubuntu:18.04/bionic [amd64])
python-six (1.11.0-2 Ubuntu:18.04/bionic [all])
python-cartopy (0.14.2+dfsg1-2build3 Ubuntu:18.04/bionic [amd64])
python3-pkg-resources (39.0.1-2 Ubuntu:18.04/bionic [all])
python3-pyshp (1.2.12+ds-1 Ubuntu:18.04/bionic [all])
python3-shapely (1.6.4-1 Ubuntu:18.04/bionic [amd64])
python3-six (1.11.0-2 Ubuntu:18.04/bionic [all])
python3-cartopy (0.14.2+dfsg1-2build3 Ubuntu:18.04/bionic [amd64])
しかし、実は下記のとおり、Google Colaboratoryには最初からpipでShapelyの最新版v1.7.0が入ってしまっているようです。
In [3]: !pip list | grep Shapely
Out[3]: Shapely 1.7.0
この状態で単純にimportすると、cartopyはaptのもの、Shapelyはpipのものが使われます。これが非互換の原因です。面倒くさいですね。
In [4]:
import shapely
shapely.__version__
Out[4]: '1.7.0'
余談:問題2だけなら実は対応策は簡単
問題2についてはpipとaptの併用が悪さをしているので、こちらだけなら実は簡単です。冒頭のapt-get install
の前か後に下記を実行してあげれば、Shapelyはaptで入れたもの(deb版)が使われるからです(pip uninstall
前にimport cartopy
してしまうと、その中でimport shapely
されてしまうので注意)。
!pip uninstall -y shapely
対応策
原因が上記であることから、対応策の方針は下記のようになります。
- cartopy v0.16.0以降(できれば最新版v0.17.0)を入れる
- 同じGEOSにリンクされたShapelyとcartopyを使う
具体的には、こうします。
- pipで入れたShapelyは削除する
- ShapelyがリンクされているGEOSを用いて、cartopy最新版をpipでソースからビルドして入れる
apt-get build-dep
ソースから入れる際には、GEOSなどの依存ライブラリが入っていないといけません。1つ1つ入れるのは面倒なので、今回はapt-get build-dep
を使います。
apt-get build-dep
は、指定したdebパッケージのソースパッケージをビルドするのに必要なdebパッケージをインストールしてくれるコマンドです。今回だと、cartopy v0.17.0のビルドに必要な環境を、cartopy v0.14.2のdebパッケージのソースパッケージのビルド情報に基づいて整えることになります。当然ながらv0.14.2とv0.17.0とで依存関係に変化がある場合はこれだとうまくいきませんが、大きな変化がなければ大丈夫でしょう(実際、今回はこれでうまくいきました)。
aptのsources.listの編集
では早速apt-get build-dep
を、と行きたいところですが、いきなり走らせてもエラーになります。
In [5]: !apt-get build-dep cartopy
Out[5]:
Reading package lists... Done
E: Unable to find a source package for cartopy
apt-get build-dep
で使うソースパッケージ情報は、aptのパッケージ取得元設定である/etc/apt/sources.list
(および/etc/apt/sources.list.d/*
)では、デフォルトではコメントアウトされているからです。
In [6]: !grep deb /etc/apt/sources.list
Out[6]:
deb http://archive.ubuntu.com/ubuntu/ bionic main restricted
# deb-src http://archive.ubuntu.com/ubuntu/ bionic main restricted
deb http://archive.ubuntu.com/ubuntu/ bionic-updates main restricted
# deb-src http://archive.ubuntu.com/ubuntu/ bionic-updates main restricted
deb http://archive.ubuntu.com/ubuntu/ bionic universe
# deb-src http://archive.ubuntu.com/ubuntu/ bionic universe
deb http://archive.ubuntu.com/ubuntu/ bionic-updates universe
# deb-src http://archive.ubuntu.com/ubuntu/ bionic-updates universe
deb http://archive.ubuntu.com/ubuntu/ bionic multiverse
# deb-src http://archive.ubuntu.com/ubuntu/ bionic multiverse
deb http://archive.ubuntu.com/ubuntu/ bionic-updates multiverse
# deb-src http://archive.ubuntu.com/ubuntu/ bionic-updates multiverse
deb http://archive.ubuntu.com/ubuntu/ bionic-backports main restricted universe multiverse
# deb-src http://archive.ubuntu.com/ubuntu/ bionic-backports main restricted universe multiverse
# deb http://archive.canonical.com/ubuntu bionic partner
# deb-src http://archive.canonical.com/ubuntu bionic partner
deb http://security.ubuntu.com/ubuntu/ bionic-security main restricted
# deb-src http://security.ubuntu.com/ubuntu/ bionic-security main restricted
deb http://security.ubuntu.com/ubuntu/ bionic-security universe
# deb-src http://security.ubuntu.com/ubuntu/ bionic-security universe
deb http://security.ubuntu.com/ubuntu/ bionic-security multiverse
# deb-src http://security.ubuntu.com/ubuntu/ bionic-security multiverse
deb https://cloud.r-project.org/bin/linux/ubuntu bionic-cran35/
原因はそれだけなので、単にdeb
で始まる行をdeb-src
に置換したファイルを用意してやり、apt-get update
でパッケージ情報を更新してあげるだけで解決できます。
In [7]:
!grep '^deb ' /etc/apt/sources.list | \
sed 's/^deb /deb-src /g' | \
tee /etc/apt/sources.list.d/deb-src.list
!apt-get -qq update
これで前述のapt-get build-dep
も通るようになります。
インストール
あとは、pip install --no-binary
を用いて、バイナリパッケージを使わずビルドするようにすれば冒頭で書いた手順になります。aptのShapelyで問題ない場合とGoogle Colaboratoryに元々入っていた最新版を使いたい場合の両方があると思いますので、2パターン示します。どのShapelyを使いたいかによって変えるとよいでしょう。
パターン1:aptのdebパッケージ版Shapely (v1.6.4) を使う場合
!grep '^deb ' /etc/apt/sources.list | \
sed 's/^deb /deb-src /g' | \
tee /etc/apt/sources.list.d/deb-src.list
!apt-get -qq update
!apt-get -qq build-dep python3-cartopy
!pip uninstall -y shapely
!pip install --no-binary cartopy cartopy==0.17.0
パターン2:最新版のShapely (v1.7.0) をソースから入れて使う場合
!grep '^deb ' /etc/apt/sources.list | \
sed 's/^deb /deb-src /g' | \
tee /etc/apt/sources.list.d/deb-src.list
!apt-get update
!apt-get -qq build-dep python3-cartopy
!apt-get -qq remove python-shapely python3-shapely
!pip install --no-binary shapely shapely --force
!pip install --no-binary cartopy cartopy==0.17.0
動作確認
問題1と2のPythonプログラムはどちらも問題なく動きます。下記のように、公式ギャラリーのwavesもきちんと動きます。
import matplotlib.pyplot as plt
import numpy as np
import cartopy.crs as ccrs
def sample_data(shape=(73, 145)):
"""Return ``lons``, ``lats`` and ``data`` of some fake data."""
nlats, nlons = shape
lats = np.linspace(-np.pi / 2, np.pi / 2, nlats)
lons = np.linspace(0, 2 * np.pi, nlons)
lons, lats = np.meshgrid(lons, lats)
wave = 0.75 * (np.sin(2 * lats) ** 8) * np.cos(4 * lons)
mean = 0.5 * np.cos(2 * lats) * ((np.sin(2 * lats)) ** 2 + 2)
lats = np.rad2deg(lats)
lons = np.rad2deg(lons)
data = wave + mean
return lons, lats, data
def main():
fig = plt.figure(figsize=(10, 5))
ax = fig.add_subplot(1, 1, 1, projection=ccrs.Mollweide())
lons, lats, data = sample_data()
ax.contourf(lons, lats, data,
transform=ccrs.PlateCarree(),
cmap='nipy_spectral')
ax.coastlines()
ax.set_global()
plt.show()
main()
追記(2020-04-15):初期状態に戻す
インストールではOSのファイルシステムの中身(状態)が変わりますので、インストールを試行錯誤する場合、初期状態に戻せるやり方を知っておいたほうがよいでしょう。下記の方法でリセットできます。
- 「ランタイム」メニューから「ランタイムを出荷時設定にリセット」を選択
「ランタイムを再起動」だと、コードの実行状態は初期状態(何もメモリに載っていない状態)になりますが、ファイルシステムは変化したままです。