tpc29-2

TOPIC 29|Pythonで活用する:応用編|PlateauKit + PlateauLabを用いたAI連携とアプリ構築[2/2]|UIを持つインタラクティブなアプリを構築する

このトピックでは、PLATEAUの3D都市モデルとPythonの豊富なライブラリを組み合わせることで、どのようなことができるかを見ていきます。スライダーやボタンなどを備えたインタラクティブなUIを作る応用例として、時間経過に伴ってどの範囲に影響があるのかを可視化して確認できるインタラクティブなアプリの構築方法を解説します。

Share

このトピックでは、PLATEAUの3D都市モデルとPythonの豊富なライブラリを組み合わせることで、どのようなことができるかを見ていきます。スライダーやボタンなどを備えたインタラクティブなUIを作る応用例として、時間経過に伴ってどの範囲に影響があるのかを可視化して確認できるインタラクティブなアプリの構築方法を解説します。

【目次】

29.3   UIを持つインタラクティブなアプリを構築する

 29.3.1 必要なデータの取得と下準備

 29.3.2 スライダーを追加する(ipywidgetsの使用)

 29.3.3 マップとインタラクティブ性の追加

29.4   まとめ

29.3_UIを持つインタラクティブなアプリを構築する

最後に、PlateauKit+PlateauLabを利用して、独自のユーザーインターフェイス(UI)を持つアプリケーションを作成してみましょう。PLATEAUの3D都市モデルの建築物は、浸水想定に関する情報(浸水継続時間)という情報を持っています。この情報を用いた例として、浸水からの時間経過をスライダーで操作したとき、どのように状況が変化するかを可視化できるアプリを作成してみます。 

【メモ】

以下では新規のアプリを作成しています。そのため、ローカルのJupyter環境で開発する場合は、新しいノートブックを作成するとよいでしょう。Google Colabではシステム上、ノートブックごとに実行環境が独立しているため、インストールしたPlateauKitやデータセットを引き継げませんが、ローカルのJupyter環境の場合には、特に設定しなくても新しいノートブックにこれらを引き継げます。

29.3.1 _ 必要なデータの取得と下準備

まずはPLATEAUデータセットから、指定した範囲を取得します。セルを追加して、次のコードを実行します。 

from plateaukit import load_dataset

dataset = load_dataset("plateau-13113-shibuya-ku-2023")
area = dataset.area_from_landmark("渋谷駅")

リスト29-9 渋谷駅周辺の範囲(Area)オブジェクトを取得する

今回は、河川による洪水浸水想定に関する情報を利用します。まず、範囲のデータフレームを表示して内容を確認します。セルを追加して、次のコードを実行します。

area.gdf
図29-12 範囲のデータフレームを確認する

データフレームの表の列のうち、「riverFloodingRisk」が今回利用する属性です(PLATEAUのCityGML仕様における「uro:RiverFloodingRiskAttribute」に相当)。この属性には、洪水浸水想定の対象としている指定河川ごとにデータが格納されています。

【3D都市モデル標準製品仕様書】
https://www.mlit.go.jp/plateaudocument/

「riverFloodingRisk」の値は辞書型の値(または値なし)として構成されています。少し複雑ですが、列「riverFloodingRisk」に対してpd.Seriesを適用することで、指定河川ごとに列を分けたデータフレームを作成します。セルを追加して、次のコードを実行します。

import pandas as pd

river_flooding_risk_df = area.gdf["riverFloodingRisk"].apply(pd.Series)
river_flooding_risk_df

リスト29-10 指定河川ごとに列を分ける

図29-13 指定河川ごとに列を分けたデータフレーム

データフレームの表中の列のうち、今回は「古川水系渋谷川・古川・目黒川水系目黒川・呑川水系呑川」を対象としてデータを取得します。セルを追加して次のコードを実行し、必要な情報を取り出します。

・「古川水系渋谷川・古川・目黒川水系目黒川・呑川水系呑川」の列から、値が欠けている行の部分を除外する(①)。

・今回利用する浸水継続時間の値(duration属性の値)のみを取り出す(②)。

・元のデータフレームに「riverFloodingRiskDuration」という列を作成し、今回利用する浸水継続時間の値で拡張する(③)。

river = river_flooding_risk_df["古川水系渋谷川・古川・目黒川水系目黒川・呑川水系呑川"].dropna() # ①
duration = river.apply(lambda x: x["duration"]) # ②
area.gdf["riverFloodingRiskDuration"] = duration # ③
area.gdf
図29-14 浸水継続時間としてriverFloodingRiskDuration列を追加した実行結果

これでデータの準備ができました。このデータを用いて、指定した時間(elapsed_hours)よりも浸水継続時間が長い列を判定するには、次のようにします。すると、1.0時間以上浸水する可能性がある建物の一覧が得られます。

elapsed_hours = 1.0 
in_duration = elapsed_hours <= gdf["riverFloodingRiskDuration"]
in_duration

リスト29-11 1.0時間以上浸水可能性がある建物の一覧を取得する

図29-15 実行結果

29.3.2 _ スライダーを追加する(ipywidgetsの使用)

経過時間(elapsed_hours)を変更可能にするために、ユーザーが操作できるスライダーを追加しましょう。スライダーの表示処理をshow_widgetという名前の関数として実装していきます。新しいセルを追加して、以下のスライダーを表示するだけのコードを実行します。すると、画面にスライダーが表示されます。

import ipywidgets as widgets 

def show_widget():
slider = widgets.FloatSlider(
layout=widgets.Layout(width="100%"), # 幅を100%に設定
)
display(slider)

show_widget()

リスト29-12 スライダーを表示する

図29-16 スライダーが表示された

さらに、スライダーの細かい設定を追加します。同じセルを、今度は次のように書き換えて実行します。スライダーの左にラベル(「0.5m浸水後経過時間(h)」という文字列)が表示され、スライダーの最小値、最大値、刻み幅が設定されます。

import ipywidgets as widgets

def show_widget():
slider = widgets.FloatSlider(
value=0.0, # 初期値
min=0, # 最小値
max=12.0, # 最大値
step=0.01, # 刻み幅
description="0.5m浸水後経過時間 (h)", # 説明
layout=widgets.Layout(width="100%"),
)

display(slider)

show_widget()

リスト29-13 スライダーにラベル、最小値、最大値、刻み幅を設定する

図29-17 ラベルが表示され、最小値・最大値などの設定が変わった

29.3.3 _ マップとインタラクティブ性の追加

スライダーに加えて、都市モデルのマップも表示しましょう。引き続き同じセルを、今度は次のように書き換えて実行すると、スライダーの下にマップが表示されます。

import ipywidgets as widgets 

from plateaukit.core.widgets.interactive_deck import InteractiveDeck

def show_widget():
slider = widgets.FloatSlider(
value=0.0,
min=0,
max=12.0,
step=0.01,
description="0.5m浸水後経過時間(h)",
layout=widgets.Layout(width="100%"),
)
output = widgets.Output()
deck = InteractiveDeck(area.gdf)
with output:
display(deck.deck)

display(slider, output)

show_widget()

リスト29-14 スライダーとともにマップを表示する

図29-18 マップが表示された

次に、スライダーの値が変更された際に呼び出される関数を次の手順で定義します。

① 新しい関数を定義する。関数内の結果を指定した出力先(output)に表示するために、定義の直前に @output.captureを書き加える。

② widgets.interactive関数を使って、スライダーの変更に応じて呼び出されるように先ほど定義した関数を登録する。以下の例では、スライダーの値をslider_valueという名前で関数に渡している。

同じセルを今度は次のように書き換えて実行すると、スライダーを動かすたびに、値がマップ部分の下に表示されていきます。

import ipywidgets as widgets
from plateaukit.core.widgets.interactive_deck import InteractiveDeck

def show_widget():
slider = widgets.FloatSlider(
value=0.0,
min=0,
max=12.0,
step=0.01,
description="0.5m浸水後経過時間 (h)",
layout=widgets.Layout(width="100%"),
)

output = widgets.Output()
deck = InteractiveDeck(area.gdf)
with output:
display(deck.deck)
@output.capture()
def handler(slider_value):
# ①
gdf = area.gdf.copy()
# ②
elapsed_time = slider_value
in_duration = elapsed_time <= gdf["riverFloodingRiskDuration"]
# ③
gdf["fill_color"] = in_duration.map({True: [255, 0, 255], False: None})
# ④
updated_layer = deck._create_building_layer(gdf)
deck.deck.layers = [updated_layer]
deck.deck.update()

widgets.interactive(handler, slider_value=slider)
display(slider, output)
show_widget()

リスト29-15 スライダーの値をマップ下に表示する

図29-19 スライダーを動かすと、その値が画面に表示される

先ほどのコードを少し変更して、スライダーの値に応じてマップの表示が変化するようにしましょう。次のようにコードを書き加えます(リスト 29-16)。

・選択範囲のデータフレームをコピーする(①)。

・スライダーの値を経過時間として、建築物ごとに浸水継続時間内かどうかを判定する(②)。
・浸水継続時間内の場合にのみ建物の色を青色に変更する(③)。
・マップの表示を変更後のデータフレームで更新する(④)。

同じセルを、今度は次のように書き換えて実行し、スライダーを操作すると、スライダーで指定した経過時間に応じて表示が変化します(変化が見えにくい場合は、色がついているエリアにマップ上でズームしてみてください)。

【メモ】

浸水継続時間とは、浸水の深さが0.5mに達してから、0.5mを下回るまでにかかる最大の時間です。今回の例では、かかる時間の長さを各建築物で比較しています。実際の浸水のプロセス全体をシミュレーションするには、各建築物で浸水の深さが0.5mに達するまでの時間なども考慮する必要があることに注意してください。

import ipywidgets as widgets 
from plateaukit.core.widgets.interactive_deck import InteractiveDeck
def show_widget():
slider = widgets.FloatSlider(
value=0.0,
min=0,
max=12.0,
step=0.01,
description="0.5m浸水後経過時間(h)",
layout=widgets.Layout(width="100%"),
)

output = widgets.Output()
deck = InteractiveDeck(area.gdf)
with output:
display(deck.deck)
@output.capture()
def handler(slider_value):
# ①
gdf = area.gdf.copy()
# ②
elapsed_time = slider_value
in_duration = elapsed_time <= gdf["riverFloodingRiskDuration"]
# ③
gdf["fill_color"] = in_duration.map({True: [255, 0, 255], False: None})
# ④
updated_layer = deck._create_building_layer(gdf)
deck.deck.layers = [updated_layer]
deck.deck.update()

widgets.interactive(handler, slider_value=slider)
display(slider, output)
show_widget()

リスト29-16 スライダーで指定した経過時間に応じて、浸水する可能性がある建物を色づけする

図29-20 スライダーを調整すると、それに伴い浸水の影響を受ける建物の色づけが変わる

最後に、見出しを追加して見栄えを整えてみましょう。見出しをつけるには、テキストセル(Markdownセル)を利用します。

セルを追加したい場所の直前のセルを選択した状態で、上部のメニューなどから「テキストセル」を選びます。テキストセルを作成して以下のように入力すると、図29-21のように見出しが作成されます。

# 浸水継続時間シミュレーション

このように、Google ColabやJupyter環境では、見出しや説明などのさまざまなテキストをコードの間に挿入することができます。

図29-21 見出しを追加した結果(コードを非表示にした状態)
コラム:VoilaやStreamlitなどによる単体アプリ化

このチュートリアルでは、JupyterLab内でユーザーから操作可能なアプリを構築しました。VoilaやStreamlitといったライブラリやサービスを利用すると、こうしたコードを少し書き換えて、単体で利用可能なWebアプリとして公開することもできます。ぜひ試してみてください。

Voila: https://github.com/voila-dashboards/voila

Streamlit: https://streamlit.io/

Shiny for Python: https://shiny.posit.co/py/

Dash: https://dash.plotly.com/

29.4 _ まとめ

このチュートリアルでは、Pythonを使って3D都市モデルを扱う応用的な方法を解説しました。

PlateauKit+PlateauLabを使って3D都市モデルを読み込み、LLMによるデータ処理やインタラクティブな処理ができることがおわかりいただけたと思います。

データ分析、データ処理、地図表示など、読み込んだデータセットを活用する方法は、たくさんあります。ぜひさまざまなアイデアでPLATEAUの3D都市モデルを活用してみてください。

【文】

小関 健太郎

【協力】

大澤文孝