電子ペーパに雨雲レーダ画像を表示する方法を紹介します.
はじめに
『電子ペーパディスプレイで作る気象情報パネル』で,右下に雨雲レーダ画像を表示しているのですが,そこで使ったテクニックを紹介します.
雨雲レーダ画像に限らず,電子ペーパにネット上の情報を表示する際に活用できると思います.
データソース
今回表示したいのは,気象庁のナウキャストに掲載されている雨雲画像です.
他にも色々なサイトが雨雲レーダ画像を提供していますが,後述する色置換のテクニックを使うことを考えると気象庁のものが最も扱いやすいです.
課題
電子ペーパーに表示する場合,先ほどのページをそのまま表示すれば良いように見えますが,実際にやると次の点が気になります.
- 雨雲レーダ画像以外の設定用のアイコン等が入ってしまう
- グレースケールにしたときに雨の強度が直感的に分からない
前者は,ウィンドウサイズを大きくしておいて,中央を切り抜けば対応できるのですが,後者はやや厄介です.
実際に素直にグレースケール化してみると,次のようになります.
赤で囲った部分は一番雨量が強いのですが,グレースケールにすると他の場所の方が色が濃くなってしまっています.雨量の強弱が色の色の濃淡に対応しておらず,直感的に状況が把握できません.
以降では,PythonでSeleniumを使いながらこれらを解決する方法を紹介します.
雨雲レーダ画像以外のアイコン非表示化
非表示にする機能が提供されていないので,Javascriptで強制的に非表示にすればOK.
アイコンはダイナミックに描画されているので,処理する前に表示されていることをEC.presence_of_element_located
で確認すると安定動作します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
PARTS_LIST = [ {"class": "jmatile-map-title", "mode": "none"}, {"class": "leaflet-bar", "mode": "none"}, {"class": "leaflet-control-attribution", "mode": "none"}, {"class": "leaflet-control-scale-line", "mode": "none"}, ] SCRIPT_CHANGE_DISPAY = """ var elements = document.getElementsByClassName("{class_name}") for (i = 0; i < elements.length; i++) {{ elements[i].style.display="{mode}" }} """ wait = WebDriverWait(driver, 5) for parts in PARTS_LIST: wait.until(EC.presence_of_element_located((By.CLASS_NAME, parts["class"]))) for parts in PARTS_LIST: driver.execute_script( SCRIPT_CHANGE_DISPAY.format( class_name=parts["class"], mode=parts["mode"], ) ) |
処理するとこんな感じにすっきりします.
雨の強度のグレースケール対応
こちらは,素直に色を置換することで実現します.カラー表示では色相を使って雨の強度を示しているので,これを利用して HSV 空間で色の置換を行います.
処理の流れは以下のようになります.
- Selenium の
screenshot_as_png
を使って雨雲レーダ画像を PNG 形式で取得. - OpenCV で読み込み,HSV 形式に変換.
- 画素の色相(H)と彩度(S)に基づいて色を置換.
- HSV 形式から RGB 形式に変換.
コード全体は下記です.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
CLOUD_IMAGE_XPATH = '//div[contains(@id, "jmatile_map_")]' RAINFALL_INTENSITY_LEVEL = [ # NOTE: 白 {"func": lambda h, s: (160 < h) & (h < 180) & (s < 20), "value": 1}, # NOTE: 薄水色 {"func": lambda h, s: (140 < h) & (h < 150) & (90 < s) & (s < 100), "value": 5}, # NOTE: 水色 {"func": lambda h, s: (145 < h) & (h < 155) & (210 < s) & (s < 230), "value": 10}, # NOTE: 青色 {"func": lambda h, s: (155 < h) & (h < 165) & (230 < s), "value": 20}, # NOTE: 黄色 {"func": lambda h, s: (35 < h) & (h < 45), "value": 30}, # NOTE: 橙色 {"func": lambda h, s: (20 < h) & (h < 30), "value": 50}, # NOTE: 赤色 {"func": lambda h, s: (0 < h) & (h < 8), "value": 80}, # NOTE: 紫色 {"func": lambda h, s: (225 < h) & (h < 235) & (240 < s)}, ] png_data = driver.find_element(By.XPATH, CLOUD_IMAGE_XPATH).screenshot_as_png img_rgb = cv2.imdecode( np.asarray(bytearray(png_data), dtype=np.uint8), cv2.IMREAD_COLOR ) img_hsv = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2HSV_FULL).astype(np.float32) h, s, v = cv2.split(img_hsv) # NOTE: 降雨強度の色をグレースケール用に変換 for i, level in enumerate(RAINFALL_INTENSITY_LEVEL): img_hsv[level["func"](h, s)] = (0, 80, 255 / 16 * (16 - i * 2)) # NOTE: 白地図の色をやや明るめにする img_hsv[s < 30, 2] = np.clip(pow(v[(s < 30)], 1.35) * 0.3, 0, 255) img = PIL.Image.fromarray( cv2.cvtColor( cv2.cvtColor(img_hsv.astype(np.uint8), cv2.COLOR_HSV2RGB_FULL), cv2.COLOR_RGB2RGBA, ) ) |
Numpy の機能を使うと,色の置換が img_hsv[level["func"](h, s)] = (0, 80, 255 / 16 * (16 - i * 2))
とシンプルに書けるので気持ちいいですね.
出力画像は次のようになります.
あとは.見やすくするために凡例とか同心円を描画してグレースケールに変換すれば完成です.
コメント