現在の場所を選択して地図上に詳細を表示する

このチュートリアルでは、Android デバイスの現在地を特定して、その場所にあるお店やサービスなどのスポットに関する詳細情報を表示する方法を紹介します。Maps SDK for AndroidPlaces SDK for Android、および Google Play 開発者サービスの Location API に含まれる融合された位置予測プロバイダを使用して Android アプリを作成するには、このチュートリアルを参考にしてください。

コードを取得する

GitHub から、Google Maps Android API v2 サンプル リポジトリをダウンロードするかクローンを作成します。

開発プロジェクトを設定する

次のステップに従って、Android Studio でチュートリアル プロジェクトを作成します。

  1. Android Studio をダウンロードしてインストールします。
  2. Android Studio に Google Play 開発者サービス パッケージを追加します。
  3. Google Maps Android API v2 のサンプル リポジトリをダウンロードするかクローンを作成します(このチュートリアルの閲覧を開始した時点で行っていない場合)。
  4. 次の手順でチュートリアル プロジェクトをインポートします。

    • Android Studio で、[File] > [New] > [Import Project] を選択します。
    • ダウンロードした Google Maps Android API v2 のサンプル リポジトリの保存先に移動します。
    • 次の場所で CurrentPlaceDetailsOnMap プロジェクトを探します。
      PATH-TO-SAVED-REPO/android-samples/tutorials/CurrentPlaceDetailsOnMap
    • プロジェクト ディレクトリを選択して、[OK] をクリックします。Android Studio で、Gradle ビルドツールを使用してプロジェクトが作成されます。

API キーを取得して必要な API を有効にする

このチュートリアルを完了するには、Maps SDK for Android と Places SDK for Android の使用が許可されている Google API キーが必要です。

キーを取得して API を有効にするには、下のボタンをクリックしてください。

使ってみる

詳細については、API キーの取得に関する詳しいガイドをご覧ください。

アプリに API キーを追加する

  1. プロジェクトの gradle.properties ファイルを編集します。
  2. API キーを GOOGLE_MAPS_API_KEY プロパティの値に貼り付けます。

    GOOGLE_MAPS_API_KEY=PASTE-YOUR-API-KEY-HERE

    アプリを作成すると、Gradle で API キーがアプリの Android マニフェストにコピーされます。マニフェストの文字列 google_maps_key を Gradle のプロパティ GOOGLE_MAPS_API_KEY にマッピングするため、アプリの build.gradle ファイルには次の行が含まれています。

    resValue "string", "google_maps_key",
            (project.findProperty("GOOGLE_MAPS_API_KEY") ?: "")
    

アプリを作成して実行する

  1. Android デバイスをコンピュータに接続します。手順に沿って、Android デバイスでデベロッパー オプションを有効にし、デバイスを検出するようにシステムを設定します(または、Android Virtual Device(AVD)Manager を使用して仮想デバイスを設定することもできます。エミュレータを指定する際は、Google API を含むイメージを選択する必要があります。詳しくは、スタートガイドをご覧ください)。
  2. Android Studio で [Run] メニュー オプション(またはプレイボタン アイコン)をクリックします。 表示される指示に沿ってデバイスを選択します。

Android Studio で Gradle が起動してアプリが作成され、そのアプリがデバイスまたはエミュレータ上で実行されます。このページの画像のように、現在地の周囲に多数のマーカーが立った地図が表示されます。

トラブルシューティング:

  • 地図が表示されない場合は、上記の手順どおりに API キーを取得してアプリに追加しているかご確認ください。Android Studio の Android Monitor のログでも、API キーに関するエラー メッセージを確認してください。
  • 地図にシドニー ハーバー ブリッジ(アプリで指定されたデフォルトの場所)を示すマーカーのみが表示されている場合は、位置情報の利用許可をアプリに付与していることを確認してください。アプリでは、Android パーミッションに関するガイドに記載のパターンに沿って、起動時に位置情報の利用許可を要求するメッセージが表示されます。なお、[Settings] > [Apps] > [app name] > [Permissions] > [Location] を選択して、デバイス上でパーミッションを直接設定することもできます。コードでパーミッションを処理する方法について詳しくは、以下のアプリで位置情報の利用許可をリクエストするためのガイドをご覧ください。
  • ログを表示してアプリをデバッグするには、Android Studio デバッグツールを使用します。

コードを理解する

このパートでは、CurrentPlaceDetailsOnMap アプリの最も重要な部分について説明します。同様のアプリを作成する方法について理解を深めていただけます。

Places API クライアントをインスタンス化する

次のインターフェースは、Places SDK for Android への主なエントリ ポイントとなります。

  • GeoDataClient では、ローカル プレイスやビジネス情報に関する Google のデータベースにアクセスできます。
  • PlaceDetectionClient では、デバイスの現在地をすばやく検知して、その場所の位置情報を報告することができます。

LocationServices インターフェースは、Android の位置情報サービスの主なエントリ ポイントです。

API を使用するには、フラグメントまたはアクティビティの onCreate() メソッドに含まれる GeoDataClientPlaceDetectionClientFusedLocationProviderClient を、次のコードサンプルに示すようにインスタンス化します。

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // Construct a GeoDataClient.
    mGeoDataClient = Places.getGeoDataClient(this, null);

    // Construct a PlaceDetectionClient.
    mPlaceDetectionClient = Places.getPlaceDetectionClient(this, null);

    // Construct a FusedLocationProviderClient.
    mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
}

位置情報の利用許可をリクエストする

デバイスの位置を特定して、ユーザーが地図上で [現在地] ボタンをタップできるようにするには、アプリで位置情報の利用許可をリクエストする必要があります。

このチュートリアルでは、精度の高い位置情報の利用許可をリクエストするためのコードを提供します。 詳細については、Android パーミッションに関するガイドをご覧ください。

  1. パーミッションを <manifest> 要素の子要素として Android マニフェストに追加します。

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.currentplacedetailsonmap">
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    </manifest>
    
  2. アプリでランタイム パーミッションをリクエストして、ユーザーが位置情報の利用許可を許可または拒否できるようにします。ユーザーが精度の高い位置情報の利用許可を付与しているかどうかは、次のコードで確認できます。許可していない場合は、パーミッションをリクエストします。

    private void getLocationPermission() {
        /*
         * Request location permission, so that we can get the location of the
         * device. The result of the permission request is handled by a callback,
         * onRequestPermissionsResult.
         */
        if (ContextCompat.checkSelfPermission(this.getApplicationContext(),
                android.Manifest.permission.ACCESS_FINE_LOCATION)
                == PackageManager.PERMISSION_GRANTED) {
            mLocationPermissionGranted = true;
        } else {
            ActivityCompat.requestPermissions(this,
                    new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
                    PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
        }
    }
    
  3. パーミッション リクエストの結果を処理するよう onRequestPermissionsResult() コールバックをオーバーライドします。

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        mLocationPermissionGranted = false;
        switch (requestCode) {
            case PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    mLocationPermissionGranted = true;
                }
            }
        }
        updateLocationUI();
    }
    

    このチュートリアルの後半部分で、updateLocationUI() メソッドを記述します。

地図を追加する

Maps SDK for Android を使用して地図を表示します。

  1. アクティビティのレイアウト ファイル(activity_maps.xml)に <fragment> 要素を追加します。この要素では、地図のコンテナとして機能し、GoogleMap オブジェクトへのアクセス権を付与するよう SupportMapFragment を定義します。このチュートリアルでは、Android フレームワークの以前のバージョンとの下位互換性を確保するため、地図フラグメントの Android サポート ライブラリ バージョンを使用します。

    <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.currentplacedetailsonmap.MapsActivityCurrentPlace" />
    
    
  2. アクティビティの onCreate() メソッドで、レイアウト ファイルをコンテンツ ビューとして設定します。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_maps);
    }
    
  3. OnMapReadyCallback インターフェースを実装し、GoogleMap オブジェクトが使用可能な場合に地図を設定するよう onMapReady() メソッドをオーバーライドします。

    public void onMapReady(GoogleMap map) {
        mMap = map;
    
        // Do other setup activities here too, as described elsewhere in this tutorial.
    
        // Turn on the My Location layer and the related control on the map.
        updateLocationUI();
    
        // Get the current location of the device and set the position of the map.
        getDeviceLocation();
    }
    
  4. アクティビティの onCreate() メソッドで、FragmentManager.findFragmentById() を呼び出して地図フラグメントに対するハンドルを取得します。 次に、getMapAsync() を使用して、地図コールバックを登録します。

    SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
            .findFragmentById(R.id.map);
    mapFragment.getMapAsync(this);
    
  5. 地図上に位置情報コントロールを設定するよう updateLocationUI() メソッドを記述します。ユーザーが位置情報の利用許可を付与している場合は、地図上で現在地レイヤと関連するコントロールを有効にします。許可していない場合は、現在地レイヤとコントロールを無効にして、現在地を null に設定します。

    private void updateLocationUI() {
        if (mMap == null) {
            return;
        }
        try {
            if (mLocationPermissionGranted) {
                mMap.setMyLocationEnabled(true);
                mMap.getUiSettings().setMyLocationButtonEnabled(true);
            } else {
                mMap.setMyLocationEnabled(false);
                mMap.getUiSettings().setMyLocationButtonEnabled(false);
                mLastKnownLocation = null;
                getLocationPermission();
            }
        } catch (SecurityException e)  {
            Log.e("Exception: %s", e.getMessage());
        }
    }
    

Android デバイスの位置情報を取得して地図上の位置を特定する

融合された位置予測プロバイダを使用して、デバイスで最後に記録された位置情報を確認し、その位置情報に基づいて地図上での位置を特定します。このチュートリアルでは、必要なコードが提供されています。デバイスの位置情報の取得について詳しくは、Google Play 開発者サービスの Location API に含まれる融合された位置予測プロバイダに関するガイドをご覧ください。

private void getDeviceLocation() {
    /*
     * Get the best and most recent location of the device, which may be null in rare
     * cases when a location is not available.
     */
    try {
        if (mLocationPermissionGranted) {
            Task locationResult = mFusedLocationProviderClient.getLastLocation();
            locationResult.addOnCompleteListener(this, new OnCompleteListener() {
                @Override
                public void onComplete(@NonNull Task task) {
                    if (task.isSuccessful()) {
                        // Set the map's camera position to the current location of the device.
                        mLastKnownLocation = task.getResult();
                        mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(
                                new LatLng(mLastKnownLocation.getLatitude(),
                                        mLastKnownLocation.getLongitude()), DEFAULT_ZOOM));
                    } else {
                        Log.d(TAG, "Current location is null. Using defaults.");
                        Log.e(TAG, "Exception: %s", task.getException());
                        mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(mDefaultLocation, DEFAULT_ZOOM));
                        mMap.getUiSettings().setMyLocationButtonEnabled(false);
                    }
                }
            });
        }
    } catch(SecurityException e)  {
        Log.e("Exception: %s", e.getMessage());
    }
}

現在の場所を取得する

デバイスの現在地にある場所の候補リストを取得するには、Places SDK for Android を使用します。この場合の場所とは、お店やサービスなどのスポットのことです。

このチュートリアルでは、ユーザーが [場所を取得] ボタンをクリックしたときに現在の場所を取得する方法を示します。ボタンをクリックすると場所の候補リストが表示され、ユーザーが場所を選択すると、その場所の地図上の位置にマーカーが追加されます。このチュートリアルでは、Places SDK for Android を操作するために必要なコードを提供します。詳細については、現在の場所を取得するためのガイドをご覧ください。

  1. オプション メニューのレイアウト ファイル(current_place_menu.xml)を作成して、オプション メニューを設定するよう onCreateOptionsMenu() メソッドをオーバーライドします。コードについては、付属のサンプルアプリをご覧ください。
  2. ユーザーが [場所を取得] オプションをクリックしたときに現在の場所を取得するよう onOptionsItemSelected() メソッドをオーバーライドします。
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.option_get_place) {
            showCurrentPlace();
        }
        return true;
    }
    
  3. デバイスの現在地にある場所の候補リストを取得するための showCurrentPlace() メソッドを作成します。

    private void showCurrentPlace() {
        if (mMap == null) {
            return;
        }
    
        if (mLocationPermissionGranted) {
            // Use fields to define the data types to return.
            List<Place.Field> placeFields = Arrays.asList(Place.Field.NAME, Place.Field.ADDRESS,
                    Place.Field.LAT_LNG);
    
            // Use the builder to create a FindCurrentPlaceRequest.
            FindCurrentPlaceRequest request =
                    FindCurrentPlaceRequest.newInstance(placeFields);
    
            // Get the likely places - that is, the businesses and other points of interest that
            // are the best match for the device's current location.
            @SuppressWarnings("MissingPermission") final
            Task<FindCurrentPlaceResponse> placeResult =
                    mPlacesClient.findCurrentPlace(request);
            placeResult.addOnCompleteListener (new OnCompleteListener<FindCurrentPlaceResponse>() {
                @Override
                public void onComplete(@NonNull Task<FindCurrentPlaceResponse> task) {
                    if (task.isSuccessful() && task.getResult() != null) {
                        FindCurrentPlaceResponse likelyPlaces = task.getResult();
    
                        // Set the count, handling cases where less than 5 entries are returned.
                        int count;
                        if (likelyPlaces.getPlaceLikelihoods().size() < M_MAX_ENTRIES) {
                            count = likelyPlaces.getPlaceLikelihoods().size();
                        } else {
                            count = M_MAX_ENTRIES;
                        }
    
                        int i = 0;
                        mLikelyPlaceNames = new String[count];
                        mLikelyPlaceAddresses = new String[count];
                        mLikelyPlaceAttributions = new List[count];
                        mLikelyPlaceLatLngs = new LatLng[count];
    
                        for (PlaceLikelihood placeLikelihood : likelyPlaces.getPlaceLikelihoods()) {
                            // Build a list of likely places to show the user.
                            mLikelyPlaceNames[i] = placeLikelihood.getPlace().getName();
                            mLikelyPlaceAddresses[i] = placeLikelihood.getPlace().getAddress();
                            mLikelyPlaceAttributions[i] = placeLikelihood.getPlace()
                                    .getAttributions();
                            mLikelyPlaceLatLngs[i] = placeLikelihood.getPlace().getLatLng();
    
                            i++;
                            if (i > (count - 1)) {
                                break;
                            }
                        }
    
                        // Show a dialog offering the user the list of likely places, and add a
                        // marker at the selected place.
                        MapsActivityCurrentPlace.this.openPlacesDialog();
                    }
                    else {
                        Log.e(TAG, "Exception: %s", task.getException());
                    }
                }
            });
        } else {
            // The user has not granted permission.
            Log.i(TAG, "The user did not grant location permission.");
    
            // Add a default marker, because the user hasn't selected a place.
            mMap.addMarker(new MarkerOptions()
                    .title(getString(R.string.default_info_title))
                    .position(mDefaultLocation)
                    .snippet(getString(R.string.default_info_snippet)));
    
            // Prompt the user for permission.
            getLocationPermission();
        }
    }
    
  4. ユーザーにフォームを表示して場所を候補リストから選択できようにするための openPlacesDialog() メソッドを作成します。選択された場所の地図上の位置にマーカーを追加します。マーカーのコンテンツには、場所の名前と住所のほか、API で提供される属性が含められます。

    private void openPlacesDialog() {
        // Ask the user to choose the place where they are now.
        DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                // The "which" argument contains the position of the selected item.
                LatLng markerLatLng = mLikelyPlaceLatLngs[which];
                String markerSnippet = mLikelyPlaceAddresses[which];
                if (mLikelyPlaceAttributions[which] != null) {
                    markerSnippet = markerSnippet + "\n" + mLikelyPlaceAttributions[which];
                }
    
                // Add a marker for the selected place, with an info window
                // showing information about that place.
                mMap.addMarker(new MarkerOptions()
                        .title(mLikelyPlaceNames[which])
                        .position(markerLatLng)
                        .snippet(markerSnippet));
    
                // Position the map's camera at the location of the marker.
                mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(markerLatLng,
                        DEFAULT_ZOOM));
            }
        };
    
        // Display the dialog.
        AlertDialog dialog = new AlertDialog.Builder(this)
                .setTitle(R.string.pick_place)
                .setItems(mLikelyPlaceNames, listener)
                .show();
    }
    
  5. 情報ウィンドウのコンテンツ用のカスタム レイアウトを作成します。カスタム レイアウトを作成すると、情報ウィンドウに複数行のコンテンツを表示することができます。最初に、情報ウィンドウのタイトルのテキストビューとスニペットのテキストビュー(つまり、情報ウィンドウのテキスト コンテンツ)が含まれる XML レイアウト ファイル(custom_info_contents.xml)を追加します。

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layoutDirection="locale"
        android:orientation="vertical">
        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:textColor="#ff000000"
            android:textStyle="bold" />
    
        <TextView
            android:id="@+id/snippet"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#ff7f7f7f" />
    </LinearLayout>
    
    
  6. レイアウトをインフレートして情報ウィンドウのコンテンツを読み込むための InfoWindowAdapter インターフェースを実装します。

    @Override
    public void onMapReady(GoogleMap map) {
        // Do other setup activities here too, as described elsewhere in this tutorial.
        mMap.setInfoWindowAdapter(new GoogleMap.InfoWindowAdapter() {
    
        @Override
        // Return null here, so that getInfoContents() is called next.
        public View getInfoWindow(Marker arg0) {
            return null;
        }
    
        @Override
        public View getInfoContents(Marker marker) {
            // Inflate the layouts for the info window, title and snippet.
            View infoWindow = getLayoutInflater().inflate(R.layout.custom_info_contents, null);
    
            TextView title = ((TextView) infoWindow.findViewById(R.id.title));
            title.setText(marker.getTitle());
    
            TextView snippet = ((TextView) infoWindow.findViewById(R.id.snippet));
            snippet.setText(marker.getSnippet());
    
            return infoWindow;
          }
        });
    }
    

地図の状態を保存する

地図のカメラ位置とデバイスの位置情報を保存します。ユーザーが Android デバイスを回転させるか、設定に変更を加えると、Android フレームワークでマップのアクティビティが破棄されて再構築されます。スムーズなユーザー エクスペリエンスを確保するには、関連するアプリの状態を保存しておき、必要が生じた場合に復元することをおすすめします。

このチュートリアルでは、地図の状態を保存するために必要なすべてのコードが提供されています。詳細については、savedInstanceState バンドルに関するガイドをご覧ください。

  1. マップのアクティビティで、アクティビティの状態の保存に使用するキーの値を設定します。

    private static final String KEY_CAMERA_POSITION = "camera_position";
    private static final String KEY_LOCATION = "location";
    
  2. アクティビティが一時停止したときの状態を保存するための onSaveInstanceState() コールバックを実装します。

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        if (mMap != null) {
            outState.putParcelable(KEY_CAMERA_POSITION, mMap.getCameraPosition());
            outState.putParcelable(KEY_LOCATION, mLastKnownLocation);
            super.onSaveInstanceState(outState);
        }
    }
    
  3. アクティビティの onCreate() メソッドで、デバイスの位置情報と地図のカメラ位置を取得します(過去に保存している場合)。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState != null) {
            mCurrentLocation = savedInstanceState.getParcelable(KEY_LOCATION);
            mCameraPosition = savedInstanceState.getParcelable(KEY_CAMERA_POSITION);
        }
        ...
    }