「全球及び日本域150年連続実験データ」を可視化する その6-回る地球上に地上気温変化を描く。
はじめに
前回は、QGIS上で全球の地上気温変化アニメーションを作成しました。
ただやはり、本物の地球を観察しているような視点で描きたい! ということで(画面上で)回る地球儀にデータを貼り付けてアニメーションを作ってみます。
温度変化マップの作成
前回作成した年平均気温の変化データからcartpyを用いて正距円筒図法のマップを作成します。
# ライブラリのインポート import os import pandas as pd import xarray as xr import matplotlib.pyplot as plt import cartopy.crs as ccrs from matplotlib.colors import LinearSegmentedColormap, Normalize from matplotlib.colorbar import ColorbarBase # カラーマップの作成 colors = ["blue", "#ffffe1", "red", "black"] nodes = [0.0, 0.2, 0.6, 1.0] TC_cmap = LinearSegmentedColormap.from_list("tccmap", list(zip(nodes, colors))) # データの読み込み os.chdir("/mnt/c/Users/hoge/ONEFIFTY") ds = xr.open_dataset("GCM60_RCP85_tmpchange_sfc_avr_year.nc") relief = plt.imread("../natural_earth/MSR_50M/MSR_50M.tif") # 正距円筒図法のマップを作成 for h_date,h_arr in ds.groupby("time"): fig = plt.figure(figsize=(24, 12)) ax = fig.add_subplot(1,1,1,projection=ccrs.PlateCarree()) h_arr["temperature"].plot.imshow(ax=ax, cmap=TC_cmap, vmin=-5.0,vmax=20.0, transform=ccrs.PlateCarree(),add_colorbar=False,add_labels=False) ax.imshow(relief,alpha=0.2, cmap="gray",extent=(-180,180,-90,90)) ax.coastlines(resolution='50m',linewidth=.1) ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=False, linewidth=1, alpha=0.8) plt.axis('tight') plt.axis('off') fig.subplots_adjust(left=0, right=1, bottom=0, top=1) #保存 plt.savefig(pd.to_datetime(h_date).strftime("./TCanu_latlon_png/anu_%Y.png")) plt.clf() plt.close() # カラーバーを描く fig = plt.figure(figsize=(1,7)) ax = fig.add_axes([0.05, 0.05, 0.9, 0.9]) norm = Normalize(vmin=-5.0, vmax=20.0) cb = ColorbarBase(ax, orientation='vertical', cmap=TC_cmap,norm=norm, extend='both') cb.ax.yaxis.set_tick_params(color="white") cb.ax.set_yticklabels(["Cold","0","","","","Warm"]) cb.outline.set_edgecolor("white") plt.setp(plt.getp(cb.ax.axes, 'yticklabels'), color="white",fontsize=20) fig.patch.set_facecolor("black") plt.savefig('TC_cb_var.png', bbox_inches='tight', facecolor="black")
アニメーション作成
アニメーションロゴの作成ではGIMP上でGIFを作成しましたが、GIFを使うと256階調のインデックスカラーに変換されてしまい、グラデーションがうまく出ない・・・
そこで1フレームずつpngを作成し、連番画像からffmpegを使ってmp4を作ることにしました。
タイトル−キャプション画像の作成
GIMPを用いて、アニメーション動画の背景とタイトル・キャプションを作成し、pngで保存しておきます。
フレーム画像の作成
作成したマップをGIMPを使って地球儀に変換し、タイトル−キャプション画像に貼り付けてからpngに書き出します。ファイル名等の文字列の扱いはPythonでやったほうが楽なので、一連の処理をするスプリクトを作成し、Pythonで実行します。
処理スクリプト
1枚のマップを読み込んで地球儀に貼り付け、回転角を調節してから背景画像に貼り付け、pngに書き出すScript-fu
(define (latlonmap-to-flame base_png inFile outDir yr_str rotate1 rotate2 sq-width) (let* ( ;変数の設定 (base_img (car (gimp-file-load RUN-NONINTERACTIVE base_png base_png))) (globe_img (car (gimp-file-load RUN-NONINTERACTIVE inFile inFile))) (src-layer (car (gimp-image-get-active-layer globe_img))) (globe_layers (cadr (gimp-image-get-layers globe_img))) (rotate_y rotate1) (new_globelayer 0) (l 0) (l_name "fl_") (layer_id 0) ) ;レイヤー名(fl-{year}_{number})を返す関数 (define (res_layer_name yr_str layer_id) (let* ( (s (string-append "0000" (number->string layer_id))) (n (string-length s)) ) (string-append "fl-" yr_str "_" (substring s (- n 2) n) ) ) ) ;球体に変換したレイヤを背景に結合し、pngで書き出す関数 (define (create_flame l) (let* ( (new_flame (car (gimp-image-duplicate base_img ))) (new_flamelayer (car (gimp-layer-new-from-drawable l new_flame))) (l_name (car (gimp-layer-get-name l))) (outFile (string-append outDir "/" l_name ".png")) (tx_layer 0) (d_able 0) ) (gimp-image-add-layer new_flame new_flamelayer 0) (gimp-layer-set-offsets new_flamelayer 50 0) (set! tx_layer (car (gimp-text-fontname new_flame new_flamelayer 10 0 yr_str -1 TRUE 100 POINTS "Rounded-L M+ 2p Heavy"))) (gimp-floating-sel-to-layer tx_layer) (gimp-text-layer-set-color tx_layer '(255 255 255)) (gimp-image-merge-visible-layers new_flame CLIP-TO-BOTTOM-LAYER ) (set! d_able (car (gimp-image-get-active-drawable new_flame) )) (file-png-save-defaults RUN-NONINTERACTIVE new_flame d_able outFile outFile) (gimp-image-delete new_flame) ) ) ;マップを縦横同じサイズの画像に変換 (gimp-image-scale globe_img sq-width sq-width) ;球体に変換し0.5刻みで回転させたレイヤを作成 (while (< rotate_y rotate2) (set! new_globelayer (car(gimp-layer-copy src-layer TRUE))) (set! l_name (res_layer_name yr_str layer_id)) (gimp-layer-set-name new_globelayer l_name) (gimp-image-add-layer globe_img new_globelayer 0) (plug-in-map-object 1 globe_img new_globelayer 1 0.5 0.5 20.0 0.5 0.5 0.0 0 0 0 0 0 0 0 rotate_y 35 1 '(255 255 255) 0.5 0.5 10 0 0 1 0.4 0.9 0.4 0 27.0 TRUE FALSE FALSE TRUE 0.48 0.5 0.5 0.5 1.0 -1 -1 -1 -1 -1 -1 -1 -1) (set! rotate_y (+ rotate_y 0.5)) (set! layer_id (+ layer_id 1)) ) (gimp-image-remove-layer globe_img src-layer) ;レイヤごとに背景と結合し、保存する。 (set! globe_layers (cadr (gimp-image-get-layers globe_img))) (for-each (lambda (l) (create_flame l) ) (vector->list globe_layers) ) (gimp-image-delete base_img) (gimp-image-delete globe_img) ) ) ;GIMPへの登録設定 (script-fu-register "latlonmap-to-flame" ;func name "latlonmap-to-flame" ;menu label "月平均値アニメーションのフレームを作成" ;description "ccilobo" ;author "copyright 2023, ccilabo" ;copyright notice "Sep 14, 2023" ;date created "" ;image type that the script works on ) (script-fu-menu-register "latlonmap-to-flame" "<Image>/Script-Fu/CCILabo")
1年づつ処理する
pythonのsubprocessを用いて 1ファイルづつ処理し、最後にファイル名に連番を振ります。
# ライブラリの読み込み import os import pathlib import numpy as np import pandas as pd import subprocess # 処理ファイルのリストを作成 maps_df = pd.DataFrame({"f_path":list(pathlib.Path("/mnt/c/Users/hoge/ONEFIFTY/TCanu_latlon_png").glob("*.png"))}) maps_df["f_name"] = maps_df["f_path"].map(lambda x: x.name) maps_df["yr_str"] = maps_df["f_name"].str.extract("anu_(\d{4}).png") # 30年で1回転(1年あたり24フレーム)するように開始角:終了角を設定する maps_df["rotate1"] = maps_df.index.values*24%720/2 - 180 maps_df["rotate2"] = maps_df.index.values*24%720/2 - 168 # 1ファイルずつ処理 for ind,hrow in maps_df.iterrows(): cmd_str = 'gimp-2.10 -d -i -b \'(latlonmap-to-flame \"/mnt/c/Users/hoge/ONEFIFTY/annually_animebase.png\" \"{0}\" \"/mnt/c/Users/hoge/ONEFIFTY/TCanu_flame_png\" \"{1}\" {2} {3} 918)\' -b \'(gimp-quit 0)\''.format(str(hrow["f_path"]),hrow["yr_str"],hrow["rotate1"],hrow["rotate2"]) subprocess.run(cmd_str,shell=True) # ファイル名を連番に変更 flame_df = pd.DataFrame({"f_path":list(pathlib.Path("/mnt/c/Users/hoge/ONEFIFTY/TCanu_flame_png").glob("*.png"))}) flame_df["f_stem"] = flame_df["f_path"].map(lambda x: x.stem) flame_df = flame_df.sort_values("f_stem").reset_index(drop=True) flame_df["f_serial"] =flame_df.apply(lambda x: "fl-{:04}.png".format(x.name),axis=1) for ind,hrow in flame_df.iterrows(): hrow["f_path"].rename(hrow["f_path"].parent / hrow["f_serial"])
動画に変換
ffmpegを使ってmp4動画に変換します
cd /mnt/d/Users/kazuh/ONEFIFTY/ ffmpeg -framerate 30 -i ./TCanu_flame_png/fl-%04d.png -vcodec libx264 -pix_fmt yuv420p -r 60 TC_globe_annualy_150yr.mp4
mp4動画ができました。
「全球及び日本域150年連続実験データ」の全球60km150年連続実験データの温度変化を”回る地球儀”上でアニメーションする方法を紹介しました。試してみてくださいね~。
※本記事では文部科学省「統合的気候モデル高度化研究プログラム」において、地球シミュレータを用いて作成されたデータを使用しました。