Elixir Livebook で佐賀県のGTFSからバス停を地図に落とせたらいいなー

# はじめに
「GTFSを使うことになったから調べてて。」とのこと。
何のことでしょう。GTFSって何? 路線バス情報? データですか?
ならば「@nako_sleep_9hさん主催のpiyopiyo.ex #14：Elixirでデータ分析をしてみよう！」でLivebook　Explorerの使い方を勉強会したばかりのElixir初心者として、これを利用しない手はありません。

ここは業務の大義名分の下、Livebookを使ってElixirを勉強するシチュエーションに持ちこみましょう。
まあ何だかわからないけど手始めに、「Elixir Livebookで佐賀県のGTFSからバス停を地図に落とせたらいいなー」をやってみました。

# 実行環境
* Mac Ventura 13.1 
* Livebook app 

Livebookはデスクトップアプリを使ってます。

https://livebook.dev/

# 佐賀県のGTFSデータ
使うGTFSがまだ手に入らないとのことなので、佐賀県が公開してくださってるデータをありがたく使わせていただきます。

http://opendata.sagabus.info/

saga-current.zip をダウンロードして、@RyoWakabayashi さんの記事を参考にして読み込んでみます。

https://qiita.com/RyoWakabayashi/items/dfb5495b9c25eff710a2#comment-9fc58ad4ce4359709dce

色々やってるうちにこんなになってしまったセッティング。
```elixir
Mix.install(
  [
    {:explorer, "~> 0.5"},
    {:csv, "~> 3.0"},
    {:geo, "~> 3.4"},
    {:kino, "~> 0.8"},
    {:kino_maplibre, "~> 0.1.3"},
    {:req, "~> 0.3.0"}
  ]
)
```
上記記事とか色々を参考にしたエリアスとかの設定
```elixir
alias Explorer.DataFrame, as: Df
alias Explorer.Series, as: Sr
alias MapLibre, as: Ml
require Explorer.DataFrame
```
試しにダウンロードデータ解凍してフォルダごとノートファイルのとこに設置して読み込んでみます。
```elixir
Path.absname('./gtfs-sample/saga-current/calendar_dates.txt')
|> Df.from_csv!()
|> Kino.DataTable.new()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3657/64e062d9-fcd0-4311-a6e5-42d907b790d1.png)

こんな調子で本命の停留所データも読み込み....できない....
とあるカラムがintegerと解釈されてる中で空文字("")が混じっていて、そこではじかれてるらしい?

ここで、「データフレーム」すらわかってなかった自分に気が付く。

データ解析がしやすい形になっているらしい?
ではCSVをまんま読み込んで文字データでデータフレームの形を作ってみてはどうだろうか。
```elixir
stops_data = 
  Path.absname("./gtfs-sample/saga-current/stops.txt")
  |> File.stream!()
  |> Enum.map(& &1 |> String.replace("\uFEFF", ""))
  |> CSV.decode!()
  |> Enum.to_list()
```
BOM入ってるしー。
これをヘッダとデータ切り離して、ぐに〜と交わらせたらデータフレーム作れるんじゃないだろうか。
```elixir
csv_header =
  stops_data
  |> hd()
  |> Enum.map(& &1 |> String.to_atom())

csv_maps =
  stops_data
  |> tl
  |> Enum.map(& List.zip([csv_header, &1]) |> Enum.into(%{}))
stops_df =
  csv_maps
  |> Df.new()

stops_df |> Kino.DataTable.new()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3657/d83a27ef-b1ca-559e-3371-b517724c3144.png)

それらしいのが出てきた。けど、このままだと全部文字列データなので、緯度経度だけでもなんとかします。
```elixir
stop_lat = stops_df[:stop_lat] |> Sr.cast(:float)
stop_lon = stops_df[:stop_lon] |> Sr.cast(:float)

stops_df =
  stops_df
  |> Df.put(:stop_lat, stop_lat)
  |> Df.put(:stop_lon, stop_lon)

stops_df |> Kino.DataTable.new
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3657/f19ccaf3-0bf9-08a9-3c6b-16618cd13292.png)


# GeoJSON にして可視化してみる

どうしたら地図に落とせるのだろうか、ということでまた@RyoWakabayashiさんの記事のお世話になります。

https://qiita.com/RyoWakabayashi/items/1a12439d2971444ba33b

GeoJSONにするために、さっきのデータフレームから緯度経度のタプル配列を作ってみます。
```elixir
lon_list = 
  stops_df[:stop_lon]
  |> Sr.to_enum()
  |> Enum.to_list()

lat_list =
  stops_df[:stop_lat]
  |> Sr.to_enum()
  |> Enum.to_list()

lon_lat_list = 
  lon_list
  |> Enum.zip(lat_list)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3657/1806b960-74e4-5745-a8c6-60a738980ce8.png)

ここで気が付く。自分が描きたいのはポイントで、記事にあるポリゴンデータではない。
ではGeoライブラリにポイントっぽいものを探してみます。
ここで@piacerexさんが hexdocs.pm のURLの後にライブラリ名入れると調べられると言っていたのを思い出したのでやってみます。

https://hexdocs.pm/geo/

%Geo.Point がそれっぽい?
てゆーことは、もしかしてポリゴンデータの配列の代わりにポイントデータの配列を形作ってやればいいのだろうか。
```elixir
stop_geojson =
  %Geo.GeometryCollection{
    geometries: lon_lat_list
      |> Enum.map(fn p -> %Geo.Point{coordinates: p} end)
  }
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3657/7c3c7f0b-2c36-0e80-57a6-de46042f64fc.png)

なんかそれっぽくなった気がします。
では、上の@RyoWakabayashiさんの記事を参考にしてセンター座標を作ってみます。
```elixir
stop_coordinates =
  stop_geojson.geometries
  |> Enum.map(& &1.coordinates)

lon = Enum.map(stop_coordinates, & elem(&1, 0))
lat = Enum.map(stop_coordinates, & elem(&1, 1))
stop_center =
  {
    (Enum.min(lon) + Enum.max(lon)) / 2,
    (Enum.min(lat) + Enum.max(lat)) / 2
  }
```

これをMapLibreに入れたら地図に落としてくれるのだろうか。
スマートセルを使わないでコードで書いてみたい。
ところで落としたいのはポイントなのでそれっぽいのないだろうかと探す。

https://hexdocs.pm/maplibre

見てるとcircleというのがある。これを使えばいいのかなと思った。

と、ここでMapLibreの :style にいい感じな地図データのURL例を見つけました。

```elixir
Ml.new(center: stop_center, zoom: 11, style: "https://api.maptiler.com/maps/streets/style.json?key=get_your_own_OpIi9ZULNHzrESv6T2vL")
|> Ml.add_geo_source("data", stop_geojson)
|> Ml.add_layer(
  id: "stop",
  source: "data",
  type: :circle,
  paint: [circle_color: "#aa00ff", ]
)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3657/e1ee3b6a-c50c-7ba6-661b-daff61efc625.png)

びっくりです。とてもらしいんですけど、マジですか?!
生半可なelixir初心者でもこんなことが出来てしまうのですか?

しかも、知らなかったのですが、このMapLibreってクォータービューになる!!
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3657/695e0c47-ce3a-d44d-71c4-4e9e633af5c6.png)
凄すぎる!!


# まとめ
GTFSとお友達になるためにElixir Livebookを使うのは楽しい。
(振り返ってみると無理やりデータフレームにする必要あったのだろうか?)