【osmdroid】GPSで現在位置を表示する(Java)

はじめに

 マップアプリでは、ただマップを表示するだけでなく、現在位置も表示したいと思う場合があります。そこで今回は、GPS を用いて osmdroid (Android で OpenStreetMap を使えるようにしたライブラリ)上に現在位置を表示する方法について説明したいと思います。

 なお、この記事は「【osmdroid】Android でマップを表示する」の続きです。
 

図1. osmdroid上にGPSで現在位置を表示

Fused Location Provider

Fused Location Provider とは

 先ほどは GPS と書きましたが、実際には Fused Location Provider を使用します。
 Fused Location Provider とは、GPS だけではなく、Wifi や電話回線網などの情報も組み合わせて、位置情報を受信する仕組みです。Fused Location Provider が出るまでは GPS、Wifi などの切り分けをプログラマがいちいち行っていましたが、これを実装すると特に切り分けを特に意識しなくても位置情報を得ることができるようになり、楽に位置情報の受信処理を書くことが出来ます。

アプリの権限をリクエスト

 位置情報は個人情報にあたるため、権限リクエストをする必要があります。
 まず、マニフェストに「ACCESS_FINE_LOCATION」と「ACCESS_COARSE_LOCATION」の権限取得を宣言します。これらはそれぞれ、精度の高い位置情報と、比較的精度の低い位置情報へのアクセス許可になります。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    ...

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

    <application
        ...

 また、Fused Location Provider を使用するためには、Google play service の設定が必要です。
 build.gradle (:app) に「play-service-lcation」を追加します。

dependencies {
    ...
    implementation 'com.google.android.gms:play-services-location:21.0.1'
    ...
}

 アプリ起動後にユーザーに許可を得ることも必要です。下図が位置情報の権限リクエストのフローになります。APIレベルが23以上か、ユーザーが既に権限を付与しているか、ユーザーが権限を許可したかに応じて、実行する処理を切り分けます。
 

図2. 権限リクエストのフロー

 具体的なソースコードは以下のようになります。

private final int REQUEST_PERMISSION = 1000;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Configuration.getInstance().load(getApplicationContext(), PreferenceManager.getDefaultSharedPreferences(getApplicationContext()));
    setContentView(R.layout.activity_main);

    /*---------- GPSのアクセス許可設定 ----------*/
    // API23以上でパーミッションを確認する。
    if (Build.VERSION.SDK_INT >= 23) {
        // パーミッションが既に許可されている場合
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
            // 実行
            startLocationActivity();
        // 権限の根拠を示す必要がある場合
        } else if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)) {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            // 権限が必要な理由・メリットを説明
            builder.setMessage("アプリを継続するためには正確な位置情報の取得が必要です")
                    .setPositiveButton("OK", (dialog, id) ->
                            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION,}, REQUEST_PERMISSION))
                    .setNegativeButton("No thanks", (dialog, id) ->
                            Toast.makeText(this, "正確な位置情報が許可されないとアプリを実行できません", Toast.LENGTH_LONG).show());
            builder.create();
            builder.show();
        // 権限の根拠を示す必要が無い場合
        } else {
            // システム権限を要求する
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION,}, REQUEST_PERMISSION);
        }
    } else {
        // APIが22以下なら実行
        startLocationActivity();
    }
}

// パーミッションダイアログの結果受け取り
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == REQUEST_PERMISSION) {
        // ダイアログで承認された場合
        if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            startLocationActivity();
        // ダイアログで拒否された場合
        } else {
            Toast.makeText(this, "正確な位置情報が許可されないとアプリを実行できません", Toast.LENGTH_LONG).show();
        }
    }
}

位置情報の更新リクエスト

 以上で Fused Location Provider を使う準備が整いました。移動している場合などを想定すると、継続した位置情報の取得が必要になります。そのため、位置情報の更新リクエスト(Location Request)を作成する必要が有ります。

private LocationRequest.Builder locationRequest;
locationRequest = new LocationRequest.Builder(1000); // インターバルを1000ミリ秒に設定
locationRequest.setPriority(Priority.PRIORITY_HIGH_ACCURACY);

Location Callback を用いると、この Location Request を元に、逐次位置情報を取得することが出来ます。

private LocationCallback locationCallback;

locationCallback = new LocationCallback() {
    @Override
    public void onLocationResult(@NonNull LocationResult locationResult) {
        for (Location location : locationResult.getLocations()) {
            // 処理結果
        }
     }
};

ソースコード

 MainActivity.java の全体のソースコードは以下のようになります。

package com.example.trekkingtrail;

import android.Manifest;
import android.app.AlertDialog;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Build;
import android.os.Bundle;
import android.os.Looper;
import android.preference.PreferenceManager;
import android.widget.Toast;

import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.Priority;

import org.osmdroid.api.IMapController;
import org.osmdroid.config.Configuration;
import org.osmdroid.util.GeoPoint;
import org.osmdroid.views.MapView;
import org.osmdroid.views.overlay.Marker;

public class MainActivity extends AppCompatActivity {

    private final int REQUEST_PERMISSION = 1000;
    private boolean requestingLocationUpdates = false;
    private boolean firstAccess = true;
    private LocationRequest.Builder locationRequest;
    private LocationCallback locationCallback;
    private FusedLocationProviderClient fusedLocationClient;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Configuration.getInstance().load(getApplicationContext(), PreferenceManager.getDefaultSharedPreferences(getApplicationContext()));
        setContentView(R.layout.activity_main);

        /*---------- GPSのアクセス許可設定 ----------*/
        // API23以上でパーミッションを確認する。
        if (Build.VERSION.SDK_INT >= 23) {
            // パーミッションが既に許可されている場合
            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
                // 実行
                startLocationActivity();
            // 権限の根拠を示す必要がある場合
            } else if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)) {
                AlertDialog.Builder builder = new AlertDialog.Builder(this);
                // 権限が必要な理由・メリットを説明
                builder.setMessage("アプリを継続するためには正確な位置情報の取得が必要です")
                        .setPositiveButton("OK", (dialog, id) ->
                                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION,}, REQUEST_PERMISSION))
                        .setNegativeButton("No thanks", (dialog, id) ->
                                Toast.makeText(this, "正確な位置情報が許可されないとアプリを実行できません", Toast.LENGTH_LONG).show());
                builder.create();
                builder.show();
            // 権限の根拠を示す必要が無い場合
            } else {
                // システム権限を要求する
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION,}, REQUEST_PERMISSION);
            }
        } else {
            // APIが22以下なら実行
            startLocationActivity();
        }
    }

    // パーミッションダイアログの結果受け取り
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_PERMISSION) {
            // ダイアログで承認された場合
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                startLocationActivity();
            // ダイアログで拒否された場合
            } else {
                Toast.makeText(this, "正確な位置情報が許可されないとアプリを実行できません", Toast.LENGTH_LONG).show();
            }
        }
    }

    private void startLocationActivity() {
        MapView mapView = (MapView) findViewById(R.id.mapView);
        IMapController mapController = mapView.getController();

        // マップの初期設定
        mapView.setHorizontalMapRepetitionEnabled(true);
        mapView.setVerticalMapRepetitionEnabled(false);
        mapView.setScrollableAreaLimitLatitude(MapView.getTileSystem().getMaxLatitude(), MapView.getTileSystem().getMinLatitude(), 0);
        mapView.setMinZoomLevel(3.0);
        mapController.setZoom(9.0);
        mapView.setMultiTouchControls(true);

        requestingLocationUpdates = true;
        locationRequest = new LocationRequest.Builder(1000);
        locationRequest.setPriority(Priority.PRIORITY_HIGH_ACCURACY);

        locationCallback = new LocationCallback() {
            @Override
            public void onLocationResult(@NonNull LocationResult locationResult) {
                for (Location location : locationResult.getLocations()) {
                    // マップの表示を一旦すべて消す
                    mapView.getOverlays().clear();
                    mapView.invalidate();

                    // 現在位置を示すマーカーを追加
                    GeoPoint currentPoint = new GeoPoint(location.getLatitude(), location.getLongitude());
                    Marker currentMarker = new Marker(mapView);
                    currentMarker.setPosition(currentPoint);
                    mapView.getOverlays().add(currentMarker);

                    // アプリを開いた瞬間なら現在位置を中心に表示する
                    if (firstAccess) {
                        mapController.setCenter(currentPoint);
                        firstAccess = false;
                    }
                }
            }
        };
        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
    }
    private void startLocationUpdates() {
        if (ActivityCompat.checkSelfPermission(
                this, Manifest.permission.ACCESS_FINE_LOCATION)
                != PackageManager.PERMISSION_GRANTED) {
            return;
        }
        fusedLocationClient.requestLocationUpdates(locationRequest.build(), locationCallback, Looper.getMainLooper());
    }
    private void stopLocationUpdates() {
        fusedLocationClient.removeLocationUpdates(locationCallback);
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (requestingLocationUpdates) {
            startLocationUpdates();
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        stopLocationUpdates();
    }
}

 実行すると、現在位置が表示されました。

図3. osmdroid上にGPSで現在位置を表示

デモアプリ

 当サイトでは、osmdroid を用いた GPS ロガーのデモアプリを公開しています。使い方はこちら。下記のボタン、あるいはQRコードよりダウンロードできます。このアプリは Android4.1 から Android13 までに対応しています。
 

 


備考:デモアプリの Google Play での公開はめんどくさすぎて心が折れました。
 なお、デモアプリ上で使用しているマップデータは当サイトのサーバーに保存しているため、サーバースペックにより低解像度の地図となっています。高解像度な地図をご所望の方は、こちらにソースコードを公開しているので、Android Studio でアプリを作成して、OpenStreetMap にアクセスしてください。
 機種によってはバッテリー消費を抑える為、アプリが強制停止されることがあります。その場合は、バッテリー設定より、バックグラウンドでの動作を常に許可してください。
 → プライバシーポリシー

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です