Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
APPLIES TO:
NoSQL
Geospatial data in Azure Cosmos DB for NoSQL allows you to store location information and perform common queries, including but not limited to:
- Finding if a location is within a defined area
- Measuring the distance between two locations
- Determining if a path intersects with a location or area
This guide walks through the process of creating geospatial data, indexing the data, and then querying the data in a container.
Prerequisites
- An existing Azure Cosmos DB for NoSQL account.
- If you don't have an Azure subscription, Try Azure Cosmos DB for NoSQL free.
- If you have an existing Azure subscription, create a new Azure Cosmos DB for NoSQL account.
- Latest version of .NET.
- Latest version of Azure CLI.
- If you're using a local installation, sign in to the Azure CLI by using the
az logincommand.
- If you're using a local installation, sign in to the Azure CLI by using the
Create container and indexing policy
All containers include a default indexing policy that will successfully index geospatial data. To create a customized indexing policy, create an account and specify a JSON file with the policy's configuration. In this section, a custom spatial index is used for a newly created container.
Open a terminal.
Create a shell variable for the name of your Azure Cosmos DB for NoSQL account and resource group.
# Variable for resource group name resourceGroupName="<name-of-your-resource-group>" # Variable for account name accountName="<name-of-your-account>"Create a new database named
cosmicworksusingaz cosmosdb sql database create.az cosmosdb sql database create \ --resource-group "<resource-group-name>" \ --account-name "<nosql-account-name>" \ --name "cosmicworks" \ --throughput 400Create a new JSON file named index-policy.json and add the following JSON object to the file.
{ "indexingMode": "consistent", "automatic": true, "includedPaths": [ { "path": "/*" } ], "excludedPaths": [ { "path": "/\"_etag\"/?" } ], "spatialIndexes": [ { "path": "/location/*", "types": [ "Point", "Polygon" ] } ] }Use
az cosmosdb sql container createto create a new container namedlocationswith a partition key path of/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.jsonFinally, get the account endpoint for your account using
az cosmosdb showand a JMESPath query.az cosmosdb show \ --resource-group "<resource-group-name>" \ --name "<nosql-account-name>" \ --query "documentEndpoint"Record the account endpoint as you will need this in the next section.
Create .NET SDK console application
The .NET SDK for Azure Cosmos DB for NoSQL provides classes for common GeoJSON objects. Use this SDK to streamline the process of adding geographic objects to your container.
Open a terminal in an empty directory.
Create a new .NET application by using the
dotnet newcommand with the console template.dotnet new consoleImport the
Microsoft.Azure.CosmosNuGet package using thedotnet add packagecommand.dotnet add package Microsoft.Azure.Cosmos --version 3.*Warning
Entity Framework does not currently spatial data in Azure Cosmos DB for NoSQL. Use one of the Azure Cosmos DB for NoSQL SDKs for strongly-typed GeoJSON support.
Import the
Azure.IdentityNuGet package.dotnet add package Azure.Identity --version 1.*Build the project with the
dotnet buildcommand.dotnet buildOpen the integrated developer environment (IDE) of your choice in the same directory as your .NET console application.
Open the newly created Program.cs file and delete any existing code. Add using directives for the
Microsoft.Azure.Cosmos,Microsoft.Azure.Cosmos.Linq, andMicrosoft.Azure.Cosmos.Spatialnamespaces.using Microsoft.Azure.Cosmos; using Microsoft.Azure.Cosmos.Linq; using Microsoft.Azure.Cosmos.Spatial;Add another using directive for the
Azure.Identitynamespace.using Azure.Identity;Create a new variable named
credentialof typeDefaultAzureCredential.DefaultAzureCredential credential = new();Create a string variable named
endpointwith your Azure Cosmos DB for NoSQL account endpoint.string endpoint = "<nosql-account-endpoint>";Create a new instance of the
CosmosClientclass passing inconnectionStringand wrapping it in a using statement.using CosmosClient client = new (connectionString);Retrieve a reference to the previously created container (
cosmicworks/locations) in the Azure Cosmos DB for NoSQL account by usingCosmosClient.GetDatabaseand thenDatabase.GetContainer. Store the result in a variable namedcontainer.var container = client.GetDatabase("cosmicworks").GetContainer("locations");Save the Program.cs file.
Add geospatial data
The .NET SDK includes multiple types in the Microsoft.Azure.Cosmos.Spatial namespace to represent common GeoJSON objects. These types streamline the process of adding new location information to items in a container.
Create a new file named Office.cs. In the file, add a using directive to
Microsoft.Azure.Cosmos.Spatialand then create aOfficerecord type with these properties:Type Description Default value id stringUnique identifier name stringName of the office location PointGeoJSON geographical point category stringPartition key value business-officeusing Microsoft.Azure.Cosmos.Spatial; public record Office( string id, string name, Point location, string category = "business-office" );Note
This record includes a
Pointproperty representing a specific position in GeoJSON. For more information, see GeoJSON Point.Create another new file named Region.cs. Add another record type named
Regionwith these properties:Type Description Default value id stringUnique identifier name stringName of the office location PolygonGeoJSON geographical shape category stringPartition key value business-regionusing Microsoft.Azure.Cosmos.Spatial; public record Region( string id, string name, Polygon location, string category = "business-region" );Note
This record includes a
Polygonproperty representing a shape composed of lines drawn between multiple locations in GeoJSON. For more information, see GeoJSON Polygon.Create another new file named Result.cs. Add a record type named
Resultwith these two properties:Type Description name stringName of the matched result distanceKilometers decimalDistance in kilometers public record Result( string name, decimal distanceKilometers );Save the Office.cs, Region.cs, and Result.cs files.
Open the Program.cs file again.
Create a new
Polygonin a variable namedmainCampusPolygon.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), }) } );Create a new
Regionvariable namedmainCampusRegionusing the polygon, the unique identifier1000, and the nameMain Campus.Region mainCampusRegion = new ("1000", "Main Campus", mainCampusPolygon);Use
Container.UpsertItemAsyncto add the region to the container. Write the region's information to the console.await container.UpsertItemAsync<Region>(mainCampusRegion); Console.WriteLine($"[UPSERT ITEM]\t{mainCampusRegion}");Tip
This guide uses upsert instead of insert so you can run the script multiple times without causing a conflict between unique identifiers. For more information on upsert operations, see creating items.
Create a new
Pointvariable namedheadquartersPoint. Use that variable to create a newOfficevariable namedheadquartersOfficeusing the point, the unique identifier0001, and the nameHeadquarters.Point headquartersPoint = new (-122.12827, 47.63980); Office headquartersOffice = new ("0001", "Headquarters", headquartersPoint);Create another
Pointvariable namedresearchPoint. Use that variable to create anotherOfficevariable namedresearchOfficeusing the corresponding point, the unique identifier0002, and the nameResearch and Development.Point researchPoint = new (-96.84369, 46.81298); Office researchOffice = new ("0002", "Research and Development", researchPoint);Create a
TransactionalBatchto upsert bothOfficevariables as a single transaction. Then, write both office's information to the 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}");Note
For more information on transactions, see transactional batch operations.
Save the Program.cs file.
Run the application in a terminal using
dotnet run. Observe that the output of the application run includes information about the three newly created items.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 }
Query geospatial data using NoSQL query
The types in the Microsoft.Azure.Cosmos.Spatial namespace can be used as inputs to a NoSQL parameterized query to use built-in functions like ST_DISTANCE.
Open the Program.cs file.
Create a new
stringvariable namednosqlwith the query is used in this section to measure the distance between points.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 ";Create a new
QueryDefinitionvariable namedqueryusing thenosqlStringvariable as a parameter. Then use theQueryDefinition.WithParameterfluent method multiple times to add these parameters to the query:Value @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));Create a new iterator using
Container.GetItemQueryIterator<>, theResultgeneric type, and thequeryvariable. Then, use a combination of a while and foreach loop to iterate over all results in each page of results. Output each result to the 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}"); } }Note
For more information on enumerating query results, see query items.
Save the Program.cs file.
Run the application again in a terminal using
dotnet run. Observe that the output now includes the results of the query.dotnet run[DISTANCE KM] Result { name = Headquarters, distanceKilometers = 3.34 } [DISTANCE KM] Result { name = Research and Development, distanceKilometers = 1907.43 }
Query geospatial data using LINQ
The LINQ to NoSQL functionality in the .NET SDK supports including geospatial types in the query expressions. Even further, the SDK includes extension methods that map to equivalent built-in functions:
| Extension method | Built-in function |
|---|---|
Distance() |
ST_DISTANCE |
Intersects() |
ST_INTERSECTS |
IsValid() |
ST_ISVALID |
IsValidDetailed() |
ST_ISVALIDDETAILED |
Within() |
ST_WITHIN |
Open the Program.cs file.
Retrieve the
Regionitem from the container with a unique identifier of1000and store it in a variable namedregion.Region region = await container.ReadItemAsync<Region>("1000", new PartitionKey("business-region"));Use the
Container.GetItemLinqQueryable<>method to get a LINQ queryable, and the build the LINQ query fluently by performing these three actions:Use the
Queryable.Where<>extension method to filter to only items with acategoryequivalent to"business-office".Use
Queryable.Where<>again to filter to only locations within theregionvariable'slocationproperty usingGeometry.Within().Translate the LINQ expression to a feed iterator using
CosmosLinqExtensions.ToFeedIterator<>.
var regionIterator = container.GetItemLinqQueryable<Office>() .Where(o => o.category == "business-office") .Where(o => o.location.Within(region.location)) .ToFeedIterator<Office>();Important
In this example, the office's location property has a point, and the region's location property has a polygon.
ST_WITHINis determining if the point of the office is within the polygon of the region.Use a combination of a while and foreach loop to iterate over all results in each page of results. Output each result to the console.
while (regionIterator.HasMoreResults) { var response = await regionIterator.ReadNextAsync(); foreach (var office in response) { Console.WriteLine($"[IN REGION]\t{office}"); } }Save the Program.cs file.
Run the application one last time in a terminal using
dotnet run. Observe that the output now includes the results of the second LINQ-based query.dotnet run[IN REGION] Office { id = 0001, name = Headquarters, location = Microsoft.Azure.Cosmos.Spatial.Point, category = business-office }
Clean up resources
Remove your database after you complete this guide.
Open a terminal and create a shell variable for the name of your account and resource group.
# 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 deleteto remove the database.az cosmosdb sql database delete \ --resource-group "<resource-group-name>" \ --account-name "<nosql-account-name>" \ --name "cosmicworks"