Strava heatmapを使ったOSRM最適化

投稿者: | 2017年10月26日

今回は、Stravaのheatmapを使って、自転車経路の最適選択するというテーマです。2年ほど前からアイディアはあったのですが、最近OSRMのドキュメントを見ていたら、意外と簡単に実現できそうだったのでやってみました。

自転車で実際走っていると、「実は並行している別の道路の方が走りやすかった」「そもそも自転車通行禁止だった」等に遭遇します。道路の属性データの質・量を上げていくのも1つの解決策ですが、そこは地元の自転車乗りが一番知っているはず。Stravaのheatmapを眺めていると「ああやっぱりみんなこっち走っているのね」とか「あれ?こんなルートあったの?」とか思うことが多々あるので、このデータを使うと満足度の高いルート探索が実現できそうと思ったのが始まり。

構築手順

まず基本になるのが、こちらのOSRMのドキュメント。最後の方にある”Using Raster Data”というところ。予め用意しておいたラスターデータを使って経路の重み付けをできるというものです。例では標高差を考慮した最適化が紹介されていますが、今回はStrava Heatmap のデータを食わせればいいということになります。(そういう意味で汎用的に作ってあるんだなぁ、と感心)

さて、次なる課題は、以下になります。

  • 入力データはどうやって作る?
  • 入力データはどうやってルート最適化ロジックに反映させるの?

入力データ作成

ベースとなる入力データは当然Strava Heatmap サイトからダウンロードしてきます。

http://globalheat.strava.com/tiles/cycling/color7/${z}/${x}/${y}.png

こちらはXYZ形式の座標指定のPNG形式となっています。こちらを地図上で確認しやすいようにGeoTIFF形式に変換します。

$ gdal_translate -of Gtiff -a_ullr [xmin] [ymin] [xmax] [ymax] -a_srs EPSG:4326 src.png dst.tif

重み付けにあまり詳細なデータもいらないので、ある程度道路をカバーしているzoomlevel=10のデータを使うことにしました。作成したデータはQGIS等で確認することができます。サンプルは以下。わかりやすいように zoomlevel=10 のタイル 2×2 で伊豆半島をカバーするGeoTIFFを作成してみました。

ちなみにGeoTIFFのマージは、以下でできます。

$ gdal_merge.py -o <output_file> <input_files ...>

ここで作成したファイルはRGBA形式(4層)ですが、OSRMが処理しやすように1層にします。本来は、RGBAの4層の値から評価値を算出すべきなのですが、データを眺めてみたら緑(G)だけ抜き出してもそれなりに使えそうなデータになっていたので、とりあえずこれで行ってみることにしました。抜き出したデータを沼津〜富士のルートを地図上に重ねてみるとわかりますが、自動車のメジャーのルートである国道1号バイパスに比べ、海岸に近い県道380号(通称:旧国一)が自転車のメジャールートになっています。

このようにして、ひとまず入力するデータは用意できました。

本来は、これをASCII形式のラスターデータに変換する必要があるのですが、GeoTIFFのまま扱えた方が何か便利かと思い、osrmのraster読み込み部分を修正してみました。現状はやっつけ修正ですが、GeoTIFF対応版のソースはgithubに上げておきます(branch:charilab-tuned)。

最適化ロジックの作成

こちらは、先述のドキュメントにあるとおり、lua言語で記述されているprofileを修正するこで対応します。キーとなるのは process_segment()の部分。少し試した結果、以下のようにしてみました。

function process_segment (profile, segment)
  local sourceData = raster:query(profile.raster_source, segment.source.lon, segment.source.lat)
  local targetData = raster:query(profile.raster_source, segment.target.lon, segment.target.lat)
  local invalid = sourceData.invalid_data()
  local scaled_weight = segment.weight

  if sourceData.datum ~= invalid and targetData.datum ~= invalid then
    if sourceData.datum > 80 then
      local rate = sourceData.datum / 1000.0
      scaled_weight = scaled_weight * (1.0 - rate)
    end
    if targetData.datum > 80 then
      local rate = targetData.datum / 1000.0
      scaled_weight = scaled_weight * (1.0 - rate)
    end
  end

  segment.weight = scaled_weight
end

お試し

先ほどの例で上げた沼津〜富士のルートを試してみるとこんな感じ。左側がheatmapの重み付けを加えたエンジンでのルート。

一般的にもわかりやすいルートとして、横浜から小田原までのルート。自転車乗りは国道134号をメインで使いますよね。strava heatmap と重ねてみてもメジャーなルートが選択されているのがわかります。

ちなみに、以前のエンジンでもそこそこいい線いっているのは、すでに自転車禁止区間を考慮するエンジンになっているため(この記事参照)。Googleやルートラボでどうなるかは各自お試しください。

まとめ

とりあえず、Strava Heatmap を使ったルート最適化のプラットフォームは構築でき、ある程度の効果が見込めることはわかりましたが、実際サービスで使うとなると大変なのはここから。課題としてはこんな感じ。

  • チューニングはエンジンのチューニング(入力データの評価値の改善、重み付けの評価式の改善)には、現状経験と勘に頼らざるを得ない。
  • パラメータ変更には検索用のデータの再構築(数時間かかる)が必要なので、試行錯誤の効率が悪い。
  • そもそも最適ルートは、人によって違う。(アップダウン量、交通量、景色、トンネルの有無とか好みが多種多様)

まあ、こんなことを始めた理由は、「大手サービスが提示したルートではなく、自分好みのルートを探したい」だったので、ここで汎用的な1つのチューニングを模索するよりは、今後は、いかに個人の嗜好をルート探索に取り入れていくかを考えていきたい。

まずは一歩前進したということで。