ちょっと話題の記事

Android Tips #25 Google Maps Android API v2 逆引きリファレンス

2012.12.17

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

前回の記事 では、アプリに Google Map を表示する手順をご紹介しました。
今回は Google Maps Android API v2 を使ってどのようなこと(カスタマイズ)ができるか、公式のサンプルプロジェクトをベースに逆引きリファレンスとしてまとめてみました!

サンプルプロジェクトのインポート

まずはサンプルプロジェクトをインポートしましょう。SDK Manager より「Google Play services」をインストールしていることが前提で進めます。 「 File > Import... 」を選択し、「Existing Android Code Into Workspace」を選択します。

import01

ルートディレクトリは以下を選択します。

${SDK_ROOT}/extras/google/google_play_services/samples/map

インポートできました。

import02

実機でアプリを起動させるためには AndroidManifest.xml の application タグ内の meta-data に自分で取得した Google Maps API Key を設定します。Google Maps API Key の取得方法はこちらを参照してください。

<meta-data
      android:name="com.google.android.maps.v2.API_KEY"
      android:value="your_api_key"/>

あとは実機で実行できるはずです!

import03

Google Maps Android API v2 でできること

ここまででサンプルアプリが実行できました。実機で試すとわかりますが、いくつかのカスタマイズ例がリストで表示されていると思います。それぞれどんなことをしているか確認していきたいと思います。

ピンを立てる - Basic Map

Basic Map はマップにピンを立てるだけの簡単なサンプルです。MarkerOptions を作り、 GoogleMap インスタンスに addMarker() で追加するだけでピンが立てられます。

// Fragmentの取得
FragmentManager manager = getSupportFragmentManager();
SupportMapFragment frag = (SupportMapFragment) manager.findFragmentById(R.id.map_frag);
// GoogleMapの取得
GoogleMap map = frag.getMap();
// ピンを立てる
LatLng position = new LatLng(0, 0);
MarkerOptions options = new MarkerOptions();
options.position(position);
options.title("タイトル");
map.addMarker(options);

app01

指定の緯度・経度に移動する - Camera

指定した緯度・経度に遷移できるサンプルです。アニメーションの有効・無効も選択可能です。また、アニメーションの途中で停止することもできます。

// シドニーに行く
CameraPosition sydney = new CameraPosition.Builder()
		.target(new LatLng(-33.87365, 151.20689)).zoom(15.5f)
		.bearing(0).tilt(25).build();
map.animateCamera(CameraUpdateFactory.newCameraPosition(sydney));

app02

イベントをハンドリングする - Events

マップ操作中のイベントを取得するサンプルです。

// リスナーをセット
map.setOnMapClickListener(new OnMapClickListener() {
	@Override
	public void onMapClick(LatLng point) {
		String text = "latitude=" + point.latitude + ", longitude=" + point.longitude;
		Toast.makeText(getApplicationContext(), text, Toast.LENGTH_LONG).show();
	}
});
map.setOnMapLongClickListener(new OnMapLongClickListener() {
	@Override
	public void onMapLongClick(LatLng point) {
		String text = "latitude=" + point.latitude + ", longitude=" + point.longitude;
		Toast.makeText(getApplicationContext(), text, Toast.LENGTH_LONG).show();
	}
});
map.setOnCameraChangeListener(new OnCameraChangeListener() {
	@Override
	public void onCameraChange(CameraPosition position) {
		LatLng point = position.target;
		String text = "latitude=" + point.latitude + ", longitude=" + point.longitude;
		Toast.makeText(getApplicationContext(), text, Toast.LENGTH_LONG).show();
	}
});

app03

渋滞情報や現在位置を表示する - Layers

表示に Traffic (渋滞情報の表示)、 My Location (現在位置) のレイヤーを追加します。また、表示タイプを HYBRID, ROADMAP, SATELLITE, TERRAIN のいずれかに変更できます。

// 航空写真に変更
map.setMapType(GoogleMap.MAP_TYPE_SATELLITE);
// 渋滞状況を表示
map.setTrafficEnabled(true);
// 現在の位置情報を表示
map.setMyLocationEnabled(true);

app04

現在位置を好きな緯度・経度に設定する - Location Source Demo

現在位置を好きな緯度・経度に設定するには GoogleMap#setLocationSource() を使用します。引数には LocationSource インターフェースを実装した自作クラスのインスタンスを渡します。 自作クラスでは以下の手順で緯度・経度を設定します。 LocationSource インターフェースの activate() メソッドで渡される OnLocationChangedListener の onLocationChanged() を呼ぶことでその LocationSource の緯度・経度を設定できます。

  1. activate() メソッドで渡される OnLocationChangedListener を取得する
  2. Location インスタンスを作成して緯度・経度を設定する
  3. Location インスタンスを OnLocationChangedListener#onLocationChanged() で渡す

例えば以下のように実装します。

private class MyLocationSource implements LocationSource {
    @Override
    public void activate(OnLocationChangedListener listener) {
        // 好きな緯度・経度を設定した Location を作成
        Location location = new Location("MyLocation");
        location.setLatitude(0);
        location.setLongitude(0);
        location.setAccuracy(100); // 精度
        // Location に設定
        listener.onLocationChanged(location);
    }
    @Override
    public void deactivate() {
    }
}

そして実装したクラスのインスタンスを以下のようにマップに設定します。setMyLocationEnabled() を true に設定しないと表示されないので注意してください。

MyLocationSource source = new MyLocationSource();
mMap.setLocationSource(source);
mMap.setMyLocationEnabled(true);

app05

各種操作の有効・無効を設定する - UI Settings

UIの表示・非表示、ジェスチャーの有効・無効を設定できます。設定は GoogleMap#getUiSettings() で取得できる UiSettings インスタンスから設定します。その他に GoogleMap#setMyLocationEnabled() で現在位置表示の有効・無効も設定できます。

// 現在位置表示の有効化
mMap.setMyLocationEnabled(true);
// 設定の取得
UiSettings settings = mMap.getUiSettings();
// コンパスの有効化
settings.setCompassEnabled(true);
// 現在位置に移動するボタンの有効化
settings.setMyLocationButtonEnabled(true);
// ズームイン・アウトボタンの有効化
settings.setZoomControlsEnabled(true);
// すべてのジェスチャーの有効化
settings.setAllGesturesEnabled(true);
// 回転ジェスチャーの有効化
settings.setRotateGesturesEnabled(true);
// スクロールジェスチャーの有効化
settings.setScrollGesturesEnabled(true);
// Tlitジェスチャー(立体表示)の有効化
settings.setTiltGesturesEnabled(true);
// ズームジェスチャー(ピンチイン・アウト)の有効化
settings.setZoomGesturesEnabled(true);

app06

画像をオーバーレイで表示する - Ground Overlays

指定の場所に Bitmap をオーバーレイで表示します。指定する Bitmap は BitmapDescriptor というクラスのインスタンスを生成して使います。 BitmapDescriptor を生成する BitmapDescriptorFactory というクラスがあるので、これを使用してリソースから呼び出します。アルファ値を調整することも可能です。

// 貼り付ける場所まで移動しておく
LatLng location = new LatLng(40.714086, -74.228697);
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(location, 11));

// マップに貼り付ける BitmapDescriptor を生成
BitmapDescriptor descriptor = BitmapDescriptorFactory.fromResource(R.drawable.newark_nj_1922);

// 貼り付ける設定
GroundOverlayOptions options = new GroundOverlayOptions();
options.image(descriptor);
options.anchor(0, 1);
options.position(location, 8600f, 6500f);

// マップに貼り付け・アルファを設定
GroundOverlay overlay = mMap.addGroundOverlay(options);
overlay.setTransparency(0.5F);

app07

マーカーをカスタマイズする - Markers

マップ上に立てたピンをタップすると情報が表示できますが、表示される情報をカスタマイズすることができます。※ 情報を表示する View 内のテキストカラーや背景表示のカスタマイズ方法は解説が長くなりそうなので別な記事でご紹介します。

// オプション設定
MarkerOptions options = new MarkerOptions();
// 緯度・経度
options.position(new LatLng(0, 0));
// タイトル・スニペット
options.title("Title");
options.snippet("Snippet");
// アイコン(マップ上に表示されるピン)
options.icon(BitmapDescriptorFactory.fromResource(R.drawable.arrow));

// マーカーを貼り付け
mMap.addMarker(options);

app08

ポリゴンを描画する - Polygons

マップ上にポリゴンを描画します。描画する内容の設定には PolygonOptions クラスを使います。描画する緯度・経度は add() メソッドを使うか、まとめてセットできる addAll() メソッドを使います。また addHole() メソッドで描画する場所の中で抜き (間を空ける) こともできます。

private void setUpMap() {
    // 設定
    PolygonOptions options = new PolygonOptions();
    // 描画する座標を設定
    options.addAll(createRectangle(new LatLng(-20, 130), 5, 5));
    // 抜き
    options.addHole(createRectangle(new LatLng(-22, 128), 1, 1));
    // 塗り
    options.fillColor(0x44ff0000);
    // 線
    options.strokeColor(Color.RED);
    // 線幅
    options.strokeWidth(5);
    // 描画
    mMap.addPolygon(options);
}

private List<LatLng> createRectangle(LatLng center, double halfWidth, double halfHeight) {
    return Arrays.asList(new LatLng(center.latitude - halfHeight, center.longitude - halfWidth),
            new LatLng(center.latitude - halfHeight, center.longitude + halfWidth),
            new LatLng(center.latitude + halfHeight, center.longitude + halfWidth),
            new LatLng(center.latitude + halfHeight, center.longitude - halfWidth),
            new LatLng(center.latitude - halfHeight, center.longitude - halfWidth));
}

app09

線を描画する - Polylines

線を描画するには PolylinesOptions クラスを使います。 基本的には PolygonOptions と同じ感じです。 ある緯度・経度とある緯度・経度を結ぶ線を描画しますが、 geodesic() メソッドを使うと測地線 (2点間を結ぶ最短曲線) を描画することもできます。

// 設定
PolylineOptions options = new PolylineOptions();
options.add(new LatLng(35.689488, 139.691706)); // 東京
options.add(new LatLng(34.052234, -118.243685)); // ロサンゼルス
options.color(0xcc00ffff);
options.width(10);
options.geodesic(true); // 測地線で表示
mMap.addPolyline(options);

app10

好きな画像をタイル表示する - Tile Overlays

// マップを非表示
mMap.setMapType(GoogleMap.MAP_TYPE_NONE);

// 全部 Droid くん
TileProvider provider = new UrlTileProvider(256, 256) {
    @Override
    public URL getTileUrl(int x, int y, int zoom) {
        URL url = null;
        try {
            url = new URL("http://www.android.com/media/wallpaper/gif/android_logo.gif");
        } catch (MalformedURLException e) {
            throw new AssertionError(e);
        }
        return url;
    }
};

mMap.addTileOverlay(new TileOverlayOptions().tileProvider(provider));

app11

レイアウトXMLでオプションを指定する - Options

レイアウトXML上で表示位置や各ジェスチャーの有効・無効などを設定することができます。 fragment タグ内に map から始まるプロパティを指定することで、設定された状態でマップを表示することができます。

<fragment xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:map="http://schemas.android.com/apk/res-auto"
  android:id="@+id/map"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:layout_margin="5dp"
  class="com.google.android.gms.maps.SupportMapFragment"
  map:cameraBearing="112.5"
  map:cameraTargetLat="-33.796923"
  map:cameraTargetLng="150.922433"
  map:cameraTilt="30"
  map:cameraZoom="13"
  map:mapType="normal"
  map:uiCompass="false"
  map:uiRotateGestures="true"
  map:uiScrollGestures="false"
  map:uiTiltGestures="true"
  map:uiZoomControls="false"
  map:uiZoomGestures="true"/>

app12

複数のマップを表示する - Multiple Maps

レイアウトXMLに複数の Fragment を置いておくことで、複数のマップを表示することもできます。多すぎるとパフォーマンスが低下するので注意して使いましょう。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:map="http://schemas.android.com/apk/res-auto"
  android:id="@+id/map_container"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical">
  <LinearLayout
    android:id="@+id/map_container2"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_weight="0.5"
    android:orientation="horizontal">
    <fragment
      android:id="@+id/map1"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:layout_weight="0.5"
      class="com.google.android.gms.maps.SupportMapFragment"
      map:cameraTargetLat="40.72"
      map:cameraTargetLng="-74.00"
      map:cameraZoom="8"/>
    <fragment
      android:id="@+id/map2"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:layout_weight="0.5"
      class="com.google.android.gms.maps.SupportMapFragment"
      map:cameraTargetLat="51.51"
      map:cameraTargetLng="-0.12"
      map:cameraZoom="8"/>
  </LinearLayout>
  <LinearLayout
    android:id="@+id/map_container2"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_weight="0.5"
    android:orientation="horizontal">
    <fragment
      android:id="@+id/map3"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:layout_weight="0.5"
      class="com.google.android.gms.maps.SupportMapFragment"
      map:cameraTargetLat="48.85"
      map:cameraTargetLng="2.35"
      map:cameraZoom="8"/>
    <fragment
      android:id="@+id/map4"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:layout_weight="0.5"
      class="com.google.android.gms.maps.SupportMapFragment"
      map:cameraTargetLat="35.69"
      map:cameraTargetLng="139.69"
      map:cameraZoom="8"/>
  </LinearLayout>
</LinearLayout>

app13

表示位置を保持する - Retain Map

MapFragment#setRetainInstance() を true に設定しておくと、 Activity の再生成時 ( onCreate() で savedInstanceState が渡される状態 ) に MapFragment#getMap() で以前表示していた状態でマップを復元できます。例えば onCreate() で以下のように GoogleMap インスタンスを取得します。

if (savedInstanceState == null) {
    // Activity が初めて生成されたとき
    mapFragment.setRetainInstance(true);
    mMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)).getMap();
} else {
    // Activity が再生成されたとき
    mMap = mapFragment.getMap();
}

Fragment を使わずに表示する - Raw MapView

MapFragment を介さず直接 MapView をレイアウトに配置して表示することもできます。 GoogleMap インスタンスは MapView#getMap() で取得することができます。それだけで表示は可能ですが、 MapView には Activity と同じようなライフサイクルメソッドがあるので、 Activity のライフサイクルメソッドと同じタイミングで呼ぶようにしましょう。

レイアウトXML

<com.google.android.gms.maps.MapView 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/map"
  android:layout_width="match_parent"
  android:layout_height="match_parent"/>

Javaソース

// MapView の取得
MapView view = (MapView) findViewById(R.id.map);
// GoogleMap の取得
GoogleMap map = view.getMap();
// 以下は Activity のライフサイクルメソッドにあわせる
view.onResume();
view.onPause();
view.onDestroy();
view.onLowMemory();
view.onSaveInstanceState(outState);

【map15.png】

マップを動的に追加する - Programmatically Add Map

Javaソース上からマップを動的に追加するには、 MapFragment をインスタンス化し、 FragmentManager で対象の Fragment を指定、 FragmentTransaction で追加します。 APIレベル4から使用したい場合は MapFragment ではなく SupportMapFragment を使い、 FragmentManager と FragmentTransaction は SupportPackage のクラスを使用しましょう。

// FragmentManager の取得
FragmentManager manager = getSupportFragmentManager();
// SupportMapFragment の取得 (既にあれば)
SupportMapFragment frag = (SupportMapFragment) manager.findFragmentByTag("map");
if (frag == null) {
    // SupportMapFragment の生成
    frag = SupportMapFragment.newInstance();
    // Fragment の追加
    FragmentTransaction transaction = manager.beginTransaction();
    transaction.add(android.R.id.content, frag, "map");
    transaction.commit();
}

まとめ

個人的な感想としては、緯度・経度をレイアウトで指定できたり、マップ上に図をレンダリングできたり、ジェスチャーの有効・無効が細かく指定できたり、アプリに合わせて自由に、そして詳細にカスタマイズできそうなのでとても良い印象でした。とても使いやすいです! v2 がリリースされたついでにアプリ作ってみようかな〜ってかたは、サクッと導入できるしサクッとカスタマイズできるので、ぜひ参考にしながら使ってみてください!

参考