你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

索引和查询 Azure Cosmos DB for NoSQL 中的 GeoJSON 位置数据

适用范围: NoSQL

通过 Azure Cosmos DB for NoSQL 中的地理空间数据,可存储位置信息并执行常见查询,这包括但不限于:

  • 查找某个位置是否在定义的区域内
  • 测量两个位置之间的距离
  • 确定某条道路是否横穿过某个位置或区域

本指南逐步讲解创建地理空间数据、索引数据,然后在容器中查询数据的过程。

先决条件

创建容器和索引策略

所有容器都包含一个默认索引策略,该策略将成功地索引地理空间数据。 若要创建自定义索引策略,请创建一个帐户,并使用策略的配置指定 JSON 文件。 在本部分中,对新创建的容器使用自定义空间索引。

  1. 打开终端。

  2. 创建一个 shell 变量来存储 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 $resourceGroupName \
        --account-name $accountName \
        --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 新建一个分区键路径为 /regionlocations 容器。

    az cosmosdb sql container create \
        --resource-group $resourceGroupName \
        --account-name $accountName \
        --database-name "cosmicworks" \
        --name "locations" \
        --partition-key-path "/category" \
        --idx @index-policy.json
    
  6. 使用 az cosmosdb keys list 检索帐户的主要连接字符串。

    az cosmosdb keys list \
        --resource-group $resourceGroupName \
        --name $accountName \
        --type "connection-strings" \
        --query "connectionStrings[?keyKind == \`Primary\`].connectionString" \
        --output tsv
    

    提示

    若要查看帐户的所有可能的连接字符串,请使用 az cosmosdb keys list --resource-group $resourceGroupName --name $accountName --type "connection-strings"

  7. 记录连接字符串。 本指南稍后将使用此凭据。

创建 .NET SDK 控制台应用程序

.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.*
    

    警告

    实体框架目前不支持 Azure Cosmos DB for NoSQL 中的空间数据。 使用其中一个 Azure Cosmos DB for NoSQL SDK 来获取强类型的 GeoJSON 支持。

  4. 使用 dotnet build 命令生成项目。

    dotnet build
    
  5. 在 .NET 控制台应用程序所在的同一目录中打开所选的集成开发人员环境 (IDE)。

  6. 打开新创建的 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;
    
  7. 使用本指南前面记录的连接字符串添加名为 *connectionString 的字符串变量。

    string connectionString = "<your-account-connection-string>"
    
  8. 创建 CosmosClient 类的新实例,从而传入 connectionString 并将其包装在 using 语句中。

    using CosmosClient client = new (connectionString);
    
  9. 通过使用 CosmosClient.GetDatabase,然后使用 Database.GetContainer,在 Azure Cosmos DB for NoSQL 帐户中检索对先前创建的容器 (cosmicworks/locations) 的引用。 将结果存储在名为 container 的变量中。

    var container = client.GetDatabase("cosmicworks").GetContainer("locations");
    
  10. 保存 Program.cs 文件。

添加地理空间数据

.NET SDK 在 Microsoft.Azure.Cosmos.Spatial 命名空间中包含多个类型来表示常见的 GeoJSON 对象。 这些类型简化了向容器中的项添加新位置信息的过程。

  1. 创建一个名为 Office.cs 的新文件。 在该文件中,将 using 指令添加到 Microsoft.Azure.Cosmos.Spatial,然后使用以下属性创建 Office记录类型

    类型 说明 默认值
    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"
    );
    

    注意

    此记录包含一个 Point 属性,该属性表示在 GeoJSON 中的特定位置。 有关详细信息,请参阅 GeoJSON 点

  2. 另外新建一个名为 Region.cs 的文件。 使用以下属性另外添加一个名为 Region 的记录类型:

    类型 说明 默认值
    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"
    );
    

    注意

    此记录包含一个 Polygon 属性,该属性表示由在 GeoJSON 中的多个位置之间绘制的线条组成的形状。 有关详细信息,请参阅 GeoJSON 多边形

  3. 另外新建一个名为 Result.cs 的文件。 添加包含下面两个属性的名为 Result 的记录类型:

    类型 说明
    name string 匹配结果的名称
    distanceKilometers decimal 距离(以公里为单位)
    public record Result(
        string name,
        decimal distanceKilometers
    );
    
  4. 保存 Office.cs、Region.cs 和 Result.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. 使用多边形、唯一标识符 1000 和名称 Main Campus 新建一个名为 mainCampusRegionRegion 变量。

    Region mainCampusRegion = new ("1000", "Main Campus", mainCampusPolygon);
    
  8. 使用 Container.UpsertItemAsync 将区域添加到容器。 将区域的信息写入控制台。

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

    提示

    本指南使用“更新插入”而不是“插入”,因此可以多次运行脚本,而不会导致唯一标识符之间出现冲突。 有关更新插入操作的详细信息,请参阅创建项

  9. 新建一个名为 headquartersPointPoint 变量。 通过该变量,使用点、唯一标识符 0001 和名称 Headquarters 新建一个名为 headquartersOfficeOffice 变量。

    Point headquartersPoint = new (-122.12827, 47.63980);
    Office headquartersOffice = new ("0001", "Headquarters", headquartersPoint);
    
  10. 另外创建一个名为 researchPointPoint 变量。 通过该变量,使用相应的点、唯一标识符 0002 和名称 Research and Development 另外创建一个名为 researchOfficeOffice 变量。

    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 命名空间中的类型可用作 NoSQL 参数化查询的输入,以使用 ST_DISTANCE 等内置函数。

  1. 打开 Program.cs 文件。

  2. 使用在本部分中所用的查询新建一个名为 nosqlstring 变量,来测量点之间的距离。

    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
    ";
    

    提示

    此查询将地理空间函数置于子查询中,来简化在 SELECTWHERE 子句中多次重用已计算的值的过程。

  3. 使用 nosqlString 变量作为参数,新建一个名为 queryQueryDefinition 变量。 然后,多次使用 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<>,通过 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>();
    

    重要

    在此示例中,办公室的位置属性具有点,区域的位置属性具有多边形。 ST_WITHIN 确定办公室的点是否在该区域的多边形内。

  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 在终端中最后运行一次应用程序。 请注意,输出现在包含第二个基于 LINQ 的查询的结果。

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

清理资源

完成本指南后,请移除你的数据库。

  1. 打开终端,创建一个 shell 变量来存储帐户和资源组的名称。

    # 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 $resourceGroupName \
        --account-name $accountName \
        --name "cosmicworks"
    

后续步骤