最近 Java や Kotlin 言語の周りの様々な記事で目にする単語「ラムダ(Lambda)」。
私はわりと古めの Java ユーザーなので、「ラムダ」については概念程度を少し理解できる程度で留まっています。
そこで今回は、私が ArcGIS Runtime SDK for Android という地図アプリ開発キット製品を担当していることから、流行りの Kotlin でラムダ記述を取り入れて地図アプリを作成してみました。
作った地図アプリと仕組み
あらかじめ用意しておいた、東京都 23 区のポリゴン データ(市区町村の境界図形データ)に対して任意の文字列(例えば、「千代田区平河町2丁目」など、区と町丁字で構成された文字列)で検索する地図アプリを作成してみました。丁目を指定しない場合は1丁目が返ります。区を指定しない場合でも、該当する町丁目名があればその場所を返します。
該当するポリゴン フィーチャがみつかったら、その場所へズームして表示します。
ポリゴン データは GIS のクラウドサービスである ArcGIS Online でホストすることにより Web サービス(フィーチャ サービス)化していて、このサービスに対して ArcGIS Runtime SDK for Android のメソッドを使用して文字列検索し、該当するポリゴン フィーチャを判定するという仕組みになっています。
このポリゴン フィーチャのひとつひとつに区名や町丁字名などの属性情報を保持しています。実際に中身を見てみると次のようになっています。
全体のコードは ESRIジャパン GitHub にて公開しています。
Kotlin コードでラムダにふれてみた
今回書いてみたコードです。
手順として、
といったことがメインの手順になります。全体のコードは以下の通りで、手順に沿って解説していきます。
/**
* 入力の文字をもとに該当するポリゴンを探すメソッドをラムダ式で書いてみる
*
* */
var searchForPolygon: (String) -> Unit = { searchString ->
・・・
// 検索文字列を作成して query に設定する
var query = QueryParameters()
query.whereClause = createstr(searchString);
Log.d(TAG, query.whereClause.toString())
// ホストしているポリゴン データに対して検索をかける
val future = mServiceFeatureTable!!.queryFeaturesAsync(query)
future.addDoneListener {
try {
// 検索結果全体を取得する
val result = future.get()
// 結果の検査
if (result.iterator().hasNext()) {
// 最初の結果を取得して、該当するポリゴンへズームする
val feature = result.iterator().next()
val envelope = feature.geometry.extent
mMapView!!.setViewpointGeometryAsync(envelope, 200.0)
// ズームしたポリゴンの表示色を変更する
var selectGraphic = Graphic(feature.geometry,mSimpleFillSymbol)
mGraphicOverlay!!.graphics.add(selectGraphic)
}・・・
} catch (e: Exception) {
Toast.makeText(this@MainActivity, "フィーチャ検索に失敗しました: " + searchString + ". Error=" + e.message, Toast.LENGTH_SHORT).show()
}
}
}
1.ラムダの書き方で基本的な枠組みを定義する
私の理解の範囲ですが「ラムダ」とは、関数を変数のように定義してあつかうことができるもの。ということで、その実際のメリットはコードの可読性を上げたり、コード量を減らしたりということが挙げられると思います。しかしその反面、従来の Java ユーザーからすると少し不思議な記述法になります。
Kotlin文法 - 関数とラムダの記事を参考にして、中身の処理を抜いた以下の形が基本式となります。
var searchForPolygon: (String) -> Unit = { searchString -> }
これは、(String) 型の引数を入れて-> Unit型 が戻るという意味で、実際に入力したい引数は {“引数” -> true } に設定します。Kotlin リファレンスによれば、Unit 型とは Java で Void 型を表すそうです。
2.引数(searchString)の検索文字列から query オブジェクトを作成して検索条件を作成する
ここからは ArcGIS Runtime SDK for Android を使用したコードになります。
query.whereClause の変数に、ポリゴン フィーチャを検索するための条件を作成します。searchString を引数にして次のメソッドを作成して条件を設定しています。
/**
* ArcGIS へ CALLする検索文字列を作成
* */
fun createstr (searchString: String) : String {
// searchString には画面に入力された文字列が入ってくる
var reString: String? = null
val ku = searchString.split("区".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray()
if (ku.size > 1) {
// 区で split できたら「区+丁目」で検索できる条件を作成する
reString = "upper(CSS_NAME) LIKE '" + ku[0] + "区' and (MOJI) LIKE '%" + ku[1] + "%'"
} else {
// 区で split できない場合は入力文字列で検索できる条件を作成する
reString = "upper(MOJI) LIKE '%" + ku[0] + "%'"
}
return reString
}
“CSS_NAME” や ”MOJI” は、ポリゴン フィーチャの属性に定義されているカラム名(属性項目名)です。23 区名と町丁目名が、別々のカラムとして定義されています。
よって、例えば “千代田区平河町2丁目” と入力した場合は次の条件文ができます。
upper(CSS_NAME) LIKE '千代田区' and (MOJI) LIKE '%平河町2丁目%'
3.ポリゴンに対して検索をかける
コードの主要部分はここです。Listener を使って検索結果を取得します。
// ホストしているポリゴン データに対して検索をかける
val future = mServiceFeatureTable!!.queryFeaturesAsync(query)
future.addDoneListener {
try {
// 検索結果全体を取得する
val result = future.get()
// 結果の検査
if (result.iterator().hasNext()) {
// 最初の結果を取得して、該当するポリゴンへズームする
val feature = result.iterator().next()
val envelope = feature.geometry.extent
mMapView!!.setViewpointGeometryAsync(envelope, 200.0)
// ズームしたフィーチャを選択した状態にする
mFeatureLayer!!.selectFeature(feature)
} ・・・
} catch (e: Exception) {
Toast.makeText(this@MainActivity, "フィーチャ検索に失敗しました: " + searchString + ". Error=" + e.message, Toast.LENGTH_SHORT).show()
}
}
ラムダとの戦闘後記
ラムダの記述は、それが実装される前の Java コーディングに慣れているととても理解するのが難しいです。
文法を説明している記事や短い用例を端的に表したサンプルを結構みつけることができましたが、実際に自分でやりたいことを交えてコーディングすると何が悪いのかわからないコンパイルエラーに数多くぶつかりました。そして、今回クローズアップしたコードのような一定の処理を交えてラムダを紹介しているブログには行き当たりませんでしたのでこの記事を執筆してみました。
なんとかラムダを取り入れてコードを書くことを目標にして達成?できましたが、ラムダ・無名関数・SAM 変換など、意味や使いどころをわかってクールなコードを書くことを目指していきたいと思います!
関連リンク集
ArcGIS 関連ページ
・ArcGIS for Developers 開発リソース集