# 緯度経度の座標情報をkepler.glで可視化する [kepler.gl](https://kepler.gl/)は地図上で様々なデータを可視化することができるツールである。今回は仙台市職員及び[ファンライド事業](https://www.city.sendai.jp/kyodosuishin/kurashi/manabu/npo/shimin/jisshijigyo/sedo/r2.html)に参加している一般市民の自転車移動データを動画で可視化する。今回のキモは座標データをkepler.glが読み込めるようなGeoJsonファイルに変換するところ。 # 手元にあるもの gpxデータから作成したcsvファイル。 内訳はこのようになっている。 | ID | 時刻 | 経度 | 緯度 | 速度 | trackID | segmentID | pointID | |:---:|:--------:|:-----:|:-----:|:-----:|:-------:|:---------:|:-------:| | int | datetime | float | float | float | int | int | int | このうち、IDとtrackIDで分類すればトリップを一意に特定できるっぽい仕組みになっている。 # 求められているもの 公式ドキュメントによると、 > **How to use trip layer to animate path** Data format Currently trip layer support a special geoJSON format where the coordinate linestring has a 4th element denoting timestamp. In order to animate the path, the geoJSON data needs to contain LineString in its features' geometry, and the coordinates in the LineString need to have 4 elements in the format of [longitude, latitude, altitude, timestamp], with the last element being a timestamp. Valid timestamp formats include unix in seconds such as 1564184363 or in milliseconds such as 1564184363000. ということらしい。要約すると以下が必要である。 * GeoJsonファイル * LineString形式 * 座標形式:[**経**度,**緯**度,標高,UNIX秒] **【追記】ポイントデータは3点以上ないと動画による可視化ができないので、ODペアのみのデータを用いる際には別の手法を使ったほうが良い** ありがたいことにサンプルデータもついており、 ```geojson= { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "vendor": "A" }, "geometry": { "type": "LineString", "coordinates": [ [-74.20986, 40.81773, 0, 1564184363], [-74.20987, 40.81765, 0, 1564184396], [-74.20998, 40.81746, 0, 1564184409] ] } } ] } ``` この形式が求められている。 # GeoJsonについて だいたいJsonと同じらしい。Googleのロケーション履歴をダウンロードしようとするとJson形式で提供されるし、地理情報を含むJsonファイルを区別の意味を込めてGeoJsonと呼んでいるような雰囲気を感じた。実際、同じ内容で拡張子だけ.jsonにしたファイルも同様に読み込むことができた。~~ちなみに、今回の内容は**時間と根気**があればテキストエディタで平文を打ち込んで、拡張子を.geojsonにすれば終わってしまう内容である。~~ 改めて先ほどのサンプルデータに注目すると、 - FeatureCollection - type - Features - type - properties - type - vendor - geometry - type - coordinates という構造になっている。つまり、これを満たす構造にしてやれば読み込まれるということである。 # DataFrameから座標リストの作成 各データの列名は以下の通り。(今回は標高データがないので速度データで代用している) | ID | 時刻 | 経度 | 緯度 | 速度 | trackID | segmentID | pointID | |:---:|:--------:|:-----:|:-----:|:-----:|:-------:|:---------:|:-------:| | "ID" | "time" | "longitude" | "latitude" | "speed" | "trackID" | "segmentID" | "pointID" | まず、時刻をdatetime型からtimestamp型(UNIX秒)に変換する。 ```python= df["time"] = df["time"].apply(lambda x: x.timestamp()) #DataFrame ``` 次にDataFrameから必要な項目だけを取り出す。リストで指定することで複数項目を一気に取得できる。 ```python= df_coordinate = df[["longitude","latitude","speed","time"]] #DataFrame ``` 次に、IDとtrackIDで分類できるようにする。 ```python= g = df_coordinate.groupby(["ID","trackID"]) #DataFrameGroupBy ``` 次に各行(df[["longitude","latitude","speed","time"]]の一行ずつ)をリストにする。 coordinate_listにはdf_coordinateのID,trackID毎のリストのSeriesが作成される。 ```python= coordinate_list= g.apply(lambda d : d.values.tolist()) #Series ここ間違えやすい ``` 最後にSeriesをlistに変換する。 ```python= coordinate_list = coordinate_list.values.tolist() #list ``` これで、手元にリスト[経度,緯度,標高,UNIX秒]の1トリップ分のリストのリストができている。 - すべてのトリップのリスト - 1トリップのリスト - 1点のリスト - 経度 - 緯度 - 標高(速度) - 時刻 という構造である。 # GeoJson形式への変換 前項で作成したリストを用いて、GeoJsonデータを作成する。これには[geojson](https://pypi.org/project/geojson/#geojson-encoding-decoding)というライブラリを用いた。 素人目の認識としては、json書式に整えてくれるモジュールである。 先ほども書いた通り、求められるデータは以下の形式である。これらのデータを下から組み立てるように作成していくのが着実である。 - FeatureCollection - type(自己紹介) - Features - type(自己紹介) - properties(geometoryの詳細) - type(自己紹介) - vendor - geometry(地理データ) - type(自己紹介) - coordinates(LineString) このモジュールは単純で、例えばFeatureを作りたければ以下のようにすればよい。 つまり、自己紹介部分や括弧などを勝手に追加してくれるというものである。 ```python= >>> Feature(geometry=my_point) # doctest: +ELLIPSIS {"geometry": {"coordinates": [-3.68..., 40.4...], "type": "Point"}, "properties": {}, "type": "Feature"} ``` この例文を参考に組み立てたものを以下に示す。 ```python= #Featureを蓄積するリストを作る feature_list = list() #FeatureリストにFeatureを蓄積させる for i in range(len(coordinate_list)): #ptsに1トリップ分のリストを代入 pts = coordinate_list[i] #lsにptsをGeoJson形式に変換したものを代入 ls = geojson.LineString(pts) #lsをgeometryとし、propertiesと合わせてFeatureを作成 #feature_listの末尾に追加 feature_list.append(geojson.Feature(properties={'tripID': str(i)},geometry=ls)) #feature_listをFeatureCollectionに変換 Tripdata = geojson.FeatureCollection(feature_list) ``` これによってTripdataには求められる形式の平文が入ったものとなる。 ちなみにpropertiesにはFeature毎の識別情報を入れることが可能である。工夫次第では人ごとに分類、時間ごとに分類など様々な分類ができる。 # 書き出し ここまで出来ればほぼ終わりである。 新たに拡張子に.geojsonをもつファイルを作成し、そこに平文を書き込むことでデータの完成となる。 ```python= path = './Tripdata.geojson' with open(path, 'w') as f: f.write(geojson.dumps(Tripdata)) f.close() ``` # 可視化 大げさに可視化と書いたが、先ほど作成したデータをドラッグ&ドロップするだけで終了である。 [kepler.gl](https://kepler.gl/demo)を開く。 ![top](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1028597/c827da0d-a68b-a360-3623-84267d75fb68.png) GET STRATEDをクリックすると、以下の画面に遷移する。 ![get started](https://i.imgur.com/Z9qduxn.png) Drag & Drop Your File(s) Here にドラッグ&ドロップすれば自動的に動画にしてくれる。 移動する点は、色・サイズ・尾の長さが指定可能であり、レイヤを上下させることで表示する順番も指定できる。また、propertiesに属性情報を追加しておくことで、それをもとに分類することも可能である。 データはローカルで処理されるので、個人情報を入れても大丈夫(公式談)らしい。 # Tipsとまとめ すべてのデータについて、年月日だけを同じ日にしてやると時間帯のみの可視化ができるので、移動の多い時間帯が一目でわかりやすい。 変換はdatetimeのうちがやりやすいので、UNIX秒に変換する前にreplace()をするとよい。 ```python= df["time"] = df["time"].apply(lambda x: x.replace(year=2021,month=1,day=1)) df["time"] = df["time"].apply(lambda x: x.timestamp()) ``` これら一連の流れでとても見栄えのいい可視化ができるので、ぜひ挑戦してほしい。