dash-leafletによるインタラクティブな地図可視化

## はじめに
今まで地図の可視化をpythonのFoliumライブラリを使用していましたが、移動する自己位置を地図に表現したいと思っていましたが、調べた結果、FoliumだとできずにDash-Leafletを使うとできるようでした。
調べた内容を備忘録的にまとめました。

## Dashとは
DashはPlotlyによって開発されたPythonフレームワークであり、Dashを使用すると、PythonでインタラクティブなWebアプリケーションの作成ができます。
良い感じにマウスを使って表示範囲を変更しながら、データ可視化やデータ解析をしたいときにこれを簡単に構築することができるすごいフレームワークです。

https://dash.plotly.com/

## Leaflet とは
>leafletはWeb上で地図を表示するオープンソースのマップクライアントライブラリです。
>軽量でスマートフォンなどのタッチ操作にも対応した地図を手軽に表示することができます。
ユーザーによって様々なプラグインが作成されているのが特徴で、それらを組み合わせることで多様な表現を行うことが>できるので「地図なんてGoogle Maps APIで十分じゃね？」という方も、一度触ってみてください。
>by http://shimz.me/blog/leaflet-js/4142

Leaflet とは上の説明通りすごい感じのマップクライアントのJavaScriptライブラリとなっています。

## Dash-Leafletとは？？
単純にDASH + Leafletのイメージでよい模様。

dash-leaflet は、dash フレームワークを拡張して、JavaScriptライブラリであるはずのleafletを使用可能にしたライブラリで、インタラクティブな地図を簡単に作成できるようにするライブラリのようです。
（地図だけでなく、地図外にテキストを表示したりすることも可能です）
・Dash-Leaflet API
https://www.dash-leaflet.com/

## 他のライブラリは？？

同じようにLeafletをベースとしている地図描画用のpythonライブラリとしては、Foliumがあります。
こちらも、インタラクティブな地図を作成することができます。

大きな違いとしては下記の点があります。
・__Dash-Leaflet__：dashフレームワークを拡張しているので、対話的にデータの受け渡しが可能。すなわち、地図データの位置情報などをリアルタイムに変更することができる。
・__Folium__: 静的な地図を生成するためのライブラリであり、対話的なデータの受け渡しができない。すなわち、地図は __生成された後に変更できない__ 


## サンプルコード
地図にラインを描画し、そのラインをクリックすると、そのラインのGeometryを取得することができます。
このような対話的に情報をやり取りするのは、dash-leaflet にしかないメリットということのようです。

```ruby: view_map.py
import dash
from dash import html, dcc
from dash.dependencies import Input, Output, State
import dash_leaflet as dl
from shapely import wkt

app = dash.Dash(__name__)

def convert_to_coords(data):
    linestring = wkt.loads(data)
    lons, lats = list(linestring.xy[0]), list(linestring.xy[1])
    return list(zip(lats, lons))

wkt_data_blue = [
    "LINESTRING (139.6917 35.6895, 139.7017 35.6995, 139.7117 35.6895)",
    "LINESTRING (139.7017 35.6995, 139.7117 35.7095, 139.7217 35.6995)"
]

wkt_data_red = [
    "LINESTRING (139.7117 35.7095, 139.7217 35.7195, 139.7317 35.7095)"
]

coords_list_blue = [convert_to_coords(data) for data in wkt_data_blue]
coords_list_red = [convert_to_coords(data) for data in wkt_data_red]

blue_polylines = [dl.Polyline(positions=coords, color="blue", id=f"blue-{i}") for i, coords in enumerate(coords_list_blue)]
red_polylines = [dl.Polyline(positions=coords, color="red", id=f"red-{i}") for i, coords in enumerate(coords_list_red)]

app.layout = html.Div([
    dl.Map([
        dl.TileLayer(),
        dl.LayerGroup(id='marker-group'),
        *blue_polylines,
        *red_polylines
    ], center=[35.6895, 139.6917], zoom=15, id="live-map", style={'width': '100%', 'height': '500px'}),
    html.Button("Get Center", id="get-center-btn"),
    html.Pre(id='click-data', children='Click data will appear here'),
    html.Pre(id='center-data', children='Center data will appear here'),
    html.Pre(id='click-count', children='Number of clicks: 0')
])

@app.callback(
    Output('click-data', 'children'),
    [Input(polyline.id, 'n_clicks') for polyline in (blue_polylines + red_polylines)],
    [State(polyline.id, 'positions') for polyline in (blue_polylines + red_polylines)]
)
def polyline_click(*args):
    ctx = dash.callback_context

    if not ctx.triggered:
        raise dash.exceptions.PreventUpdate

    clicked_id = ctx.triggered[0]['prop_id'].split('.')[0]
    idx = (blue_polylines + red_polylines).index(next(p for p in (blue_polylines + red_polylines) if p.id == clicked_id))
    coords = args[len(blue_polylines + red_polylines) + idx]

    return f"Clicked on {clicked_id}. Coordinates: {coords}"

@app.callback(
    Output('center-data', 'children'),
    [Input('get-center-btn', 'n_clicks')],
    [State('live-map', 'center')]
)
def display_center_data(n, center):
    if center:
        return f"Center Latitude: {center[0]}, Longitude: {center[1]}"
    else:
        return 'Center data will appear here'

@app.callback(
    Output('click-count', 'children'),
    [Input('live-map', 'n_clicks')]
)
def display_click_count(n_clicks):
    return f"Number of clicks: {n_clicks}"

if __name__ == '__main__':
    app.run_server(debug=True)

```

## おわりに
上記に車のドライブや、自転車で走ったときの、時刻とGPSの情報を紐づけて、現在の位置と走行した経路を描画したりできそうです。