
最近 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 コードでラムダにふれてみた
今回書いてみたコードです。
手順として、
- ラムダの書き方で基本的な枠組みを定義する
- 引数(searchString)の検索文字列から query オブジェクトを作成して検索条件を作成する
- ポリゴン フィーチャに対して検索をかける
といったことがメインの手順になります。全体のコードは以下の通りで、手順に沿って解説していきます。
<SPAN class="comment token">/**
* 入力の文字をもとに該当するポリゴンを探すメソッドをラムダ式で書いてみる
*
* */</SPAN>
<SPAN class="keyword token">var</SPAN> searchForPolygon<SPAN class="punctuation token">:</SPAN> <SPAN class="punctuation token">(</SPAN>String<SPAN class="punctuation token">)</SPAN> <SPAN class="operator token">-</SPAN><SPAN class="operator token">></SPAN> Unit <SPAN class="operator token">=</SPAN> <SPAN class="punctuation token">{</SPAN> searchString <SPAN class="operator token">-</SPAN><SPAN class="operator token">></SPAN>
・・・
<SPAN class="comment token">// 検索文字列を作成して query に設定する</SPAN>
<SPAN class="keyword token">var</SPAN> query <SPAN class="operator token">=</SPAN> <SPAN class="token function">QueryParameters</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN>
query<SPAN class="punctuation token">.</SPAN>whereClause <SPAN class="operator token">=</SPAN> <SPAN class="token function">createstr</SPAN><SPAN class="punctuation token">(</SPAN>searchString<SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN>
Log<SPAN class="punctuation token">.</SPAN><SPAN class="token function">d</SPAN><SPAN class="punctuation token">(</SPAN>TAG<SPAN class="punctuation token">,</SPAN> query<SPAN class="punctuation token">.</SPAN>whereClause<SPAN class="punctuation token">.</SPAN><SPAN class="token function">toString</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">)</SPAN>
<SPAN class="comment token">// ホストしているポリゴン データに対して検索をかける</SPAN>
val future <SPAN class="operator token">=</SPAN> mServiceFeatureTable<SPAN class="operator token">!</SPAN><SPAN class="operator token">!</SPAN><SPAN class="punctuation token">.</SPAN><SPAN class="token function">queryFeaturesAsync</SPAN><SPAN class="punctuation token">(</SPAN>query<SPAN class="punctuation token">)</SPAN>
future<SPAN class="punctuation token">.</SPAN>addDoneListener <SPAN class="punctuation token">{</SPAN>
<SPAN class="keyword token">try</SPAN> <SPAN class="punctuation token">{</SPAN>
<SPAN class="comment token">// 検索結果全体を取得する</SPAN>
val result <SPAN class="operator token">=</SPAN> future<SPAN class="punctuation token">.</SPAN><SPAN class="keyword token">get</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN>
<SPAN class="comment token">// 結果の検査</SPAN>
<SPAN class="keyword token">if</SPAN> <SPAN class="punctuation token">(</SPAN>result<SPAN class="punctuation token">.</SPAN><SPAN class="token function">iterator</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">.</SPAN><SPAN class="token function">hasNext</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">)</SPAN> <SPAN class="punctuation token">{</SPAN>
<SPAN class="comment token">// 最初の結果を取得して、該当するポリゴンへズームする</SPAN>
val feature <SPAN class="operator token">=</SPAN> result<SPAN class="punctuation token">.</SPAN><SPAN class="token function">iterator</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">.</SPAN><SPAN class="token function">next</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN>
val envelope <SPAN class="operator token">=</SPAN> feature<SPAN class="punctuation token">.</SPAN>geometry<SPAN class="punctuation token">.</SPAN>extent
mMapView<SPAN class="operator token">!</SPAN><SPAN class="operator token">!</SPAN><SPAN class="punctuation token">.</SPAN><SPAN class="token function">setViewpointGeometryAsync</SPAN><SPAN class="punctuation token">(</SPAN>envelope<SPAN class="punctuation token">,</SPAN> <SPAN class="number token">200.0</SPAN><SPAN class="punctuation token">)</SPAN>
<SPAN class="comment token">// ズームしたポリゴンの表示色を変更する</SPAN>
<SPAN class="keyword token">var</SPAN> selectGraphic <SPAN class="operator token">=</SPAN> <SPAN class="token function">Graphic</SPAN><SPAN class="punctuation token">(</SPAN>feature<SPAN class="punctuation token">.</SPAN>geometry<SPAN class="punctuation token">,</SPAN>mSimpleFillSymbol<SPAN class="punctuation token">)</SPAN>
mGraphicOverlay<SPAN class="operator token">!</SPAN><SPAN class="operator token">!</SPAN><SPAN class="punctuation token">.</SPAN>graphics<SPAN class="punctuation token">.</SPAN><SPAN class="token function">add</SPAN><SPAN class="punctuation token">(</SPAN>selectGraphic<SPAN class="punctuation token">)</SPAN>
<SPAN class="punctuation token">}</SPAN>・・・
<SPAN class="punctuation token">}</SPAN> <SPAN class="keyword token">catch</SPAN> <SPAN class="punctuation token">(</SPAN><SPAN class="token class-name">e</SPAN><SPAN class="punctuation token">:</SPAN> Exception<SPAN class="punctuation token">)</SPAN> <SPAN class="punctuation token">{</SPAN>
Toast<SPAN class="punctuation token">.</SPAN><SPAN class="token function">makeText</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="keyword token">this</SPAN>@MainActivity<SPAN class="punctuation token">,</SPAN> <SPAN class="string token">"フィーチャ検索に失敗しました: "</SPAN> <SPAN class="operator token">+</SPAN> searchString <SPAN class="operator token">+</SPAN> <SPAN class="string token">". Error="</SPAN> <SPAN class="operator token">+</SPAN> e<SPAN class="punctuation token">.</SPAN>message<SPAN class="punctuation token">,</SPAN> Toast<SPAN class="punctuation token">.</SPAN>LENGTH_SHORT<SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">.</SPAN><SPAN class="token function">show</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN>
<SPAN class="punctuation token">}</SPAN>
<SPAN class="punctuation token">}</SPAN>
<SPAN class="punctuation token">}</SPAN><SPAN class="line-numbers-rows"><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN></SPAN>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 を引数にして次のメソッドを作成して条件を設定しています。
<SPAN class="comment token">/**
* ArcGIS へ CALLする検索文字列を作成
* */</SPAN>
fun createstr <SPAN class="punctuation token">(</SPAN>searchString<SPAN class="punctuation token">:</SPAN> String<SPAN class="punctuation token">)</SPAN> <SPAN class="punctuation token">:</SPAN> String <SPAN class="punctuation token">{</SPAN>
<SPAN class="comment token">// searchString には画面に入力された文字列が入ってくる</SPAN>
<SPAN class="keyword token">var</SPAN> reString<SPAN class="punctuation token">:</SPAN> String<SPAN class="operator token">?</SPAN> <SPAN class="operator token">=</SPAN> <SPAN class="keyword token">null</SPAN>
val ku <SPAN class="operator token">=</SPAN> searchString<SPAN class="punctuation token">.</SPAN><SPAN class="token function">split</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="string token">"区"</SPAN><SPAN class="punctuation token">.</SPAN><SPAN class="token function">toRegex</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">.</SPAN><SPAN class="token function">dropLastWhile</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">{</SPAN> it<SPAN class="punctuation token">.</SPAN><SPAN class="token function">isEmpty</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN> <SPAN class="punctuation token">}</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">.</SPAN><SPAN class="token function">toTypedArray</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN>
<SPAN class="keyword token">if</SPAN> <SPAN class="punctuation token">(</SPAN>ku<SPAN class="punctuation token">.</SPAN>size <SPAN class="operator token">></SPAN> <SPAN class="number token">1</SPAN><SPAN class="punctuation token">)</SPAN> <SPAN class="punctuation token">{</SPAN>
<SPAN class="comment token">// 区で split できたら「区+丁目」で検索できる条件を作成する</SPAN>
reString <SPAN class="operator token">=</SPAN> <SPAN class="string token">"upper(CSS_NAME) LIKE '"</SPAN> <SPAN class="operator token">+</SPAN> ku<SPAN class="punctuation token">[</SPAN><SPAN class="number token">0</SPAN><SPAN class="punctuation token">]</SPAN> <SPAN class="operator token">+</SPAN> <SPAN class="string token">"区' and (MOJI) LIKE '%"</SPAN> <SPAN class="operator token">+</SPAN> ku<SPAN class="punctuation token">[</SPAN><SPAN class="number token">1</SPAN><SPAN class="punctuation token">]</SPAN> <SPAN class="operator token">+</SPAN> <SPAN class="string token">"%'"</SPAN>
<SPAN class="punctuation token">}</SPAN> <SPAN class="keyword token">else</SPAN> <SPAN class="punctuation token">{</SPAN>
<SPAN class="comment token">// 区で split できない場合は入力文字列で検索できる条件を作成する</SPAN>
reString <SPAN class="operator token">=</SPAN> <SPAN class="string token">"upper(MOJI) LIKE '%"</SPAN> <SPAN class="operator token">+</SPAN> ku<SPAN class="punctuation token">[</SPAN><SPAN class="number token">0</SPAN><SPAN class="punctuation token">]</SPAN> <SPAN class="operator token">+</SPAN> <SPAN class="string token">"%'"</SPAN>
<SPAN class="punctuation token">}</SPAN>
<SPAN class="keyword token">return</SPAN> reString
<SPAN class="punctuation token">}</SPAN><SPAN class="line-numbers-rows"><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN></SPAN>“CSS_NAME” や ”MOJI” は、ポリゴン フィーチャの属性に定義されているカラム名(属性項目名)です。23 区名と町丁目名が、別々のカラムとして定義されています。
よって、例えば “千代田区平河町2丁目” と入力した場合は次の条件文ができます。
<SPAN class="token function">upper</SPAN><SPAN class="punctuation token">(</SPAN>CSS_NAME<SPAN class="punctuation token">)</SPAN> LIKE <SPAN class="string token">'千代田区'</SPAN> and <SPAN class="punctuation token">(</SPAN>MOJI<SPAN class="punctuation token">)</SPAN> LIKE <SPAN class="string token">'%平河町2丁目%'</SPAN><SPAN class="line-numbers-rows"><SPAN></SPAN></SPAN>
3.ポリゴンに対して検索をかける
コードの主要部分はここです。Listener を使って検索結果を取得します。
<SPAN class="comment token">// ホストしているポリゴン データに対して検索をかける</SPAN>
val future <SPAN class="operator token">=</SPAN> mServiceFeatureTable<SPAN class="operator token">!</SPAN><SPAN class="operator token">!</SPAN><SPAN class="punctuation token">.</SPAN><SPAN class="token function">queryFeaturesAsync</SPAN><SPAN class="punctuation token">(</SPAN>query<SPAN class="punctuation token">)</SPAN>
future<SPAN class="punctuation token">.</SPAN>addDoneListener <SPAN class="punctuation token">{</SPAN>
<SPAN class="keyword token">try</SPAN> <SPAN class="punctuation token">{</SPAN>
<SPAN class="comment token">// 検索結果全体を取得する</SPAN>
val result <SPAN class="operator token">=</SPAN> future<SPAN class="punctuation token">.</SPAN><SPAN class="keyword token">get</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN>
<SPAN class="comment token">// 結果の検査</SPAN>
<SPAN class="keyword token">if</SPAN> <SPAN class="punctuation token">(</SPAN>result<SPAN class="punctuation token">.</SPAN><SPAN class="token function">iterator</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">.</SPAN><SPAN class="token function">hasNext</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">)</SPAN> <SPAN class="punctuation token">{</SPAN>
<SPAN class="comment token">// 最初の結果を取得して、該当するポリゴンへズームする</SPAN>
val feature <SPAN class="operator token">=</SPAN> result<SPAN class="punctuation token">.</SPAN><SPAN class="token function">iterator</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">.</SPAN><SPAN class="token function">next</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN>
val envelope <SPAN class="operator token">=</SPAN> feature<SPAN class="punctuation token">.</SPAN>geometry<SPAN class="punctuation token">.</SPAN>extent
mMapView<SPAN class="operator token">!</SPAN><SPAN class="operator token">!</SPAN><SPAN class="punctuation token">.</SPAN><SPAN class="token function">setViewpointGeometryAsync</SPAN><SPAN class="punctuation token">(</SPAN>envelope<SPAN class="punctuation token">,</SPAN> <SPAN class="number token">200.0</SPAN><SPAN class="punctuation token">)</SPAN>
<SPAN class="comment token">// ズームしたフィーチャを選択した状態にする</SPAN>
mFeatureLayer<SPAN class="operator token">!</SPAN><SPAN class="operator token">!</SPAN><SPAN class="punctuation token">.</SPAN><SPAN class="token function">selectFeature</SPAN><SPAN class="punctuation token">(</SPAN>feature<SPAN class="punctuation token">)</SPAN>
<SPAN class="punctuation token">}</SPAN> ・・・
<SPAN class="punctuation token">}</SPAN> <SPAN class="keyword token">catch</SPAN> <SPAN class="punctuation token">(</SPAN><SPAN class="token class-name">e</SPAN><SPAN class="punctuation token">:</SPAN> Exception<SPAN class="punctuation token">)</SPAN> <SPAN class="punctuation token">{</SPAN>
Toast<SPAN class="punctuation token">.</SPAN><SPAN class="token function">makeText</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="keyword token">this</SPAN>@MainActivity<SPAN class="punctuation token">,</SPAN> <SPAN class="string token">"フィーチャ検索に失敗しました: "</SPAN> <SPAN class="operator token">+</SPAN> searchString <SPAN class="operator token">+</SPAN> <SPAN class="string token">". Error="</SPAN> <SPAN class="operator token">+</SPAN> e<SPAN class="punctuation token">.</SPAN>message<SPAN class="punctuation token">,</SPAN> Toast<SPAN class="punctuation token">.</SPAN>LENGTH_SHORT<SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">.</SPAN><SPAN class="token function">show</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN>
<SPAN class="punctuation token">}</SPAN>
<SPAN class="punctuation token">}</SPAN><SPAN class="line-numbers-rows"><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN></SPAN>
ラムダとの戦闘後記
ラムダの記述は、それが実装される前の Java コーディングに慣れているととても理解するのが難しいです。
文法を説明している記事や短い用例を端的に表したサンプルを結構みつけることができましたが、実際に自分でやりたいことを交えてコーディングすると何が悪いのかわからないコンパイルエラーに数多くぶつかりました。そして、今回クローズアップしたコードのような一定の処理を交えてラムダを紹介しているブログには行き当たりませんでしたのでこの記事を執筆してみました。
なんとかラムダを取り入れてコードを書くことを目標にして達成?できましたが、ラムダ・無名関数・SAM 変換など、意味や使いどころをわかってクールなコードを書くことを目指していきたいと思います!
関連リンク集
ArcGIS 関連ページ
・ArcGIS for Developers
・ArcGIS for Developers 開発リソース集
・ArcGIS Online
・ArcGIS Open Data