しばやん雑記

ASP.NET とメイドさんが大好きなフリーランスのプログラマーのブログ

Azure Search で位置情報を利用したフィルタリングやソートを試してみた

Azure Search ではインデックスの作成時に WithGeographyPointField メソッドで指定することで、検索インデックスに緯度経度からなる位置情報を持たせることが出来ます。

var client = new IndexManagementClient(connection);

await client.CreateIndexAsync(new Index("items")
    .WithStringField("id", p => p.IsKey().IsRetrievable())
    .WithStringField("name", p => p.IsSearchable().IsRetrievable())
    .WithGeographyPointField("location", p => p.IsFilterable().IsSortable().IsRetrievable())
);

ちなみに Azure Search では文字列と文字列のコレクションのみ検索対象に出来るので、この場合 location フィールドに IsSearchable メソッドを追加するとインデックスの作成に失敗します。

位置情報に関して、インデックスへデータを追加する場合には GeoJSON 形式で指定する必要があります。残念ながら RedDog.Search では緯度経度を表すクラスなどは用意されていないため、匿名型を使って GeoJSON としてシリアライズされる形で指定します。

var client = new IndexManagementClient(connection);

var operations = new[]
{
    new IndexOperation(IndexOperationType.Upload, "id", "1")
        .WithProperty("name", "シグマコンサルティング")
        .WithProperty("location", new { type = "Point", coordinates = new[] { 139.782007, 35.684080 } }),
    new IndexOperation(IndexOperationType.Upload, "id", "2")
        .WithProperty("name", "pnop")
        .WithProperty("location", new { type = "Point", coordinates = new[] { 139.740642, 35.621045 } }),
    new IndexOperation(IndexOperationType.Upload, "id", "3")
        .WithProperty("name", "スカイコード")
        .WithProperty("location", new { type = "Point", coordinates = new[] { 139.782007, 35.684080 } }),
    new IndexOperation(IndexOperationType.Upload, "id", "4")
        .WithProperty("name", "ソーシャルグリッド")
        .WithProperty("location", new { type = "Point", coordinates = new[] { 139.784001, 35.685986 } })
};

await client.PopulateAsync("items", operations);

データ追加時の注意点は、GeoJSON では経度,緯度の順番で格納する必要があることです。

これでインデックスの準備が完了したので、フィルタリングとソートで別々にコードを見ていきます。

フィルタリング

よくお店の検索などで見かける、現在地から 1km 以内のスポットを探すといったときに使える機能です。フィルタの内部で使える式に関しては、カスタマイズされた OData のシンタックスが使えます。

OData Expression Syntax for Azure Search

geo.distance 関数を使うと 2 点間の距離を計算できるので、その結果でフィルタリングを行います。

var client = new IndexQueryClient(connection);

var result = await client.SearchAsync("items", new SearchQuery()
    .Filter("geo.distance(location, geography'POINT(139.766118 35.681633)') le 1.5")
);

foreach (var item in result.Body.Records)
{
    Console.WriteLine("{0} - {1},{2}", item.Score, item.Properties["name"], item.Properties["location"]);
}

今回は東京駅から 1.5km 圏内にあるスポットだけ表示するようにしてみます。

f:id:shiba-yan:20140823113609p:plain

ちょっと結果画像は切れてしまいましたが、1.5km 圏内にある 2 つのスポットだけが出力されました。

ソート

これも現在地から一番近いスポットを探すといったときに使える機能です。使う式に関してはフィルタリングの時とほぼ同じですが、ソートでは並び順を指定することが出来ます。

var client = new IndexQueryClient(connection);

var result = await client.SearchAsync("items", new SearchQuery()
    .OrderBy("geo.distance(location, geography'POINT(139.766118 35.681633)') desc")
);

foreach (var item in result.Body.Records)
{
    Console.WriteLine("{0} - {1},{2}", item.Score, item.Properties["name"], item.Properties["location"]);
}

今回は東京駅から距離が離れている順にソートするようにしてみます。

f:id:shiba-yan:20140823114042p:plain

しっかりと距離が離れている順にソートを行って出力することが出来ました。

今回は距離だけの簡単な使い方でしたが、位置情報としては指定したエリア内に含まれているかどうかといったフィルタリングも可能なので、複雑な検索も行えるようになっています。