この記事は Frank van Diggelen による Android Developers Blog の記事 "Improving urban GPS accuracy for your app" を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
Android では、デベロッパーの皆さんがユーザーにとって、便利なアプリをできる限り簡単に作成できるようにしたいと考えています。そのため、Fused Location Provider API(FLP)などの API では、位置情報を最大限に活用できるようにすることを目指しています。しかし、密集した都市部では、通りの反対側や誤った区画を指すなど、その位置情報の精度が低いというご指摘をこれまで多くの方からいただいておりました。
ライドシェアやナビゲーションなど、特によく利用されている位置情報アプリでは、この点が非常に重要になります。たとえば、街でユーザーがライドシェアをリクエストしても、GPS エラーのためアプリが簡単に位置を特定できない場合があります。
誤って通りの反対側を指してしまうエラーが起こる主な原因は、都市では GPS シグナルが反射しやすいためです。この大きな GPS 問題の解決に役立てるため、私たちは新しいプロジェクトを立ち上げました。そのソリューションは、3D マッピングを活用して補正を行うという方法です。これには、建物の 3D モデル、未加工の GPS 測定データ、機械学習を組み合わせる必要があるため、Google のような企業の規模でしか実現できません。
12 月の Pixel Feature Drop では、Pixel 5 と Pixel 4a5G に 3D マッピングを活用した GPS 補正が追加されます。Pixel で使われている Qualcomm® Snapdragon™ 5G Mobile Platform にフィードバックを提供するシステム API により、都市部(ビルの谷間、いわゆる「アーバン キャニオン」)での精度が大幅に向上します。
この写真から、3D マッピングを活用した補正を行わない場合、GPS の結果が不安定になって通りの反対側(または誤った区画)を指す頻度が高くなることがわかります。一方、3D マッピングを活用した補正を行うと、位置は何倍も正確になります。
都市部では GPS が構造的に誤った場所を指す点が今までの問題でした。その問題は、すべての GPS システムが衛星からの見通し線に基づいて位置を特定しているために引き起こされています。しかし大都市では、直接の信号は建物によって妨害されるため、ほぼすべての信号が障害物に反射した後に届くことになります。
GPS チップは信号が見通し線上にあることを仮定しているので、信号が余分な経路を通過してきた分だけ、計算に誤差が生じます。最も一般的な副作用は、位置が通りの反対側に表示されることですが、特に多くの高層ビルが建っている大都市では、誤った区画に表示されることもあります。
この問題は、10 年以上にわたって解決が試みられてきました。しかし、Android に 3D マッピングを活用した補正が搭載されるまでは、大規模な解決策は存在しませんでした。
Google Play サービスの 3D マッピングを活用した補正モジュールには、Google が保持している世界中の 3,850 以上の都市の 3D 建造物モデルのタイルが含まれています。現在、Google Play サービスの 3D マッピングを活用した補正は、歩行者による利用のみをサポートしています。歩きながらデバイスの GPS を使うと、Android の Activity Recognition API が今歩行していることを認識します。さらに、3,850 以上の都市のいずれかにいる場合、その都市の 3D モデルのタイルがダウンロードされ、スマートフォンにキャッシュされます。キャッシュのサイズは約 20 MB で、写真 6 枚程度の大きさです。
このモジュールでは、3D マッピングを活用した補正アルゴリズムが難問を解決します。つまり、GPS の位置が正しくない場合、どうすれば信号を妨害したり反射したりしている建物を知ることができるかという、卵が先か鶏が先かわからないような問題です。この問題は、3D マッピングを活用した補正によって解決され、FLP には一連の補正済みの位置が渡されます。システム API はこの情報を GPS チップに渡し、チップがそれ以降の GPS 精度を改善できるようにします。
12 月の Pixel Feature Drop では、Pixel 5 と Pixel 4a(5G) を対象に、3D マッピングを活用した補正のバージョン 2 をリリースします。これにより、通りの反対側を指すという現象の発生率が約 75% 減少します。Android 8 以降を使っているその他の Android スマートフォンでは、FLP にバージョン 1 が実装されており、通りの反対側を指すという現象の発生率が約 50% 減少します。2021 年初頭には、Android エコシステム全体(Android 8 以降)でバージョン 2 を利用できるようになる予定です。
Android の 3D マッピングを活用した補正は、米国の Global Positioning System(GPS)に加え、その他の Global Navigation Satellite System(GNSS)の信号にも対応しています。具体的には、GLONASS、Galileo、BeiDou、QZSS の信号です。
GPS チップ パートナーは、それぞれのテクノロジーにおけるこの作業の重要性について、次のように述べています。
「ユーザーは、モバイル スマートフォンの位置情報やナビゲーション機能の精度に依存しています。位置情報テクノロジーは、お気に入りのレストランを見つけたり、タイミングよくライドシェア サービスを利用したりする際に非常に重要ですQualcomm Technologies は、Google の 3D マッピングを活用した補正を組み込んだ最新のQualcomm® Location Suite テクノロジーによるユーザー エクスペリエンスの改善で、主導的な役割を果たしています。今回の Google との共同作業は、歩道レベルの精度の位置情報実現に向けた重要なマイルストーンです」
―― Qualcomm Technologies, Inc. プロダクト管理担当副社長、Francesco Grilli 氏
「Broadcom は、BCM47765 二周波 GNSS チップのナビゲーション エンジンに Google の 3D マッピングを活用した補正を組み込みました。二周波の L1 および L5 信号と 3D マッピングを活用した補正を組み合わせることで、アーバン キャニオンでこれまでにない精度を実現できます。L5 と Google の補正を組み合わせれば、都市部での GNSS の活用に革命を起こすことができます」
――Broadcom Inc. エンジニアリング担当シニア ディレクター、Charles Abraham 氏
「Google の 3D マッピングを活用した補正によって、スマートフォン ユーザーが都市環境で歩行しているときの個人の位置情報の精度が大きく進展しました。MediaTek Dimensity 5G ファミリーは、3D マッピングを活用した補正に対応しています。これにより、高精度デュアルバンド GNSS や業界トップレベルのデッドレコニング パフォーマンスに加えて、最高精度のグローバル位置情報を 5G スマートフォン ユーザーに提供できます」
―― MediaTek
Android 8 以降を実行しているスマートフォンでは、3,850 以上の都市で GPS をオンにして歩行しているときに、Android の 3D マッピングを活用した補正が自動的に作動します。デベロッパーがこの改善を活用する最適な方法は、FLP を使って位置情報を取得することです。GPS チップによるさらに高度な 3D マッピングを活用した補正は、現在のところ、Pixel 5 と Pixel 4a 5G で利用できます。また、今後数週間のうちに、Android エコシステム全体(Android 8 以降)に対してロールアウトされる予定です。今後、運転中などの他のモードもサポートする予定です。
Android の 3D マッピングを活用した補正は、以下を含む 3,850 以上の都市で利用できます。
Google Earth 3D モデルの拡張とともに、3D マッピングを活用した補正の範囲も広がります。
Google マップにもアップデートが行われます。これにより、一部の都市で、歩道、横断歩道、安全地帯などの歩行者向けの街区レベルの情報の精度が向上します。2021 年には、Google Maps Platform を使っているアプリにもこのアップデートが提供されます。3D マッピングを活用した補正による位置情報の精度向上と合わせて、皆さんのようなデベロッパーが、世界中で Android を使っている 20 億人の歩行者のユースケースのサポートを向上させてくださることを期待しています。
私たちは 3D マッピングを活用した補正の他にも、位置情報の精度と利便性を向上させる努力を懸命に続けています。Fused Location Provider API(FLP)の最新の改善項目は、以下のとおりです。
getCurrentLocation
FusedLocationProviderClient
Qualcomm および Snapdragon は、Qualcomm Incorporated の商標または登録商標です。Qualcomm Snapdragon および Qualcomm Location Suite は、Qualcomm Technologies, Inc. および/またはその子会社の製品です。
compileSdkVersion
targetSdkVersion
JobScheduler
Service
JobService
IntentService
ChannelProgramsJobService
ChannelLoggerJobService
ChannelMetadataJobService
ChannelDeletionJobService
Channel
channelId
jobId
Application-provided id for this job. Subsequent calls to cancel, or jobs created with the same jobId, will update the pre-existing job with the same id. This ID must be unique across all clients of the same uid (not just the same package). You will want to make sure this is a stable id across app updates, so probably not based on a resource ID.
jobId= "ChannelPrograms" + channelId
jobId= "ChannelLogs" + channelId
JobIdManager
public class JobIdManager { public static final int JOB_TYPE_CHANNEL_PROGRAMS = 1; public static final int JOB_TYPE_CHANNEL_METADATA = 2; public static final int JOB_TYPE_CHANNEL_DELETION = 3; public static final int JOB_TYPE_CHANNEL_LOGGER = 4; public static final int JOB_TYPE_USER_PREFS = 11; public static final int JOB_TYPE_USER_BEHAVIOR = 21; @IntDef(value = { JOB_TYPE_CHANNEL_PROGRAMS, JOB_TYPE_CHANNEL_METADATA, JOB_TYPE_CHANNEL_DELETION, JOB_TYPE_CHANNEL_LOGGER, JOB_TYPE_USER_PREFS, JOB_TYPE_USER_BEHAVIOR }) @Retention(RetentionPolicy.SOURCE) public @interface JobType { } //16-1 for short. Adjust per your needs private static final int JOB_TYPE_SHIFTS = 15; public static int getJobId(@JobType int jobType, int objectId) { if ( 0 < objectId && objectId < (1<< JOB_TYPE_SHIFTS) ) { return (jobType << JOB_TYPE_SHIFTS) + objectId; } else { String err = String.format("objectId %s must be between %s and %s", objectId,0,(1<<JOB_TYPE_SHIFTS)); throw new IllegalArgumentException(err); } } }
User
Cat
Dog
JOB_TYPE
-JobService
JOB_TYPE_
JobIdManager.getJobId(...)
import android.app.job.JobInfo; import android.app.job.JobParameters; import android.app.job.JobService; import android.content.ComponentName; import android.content.Context; import android.os.PersistableBundle; public class ChannelProgramsJobService extends JobService { private static final String CHANNEL_ID = "channelId"; . . . public static void schedulePeriodicJob(Context context, final int channelId, String channelName, long intervalMillis, long flexMillis) { JobInfo.Builder builder = scheduleJob(context, channelId); builder.setPeriodic(intervalMillis, flexMillis); JobScheduler scheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); if (JobScheduler.RESULT_SUCCESS != scheduler.schedule(builder.build())) { //todo what? log to server as analytics maybe? Log.d(TAG, "could not schedule program updates for channel " + channelName); } } private static JobInfo.Builder scheduleJob(Context context,final int channelId){ ComponentName componentName = new ComponentName(context, ChannelProgramsJobService.class); final int jobId = JobIdManager .getJobId(JobIdManager.JOB_TYPE_CHANNEL_PROGRAMS, channelId); PersistableBundle bundle = new PersistableBundle(); bundle.putInt(CHANNEL_ID, channelId); JobInfo.Builder builder = new JobInfo.Builder(jobId, componentName); builder.setPersisted(true); builder.setExtras(bundle); builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); return builder; } ... }
03-09 16:39:32.122 15107 15107 I crash_dump32: performing dump of process 14942 (target tid = 14971) 03-09 16:39:32.127 15107 15107 F DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 03-09 16:39:32.127 15107 15107 F DEBUG : Build fingerprint: 'google/sailfish/sailfish:O/OPP1.170223.013/3795621:userdebug/dev-keys' 03-09 16:39:32.127 15107 15107 F DEBUG : Revision: '0' 03-09 16:39:32.127 15107 15107 F DEBUG : ABI: 'arm' 03-09 16:39:32.127 15107 15107 F DEBUG : pid: 14942, tid: 14971, name: WorkHandler >>> com.redacted <<< 03-09 16:39:32.127 15107 15107 F DEBUG : signal 31 (SIGSYS), code 1 (SYS_SECCOMP), fault addr -------- 03-09 16:39:32.127 15107 15107 F DEBUG : Cause: seccomp prevented call to disallowed system call 55 03-09 16:39:32.127 15107 15107 F DEBUG : r0 00000091 r1 00000007 r2 ccd8c008 r3 00000001 03-09 16:39:32.127 15107 15107 F DEBUG : r4 00000000 r5 00000000 r6 00000000 r7 00000037
adb shell setenforce 0 && adb stop && adb start
//bionic/libc/seccomp
//cts/tests/tests/security/jni/android_security_cts_SeccompTest.cpp
add_key
keyctl
openat