0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Flet の調査 - Map コントロールの使い勝手を確認する

Last updated at Posted at 2024-11-17

はじめに

2024/6 (v0.23.0) に追加された Map コントロールが面白そうなので、使い勝手を確認してみる。

Animation2.gif

Map コントロールの情報

前提事項

  • Flet v0.24.1

調査内容

サンプルを実行してみる

まずは、ドキュメントにあるサンプルをコピペして実行してみる。

  • 問題なく表示され、初期表示はアフリカ大陸
  • クリックでマーカーを置くことができる
  • 右クリックで〇を置くことができる
  • マウスホイールで拡大縮小できる
  • ドラッグで動かすことができる
  • 日本の情報もそれなりに入っている
  • 一番縮小すると変な感じになるので制御可能なのか気になった(地球4.7個分ぐらい見える)
  • 地図のもとは OpenStreetMap というサービス … https://www.openstreetmap.org/about
サンプルソース
import random
import flet as ft
import flet.map as map


def main(page: ft.Page):
    marker_layer_ref = ft.Ref[map.MarkerLayer]()
    circle_layer_ref = ft.Ref[map.CircleLayer]()

    def handle_tap(e: map.MapTapEvent):
        print(
            f"Name: {e.name} - coordinates: {e.coordinates} - Local: ({e.local_x}, {e.local_y}) - Global: ({e.global_x}, {e.global_y})"
        )
        if e.name == "tap":
            marker_layer_ref.current.markers.append(
                map.Marker(
                    content=ft.Icon(
                        ft.icons.LOCATION_ON, color=ft.cupertino_colors.DESTRUCTIVE_RED
                    ),
                    coordinates=e.coordinates,
                )
            )
        elif e.name == "secondary_tap":
            circle_layer_ref.current.circles.append(
                map.CircleMarker(
                    radius=random.randint(5, 10),
                    coordinates=e.coordinates,
                    color=ft.colors.random_color(),
                    border_color=ft.colors.random_color(),
                    border_stroke_width=4,
                )
            )
        page.update()

    def handle_event(e: map.MapEvent):
        print(
            f"{e.name} - Source: {e.source} - Center: {e.center} - Zoom: {e.zoom} - Rotation: {e.rotation}"
        )

    page.add(
        ft.Text("Click anywhere to add a Marker, right-click to add a CircleMarker."),
        map.Map(
            expand=True,
            configuration=map.MapConfiguration(
                initial_center=map.MapLatitudeLongitude(15, 10),
                initial_zoom=4.2,
                interaction_configuration=map.MapInteractionConfiguration(
                    flags=map.MapInteractiveFlag.ALL
                ),
                on_init=lambda e: print(f"Initialized Map"),
                on_tap=handle_tap,
                on_secondary_tap=handle_tap,
                on_long_press=handle_tap,
                on_event=handle_event,
            ),
            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="Flet",
                            on_click=lambda e: e.page.launch_url("https://flet.dev"),
                        ),
                    ]
                ),
                map.SimpleAttribution(
                    text="Flet",
                    alignment=ft.alignment.top_right,
                    on_click=lambda e: print("Clicked SimpleAttribution"),
                ),
                map.MarkerLayer(
                    ref=marker_layer_ref,
                    markers=[
                        map.Marker(
                            content=ft.Icon(ft.icons.LOCATION_ON),
                            coordinates=map.MapLatitudeLongitude(30, 15),
                        ),
                        map.Marker(
                            content=ft.Icon(ft.icons.LOCATION_ON),
                            coordinates=map.MapLatitudeLongitude(10, 10),
                        ),
                        map.Marker(
                            content=ft.Icon(ft.icons.LOCATION_ON),
                            coordinates=map.MapLatitudeLongitude(25, 45),
                        ),
                    ],
                ),
                map.CircleLayer(
                    ref=circle_layer_ref,
                    circles=[
                        map.CircleMarker(
                            radius=10,
                            coordinates=map.MapLatitudeLongitude(16, 24),
                            color=ft.colors.RED,
                            border_color=ft.colors.BLUE,
                            border_stroke_width=4,
                        ),
                    ],
                ),
                map.PolygonLayer(
                    polygons=[
                        map.PolygonMarker(
                            label="Popular Touristic Area",
                            label_text_style=ft.TextStyle(
                                color=ft.colors.BLACK,
                                size=15,
                                weight=ft.FontWeight.BOLD,
                            ),
                            color=ft.colors.with_opacity(0.3, ft.colors.BLUE),
                            coordinates=[
                                map.MapLatitudeLongitude(10, 10),
                                map.MapLatitudeLongitude(30, 15),
                                map.MapLatitudeLongitude(25, 45),
                            ],
                        ),
                    ],
                ),
                map.PolylineLayer(
                    polylines=[
                        map.PolylineMarker(
                            border_stroke_width=3,
                            border_color=ft.colors.RED,
                            gradient_colors=[ft.colors.BLACK, ft.colors.BLACK],
                            color=ft.colors.with_opacity(0.6, ft.colors.GREEN),
                            coordinates=[
                                map.MapLatitudeLongitude(10, 10),
                                map.MapLatitudeLongitude(30, 15),
                                map.MapLatitudeLongitude(25, 45),
                            ],
                        ),
                    ],
                ),
            ],
        ),
    )


ft.app(main)

↓初期表示
image.png

↓日本に移動した表示
image.png

↓一番縮小した表示
image.png

初期表示位置を変えてみる

初期表示位置を東京駅に変えてみる。

  • コントロールの定義に設定があるので変更した
  • ★1 で緯度と経度を指定するとそこが中心になるので、東京駅の緯度経度を調べて設定
  • ★2 は、zoomが 4.2 だと足りないのでちょうどよさそうな 16 に変更

↓変更箇所抜粋

        map.Map(
            expand=True,
            configuration=map.MapConfiguration(
                initial_center=map.MapLatitudeLongitude(35.6809591, 139.7673068), # ★1
                initial_zoom=16, # ★2
                interaction_configuration=map.MapInteractionConfiguration(
                    flags=map.MapInteractiveFlag.ALL
                ),

↓変更後の初期表示
image.png

マーカーの表示を変更してみる

サンプルのマーカー表示がいまいち見えづらいので、見えやすい表示に改善する。

  • Icon だけだと地図との境界がなくて見えづらい
  • IconButton なども試したが、CircleAvatar に Icon を入れるが一番良さそうである
  • 試しに食事場所をプロットするイメージのアイコンにしてみた

↓変更箇所抜粋

    def handle_tap(e: map.MapTapEvent):
        print(
            f"Name: {e.name} - coordinates: {e.coordinates} - Local: ({e.local_x}, {e.local_y}) - Global: ({e.global_x}, {e.global_y})"
        )
        if e.name == "tap":
            marker_layer_ref.current.markers.append(
                map.Marker(
                    content=ft.CircleAvatar( # ここを変更
                        content=ft.Icon(ft.icons.DINING_OUTLINED),
                        color=ft.colors.RED,
                        bgcolor=ft.colors.WHITE,
                    ),
                    coordinates=e.coordinates,
                )
            )

↓元の表示
image.png

↓変更後の表示
image.png

縮小しすぎるのを防ぐ

デフォルトで最大まで縮小すると小さくなり過ぎるので制御をいれる。

  • これは min_zoom を設定するだけで縮小を止めることが可能
  • 今回は、min_zoom=12 ぐらいがちょうど良かった
  • 同様に拡大も行き過ぎるので、max_zoom=18 を設定した
  • Map を使うときは min_zoom, max_zoom を指定した方が良さそう

↓変更箇所抜粋

        map.Map(
            expand=True,
            configuration=map.MapConfiguration(
                initial_center=map.MapLatitudeLongitude(35.6809591, 139.7673068),  # ★1
                initial_zoom=16,  # ★2
                interaction_configuration=map.MapInteractionConfiguration(
                    flags=map.MapInteractiveFlag.ALL,
                ),
                min_zoom=12,  # ここを追加
                max_zoom=18,  # ここを追加

↓min_zoom=12 で最大縮小した様子
image.png

↓max_zoom=18 で最大拡大した様子
image.png

情報が入力できるようにしてみる

マーカーを置くときに情報を入力するステップを追加する。

  • マーカーを配置後、BottomSheet を表示
  • 保存ボタンをクリックしたら情報を保存する(とりあえず保存はしない)
  • キャンセルならマーカーを削除する

↓変更箇所抜粋(BottomSheet の実装)

    def handle_bs_save(e):
        # TODO: ここで情報を保存する
        page.close(bs)

    def handle_bs_cancel(e):
        del marker_layer_ref.current.markers[-1]  # 最後のマーカーを削除する
        page.update()
        page.close(bs)

    def bs_open():
        tf_comment.value = ""  # コメントクリア
        page.open(bs)

    tf_comment = ft.TextField(
        label="コメント", multiline=True, max_lines=5, autofocus=True
    )

    bs = ft.BottomSheet(
        dismissible=False,
        content=ft.Container(
            padding=20,
            content=ft.Column(
                tight=True,
                controls=[
                    ft.Text("情報"),
                    tf_comment,
                    ft.Row(
                        [
                            ft.ElevatedButton("保存", on_click=handle_bs_save),
                            ft.ElevatedButton("キャンセル", on_click=handle_bs_cancel),
                        ]
                    ),
                ],
            ),
        ),
    )

↓変更箇所抜粋(tapでBottomSheetを表示)

    def handle_tap(e: map.MapTapEvent):
        ・・・省略・・・
        if e.name == "tap":
            marker_layer_ref.current.markers.append(
                map.Marker(
                    content=ft.CircleAvatar(  # ここを変更
                        content=ft.Icon(ft.icons.DINING_OUTLINED),
                        color=ft.colors.RED,
                        bgcolor=ft.colors.WHITE,
                    ),
                    coordinates=e.coordinates,
                )
            )
            page.update()
            bs_open()  # BottomSheetを表示

↓実際の動き
Animation1.gif

マーカーをクリックしたら情報を表示する

配置したマーカーをクリックしたら情報を表示するようにする。

  • マーカーをクリックできるように Container で囲い、クリックの処理を足す
  • BottomSheet の表示を新規と編集で出し分ける
  • 編集のときは、保存するか、単に閉じるだけにする

↓変更箇所抜粋(BottomSheet を編集の場合に対応)

    def bs_open(edit=False):
        tf_comment.value = ""  # コメントクリア
        buttons[1].visible = True
        buttons[2].visible = False
        if edit: # 編集の場合
            tf_comment.value = "ダミーコメント" # TODO:保存してある情報を持ってくる
            buttons[1].visible = False
            buttons[2].visible = True
        page.open(bs)
    ・・・省略・・・
    
    buttons = [
        ft.ElevatedButton("保存", on_click=handle_bs_save),
        ft.ElevatedButton("キャンセル", on_click=handle_bs_cancel),
        ft.ElevatedButton("閉じる", on_click=handle_bs_close),
    ]

    bs = ft.BottomSheet(
        dismissible=False,
        content=ft.Container(
            padding=20,
            content=ft.Column(
                tight=True,
                controls=[
                    ft.Text("情報"),
                    tf_comment,
                    ft.Row(buttons),
                ],
            ),
        ),
    )

↓変更箇所抜粋(Marker の設定を変更)

    def handle_tap(e: map.MapTapEvent):
        ・・・省略・・・
        if e.name == "tap":
            marker_layer_ref.current.markers.append(
                map.Marker(
                    content=ft.Container(
                        ft.CircleAvatar(  # ここを変更
                            content=ft.Icon(ft.icons.DINING_OUTLINED),
                            color=ft.colors.RED,
                            bgcolor=ft.colors.WHITE,
                        ),
                        on_click=lambda e: bs_open(True), # クリックでBottomSheetを開く
                    ),
                    ・・・省略・・・

↓実際の動き
Animation2.gif

まとめ

Map コントロールを使えば、地図上に情報をプロットすることが簡単にできることが確認できた。
マーカーにはコントロールが指定できるので、工夫すればいろいろなことができそうではある。

以上です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?