mpl_toolkits.basemap 開発はストップ、cartopy 使えやヲラ、てことなので

かつてのように pip install basemap 的なことをしても、もはや PyPI にも登録されてないわけよ、インストール出来ない、のな。

いつまで掲載され続けるかわからんけど、「deprecated になったぜのアナウンス」。deprecated どころか install 出来ないんで、お急ぎたい人にはお急ぎたい話になりうろう。

どういうやりとりがあっての流れなのかは良くわからないけれど、とにかく Basemap が 2016 年より「新規プロジェクトがこれを置き換える前提で」元の作者の Jeff Whitaker から「新規プロジェクト」の管理下に移されて、そして今まさに「置き換え完了した」てことなわけね。その「新規プロジェクト」てのが Cartopy project。ちょっとオモロイのは、Jeff Whitaker って、NOAA、つまり「アメリカ海洋気象庁」の人なんだけれども、Cartopy を起こした集団てこれ、イギリスの気象庁に強く関係してそうなんだよね。(プロジェクトそのものはイギリスの気象庁とは独立してるみたいだけども。)

こいつの導入は例によって「Windows でだけ鬱陶しい」。やってないけど Unix 系(Mac も)で苦労する要素はおそらくないと思う。細かく言うとこれの必須の依存物は:

  1. GEOS
  2. PROJ
    • PROJ 6.x 以降の場合は、これが sqlite3、curl、tiff に依存。
    • PROJ 8 は、説明されてないがまだ対応されてないので、7までを使うこと。(2022-03-15追記: 現在状況変わってる気がする。あとで追記する。)

ワタシはこれまで PROJ 6.x 以前を使って何か苦労した記憶は、GEOS ともどもなかった。これは Windows 版ビルドですら同じで、何か問題があっても数秒で解決出来るような問題にしか巡り合ってきてない。PROJ 6.x 以上で sqlite3、curl、tiff が依存として増えたところで、Unix ならなんら苦労は増えないであろう。

PyPI に置かれてる公式の cartopy が「ビルド上等」、つまり、Windows ユーザが喜ぶタイプのバイナリ配布でないので、何も考えたくない人は、Christoph Gohlke の Unofficial Windows Binaries for Python Extension Packages のものを使うと良い:

1 [me@host: ~]$ py -3 -m pip install Cartopy-0.19.0.post1-cp39-cp39-win_amd64.whl

「非公式だとは何事だ」と気分悪さを感じる人や、あるいは何か事情があって正確にバージョン等々のコントロールが必要で、「むしろビルド上等上等!」という人のために、一応ワタシが成功したパターンを書いとく(当たり前だけど Visual Studio、Cython は自明の前提)。概要:

  1. GEOS は結構無頓着でもオッケーで、野良ビルドても、vcpkg のでもヨシ。
  2. 現時点最新の cartopy の setup.py の問題で、vcpkg の PROJ は使えない。proj.exe が必要なため。一応 PR を書いたが、受け容れられるかはわからん。
  3. PROJ について、共有ライブラリとしてのリンクがついに成功せず。
    • PROJ のビルド自体は出来るよ、そうじゃなくて。
    • cartopy の pyd (_crs.cp39-win_amd64.pyd 等)が proj.dll にリンクするが、このロードに失敗し、実行(インポート)出来ない。

そういうわけで、GEOS は vcpkg で取り寄せたものを、PROJ については、依存物のない PROJ 5系をダウンロードしてビルドすることにしよう、と。

c:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Auxiliary/Build/vcvars64.bat からスタートした(「管理者として実行」として起動した)コンソール(MSYS の bash)より:

CMake はインストールして、なおかつパスを通しておくのだぞ。
 1 [me@host: ~]$ tar zxvf proj-5.2.0.tar.gz
 2 [me@host: ~]$ cd proj-5.2.0
 3 [me@host: proj-5.2.0]$ mkdir build
 4 [me@host: proj-5.2.0]$ cd build
 5 [me@host: build]$ # 以下は「静的ライブラリとして作るぜ」。
 6 [me@host: build]$ cmake -DBUILD_SHARED_LIBS=OFF ..
 7    ... (snip) ...
 8 [me@host: build]$ # 以降は本来は cmake だけで実行できるはずのことなのだが、
 9 [me@host: build]$ # proj のバージョンごとに cmake のコントロールがマチマチで、
10 [me@host: build]$ # 情報通りにやっても意図したことにならないので、「cmake 箱庭
11 [me@host: build]$ # の外で遊ぶ」:
12 [me@host: build]$ export PATH="${PATH}":"/c/Program Files (x86)/Microsoft Visual Studio/2019/Community/MSBuild/Current/Bin/amd64/"
13 [me@host: build]$ MSBuild.exe -p:Configuration=Release -p:platform=x64 PROJ4.sln
14    ... (snip) ...
15 [me@host: build]$ cmake -P cmake_install.cmake
16    ... (c:/OSGeo4W にインストールされる、と思う) ...

バージョニングされた命名で c:/OSGeo4W/local/lib/proj_5_2.lib が出来上がるのだが、のちのリンクでこれが面倒で、幸いこれが static Library であるのだからその未来永劫名乗るべき名前には無頓着でいいわけで、なので、リンクするなりリネームするなりして c:/OSGeo4W/local/lib/proj.lib にしとくといい:

1 [me@host: build]$ ln c:/OSGeo4W/local/lib/proj_5_2.lib c:/OSGeo4W/local/lib/proj.lib

ワタシは vcpkg を「c:/develop」の下においたので、vcpkg 管理版の geos と今作った PROJ を参照してもらうようにする環境変数設定は例えばこうなる:

 1 [me@host: ~]$ # geos_c.dll が /bin の下にいるのであって、そこへパスを通す必要がある
 2 [me@host: ~]$ # のであって、geos-config.exe みたいなのがあってそれを起動できるように
 3 [me@host: ~]$ # するためではないよ。
 4 [me@host: ~]$ export PATH=/c/develop/vcpkg/packages/geos_x64-windows/bin
 5 [me@host: ~]$ # 対してこちら↓は proj.exe の存在を cartopy の setup.py が前提にするため。
 6 [me@host: ~]$ export PATH=/c/OSGeo4W/bin:$PATH
 7 [me@host: ~]$
 8 [me@host: ~]$ # LIB、INCLUDE は MSVC が参照してくれる環境変数ね。
 9 [me@host: ~]$ export LIB=c:/develop/vcpkg/packages/geos_x64-windows/lib";${LIB}"
10 [me@host: ~]$ export INCLUDE=c:/develop/vcpkg/packages/geos_x64-windows/include";${INCLUDE}"
11 [me@host: ~]$ export LIB=c:/OSGeo4W/local/lib";${LIB}"
12 [me@host: ~]$ export INCLUDE=c:/OSGeo4W/local/include";${INCLUDE}"

この状態にまで出来れば、きっと以下は問題なく成功する:

1 [me@host: ~]$ py -3 -m pip install cartopy

なお、「geos-config.exe なんかない」件に関して、スクリプト呼び出しの EXE 化を前提にして自分でまかなうことは出来るので、そちらが好みならそちらでどうぞ。その場合は geos-config.sh は例えばこんな感じ:

geos-config.sh
 1 #!/bin/sh
 2 
 3 # escape paths
 4 escape() {
 5   echo "$1" | sed 's/ /\\ /g'
 6 }
 7 
 8 prefix=`escape "c:/develop/vcpkg/packages/geos_x64-windows/"`
 9 # exec_prefix is interpolated in c:/develop/GEOS/lib
10 exec_prefix=`escape "c:/develop/vcpkg/packages/geos_x64-windows/bin"`
11 libdir=`escape "c:/develop/vcpkg/packages/geos_x64-windows/lib"`
12 
13 usage()
14 {
15   cat <<EOF
16 Usage: geos-config [OPTIONS]
17 Options:
18      [--prefix]
19      [--version]
20      [--libs]
21      [--clibs]
22      [--cclibs]
23      [--static-clibs]
24      [--static-cclibs]
25      [--cflags]
26      [--ldflags]
27      [--includes]
28      [--jtsport]
29 EOF
30   exit $1
31 }
32 
33 if test $# -eq 0; then
34   usage 1 1>&2
35 fi
36 
37 while test $# -gt 0; do
38   case "$1" in
39     -*=*) optarg=`echo "$1" | sed 's/[-_a-zA-Z0-9]*=//'` ;;
40     *) optarg= ;;
41   esac
42   case $1 in
43     --prefix)
44       echo ${prefix}
45       ;;
46     --version)
47       echo 3.9.1
48       ;;
49     ##--libs)
50     ##  # TODO: make an alias for --clibs
51     ##  # see http://trac.osgeo.org/geos/ticket/497
52     ##  echo -L${libdir} geos-3.9.1.lib
53     ##  ;;
54     --clibs)
55       echo -L${libdir} -lgeos_c
56       ;;
57     ##--cclibs)
58     ##  echo -L${libdir} geos.lib
59     ##  ;;
60     ##--static-clibs)
61     ##  echo -L${libdir} geos_c.lib geos.lib
62     ##  ;;
63     ##--static-cclibs)
64     ##  echo -L${libdir} geos.lib
65     ##  ;;
66     --cflags)
67       echo -I${prefix}/include
68       ;;
69     --ldflags)
70       echo -L${libdir}
71       ;;
72     --includes)
73       echo ${prefix}/include
74       ;;
75     ##--jtsport)
76     ##  echo 1.17.0
77     ##  ;;
78     *)
79       usage 1 1>&2
80       ;;
81   esac
82   shift
83 done

ともあれこれで無事導入が成功した、として。

2021-05-22追記:
すまん、色々無頓着で、雑に書いちゃダメな部分で雑にやっちゃった。ほかのものを検証中に二つ気づいたので、補足しておく。

一つは、「PROJ 5.x でビルド」は、公式にはこれは NG の可能性がある。cartopy ではないが pyproj の方ははっきりと最低レベルを PROJ 6.2 としていた。ビルドはできるし問題なく動いてはいるけれど、そういうのもあるので、Christoph Gohlke の方に頼っておいた方が無難かもしれない。彼のほうがワタシよりこうしたことへの措置には慣れているはずだから、同じ非公式でもワタシの決断よりはきっとあてになる、はず。

もう一つは Python 3.5 の話。2.7 と同じく、Python 3.5 も昨年既にそのライフサイクルを終えていて、なのでそもそもが「Python 3.5 をまだ使っている」ということ自体が不健全であることは念押ししつつも、個人が自由にやるんでなく組織として使う場合、乗り換えは簡単にはいかなかったりもすると思うので一応言っとく。ワタシがここに書いた情報だけで 3.5 版のビルドは出来ない。幸い Christoph Gohlke はまだ 3.5 版も保守し続けている。そちらを素直に使うといい。(3.4 も同じ話で、Christoph Gohlke 版は、本日時点で 2.7、3.4~3.9 を保守している。)どうしても 3.5 の自力ビルドを(今からわざわざ準備して)行う必要がある特殊な事情持ちの人は、こちらを参照のこと。この準備をすれば「たぶん可能」。

3.6 以降は、ワタシは実際に試して確認はしていないけれど、おそらく自力ビルドも大丈夫だろうと思う。(lru_dict では 3.5 が NG、3.6 以降大丈夫なのを実際に確認した、ので、それから考えればおそらく。)

さっそくお試したい、かんたんなへろうわルドはないものか、てのは、はっきりいってソースコードを持ってきて、その中にぶち込まれてる examples がまぁまぁ量があるので、それをお試せばよいかと思う:

1 [me@host: ~]$ py -3 cartopy/examples/gridlines_and_labels/gridliner.py

とか。そうなんだけれど、一応ワタシが最初に書いたヤツも見せとく:

 1 # -*- coding: utf-8 -*-
 2 import cartopy.crs as ccrs
 3 import cartopy.feature as cfeature
 4 import matplotlib.pyplot as plt
 5 
 6 
 7 def main():
 8     lat, lon = 35.224167, 139.025373  # 箱根駒ケ岳
 9     fig = plt.figure()
10     ax1 = fig.add_subplot(projection=ccrs.Geostationary(lon))
11     ax1.set_extent([lon - 2, lon + 2, lat - 2, lat + 2], ccrs.PlateCarree())
12     ax1.coastlines()
13     ax1.add_feature(cfeature.LAND)
14     #ax1.add_feature(cfeature.OCEAN)
15     ax1.gridlines()
16     # あとは普通に pclor だの countour だの普段どおりお好きに出来る。
17     plt.savefig("my1.png", bbox_inches="tight")
18     #plt.show()
19 
20 
21 if __name__ == '__main__':
22     main()

こんなん出ますよな: my1

なお、一応何もしなくても問題なく動いてるんだけれど、本来 PROJ_LIB をセットしてあげなきゃいかんのじゃないかな、と言うのだけは個人的に謎。設定しなければならんのだとしたら、たぶんこうなんじゃないかと思う:

1 [me@host: ~]$ export PROJ_LIB=c:/OSGeo4W/share

けどやってもやらなくても現状動いちゃってるんで、これの正しさがわからん。


まだほんとに軽く眺めただけなのだけれど、おそらく Basemap よりも綺麗に matplotlib に統合されてる。Basemap はちょっと癖が強いところがあったんだけど、cartopy の方が「matplotlib な日常」そのままに使える感がより強いんではないかしら、という印象を受けた。まぁ使い込みだすと印象変わってくる可能性もないではないけれどもね。

あと、これは想像なんだけれど、「pyproj」も、cartopy に完全に取り込まれるんじゃないかと思う。pyproj も Jeff Whitaker だからね。ついでにいうと pygrib も近い運命を辿るかもなぁて気がする。さてどうなるかね?


2021-05-15追記:
「あとは普通に pclor だの countour だの普段どおりお好き」が「pcolor, contour」の間違いなのはご愛嬌としても、最低でも「そうしたことが確かに出来るんだろうな?」てことを言わない情報って不安になると思う。それをしなかったのはワタシの準備が整ってなかったから。昨年頭に Windows 7 を退役させて Windows 10 に乗り換えたあとの環境整備を、色々ずっと怠ってきてたツケがね、今頃。

元の Basemap、それに今度のこの cartopy が威力を発揮する一番手始めなものは、やっぱり気象分野にある。例によって pygrib ね。Windows でのそれについては、ここに追記を書いておいた。として準備をしておけば、めでたく「お湯が湧く温度を予言出来る」、てことだけど、まぁもちっとシンプルな例から始めようかなと:

vis_msmgpv_temp_example.py
 1 # -*- coding: utf-8 -*-
 2 import os
 3 import sys
 4 import pygrib
 5 import numpy as np
 6 import cartopy.crs as ccrs
 7 import cartopy.feature as cfeature
 8 import matplotlib.pyplot as plt
 9 
10 
11 def _read(srcfn):
12     src = pygrib.open(srcfn)
13     for s in src:
14         yield (s.name, s.parameterName), s.validDate, s.latlons(), s.values
15     src.close()
16 
17 
18 def main(gpv):
19     key = "Temperature"
20     data = {}
21     inivd = None
22     for yk, vd, latlons, values in _read(gpv):
23         if key in yk and key not in data:
24             data[key] = latlons, values
25             inivd = vd
26         if len(data) == 1:
27             break
28     (lats, lons), values = data[key]
29     X, Y = lons[0,:], lats[:,0]
30     #
31     fig = plt.figure()
32     fig.set_size_inches(16.53, 11.69)
33     crs = ccrs.Geostationary(X.mean())
34     ax1 = fig.add_subplot(projection=crs)
35     ax1.set_extent([X.min(), X.max(), Y.min(), Y.max()], ccrs.PlateCarree())
36     ax1.pcolormesh(X, Y, values, transform=ccrs.PlateCarree(), shading="auto")
37     ax1.coastlines()
38     ax1.gridlines()
39     #ax1.add_feature(cfeature.LAND)
40     #ax1.add_feature(cfeature.OCEAN)
41 
42     plt.savefig("{}_{}_{}.png".format(
43         os.path.basename(gpv), str(inivd).partition(":")[0], key), bbox_inches="tight")
44     #plt.show()
45 
46 
47 if __name__ == '__main__':
48     # 入力は "Z__C_RJTD_20210514000000_MSM_GPV_Rjp_L-pall_FH00-15_grib2.bin" など。
49     main(sys.argv[1])

Z__C_RJTD_20210514000000_MSM_GPV_Rjp_L-pall_FH00-15_grib2.bin_2021-05-14 00_Temperature
気温予測のデータってもともと地形が綺麗に見えちゃうんで「coastlinesしないと位置とデータのマッピングがワカラン」てもんではなくて、そういう意味で「cartopy、ステキっ」がやや霞んでしまうんだけれど、逆にいえば最初におためすものとしては「あ、確かに正しいね」がすぐにわかるので、例としては結構ふさわしいもんではあったりする。

colorbar:

vis_msmgpv_temp_example2.py
 1 # -*- coding: utf-8 -*-
 2 import os
 3 import sys
 4 import pygrib
 5 import numpy as np
 6 import cartopy.crs as ccrs
 7 import cartopy.feature as cfeature
 8 import matplotlib.pyplot as plt
 9 
10 
11 def _read(srcfn):
12     src = pygrib.open(srcfn)
13     for s in src:
14         yield (s.name, s.parameterName), s.validDate, s.latlons(), s.values
15     src.close()
16 
17 
18 def main(gpv):
19     key = "Temperature"
20     data = {}
21     inivd = None
22     for yk, vd, latlons, values in _read(gpv):
23         if key in yk and key not in data:
24             data[key] = latlons, values
25             inivd = vd
26         if len(data) == 1:
27             break
28     (lats, lons), values = data[key]
29     X, Y = lons[0,:], lats[:,0]
30     #
31     fig = plt.figure()
32     fig.set_size_inches(16.53, 11.69)
33     crs = ccrs.Geostationary(X.mean())
34     ax1 = fig.add_subplot(projection=crs)
35     ax1.set_extent([X.min(), X.max(), Y.min(), Y.max()], ccrs.PlateCarree())
36     CS = ax1.pcolormesh(X, Y, values, transform=ccrs.PlateCarree(), shading="auto")
37     ax1.coastlines()
38     ax1.gridlines()
39     plt.colorbar(CS, ax=ax1)
40     #ax1.add_feature(cfeature.LAND)
41     #ax1.add_feature(cfeature.OCEAN)
42 
43     plt.savefig("{}_{}_{}_cb.png".format(
44         os.path.basename(gpv), str(inivd).partition(":")[0], key), bbox_inches="tight")
45     #plt.show()
46 
47 
48 if __name__ == '__main__':
49     # 入力は "Z__C_RJTD_20210514000000_MSM_GPV_Rjp_L-pall_FH00-15_grib2.bin" など。
50     main(sys.argv[1])

Z__C_RJTD_20210514000000_MSM_GPV_Rjp_L-pall_FH00-15_grib2.bin_2021-05-14 00_Temperature_cb

contourf (or contour)のも一応:

vis_msmgpv_temp_example3.py
 1 # -*- coding: utf-8 -*-
 2 import os
 3 import sys
 4 import pygrib
 5 import numpy as np
 6 import cartopy.crs as ccrs
 7 import cartopy.feature as cfeature
 8 import matplotlib.pyplot as plt
 9 
10 
11 def _read(srcfn):
12     src = pygrib.open(srcfn)
13     for s in src:
14         yield (s.name, s.parameterName), s.validDate, s.latlons(), s.values
15     src.close()
16 
17 
18 def main(gpv):
19     key = "Temperature"
20     data = {}
21     inivd = None
22     for yk, vd, latlons, values in _read(gpv):
23         if key in yk and key not in data:
24             data[key] = latlons, values
25             inivd = vd
26         if len(data) == 1:
27             break
28     (lats, lons), values = data[key]
29     X, Y = lons[0,:], lats[:,0]
30     #
31     fig = plt.figure()
32     fig.set_size_inches(16.53, 11.69)
33     crs = ccrs.Geostationary(X.mean())
34     ax1 = fig.add_subplot(projection=crs)
35     ax1.set_extent([X.min(), X.max(), Y.min(), Y.max()], ccrs.PlateCarree())
36     #CS = ax1.contour(X, Y, values, transform=ccrs.PlateCarree())
37     CS = ax1.contourf(X, Y, values, transform=ccrs.PlateCarree())
38     ax1.coastlines()
39     ax1.gridlines()
40     plt.colorbar(CS, ax=ax1)
41 
42     #contourfでは以下は邪魔だが contour ではどちらかはあるといいか?
43     #ax1.add_feature(cfeature.OCEAN)
44     #ax1.add_feature(cfeature.LAND)
45 
46     #plt.savefig("{}_{}_{}_cont.png".format(
47     #    os.path.basename(gpv), str(inivd).partition(":")[0], key), bbox_inches="tight")
48     plt.savefig("{}_{}_{}_contf.png".format(
49         os.path.basename(gpv), str(inivd).partition(":")[0], key), bbox_inches="tight")
50     #plt.show()
51 
52 
53 if __name__ == '__main__':
54     # 入力は "Z__C_RJTD_20210514000000_MSM_GPV_Rjp_L-pall_FH00-15_grib2.bin" など。
55     main(sys.argv[1])

Z__C_RJTD_20210514000000_MSM_GPV_Rjp_L-pall_FH00-15_grib2.bin_2021-05-14 00_Temperature_cont
Z__C_RJTD_20210514000000_MSM_GPV_Rjp_L-pall_FH00-15_grib2.bin_2021-05-14 00_Temperature_contf


2021-05-15 18:30追記:
出来てないことが理由で誤魔化してることって、すぐにバレてたりするかいね? tick についてワタシは何も言ってない。うまくいってなかったからだよ。

わかってみると、ちょっとイヤな事態になってるなぁと思う。一応解決の方向に向かおうとはしてるらしいのではあるけれど、リリースされてるやつに取り込まれてない気がする。

問題と思う事象が2つあって、一つは「明示的に set_xticks, set_yticks」しないと絶対に ticklabel を打ってくんない」点。もう一つがリンク先が問題にしてる「Cannot handle non-rectangular coordinate」問題。要は「PlateCarree ならいいが Geostationary だとダメ」てこと、例えば。

set_xticks, set_yticks を必ず行わねばならぬ、が鬱陶しいのは、以下のようにサボると:

vis_msmgpv_temp_example4.py
 1 # -*- coding: utf-8 -*-
 2 import os
 3 import sys
 4 import pygrib
 5 import numpy as np
 6 import cartopy.crs as ccrs
 7 import cartopy.feature as cfeature
 8 from cartopy.mpl.ticker import LongitudeFormatter, LatitudeFormatter
 9 import matplotlib.pyplot as plt
10 
11 
12 def _read(srcfn):
13     src = pygrib.open(srcfn)
14     for s in src:
15         yield (s.name, s.parameterName), s.validDate, s.latlons(), s.values
16     src.close()
17 
18 
19 def main(gpv):
20     key_u, key_v = "U component of wind", "V component of wind"
21     data = {}
22     inivd = None
23     for yk, vd, latlons, values in _read(gpv):
24         if key_u in yk and key_u not in data:
25             data[key_u] = latlons, values
26             if inivd is None:
27                 inivd = vd
28         if key_v in yk and key_v not in data:
29             data[key_v] = latlons, values
30             if inivd is None:
31                 inivd = vd
32         if len(data) == 2:
33             break
34     (lats, lons), values_u = data[key_u]
35     _, values_v = data[key_v]
36     X, Y = lons[0,:], lats[:,0]
37     #
38     fig = plt.figure()
39     fig.set_size_inches(16.53, 11.69)
40     crs = ccrs.PlateCarree(central_longitude=X.mean())  #Geostationary(X.mean())はダメ
41     ax1 = fig.add_subplot(projection=crs)
42     ax1.set_xticks(np.arange(X.min(), X.max() + 1), crs=ccrs.PlateCarree())
43     ax1.set_yticks(np.arange(Y.min(), Y.max() + 1), crs=ccrs.PlateCarree())
44     lon_formatter = LongitudeFormatter(zero_direction_label=True)
45     lat_formatter = LatitudeFormatter()
46     ax1.xaxis.set_major_formatter(lon_formatter)
47     ax1.yaxis.set_major_formatter(lat_formatter)
48     ax1.set_extent([X.min(), X.max(), Y.min(), Y.max()], ccrs.PlateCarree())
49     CS = ax1.pcolormesh(X, Y, np.sqrt(values_u**2 + values_v**2), transform=ccrs.PlateCarree(), shading="auto")
50     ax1.coastlines()
51     ax1.gridlines()
52     plt.colorbar(CS, ax=ax1)
53     #ax1.add_feature(cfeature.OCEAN)
54     #ax1.add_feature(cfeature.LAND)
55 
56     plt.savefig("{}_{}_{}.png".format(
57         os.path.basename(gpv), str(inivd).partition(":")[0], key_u[:-2]), bbox_inches="tight")
58     #plt.show()
59 
60 
61 if __name__ == '__main__':
62     # 入力は "Z__C_RJTD_20210514000000_MSM_GPV_Rjp_L-pall_FH00-15_grib2.bin" など。
63     main(sys.argv[1])

こんなん出てまう:
Z__C_RJTD_20210514000000_MSM_GPV_Rjp_L-pall_FH00-15_grib2.bin_2021-05-14 00_U component of wi
「47.4°N」という tick を打ちたいわけないんであって、なおかつせっかく gridlines が引いてるとこにこそ打ちたい、てことであろう、その tick る位置をチマチマ計算で頑張って求める必要がある、ってこった。

まぁまだ新しいプロジェクトなのだから、てことで、多少の不足は受け容れつつ、てことかなぁ。


2021-05-16追記:
「その tick る位置をチマチマ計算で頑張って求める必要がある」に過剰に反応されると弱りそうなので、「困難なことに不平を言ってるわけじゃない」ことを一応示しとく:

「たとえば」。唯一の解だと思わないこと。
1     ax1.set_xticks(
2         [v for v in np.arange(np.ceil(X.min()), np.floor(X.max()) + 1)
3          if int(v) % 5 == 0],
4         crs=ccrs.PlateCarree())
5     ax1.set_yticks(
6         [v for v in np.arange(np.ceil(Y.min()), np.floor(Y.max()) + 1)
7          if int(v) % 5 == 0],
8         crs=ccrs.PlateCarree())

Z__C_RJTD_20210514000000_MSM_GPV_Rjp_L-pall_FH00-15_grib2.bin_2021-05-14 00_U component of wind
よく出来たインフラほど、こうしたことは「不満がある場合にだけカスタマイズ」としたくて書かねばならぬコード、という扱いとなり、「必ず書かなければならないもの」とはならない。てことね。


2021-06-09追記:
ticks の件なのだが、gallaly でキッチリ説明されてた:

vis_msmgpv_temp_example4.py
 1 # -*- coding: utf-8 -*-
 2 import os
 3 import sys
 4 import pygrib
 5 import numpy as np
 6 import cartopy.crs as ccrs
 7 #####import cartopy.feature as cfeature
 8 #####from cartopy.mpl.ticker import LongitudeFormatter, LatitudeFormatter
 9 import matplotlib.pyplot as plt
10 
11 
12 def _read(srcfn):
13     src = pygrib.open(srcfn)
14     for s in src:
15         yield s.level, (s.name, s.parameterName), s.validDate, s.latlons(), s.values
16     src.close()
17 
18 
19 def main(gpv):
20     key_u, key_v = "U component of wind", "V component of wind"
21     data = {}
22     inivd = None
23     for level, yk, vd, latlons, values in _read(gpv):
24         if key_u in yk and key_u not in data:
25             data[key_u] = latlons, values
26             if inivd is None:
27                 inivd = vd
28         if key_v in yk and key_v not in data:
29             data[key_v] = latlons, values
30             if inivd is None:
31                 inivd = vd
32         if len(data) == 2:
33             break
34     (lats, lons), values_u = data[key_u]
35     _, values_v = data[key_v]
36     X, Y = lons[0,:], lats[:,0]
37     #
38     fig = plt.figure()
39     fig.set_size_inches(16.53, 11.69)
40     crs = ccrs.Geostationary(X.mean())
41     ax1 = fig.add_subplot(projection=crs)
42     ######↓なんと、これらの苦労がいらんてことなのであった…
43     ######ax1.set_xticks(np.arange(X.min(), X.max() + 1), crs=ccrs.PlateCarree())
44     ######ax1.set_yticks(np.arange(Y.min(), Y.max() + 1), crs=ccrs.PlateCarree())
45     ######lon_formatter = LongitudeFormatter(zero_direction_label=True)
46     ######lat_formatter = LatitudeFormatter()
47     ######ax1.xaxis.set_major_formatter(lon_formatter)
48     ######ax1.yaxis.set_major_formatter(lat_formatter)
49     ext = [X.min(), X.max(), Y.min(), Y.max()]
50     ax1.set_extent(ext, ccrs.PlateCarree())
51     CS = ax1.pcolormesh(
52         X, Y, np.sqrt(values_u**2 + values_v**2),
53         transform=ccrs.PlateCarree(), shading="auto")
54     ax1.coastlines()
55     ax1.gridlines(draw_labels=True, dms=True, x_inline=False, y_inline=False)
56     plt.colorbar(CS, ax=ax1)
57     #ax1.add_feature(cfeature.OCEAN)
58     #ax1.add_feature(cfeature.LAND)
59 
60     plt.savefig("{}_{}_{}_1000hPa_ticks.png".format(
61         os.path.basename(gpv),
62         str(inivd).partition(":")[0], "wind"), bbox_inches="tight")
63     #plt.show()
64 
65 
66 if __name__ == '__main__':
67     # 入力は "Z__C_RJTD_20210514000000_MSM_GPV_Rjp_L-pall_FH00-15_grib2.bin" など。
68     main(sys.argv[1])

Z__C_RJTD_20210609000000_MSM_GPV_Rjp_L-pall_FH36-39_grib2.bin_2021-06-10 12_wind_1000hPa_ticks

一つ前のは dms の価値があまりみえてなかったので、ちょっとだけ変えたヤツ
 1 # -*- coding: utf-8 -*-
 2 import os
 3 import sys
 4 import pygrib
 5 import numpy as np
 6 import cartopy.crs as ccrs
 7 import matplotlib.pyplot as plt
 8 
 9 
10 def _read(srcfn):
11     src = pygrib.open(srcfn)
12     for s in src:
13         yield s.level, (s.name, s.parameterName), s.validDate, s.latlons(), s.values
14     src.close()
15 
16 
17 def main(gpv):
18     key_u, key_v = "U component of wind", "V component of wind"
19     data = {}
20     inivd = None
21     for level, yk, vd, latlons, values in _read(gpv):
22         if key_u in yk and key_u not in data:
23             data[key_u] = latlons, values
24             if inivd is None:
25                 inivd = vd
26         if key_v in yk and key_v not in data:
27             data[key_v] = latlons, values
28             if inivd is None:
29                 inivd = vd
30         if len(data) == 2:
31             break
32     (lats, lons), values_u = data[key_u]
33     _, values_v = data[key_v]
34     X, Y = lons[0,:], lats[:,0]
35     #
36     fig = plt.figure()
37     fig.set_size_inches(16.53, 11.69)
38     crs = ccrs.Stereographic(X.mean())
39     ax1 = fig.add_subplot(projection=crs)
40     ext = [X.min() + 8, X.max() - 8, Y.min() + 8, Y.max() - 8]
41     ax1.set_extent(ext, ccrs.PlateCarree())
42     CS = ax1.pcolormesh(
43         X, Y, np.sqrt(values_u**2 + values_v**2),
44         transform=ccrs.PlateCarree(), shading="auto")
45     ax1.coastlines()
46     ##### 「Thanks to the dms keyword, minutes are used when appropriate to display
47     ##### fractions of degree.」。dms の制御さえあればカスタムフォーマッタは
48     ##### あんましいらんであろう、というノリなんだと思うわ。
49     ax1.gridlines(draw_labels=True, dms=True, x_inline=False, y_inline=False)
50     plt.colorbar(CS, ax=ax1)
51 
52     plt.savefig("{}_{}_{}_1000hPa_ticks2.png".format(
53         os.path.basename(gpv),
54         str(inivd).partition(":")[0], "wind"), bbox_inches="tight")
55     #plt.show()
56 
57 
58 if __name__ == '__main__':
59     # 入力は "Z__C_RJTD_20210514000000_MSM_GPV_Rjp_L-pall_FH00-15_grib2.bin" など。
60     main(sys.argv[1])

Z__C_RJTD_20210609000000_MSM_GPV_Rjp_L-pall_FH36-39_grib2.bin_2021-06-10 12_wind_1000hPa_ticks2
上でリンクした issue はこの措置をしたということか…?


2022-03-15追記:
1年も経たないうちにここまで廃れるか…。どうやら依存関係についてかなりカッチリ整理したらしく、一年前と結構状況が変わってる。

今お試そうとしてたのが cartopy-0.19precartopy-0.20.2 なのだが、そもそも「Python 3.7+」という条件が付いてしまった。つまり「Python 2.7 なんか論外」なのはわかるとしても、Python 3.6 すら許さん、てことなのね。そして、きっちり「GEOS は 3.7.2 以上」「PROJ は 8.0.0」となった。

という状態に対して、ここまでに書いてきた導入についての話をどう更新したもんかなぁとお悩み中である。Christoph Gohlke 版の人は困らんと思うので、野良ビルドした人、したい人向けの話ね。今実は WSL (Windows Subsystem for Linux) という手段を知り、そっちの方が皆にとって幸せかもしれんな、と思ったりしてるんだけれど、そっちはそっちで「一撃」ではなかったのね。ちょっと話長くなりそうなの。

ちょっとどう整理して伝えようか悩んでるんであとにする。「Windows ネイティブとしての野良ビルド」については GEOS, PROJ のバージョンを変えるだけで済むかもしれんがまだ試みてなくて、Christoph Gohlke 版はまだ状況確認してなくて、WSL (の ubuntu)版は既に体験済みだがメモしてなくて、て具合なので。でも今回のこの追記だけでも突破口を開ける人もいるんではないかと思ったので、一応取り急いでみた。


2022-03-15追記(2):
Windows ネイティブな野良ビルド版ネタは実は一瞬諦めかけたんだけど、節穴の混入で「実はうまくいってた」ことに気付かず少し損した。ともあれそいつから。

まず、「vcpkg をアップグレードする」ことで最初につまづいた。まさに proj.4 が原因で vcpkg upgrade に失敗した。なんだかこれはもうどうしようもなさそうな感じで、あれこれ試行錯誤するも先に進む気配がなかったので、これは割り切って「まっさらな vcpkg」を改めて作った。むろんワタシ自身のネタを読めば始め方はわかる。旧はリネームして vcpkg.broken として残したが、たぶんもうこのまま捨てるだろう。

で、結果的には最初の「cartopy-0.19-post1 + バージョン無頓着な geos + 「依存物は vcpkg で取りそろえた、vcpkg 範疇外の proj ソース配布物野良ビルド」」よりもスッキリしたビルドを実現出来た:

  • geos のライブラリ本体は vcpkg install geos --triplet=x64-windows (入ったバージョンは 3.10.0)
  • proj のライブラリ本体は vcpkg install proj --triplet=x64-windows-static (入ったバージョンは 9.0.0)

VC 用の環境変数は、なので、ワタシの場合は:

ちょっとややこしいが INCLUDE, LIB は MSVC++ 向けの「Windows マナー」、PATH は MSYS つまり「Unix マナー」(あくまでもワタシのケースではね)
1 export INCLUDE="c:/develop/vcpkg/installed/x64-windows/include;$INCLUDE"
2 export INCLUDE="c:/develop/vcpkg/installed/x64-windows-static/include;$INCLUDE"
3 export LIB="c:/develop/vcpkg/installed/x64-windows/lib;$LIB"
4 export LIB="c:/develop/vcpkg/installed/x64-windows-static/lib;$LIB"
5 export PATH="/c/develop/vcpkg/installed/x64-windows/bin:$PATH"
6 export PATH="/c/develop/vcpkg/installed/x64-windows-static/bin:$PATH"

こんな感じ。proj がダイナミックリンクのバージョンはダメだった。スタティックリンクはリンクで警告は出るけれど、問題なく動いてるみたい。(この警告を致命傷と一瞬判断してしまい、失敗したと思ってしまったのだった。)

これだけだぜ、と言いたいところだけどそうはいかなくて、最初に書いた時も言った通り「proj.exe がない、geos-config.exe がない」問題があるので、vcpkg でライブラリを見繕って環境変数を通しただけではどうともならない。これについては、最初に書いたときにやってた「exeねばなりません」によるんだけれど、そのうちの「exemimicry」の方を利用した。exemimicry はこのように構成した:

.exemimicry.go.conf.json
 1 {{/*
 2    configuration of exemimicry.go.
 3    basically this file is a json-like format.
 4 */}}
 5 {
 6     "wsl": {
 7         "prog": "C:/Windows/System32/wsl.exe",
 8         "args": [[], []],
 9         "revert_msysroot": true
10     },
11     "geos-config": {
12         "prog": "c:/MinGW/msys/1.0/bin/bash.exe",
13         "args": [["c:/MinGW/msys/1.0/local/bin/geos-config.sh"], []]
14     },
15     "proj": {
16         "prog": "c:/MinGW/msys/1.0/bin/echo.exe",
17         "args": [["proj", "9.0.0"], []]
18     }
19 }

geos-config.sh の中身は、最初に書いたやつが上のほうにあるので、それ参照。基本的にバージョン番号を変えただけ。

exemimicry は…、すまんね、ワタシは EXE を配布する形は取りたくないんだ、だから、ご自身で Go をインストールして、ご自身でビルドして exemimicry.exe をまずは作ってくれ。go.mod も掲載してなくて不案内極まりなくてほんと申し訳ないのだけれど、すまんが頑張ってくれ。で、exemimicry.exe を作ったら、geos-config.exe と proj.exe をそれへのハードリンクとして作る。もちろんパスが通った場所に。そうすれば cartopy の setup.py はそれを使うので、ビルドがうまくいく。

なお、今回のケースでは pip では出来ずにソースを持ってきてビルドするのしかうまくいかなかったのだが、これが setuptools_scm に暗黙に依存してるので注意。これを使ってるがために、「ソースコード.tar.gz をダウンロードしてきて setup.py install」ではダメなの。ちゃんと git or mercurial レポジトリと対応が取れなくてはいけなくて、なので、実質 git clone してきたソース以外でのビルドは「成功したフリ」にしかならない。(_version.py が生成されないので実行時にインポートエラーで死ぬ。)

ちょっと皆にちゃんと伝わるか不安だが、とにかく「cartopy 0.20 を vcpkg 活用して野良ビルド」はうまくいく。無論先に言った通り Python 3.7+ だからね? 2.7 は論外、Python 3.6 ですら見捨てられた、てことにはワタシは何も出来ない。

「ビルドはうまくいく」という言い回しに注意。実はワタシの環境だと実行時に「<frozen importlib._bootstrap>:228: UserWarning: PROJ 8+ is required. Current version: 7.2.1」が出てしまう。proj 9.0.0 を static リンクしているのだからこの警告はおかしいわけだが、これはたぶん pyproj がリンクしてる proj の問題ではないかと思う。vcpkg.broken として残した旧をのぞき込んでみたところ、そこでの proj は確かに 7.2.1 で、そして pyproj はこれにリンクして作ったんだったと思うので。原因がこの想像通りなら pyproj からやりなおせばいいだけだ。のであまり問題視はしてない。そうでない場合はちょっと困るわね、まったく想像がつかない。まぁでも「うまくいってるとこまで」としては、ここまででも結構役に立つと思ってくれる人もいるんではないかと思うので、「ひとまずこれでよし」としとく。(ちゃんと出来たらまた追記の形ででも。)

あとは Christoph Gohlke 版の状況と WSL のだけど、これは後日。といっても前者は手を動かして確認するつもりはないのだけどね。WSL はちょっと色々書くことが多くなると思うけど、別ページに書くほどでもないので、同じくここに追記の形で書くつもり。ケド今日は既にちょっと疲れ果てたので今日はやらない。


2022-03-15追記(3):
「pyproj がリンクしてる proj の問題ではないか」は完全にビンゴで、単にさっきまで入ってた pyproj 3.0.1 を消して、pip で最新 stable の 3.3.0 をインストールしただけで警告は消えた。

この「警告が消えた」もちょっと気持ち悪くて、pyproj 3.3.0 の wheel が同梱してるのって、PROJ 8.2.0 らしいんだわ、これって vcpkg の 9.0.0 と合ってないんだよね。でも、警告は消えるし、examples もちゃんと動くし。

まぁあまり気にしないでおこうと思う。というのも、そもそも「Windows で無理やり動かす」のって、ワタシは「便利、ということでしかない」と思ってるから、なのよね。製品ではやはり「ちゃんんと Unix で動かせ」という正論が本来唯一の正義だと思うの。こんな不毛な作業ばかり強いられる環境なんか、枯れるハズがないのだから。そして「Windows でも動くのは便利だ」というだけのことなの。たとえば自宅には Windows しかなく職場で使う PC も基本 Windows だが製品のためにデプロイすべきサーバ環境がありそれは Unix、みたいなシナリオだと、なかなか Unix での検証がしづらくて、みたいなことでしょ、きっと。少なくともワタシは何度もそういうシチュエーションを経験した。どういうわけだか「仕事には Windows が一番だ」としてデスクトップ Unix を認めない文化の方が主流なのよね、ワタシには今一つ理解は出来ないのだけれど。(5年くらい前からは随分デザイン系以外の一般企業でもマック系が採用されだしたけど、まだ Windwos しか認めない文化って多いでしょ、家電量販店の売り場面積比率を見ればわかる。)


2022-03-16追記:
予告通りWSL (Windows Subsystem for Linux) によるアプローチね。そうなんだけれど、前置きがちょっと長くなるよ。

「Windows Subsystem for Linux」という名前は、エンドユーザ目線だとちょっと実体をちゃんと表現出来ていなくて、利用者目線だとむしろ「linux on Windows」そのものにみえる。仕組みは間違いなく「Windows Subsystem for Linux」であるけれど。どういうことかって、「linux で動くはずの ELF バイナリがそのまんま動作するための仕組み」だから。リンクした先で wine で譬えてるが、ほんとにまさにソレ。linux 実機でビルドしたバイナリをそのまんま持ってきて、そのまんま動く、というのは、そのバイナリ目線では「win for linux」、ユーザはでも「Windows 上でそのバイナリを動かす」と認識するであろう、そういう関係ね。

そういう関係なので、「WSL 上で cartopy を動かそうぜっ」というネタは、8~9割は「linux そのもの向けネタ」としても機能しうる、てことね。たとえば ubuntu on WSL ならば、「ubuntu 実機」向けの情報にもなりうるの。8~9割? そう、どうしたって「WSL 固有の事情」は避けられない。それにワタシのこのネタを読んでくれてる人がどれだけ linux のことを知ってくれてるかわからんので、多少はそれ向けのことも言わねばならんし。…みたいなことがちょっとややこしいネタね。

前置き終わり。本題へ。

まず前提として、「WSL2 は、Windows 10 では linux の GUI は動かせない」。これはたとえば ffmpeg はほとんど完全に動かせるが ffplay はダメ、みたいなことになる。cartopy のケースだと、イメージとしてセーブする使い方は動くが、show() などで直接プレビューするような作りのものは動かせない。これはつまり、cartopy の examples にある例は、実質全滅で、動かしたければ、書き換える必要がある、てこと。こればっかりは諦めるしかない、Windows 10 では。(ワタシは 10 を 11 にアップグレードするつもりはなかったのだが、この件から、本気でアップグレードしようか考え始めてる。情報収集中…)

リンク先でちょっと言ったように、「まっさらのインストールしたての ubuntu (など)」を準備しやすく、なおかつ今回の場合はきっとその「限りなくオリジナルの ubuntu on WSL2」に対して導入しようとする説明が読者にとってもわかりやすいと思ったんで、そうした。具体的には、Microsoft Store でインストールしたばかりの ubuntu (20.04.4LTS) をそのまま export してそのまま別名で import したそれ。要するに持ってきたばかりの ubuntu on WSL2 に導入する例、てことね。こういうことを気軽に出来るのも WSL2 のメリットだと思う。

以下、WSL2 のケースだと ubuntu.exe みたいなランチャから起動するか「wsl -d your-distro -u your-account」で起動するかした「マインドとしては linux box の中」の作業ね。(なので、今のワタシの場合 ubuntu なので、「本物の ubuntu linux 実機」でもだいたい似た話になるはず。)

最初にやったのは cartopy 関係ないヤツ。sudo apt upgrade ね。これにより「まっさらの」ではなくなるけれど、「マインドとしては ubuntu としてのデフォルト」の最新に追従する、ということになる。Windows でいうところの Windows Update みたいなもんだと思ってもらえればいいかな、たぶん。(ちなみに distro が採用するパッケージマネージャはそれぞれ、なので、たとえば CentOS 系の distro (Fedora など) では、今は dnf ね。dnf ネタは前にちょっと書いたことがある。)

「apt 管理されている cartopy の有無の確認 → apt search cartopy」→ 0.17 が管理されている → 「満足」の人は、sudo apt install python3-cartopy python3-cartopy-data (などその他依存物も同じノリで)で終わり。で、ほんとは「不満だ、しかも Python 3.9 がいい」というストーリーにしたかったんだけど、結論から言って、「ビルド・インストールは問題なく出来たが、動作しなかった」。個人的に 0.17 は古過ぎると思ったのだ、一年前にワタシが扱ったのは 0.19 Post1 なのだから。でもダメだった。

ひとまず、うまくはいかなかったけれど、やったことは記録しとく。全部が役に立たない情報というわけでもないからね。

「ubuntu 20.04.4LTS のデフォルトの Python3 は 3.8 だが 3.9 を使いたいので」。結果的にはこれだけよ:

1 [me@my-ubuntu]$ sudo apt install python3.9 python3.9-dev
2 [me@my-ubuntu]$ curl https://bootstrap.pypa.io/get-pip.py --output get-pip.py
3 [me@my-ubuntu]$ sudo python3.9 get-pip.py 

これだけよつーても地味に「-devが必要」が罠ちゃぁ罠だし(入れないと「Python.h なんかないぞ」問題としてハマる)、なぜか pip、ensurepip が入ってない謎の Python3.9 になってて、手動 get-pip.py してる。なんなんだろうねぇこれ。ensurepip って標準になったんじゃなかったか? なぜに抜けてる?

次。GEOS は? apt search libgeos で 3.8.0 が apt にあることがわかる。これは cartopy 0.20 が求める「GEOS 3.7.2 以上」に合致してるので、これはそのまま使う。つまり sudo apt install libgeos'*'

そして PROJ なのだが、apt のものは 6.3.1-1 なので「論外」てことになった。ゆえにここからが野良感満載になる。

PROJ が野良だとしても、その PROJ が依存しているものすべてを野良る必要はないわけで、ので、依存物を apt で賄うのだが、何が依存物だったのか、曖昧な記憶で入れると無駄なので、とにかく PROJ ソースを持ってきてみる:

1 [me@my-ubuntu: ~]$ curl https://codeload.github.com/OSGeo/PROJ/tar.gz/refs/tags/9.0.0 --ou proj9.0.0.tar.gz
2 [me@my-ubuntu: ~]$ tar zxvf proj9.0.0.tar.gz
3 [me@my-ubuntu: ~]$ cd PROJ-9.0.0
4 [me@my-ubuntu: PROJ-9.0.0]$ 

この時点で「含まれている説明「INSTALL」が完全に outdated」とわかる。うーん、サイトを見るべきだったね。これ。要件は:

  • C99 compiler
  • C++11 compiler
  • CMake >= 3.9
  • SQLite3 >= 3.11: headers and library for target architecture, and sqlite3 executable for build architecture.
  • libtiff >= 4.0 (optional but recommended)
  • curl >= 7.29.0 (optional but recommended)

最初の C99 コンパイラは最近の linux のパッケージマネージャ的にはまずは大丈夫で、ただし「コンパイラを使うなんて開発者しかしねーんだぜヴぁーかヴぁーか」ポリシーを持つアホな distro だと「わざわざインストールする必要がある」ことがあるので注意ね。基本 RedHat 系に多いかな。この ubuntu は大丈夫。最初から base のビルド環境は入ってる。

でも C++ コンパイラと cmake は入ってない。ので、まずは cmake を apt で、と思ったが、入ったのは 3.16.3。うーむ、こういうの、「新は旧を兼ねる」として、新の機能に依存してるわけでもないのにいい加減に宣言してることが多いからなぁ、まずはこれでやってみるか。C++ コンパイラについては、sudo apt install g++ で 9.4.0 が入ったが、C++11 だよねこれ、最近 GCC なんて自分では扱ってこなかったからちゃんと把握してないが、そもそも GCC の C++11 化は対応が早かった記憶があるので、たぶん大丈夫ろ。

libsqlite3 は apt のものは 3.31.1 なのでこれはそのまま使える。libtiff も 4.1.0、これもおっけー、libcurl は libcurl4 7.68、問題はない。というか libcurl4 は libcurl4-gnutls-dev みたいなのを指定したのだが、これがワタシは良くわからなくてなぁ、なんなのこれ。でもこれらを入れれば cmake の最初のステップ(Makefile を作るステップ)は成功するの。うん、まぁ出来るんだからこれはいいか…。

ここまで準備出来てれば Build Steps に従うだけ。まぁワタシは「curl はプリインストールだからいらんよね」として libcurl4-* への依存で失敗したりと何度か行きつ戻りつしたんだけれどもね、ともあれ、ビルド・インストールは何の問題もなく。(にしても cmake ビルドを要求する OSS が共通して抱えてるドキュメント問題があるよなぁと常々思ってる。最後の「--target install」は何も指示しなければ普通管理者(root)権限が必要なので、sudo がある環境なら sudo、ないなら su で root になって作業、みたいなことが必要なのだが、なぜかその説明をしてないドキュメントが多い。誰も文句言わないんだろうか…。)インストールは /usr/local 配下ね(明示的に指示しない限りは)。

データが必要だったよな、と読み進めると、二つの方式があるみたい。いつから? 従来型(というかワタシが唯一知ってたやつ)は proj-data なのだが、share/proj のことなら、インストールされたみたい。てことは、何もしなくていいのかな? もう一つの方式は projsync なのだが、なんか動かんぞてことで、ひとまず「旧式(?)」で良かろう、としとく。ひとまずこのまま先に進んでみる。

素直に「sudo python3.9 -m pip install cartopy」。うん、問題なくインストール出来た。後は動作確認…、なのだが、ここからは「WSL2 に固有」の話が多くなる。linux 実機の方はご注意。

ひとまず examples が WSL2 on Windows 10 だとダメなのはやる前から知ってる。ので…、どうするか、examples を書き換えて、としてみる? と、「plt.show()」を「plt.savefig(“aaa.png”)」みたいに直して動かしてみた、たとえば:

nightshade.py
 1 import datetime
 2 import matplotlib.pyplot as plt
 3 import cartopy.crs as ccrs
 4 from cartopy.feature.nightshade import Nightshade
 5 
 6 
 7 fig = plt.figure(figsize=(10, 5))
 8 ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
 9 
10 date = datetime.datetime(1999, 12, 31, 12)
11 
12 ax.set_title(f'Night time shading for {date}')
13 ax.stock_img()
14 ax.add_feature(Nightshade(date, alpha=0.2))
15 #plt.show()
16 plt.savefig("ns.png", bbox_inches="tight")

これがねぇ…、その savefig で落ちてるみたいなんだ、「free(): invalid size」…なんぞそれ? ABI のミスマッチとかそんな問題かもしれんし、ともちょっと思うけれど、情報が少なすぎてよくわからん。と思って少しほかの distro でもやってみてたんだけど、どうやら「plt.show()」で出るエラーと同じなので、とすればこれは、linux の GUI (たぶん X11 に関係する部分全部)への依存関係が 0.17 と 0.20 で違っていることが原因、てことになりそう。これはどうしようもないなぁ、諦めるしかないか…。(実は fedora は Python 3.10 が dnf 管理で入ってて、GEOS も PROJ も期待の新しいやつなのだが、それが同じエラーで落ちたことから、ワタシの野良ビルドの何かしらミスが原因でないことがわかったから「諦める」が結論になったの。でなけりゃ、「fedora なら簡単」が答えになりえたんだなこれが。→ 2022-03-18追記(2)参照。)

というわけで、ワタシは「Ubuntu 20.04.4LTS on WSL2 on Windows 10 での apt 管理されてる cartopy 0.17」でひとまず満足しておくことにした。こちらは問題なくこの nightshade.py が動く。保守的な distro はどうしても数年単位で遅れがちになるので、感覚的に「いまさらそれかよ」感が強くなることがあるけれど、まぁこういうのは付き物だしな、でことで。

さて。ここまでを「Ubuntu 20.04.4LTS on WSL2 on Windows 10 に導入成功した」という話だと思い込むとして。「Windows ユーザとしてのワシが普段使いとしてこの cartopy に依存する」話。

ひとつには上でやってきたように「ubuntu.exe みたいなランチャから起動するか「wsl -d your-distro -u your-account」で起動するかした「マインドとしては linux box の中」で使う」というのが素直なのだけれど、WSL/WSL2 の場合はもう一つオプションがあって、「Windows 側から wsl.exe コマンド経由で使う」ことが出来るわけね。マウントポイントのマッピングのところだけが鬱陶しいけれど、例えば Windows での「c:/Users/hhsprings/mycartopyexample01.py」があるとして、こんな風に呼び出せる:

Windows 上の MSYS のような疑似 Unix の bash から
1 [me@host: ~]$ wsl.exe -d my-Ubuntu20.04.4LTS -u hhsprings python3.8 '/mnt/c/Users/hhsprings/mycartopyexample01.py'

ほんとに感覚的に ssh してる感じよね。ワタシが期待したかった 0.19 以上じゃないので、この実行結果は残念なものにはなったけれど、「0.17 のつもりで使う」のならこれで満足出来る可能性はあるろ? (「残念な結果」の原因が、単に resolution のデフォルトが変わったということだけが理由だったので、その程度の差異ばかりなら、0.17 でも十分、ということはありうることだろう。)

Windows ネイティブの方の Python と WSL2 側の Python を共有する、みたいなことは無理だけど、こういうスクリプトmainに依存するだけの関係であれば、WSL2 のものを使う、というのは、ワタシは結構ありなんじゃないかと思う。

ひとまず WSL2 で cartopy、はこんな感じ。WSL/WSL2 そのものや docker はオススメでも、実用的日常使いしようとすると、まぁ色々あるわね。これは仕方ないか…。そもそもやってみたのはたった一つの例なので、ほんとに「show()以外は使える」のかまでは見てなくて、なので、全然実用になってない可能性はある。そういう場合は、まぁ従来通り VirtualBox でちゃんと本物をトライしたらいいと思う。WSL/WSL2 を使うのと違って「重い」けど「ちゃんと完全」なので。


2022-03-17追記:
昨晩の地震による大規模停電に見事にハマってしまい、始めたのが20時くらいだったと思うのだが、やっとさっき Windows 11 へのアップグレードが終わった。ダウンロード・インストールそのものもワタシの環境だとべらぼーに時間がかかったことになるわけだが、そこに「大規模停電の3時間」が加わってしまったてわけ。幸い、停電でストップしたときの状態は「ダウンロード」だったので助かった。インストール最中だったら、リカバリ出来ない状態に陥った可能性があったんではないかと思うんだよね。

で、この説明に従って WSL を更新してみた、のだが…。「free(): invalid size」は変わらず。X11 関係ないのかしら。

うわー残念、だったら Windwos 10 に戻すか…、とはならない、ワタシの場合。今始めたばかりの Windows 11 は、ワタシには不愉快要素がまだ見当たらないし、たとえば公式説明で紹介されてる gedit とかはほんとに素直に何の問題もなく動くようなんだよね。(コンソールに警告は出てるけど動いてる。)少なくとも ubuntu on WSL2 on Windows 11 の場合は ffplay も動くし、とにかく「ちゃんと本物の linux」度が増してる。なので cartopy の件では救世主度は低かったとしても、少なくとも「WSL2 on Windows 11」そのものはかなり生活を変えるインパクトがあると思うぞ。linux+X 版の emacs が動いたら結構感動する気がするがどうだろうか(まだ試みてはいないけど)。

WSL2 on Windows 11 にする一番のネックはたぶん「何もしないと、旧 VirtualBox 等が使えない」ことかと思うが、対応の VirtualBox 等がリリースされている、みたいなことを読んだ。きっと共存出来るんだろう。ワタシは必要になったら試みてみようと思うが、すぐにはやらない。少なくとも Microsoft 自身が書いてる通り「全部が出来るとは言ってない」ので VirtualBox (等) には今後もお世話にはなると思うんだよね、だからそのいずれかはいつかはワタシにも訪れる。ので、そのときには何かしら書こうと思う。

なお、ワタシの作業順のケースだと、「Windows 11 に乗り換え前は gui 関連パッケージは何一つ入れたつもりはない」からの「Windows 11 に乗り換えてドライバインストールして wsl --update」だったわけだけど、matplotlib を使う一番シンプルな以下スクリプト:

1 import matplotlib.pyplot as plt
2 fig = plt.figure()
3 ax = fig.add_subplot()
4 ax.plot(range(10), range(10, 0, -1))
5 plt.show()

は、「gedit などインストール」の前後で振る舞いが変わった。つまり「GUI なしなので agg で動作」の状態(つまり何もアウトプットがない)だったのが、gedit (など)インストールしたら、show が期待通りの振る舞いをするようになった。gedit だから gtk かな、gedit のインストールがその基盤の gtk インストールを引き起こして…、て流れ。そういうわけで、「free(): invalid size」はほんとに全然別の原因みたいだね。どうするか、もっと突っ込んで調べるか? うーん、果てしないような気がするしなぁ…。


2022-03-18追記:
「ほんとは簡単なんだぜ、Windows だけが異様なんや」と言いたかったのに簡単なインストールに巡り合ってなくて、ちょっと困ってたんだけど、「WSL2 on Windows 11 の状態だと昔の VirtualBox は動かない」の記念に、この際なので「WSL2 on Windows 11 で動く最新 VirtualBox + Fedora 35 Workstation」てみた。

むろん「Fedora の(VirtualBox への)インストール」そのものがちょっとは時間がかかるもの、だけれどもな、今問題にしている「cartopy の導入」は、「sudo dnf install python3-cartopy」一撃だった。実に簡単である…。(もちろんちゃんと動いてるよ(入ったのは 0.20.1)。これが問題なく動いたので、上でちょっと書いた「Fedoraremix on WSL2」はもう一回トライしてみようかしら? それでダメならやはり WSL2 固有の事情ってことになるので。)


2022-03-18追記(2):
あー、ヤッタゼ。やっぱり「WSL2 on Windows 11 は救世主」たりえますぞ、ちゃんと。

さっきの VirtualBox の方で問題なかったので、「同じ Fedora なんだから」と fedoraremix の「ほぼまっさら」からやり直してみた。具体的には「Microsoft Store からインストールしてユーザ作成した直後を export した tar」を import したもの。「VirtualBox でやったのと同じで「sudo dnf install python3-cartopy」一撃、なのか?」。一撃だった。先に試した時は2つの dnf install を経たやり方だったんよね、それでうまくいかなかった(何をしたかは忘れた)。ともあれ、正解がわかってしまった今となっては「Windows で cartopy を遊びたいなら WSL2 on Windows 11 (fedoraremix) が救世主」かなと思う。

むろんパッケージマネージャのポリシーに振り回されてもいい人にはこれでいいけど、「最新を使いたい」とかだと、ちょっと大変にはなるよ。でも Fedora って(特に RPMFusion を組み込んだものは)「無節操に新しいものを取り込む」タイプのディストリビューションなので、そこまで苦痛は感じないと思う。

そして「WSL2 なので」てことだよ。VirtualBox にはない恩恵がある。本当にこれは「シームレス」で、ファイルシステム連携に関しては素の状態で「/mnt」を介して Windows⇔linux の行き来が出来る。VirtualBox でも出来ることだが、経験者はわかってるだろう、これがそこそこダルいことを。



Related Posts