M1 Macでmatplotlib+cartopyの環境構築

# はじめに

気象データを Python を使って地図とともに可視化するためには、matplotlib と cartopy を使うことが一般的です。Intel Macでの環境構築については、[「Intel Macでmatplotlib+cartopyの環境構築」](https://qiita.com/wm-ytakano/items/8445db3456e4c35dbb54)という記事で既に解説しました。

ここではApple M1チップを搭載したMacでの環境構築について説明します。環境については以下の通りです。

- OS: macOS Big Sur 11.2
- チップ: Apple M1

# Homebrew
まずはHomebrewのインストールから説明します。[公式サイト](https://brew.sh/)にアクセスし、書かれているコマンドを実行します。

```shell
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
```

こちらの環境では実行すると

```
Error: Fetching /opt/homebrew failed!
Failed during: /opt/homebrew/bin/brew update --force --quiet
```

というエラーメッセージが出ました。
しかし、`~/.zshrc`に以下のようにパスを設定し、`source ~/.zshrc`で読み込み直した後、`brew update`するとエラーは出ないようなので、特に問題はなさそうです。

```.zshrc
export PATH="/opt/homebrew/bin:$PATH"
export LD_LIBRARY_PATH="/opt/homebrew/lib:$LD_LIBRARY_PATH"
```

ここで`PATH`は`brew`コマンドを使うために必要になります。`LD_LIBRARY_PATH`が設定されていない場合、後にプログラムの実行時にライブラリが見つからずエラーになります。

バージョンは3.1.4がインストールされました。

```shell
$ brew --version
Homebrew 3.1.4
Homebrew/homebrew-core (git revision a469d3bb2a; last commit 2021-04-29)
```

バージョン3.0.0から公式にHomebrewがApple Siliconをサポートするようになりました（[公式アナウンス](https://brew.sh/2021/02/05/homebrew-3.0.0/)）。今インストールされたのは、Rosetta 2を使わないM1ネイティブ対応版になります。Intel版との主な違いは、インストール先が`/opt/homebrew`に変更されたことと、まだM1未対応のformulaeが存在することです。

`wget`をインストールして、ひとまず先に進みます。

```shell
brew install wget
```

# Miniforge
Pythonのパッケージ管理や仮想環境の作成にはAnacondaやMinicondaがよく使われますが、これは残念ながらM1 Macには現在対応していません。その代わりに、Miniforgeと呼ばれるM1 Macに対応したディストリビューションを使います。基本的にMinicondaと同じように、`conda`コマンドが使えます。

[公式ページ](https://github.com/conda-forge/miniforge)にあるインストーラをダウンロードし実行します。

```shell
wget https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-MacOSX-arm64.sh
bash Miniforge3-MacOSX-arm64.sh
```

ちなみに、試していませんが、`brew install miniforge`でもインストールできるようです。

# Cartopy用の環境構築
ここでは`cartopy-env`という名前の仮想環境をPython 3.9をベースに作成することにします。

```shell
conda create -n cartopy-env python=3.9
conda activate cartopy-env
```

Cartopy以外でよく使うパッケージを`conda`で先にインストールしておきます。

```shell
conda install -c conda-forge numpy scipy xarray netcdf4 matplotlib 
```

Intel Macの場合は、`conda install -c conda-forge cartopy`でインストールできたわけですが、[conda-forge の cartopy](https://anaconda.org/conda-forge/cartopy) は osx-arm64 に対応していないため、`conda`コマンドではインストールできません。
そこで[Cartopyの公式サイト](https://scitools.org.uk/cartopy/docs/latest/installing.html)を参照しつつ、ソースからビルドすることになります。

まず、依存するパッケージを先にインストールします。

```shell
brew install proj geos
brew link proj
pip install --upgrade pyshp
pip install shapely --no-binary shapely
```

その後、`pip`でcartopyをインストールするのですが、環境変数`CFLAGS`でコンパイルオプションを指定しないと、エラーになります。

```shell
export CFLAGS="-I/opt/homebrew/include -L/opt/homebrew/lib"
pip install cartopy
```

この状態でPythonで`import cartopy`をしてみると、ImportErrorが出ることがあります。

```
>>> import cartopy
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/opt/homebrew/Caskroom/miniforge/base/envs/cartopy-env/lib/python3.9/site-packages/cartopy/__init__.py", line 96, in <module>
    import cartopy.crs
  File "/opt/homebrew/Caskroom/miniforge/base/envs/cartopy-env/lib/python3.9/site-packages/cartopy/crs.py", line 36, in <module>
    from cartopy._crs import (CRS, Geodetic, Globe, PROJ4_VERSION,
ImportError: dlopen(/opt/homebrew/Caskroom/miniforge/base/envs/cartopy-env/lib/python3.9/site-packages/cartopy/_crs.cpython-39-darwin.so, 2): Library not loaded: @rpath/libproj.22.dylib
  Referenced from: /opt/homebrew/Caskroom/miniforge/base/envs/cartopy-env/lib/python3.9/site-packages/cartopy/_crs.cpython-39-darwin.so
  Reason: image not found
```

`/opt/homebrew/lib/`を見てみると、`libproj.19.dylib`と`libproj.dylib`は見つかりますが、`libproj.22.dylib`がないようです。
[同様のissue](https://github.com/SciTools/cartopy/issues/823)はすでに上がっており、対処方法として最も手っ取り早いのは`/opt/homebrew/lib/`で`libproj.22.dylib`のシンボリックリンクを作成することです。

```
ln -s libproj.dylib libproj.22.dylib
```

最後に、以下のプログラムを実行し、日本付近の地図が描画されれば、正しくインストールされたことが確かめられます。

```draw.py
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import cartopy.crs as ccrs
import cartopy.feature as cfeature

land_50m = cfeature.NaturalEarthFeature(
    'physical', 'land', '50m',
    edgecolor='face',
    facecolor=cfeature.COLORS['land'])

plt.figure()
ax = plt.axes(projection=ccrs.Mercator())
ax.set_extent([120, 150, 20, 50], ccrs.PlateCarree())
ax.add_feature(land_50m)
ax.coastlines(resolution='50m', lw=0.5)
ax.gridlines(xlocs=mticker.MultipleLocator(10),
            ylocs=mticker.MultipleLocator(10),
            linestyle='-',
            color='gray')
plt.savefig("mercator.png")
```

![mercator.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/829116/03559ac6-f67e-d76d-1152-862ad5fa800a.png)