Azure Cosmos DB for NoSQL における GeoJSON の位置情報データのインデックス作成とクエリ

適用対象: NoSQL

Azure Cosmos DB for NoSQL の地理空間データを使用すると、位置情報を格納し、次のような一般的なクエリを実行できます。

  • 定義された領域内に場所があるかどうかを検出する
  • 2 つの場所間の距離を測定する
  • 進路が場所や領域と交差するかどうかを判断する

このガイドでは、地理空間データを作成して、データのインデックスを作成し、コンテナー内のデータに対してクエリを実行するプロセスについて説明します。

前提条件

コンテナーとインデックス作成ポリシーの作成

すべてのコンテナーには、正常に地理空間データのインデックスを作成する既定のインデックス作成ポリシーが含まれています。 カスタマイズされたインデックス作成ポリシーを作成するには、アカウントを作成し、ポリシーの構成で JSON ファイルを指定します。 このセクションでは、新しく作成されたコンテナーにカスタムの空間インデックスを使用します。

  1. ターミナルを開きます。

  2. Azure Cosmos DB for NoSQL アカウントとリソース グループの名前のシェル変数を作成します。

    # Variable for resource group name
    resourceGroupName="<name-of-your-resource-group>"
    
    # Variable for account name
    accountName="<name-of-your-account>"
    
  3. az cosmosdb sql database create を使用して、cosmicworks という新しいデータベースを作成します。

    az cosmosdb sql database create \
        --resource-group "<resource-group-name>" \
        --account-name "<nosql-account-name>" \
        --name "cosmicworks" \
        --throughput 400
    
  4. index-policy.json という新しい JSON ファイルを作成し、次の JSON オブジェクトをファイルに追加します。

    {
      "indexingMode": "consistent",
      "automatic": true,
      "includedPaths": [
        {
          "path": "/*"
        }
      ],
      "excludedPaths": [
        {
          "path": "/\"_etag\"/?"
        }
      ],
      "spatialIndexes": [
        {
          "path": "/location/*",
          "types": [
            "Point",
            "Polygon"
          ]
        }
      ]
    }
    
  5. az cosmosdb sql container create を使用して、/region のパーティション キー パスを持つ、locations という新しいコンテナーを作成します。

    az cosmosdb sql container create \
        --resource-group "<resource-group-name>" \
        --account-name "<nosql-account-name>" \
        --database-name "cosmicworks" \
        --name "locations" \
        --partition-key-path "/category" \
        --idx @index-policy.json
    
  6. 最後に、az cosmosdb show と JMESPath クエリを使用してアカウント エンドポイントを取得します。

    az cosmosdb show \
        --resource-group "<resource-group-name>" \
        --name "<nosql-account-name>" \
        --query "documentEndpoint"
    
  7. 次のセクションで必要になるので、アカウント エンドポイントを記録します。

.NET コンソール アプリケーションの作成

.NET SDK for Azure Cosmos DB for NoSQL には、一般的な GeoJSON オブジェクトのクラスが用意されています。 この SDK を使用して、地理的オブジェクトをコンテナーに追加するプロセスを効率化します。

  1. 空のディレクトリでターミナルを開きます。

  2. dotnet new コマンドとコンソール テンプレートを使用して、新しい .NET アプリケーションを作成します。

    dotnet new console
    
  3. dotnet add package コマンドを使用して、Microsoft.Azure.Cosmos NuGet パッケージをインポートします。

    dotnet add package Microsoft.Azure.Cosmos --version 3.*
    

    警告

    Entity Framework は現在、Azure Cosmos DB for NoSQL の空間データをサポートしていません。 厳密に型指定された GeoJSON のサポートには、Azure Cosmos DB for NoSQL SDK のいずれかを使用します。

  4. Azure.Identity NuGet パッケージをインポートします。

    dotnet add package Azure.Identity --version 1.*
    
  5. dotnet build コマンドを使ってプロジェクトをビルドします。

    dotnet build
    
  6. .NET コンソール アプリケーションと同じディレクトリで、任意の統合開発環境 (IDE) を開きます。

  7. 新しく作成した Program.cs ファイルを開き、既存のコードをすべて削除します。 Microsoft.Azure.CosmosMicrosoft.Azure.Cosmos.LinqMicrosoft.Azure.Cosmos.Spatial の各名前空間に using ディレクティブを追加します。

    using Microsoft.Azure.Cosmos;
    using Microsoft.Azure.Cosmos.Linq;
    using Microsoft.Azure.Cosmos.Spatial;
    
  8. Azure.Identity 名前空間に対する別の using ディレクティブを追加します。

    using Azure.Identity;
    
  9. credential という名前の DefaultAzureCredential 型の新しい変数を作成します。

    DefaultAzureCredential credential = new();
    
  10. Azure Cosmos DB for NoSQL アカウント エンドポイントを含む endpoint という文字列変数を作成します。

    string endpoint = "<nosql-account-endpoint>";
    
  11. connectionString を渡して using ステートメントにラップする CosmosClient クラスの新しいインスタンスを作成します。

    using CosmosClient client = new (connectionString);
    
  12. CosmosClient.GetDatabase の後に Database.GetContainer を使用して、Azure Cosmos DB for NoSQL アカウントで先ほど作成したコンテナー (cosmicworks/locations) への参照を取得します。 結果を container という名前の変数に格納します。

    var container = client.GetDatabase("cosmicworks").GetContainer("locations");
    
  13. Program.cs ファイルを保存します。

地理空間のデータの追加

.NET SDK では、一般的な GeoJSON オブジェクトを表す複数の型が Microsoft.Azure.Cosmos.Spatial 名前空間に含まれています。 これらの型により、コンテナー内の項目に新しい位置情報を追加するプロセスが効率化されます。

  1. Office.cs という名前の新しいファイルを作成します。 そのファイルで、using ディレクティブを Microsoft.Azure.Cosmos.Spatial に追加して、次のプロパティを持つ Office レコード型を作成します。

    Type 説明 既定値
    id string 一意識別子
    name string オフィスの名前
    location Point GeoJSON の地理的位置
    category string パーティション キー値 business-office
    using Microsoft.Azure.Cosmos.Spatial;
    
    public record Office(
        string id,
        string name,
        Point location,
        string category = "business-office"
    );
    

    注意

    このレコードには、GeoJSON 内の特定の位置を表す Point プロパティが含まれています。 詳細については、GeoJSON の Point に関するページを参照してください。

  2. Region.cs という名前の別のファイルを新規作成します。 次のプロパティを使用して、Region という別のレコード型を追加します。

    Type 説明 既定値
    id string 一意識別子
    name string オフィスの名前
    location Polygon GeoJSON の地理的形状
    category string パーティション キー値 business-region
    using Microsoft.Azure.Cosmos.Spatial;
    
    public record Region(
        string id,
        string name,
        Polygon location,
        string category = "business-region"
    );
    

    注意

    このレコードには、GeoJSON 内の複数の場所の間に描画された線で構成される図形を表す Polygon プロパティが含まれています。 詳細については、GeoJSON の Polygon に関するページを参照してください。

  3. Result.cs という名前の別のファイルを新規作成します。 次の 2 つのプロパティを使用して、Result というレコード型を追加します。

    Type 説明
    name string 一致した結果の名前
    distanceKilometers decimal キロメートル単位の距離
    public record Result(
        string name,
        decimal distanceKilometers
    );
    
  4. Office.csRegion.csResult.cs の各ファイルを保存します。

  5. Program.cs ファイルをもう一度開きます。

  6. mainCampusPolygon という名前の変数に、新しい Polygon を作成します。

    Polygon mainCampusPolygon = new (
        new []
        {
            new LinearRing(new [] {
                new Position(-122.13237, 47.64606),
                new Position(-122.13222, 47.63376),
                new Position(-122.11841, 47.64175),
                new Position(-122.12061, 47.64589),
                new Position(-122.13237, 47.64606),
            })
        }
    );
    
  7. Polygon を使用した mainCampusRegion という名前の新しい Region 変数、一意識別子の 1000Main Campus という名前を作成します。

    Region mainCampusRegion = new ("1000", "Main Campus", mainCampusPolygon);
    
  8. Container.UpsertItemAsync を使用してコンテナーにリージョンを追加します。 リージョンの情報をコンソールに書き込みます。

    await container.UpsertItemAsync<Region>(mainCampusRegion);
    Console.WriteLine($"[UPSERT ITEM]\t{mainCampusRegion}");
    

    ヒント

    このガイドでは、一意の識別子間の競合を引き起こさずにスクリプトを複数回実行できるように、insert ではなく upsert を使用します。 upsert 操作の詳細については、項目の作成に関するページを参照してください。

  9. headquartersPoint という名前の新しい Point 変数を作成します。 その変数を使用し、Point、一意識別子の 0001Headquarters という名前を使って、headquartersOffice という新しい Office 変数を作成します。

    Point headquartersPoint = new (-122.12827, 47.63980);
    Office headquartersOffice = new ("0001", "Headquarters", headquartersPoint);
    
  10. researchPoint という名前の別の Point 変数を作成します。 その変数を使用し、対応する Point、一意識別子の 0002Research and Development という名前を使って、researchOffice という別の Office 変数を作成します。

    Point researchPoint = new (-96.84369, 46.81298);
    Office researchOffice = new ("0002", "Research and Development", researchPoint);
    
  11. TransactionalBatch を作成し、両方の Office 変数を単一のトランザクションとしてアップサートします。 その後、両方のオフィスの情報をコンソールに書き込みます。

    TransactionalBatch officeBatch = container.CreateTransactionalBatch(new PartitionKey("business-office"));
    officeBatch.UpsertItem<Office>(headquartersOffice);
    officeBatch.UpsertItem<Office>(researchOffice);
    await officeBatch.ExecuteAsync();
    
    Console.WriteLine($"[UPSERT ITEM]\t{headquartersOffice}");
    Console.WriteLine($"[UPSERT ITEM]\t{researchOffice}");
    

    注意

    トランザクションの詳細については、トランザクション バッチの操作に関するページを参照してください。

  12. Program.cs ファイルを保存します。

  13. dotnet run を使用して、ターミナルでアプリケーションを実行します。 アプリケーションの実行の出力に、新たに作成された 3 つの項目に関する情報が含まれていることを確認します。

    dotnet run
    
    [UPSERT ITEM]   Region { id = 1000, name = Main Campus, location = Microsoft.Azure.Cosmos.Spatial.Polygon, category = business-region }
    [UPSERT ITEM]   Office { id = 0001, name = Headquarters, location = Microsoft.Azure.Cosmos.Spatial.Point, category = business-office }
    [UPSERT ITEM]   Office { id = 0002, name = Research and Development, location = Microsoft.Azure.Cosmos.Spatial.Point, category = business-office }
    

NoSQL クエリを使用した地理空間データに対するクエリの実行

Microsoft.Azure.Cosmos.Spatial 名前空間の型は、ST_DISTANCE などの組み込み関数を使うために、NoSQL のパラメーター化クエリへの入力として使用できます。

  1. Program.cs ファイルを開きます。

  2. このセクションでは、Point 間の距離を測定するために、クエリを使って nosql という名前の新しい string 変数を作成します。

    string nosqlString = @"
        SELECT
            o.name,
            NumberBin(distanceMeters / 1000, 0.01) AS distanceKilometers
        FROM
            offices o
        JOIN
            (SELECT VALUE ROUND(ST_DISTANCE(o.location, @compareLocation))) AS distanceMeters
        WHERE
            o.category = @partitionKey AND
            distanceMeters > @maxDistance
    ";
    

    ヒント

    このクエリでは、地理空間関数をサブクエリ内に配置し、SELECT 句と WHERE 句で既に計算された値を複数回再使用するプロセスを簡略化します。

  3. nosqlString 変数をパラメーターとして使用して、query という名前の新しい QueryDefinition 変数を作成します。 その後、QueryDefinition.WithParameter という fluent メソッドを複数回使用して、これらのパラメーターをクエリに追加します。

    @maxDistance 2000
    @partitionKey "business-office"
    @compareLocation new Point(-122.11758, 47.66901)
    var query = new QueryDefinition(nosqlString)
        .WithParameter("@maxDistance", 2000)
        .WithParameter("@partitionKey", "business-office")
        .WithParameter("@compareLocation", new Point(-122.11758, 47.66901));
    
  4. Container.GetItemQueryIterator<>Result ジェネリック型、query 変数を使用して、新しい反復子を作成します。 その後、while ループと foreach ループの組み合わせを使用して、結果の各ページ内のすべての結果を反復処理します。 それぞれの結果をコンソールに出力します。

    var distanceIterator = container.GetItemQueryIterator<Result>(query);
    while (distanceIterator.HasMoreResults)
    {
        var response = await distanceIterator.ReadNextAsync();
        foreach (var result in response)
        {
            Console.WriteLine($"[DISTANCE KM]\t{result}");
        }
    }
    

    注意

    クエリ結果の列挙の詳細については、クエリ項目に関するページを参照してください。

  5. Program.cs ファイルを保存します。

  6. dotnet run を使用して、ターミナルでアプリケーションを再実行します。 出力にクエリの結果が含まれていることを確認します。

    dotnet run
    
    [DISTANCE KM]   Result { name = Headquarters, distanceKilometers = 3.34 }
    [DISTANCE KM]   Result { name = Research and Development, distanceKilometers = 1907.43 }
    

LINQ を使用した地理空間データに対するクエリの実行

.NET SDK の LINQ to NoSQL 機能では、クエリ式に地理空間型を含めることができます。 さらに、この SDK には、同等の組み込み関数にマッピングされる次のような拡張メソッドが含まれています。

拡張メソッド 組み込み関数
Distance() ST_DISTANCE
Intersects() ST_INTERSECTS
IsValid() ST_ISVALID
IsValidDetailed() ST_ISVALIDDETAILED
Within() ST_WITHIN
  1. Program.cs ファイルを開きます。

  2. 1000 の一意識別子を持つ項目をコンテナーから Region 項目を取得して、region という変数に格納します。

    Region region = await container.ReadItemAsync<Region>("1000", new PartitionKey("business-region"));
    
  3. Container.GetItemLinqQueryable<> メソッドを使用し、LINQ をクエリ可能にしてから、次の 3 つのアクションを実行して LINQ クエリを円滑に構築します。

    1. Queryable.Where<> 拡張メソッドを使用して、"business-office" と同等の category を持つ項目のみをフィルター処理する。

    2. Queryable.Where<> を再使用し、Geometry.Within() を使って region 変数の location プロパティ内の場所のみをフィルター処理する。

    3. CosmosLinqExtensions.ToFeedIterator<> を使用して、LINQ 式をフィード反復子に変換する。

    var regionIterator = container.GetItemLinqQueryable<Office>()
        .Where(o => o.category == "business-office")
        .Where(o => o.location.Within(region.location))
        .ToFeedIterator<Office>();
    

    重要

    この例では、オフィスの location プロパティに Point があり、リージョンの location プロパティに Polygon があります。 ST_WITHIN では、オフィスの Point が当該のリージョンの Polygon 内にあるかどうかを判断します。

  4. while ループと foreach ループの組み合わせを使用して、結果の各ページ内のすべての結果を反復処理します。 それぞれの結果をコンソールに出力します。

    while (regionIterator.HasMoreResults)
    {
        var response = await regionIterator.ReadNextAsync();
        foreach (var office in response)
        {
            Console.WriteLine($"[IN REGION]\t{office}");
        }
    }
    
  5. Program.cs ファイルを保存します。

  6. dotnet run を使用し、ターミナルでアプリケーションを最後に 1 回実行します。 出力に 2 つ目の LINQ ベースのクエリに関する結果が含まれていることを確認します。

    dotnet run
    
    [IN REGION]     Office { id = 0001, name = Headquarters, location = Microsoft.Azure.Cosmos.Spatial.Point, category = business-office }
    

リソースをクリーンアップする

このガイドを完了したら、データベースを削除します。

  1. ターミナルを開き、アカウントとリソース グループの名前のシェル変数を作成します。

    # Variable for resource group name
    resourceGroupName="<name-of-your-resource-group>"
    
    # Variable for account name
    accountName="<name-of-your-account>"
    
  2. az cosmosdb sql database delete を使用してデータベースを削除します。

    az cosmosdb sql database delete \
        --resource-group "<resource-group-name>" \
        --account-name "<nosql-account-name>" \
        --name "cosmicworks"
    

次のステップ