Google Maps の検索結果から Yahoo! カーナビで経路案内できる iOS ショートカット
…を作りました。
2021 年 1 月 30 日追記
2020 年 7 月頃に Google Maps 側の仕様変更に伴い動かなくなってしまい、自身も iOS ユーザで無くなったことからメンテされていなかったこのショートカットですが、@musubi05 さんが後継となるショートカットを作成し公開してくれました。iOS ショートカットの紹介記事としては未だこの記事も有用かと思いますが、単にショートカットを利用したいだけという場合は、『「Google Maps の検索結果から Yahoo! カーナビで経路案内できる iOS ショートカット」を復活させた』を参照してください。
追記終わり。
動作している様子
見て〜!これ良くない? pic.twitter.com/ORzK8uJtL6
— Otchy (@otchy) 2019年6月4日
ショートカットのインストール用リンク
v2: https://www.icloud.com/shortcuts/7af5726aeab24b7ea4c278e03949e28d
v1: https://www.icloud.com/shortcuts/4bc792b730ad41ea9d1b8fc0177aa771
[2019-07-28 追記] 一部の URL に対応出来ていない事が分かりました。
※詳しくは後述しますが、将来的にショートカットをアップデートする必要が発生する可能性が高いです。このページをストックなりしておいてもらえれば、将来的にこのリンクをアップデートした時に通知を送るようにします。
ショートカットを作成した背景
Google Maps がゼンリンの地図の使用をやめた影響で色々と起こしている問題については、今さらここで細かくは触れません。正直なところ都内で使っている分にはさほどの不便は感じないですし、まあ Google Maps のままでもいいかなと思っています。しかしながら、ゼンリンをやめる以前からアグレッシブ過ぎる経路案内で民家の塀ギリギリを運転する羽目になったりという事はしょっちゅうですし、またゼンリンをやめて以降、特に地方部での経路案内の劣化が顕著です。先週末旅行に行った際も、平気で車止めがしてある神社の境内を行けといい、そのまま石段を下れなどと指示をされるので閉口してしまいました。間違っても徒歩ルート検索はしていません。カブを運転する新聞配達のおにーさんが Android ユーザだったりするんでしょうかね?こういうケースって。
まあともかく、Google が CGM で行くと舵を切った以上、都市部はともかくとして、残念ながら地方部で急激に経路案内が改善するという事は考えにくいです。例えば「人口の 95% が住む領域で 95% 程度の正確さがあればローンチには十分だ。精度はこれから上げていけばいい。」というのは Web 企業の考えとして理解出来るのですが、経路案内が本当に重要なのってむしろ残りの 5% の方だったりするので、このまま Google Maps に頼り切るのは大きなリスクであると考えました。
代替案として有力なのは Yahoo! カーナビで、以前から経路案内の優秀さには定評がありました。もちろん地図も安心安全のゼンリンです。ただ如何せん、Google Maps の強力な検索機能は捨てがたい、ローカルデータの優秀さは捨てがたい、ヘビーユースしてるマイマップの便利さは捨てがたい。そこでこれらの利点を損なうことなく、経路案内の部分だけ簡単に外部にアウトソースする解決策がこのショートカット、という事になります。
iOS ショートカットの技術的解説
ここからがようやく本題です。最終的な解決策に至るまでの経過をまず紹介させて下さい。
試行錯誤の日々
ツイッターの自分のつぶやきを追ってみると、最初 2019-03-22 に、ゼンリンをやめてしまった Google Maps に絶望したあと、2019-04-06 にはこのアイディアを思いついて、色々な、ある種の正攻法で試行錯誤するも、iOS ショートカットの機能だけで実現することが叶わずいったん諦めていました。
再び気運が盛り上がったのは 2019-05-29 に Puppeteer が GAE で動くと分かった時で、サーバサイドでほとんどを動かす方向で、けっこういいところまで開発を進めていました。ただその過程で、Google Maps が返す HTML の解析などを進めていると「あれ?これはやっぱりサーバサイド無くてもやっぱりいけるかも??」となって最終的に完成したのがこの iOS ショートカットになります。
したがって、外部リソースには一切依存せず、iOS ショートカット内で全ての機能が完結しています。ですので、Google Maps ならびに Yahoo! カーナビの仕様が変わらない限りはこのまま動作し続けるはずです。
ショートカットの全体像
過去にははてブを開くなんていうショートカットを作って公開したり、他にもいくつか作ったりしたことはあるのですが、自分が書いた iOS ショートカットの中では最長になりました。
このまま読み下すのは厳しいと思うので、各部に分けて詳細を解説していきます。実際にショートカットを作り始めてみて、いくつものハマりポイントにハマり、時にはハックを編み出したりしなければならなかったので、iOS ショートカットならではの Tips に溢れています。
URL の取得
Google Maps アプリからランドマークを他のアプリに "シェア" すると、例えば下記のような情報が得られます。ランドマークの名前、その住所、そしてそれを表す短縮 URL ですね。
東京駅
〒100-0005 東京都千代田区丸の内1丁目9
https://goo.gl/maps/Sztm38eCx3kUxdLr7
これはシンプルな改行区切りのテキストで、この中から実際に目的地の緯度経度を所得出来る URL を取り出したいわけです。そこで便利アクション「入力からURLを取得」の出番です。このアクションにテキストを渡すと、その中から URL を探して返してくれます。これで万事上手くいく!…と思うじゃないですか?
ところが、Google Maps がシェアするテキストの中に電話番号が含まれる場合、Apple は「ほら電話番号も要るやろ?tel:
スキーマ付けて URL にしといたで?どや!便利やろ?」って余計なことをするため、「入力からURLを取得」の結果が複数の URL からなるリストになって上手くいきません。Safari でウェブページを開いた時に電話番号っぽい文字列があると勝手にリンクするあれをそのまま適用した感じですね。ええ、だいぶ長いこと原因が分からずにハマりましたとも。
そこで産まれたのがこのハックです。まず入力のテキストを ://
という区切り文字でリストに分割します。今回の例だと ["東京駅\n〒100 … 1丁目9\nhttps", "goo.gl/maps/Sztm38eCx3kUxdLr7"]
といった長さが 2 のリストが得られます。そして短縮 URL が必ず最後にある事を利用して、結果のリストから「最後の項目を取得」します。そして得られた goo.gl/maps/Sztm38eCx3kUxdLr7
という文字列の前に https://
を付けた新しい URL
オブジェクトを生成することで、本来必要としている URL を取得出来ます。
HTML の取得
HTTP(S) でのコンテンツの取得には「URLの内容を取得」アクションを使います。設定で、GET
, POST
, PUT
... などのメソッドを選択することも出来るし、任意の HTTP ヘッダも追加出来る思ったより真っ当な感じで、ヘルプ曰く「アクションに渡されたURLの内容を取得します。」なので、これでその短縮 URL を GET
すれば必要な HTML が手に入る!…と思うじゃないですか?
最初は、短縮 URL は上手く動かないんじゃないかと心配しました。「URLを展開」なんていう、短縮 URL の 301
, 302
リダイレクトを解釈して最終結果の URL を取得するための専用アクションなんかも用意されていますし。ただその心配は杞憂でした。単に「URLの内容を取得」するだけで短縮 URL の展開は自動的にやってくれました。
困ったのは結果のフォーマットです。テキストとして解釈しようとしたら、HTML そのものではなく、HTML をレンダリングした結果のテキストになってしまい、無意味な長い空白と、JavaScript 実行前の簡素なテキストしか手に入りませんでした。ここもまあハマりポイントで、最初に iOS ショートカットに挑戦した時は突破出来ずに諦めたポイントでもあります。
最終的に分かったのは、「URLの内容を取得」アクションで得られる結果はテキストではなく「リッチテキスト」というオブジェクトで、そこから元の HTML を取得するためには「リッチテキストからHTMLを作成」アクションでソースに戻さなければならないという事です。酷い初見殺しですね!これ、日本語だと真っ当な情報が見つかりません。(2019-06-05 Google 調べ)
HTML のスクレイピング
HTML に限らず何らかのテキストから情報を抜き出したい時に使えるのが「マッチテキスト」アクションです。正規表現がマッチしたテキストを取り出すことが出来ます。初めて素直に予想通りの動きをしてくれるアクションの解説が出来ました。
今回使用した正規表現は、https://www\.google\.com/maps/preview/place/[^/]+/[^/]+/
です。Google Maps の JavaScript を実行していない素の HTML の中で、緯度経度を確実に含む文字列として使えそうな唯一のテキストがこれでした。
スクレイピングの根本的な問題ですが、Google 側が仕様変更を行った場合に、この正規表現がマッチしなくなってショートカット自体が使えなくなる可能性があります。ですので、この記事をストックなりしておいてもらえれば、将来的にショートカットをアップデートした際に通知を送るようにします。
[2019-07-28 追記] ここで解説している正規表現で通常のケースは問題無いのですが、マイマップにピンを落として保存した場所、つまり緯度経度の情報だけでランドマーク情報が無い URL の場合、これでは上手くいかない事が分かったので、ショートカットのバージョンアップをしました。内容的には上記の正規表現で何もマッチしなかった場合に、別の正規表現 https://maps\.google\.com/maps/api/staticmap\?center=[^%]+%2C[^&]+
を試すような内容になっています。この正規表現で取得出来る緯度経度は、ランドマーク情報がある場合はランドマークと微妙にずれた地点を表すくせに、ランドマーク情報がない場合は正確に地図の中央になるという何とも使いにくい値です。
緯度の取得
スクレイピングの結果、例えば https://www.google.com/maps/preview/place/%E3%80%92<中略>%E9%A7%85/@35.6812362,139.7671248,3241a,13.1y/
のようなテキストが手に入ります。この中の 35.6812362
が緯度に相当するので、ゴリゴリと文字列を編集して取り出していきます。@
で分割して「最後の項目」を取得 → 35.6812362,139.7671248,3241a,13.1y/
、,
で分割して「項目のインデックス」が「1」の項目を取得 → 35.6812362
という手順です。最終的に取得できた 35.6812362
は後で利用するために「変数を設定」アクションで変数 lat
に入れておきます。
プログラミング経験のある人なら二箇所ほど「あれ?」と思うポイントがあったでしょうか。そうですね、「最後の項目」に対応する「最初の項目」は実際にあります。でもここでそれを使わずにあえてインデックスでアクセスしたのは、後述の経度では必ずインデックスが必要になるので対称性を持たせた方が気持ちいいというだけの理由です。
そして二つ目の「あれ?」ですが、そうなんです。iOS ショートカットにおいてリストオブジェクトにアクセスするインデックスは 1 オリジンです。はい再び出ましたね、ハマりポイントです。
経度の取得
ほとんど緯度の取得と同じですが、初期状態が異なります。まず最初に「変数を取得」し、変数として以前のアクションの結果「テキストを分割」を選ぶことで、いったん 35.6812362
になった状態を ["35.6812362", "139.7671248", "3241a,13.1y/"]
に戻します。そして 2 つめの項目を取得して long
に代入。ここはシンプルですね。
Yahoo! カーナビを起動する URL スキームの生成
さあいよいよ最後のステップです。Yahoo! 地図のブログ記事を参考に、Yahoo! カーナビを起動する URL スキームをここまでで取得した緯度と経度を元に作ります。変数 lat
と long
がすでに定義されているので、そこから yjcarnavi://navi/select?lat={lat}&lon={long}
のような URL を生成し、「URLを開く」アクションに渡すと、この URL スキームが発動してめでたく Yahoo! カーナビ上で経路検索することが出来ます。
ショートカットの改造
さてこのショートカット、一番最後の URL スキームの部分だけ改造すれば、緯度や経度を受け取ることが出来る任意のアプリを起動することが出来ます。NAVITIME やマピオンなど他の地図アプリが URL スキーマに対応しているかは調べていないので分かりませんが、対応していれば改造は簡単です。ぜひ自分のお気に入りアプリが URL スキーマに対応しているか調べてみて、ショートカットの改造も楽しんで下さい。
何か面白いショートカットが出来たら、コメントで教えてもらえると嬉しいです。ショートカットの公開方法については、iOS 12 のショートカットアプリ入門あるいは「はてブを開く」を作った話の最後の方で触れているので、参考になればと思います。
では、Google Maps の経路案内を回避して、安心安全なドライブを!