🌏

Fletで世界の地震情報を表示してみた

2025/01/15に公開

はじめに

Fletでマップを表示できることを知り、地震情報の描画を試してみました。

Fletは、FlutterをベースにしたPythonフレームワークで、Web、デスクトップ、モバイルアプリケーションに対応しています。個人的には、ロゴがポップな感じで好きです。

動作環境

  • WSL2 Ubuntu (Windows 11)
  • Python 3.12.3
  • uv 0.5.18
  • flet 0.25.2

環境構築

uvを使用して、仮想環境でFletを使えるようにしました[1]

# プロジェクトの作成
uv init flet-map
cd flet-map
# ライブラリの追加
uv add flet[all]
uv add requests
# Fletアプリケーションのテンプレート作成
uv run flet create .

上記を実行すると、ディレクトリ構造はこのようになります。

.
├── README.md
├── hello.py
├── pyproject.toml
├── src
│   ├── assets
│   │   └── icon.png
│   └── main.py
├── storage
│   ├── README.md
│   ├── data
│   └── temp
└── uv.lock

今回はhello.pyassetsディレクトリ、storageディレクトリは使用しませんが、削除しなくても問題ありません。

コード

Fletのドキュメントのサンプルコードを参考に、srcディレクトリ内のmain.pyを書き換えました。

アメリカ地質調査所(USGS)のリアルタイムフィードから地震情報を取得し、描画するようにします。対象とする期間や地震の規模によっていくつかのフィードがありますが、今回は過去30日間の重大な地震(significant earthquakes)[2]を取得します。詳細はドキュメントをご確認ください。

コード全体を確認する
from datetime import datetime, timedelta, timezone
import flet as ft
import flet.map as map
import requests

EARTHQUAKE_URL = 'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/significant_month.geojson'
JST = timezone(timedelta(hours=+9), 'JST')

def fetch_earthquake_data(url):
    response = requests.get(url)
    return response.json()

def format_earthquake_info(time, magnitude, depth):
    return f'Time: {time.strftime("%Y-%m-%d %H:%M:%S")} JST\nMagnitude: {magnitude}\nDepth: {depth} km'

def create_marker(latitude, longitude, earthquake_info):
    return map.Marker(
        content=ft.Icon(
            ft.Icons.CIRCLE,
            color=ft.Colors.RED,
            tooltip=earthquake_info,
        ),
        coordinates=map.MapLatitudeLongitude(latitude, longitude),
    )

def add_earthquake_markers(marker_layer, earthquake_data):
    for earthquake in earthquake_data['features']:
        longitude = earthquake['geometry']['coordinates'][0]
        latitude = earthquake['geometry']['coordinates'][1]
        depth = earthquake['geometry']['coordinates'][2]
        magnitude = earthquake['properties']['mag']
        time = datetime.fromtimestamp(earthquake['properties']['time'] / 1000, JST)
        earthquake_info = format_earthquake_info(time, magnitude, depth)
        marker_layer.current.markers.append(
            create_marker(latitude, longitude, earthquake_info)
        )

def refresh_data(page, marker_layer_ref):
    earthquake_data = fetch_earthquake_data(EARTHQUAKE_URL)
    marker_layer_ref.current.markers.clear()
    add_earthquake_markers(marker_layer_ref, earthquake_data)
    page.update()

def setup_page(page, marker_layer_ref):
    page.title = 'Earthquake Map'
    page.appbar = ft.AppBar(
        title=ft.Text('Significant Earthquakes in the Past 30 Days'),
        bgcolor=ft.Colors.LIGHT_BLUE_ACCENT_700,
        actions=[
            ft.IconButton(
                ft.Icons.REFRESH,
                on_click=lambda e: refresh_data(page, marker_layer_ref),
            ),
        ],
    )
    page.add(
        ft.Text('Hover over the markers to see the earthquake information.'),
        map.Map(
            expand=True,
            initial_zoom=2,
            interaction_configuration=map.MapInteractionConfiguration(
                flags=map.MapInteractiveFlag.ALL,
            ),
            on_init=lambda e: print('Initialized Map'),
            layers=[
                map.TileLayer(
                    url_template='https://tile.openstreetmap.org/{z}/{x}/{y}.png',
                    on_image_error=lambda e: print('TileLayer Error'),
                ),
                map.RichAttribution(
                    attributions=[
                        map.TextSourceAttribution(
                            text='OpenStreetMap Contributors',
                            on_click=lambda e: e.page.launch_url(
                                'https://openstreetmap.org/copyright',
                            ),
                        ),
                        map.TextSourceAttribution(
                            text="U.S. Geological Survey",
                            on_click=lambda e: e.page.launch_url(
                                "https://earthquake.usgs.gov/earthquakes/feed/v1.0/geojson.php",
                            ),
                        ),
                        map.TextSourceAttribution(
                            text='Flet',
                            on_click=lambda e: e.page.launch_url(
                                'https://flet.dev'
                            ),
                        ),
                    ],
                ),
                map.MarkerLayer(
                    ref=marker_layer_ref,
                    markers=[],
                ),
            ],
        ),
    )

def main(page: ft.Page):
    marker_layer_ref = ft.Ref[map.MarkerLayer]()
    setup_page(page, marker_layer_ref)
    refresh_data(page, marker_layer_ref)

ft.app(main)

実行結果

まずはデスクトップアプリケーションとして実行してみます。

uv run flet run


震央を表す円をホバーすると、詳細情報が表示されます。

画面右下のアイコンをクリックすると、出典が表示されます。

次に、Webアプリケーションとして実行してみます。

uv run flet run --web --port 8000

http://localhost:8000/ に接続すると、下記のように表示されます。

デスクトップ版と同じ内容を表示できました!

なお、今回は実施しませんが、アプリケーションを公開・配布する際にはビルドすることになります。Flutter SDKをインストールなどの準備が必要です。詳細はドキュメントを参照ください。

おわりに

この記事では、Fletを使用してマップに地震情報を表示してみました。

はじめてFletを触ってみましたが、クロスプラットフォーム対応なだけでなく、PySimpleGUIなどのGUIライブラリと比較しても、モダンなデザインのアプリケーションが作りやすそうな印象を受けました。機会があれば、活用していきたいです。

脚注
  1. uvのインストール方法についてはこちらを参照ください https://docs.astral.sh/uv/getting-started/installation/ ↩︎

  2. USGSによる定義 https://earthquake.usgs.gov/earthquakes/browse/significant.php#sigdef ↩︎

レスキューナウテックブログ

Discussion