Microsoft.Azure.Cosmos.Samples/Usage/Geospatial/Program.cs (321 lines of code) (raw):

namespace Cosmos.Samples.Geospatial { using System; using System.Threading.Tasks; using System.Collections.Generic; using Microsoft.Azure.Cosmos; using System.Linq; using Microsoft.Azure.Cosmos.Spatial; using Microsoft.Azure.Cosmos.Linq; using Microsoft.Extensions.Configuration; public class Program { // The Cosmos client instance private static CosmosClient cosmosClient; // The database we will create private static Database database; // The container we will create private static Container container; // The name of the database and container we will use for the demo private static readonly string databaseId = "spatial-samples-db"; private static readonly string containerId = "spatial-samples-cn"; // The partition key used in the sample private static readonly string partitionKey = "/name"; static async Task Main(string[] args) { try { IConfigurationRoot configuration = new ConfigurationBuilder() .AddJsonFile("appSettings.json") .Build(); string endpoint = configuration["EndPointUrl"]; if (string.IsNullOrEmpty(endpoint)) { throw new ArgumentNullException("Please specify a valid endpoint in the appSettings.json"); } string authKey = configuration["AuthorizationKey"]; if (string.IsNullOrEmpty(authKey) || string.Equals(authKey, "Super secret key")) { throw new ArgumentException("Please specify a valid AuthorizationKey in the appSettings.json"); } Console.WriteLine("Beginning operations...\n"); //get a Cosmos Client using (cosmosClient = new CosmosClient(endpoint, authKey)) { await Program.RunDemoAsync(cosmosClient); } Program p = new Program(); } catch (CosmosException de) { Exception baseException = de.GetBaseException(); Console.WriteLine("{0} error occurred: {1}", de.StatusCode, de); } catch (Exception e) { Console.WriteLine("Error: {0}", e); } finally { Console.WriteLine("End of demo, press any key to exit."); Console.ReadKey(); } } /// <summary> /// Run the geospatial demo. /// </summary> /// <returns>The Task for asynchronous execution.</returns> private static async Task RunDemoAsync(CosmosClient cosmosClient) { // Create the database if necessary await Program.Setup(cosmosClient); // Create a new container to enable spatial indexing. container = await GetContainerWithSpatialIndexingAsync(); // Spatial Index work await Program.SpatialIndex(); // Uncomment to delete database // await database.DeleteAsync(); } private static async Task Setup(CosmosClient cosmosClient) { database = await cosmosClient.CreateDatabaseIfNotExistsAsync(databaseId); } // NOTE: In GeoJSON, longitude comes before latitude. // Cosmos DB uses the WGS-84 coordinate reference standard. // Longitudes are between -180 and 180 degrees, and latitudes between -90 and 90 degrees. private static async Task SpatialIndex() { Console.WriteLine("Creating creature items."); Creature human = new Creature { Id = Guid.NewGuid(), Name = "stef", Species = "Human", Location = new Point(31.9, -4.8) }; Creature dragon = new Creature { Id = Guid.NewGuid(), Name = "redEyesBlackDragon", Species = "Dragon", Location = new Point(31.87, -4.55) }; Creature dragon2 = new Creature { Id = Guid.NewGuid(), Name = "blueEyesWhiteDragon", Species = "Dragon", Location = new Point(32.33, -4.66) }; Area dragonPit = new Area() { Id = Guid.NewGuid(), Name = "DragonPit", Boundary = new Polygon(new List<LinearRing>() { new LinearRing(new Position[] { new Position(31.8, -5), new Position(32, -5), new Position(32, -4.7), new Position(31.8, -4.7), new Position(31.8, -5) }) }) }; // Insert items with GeoJSON spatial data. await container.CreateItemAsync(human, new PartitionKey("stef")); await container.CreateItemAsync(dragon, new PartitionKey("redEyesBlackDragon")); await container.CreateItemAsync(dragon2, new PartitionKey("blueEyesWhiteDragon")); // Check for points within a circle/radius relative to another point. Common for "What's near me?" queries. await RunDistanceQuery(human.Location); // Check for points within a polygon. Cities/states/natural formations are all commonly represented as polygons. await RunWithinPolygonQuery(); // How to check for valid geospatial objects. Checks for valid latitude/longtiudes and if polygons are well-formed, etc. await CheckIfPointOrPolygonIsValid(); // Now demonstrate how to implement "geo-fencing", i.e. index polygons and query if a point lies within the polygon // For example, you can identify when the user of your mobile app enters a new country by storing the polygons representing each country // Modify container with Polygon spatial type indexing Console.WriteLine("Inserting polygon."); await ModifyContainerWithSpatialIndexingAsync(); await container.CreateItemAsync(dragonPit, new PartitionKey("DragonPit")); // Check for points within a polygon. Now, we store polygons, and query for polygons that cover a specified point. await RunInverseWithinPolygonQuery(); await RunIntersectsQuery(); } /// <summary> /// Run a distance query using SQL, LINQ and parameterized SQL. /// </summary> /// <param name="from">The position to measure distance from.</param> private static async Task RunDistanceQuery(Point from) { // Cosmos DB uses the WGS-84 coordinate reference system (CRS). In this reference system, distance is measured in meters. So 30km = 30000m. // There are several built-in SQL functions that follow the OGC naming standards and start with the "ST_" prefix for "spatial type". // SQL Query Console.WriteLine("Performing a ST_DISTANCE proximity query in SQL"); string sqlQuery = @"SELECT * FROM e where e.species ='Dragon' AND ST_DISTANCE(e.location, {'type': 'Point', 'coordinates':[31.9, -4.8]}) < 30000"; FeedResponse<Creature> results = await container.GetItemQueryIterator<Creature>(sqlQuery).ReadNextAsync(); List<Creature> list = results.ToList(); foreach (Creature item in list) { Console.WriteLine("SQL QUERY: " + item.Name); } Console.WriteLine(); // LINQ query Console.WriteLine("Performing a ST_DISTANCE proximity query in LINQ"); using (FeedIterator<Creature> linqQueryIterator = container.GetItemLinqQueryable<Creature>(allowSynchronousQueryExecution: true) .Where(a => a.Species == "Dragon" && a.Location.Distance(from) < 30000) .ToFeedIterator<Creature>()) { while (linqQueryIterator.HasMoreResults) { foreach (Creature item in await linqQueryIterator.ReadNextAsync()) { { Console.WriteLine("LINQ QUERY: " + item); } } } } Console.WriteLine(); // SQL w/ Parameters Console.WriteLine("Performing a ST_DISTANCE proximity query in parameterized SQL"); QueryDefinition parameterizedSQLQuery = new QueryDefinition("SELECT * FROM e " + "WHERE e.species = @species AND ST_DISTANCE(e.location, @human) < 30000") .WithParameter("@species", "Dragon") .WithParameter("@human", from); FeedResponse<Creature> parameterizedSQLQueryResults = await container.GetItemQueryIterator<Creature>(parameterizedSQLQuery).ReadNextAsync(); List<Creature> parameterizedSQLQueryList = parameterizedSQLQueryResults.ToList(); foreach (Creature item in parameterizedSQLQueryList) { Console.WriteLine("QUERY WITH PARAMETER: " + item.Name); } Console.WriteLine(); } /// <summary> /// Run a intersects query using SQL and LINQ to determine if a creature location intersects with a new line. /// </summary> private static async Task RunIntersectsQuery() { // LINQ query Console.WriteLine("Performing a ST_INTERSECTS query in LINQ"); using (FeedIterator<Creature> linqQueryIterator = container.GetItemLinqQueryable<Creature>(allowSynchronousQueryExecution: true) .Where(a => a.Location.Intersects(new LineString(new[] { new Position (32.33, -4.66), new Position (32.34, -4.66), new Position (31.87, -4.55) }))) .ToFeedIterator<Creature>()) { while (linqQueryIterator.HasMoreResults) { foreach (Creature result in await linqQueryIterator.ReadNextAsync()) { Console.WriteLine("ST_INTERSECTS - LINQ QUERY:----" + result.Name); } } } Console.WriteLine(); //SQL query Console.WriteLine("Performing a ST_INTERSECTS query in SQL"); string sqlQuery = "SELECT * FROM e WHERE ST_INTERSECTS(e.location, {'type':'LineString', 'coordinates': [[32.33, -4.66],[32.34, -4.66],[31.87, -4.55]]})"; FeedResponse<Creature> results = await container.GetItemQueryIterator<Creature>(sqlQuery).ReadNextAsync(); List<Creature> list = results.ToList(); foreach (Creature item in list) { Console.WriteLine("ST_INTERSECTS - SQL QUERY:----" + item.Name); } } /// <summary> /// Run a within query (get points within a box/polygon) using SQL and LINQ. /// </summary> private static async Task RunWithinPolygonQuery() { // SQL query Console.WriteLine("Performing a ST_WITHIN proximity query in SQL"); string sqlQuery = "SELECT * FROM e WHERE ST_WITHIN(e.location, {'type':'Polygon', 'coordinates': [[[31.8, -5], [32, -5], [32, -4.7], [31.8, -4.7], [31.8, -5]]]})"; FeedResponse<Creature> results = await container.GetItemQueryIterator<Creature>(sqlQuery).ReadNextAsync(); List<Creature> list = results.ToList(); foreach (Creature item in list) { Console.WriteLine("ST_WITHIN QUERY: " + item.Name); } // LINQ query Console.WriteLine("Performing a ST_WITHIN proximity query in LINQ\n"); using (FeedIterator<Creature> linqQueryIterator = container.GetItemLinqQueryable<Creature>(allowSynchronousQueryExecution: true).Where(a => a.Location .Within(new Polygon(new[] { new LinearRing(new[] { new Position(31.8, -5), new Position(32, -5), new Position(32, -4.7), new Position(31.8, -4.7), new Position(31.8, -5) }) }))) .ToFeedIterator<Creature>()) { while (linqQueryIterator.HasMoreResults) { foreach (Creature item in await linqQueryIterator.ReadNextAsync()) { { Console.WriteLine("LINQ QUERY: " + item.Name); } } } } Console.WriteLine(); } /// <summary> /// Check if a point or polygon is valid using built-in functions. An important thing to note is that since Cosmos DB's query is designed to handle heterogeneous types, /// bad input parameters will evaluate to "undefined" and get skipped over instead of returning an error. For debugging and fixing malformed geospatial objects, please /// use the built-in functions shown below. /// </summary> private static async Task CheckIfPointOrPolygonIsValid() { Console.WriteLine("Checking if a point is valid ..."); QueryDefinition parameterizedSQLQuery = new QueryDefinition("SELECT ST_ISVALID(@point), ST_ISVALIDDETAILED(@point)") .WithParameter("@point", new Point(31.9, -132.8)); FeedResponse<dynamic> results = await container.GetItemQueryIterator<dynamic>(parameterizedSQLQuery).ReadNextAsync(); Console.WriteLine($"Point Valid: {results.First()}"); Console.WriteLine("Checking if a polygon is valid ..."); QueryDefinition parameterizedSQLQuery2 = new QueryDefinition("SELECT ST_ISVALID(@polygon), ST_ISVALIDDETAILED(@polygon)") .WithParameter("@polygon", new Polygon(new[] { new LinearRing(new[] { new Position(31.8, -5), new Position(32, -5), new Position(32, -4.7), new Position(31.8, -4.7) }) })); FeedResponse<dynamic> results2 = await container.GetItemQueryIterator<dynamic>(parameterizedSQLQuery2).ReadNextAsync(); Console.WriteLine($"Polygon Valid: {results2.First()}"); } /// <summary> /// Run a within query (get points within a box/polygon) using SQL and LINQ. /// </summary> private static async Task RunInverseWithinPolygonQuery() { // SQL Query Console.WriteLine("Performing an inverse ST_WITHIN proximity query in SQL (polygons indexed by Cosmos DB, point supplied as argument)"); string sqlQuery = "SELECT * FROM everything e WHERE ST_WITHIN({'type':'Point', 'coordinates': [31.9, -4.9]}, e.boundary)"; FeedResponse<Area> results = await container.GetItemQueryIterator<Area>(sqlQuery).ReadNextAsync(); List<Area> areaList = results.ToList(); foreach (Area area in areaList) { Console.WriteLine("SQL QUERY - ST_WITHIN A POLYGON: " + area); } Console.WriteLine(); // LINQ Query Console.WriteLine("Performing an inverse ST_WITHIN proximity query in LINQ (polygons indexed by Cosmos DB, point supplied as argument)"); using (FeedIterator<Area> linqQueryIterator = container.GetItemLinqQueryable<Area>(allowSynchronousQueryExecution: true) .Where(a => new Point(31.9, -4.9).Within(a.Boundary)) .ToFeedIterator<Area>()) { while (linqQueryIterator.HasMoreResults) { foreach (Area area2 in await linqQueryIterator.ReadNextAsync()) { { Console.WriteLine("LINQ QUERY - ST_WITHIN A POLYGON: " + area2); } } } } Console.WriteLine(); } // Geospatial indexing on a container private static async Task<Container> GetContainerWithSpatialIndexingAsync() { ContainerProperties spatialContainerProperties = new ContainerProperties(containerId, Program.partitionKey); SpatialPath locationPath = new SpatialPath { Path = "/location/?" }; Console.WriteLine("Creating new CONTAINER w/ spatial index..."); locationPath.SpatialTypes.Add(SpatialType.Point); spatialContainerProperties.IndexingPolicy.SpatialIndexes.Add(locationPath); Container simpleContainer = await database.CreateContainerIfNotExistsAsync(spatialContainerProperties); return simpleContainer; } // Modifying geospatial indexing on a container private static async Task ModifyContainerWithSpatialIndexingAsync() { Container containerToUpdate = cosmosClient.GetContainer(databaseId, containerId); ContainerProperties updateContainerProperties = new ContainerProperties(containerId, Program.partitionKey); //Changing the Geopspatial Config from the deafult of geography to geometry. This change will need to include the addition of a bounding box. GeospatialConfig geospatialConfig = new GeospatialConfig(GeospatialType.Geometry); updateContainerProperties.GeospatialConfig = geospatialConfig; SpatialPath locationPath = new SpatialPath { Path = "/location/?", BoundingBox = new BoundingBoxProperties() { Xmin = 30, Ymin = -10, Xmax = 40, Ymax = 10 } }; Console.WriteLine("Updating CONTAINER w/ new spatial index..."); locationPath.SpatialTypes.Add(SpatialType.Point); locationPath.SpatialTypes.Add(SpatialType.LineString); locationPath.SpatialTypes.Add(SpatialType.Polygon); updateContainerProperties.IndexingPolicy.SpatialIndexes.Add(locationPath); await containerToUpdate.ReplaceContainerAsync(updateContainerProperties); } } }