Indexar e consultar dados de localização GeoJSON no Azure Cosmos DB for NoSQL
APLICA-SE A: NoSQL
Os dados geoespaciais no Azure Cosmos DB for NoSQL permitem armazenar informações de localização e executar consultas comuns, incluindo, mas não se limitando a:
- Descobrir se um local está dentro de uma área definida
- Medir a distância entre dois locais
- Determinar se um caminho se cruza com um local ou uma área
Esse guia orienta você ao longo do processo de criar dados geoespaciais, indexar os dados e, em seguida, consultar os dados em um contêiner.
Pré-requisitos
- Uma conta do Azure Cosmos DB for NoSQL.
- Se não tiver uma assinatura do Azure, Experimente o Azure Cosmos DB for NoSQL gratuitamente.
- Se tiver uma assinatura existente do Azure, crie uma conta nova do Azure Cosmos DB for NoSQL.
- Versão mais recente do .NET.
- Versão mais recente da CLI do Azure.
- Se estiver usando uma instalação local, entre com a CLI do Azure usando o comando
az login
.
- Se estiver usando uma instalação local, entre com a CLI do Azure usando o comando
Criar um contêiner e uma política de indexação
Todos os contêineres incluem uma política de indexação padrão que irá indexar os dados geoespaciais com sucesso. Para criar uma política de indexação personalizada, crie uma conta e especifique um arquivo JSON com a configuração da política. Nesta seção, um índice espacial personalizado é usado para um contêiner recém-criado.
Abra um terminal.
Crie uma variável de shell para o nome da conta e do grupo de recursos do seu 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>"
Crie um novo banco de dados chamado
cosmicworks
usandoaz cosmosdb sql database create
.az cosmosdb sql database create \ --resource-group "<resource-group-name>" \ --account-name "<nosql-account-name>" \ --name "cosmicworks" \ --throughput 400
Crie um novo arquivo JSON chamado index-policy.json e adicione ao arquivo o objeto JSON a seguir.
{ "indexingMode": "consistent", "automatic": true, "includedPaths": [ { "path": "/*" } ], "excludedPaths": [ { "path": "/\"_etag\"/?" } ], "spatialIndexes": [ { "path": "/location/*", "types": [ "Point", "Polygon" ] } ] }
Use um
az cosmosdb sql container create
para criar um novo contêiner chamadolocations
com um caminho de chave de partição de/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
Para terminar, obtenha o ponto de extremidade de conta para a sua conta usando
az cosmosdb show
e uma consulta JMESPath.az cosmosdb show \ --resource-group "<resource-group-name>" \ --name "<nosql-account-name>" \ --query "documentEndpoint"
Anote o ponto de extremidade da conta porque você vai precisar dele na próxima seção.
Criar um SDK de aplicativo de console do .NET
O SDK do .NET para o Azure Cosmos DB for NoSQL fornece classes para objetos GeoJSON comuns. Use esse SDK para simplificar o processo de adicionar objetos geográficos ao seu contêiner.
Abra um terminal em um diretório vazio.
Crie um novo aplicativo .NET usando o comando
dotnet new
com o modelo de console .dotnet new console
Importe o pacote NuGet
Microsoft.Azure.Cosmos
usando o comandodotnet add package
.dotnet add package Microsoft.Azure.Cosmos --version 3.*
Aviso
Atualmente, o Entity Framework não dá suporte a dados espaciais no Azure Cosmos DB for NoSQL. Use um dos SDKs do Azure Cosmos DB for NoSQL para obter um suporte fortemente tipado ao GeoJSON.
Importe o pacote NuGet
Azure.Identity
.dotnet add package Azure.Identity --version 1.*
Compile o projeto com o
dotnet build
comando.dotnet build
Abra o ambiente de desenvolvedor integrado (IDE) de sua escolha no mesmo diretório que o seu aplicativo de console do .NET.
Abra o arquivo Program.cs recém-criado e exclua qualquer código existente. Adicione o uso de diretivas para os namespaces
Microsoft.Azure.Cosmos
,Microsoft.Azure.Cosmos.Linq
eMicrosoft.Azure.Cosmos.Spatial
.using Microsoft.Azure.Cosmos; using Microsoft.Azure.Cosmos.Linq; using Microsoft.Azure.Cosmos.Spatial;
Adicione outra diretiva de uso para o namespace
Azure.Identity
.using Azure.Identity;
Crie uma nova variável do tipo
credential
chamadaDefaultAzureCredential
.DefaultAzureCredential credential = new();
Crie uma variável de cadeia de caracteres chamada
endpoint
com o ponto de extremidade da sua conta do Azure Cosmos DB for NoSQL.string endpoint = "<nosql-account-endpoint>";
Crie uma nova instância da classe
CosmosClient
transmitindo-a paraconnectionString
e encapsulando-a em uma instrução using.using CosmosClient client = new (connectionString);
Recupere uma referência ao contêiner criado anteriormente (
cosmicworks/locations
) na conta do Azure Cosmos DB for NoSQL usandoCosmosClient.GetDatabase
e, em seguida,Database.GetContainer
. Armazene o resultado em uma variável chamadacontainer
.var container = client.GetDatabase("cosmicworks").GetContainer("locations");
Salve o arquivo Program.cs.
Adicionar dados geoespaciais
O SDK do .NET inclui vários tipos no namespace Microsoft.Azure.Cosmos.Spatial
para representar objetos GeoJSON comuns. Esses tipos simplificam o processo de adicionar novas informações de localização aos itens em um contêiner.
Crie um novo arquivo chamado Office.cs. No arquivo, adicione uma diretiva using a
Microsoft.Azure.Cosmos.Spatial
e crie umOffice
tipo de registro com essas propriedades:Type Descrição Valor padrão id string
Identificador exclusivo name string
Nome do escritório local Point
Ponto geográfico GeoJSON category 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" );
Observação
Esse registro inclui uma propriedade
Point
que representa uma posição específica no GeoJSON. Para obter mais informações, confira Ponto GeoJSON.Crie outro arquivo novo chamado Category.cs. Adicione outro tipo de registro chamado
Region
com essas propriedades:Type Descrição Valor padrão id string
Identificador exclusivo name string
Nome do escritório local Polygon
Forma geográfica GeoJSON category 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" );
Observação
Esse registro inclui uma propriedade
Polygon
que representa uma forma composta de linhas desenhadas entre diversos locais no GeoJSON. Para obter mais informações, confira Polígono GeoJSON.Crie outro arquivo novo chamado Result.cs. Adicione um tipo de registro chamado
Result
com essas duas propriedades:Type Descrição name string
Nome do resultado equiparado distanceKilometers decimal
Distância em quilômetros public record Result( string name, decimal distanceKilometers );
Salve os arquivos Office.cs, Region.cs e Result.cs.
Abra o arquivo Program.cs outra vez.
Crie um novo
Polygon
em uma variável chamadamainCampusPolygon
.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), }) } );
Crie uma nova variável
Region
chamadamainCampusRegion
usando o polígono, o identificador único1000
e o nomeMain Campus
.Region mainCampusRegion = new ("1000", "Main Campus", mainCampusPolygon);
Use
Container.UpsertItemAsync
para adicionar a região ao contêiner. Escreva as informações da região no console.await container.UpsertItemAsync<Region>(mainCampusRegion); Console.WriteLine($"[UPSERT ITEM]\t{mainCampusRegion}");
Dica
Este guia usa upsert em vez de inserir para que você possa executar o script várias vezes sem causar um conflito entre os identificadores únicos. Para obter mais informações sobre operações upsert, consulte como criar itens.
Crie uma nova variável
Point
chamadaheadquartersPoint
. Use essa variável para criar uma nova variávelOffice
chamadaheadquartersOffice
usando o ponto, o identificador único0001
e o nomeHeadquarters
.Point headquartersPoint = new (-122.12827, 47.63980); Office headquartersOffice = new ("0001", "Headquarters", headquartersPoint);
Crie outra
Point
variável chamadaresearchPoint
. Use essa variável para criar uma outra variávelOffice
chamadaresearchOffice
usando o respectivo ponto, o identificador único0002
e o nomeResearch and Development
.Point researchPoint = new (-96.84369, 46.81298); Office researchOffice = new ("0002", "Research and Development", researchPoint);
Crie um
TransactionalBatch
para fazer upsert de ambas as variáveisOffice
como uma única transação. Em seguida, grave as informações dos dois escritórios no console.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}");
Observação
Para obter mais informações sobre transações, confira operações em lotes transacionais.
Salve o arquivo Program.cs.
Execute o aplicativo em um terminal usando
dotnet run
. Observe que a saída da execução do aplicativo inclui informações sobre os três itens recém-criados.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 usando a consulta NoSQL
Os tipos no namespace Microsoft.Azure.Cosmos.Spatial
podem ser usados como entradas de dados para uma consulta parametrizada NoSQL para usar funções integradas como ST_DISTANCE
.
Abra o arquivo Program.cs.
Criar uma nova variável
string
chamadanosql
com a consulta é usado nesta seçã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
Essa consulta coloca a função geoespacial dentro de uma subconsulta para simplificar o processo de reutilização do valor já calculado várias vezes nas cláusulas
SELECT
eWHERE
.Crie uma nova variável
QueryDefinition
chamadaquery
usando a variávelnosqlString
como um parâmetro. Em seguida, use o método fluenteQueryDefinition.WithParameter
várias vezes para adicionar esses 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));
Crie um novo iterador usando
Container.GetItemQueryIterator<>
, o tipo genéricoResult
e a variávelquery
. Em seguida, use uma combinação de um loop while e foreach para iterar todos os resultados em cada página de resultados. Gere cada resultado para o console.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}"); } }
Observação
Para obter mais informações sobre como enumerar os resultados da consulta, confira itens de consulta.
Salve o arquivo Program.cs.
Execute o aplicativo em um terminal usando
dotnet run
. Observe que a saída agora inclui 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 usando LINQ
A funcionalidade LINQ to NoSQL no SDK do .NET é compatível com a inclusão de tipos geoespaciais nas expressões de consulta. Mais ainda, o SDK inclui métodos de extensão que são mapeados para funções integradas equivalentes:
Método de extensão | Função integrada |
---|---|
Distance() |
ST_DISTANCE |
Intersects() |
ST_INTERSECTS |
IsValid() |
ST_ISVALID |
IsValidDetailed() |
ST_ISVALIDDETAILED |
Within() |
ST_WITHIN |
Abra o arquivo Program.cs.
Recupere o item
Region
do contêiner com um identificador único de1000
e o armazene em uma variável chamadaregion
.Region region = await container.ReadItemAsync<Region>("1000", new PartitionKey("business-region"));
Use o método
Container.GetItemLinqQueryable<>
para obter um LINQ consultável e crie a consulta LINQ fluentemente executando essas três ações:Use o método de extensão
Queryable.Where<>
para filtrar apenas para itens com umacategory
equivalente a"business-office"
.Use
Queryable.Where<>
novamente para filtrar apenas para locais dentro da propriedadelocation
da variável daregion
usandoGeometry.Within()
.Traduza a expressão LINQ em um iterador de feed usando
CosmosLinqExtensions.ToFeedIterator<>
.
var regionIterator = container.GetItemLinqQueryable<Office>() .Where(o => o.category == "business-office") .Where(o => o.location.Within(region.location)) .ToFeedIterator<Office>();
Importante
Nesse 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á determinando se o ponto do escritório está dentro do polígono da região.Use uma combinação de um loop while e foreach para iterar todos os resultados em cada página de resultados. Gere cada resultado para o console.
while (regionIterator.HasMoreResults) { var response = await regionIterator.ReadNextAsync(); foreach (var office in response) { Console.WriteLine($"[IN REGION]\t{office}"); } }
Salve o arquivo Program.cs.
Execute o aplicativo uma última vez em um terminal usando
dotnet run
. Observe que a saída agora inclui 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 seu banco de dados após concluir este guia.
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>"
Use
az cosmosdb sql database delete
para remover o banco de dados.az cosmosdb sql database delete \ --resource-group "<resource-group-name>" \ --account-name "<nosql-account-name>" \ --name "cosmicworks"