ジオロケーション・トラッキングでUber/Lyftアプリを作ろう
Published: 2024-02-22 07:36:19

**アーカイブからのこの投稿では、車両の位置を追跡するためにPubNubを使用して、独自のUberまたはLyftクローンを構築する方法を説明します。 コード・スニペットのいくつかは古いバージョンのライブラリを参照しているかもしれませんが、この記事にあるものはすべて今日の読者にとって適切なものです。**  
  

他のデバイスのリアルタイムの位置を追跡することは、特にLyftやUberのような人気のあるモバイルアプリの基本です。

LyftやUberのように。この機能を構築するのは難しく、面倒なものだが、PubNubを利用した運転手と乗客の間の簡単なパブリッシュ／サブスクライブ・モデルで簡素化しよう。

チュートリアル概要
---------

**このチュートリアルの[全ソース](https://github.com/PubNubDevelopers/RealtimeTaxiAndroidDemo)コードはこちらから入手できます。**

このチュートリアルでは、この一般的なユースケースをAndroidアプリケーションで実装する方法を紹介します。今回作成するサンプルアプリでは、まずユーザーに運転手か同乗者かを尋ねます。ユーザーがドライバーの場合、現在地はチャンネルに公開され、5秒ごと（このサンプルでは）、またはアプリが必要とする回数ごとに更新されます。

ユーザーが乗客の場合は、同じチャンネルにサブスクライブして、ドライバーの現在位置の更新を受け取ります。このパブリッシュ/サブスクライブモデルは、下のビジュアルに表示されています。

[![Uber/Lyft App](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1j4o/69ed800cea3e73d95979e9cad4cbfb3b/Screen-Shot-2018-06-06-at-11.58.58-AM.png "Uber/Lyft App")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1j4o/69ed800cea3e73d95979e9cad4cbfb3b/Screen-Shot-2018-06-06-at-11.58.58-AM.png)

Google Maps APIとPubNubのセットアップ
-----------------------------

まず、地図機能のために[PubNub Android SDKと](https://pubnub.com/docs/sdks/java/android/)Google Play Servicesを使用するために、Gradleの依存関係を記述する必要があります。また、PubNubメッセージの基本的なJSON解析を完了するために、

```js
com.fasterxml.jackson
```

ライブラリを 実装します。

dependenciesの下にあるアプリモジュールのbuild.gradleファイルに、以下のコマンドのcodeblockを入力し、必要なものがあることを確認する。メニューのツール部分にあるAndroid SDK ManagerからGoogle Play Servicesがインストールされていることを確認し、必要なライブラリを実装する。

```js
implementation group: 'com.pubnub', name: 'pubnub-gson', version: '4.12.0'
implementation 'com.google.android.gms:play-services-maps:15.0.1'
implementation "com.google.android.gms:play-services-location:15.0.1"
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.9.2'
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.9.2'
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.2'
```

必要な依存関係が揃ったので、Android Manifestファイルに必要なパーミッションがあることを確認しよう。ネットワーク状態へのアクセスとインターネット接続の確立のパーミッションがあれば、ユーザーはPubNub APIへの接続を行うことができる。 位置情報パーミッションは後でドライバーの位置情報を要求するときに使用する。

```js
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
```

AndroidアプリでPubNubを設定するには、[PubNub Admin Dashboardで](https://dashboard.pubnub.com/signup/)PubNubアプリを作成する必要があります **（無料です）**。 アプリを作成すると、購読キーと公開キーが割り当てられます。 次のパートでは、これらの認証情報をMainActivityクラスで使用して接続を確立します。

次に、Google Maps APIキーを設定し、アプリケーションで使用できるようにします。これを行うには、[Google の Developer API コンソールに](https://console.cloud.google.com/projectselector2/apis/dashboard?supportedpurview=project)アクセスします。ここで新しいプロジェクトを作成し、APIキーを生成します。APIキーが手に入ったので、次のコードを書いてGoogle Maps APIを設定し、生成したAPIキーで設定します。

```js
<meta-data
   android:name="com.google.android.gms.version"
   android:value="@integer/google_play_services_version" />
<meta-data
   android:name="com.google.android.geo.API_KEY"
   android:value=”ENTER_KEY_HERE" />
```

メインアクティビティ
----------

MainActivityクラスには3つの重要な機能があります：

1.  運転手か乗客かに応じて、ユーザーをそれぞれのインターフェースに誘導する。
2.  アプリのすべてのユーザーに対してPubNubへの接続を確立する。
3.  位置情報へのアクセス許可のチェック

まず、ユーザーをドライバーと乗客に分け、それぞれのユーザーインターフェースを提供します。これはDriverActivityクラスとPassengerActivityクラスを作成することで行います。詳細は第2回と第3回で説明します。

MainActivityでは、ユーザーに運転手または乗客としての役割をアプリに知らせるよう促す、シンプルなボタンベースのインターフェースを作成します。AndroidのIntentクラスを使って、どのボタンが押されたかに応じて、MainActivityクラスからDriverActivityクラスへ、またはMainActivityクラスからPassengerActivityクラスへ遷移することができます。

インターフェイスは次のように表示されます：

[![screenshot](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1j4p/89ff1d0cceea0f257e1b66aacc1fa7a6/screenshot.png "screenshot")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1j4p/89ff1d0cceea0f257e1b66aacc1fa7a6/screenshot.png)

```js
driverButton.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
       startActivity(new Intent(MainActivity.this, DriverActivity.class));
   }
});
passengerButton.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
       startActivity(new Intent(MainActivity.this, PassengerActivity.class));
   }
});
```

次に、[Admin Dashboardから](https://dashboard.pubnub.com/signup)PubNubの認証情報を使用して、

```js
PNConfiguration
```

インスタンスを 作成 します。これにより 、アプリとPubNubを接続する

```js
PubNub
```

インスタンスを 作成 できます。MainActivityクラスのこのPubNubインスタンスは、DriverActivityとPassengerActivityの両方からアクセスできるようにpublicかつstaticに します。アプリのすべてのユーザーに対してPubNubが適切に初期化されるように、Main Activityの

```js
onCreate()
```

メソッドで以下のメソッドを呼び出します 。これはすべて次のコードに示されています。

```js
private void initPubnub() {
        PNConfiguration pnConfiguration = new PNConfiguration();
        pnConfiguration.setSubscribeKey(Constants.PUBNUB_SUBSCRIBE_KEY);
        pnConfiguration.setPublishKey(Constants.PUBNUB_PUBLISH_KEY);
        pnConfiguration.setSecure(true);
        pubnub = new PubNub(pnConfiguration);
    }
```

最後に、ユーザーがアプリの位置情報サービスを有効にしていることを確認します。これは次のコードで実行できます。もしユーザーが位置情報へのアクセスを許可していない場合、アプリはこの許可を要求します。

```js
public void checkPermission() {
   if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED ||
           ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED
           ) {//Can add more as per requirement
       ActivityCompat.requestPermissions(this,
               new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION},
               123);
   }
}
```

ドライバーのアクティビティ
-------------

DriverActivityクラスの唯一の機能は、ドライバーの位置を取得し、一定の時間間隔でこのデータをPubNubチャネルに公開することです。

まず、位置情報のリクエストを行う方法を見てみましょう。 ドライバーに位置情報の更新を要求するために、Google Location APIの

```js
FusedLocationProviderClient
```

。また、

```js
LocationRequest
```

クラスを使用して、優先度、最小変位、時間間隔など、位置情報リクエストの重要なパラメータを指定 します。

このサンプルでは、時間間隔を5000ミリ秒にして、5秒ごとに位置情報を更新するようにし、最小変位を10メートルにして、少なくとも10メートルの変化があった場合にのみ位置情報を更新するようにする。最後に、高精度の位置追跡のために、モバイル・デバイスのGPSを使用することを希望する。この高精度は、乗客が目的地まで地図を横切って移動するドライバー（車のアイコンで表示される）の詳細な表示を見ることを可能にするために重要である。

```js
LocationRequest locationRequest = LocationRequest.create();
locationRequest.setInterval(5000);
locationRequest.setFastestInterval(5000);
locationRequest.setSmallestDisplacement(10);
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
```

位置情報リクエストのパラメータを定義したので、この LocationRequest オブジェクトを FusedLocationProviderClient オブジェクトの位置情報更新リクエストに渡します。

```js
mFusedLocationClient.requestLocationUpdates(locationRequest, new LocationCallback() {
   @Override
   public void onLocationResult(LocationResult locationResult) {
       Location location = locationResult.getLastLocation();
...
```

リクエストから得た位置情報をLinkedHashMapに変換し、PubNubチャネルの位置情報メッセージのフォーマットに合うようにします。LinkedHashMapには2つのキー "lat "と "lng "と、これらのキーに対応する値があり、どちらもStringになります。

これで、LinkedHashMap形式のメッセージをPubNubチャンネルに公開することができるようになりました。これを次のコードに示す。メインアクティビティのonCreateメソッドでPubNubインスタンスを取得するために

```js
MainActivity.pubnub
```

。PubNubを再度初期化して複数の接続を確立することは避けたい。代わりに、MainActivityクラスですでに作成したものを使用します。

```js
MainActivity.pubnub.publish()
       .message(message)
       .channel(Constants.PUBNUB_CHANNEL_NAME)
       .async(new PNCallback<PNPublishResult>() {
           @Override
           public void onResponse(PNPublishResult result, PNStatus status) {
               // handle publish result, status always present, result if successful
               // status.isError() to see if error happened
               if (!status.isError()) {
                   System.out.println("pub timetoken: " + result.getTimetoken());
               }
               System.out.println("pub status code: " + status.getStatusCode());
           }
       });
```

パッセンジャー・アクティビティ
---------------

PassengerActivityクラスで、更新中のドライバーの位置を表示するMapViewを作成します。Passenger ActivityにMapView UI要素を追加するには、このアクティビティに対応するlayout.xmlファイルに次のコードを含める必要があります。

```js
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/map"
   android:name="com.google.android.gms.maps.SupportMapFragment"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context="com.example.mapwithmarker.MapsMarkerActivity" />
```

次に、レイアウトファイルで定義したIDを使用して 、

```js
MapFragment
```

オブジェクトを インスタンス化 します。サンプルコードでは、IDとして「map」を使用しています。また 、PassengerActivityに

```js
onMapReadyCallback
```

を実装して、

```js
onMapReady
```

メソッドを設定します。そして、

```js
PassengerActivity.this
```

をMapFragmentオブジェクトの

```js
getMapAsync
```

メソッドに パラメータとして 渡します 。これで、MapFragmentオブジェクトのonMapReadyコールバックメソッドをセットアップすることができる。

```js
mMapFragment = (SupportMapFragment) getSupportFragmentManager()
       .findFragmentById(R.id.map);
mMapFragment.getMapAsync(PassengerActivity.this);
```

さて、ドライバーの現在位置の更新を受け取るために、ドライバーの位置チャンネルを購読したい。マップの準備ができたら、このコードをコールバックメソッドに含める。さらに、

```js
SubscribeCallback
```

を持つリスナーを追加して、メッセージを受信したときにアプリがどのように動作するかを知って おく必要がある。

PubNubのAndroidドキュメントに記載されているように、まずリスナーを追加し、次にPubNubインスタンスをチャネルにサブスクライブするという順序で実行する必要があります。メッセージを受信したら、JSON出力をLinkedHashMapに変換し、簡単にパースして経度と緯度を取得できるようにしたい。JSON出力をLinkedHashMap に変換す るには、

```js
JsonUtil
```

クラスをインポー� �する必要がある。 クラスには 、これを可能に するメソッド

```js
fromJson()
```

が含まれて いる。このクラスは全ソースコードに示されている。ドライバーの位置がわかったら、メソッド

```js
updateUI()
```

を呼び出し、新しい位置をパラメータとして渡す ことで、UIを更新しなければならない 。

```js
MainActivity.pubnub.addListener(new SubscribeCallback() {
    @Override
    public void status(PubNub pub, PNStatus status) {
    }
    @Override
    public void message(PubNub pub, final PNMessageResult message) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                try {
                    Map<String, String> newLocation = JsonUtil.fromJson(message.getMessage().toString(), LinkedHashMap.class);
                    updateUI(newLocation);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }
    @Override
    public void presence(PubNub pub, PNPresenceEventResult presence) {
    }
});
MainActivity.pubnub.subscribe()
        .channels(Arrays.asList(Constants.PUBNUB_CHANNEL_NAME)) // subscribe to channels
        .execute();
```

updateUIメソッドには2つのケースがあります。

最初のケースは、ドライバーのマーカーがまだ存在しない場合です。この場合、

```js
Marker
```

オブジェクトを インスタンス化 し、その位置をドライバーの最初に追跡された位置に設定 します。また、ドライバーの車のストリートビューを維持するために、地図を拡大しなければならない。

つ目のケースは、すでにドライバーのマーカーが存在する場合です。この場合、Markerオブジェクトをインスタンス化し直す必要はありません。この場合、Markerオブジェクトを再度インスタンス化する必要はなく、単純にマップ上のマーカーの位置を新しい場所に移動するだけです。これは

```js
animateCar()
```

メソッドを呼び出すことで行います。また、MapViewのカメラが新しい場所に移動することを確認する必要があります。従って、ドライバーのマーカーは常にMapViewの範囲内にあることになります。これを次のコードに示す。

```js
private void updateUI(Map<String, String> newLoc) {
        LatLng newLocation = new LatLng(Double.valueOf(newLoc.get("lat")), Double.valueOf(newLoc.get("lng")));
        if (driverMarker != null) {
            animateCar(newLocation);
            boolean contains = mGoogleMap.getProjection()
                    .getVisibleRegion()
                    .latLngBounds
                    .contains(newLocation);
            if (!contains) {
                mGoogleMap.moveCamera(CameraUpdateFactory.newLatLng(newLocation));
            }
        } else {
            mGoogleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(
                    newLocation, 15.5f));
            driverMarker = mGoogleMap.addMarker(new MarkerOptions().position(newLocation).
                    icon(BitmapDescriptorFactory.fromResource(R.drawable.car)));
        }
    }
```

animateCarメソッドでは、車のマーカーを滑らかな線で新しい場所に移動させます。このアニメーションは5秒間続けなければなりません。なぜなら、このアプリは5秒ごとにドライバーの位置の更新を受け取るからです。こうすることで、アニメーションが終わるたびに、次のアニメーションを開始するための新しい位置情報が更新される。そうすることで、車のアニメーションのタイムラグを最小限に抑えることができる。

また

```js
LatLngInterpolator
```

インターフェースも利用する。これにより

```js
interpolate()
```

メソッドを実装することができる。この端数はメソッド

```js
getAnimatedFraction()
```

.インスタンスからこのメソッドを呼び出します。

```js
ValueAnimator
```

インスタンスからこのメソッドを呼び出します。

```js
onAnimationUpdate()
```

コールバックメソッドから得られるインスタンスからこのメソッドを呼び出します。この分数は一定の割合で増加し、5秒後には1になる（新しい場所までずっと）。この車のアニメーションは、以下のコードに示されている。

```js
private void animateCar(final LatLng destination) {
        final LatLng startPosition = driverMarker.getPosition();
        final LatLng endPosition = new LatLng(destination.latitude, destination.longitude);
        final LatLngInterpolator latLngInterpolator = new LatLngInterpolator.LinearFixed();
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
        valueAnimator.setDuration(5000); // duration 5 seconds
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                try {
                    float v = animation.getAnimatedFraction();
                    LatLng newPosition = latLngInterpolator.interpolate(v, startPosition, endPosition);
                    driverMarker.setPosition(newPosition);
                } catch (Exception ex) {
                }
            }
        });
        valueAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
            }
        });
        valueAnimator.start();
    }
```

```js
private interface LatLngInterpolator {
       LatLng interpolate(float fraction, LatLng a, LatLng b);
       class LinearFixed implements LatLngInterpolator {
           @Override
           public LatLng interpolate(float fraction, LatLng a, LatLng b) {
               double lat = (b.latitude - a.latitude) * fraction + a.latitude;
               double lngDelta = b.longitude - a.longitude;
               if (Math.abs(lngDelta) > 180) {
                   lngDelta -= Math.signum(lngDelta) * 360;
               }
               double lng = lngDelta * fraction + a.longitude;
               return new LatLng(lat, lng);
           }
       }
   }
```

さらなる改良
------

このサンプルをさらに発展させたいのであれば、1つの機能として、車が曲がるときにマーカーも曲がるようにするとよいでしょう。この機能を追加するには、メッセージ構造にキーと値のペアを1つ追加する必要があります。"bearing "というキーと値を入れる必要があります。そうすれば、ドライバーの方位によってマーカーを回転させるだけだ。

おめでとう！
------

これで、MainActivity、DriverActivity、PassengerActivityを作成し、シンプルなLyft/Uber Androidデモを作成するのに必要なPublish/Subscribeモデルを構築することに成功しました。[完全なソース](https://github.com/PubNubDevelopers/RealtimeTaxiAndroidDemo)コードはこちらをクリックしてください。

  
**埋め込みコンテンツが**このページで**利用**できない場合は、[https://www.youtube.com/embed/kQ6EzqoQu5A](https://www.youtube.com/embed/kQ6EzqoQu5A)からもご覧いただけます。

その他のリソース
--------

-   [あなたのアプリケーションにGeolocation APIを追加すべきですか？](https://www.pubnub.com/guides/should-you-add-a-geolocation-api-to-your-app/)
    
-   [Geolocation デモ](https://www.pubnub.com/demos/geolocation-demo/)
    
-   [ジオロケーション開発者パス](https://www.pubnub.com/developers/geolocation-developer-path/)
    

PubNubはどのようにあなたを助けることができますか？
============================

この記事は[PubNub.comに](https://www.pubnub.com/blog/build-your-own-uber-lyft-app-geolocation-tracking-android/)掲載されたものです。

PubNubのプラットフォームは、開発者がWebアプリ、モバイルアプリ、IoTデバイス向けにリアルタイムのインタラクティブ機能を構築、提供、管理できるように支援します。

私たちのプラットフォームの基盤は、業界最大かつ最もスケーラブルなリアルタイムエッジメッセージングネットワークです。世界15か所以上で8億人の月間アクティブユーザーをサポートし、99.999%の信頼性を誇るため、停電や同時実行数の制限、トラフィックの急増による遅延の問題を心配する必要はありません。

PubNubを体験
---------

[ライブツアーを](https://www.pubnub.com/tour/introduction/)チェックして、5分以内にすべてのPubNub搭載アプリの背後にある本質的な概念を理解する

セットアップ
------

[PubNubアカウントに](https://admin.pubnub.com/signup/)サインアップすると、PubNubキーに無料ですぐにアクセスできます。

始める
---

[PubNubのドキュメントは](https://www.pubnub.com/docs)、ユースケースや[SDKに](https://www.pubnub.com/docs)関係なく、あなたを立ち上げ、実行することができます。