Skip to content

Commit 5c4f8c5

Browse files
committed
Add sample for how to sort by spatial distance
1 parent abf4d4e commit 5c4f8c5

File tree

11 files changed

+498
-0
lines changed

11 files changed

+498
-0
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio 2013
4+
VisualStudioVersion = 12.0.31101.0
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{4BC78984-F2D8-4834-9472-0C03B1E9E986}"
7+
ProjectSection(ProjectDependencies) = postProject
8+
{402EE4D6-CAD8-4BBE-831F-8F95BA78C289} = {402EE4D6-CAD8-4BBE-831F-8F95BA78C289}
9+
EndProjectSection
10+
EndProject
11+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spatial", "Spatial\Spatial.csproj", "{402EE4D6-CAD8-4BBE-831F-8F95BA78C289}"
12+
EndProject
13+
Global
14+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
15+
Debug|Any CPU = Debug|Any CPU
16+
Release|Any CPU = Release|Any CPU
17+
EndGlobalSection
18+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
19+
{4BC78984-F2D8-4834-9472-0C03B1E9E986}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
20+
{4BC78984-F2D8-4834-9472-0C03B1E9E986}.Debug|Any CPU.Build.0 = Debug|Any CPU
21+
{4BC78984-F2D8-4834-9472-0C03B1E9E986}.Release|Any CPU.ActiveCfg = Release|Any CPU
22+
{4BC78984-F2D8-4834-9472-0C03B1E9E986}.Release|Any CPU.Build.0 = Release|Any CPU
23+
{402EE4D6-CAD8-4BBE-831F-8F95BA78C289}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24+
{402EE4D6-CAD8-4BBE-831F-8F95BA78C289}.Debug|Any CPU.Build.0 = Debug|Any CPU
25+
{402EE4D6-CAD8-4BBE-831F-8F95BA78C289}.Release|Any CPU.ActiveCfg = Release|Any CPU
26+
{402EE4D6-CAD8-4BBE-831F-8F95BA78C289}.Release|Any CPU.Build.0 = Release|Any CPU
27+
EndGlobalSection
28+
GlobalSection(SolutionProperties) = preSolution
29+
HideSolutionNode = FALSE
30+
EndGlobalSection
31+
EndGlobal
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<configuration>
3+
<startup>
4+
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
5+
</startup>
6+
<runtime>
7+
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
8+
<dependentAssembly>
9+
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
10+
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
11+
</dependentAssembly>
12+
</assemblyBinding>
13+
</runtime>
14+
</configuration>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System.Reflection;
2+
using System.Runtime.CompilerServices;
3+
using System.Runtime.InteropServices;
4+
5+
// General Information about an assembly is controlled through the following
6+
// set of attributes. Change these attribute values to modify the information
7+
// associated with an assembly.
8+
[assembly: AssemblyTitle("Spatial")]
9+
[assembly: AssemblyDescription("")]
10+
[assembly: AssemblyConfiguration("")]
11+
[assembly: AssemblyCompany("")]
12+
[assembly: AssemblyProduct("Spatial")]
13+
[assembly: AssemblyCopyright("Copyright © 2015")]
14+
[assembly: AssemblyTrademark("")]
15+
[assembly: AssemblyCulture("")]
16+
17+
// Setting ComVisible to false makes the types in this assembly not visible
18+
// to COM components. If you need to access a type in this assembly from
19+
// COM, set the ComVisible attribute to true on that type.
20+
[assembly: ComVisible(false)]
21+
22+
// The following GUID is for the ID of the typelib if this project is exposed to COM
23+
[assembly: Guid("17c821ed-f825-4ae6-9652-125a2543729c")]
24+
25+
// Version information for an assembly consists of the following four values:
26+
//
27+
// Major Version
28+
// Minor Version
29+
// Build Number
30+
// Revision
31+
//
32+
// You can specify all the values or you can default the Build and Revision Numbers
33+
// by using the '*' as shown below:
34+
// [assembly: AssemblyVersion("1.0.*")]
35+
[assembly: AssemblyVersion("1.0.0.0")]
36+
[assembly: AssemblyFileVersion("1.0.0.0")]
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
4+
<PropertyGroup>
5+
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
6+
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
7+
<ProjectGuid>{402EE4D6-CAD8-4BBE-831F-8F95BA78C289}</ProjectGuid>
8+
<OutputType>Library</OutputType>
9+
<AppDesignerFolder>Properties</AppDesignerFolder>
10+
<RootNamespace>Spatial</RootNamespace>
11+
<AssemblyName>Spatial</AssemblyName>
12+
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
13+
<FileAlignment>512</FileAlignment>
14+
</PropertyGroup>
15+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
16+
<PlatformTarget>AnyCPU</PlatformTarget>
17+
<DebugSymbols>true</DebugSymbols>
18+
<DebugType>full</DebugType>
19+
<Optimize>false</Optimize>
20+
<OutputPath>bin\Debug\</OutputPath>
21+
<DefineConstants>DEBUG;TRACE</DefineConstants>
22+
<ErrorReport>prompt</ErrorReport>
23+
<WarningLevel>4</WarningLevel>
24+
</PropertyGroup>
25+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
26+
<PlatformTarget>AnyCPU</PlatformTarget>
27+
<DebugType>pdbonly</DebugType>
28+
<Optimize>true</Optimize>
29+
<OutputPath>bin\Release\</OutputPath>
30+
<DefineConstants>TRACE</DefineConstants>
31+
<ErrorReport>prompt</ErrorReport>
32+
<WarningLevel>4</WarningLevel>
33+
</PropertyGroup>
34+
<PropertyGroup>
35+
<StartupObject />
36+
</PropertyGroup>
37+
<ItemGroup>
38+
<Reference Include="Microsoft.Azure.Documents.Client, Version=1.4.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
39+
<SpecificVersion>False</SpecificVersion>
40+
<HintPath>..\..\..\..\..\..\..\..\NuGetPackages\Microsoft.Azure.DocumentDB.1.4.1\lib\net40\Microsoft.Azure.Documents.Client.dll</HintPath>
41+
</Reference>
42+
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
43+
<SpecificVersion>False</SpecificVersion>
44+
<HintPath>..\..\..\..\..\..\..\..\NuGetPackages\Newtonsoft.Json.6.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
45+
</Reference>
46+
<Reference Include="System" />
47+
<Reference Include="System.Core" />
48+
<Reference Include="System.Xml.Linq" />
49+
<Reference Include="System.Data.DataSetExtensions" />
50+
<Reference Include="Microsoft.CSharp" />
51+
<Reference Include="System.Data" />
52+
<Reference Include="System.Xml" />
53+
</ItemGroup>
54+
<ItemGroup>
55+
<Compile Include="SpatialHelper.cs" />
56+
<Compile Include="Properties\AssemblyInfo.cs" />
57+
</ItemGroup>
58+
<ItemGroup>
59+
<None Include="App.config" />
60+
<None Include="packages.config" />
61+
</ItemGroup>
62+
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
63+
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
64+
Other similar extension points exist, see Microsoft.Common.targets.
65+
<Target Name="BeforeBuild">
66+
</Target>
67+
<Target Name="AfterBuild">
68+
</Target>
69+
-->
70+
</Project>
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
namespace Spatial
2+
{
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Threading.Tasks;
7+
using Microsoft.Azure.Documents;
8+
using Microsoft.Azure.Documents.Client;
9+
using Microsoft.Azure.Documents.Linq;
10+
using Microsoft.Azure.Documents.Spatial;
11+
using Newtonsoft.Json;
12+
13+
public sealed class SpatialHelper
14+
{
15+
private static readonly int MaxRetries = 100;
16+
private static readonly int StatusCodeTooManyRequests = 429;
17+
private static readonly TimeSpan MinSleepTime = TimeSpan.FromMilliseconds(100);
18+
19+
/// <summary>
20+
/// Get the points near the specified point in the order of increasing distance using spatial queries.
21+
/// </summary>
22+
/// <param name="client">
23+
/// The <see cref="DocumentClient"/> object.
24+
/// </param>
25+
/// <param name="documentCollectionLink">
26+
/// The collection self link string.
27+
/// </param>
28+
/// <param name="propertyName">
29+
/// The document property that stores GeoJSON.
30+
/// </param>
31+
/// <param name="point">
32+
/// The specified <see cref="Point"/> of which to find the nearby points.
33+
/// </param>
34+
/// <param name="minDistance">
35+
/// The minimum distance to start with (exclusive).
36+
/// </param>
37+
/// <param name="maxDistance">
38+
/// The maximum distance to end with (inclusive).
39+
/// </param>
40+
/// <param name="maxPoints">
41+
/// The maximum number of points to find.
42+
/// </param>
43+
public static IEnumerable<dynamic> Near(DocumentClient client, string documentCollectionLink, string propertyName, Point point, long minDistance, long maxDistance, int maxPoints)
44+
{
45+
// Validate arguments
46+
if(minDistance <= 0) throw new ArgumentOutOfRangeException("Minimum distance must be a positive number.");
47+
if(maxDistance < minDistance) throw new ArgumentOutOfRangeException("Maximum distance must be greater than or equal to minimum distance.");
48+
if(maxPoints <= 0) throw new ArgumentOutOfRangeException("Max points must be positive.");
49+
50+
List<dynamic> totalPoints = new List<dynamic>(maxPoints);
51+
52+
// Use parametrized query
53+
// Note: We do a SELECT r here. You may want to modify this and select specified properties.
54+
string queryFormat = @"SELECT r AS doc, ST_DISTANCE(r.{0}, @loc) AS distance FROM Root r WHERE ST_DISTANCE(r.{0}, @loc) BETWEEN @min AND @max";
55+
string queryString = String.Format(queryFormat, propertyName);
56+
57+
for(long distance = minDistance, previousDistance = 0; distance <= maxDistance; distance = Math.Min(2 * distance, maxDistance))
58+
{
59+
SqlQuerySpec spec = new SqlQuerySpec()
60+
{
61+
QueryText = queryString,
62+
Parameters = new SqlParameterCollection()
63+
{
64+
new SqlParameter() {
65+
Name = "@loc",
66+
Value = point
67+
},
68+
69+
new SqlParameter() {
70+
Name = "@min",
71+
Value = previousDistance
72+
},
73+
74+
new SqlParameter() {
75+
Name = "@max",
76+
Value = distance
77+
}
78+
}
79+
};
80+
81+
IDocumentQuery<dynamic> query = client.CreateDocumentQuery(documentCollectionLink, spec).AsDocumentQuery();
82+
83+
while (query.HasMoreResults)
84+
{
85+
// Fetch results using ExecuteWithRetries in case of throttle.
86+
FeedResponse<dynamic> result = ExecuteWithRetries(() => query.ExecuteNextAsync()).Result;
87+
totalPoints.AddRange(result);
88+
}
89+
90+
// Using BETWEEN can give us duplicate results. Here we remove them.
91+
totalPoints.GroupBy(result => result.doc.id).Select(group => group.First());
92+
93+
// Break once we have found enough points
94+
if(totalPoints.Count >= maxPoints) break;
95+
96+
// Break once we reach the maximum distance to search
97+
if(distance == maxDistance) break;
98+
99+
previousDistance = distance;
100+
}
101+
102+
// Sort the results based on distance
103+
totalPoints.Sort((point1, point2) => {
104+
int cmp = point1.distance.CompareTo(point2.distance);
105+
return cmp != 0 ? cmp : point1.doc.id.CompareTo(point2.doc.id);
106+
});
107+
108+
foreach(dynamic result in totalPoints.Take(maxPoints))
109+
yield return result.doc;
110+
}
111+
112+
/// <summary>
113+
/// Execute the function with retries on throttle.
114+
/// </summary>
115+
/// <typeparam name="V">The type of return value from the execution.</typeparam>
116+
/// <param name="function">The function to execute.</param>
117+
/// <returns>The response from the execution.</returns>
118+
public static async Task<V> ExecuteWithRetries<V>(Func<Task<V>> function)
119+
{
120+
TimeSpan sleepTime = TimeSpan.Zero;
121+
122+
int retryCount = 0;
123+
while(true)
124+
{
125+
try
126+
{
127+
++retryCount;
128+
return await function();
129+
}
130+
catch(Exception ex)
131+
{
132+
if(retryCount >= MaxRetries)
133+
{
134+
throw;
135+
}
136+
137+
DocumentClientException de;
138+
139+
while(ex is AggregateException)
140+
{
141+
ex = ((AggregateException)ex).InnerException;
142+
}
143+
144+
if(ex is DocumentClientException)
145+
{
146+
de = (DocumentClientException)ex.InnerException;
147+
}
148+
else
149+
{
150+
throw;
151+
}
152+
153+
if((int)de.StatusCode == StatusCodeTooManyRequests)
154+
{
155+
sleepTime = (de.RetryAfter < MinSleepTime) ? MinSleepTime : de.RetryAfter;
156+
}
157+
else
158+
{
159+
throw;
160+
}
161+
}
162+
163+
await Task.Delay(sleepTime);
164+
}
165+
}
166+
}
167+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<packages>
3+
<package id="Microsoft.Azure.DocumentDB" version="1.4.1" targetFramework="net45" />
4+
<package id="Newtonsoft.Json" version="6.0.3" targetFramework="net45" />
5+
</packages>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<configuration>
3+
<startup>
4+
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
5+
</startup>
6+
<runtime>
7+
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
8+
<dependentAssembly>
9+
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
10+
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
11+
</dependentAssembly>
12+
</assemblyBinding>
13+
</runtime>
14+
</configuration>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
namespace Test
2+
{
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Configuration;
6+
using Microsoft.Azure.Documents;
7+
using Microsoft.Azure.Documents.Client;
8+
using Microsoft.Azure.Documents.Spatial;
9+
using Spatial;
10+
11+
internal sealed class Program
12+
{
13+
//Read the DocumentDB endpointUrl and authorisationKeys from config
14+
//These values are available from the Azure Management Portal on the DocumentDB Account Blade under "Keys"
15+
//NB > Keep these values in a safe & secure location. Together they provide Administrative access to your DocumentDB account
16+
private static readonly string endpointUrl = ConfigurationManager.AppSettings["EndPointUrl"];
17+
private static readonly string authorizationKey = ConfigurationManager.AppSettings["AuthorizationKey"];
18+
19+
private static readonly string documentCollectionLink = ConfigurationManager.AppSettings["DocumentCollectionLink"];
20+
private static readonly string propertyName = ConfigurationManager.AppSettings["PropertyName"];
21+
22+
static void Main(string[] args)
23+
{
24+
DocumentClient client = new DocumentClient(new Uri(endpointUrl), authorizationKey);
25+
DocumentCollection collection = client.ReadDocumentCollectionAsync(documentCollectionLink).Result;
26+
27+
Point point = new Point(-87.636836, 41.884615);
28+
int minDistance = 100;
29+
int maxDistance = 5000;
30+
int maxPoints = 500;
31+
32+
IEnumerable<dynamic> points = SpatialHelper.Near(client, collection.DocumentsLink, propertyName, point, minDistance, maxDistance, maxPoints);
33+
34+
long count = 0;
35+
foreach(dynamic p in points)
36+
{
37+
Console.WriteLine(@"Point ID: {0}", p.id);
38+
++count;
39+
}
40+
41+
Console.WriteLine(@"Found {0} points", count);
42+
}
43+
}
44+
}

0 commit comments

Comments
 (0)