【kotlin / osmdroid】タップ位置の緯度経度取得と緯度経度による地図検索

前回の記事でOsmdroidの機能を使い、自位置を地図に反映させました。 今回は緯度経度による地図の検索と画面のタップによる緯度経度の表示機能を追加したいと思います。

blog.misatowater.com

緯度経度の表示

Androdid端末の画面(地図)をタップしたときに、その地点の緯度経度を吹き出しで表示させます。

f:id:misatospring:20191030172831j:plain

オーバーレイとしてMapEventsOverlayを追加し、タップされた位置をMapEventsReceiverで受け取ります。MapEventsReceiverは短押し(タップ)と長押しの判別が可能ですが、今回は短押しのみの実装となります。

val mapEventReceiver = object : MapEventsReceiver{
    override fun singleTapConfirmedHelper(p: GeoPoint?): Boolean {
        val toast = Toast.makeText(
            baseContext,
            "${getString(R.string.latitude)}: ${p?.latitude}\n${getString(R.string.longitude)}: ${p?.longitude}",
            Toast.LENGTH_LONG)
        toast.setGravity(BOTTOM, 0, 200)
        toast.show()
        return false
    }

    override fun longPressHelper(p: GeoPoint?): Boolean {
        return false
    }
}
mapView.overlays.add(MapEventsOverlay(mapEventReceiver))

緯度経度による地図の検索

緯度経度の表示はオーバーレイを追加するのみで実装は簡単でした。
緯度経度からの検索は

  • 検索ボタンの押下
  • 緯度経度を記入する画面の表示
  • 検索する緯度経度を取得
  • 記入された緯度経度を地図のセンターとして表示

といくつか段階をふむ必要があります。
もちろん地図画面上に緯度経度を記入するテキストボックスを常時表示しておくことも可能ですが、地図が見づらくなってしまうので記入するボックスは別画面としたほうがよさそうです。

f:id:misatospring:20191030172826j:plain

検索ボタンの設置

前回記事のセンターボタン同じイメージビューの設置ではなく、オプションメニューを設置します。オプションメニューに「戻る」、「検索」のメニューをいれ、うち「検索」は常時表示させるようにします。

メニューレイアウト

resディレクトリにmenuディレクトリを作成し、xmlファイルを作成します(res/layout/menu/option.xml)。

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:android="http://schemas.android.com/apk/res/android">
    <item
            android:id="@+id/menu_findByLL"
            android:icon="@android:drawable/ic_menu_search"
            android:title="@string/findByLL"
            app:showAsAction="always" />

    <item
            android:id="@+id/menu_back"
            android:title="@string/menuBack" />
</menu>l
オプションメニューを表示する関数(MainActivity.kt)

オプションメニューを作成するonCreateOptionsMenuとオプション選択結果を受け取るonOptionsItemSelectedを追加します。
検索オプションが選択された場合は緯度経度記入用ダイアログを開きます。

override fun onCreateOptionsMenu(menu: Menu?): Boolean {
    menuInflater.inflate(R.menu.option, menu)
    return super.onCreateOptionsMenu(menu)
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
    return when(item.itemId) {
        R.id.menu_findByLL -> {
            FindByLLDialogFragment().show(supportFragmentManager, "find")
            return true
        }

        R.id.menu_back -> {
            return true
        }

        else -> super.onOptionsItemSelected(item)
    }
}
緯度経度記入ダイアログレイアウト

res/layoutディレクトリにxmlファイルを作成します(res/layout/dialog_findll.xml)。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="300dp"
        android:layout_height="wrap_content"
        android:orientation="vertical">

    <TextView
            android:id="@+id/header"
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:background="#FFC107"
            android:gravity="center"
            android:text="@string/findByLL"
            android:textSize="24sp" />

    <EditText
            android:id="@+id/latitude"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="20dp"
            android:layout_marginRight="20dp"
            android:ems="10"
            android:hint="@string/latitude"
            android:importantForAutofill="no"
            android:inputType="numberDecimal"
            android:singleLine="true"
            android:textSize="24sp" />

    <EditText
            android:id="@+id/longitude"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="20dp"
            android:layout_marginRight="20dp"
            android:ems="10"
            android:hint="@string/longitude"
            android:importantForAutofill="no"
            android:inputType="numberDecimal"
            android:singleLine="true"
            android:textSize="24sp" />
</LinearLayout>
ダイアログ表示クラス

緯度経度を記入するダイアログを表示するクラスです。"Ok"が押下された場合はリスナーで受け取ります。

import android.app.AlertDialog
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import androidx.fragment.app.DialogFragment

import java.lang.IllegalStateException
import kotlin.ClassCastException

class FindByLLDialogFragment: DialogFragment() {

    private lateinit var listener: NoticeDialogListener

    interface NoticeDialogListener {
        fun onDialogPositiveClick(dialog: DialogFragment)
//     fun onDialogNegativeClick(dialog: DialogFragment)
    }

    override fun onAttach(context: Context) {
        super.onAttach(context)
        try {
            listener = context as NoticeDialogListener
        } catch (e: ClassCastException) {
            throw ClassCastException("$context must implement NoticeDialogListener")
        }
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return activity?.let {
            val builder = AlertDialog.Builder(it)
            val inflater = requireActivity().layoutInflater

            builder.setView(inflater.inflate(R.layout.dialog_findbyll, null))
                .setPositiveButton("Ok", ({_, _ ->
                        listener.onDialogPositiveClick(this)
                    }))
                .setNegativeButton("Cancel", ({dialog, _ ->
                        dialog?.cancel()
                    }))
            builder.create()
        } ?: throw IllegalStateException("Activity cannot be null")
    }
}
ダイアログのリスナー(MainActivity.kt)

ダイアログ結果を受け取るリスナーです。
単純に記載位置をセンターにして、値チェックの実装は省いています。またMyLocationNewOverlayにより自位置がセンターになってしまうのを止めるため、同機能をDisableにしています。

override fun onDialogPositiveClick(dialog: DialogFragment) {
    val lat = dialog.dialog?.findViewById<EditText>(R.id.latitude)?.text.toString().toDouble()
    val lon = dialog.dialog?.findViewById<EditText>(R.id.longitude)?.text.toString().toDouble()
    locationOverlay.disableFollowLocation()
    mapView.controller.animateTo(GeoPoint(lat, lon))
}