このエントリーをはてなブックマークに追加このエントリをつぶやく

地図アプリの作成-Android版その2

iPhone版に引き続き、Android版の第2回です。
地図に自位置を表示することと、位置情報サービスを利用して現在位置の情報を取得するのを実装します。

Androidにおける位置情報サービス

現時点では以下のものを位置情報ソースとして使うことができます。
・3G
・WiFi
・GPS
これらはAndroid OS内で取得され、アプリケーション側では統一したインターフェースで扱うことができます。
端末によってはこれらのチップが載っていないので使えないこともあります。

3G/WiFiは電波の強度・電波の発信源の位置を基準に位置情報を特定します。
特性としては
・(GPSにくらべて)電池を使わない
・精度が低い
といったことが挙げられます。
GPSは衛星からの電波を元に現在位置を計算します。
3G/WiFiとはほぼ逆の特性を持っていて
・電池を使う
・(衛星さえ捉えられれば)精度が高い
となっています。
衛星を使うという仕組み上、ビルの谷間や室内では精度が下がる・あるいは位置情報を測定できない、という特性もあります。
(最近のGPSレシーバは微弱電波でも測位可能になってきていますが)

さて上記3つの位置情報ソースですが、Androidでは大きく2つのグループに分類されています。
1つめは3G/WiFiを使う、NETWORK_PROVIDER
2つめはGPSを使う、GPS_PROVIDER
です。
そして、これらのプロバイダにアクセスするためのPERMISSIONとして
・NETWORK_PROVIDERだけにアクセスする ACCESS_COASE_LOCATION
・NETWORK_PROVIDER/GPS_PROVIDER両方にアクセスする、ACCESS_FINE_LOCATION
が定義されています。

自分のアプリケーションの特性に合わせてどちらかを選べばよいと思います。
今回は地図アプリケーションということでACCESS_FINE_LOCATIONを使います。
まずはAndroidManifest.xmlにACCESS_FINE_LOCATIONを追加しましょう

[cce_xml]
<?xml version=”1.0″ encoding=”utf-8″?>
<manifest xmlns:android=”http://schemas.android.com/apk/res/android”
package=”jp.co.spiritek.dev.mapapp”
android:versionCode=”1″
android:versionName=”1.0″>
<uses-sdk android:minSdkVersion=”4″ />
<uses-permission android:name=”android.permission.INTERNET”></uses-permission>
<uses-permission android:name=”android.permission.ACCESS_FINE_LOCATION”></uses-permission>

<application android:icon=”@drawable/icon” android:label=”@string/app_name”>
<activity android:name=”.MapSampleActivity”
android:label=”@string/app_name”>
<intent-filter>
<action android:name=”android.intent.action.MAIN” />
<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
</activity>
<uses-library android:name=”com.google.android.maps”></uses-library>
</application>
</manifest>
[/cce_xml]

自位置の表示

AndroidのMapViewで地図以外のものを表示するときにはOverlayという仕組みを用います。
MyLocationOverlayという位置情報と連動できるOverlayのサブクラスが提供されているので、これを使いましょう。

[cce_java]
MyLocationOverlay myLocationOverlay;
MapView mapView;
MapController controller;

@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

// マップビューを取得
mapView = (MapView) findViewById(R.id.mapview);
// マップをコントロールするためのコントローラを取得
controller = mapView.getController();
// ZoomLevel(1~21)を設定
controller.setZoom(17);

myLocationOverlay = new MyLocationOverlay(getApplicationContext(),
mapView);
myLocationOverlay.onProviderEnabled(LocationManager.GPS_PROVIDER);
myLocationOverlay.enableMyLocation();
myLocationOverlay.runOnFirstFix(new Runnable()
{
@Override
public void run()
{
controller.animateTo(myLocationOverlay.getMyLocation());
}
});
mapView.getOverlays().add(myLocationOverlay);
mapView.invalidate();
}
[/cce_java]

onProvidereEnabled()で使用する位置情報プロバイダを指定します
enabledMyLocation()で自位置の追跡を有効にします
runOnFirstFix()では一番最初に位置情報が確定したときに実行するコードを指定します。
ここでは測位した位置に地図の中心をもっていっています
そしてmapViewにoverlayを追加します。
最後にmapView.invalidate()で再描画させます。

これで、自位置にマーカーが表示されるようになります。

現在位置の追尾

このままでは自位置にマーカーは表示されますが、移動しても地図中心は移動しません。
(マーカーが画面外になると中心に移動しますが・・・)
地図中心も自位置を反映するようにしましょう。

一番手っ取り早いのは、MyLocationOverlayのサブクラスを作成し、onLocationChangedメソッドをオーバーライドすることです。
[cce_java]
class TrackingMyLocationOverlay extends MyLocationOverlay
{
MapView mapView;

public TrackingMyLocationOverlay(Context context, MapView mapView)
{
super(context, mapView);
this.mapView = mapView;
}

@Override
public void onLocationChanged(Location location)
{
super.onLocationChanged(location);
mapView.getController().animateTo(new GeoPoint(
(int) (location.getLatitude() * 1E6),
(int) (location.getLongitude() * 1E6)));
mapView.invalidate();
}
}
[/cce_java]

MyLocationOverlay#onLocationChangedは位置情報が変化したときに呼び出されます。
ですので、その位置情報をもとに地図の中心を移動すればOKです。

パラメータのLocationクラスは以下のような情報を持ちます。
・緯度(Latitude) double 南緯がマイナス、北緯がプラスの値です。WGS84測地系で単位は度(degree)
・経度(Longitude) double 西経がマイナス、東経がプラスの値です。WGS84測地系で単位は度(degree)
・位置情報プロバイダ この位置情報を測定したプロバイダの名前
・測位時刻 long 1970/1/1 0:00からのUTC時刻。単位はミリ秒。 値はJavaのDate#getTime()と互換性があります。
・高度(Altitude) double 単位はメートル
・速度(Speed) float 単位はメートル毎秒
・方位(Bearing) float 北を0として時計回りに東が90、南が180、西が270となります。単位は度。
・精度(Accuracy) float 単位はメートル

位置情報プロバイダによっては取得できないものもありますから、それぞれのパラメータを持つかどうかを判定するメソッドも提供されています。
たとえば方位(Bearing)はgetBearing()で取得できますが、取得するまえにhasBearing()で有効かどうかを判定できます。

MapViewクラスで扱う位置情報はGeoPointクラスですが、表現形式が異なるためそのまま代入することができません。(第1回参照)
GeoPointクラスの引数名がlatitudeE6,longitudeE6になっているのはこの変換式で*1E6(1×10^6=1,000,000)するからですね。

ここまでの実装で以下のように表示されます。

位置情報サービスの使用

さて、ここまでで自位置の表示は行うことができるようになりました。
ですが、位置情報そのものを扱いたいという場合にはMyLocationOverlayクラスを介して行うのは難しいこともあります。
(たとえば地図表示なしに位置情報を保存したい、等)

その場合にはLocationManagerを使用します。LocationManagerは地図表示とは独立に位置情報サービスを扱うためのクラスです。

まず位置情報サービスを取得します。
[cce_java]
LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
[/cce_java]

次にリスナを定義します。
[cce_java]
LocationListener locationListener = new LocationListener()
{
@Override
public void onStatusChanged(String provider, int status, Bundle extras)
{
}

@Override
public void onProviderEnabled(String provider)
{
}

@Override
public void onProviderDisabled(String provider)
{
}

@Override
public void onLocationChanged(Location location)
{
}
};
[/cce_java]

LocationListenerには4つのメソッドがあります

  • onStatusChanged:
    位置情報プロバイダの状態が変化したときに呼び出されます。SDKによれば
    Called when the provider status changes. This method is called when a provider is unable to fetch a location or if the provider has recently become available after a period of unavailability.
    とあるので、位置情報が取得できなくなった時、もしくは取得できなくなってから一定時間以上経過した後に位置情報が取得できるようになった時、に呼び出されるみたいですね。
    長大トンネル等を通過するような場合に、トンネルに入ってからから1回、トンネルを出てから1回呼び出されるとイメージすればよいかと思います。

  • onProviderEnabled:
    位置情報プロバイダそのものが有効になった場合に呼び出されます。Androidの設定からGPSを有効にしたときなどですね。

  • onProviderDisabled:
    onProviderEnabledの逆で、位置情報プロバイダが無効になったときに呼び出されます

  • onLocationChanged:
    位置情報が変化したときに呼び出されます。
    地図アプリで主に使うのはこのメソッドでしょう。
    MyLocationOverlayのonLocationChangedもまさにこのメソッドです。

そして位置情報が変化したときに通知してくれるようにリスナを登録します。
[cce_java]
locationManager.requestLocationUpdates(LocatioinManager.GPS_PROVIDER, 0, 0, locationListener);
[/cce_java]
リスナの登録には4つのパラメータを必要とします。

  • provider
    どの位置情報プロバイダを使うか

  • minTime
    位置情報更新間隔の最低時間(ミリ秒)。0を指定するとそのプロバイダの最小間隔になります。

  • minDistance
    位置情報更新間隔の最低距離(メートル)。0を指定すると距離による更新間隔は考慮されなくなります。

  • listener
    登録するリスナ

今回はGPS_PROVIDERだけ使用しますが、NETWORK_PROVIDERも合わせて使用したい場合、2回requestLocationUpdatesを呼び出せばOKです。(GPS_PROVIDERで1回、NETWORK_PROVIDERで1回)
それぞれのプロバイダ毎に違う更新間隔を指定できます。

位置情報サービスが不要になったらリスナを解除します。
[cce_java]
locationManager.removeUpdates(locationListener);
[/cce_java]

今回はonLocationChangedの中は定義しませんでした。
次回以降でログを保存する機能を実装するときに、さらに詳しく解説します。
次回は地図に情報を重ねる方法の予定です。

藤田 の紹介

スピリテックのCTOで今ココなう!の中の人。
カテゴリー: プログラミング   タグ: , , ,   この投稿のパーマリンク

コメントは受け付けていません。