次の方法で共有


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. cosmicworks を使用して、az cosmosdb sql database create という新しいデータベースを作成します。

    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 を使用して、locations のパーティション キー パスを持つ、/region という新しいコンテナーを作成します。

    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. Microsoft.Azure.Cosmos コマンドを使用して、dotnet add package 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.Linq の各名前空間に Microsoft.Azure.Cosmos.Spatialを追加します。

    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. 新しいCosmosClientクラスのインスタンスを作成し、connectionStringを渡してusing ステートメントでラップします。

    using CosmosClient client = new (connectionString);
    
  12. cosmicworks/locations の後に CosmosClient.GetDatabase を使用して、Azure Cosmos DB for NoSQL アカウントで先ほど作成したコンテナー (Database.GetContainer) への参照を取得します。 結果を 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レコード型を作成します。

    説明 既定値
    id string 一意識別子
    name string オフィスの名前
    ___location Point GeoJSON の地理的位置
    カテゴリー 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 という別のレコード型を追加します。

    タイプ 説明 既定値
    id string 一意識別子
    name string オフィスの名前
    ___location Polygon GeoJSON の地理的形状
    カテゴリ 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 というレコード型を追加します。

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

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

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

    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、固有識別子 1000、および名前 Main Campus を使用して、Region という名前の新しい mainCampusRegion 変数を作成します。

    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. Point という名前の新しい headquartersPoint 変数を作成します。 その変数を使用し、headquartersOffice という名前の新しい Office 変数を、ポイント、一意識別子 0001、および名前 Headquarters を用いて作成します。

    Point headquartersPoint = new (-122.12827, 47.63980);
    Office headquartersOffice = new ("0001", "Headquarters", headquartersPoint);
    
  10. Point という名前の別の researchPoint 変数を作成します。 その変数を使用して、対応するポイントと一意識別子 0002 そして名前 Research 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 間の距離を測定するために、クエリを使って string という名前の新しい nosql 変数を作成します。

    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. QueryDefinition 変数をパラメーターとして使用して、query という名前の新しい nosqlString 変数を作成します。 その後、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<> 拡張メソッドを使用して、category と同等の "business-office" を持つ項目のみをフィルター処理する。

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

    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"
    

次のステップ