0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

NCMBのKotlin SDKを使って地図検索アプリを作る(その3:位置情報検索を実装する)

0
Posted at

NCMBでは公式SDKとしてSwift/Objective-C/Kotlin/Java/Unity/JavaScript SDKを用意しています。また、それ以外にもコミュニティSDKとして、非公式ながらFlutter/React Native/Google Apps Script/C#/Ruby/Python/PHPなど幅広い言語向けにSDKが開発されています。

今回は公式SDKの一つ、Kotlin SDKを使って地図検索アプリを作ってみます。前回は位置情報をデータストアにインポートする流れを解説しました。今回は位置情報検索と、その結果を地図上に反映する流れを解説します。

完成版のコード

作成したデモアプリのコードはNCMBMania/kotlin-map-searchにアップロードしてあります。

Jetpack用Googleマップの追加

GoogleがJetpack compose用のGoogleマップコンポーネントを提供しています。インストールの流れはここを参考にしてください。なお、利用する際にはGoogleマップのAPIキーが必要です。

地図画面について

地図画面はメイン画面のタブで MapScreen として読み込まれています。

NavHost(navController = navController, startDestination = "Map") {
    composable("Map") { MapScreen()}
    composable("Import") { ImportScreen()}
}

UIについて

MapScreenは地図画面である*GoogleMap*を読み込んで、さらにマーカー消去用のフローティングアクションボタンを配置しています。

val context = LocalContext.current
// タップした位置情報が入る
var markers = remember { mutableStateListOf<LatLng>() }
// 検索条件にマッチする駅一覧が入る
var stations = remember { mutableStateListOf<NCMBObject>() }
// 画面について
Scaffold(
    // フローティングアイコンボタン
    floatingActionButton = {
        FloatingActionButton(
            modifier = Modifier.padding(end = 50.dp, bottom = 50.dp),
            onClick = {
                // 選択されたマーカー、駅情報をクリアする
                markers.clear()
                stations.clear()
            }) {
            Icon(Icons.Filled.Delete, contentDescription = "削除")
        }
    }
) { padding ->
    // 初期表示の位置情報(東京タワー)
    val tokyo = LatLng(35.6585805, 139.7454329)
    // 初期表示のズーム設定
    val cameraPositionState = rememberCameraPositionState {
        position = CameraPosition.fromLatLngZoom(tokyo, 13f)
    }
    // Googleマップのコンポーネント
    GoogleMap(
        modifier = Modifier
            .padding(padding)
            .fillMaxSize(),
        cameraPositionState = cameraPositionState,
        onMapClick = { position ->
            // 駅情報はクリア
            stations.clear()
            if (markers.size == 0) {
                // なければ追加するだけ
                markers.add(position)
            } else {
                // 選択された位置情報を最初に追加
                markers.add(0, position)
            }
            // 位置情報が2つ以上なら、最後のは不要
            if (markers.size > 2) {
                markers.removeAt(2)
            }
            // 選択されている位置情報の数に応じて検索方法を変更
            val ary = if (markers.size == 1)
                searchStationsNear(position) // 位置情報1つなら付近の駅を検索
                else searchStationSquare(markers) // 位置情報が2つなら矩形検索
            ary.forEach {
                stations.add(it)
            }
        }
    ) {
        // タップした箇所を表示するマーカー
        for(marker in markers) {
            Marker(
                state = MarkerState(position = marker),
                title = "marker",
            )
        }

        // 駅を青いマーカーで表示
        for (station in stations) {
            val geo = station.getGeo("geo")
            Marker(
                // 位置情報を取り出す
                state = MarkerState(position = LatLng(geo.mlatitude, geo.mlongitude)),
                title = "${station.getString("name")}駅",
                icon = BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_BLUE)
            )
        }
    }
}

Screenshot_20230317_171003のコピー2.png

マーカーは MapScreenmarkersstations にデータを追加すると、画面に反映されます。

// タップした箇所を表示するマーカー
for(marker in markers) {
    Marker(
        state = MarkerState(position = marker),
        title = "marker",
    )
}

// 駅を青いマーカーで表示
for (station in stations) {
    val geo = station.getGeo("geo")
    Marker(
        // 位置情報を取り出す
        state = MarkerState(position = LatLng(geo.mlatitude, geo.mlongitude)),
        title = "${station.getString("name")}駅",
        icon = BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_BLUE)
    )
}

タップした際の処理

地図をタップした際の処理です。 onMapClick が実行されます。この時、1回目のタップだった時にはその付近の駅を、2回目以降のタップだった場合には1回目のタップしたマーカーと合わせて矩形検索を行います。

onMapClick = { position ->
  // 駅情報はクリア
  stations.clear()
  if (markers.size == 0) {
      // なければ追加するだけ
      markers.add(position)
  } else {
      // 選択された位置情報を最初に追加
      markers.add(0, position)
  }
  // 位置情報が2つ以上なら、最後のは不要
  if (markers.size > 2) {
      markers.removeAt(2)
  }
  // 選択されている位置情報の数に応じて検索方法を変更
  val ary = if (markers.size == 1)
      searchStationsNear(position) // 位置情報1つなら付近の駅を検索
      else searchStationSquare(markers) // 位置情報が2つなら矩形検索
  ary.forEach {
      stations.add(it)
  }
}

付近の駅を検索する searchStationsNear 関数は以下のようになります。

// 1つのマーカーの付近(3.0km)を検索する関数
fun searchStationsNear(position: LatLng): List<NCMBObject> {
    // 検索するNCMBのデータストアクラス
    val query = NCMBQuery.forObject("Station")
    // 位置情報をNCMBGeoPointに変換
    val geo = NCMBGeoPoint(latitude = position.latitude, longitude = position.longitude)
    query.whereNearSphereKilometers("geo", geo, 3.0)
    return query.find()
}

Screenshot_20230317_171037.png

2点による矩形検索処理は以下のようになります。

// 2つのマーカーに挟まれた駅を検索する関数
fun searchStationSquare(positions: List<LatLng>): List<NCMBObject> {
    // 検索するNCMBのデータストアクラス
    val query = NCMBQuery.forObject("Station")
    // 位置情報をNCMBGeoPointに変換
    val geo1 = NCMBGeoPoint(latitude = positions[0].latitude, longitude = positions[0].longitude)
    val geo2 = NCMBGeoPoint(latitude = positions[1].latitude, longitude = positions[1].longitude)
    // 検索条件に指定
    query.whereWithinGeoBox("geo", geo1, geo2)
    // 検索実行
    return query.find()
}

Screenshot_20230317_171052.png

どちらも検索結果の駅一覧をマーカーに反映します。

// 選択されている位置情報の数に応じて検索方法を変更
val ary = if (markers.size == 1)
    searchStationsNear(position) // 位置情報1つなら付近の駅を検索
    else searchStationSquare(markers) // 位置情報が2つなら矩形検索
ary.forEach {
    stations.add(it)
}

後はフローティンアクションボタンをタップした際の処理で、マーカーを削除します。

FloatingActionButton(
    modifier = Modifier.padding(end = 50.dp, bottom = 50.dp),
    onClick = {
        // 選択されたマーカー、駅情報をクリアする
        markers.clear()
        stations.clear()
    }) {
    Icon(Icons.Filled.Delete, contentDescription = "削除")
}

これで地図検索アプリの完成です。

まとめ

NCMBを使えば位置情報を使った検索も簡単に実装できます。スマートフォンアプリでは位置情報をよく使いますので、ぜひNCMBの位置情報機能を役立ててください。

mBaaSでサーバー開発不要! | ニフクラ mobile backend

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?