Indexar e consultar dados de localização GeoJSON no Azure Cosmos DB para NoSQL

APLICA-SE A: NoSQL

Os dados geoespaciais no Azure Cosmos DB para NoSQL permitem-lhe armazenar informações de localização e realizar consultas comuns, incluindo, mas não se limitando a:

  • Determinar se uma localização está numa área definida
  • Medir a distância entre duas localizações
  • Determinar se um caminho se cruza com uma localização ou área

Este guia explica o processo de criação de dados geoespaciais, a indexação dos dados e, em seguida, a consulta dos dados num contentor.

Pré-requisitos

Criar política de contentor e indexação

Todos os contentores incluem uma política de indexação predefinida que indexará com êxito dados geoespaciais. Para criar uma política de indexação personalizada, crie uma conta e especifique um ficheiro JSON com a configuração da política. Nesta secção, é utilizado um índice espacial personalizado para um contentor criado recentemente.

  1. Abra um terminal.

  2. Crie uma variável de shell para o nome da sua conta e grupo de recursos do Azure Cosmos DB para NoSQL.

    # Variable for resource group name
    resourceGroupName="<name-of-your-resource-group>"
    
    # Variable for account name
    accountName="<name-of-your-account>"
    
  3. Crie uma nova base de dados com o az cosmosdb sql database createnome cosmicworks .

    az cosmosdb sql database create \
        --resource-group $resourceGroupName \
        --account-name $accountName \
        --name "cosmicworks" \
        --throughput 400
    
  4. Crie um novo ficheiro JSON com o nome index-policy.json e adicione o seguinte objeto JSON ao ficheiro.

    {
      "indexingMode": "consistent",
      "automatic": true,
      "includedPaths": [
        {
          "path": "/*"
        }
      ],
      "excludedPaths": [
        {
          "path": "/\"_etag\"/?"
        }
      ],
      "spatialIndexes": [
        {
          "path": "/location/*",
          "types": [
            "Point",
            "Polygon"
          ]
        }
      ]
    }
    
  5. Utilize az cosmosdb sql container create para criar um novo contentor com o nome locations com um caminho de chave de partição de /region.

    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. Obtenha a cadeia de ligação primária para a conta com az cosmosdb keys list.

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

    Dica

    Para ver todas as cadeias de ligação possíveis para uma conta, utilize az cosmosdb keys list --resource-group $resourceGroupName --name $accountName --type "connection-strings".

  7. Grave a cadeia de ligação. Irá utilizar esta credencial mais adiante neste guia.

Criar a aplicação de consola do SDK .NET

O SDK .NET para o Azure Cosmos DB para NoSQL fornece classes para objetos GeoJSON comuns. Utilize este SDK para simplificar o processo de adição de objetos geográficos ao contentor.

  1. Abra um terminal num diretório vazio.

  2. Crie uma nova aplicação .NET com o dotnet new comando com o modelo de consola .

    dotnet new console
    
  3. Importe o pacote NuGet Microsoft.Azure.Cosmos com o dotnet add package comando .

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

    Aviso

    O Entity Framework não tem atualmente dados espaciais no Azure Cosmos DB para NoSQL. Utilize um dos SDKs do Azure Cosmos DB para NoSQL para suporte GeoJSON fortemente digitado.

  4. Crie o projeto com o dotnet build comando .

    dotnet build
    
  5. Abra o ambiente de programador integrado (IDE) à sua escolha no mesmo diretório que a sua aplicação de consola .NET.

  6. Abra o ficheiro Program.cs recentemente criado e elimine qualquer código existente. Adicione diretivas de utilização para os Microsoft.Azure.Cosmosespaços de nomes , Microsoft.Azure.Cosmos.LinqeMicrosoft.Azure.Cosmos.Spatial .

    using Microsoft.Azure.Cosmos;
    using Microsoft.Azure.Cosmos.Linq;
    using Microsoft.Azure.Cosmos.Spatial;
    
  7. Adicione uma variável de cadeia com o nome *connectionString com a cadeia de ligação que gravou anteriormente neste guia.

    string connectionString = "<your-account-connection-string>"
    
  8. Crie uma nova instância da classe que passa CosmosClientconnectionString e encapsula-a numa instrução de utilização.

    using CosmosClient client = new (connectionString);
    
  9. Obtenha uma referência ao contentor criado anteriormente (cosmicworks/locations) na conta do Azure Cosmos DB para NoSQL com CosmosClient.GetDatabase e, em seguida Database.GetContainer, . Armazene o resultado numa variável chamada container.

    var container = client.GetDatabase("cosmicworks").GetContainer("locations");
    
  10. Guarde o ficheiro Program.cs.

Adicionar dados geoespaciais

O SDK .NET inclui vários tipos no Microsoft.Azure.Cosmos.Spatial espaço de nomes para representar objetos GeoJSON comuns. Estes tipos simplificam o processo de adicionar novas informações de localização a itens num contentor.

  1. Crie um novo ficheiro com o nome Office.cs. No ficheiro, adicione uma diretiva de utilização a Microsoft.Azure.Cosmos.Spatial e, em seguida, crie um Officetipo de registo com estas propriedades:

    Tipo Descrição Valor predefinido
    id string Identificador exclusivo
    nome string Nome do escritório
    localização Point Ponto geográfico GeoJSON
    categoria string Valor da chave de partição business-office
    using Microsoft.Azure.Cosmos.Spatial;
    
    public record Office(
        string id,
        string name,
        Point location,
        string category = "business-office"
    );
    

    Nota

    Este registo inclui uma Point propriedade que representa uma posição específica em GeoJSON. Para obter mais informações, veja Ponto GeoJSON.

  2. Crie outro novo ficheiro com o nome Region.cs. Adicione outro tipo de registo com o nome Region com estas propriedades:

    Tipo Descrição Valor predefinido
    id string Identificador exclusivo
    nome string Nome do escritório
    localização Polygon Forma geográfica GeoJSON
    categoria string Valor da chave de partição business-region
    using Microsoft.Azure.Cosmos.Spatial;
    
    public record Region(
        string id,
        string name,
        Polygon location,
        string category = "business-region"
    );
    

    Nota

    Este registo inclui uma Polygon propriedade que representa uma forma composta por linhas desenhadas entre várias localizações em GeoJSON. Para obter mais informações, veja GeoJSON Polygon.

  3. Crie outro novo ficheiro com o nome Result.cs. Adicione um tipo de registo com o nome Result com estas duas propriedades:

    Tipo Description
    nome string Nome do resultado correspondente
    distanceKilometers decimal Distância em quilómetros
    public record Result(
        string name,
        decimal distanceKilometers
    );
    
  4. Guarde os ficheiros Office.cs, Region.cs e Result.cs .

  5. Abra novamente o ficheiro Program.cs .

  6. Crie um novo Polygon numa variável com o nome 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. Crie uma nova Region variável com o nome mainCampusRegion com o polígono, o identificador 1000exclusivo e o nome Main Campus.

    Region mainCampusRegion = new ("1000", "Main Campus", mainCampusPolygon);
    
  8. Utilize Container.UpsertItemAsync para adicionar a região ao contentor. Escreva as informações da região na consola do .

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

    Dica

    Este guia utiliza upsert em vez de inserir para que possa executar o script várias vezes sem causar um conflito entre identificadores exclusivos. Para obter mais informações sobre operações upsert, veja Criar itens.

  9. Crie uma nova Point variável com o nome headquartersPoint. Utilize essa variável para criar uma nova Office variável chamada headquartersOffice com o ponto, o identificador 0001exclusivo e o nome Headquarters.

    Point headquartersPoint = new (-122.12827, 47.63980);
    Office headquartersOffice = new ("0001", "Headquarters", headquartersPoint);
    
  10. Crie outra Point variável com o nome researchPoint. Utilize essa variável para criar outra Office variável com o nome researchOffice com o ponto correspondente, o identificador 0002exclusivo e o nome Research and Development.

    Point researchPoint = new (-96.84369, 46.81298);
    Office researchOffice = new ("0002", "Research and Development", researchPoint);
    
  11. Crie uma TransactionalBatch para upsert ambas as Office variáveis como uma única transação. Em seguida, escreva as informações de ambos os escritórios na consola do .

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

    Nota

    Para obter mais informações sobre transações, veja Operações de lotes transacionais.

  12. Guarde o ficheiro Program.cs.

  13. Execute a aplicação num terminal com dotnet run. Observe que o resultado da execução da aplicação inclui informações sobre os três itens criados recentemente.

    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 }
    

Consultar dados geoespaciais com a consulta NoSQL

Os tipos no Microsoft.Azure.Cosmos.Spatial espaço de nomes podem ser utilizados como entradas para uma consulta parametrizada NoSQL para utilizar funções incorporadas como ST_DISTANCE.

  1. Abra o ficheiro Program.cs .

  2. Crie uma nova string variável com o nome nosql com a consulta utilizada nesta secção para medir a distância entre pontos.

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

    Dica

    Esta consulta coloca a função geoespacial numa subconsulta para simplificar o processo de reutilização do valor já calculado várias vezes nas SELECT cláusulas e WHERE .

  3. Crie uma nova QueryDefinition variável com o nome query com a nosqlString variável como um parâmetro. Em seguida, utilize o QueryDefinition.WithParameter método fluente várias vezes para adicionar estes parâmetros à consulta:

    Valor
    @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. Crie um novo iterador com Container.GetItemQueryIterator<>, o Result tipo genérico e a query variável . Em seguida, utilize uma combinação de um ciclo de tempo e foreach para iterar todos os resultados em cada página de resultados. Exportar cada resultado para a consola.

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

    Nota

    Para obter mais informações sobre a enumeração dos resultados da consulta, veja itens de consulta.

  5. Guarde o ficheiro Program.cs.

  6. Execute a aplicação novamente num terminal com dotnet run. Observe que o resultado inclui agora os resultados da consulta.

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

Consultar dados geoespaciais com LINQ

A funcionalidade LINQ para NoSQL no SDK .NET suporta a inclusão de tipos geoespaciais nas expressões de consulta. Além disso, o SDK inclui métodos de extensão que mapeiam para funções incorporadas equivalentes:

Método de extensão Função incorporada
Distance() ST_DISTANCE
Intersects() ST_INTERSECTS
IsValid() ST_ISVALID
IsValidDetailed() ST_ISVALIDDETAILED
Within() ST_WITHIN
  1. Abra o ficheiro Program.cs .

  2. Obtenha o Region item do contentor com um identificador exclusivo de 1000 e armazene-o numa variável chamada region.

    Region region = await container.ReadItemAsync<Region>("1000", new PartitionKey("business-region"));
    
  3. Utilize o Container.GetItemLinqQueryable<> método para obter uma consulta LINQ e compile a consulta LINQ fluentemente ao realizar estas três ações:

    1. Utilize o Queryable.Where<> método de extensão para filtrar apenas itens com um category equivalente a "business-office".

    2. Utilize Queryable.Where<> novamente para filtrar apenas as localizações na region propriedade da location variável com Geometry.Within().

    3. Traduza a expressão LINQ para um iterador de feed com CosmosLinqExtensions.ToFeedIterator<>.

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

    Importante

    Neste exemplo, a propriedade de localização do escritório tem um ponto e a propriedade de localização da região tem um polígono. ST_WITHIN está a determinar se o ponto do escritório está dentro do polígono da região.

  4. Utilize uma combinação de um ciclo de tempo e foreach para iterar todos os resultados em cada página de resultados. Exportar cada resultado para a consola.

    while (regionIterator.HasMoreResults)
    {
        var response = await regionIterator.ReadNextAsync();
        foreach (var office in response)
        {
            Console.WriteLine($"[IN REGION]\t{office}");
        }
    }
    
  5. Guarde o ficheiro Program.cs.

  6. Execute a aplicação uma última vez num terminal com dotnet run. Observe que o resultado inclui agora os resultados da segunda consulta baseada em LINQ.

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

Limpar os recursos

Remova a base de dados depois de concluir este guia.

  1. Abra um terminal e crie uma variável de shell para o nome da sua conta e grupo de recursos.

    # Variable for resource group name
    resourceGroupName="<name-of-your-resource-group>"
    
    # Variable for account name
    accountName="<name-of-your-account>"
    
  2. Utilize az cosmosdb sql database delete para remover a base de dados.

    az cosmosdb sql database delete \
        --resource-group $resourceGroupName \
        --account-name $accountName \
        --name "cosmicworks"
    

Passos seguintes