diff --git a/.gitignore b/.gitignore
index 094871b..f2e383b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@
/examples/packages
AssemblyInfoCommon.cs
app.config
+app.config.temp
docs
nuget.exe
AssemblyVersion.cs
@@ -103,4 +104,5 @@ Thumbs.db
# Folder config file
Desktop.ini
-.directory
\ No newline at end of file
+.directory
+*.userprefs
diff --git a/README.md b/README.md
index a122ae1..8539c92 100644
--- a/README.md
+++ b/README.md
@@ -1,31 +1,31 @@
#Generic C# Geocoding API
-Includes a model and interface for communicating with three popular Geocoding providers. Current implementations include:
+Includes a model and interface for communicating with three popular Geocoding providers. Original implementations forked from (https://github.com/chadly/Geocoding.net) includes:
* [Google Maps](https://developers.google.com/maps/) - [docs](https://developers.google.com/maps/documentation/geocoding/)
* [Yahoo! BOSS Geo Services](http://developer.yahoo.com/boss/geo/) - [docs](http://developer.yahoo.com/geo/placefinder/guide/index.html)
* [Bing Maps (aka Virtual Earth)](http://www.microsoft.com/maps/) - [docs](http://msdn.microsoft.com/en-us/library/ff701715.aspx)
-
+
+This fork adds:
+
+ * MapQuest [(Comercial API)](http://www.mapquestapi.com/) - [docs](http://www.mapquestapi.com/geocoding/)
+ * MapQuest [(OpenStreetMap)](http://open.mapquestapi.com/) - [docs](http://open.mapquestapi.com/geocoding/)
+ * Mono compatibility
+
The API returns latitude/longitude coordinates and normalized address information. This can be used to perform address validation, real time mapping of user-entered addresses, distance calculations, and much more.
See latest [release notes](https://github.com/chadly/Geocoding.net/wiki/Release-Notes).
##Installation
-Install via nuget:
-
-```
-Install-Package Geocoding.net
-```
-
-Or download the [latest release](https://github.com/chadly/Geocoding.net/releases) and add a reference to `Geocoding.dll` in your project.
+Pull from appropriate branch (Master if stability is required) and build it yourself.
##Example Usage
###Simple Example
```csharp
-IGeocoder geocoder = new GoogleGeocoder() { ApiKey = "this-is-my-optional-google-api-key" };
+IGeocoder geocoder = new MapQuestGeocoder("this-is-my-required-mapquest-api-key");
Address[] addresses = geocoder.Geocode("C");
Console.WriteLine("Formatted: " + addresses[0].FormattedAddress); //Formatted: 1600 Pennslyvania Avenue Northwest, Presiden'ts Park, Washington, DC 20500, USA
Console.WriteLine("Coordinates: " + addresses[0].Coordinates.Latitude + ", " + addresses[0].Coordinates.Longitude); //Coordinates: 38.8978378, -77.0365123
@@ -62,6 +62,7 @@ Bing [requires an API key](http://msdn.microsoft.com/en-us/library/ff428642.aspx
You will need a [consumer secret and consumer key](http://developer.yahoo.com/boss/geo/BOSS_Signup.pdf) (PDF) for Yahoo.
+MapQuest API requires a key. Sign up here: (http://developer.mapquest.com/web/products/open)
##How to Build from Source
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 0000000..23053de
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1 @@
+packages/
diff --git a/src/.nuget/NuGet.Config b/src/.nuget/NuGet.Config
new file mode 100644
index 0000000..67f8ea0
--- /dev/null
+++ b/src/.nuget/NuGet.Config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/.nuget/NuGet.targets b/src/.nuget/NuGet.targets
new file mode 100644
index 0000000..3f8c37b
--- /dev/null
+++ b/src/.nuget/NuGet.targets
@@ -0,0 +1,144 @@
+
+
+
+ $(MSBuildProjectDirectory)\..\
+
+
+ false
+
+
+ false
+
+
+ true
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+ $([System.IO.Path]::Combine($(SolutionDir), ".nuget"))
+
+
+
+
+ $(SolutionDir).nuget
+
+
+
+ $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config
+ $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config
+
+
+
+ $(MSBuildProjectDirectory)\packages.config
+ $(PackagesProjectConfig)
+
+
+
+
+ $(NuGetToolsPath)\NuGet.exe
+ @(PackageSource)
+
+ "$(NuGetExePath)"
+ mono --runtime=v4.0.30319 "$(NuGetExePath)"
+
+ $(TargetDir.Trim('\\'))
+
+ -RequireConsent
+ -NonInteractive
+
+ "$(SolutionDir) "
+ "$(SolutionDir)"
+
+
+ $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir)
+ $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols
+
+
+
+ RestorePackages;
+ $(BuildDependsOn);
+
+
+
+
+ $(BuildDependsOn);
+ BuildPackage;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Core/Address.cs b/src/Core/Address.cs
index 2df24cf..eada676 100644
--- a/src/Core/Address.cs
+++ b/src/Core/Address.cs
@@ -3,43 +3,57 @@
namespace Geocoding
{
+ ///
+ /// Most basic and generic form of address.
+ /// Just the full address string and a lat/long
+ ///
public abstract class Address
{
- readonly string formattedAddress;
- readonly Location coordinates;
- readonly string provider;
+ string formattedAddress = string.Empty;
+ Location coordinates;
+ string provider = string.Empty;
- public string FormattedAddress
+ public Address(string formattedAddress, Location coordinates, string provider)
{
- get { return formattedAddress ?? ""; }
+ FormattedAddress = formattedAddress;
+ Coordinates = coordinates;
+ Provider = provider;
}
- public Location Coordinates
+ public virtual string FormattedAddress
{
- get { return coordinates; }
- }
+ get { return formattedAddress; }
+ set
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ throw new ArgumentException("FormattedAddress is null or blank");
- public string Provider
- {
- get { return provider ?? ""; }
+ formattedAddress = value.Trim();
+ }
}
- public Address(string formattedAddress, Location coordinates, string provider)
+ public virtual Location Coordinates
{
- formattedAddress = (formattedAddress ?? "").Trim();
-
- if (String.IsNullOrEmpty(formattedAddress))
- throw new ArgumentNullException("formattedAddress");
+ get { return coordinates; }
+ set
+ {
+ if (value == null)
+ throw new ArgumentNullException("Coordinates");
- if (coordinates == null)
- throw new ArgumentNullException("coordinates");
+ coordinates = value;
+ }
+ }
- if (provider == null)
- throw new ArgumentNullException("provider");
+ public virtual string Provider
+ {
+ get { return provider; }
+ protected set
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ throw new ArgumentException("Provider can not be null or blank");
- this.formattedAddress = formattedAddress;
- this.coordinates = coordinates;
- this.provider = provider;
+ provider = value;
+ }
}
public virtual Distance DistanceBetween(Address address)
diff --git a/src/Core/Bounds.cs b/src/Core/Bounds.cs
index b0f7a6a..2ecbec8 100644
--- a/src/Core/Bounds.cs
+++ b/src/Core/Bounds.cs
@@ -55,7 +55,7 @@ public override int GetHashCode()
public override string ToString()
{
- return String.Format("{0} | {1}", southWest, northEast);
+ return string.Format("{0} | {1}", southWest, northEast);
}
}
}
diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj
index 5b8c4b6..ce5a266 100644
--- a/src/Core/Core.csproj
+++ b/src/Core/Core.csproj
@@ -35,6 +35,8 @@
false
true
+ ..\
+ true
true
@@ -56,25 +58,28 @@
true
+
+ ..\packages\Newtonsoft.Json.6.0.3\lib\net40\Newtonsoft.Json.dll
+
- 3.5
+ 4.0
-
- Properties\AssemblyInfoCommon.cs
-
+
+
+
-
+
@@ -84,6 +89,7 @@
Properties\geocoding.snk
+
@@ -105,4 +111,11 @@
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
\ No newline at end of file
diff --git a/src/Core/Distance.cs b/src/Core/Distance.cs
index 037d732..2ae2b0f 100644
--- a/src/Core/Distance.cs
+++ b/src/Core/Distance.cs
@@ -100,7 +100,7 @@ public override int GetHashCode()
public override string ToString()
{
- return String.Format("{0} {1}", value, units);
+ return string.Format("{0} {1}", value, units);
}
#region Operators
diff --git a/src/Core/Extensions.cs b/src/Core/Extensions.cs
new file mode 100644
index 0000000..e07b9bd
--- /dev/null
+++ b/src/Core/Extensions.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+
+namespace Geocoding
+{
+ public static class Extensions
+ {
+ public static bool IsNullOrEmpty(this ICollection col)
+ {
+ return col == null || col.Count == 0;
+ }
+
+ public static void ForEach(this IEnumerable self, Action actor)
+ {
+ if(actor == null)
+ throw new ArgumentNullException("actor");
+
+ if (self == null)
+ return;
+
+ foreach (T item in self)
+ {
+ actor(item);
+ }
+ }
+
+ //Universal ISO DT Converter
+ static readonly JsonConverter[] JSON_CONVERTERS = new JsonConverter[]
+ {
+ new IsoDateTimeConverter { DateTimeStyles = System.Globalization.DateTimeStyles.AssumeUniversal },
+ new StringEnumConverter(),
+ };
+ public static string ToJSON(this object o)
+ {
+ string result = null;
+ if (o != null)
+ result = JsonConvert.SerializeObject(o, Formatting.Indented, JSON_CONVERTERS);
+ return result ?? string.Empty;
+ }
+
+ public static T FromJSON(this string json)
+ {
+ T o = default(T);
+ if (!string.IsNullOrWhiteSpace(json))
+ o = JsonConvert.DeserializeObject(json, JSON_CONVERTERS);
+ return o;
+ }
+ }
+}
diff --git a/src/Core/IBatchGeocoder.cs b/src/Core/IBatchGeocoder.cs
new file mode 100644
index 0000000..add12ed
--- /dev/null
+++ b/src/Core/IBatchGeocoder.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Geocoding
+{
+ public interface IBatchGeocoder
+ {
+ ICollection Geocode(IEnumerable addresses);
+ ICollection ReverseGeocode(IEnumerable locations);
+ }
+}
diff --git a/src/Core/Location.cs b/src/Core/Location.cs
index e497b4b..9f107fa 100644
--- a/src/Core/Location.cs
+++ b/src/Core/Location.cs
@@ -1,51 +1,66 @@
-using System;
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using Newtonsoft.Json;
namespace Geocoding
{
public class Location
{
- readonly double latitude;
- readonly double longitude;
+ double latitude;
+ double longitude;
- public double Latitude
+ [JsonProperty("lat")]
+ public virtual double Latitude
{
get { return latitude; }
+ set
+ {
+ if (value < -90 || value > 90)
+ throw new ArgumentOutOfRangeException("Latitude", value, "Value must be between -90(inclusive) and 90(inclusive).");
+ if (double.IsNaN(value))
+ throw new ArgumentException("Latitude must be a valid number.", "Latitude");
+
+ latitude = value;
+ }
}
- public double Longitude
+ [JsonProperty("lng")]
+ public virtual double Longitude
{
get { return longitude; }
+ set
+ {
+ if (value <= -180 || value > 180)
+ throw new ArgumentOutOfRangeException("Longitude", value, "Value must be between -180 and 180 (inclusive).");
+ if (double.IsNaN(value))
+ throw new ArgumentException("Longitude must be a valid number.", "Longitude");
+
+ longitude = value;
+ }
}
+ protected Location()
+ : this(0, 0)
+ {
+ }
public Location(double latitude, double longitude)
{
- if (longitude <= -180 || longitude > 180)
- throw new ArgumentOutOfRangeException("longitude", longitude, "Value must be between -180 and 180 (inclusive).");
-
- if (latitude < -90 || latitude > 90)
- throw new ArgumentOutOfRangeException("latitude", latitude, "Value must be between -90(inclusive) and 90(inclusive).");
-
- if (double.IsNaN(longitude))
- throw new ArgumentException("Longitude must be a valid number.", "longitude");
-
- if (double.IsNaN(latitude))
- throw new ArgumentException("Latitude must be a valid number.", "latitude");
-
- this.latitude = latitude;
- this.longitude = longitude;
+ Latitude = latitude;
+ Longitude = longitude;
}
- private double ToRadian(double val)
+ protected virtual double ToRadian(double val)
{
return (Math.PI / 180.0) * val;
}
- public Distance DistanceBetween(Location location)
+ public virtual Distance DistanceBetween(Location location)
{
return DistanceBetween(location, DistanceUnits.Miles);
}
- public Distance DistanceBetween(Location location, DistanceUnits units)
+ public virtual Distance DistanceBetween(Location location, DistanceUnits units)
{
double earthRadius = (units == DistanceUnits.Miles) ? Distance.EarthRadiusInMiles : Distance.EarthRadiusInKilometers;
@@ -83,7 +98,7 @@ public override int GetHashCode()
public override string ToString()
{
- return String.Format("{0}, {1}", latitude, longitude);
+ return string.Format("{0}, {1}", latitude, longitude);
}
}
}
\ No newline at end of file
diff --git a/src/Core/ParsedAddress.cs b/src/Core/ParsedAddress.cs
new file mode 100644
index 0000000..88636b1
--- /dev/null
+++ b/src/Core/ParsedAddress.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Geocoding
+{
+ ///
+ /// Generic parsed address with each field separated out form the original FormattedAddress
+ ///
+ public class ParsedAddress : Address
+ {
+ public ParsedAddress(string formattedAddress, Location coordinates, string provider)
+ : base(formattedAddress, coordinates, provider)
+ {
+ }
+
+ public virtual string Street { get; set; }
+ public virtual string City { get; set; }
+ public virtual string County { get; set; }
+ public virtual string State { get; set; }
+ public virtual string Country { get; set; }
+ public virtual string PostCode { get; set; }
+ }
+}
diff --git a/src/Core/Properties/AssemblyInfo.cs b/src/Core/Properties/AssemblyInfo.cs
index eafd902..3115750 100644
--- a/src/Core/Properties/AssemblyInfo.cs
+++ b/src/Core/Properties/AssemblyInfo.cs
@@ -1,3 +1,3 @@
-using System.Reflection;
+using System.Reflection;
[assembly: AssemblyTitle("Geocoding Core")]
\ No newline at end of file
diff --git a/src/Core/ResultItem.cs b/src/Core/ResultItem.cs
new file mode 100644
index 0000000..053aacc
--- /dev/null
+++ b/src/Core/ResultItem.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Geocoding
+{
+ public class ResultItem
+ {
+ public ResultItem(Address request, IEnumerable response)
+ {
+ Request = request;
+ Response = response;
+ }
+
+ Address input;
+ ///
+ /// Original input for this response
+ ///
+ public Address Request
+ {
+ get { return input; }
+ set
+ {
+ if (value == null)
+ throw new ArgumentNullException("Input");
+
+ input = value;
+ }
+ }
+
+ IEnumerable output;
+ ///
+ /// Output for the given input
+ ///
+ public IEnumerable Response
+ {
+ get { return output; }
+ set
+ {
+ if (value == null)
+ throw new ArgumentNullException("Response");
+
+ output = value;
+ }
+ }
+ }
+}
diff --git a/src/Core/packages.config b/src/Core/packages.config
new file mode 100644
index 0000000..12fef57
--- /dev/null
+++ b/src/Core/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/Geocoding.sln b/src/Geocoding.sln
index 8786d38..faf0e96 100644
--- a/src/Geocoding.sln
+++ b/src/Geocoding.sln
@@ -1,8 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 2013
-VisualStudioVersion = 12.0.30110.0
-MinimumVisualStudioVersion = 10.0.40219.1
+# Visual Studio 2012
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{891021E5-7521-4F93-9946-B7B630DF3192}"
ProjectSection(SolutionItems) = preProject
..\build-with-tests.bat = ..\build-with-tests.bat
@@ -22,14 +20,24 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{BB5F66AC-286B-4BA5-86EC-6DED28426132}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Google", "Google\Google.csproj", "{2E608F4E-29F7-4AC3-922F-23D971312CAE}"
-EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "APIs", "APIs", "{F742864D-9400-4CE2-957A-DBD0A0237277}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Google", "Google\Google.csproj", "{2E608F4E-29F7-4AC3-922F-23D971312CAE}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yahoo", "Yahoo\Yahoo.csproj", "{B2863E87-3983-46EF-8B3E-37A4475ECA13}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft", "Microsoft\Microsoft.csproj", "{74BCB608-4674-452F-A50C-7EE61AC47EAF}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapQuest", "MapQuest\MapQuest.csproj", "{B37FC059-5E9E-4893-994A-64D835D2A54F}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{B59E8640-AAE1-4447-AB9B-5CB3AD7A4549}"
+ ProjectSection(SolutionItems) = preProject
+ .nuget\NuGet.Config = .nuget\NuGet.Config
+ .nuget\NuGet.exe = .nuget\NuGet.exe
+ .nuget\NuGet.targets = .nuget\NuGet.targets
+ .nuget\packages.config = .nuget\packages.config
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -56,6 +64,10 @@ Global
{74BCB608-4674-452F-A50C-7EE61AC47EAF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{74BCB608-4674-452F-A50C-7EE61AC47EAF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{74BCB608-4674-452F-A50C-7EE61AC47EAF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B37FC059-5E9E-4893-994A-64D835D2A54F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B37FC059-5E9E-4893-994A-64D835D2A54F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B37FC059-5E9E-4893-994A-64D835D2A54F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B37FC059-5E9E-4893-994A-64D835D2A54F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -67,5 +79,9 @@ Global
{2E608F4E-29F7-4AC3-922F-23D971312CAE} = {F742864D-9400-4CE2-957A-DBD0A0237277}
{B2863E87-3983-46EF-8B3E-37A4475ECA13} = {F742864D-9400-4CE2-957A-DBD0A0237277}
{74BCB608-4674-452F-A50C-7EE61AC47EAF} = {F742864D-9400-4CE2-957A-DBD0A0237277}
+ {B37FC059-5E9E-4893-994A-64D835D2A54F} = {F742864D-9400-4CE2-957A-DBD0A0237277}
+ EndGlobalSection
+ GlobalSection(MonoDevelopProperties) = preSolution
+ StartupItem = Tests\Tests.csproj
EndGlobalSection
EndGlobal
diff --git a/src/Google/BusinessKey.cs b/src/Google/BusinessKey.cs
index c74262d..433af42 100644
--- a/src/Google/BusinessKey.cs
+++ b/src/Google/BusinessKey.cs
@@ -20,7 +20,7 @@ public BusinessKey(string clientId, string signingKey)
string CheckParam(string value, string name)
{
- if (String.IsNullOrEmpty(value))
+ if (string.IsNullOrEmpty(value))
throw new ArgumentNullException(name, "Value cannot be null or empty.");
return value.Trim();
@@ -66,7 +66,7 @@ public override int GetHashCode()
public override string ToString()
{
- return String.Format("ClientId: {0}, SigningKey: {1}", ClientId, SigningKey);
+ return string.Format("ClientId: {0}, SigningKey: {1}", ClientId, SigningKey);
}
}
}
\ No newline at end of file
diff --git a/src/Google/Google.csproj b/src/Google/Google.csproj
index 22b0965..8cdf57d 100644
--- a/src/Google/Google.csproj
+++ b/src/Google/Google.csproj
@@ -47,9 +47,6 @@
-
- Properties\AssemblyInfoCommon.cs
-
@@ -58,7 +55,6 @@
-
diff --git a/src/Google/GoogleAddressComponent.cs b/src/Google/GoogleAddressComponent.cs
index f04c2ca..626cc4e 100644
--- a/src/Google/GoogleAddressComponent.cs
+++ b/src/Google/GoogleAddressComponent.cs
@@ -23,7 +23,7 @@ public GoogleAddressComponent(GoogleAddressType[] types, string longName, string
public override string ToString()
{
- return String.Format("{0}: {1}", Types[0], LongName);
+ return string.Format("{0}: {1}", Types[0], LongName);
}
}
}
\ No newline at end of file
diff --git a/src/Google/GoogleGeocoder.cs b/src/Google/GoogleGeocoder.cs
index 9490e2f..78ea682 100644
--- a/src/Google/GoogleGeocoder.cs
+++ b/src/Google/GoogleGeocoder.cs
@@ -21,6 +21,16 @@ public class GoogleGeocoder : IGeocoder, IAsyncGeocoder
BusinessKey businessKey;
const string keyMessage = "Only one of BusinessKey or ApiKey should be set on the GoogleGeocoder.";
+ public GoogleGeocoder(BusinessKey businessKey)
+ {
+ BusinessKey = businessKey;
+ }
+
+ public GoogleGeocoder(string apiKey)
+ {
+ ApiKey = apiKey;
+ }
+
public string ApiKey
{
get { return apiKey; }
@@ -28,6 +38,9 @@ public string ApiKey
{
if (businessKey != null)
throw new InvalidOperationException(keyMessage);
+ if (string.IsNullOrWhiteSpace(value))
+ throw new ArgumentException("ApiKey can not be null or empty");
+
apiKey = value;
}
}
@@ -37,8 +50,11 @@ public BusinessKey BusinessKey
get { return businessKey; }
set
{
- if (!String.IsNullOrEmpty(apiKey))
+ if (!string.IsNullOrEmpty(apiKey))
throw new InvalidOperationException(keyMessage);
+ if (value == null)
+ throw new ArgumentException("BusinessKey can not be null");
+
businessKey = value;
}
}
@@ -55,19 +71,19 @@ public string ServiceUrl
var builder = new StringBuilder();
builder.Append("https://maps.googleapis.com/maps/api/geocode/xml?{0}={1}&sensor=false");
- if (!String.IsNullOrEmpty(Language))
+ if (!string.IsNullOrEmpty(Language))
{
builder.Append("&language=");
builder.Append(HttpUtility.UrlEncode(Language));
}
- if (!String.IsNullOrEmpty(RegionBias))
+ if (!string.IsNullOrEmpty(RegionBias))
{
builder.Append("®ion=");
builder.Append(HttpUtility.UrlEncode(RegionBias));
}
- if (!String.IsNullOrEmpty(ApiKey))
+ if (!string.IsNullOrEmpty(ApiKey))
{
builder.Append("&key=");
builder.Append(HttpUtility.UrlEncode(ApiKey));
@@ -97,7 +113,7 @@ public string ServiceUrl
public IEnumerable Geocode(string address)
{
- if (String.IsNullOrEmpty(address))
+ if (string.IsNullOrEmpty(address))
throw new ArgumentNullException("address");
HttpWebRequest request = BuildWebRequest("address", HttpUtility.UrlEncode(address));
@@ -120,7 +136,7 @@ public IEnumerable ReverseGeocode(double latitude, double longitu
public Task> GeocodeAsync(string address)
{
- if (String.IsNullOrEmpty(address))
+ if (string.IsNullOrEmpty(address))
throw new ArgumentNullException("address");
HttpWebRequest request = BuildWebRequest("address", HttpUtility.UrlEncode(address));
@@ -129,7 +145,7 @@ public Task> GeocodeAsync(string address)
public Task> GeocodeAsync(string address, CancellationToken cancellationToken)
{
- if (String.IsNullOrEmpty(address))
+ if (string.IsNullOrEmpty(address))
throw new ArgumentNullException("address");
HttpWebRequest request = BuildWebRequest("address", HttpUtility.UrlEncode(address));
@@ -150,12 +166,12 @@ public Task> ReverseGeocodeAsync(double latitude, dou
private string BuildAddress(string street, string city, string state, string postalCode, string country)
{
- return String.Format("{0} {1}, {2} {3}, {4}", street, city, state, postalCode, country);
+ return string.Format("{0} {1}, {2} {3}, {4}", street, city, state, postalCode, country);
}
private string BuildGeolocation(double latitude, double longitude)
{
- return String.Format(CultureInfo.InvariantCulture, "{0},{1}", latitude, longitude);
+ return string.Format(CultureInfo.InvariantCulture, "{0},{1}", latitude, longitude);
}
private IEnumerable ProcessRequest(HttpWebRequest request)
@@ -289,12 +305,12 @@ Task> IAsyncGeocoder.ReverseGeocodeAsync(double latitude, d
private HttpWebRequest BuildWebRequest(string type, string value)
{
- string url = String.Format(ServiceUrl, type, value);
+ string url = string.Format(ServiceUrl, type, value);
if (BusinessKey != null)
url = BusinessKey.GenerateSignature(url);
- HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
+ var req = WebRequest.Create(url) as HttpWebRequest;
req.Proxy = Proxy;
req.Method = "GET";
return req;
diff --git a/src/MapQuest/BaseRequest.cs b/src/MapQuest/BaseRequest.cs
new file mode 100644
index 0000000..d87820a
--- /dev/null
+++ b/src/MapQuest/BaseRequest.cs
@@ -0,0 +1,146 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Web;
+using Newtonsoft.Json;
+
+namespace Geocoding.MapQuest
+{
+ ///
+ /// Geo-code request object
+ ///
+ ///
+ public abstract class BaseRequest
+ {
+ protected BaseRequest(string key) //output only, no need for default ctor
+ {
+ Key = key;
+ }
+
+ [JsonIgnore]
+ string key;
+ ///
+ /// A REQUIRED unique key to authorize use of the Routing Service.
+ ///
+ ///
+ [JsonIgnore]
+ public virtual string Key
+ {
+ get { return key; }
+ set
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ throw new ArgumentException("An application key is required for MapQuest");
+
+ key = value;
+ }
+ }
+
+ ///
+ /// Defaults to json
+ ///
+ [JsonIgnore]
+ public virtual DataFormat InputFormat { get; private set; }
+
+ ///
+ /// Defaults to json
+ ///
+ [JsonIgnore]
+ public virtual DataFormat OutputFormat { get; private set; }
+
+ [JsonIgnore]
+ RequestOptions op = new RequestOptions();
+ ///
+ /// Optional settings
+ ///
+ [JsonProperty("options")]
+ public virtual RequestOptions Options
+ {
+ get { return op; }
+ protected set
+ {
+ if (value == null)
+ throw new ArgumentNullException("Options");
+
+ op = value;
+ }
+ }
+
+ ///
+ /// if true, use Open Street Map, else use commercial map
+ ///
+ public virtual bool UseOSM { get; set; }
+
+ ///
+ /// We are using v1 of MapQuest OSM API
+ ///
+ protected virtual string BaseRequestPath
+ {
+ get
+ {
+ if (UseOSM)
+ return @"http://open.mapquestapi.com/geocoding/v1/";
+ else
+ return @"http://www.mapquestapi.com/geocoding/v1/";
+ }
+ }
+
+ ///
+ /// The full path for the request
+ ///
+ [JsonIgnore]
+ public virtual Uri RequestUri
+ {
+ get
+ {
+ var sb = new StringBuilder(BaseRequestPath);
+ sb.Append(RequestAction);
+ sb.Append("?");
+ //no need to escape this key, it is already escaped by MapQuest at generation
+ sb.AppendFormat("key={0}&", Key);
+
+ if (InputFormat != DataFormat.json)
+ sb.AppendFormat("inFormat={0}&", InputFormat);
+
+ if (OutputFormat != DataFormat.json)
+ sb.AppendFormat("outFormat={0}&", OutputFormat);
+
+ sb.Length--;
+ return new Uri(sb.ToString());
+ }
+ }
+
+ [JsonIgnore]
+ public abstract string RequestAction { get; }
+
+ [JsonIgnore]
+ string _verb = "POST";
+ ///
+ /// Default request verb is POST for security and large batch payloads
+ ///
+ [JsonIgnore]
+ public virtual string RequestVerb
+ {
+ get { return _verb; }
+ protected set { _verb = string.IsNullOrWhiteSpace(value) ? "POST" : value.Trim().ToUpper(); }
+ }
+
+ ///
+ /// Request body if request verb is applicable (POST, PUT, etc)
+ ///
+ [JsonIgnore]
+ public virtual string RequestBody
+ {
+ get
+ {
+ return this.ToJSON();
+ }
+ }
+
+ public override string ToString()
+ {
+ return this.RequestBody;
+ }
+ }
+}
diff --git a/src/MapQuest/BatchGeocodeRequest.cs b/src/MapQuest/BatchGeocodeRequest.cs
new file mode 100644
index 0000000..c3d3e38
--- /dev/null
+++ b/src/MapQuest/BatchGeocodeRequest.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Web;
+using Newtonsoft.Json;
+
+namespace Geocoding.MapQuest
+{
+ public class BatchGeocodeRequest : BaseRequest
+ {
+ public BatchGeocodeRequest(string key, ICollection addresses)
+ : base(key)
+ {
+ if (addresses.IsNullOrEmpty())
+ throw new ArgumentException("addresses can not be null or empty");
+
+ Locations = (from l in addresses select new LocationRequest(l)).ToArray();
+ }
+
+ [JsonIgnore]
+ readonly List _locations = new List();
+ ///
+ /// Required collection of concatenated address string
+ /// Note input will be hashed for uniqueness.
+ /// Order is not guaranteed.
+ ///
+ [JsonProperty("locations")]
+ public ICollection Locations
+ {
+ get { return _locations; }
+ set
+ {
+ if (value.IsNullOrEmpty())
+ throw new ArgumentNullException("Locations can not be null or empty!");
+
+ _locations.Clear();
+ (from v in value
+ where v != null
+ select v).ForEach(v => _locations.Add(v));
+
+ if (_locations.Count == 0)
+ throw new InvalidOperationException("At least one valid Location is required");
+ }
+ }
+
+ public override string RequestAction
+ {
+ get { return "batch"; }
+ }
+ }
+}
diff --git a/src/MapQuest/ClassDiagram.cd b/src/MapQuest/ClassDiagram.cd
new file mode 100644
index 0000000..1afd152
--- /dev/null
+++ b/src/MapQuest/ClassDiagram.cd
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+
+ AAgAAAAAADAAAABMIAAAAEQAIIMwAAAAAQIAhAAAAAA=
+ MapQuestLocation.cs
+
+
+
+
+
+
+
+
+ AIAgAAAAAAAAAAQBAEAEASAgAAAAAAAAAAAAAAAAQAA=
+ MapQuestGeocoder.cs
+
+
+
+
+
+
+
+
+
+
+ AAAAAAAAAAAAAAUEAAAOAQABAAAAAAAACAAYAgBAAAA=
+ BaseRequest.cs
+
+
+
+
+
+
+
+
+
+
+
+
+ AAAAAAAAAAAAAAAAAAIIAEAAAAAAAAAAAAAAAAAAAAA=
+ BatchGeocodeRequest.cs
+
+
+
+
+
+
+
+
+
+
+
+
+ AAAAAAAAAAAAAAAEAAIAAAACAAAAAgAAAAIAAAAAAAA=
+ LocationRequest.cs
+
+
+
+
+
+
+
+
+
+ AAAAAAAAAAAAAAAAAAIICAAAAAAAAAAAAAAAAAAAAAA=
+ ReverseGeocodeRequest.cs
+
+
+
+
+
+
+
+
+ AAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAA=
+ GeocodeRequest.cs
+
+
+
+
+
+
+
+
+ AAAAAACAAAAAAAABAAAAAAEAAAAAAQAAAAAAIAAAAAA=
+ RequestOptions.cs
+
+
+
+
+
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJAAAAAAAAA=
+ LocationType.cs
+
+
+
+
+
+ AAAAAAAAAAAACAAAIAAAAAAAAEAAAAAAACABAAAAAAA=
+ ResponseStatus.cs
+
+
+
+
+
+ QACAAAAAAAAAAAAAgAAAAIAABkAIAAAAAAAAACAAAAA=
+ Quality.cs
+
+
+
+
+
+ AAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAABACAAAAAAA=
+ DataFormat.cs
+
+
+
+
\ No newline at end of file
diff --git a/src/MapQuest/DataFormat.cs b/src/MapQuest/DataFormat.cs
new file mode 100644
index 0000000..70b91a5
--- /dev/null
+++ b/src/MapQuest/DataFormat.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Geocoding.MapQuest
+{
+ public enum DataFormat
+ {
+ json,
+ xml,
+ csv,
+ }
+}
diff --git a/src/MapQuest/GeocodeRequest.cs b/src/MapQuest/GeocodeRequest.cs
new file mode 100644
index 0000000..a2dbc6b
--- /dev/null
+++ b/src/MapQuest/GeocodeRequest.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Newtonsoft.Json;
+
+namespace Geocoding.MapQuest
+{
+ public class GeocodeRequest : ReverseGeocodeRequest
+ {
+ public GeocodeRequest(string key, string address)
+ : this(key, new LocationRequest(address))
+ {
+ }
+
+ public GeocodeRequest(string key, LocationRequest loc)
+ : base(key, loc)
+ {
+ }
+
+ public override string RequestAction
+ {
+ get { return "address"; }
+ }
+ }
+}
diff --git a/src/MapQuest/LocationRequest.cs b/src/MapQuest/LocationRequest.cs
new file mode 100644
index 0000000..a1cb338
--- /dev/null
+++ b/src/MapQuest/LocationRequest.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Newtonsoft.Json;
+
+namespace Geocoding.MapQuest
+{
+ public class LocationRequest
+ {
+ public LocationRequest(string street)
+ {
+ Street = street;
+ }
+
+ public LocationRequest(Location location)
+ {
+ Location = location;
+ }
+
+ [JsonIgnore]
+ string street;
+ ///
+ /// Full street address or intersection for geocoding
+ ///
+ [JsonProperty("street", NullValueHandling = NullValueHandling.Ignore)]
+ public virtual string Street
+ {
+ get { return street; }
+ set
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ throw new ArgumentException("Street can not be null or blank");
+
+ street = value;
+ }
+ }
+
+ [JsonIgnore]
+ Location location;
+ ///
+ /// Latitude and longitude for reverse geocoding
+ ///
+ [JsonProperty("latLng", NullValueHandling=NullValueHandling.Ignore)]
+ public virtual Location Location
+ {
+ get { return location; }
+ set
+ {
+ if (value == null)
+ throw new ArgumentNullException("Location");
+
+ location = value;
+ }
+ }
+
+ public override string ToString()
+ {
+ return string.Format("street: {0}", Street);
+ }
+
+ }
+}
diff --git a/src/MapQuest/LocationType.cs b/src/MapQuest/LocationType.cs
new file mode 100644
index 0000000..717e212
--- /dev/null
+++ b/src/MapQuest/LocationType.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace Geocoding.MapQuest
+{
+ ///
+ /// http://code.google.com/apis/maps/documentation/geocoding/#Types
+ ///
+ public enum LocationType
+ {
+ ///
+ /// Stop: default
+ ///
+ s,
+ ///
+ /// Via
+ ///
+ v,
+ }
+}
\ No newline at end of file
diff --git a/src/MapQuest/MapQuest.csproj b/src/MapQuest/MapQuest.csproj
new file mode 100644
index 0000000..c2866e5
--- /dev/null
+++ b/src/MapQuest/MapQuest.csproj
@@ -0,0 +1,95 @@
+
+
+
+ Debug
+ AnyCPU
+ 8.0.30703
+ 2.0
+ {B37FC059-5E9E-4893-994A-64D835D2A54F}
+ Library
+ Properties
+ Geocoding.MapQuest
+ Geocoding.MapQuest
+ v3.5
+ v4.0
+ 512
+
+ ..\
+ true
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ none
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ true
+
+
+ true
+
+
+ ..\geocoding.snk
+
+
+
+
+
+
+
+
+ ..\packages\Newtonsoft.Json.6.0.3\lib\net40\Newtonsoft.Json.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Properties\geocoding.snk
+
+
+
+
+
+
+ {654812CF-D009-4420-B9EC-D07B030926A1}
+ Core
+
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
\ No newline at end of file
diff --git a/src/MapQuest/MapQuestGeocoder.cs b/src/MapQuest/MapQuestGeocoder.cs
new file mode 100644
index 0000000..9d81845
--- /dev/null
+++ b/src/MapQuest/MapQuestGeocoder.cs
@@ -0,0 +1,266 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Web;
+
+namespace Geocoding.MapQuest
+{
+ ///
+ ///
+ ///
+ ///
+ public class MapQuestGeocoder : IGeocoder, IBatchGeocoder
+ {
+ readonly string key;
+
+ volatile bool useOSM;
+ ///
+ /// When true, will use the Open Street Map API
+ ///
+ public virtual bool UseOSM
+ {
+ get { return useOSM; }
+ set { useOSM = value; }
+ }
+
+ public MapQuestGeocoder(string key)
+ {
+ if (string.IsNullOrWhiteSpace(key))
+ throw new ArgumentException("key can not be null or blank");
+
+ this.key = key;
+ }
+
+ IEnumerable HandleSingleResponse(MapQuestResponse res)
+ {
+ if (res != null && !res.Results.IsNullOrEmpty())
+ {
+ return HandleSingleResponse(from r in res.Results
+ where r != null && !r.Locations.IsNullOrEmpty()
+ from l in r.Locations
+ select l);
+ }
+ else
+ return new Address[0];
+ }
+
+ IEnumerable HandleSingleResponse(IEnumerable locs)
+ {
+ if (locs == null)
+ return new Address[0];
+ else
+ {
+ return from l in locs
+ where l != null && l.Quality < Quality.COUNTRY
+ let q = (int)l.Quality
+ let c = string.IsNullOrWhiteSpace(l.Confidence) ? "ZZZZZZ" : l.Confidence
+ orderby q ascending, c ascending
+ select l;
+ }
+ }
+
+ public IEnumerable Geocode(string address)
+ {
+ if (string.IsNullOrWhiteSpace(address))
+ throw new ArgumentException("address can not be null or empty!");
+
+ var f = new GeocodeRequest(key, address) { UseOSM = this.UseOSM };
+ MapQuestResponse res = Execute(f);
+ return HandleSingleResponse(res);
+ }
+
+ public IEnumerable Geocode(string street, string city, string state, string postalCode, string country)
+ {
+ var sb = new StringBuilder ();
+ if (!string.IsNullOrWhiteSpace (street))
+ sb.AppendFormat ("{0}, ", street);
+ if (!string.IsNullOrWhiteSpace (city))
+ sb.AppendFormat ("{0}, ", city);
+ if (!string.IsNullOrWhiteSpace (state))
+ sb.AppendFormat ("{0} ", state);
+ if (!string.IsNullOrWhiteSpace (postalCode))
+ sb.AppendFormat ("{0} ", postalCode);
+ if (!string.IsNullOrWhiteSpace (country))
+ sb.AppendFormat ("{0} ", country);
+
+ if (sb.Length > 1)
+ sb.Length--;
+
+ string s = sb.ToString ().Trim ();
+ if (string.IsNullOrWhiteSpace (s))
+ throw new ArgumentException ("Concatenated input values can not be null or blank");
+
+ if (s.Last () == ',')
+ s = s.Remove (s.Length - 1);
+
+ return Geocode (s);
+ }
+
+ public IEnumerable ReverseGeocode(Location location)
+ {
+ if (location == null)
+ throw new ArgumentNullException ("location");
+
+ var f = new ReverseGeocodeRequest(key, location) { UseOSM = this.UseOSM };
+ MapQuestResponse res = Execute(f);
+ return HandleSingleResponse(res);
+ }
+
+ public IEnumerable ReverseGeocode(double latitude, double longitude)
+ {
+ return ReverseGeocode(new Location(latitude, longitude));
+ }
+
+ public MapQuestResponse Execute(BaseRequest f)
+ {
+ HttpWebRequest request = Send(f);
+ MapQuestResponse r = Parse(request);
+ if (r != null && !r.Results.IsNullOrEmpty())
+ {
+ foreach (MapQuestResult o in r.Results)
+ {
+ if (o == null)
+ continue;
+
+ foreach (MapQuestLocation l in o.Locations)
+ {
+ if (!string.IsNullOrWhiteSpace(l.FormattedAddress) || o.ProvidedLocation == null)
+ continue;
+
+ if (string.Compare(o.ProvidedLocation.FormattedAddress, "unknown", true) != 0)
+ l.FormattedAddress = o.ProvidedLocation.FormattedAddress;
+ else
+ l.FormattedAddress = o.ProvidedLocation.ToString();
+ }
+ }
+ }
+ return r;
+ }
+
+ HttpWebRequest Send(BaseRequest f)
+ {
+ if (f == null)
+ throw new ArgumentNullException("f");
+
+ HttpWebRequest request;
+ bool hasBody = false;
+ switch (f.RequestVerb)
+ {
+ case "GET":
+ case "DELETE":
+ case "HEAD":
+ {
+ var u = string.Format("{0}json={1}&", f.RequestUri, HttpUtility.UrlEncode(f.RequestBody));
+ request = WebRequest.Create(u) as HttpWebRequest;
+ }
+ break;
+ case "POST":
+ case "PUT":
+ default:
+ {
+ request = WebRequest.Create(f.RequestUri) as HttpWebRequest;
+ hasBody = !string.IsNullOrWhiteSpace(f.RequestBody);
+ }
+ break;
+ }
+ request.Method = f.RequestVerb;
+ request.ContentType = "application/" + f.InputFormat;
+ request.Expect = "application/" + f.OutputFormat;
+
+ if (hasBody)
+ {
+ byte[] buffer = Encoding.UTF8.GetBytes(f.RequestBody);
+ request.ContentLength = buffer.Length;
+ using (Stream rs = request.GetRequestStream())
+ {
+ rs.Write(buffer, 0, buffer.Length);
+ rs.Flush();
+ rs.Close();
+ }
+ }
+ return request;
+ }
+
+ MapQuestResponse Parse(HttpWebRequest request)
+ {
+ if (request == null)
+ throw new ArgumentNullException("request");
+
+ string requestInfo = string.Format("[{0}] {1}", request.Method, request.RequestUri);
+ try
+ {
+ string json;
+ using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
+ {
+ if ((int)response.StatusCode >= 300) //error
+ throw new HttpException((int)response.StatusCode, response.StatusDescription);
+
+ using (var sr = new StreamReader(response.GetResponseStream()))
+ json = sr.ReadToEnd();
+ }
+ if (string.IsNullOrWhiteSpace(json))
+ throw new ApplicationException("Remote system response with blank: " + requestInfo);
+
+ MapQuestResponse o = json.FromJSON();
+ if (o == null)
+ throw new ApplicationException("Unable to deserialize remote response: " + requestInfo + " => " + json);
+
+ return o;
+ }
+ catch (WebException wex) //convert to simple exception & close the response stream
+ {
+ using (HttpWebResponse response = wex.Response as HttpWebResponse)
+ {
+ var sb = new StringBuilder(requestInfo);
+ sb.Append(" | ");
+ sb.Append(response.StatusDescription);
+ sb.Append(" | ");
+ using (var sr = new StreamReader(response.GetResponseStream()))
+ {
+ sb.Append(sr.ReadToEnd());
+ }
+ throw new HttpException((int)response.StatusCode, sb.ToString());
+ }
+ }
+ }
+
+ public ICollection Geocode(IEnumerable addresses)
+ {
+ if (addresses == null)
+ throw new ArgumentNullException("addresses");
+
+ string[] adr = (from a in addresses
+ where !string.IsNullOrWhiteSpace(a)
+ group a by a into ag
+ select ag.Key).ToArray();
+ if (adr.IsNullOrEmpty())
+ throw new ArgumentException("Atleast one none blank item is required in addresses");
+
+ var f = new BatchGeocodeRequest(key, adr) { UseOSM = this.UseOSM };
+ MapQuestResponse res = Execute(f);
+ return HandleBatchResponse(res);
+ }
+
+ ICollection HandleBatchResponse(MapQuestResponse res)
+ {
+ if (res != null && !res.Results.IsNullOrEmpty())
+ {
+ return (from r in res.Results
+ where r != null && !r.Locations.IsNullOrEmpty()
+ let resp = HandleSingleResponse(r.Locations)
+ where resp != null
+ select new ResultItem(r.ProvidedLocation, resp)).ToArray();
+ }
+ else
+ return new ResultItem[0];
+ }
+
+ public ICollection ReverseGeocode(IEnumerable locations)
+ {
+ throw new InvalidOperationException("ReverseGeocode(...) is not available for MapQuestGeocoder.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/MapQuest/MapQuestLocation.cs b/src/MapQuest/MapQuestLocation.cs
new file mode 100644
index 0000000..d113c4c
--- /dev/null
+++ b/src/MapQuest/MapQuestLocation.cs
@@ -0,0 +1,150 @@
+using System;
+using System.Text;
+using System.Linq;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace Geocoding.MapQuest
+{
+ ///
+ /// MapQuest address obj.
+ ///
+ ///
+ public class MapQuestLocation : ParsedAddress
+ {
+ const string UNKNOWN = "unknown";
+ static readonly string DEFAULT_LOC = new Location(0, 0).ToString();
+
+ public MapQuestLocation(string formattedAddress, Location coordinates)
+ : base(
+ string.IsNullOrWhiteSpace(formattedAddress) ? UNKNOWN : formattedAddress,
+ coordinates ?? new Location(0, 0),
+ "MapQuest")
+ {
+ DisplayCoordinates = coordinates;
+ }
+
+ [JsonProperty("location")]
+ public override string FormattedAddress
+ {
+ get
+ {
+ return ToString();
+ }
+ set { base.FormattedAddress = value; }
+ }
+
+ [JsonProperty("latLng")]
+ public override Location Coordinates
+ {
+ get { return base.Coordinates; }
+ set { base.Coordinates = value; }
+ }
+
+ [JsonProperty("displayLatLng")]
+ public virtual Location DisplayCoordinates { get; set; }
+
+ [JsonProperty("street")]
+ public override string Street { get; set; }
+
+ [JsonProperty("adminArea5")]
+ public override string City { get; set; }
+
+ [JsonProperty("adminArea4")]
+ public override string County { get; set; }
+
+ [JsonProperty("adminArea3")]
+ public override string State { get; set; }
+
+ [JsonProperty("adminArea1")]
+ public override string Country { get; set; }
+
+ [JsonProperty("postalCode")]
+ public override string PostCode { get; set; }
+
+ public override string ToString()
+ {
+ if (base.FormattedAddress != UNKNOWN)
+ return base.FormattedAddress;
+ else
+ {
+ var sb = new StringBuilder();
+ if (!string.IsNullOrWhiteSpace(Street))
+ sb.AppendFormat("{0}, ", Street);
+
+ if (!string.IsNullOrWhiteSpace(City))
+ sb.AppendFormat("{0}, ", City);
+
+ if (!string.IsNullOrWhiteSpace(State))
+ sb.AppendFormat("{0} ", State);
+ else if (!string.IsNullOrWhiteSpace(County))
+ sb.AppendFormat("{0} ", County);
+
+ if (!string.IsNullOrWhiteSpace(PostCode))
+ sb.AppendFormat("{0} ", PostCode);
+
+ if (!string.IsNullOrWhiteSpace(Country))
+ sb.AppendFormat("{0} ", Country);
+
+ if (sb.Length > 1)
+ {
+ sb.Length--;
+
+ string s = sb.ToString();
+ if (s.Last() == ',')
+ s = s.Remove(s.Length - 1);
+
+ return s;
+ }
+ else if (Coordinates != null && Coordinates.ToString() != DEFAULT_LOC)
+ return Coordinates.ToString();
+ else
+ return UNKNOWN;
+ }
+ }
+
+ ///
+ /// Type of location
+ ///
+ [JsonProperty("type")]
+ public virtual LocationType Type { get; set; }
+
+ ///
+ /// Granularity code of quality/accuracy guarantee
+ ///
+ ///
+ [JsonProperty("geocodeQuality")]
+ public virtual Quality Quality { get; set; }
+
+ ///
+ /// Text string comparable, sort able score
+ ///
+ ///
+ [JsonProperty("geocodeQualityCode")]
+ public virtual string Confidence { get; set; }
+
+ ///
+ /// Identifies the closest road to the address for routing purposes.
+ ///
+ [JsonProperty("linkId")]
+ public virtual string LinkId { get; set; }
+
+ ///
+ /// Which side of the street this address is in
+ ///
+ [JsonProperty("sideOfStreet")]
+ public virtual SideOfStreet SideOfStreet { get; set; }
+
+ ///
+ /// Url to a MapQuest map
+ ///
+ [JsonProperty("mapUrl")]
+ public virtual Uri MapUrl { get; set; }
+
+ [JsonProperty("adminArea1Type")]
+ public virtual string CountryLabel { get; set; }
+
+ [JsonProperty("adminArea3Type")]
+ public virtual string StateLabel { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/MapQuest/MapQuestResponse.cs b/src/MapQuest/MapQuestResponse.cs
new file mode 100644
index 0000000..bfd9d62
--- /dev/null
+++ b/src/MapQuest/MapQuestResponse.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace Geocoding.MapQuest
+{
+ public class MapQuestResponse
+ {
+ //[JsonArray(AllowNullItems=true)]
+ [JsonProperty("results")]
+ public IList Results { get; set; }
+
+ [JsonProperty("options")]
+ public RequestOptions Options { get; set; }
+
+ [JsonProperty("info")]
+ public ResponseInfo Info { get; set; }
+ }
+}
diff --git a/src/MapQuest/MapQuestResult.cs b/src/MapQuest/MapQuestResult.cs
new file mode 100644
index 0000000..2710f94
--- /dev/null
+++ b/src/MapQuest/MapQuestResult.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace Geocoding.MapQuest
+{
+ ///
+ /// Result obj returned in a collection of OSM response under the property: results
+ ///
+ public class MapQuestResult
+ {
+ [JsonProperty("locations")]
+ public IList Locations { get; set; }
+
+ [JsonProperty("providedLocation")]
+ public MapQuestLocation ProvidedLocation { get; set; }
+ }
+}
diff --git a/src/MapQuest/Properties/.gitignore b/src/MapQuest/Properties/.gitignore
new file mode 100644
index 0000000..cfd2c47
--- /dev/null
+++ b/src/MapQuest/Properties/.gitignore
@@ -0,0 +1 @@
+/AssemblyVersion.cs
diff --git a/src/MapQuest/Properties/AssemblyInfo.cs b/src/MapQuest/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..eb735d8
--- /dev/null
+++ b/src/MapQuest/Properties/AssemblyInfo.cs
@@ -0,0 +1,3 @@
+using System.Reflection;
+
+[assembly: AssemblyTitle("Geocoding MapQuest API")]
\ No newline at end of file
diff --git a/src/MapQuest/Quality.cs b/src/MapQuest/Quality.cs
new file mode 100644
index 0000000..758f96f
--- /dev/null
+++ b/src/MapQuest/Quality.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Geocoding.MapQuest
+{
+ public enum Quality : int
+ {
+ ///
+ /// P1 A specific point location.
+ ///
+ POINT = 0,
+ ///
+ /// L1 A specific street address location.
+ ///
+ ADDRESS = 1,
+ ///
+ /// I1 An intersection of two or more streets.
+ ///
+ INTERSECTION = 2,
+ ///
+ /// B1 The center of a single street block. House number ranges are returned if available.
+ /// B2 The center of a single street block, which is located closest to the geographic center of all matching street blocks. No house number range is returned.
+ /// B3 The center of a single street block whose numbered range is nearest to the input number. House number range is returned.
+ ///
+ STREET = 3,
+ ///
+ /// Z2 Postal code. For USA, a ZIP+2.
+ /// Z3 Postal code. For USA, a ZIP+4.
+ ///
+ ZIP_EXTENDED = 4,
+ ///
+ /// Z1 Postal code, largest. For USA, a ZIP.
+ /// Z4 Postal code, smallest. Unused in USA.
+ ///
+ ZIP = 5,
+ ///
+ /// A5 Admin area. For USA, a city.
+ ///
+ CITY = 6,
+ ///
+ /// A4 Admin area. For USA, a county.
+ ///
+ COUNTY = 7,
+ ///
+ /// A3 Admin area. For USA, a state.
+ ///
+ STATE = 8,
+ ///
+ /// A1 Admin area, largest. For USA, a country.
+ ///
+ COUNTRY = 9,
+ }
+}
diff --git a/src/MapQuest/RequestOptions.cs b/src/MapQuest/RequestOptions.cs
new file mode 100644
index 0000000..6881b49
--- /dev/null
+++ b/src/MapQuest/RequestOptions.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Newtonsoft.Json;
+
+namespace Geocoding.MapQuest
+{
+ public class RequestOptions
+ {
+ /////
+ ///// A delimiter is used only when outFormat=csv. The delimiter is the single character used to separate the fields of a character delimited file.
+ ///// The delimiter defaults to a comma(,).
+ ///// The valid choices are ,|:;
+ /////
+ //[JsonProperty("delimiter")]
+ //public virtual char Delimiter { get; private set; }
+
+ [JsonIgnore]
+ int _maxResults = -1;
+ ///
+ /// The number of results to limit the response to in the case of an ambiguous address.
+ /// Defaults: -1 (indicates no limit)
+ ///
+ [JsonProperty("maxResults")]
+ public virtual int MaxResults
+ {
+ get { return _maxResults; }
+ set { _maxResults = value > 0 ? value : -1; }
+ }
+
+ ///
+ /// This parameter tells the service whether it should return a URL to a static map thumbnail image for a location being geocoded.
+ ///
+ [JsonProperty("thumbMaps")]
+ public virtual bool ThumbMap { get; set; }
+
+ ///
+ /// This option tells the service whether it should fail when given a latitude/longitude pair in an address or batch geocode call, or if it should ignore that and try and geo-code what it can.
+ ///
+ [JsonProperty("ignoreLatLngInput")]
+ public virtual bool IgnoreLatLngInput { get; set; }
+
+ ///
+ /// Optional name of JSONP callback method.
+ ///
+ [JsonProperty("callback", NullValueHandling = NullValueHandling.Ignore)]
+ public virtual string JsonpCallBack { get; set; }
+ }
+}
diff --git a/src/MapQuest/ResponseInfo.cs b/src/MapQuest/ResponseInfo.cs
new file mode 100644
index 0000000..55c128f
--- /dev/null
+++ b/src/MapQuest/ResponseInfo.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Newtonsoft.Json;
+
+namespace Geocoding.MapQuest
+{
+ public class ResponseInfo
+ {
+ ///
+ /// Extended copyright info
+ ///
+ //[JsonDictionary]
+ [JsonProperty("copyright")]
+ public IDictionary Copyright { get; set; }
+
+ ///
+ /// Maps to HTTP response code generally
+ ///
+ [JsonProperty("statuscode")]
+ public ResponseStatus Status { get; set; }
+
+ ///
+ /// Error or status messages if applicable
+ ///
+ //[JsonArray(AllowNullItems=true)]
+ [JsonProperty("messages")]
+ public IList Messages { get; set; }
+ }
+}
diff --git a/src/MapQuest/ResponseStatus.cs b/src/MapQuest/ResponseStatus.cs
new file mode 100644
index 0000000..3f41536
--- /dev/null
+++ b/src/MapQuest/ResponseStatus.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Geocoding.MapQuest
+{
+ public enum ResponseStatus : int
+ {
+ Ok = 0,
+ OkBatch = 100,
+ ErrorInput = 400,
+ ErrorAccountKey = 403,
+ ErrorUnknown = 500,
+ }
+}
\ No newline at end of file
diff --git a/src/MapQuest/ReverseGeocodeRequest.cs b/src/MapQuest/ReverseGeocodeRequest.cs
new file mode 100644
index 0000000..bdb5575
--- /dev/null
+++ b/src/MapQuest/ReverseGeocodeRequest.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Newtonsoft.Json;
+
+namespace Geocoding.MapQuest
+{
+ public class ReverseGeocodeRequest : BaseRequest
+ {
+ public ReverseGeocodeRequest(string key, double latitude, double longitude)
+ : this(key, new Location(latitude, longitude))
+ {
+
+ }
+
+ public ReverseGeocodeRequest(string key, Location loc)
+ : this(key, new LocationRequest(loc))
+ {
+ }
+
+ public ReverseGeocodeRequest(string key, LocationRequest loc)
+ : base(key)
+ {
+ Location = loc;
+ }
+
+ [JsonIgnore]
+ LocationRequest loc;
+ ///
+ /// Latitude and longitude for the request
+ ///
+ [JsonProperty("location")]
+ public virtual LocationRequest Location
+ {
+ get { return loc; }
+ set
+ {
+ if (value == null)
+ throw new ArgumentNullException("Location");
+
+ loc = value;
+ }
+ }
+
+ [JsonIgnore]
+ public override string RequestAction
+ {
+ get { return "reverse"; }
+ }
+ }
+}
diff --git a/src/MapQuest/SideOfStreet.cs b/src/MapQuest/SideOfStreet.cs
new file mode 100644
index 0000000..29a1675
--- /dev/null
+++ b/src/MapQuest/SideOfStreet.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Geocoding.MapQuest
+{
+ public enum SideOfStreet
+ {
+ ///
+ /// None: default
+ ///
+ N,
+ ///
+ /// Left
+ ///
+ L,
+ ///
+ /// Right
+ ///
+ R,
+ }
+}
diff --git a/src/MapQuest/packages.config b/src/MapQuest/packages.config
new file mode 100644
index 0000000..12fef57
--- /dev/null
+++ b/src/MapQuest/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft/BingMapsGeocoder.cs b/src/Microsoft/BingMapsGeocoder.cs
index 7a08ff6..2447749 100644
--- a/src/Microsoft/BingMapsGeocoder.cs
+++ b/src/Microsoft/BingMapsGeocoder.cs
@@ -35,6 +35,9 @@ public class BingMapsGeocoder : IGeocoder, IAsyncGeocoder
public BingMapsGeocoder(string bingKey)
{
+ if (string.IsNullOrWhiteSpace(bingKey))
+ throw new ArgumentException("bingKey can not be null or empty");
+
this.bingKey = bingKey;
}
@@ -64,14 +67,14 @@ private string GetQueryUrl(string street, string city, string state, string post
private string GetQueryUrl(double latitude, double longitude)
{
- var builder = new StringBuilder(string.Format(UNFORMATTED_QUERY, String.Format(CultureInfo.InvariantCulture, "{0},{1}", latitude, longitude), bingKey));
+ var builder = new StringBuilder(string.Format(UNFORMATTED_QUERY, string.Format(CultureInfo.InvariantCulture, "{0},{1}", latitude, longitude), bingKey));
AppendGlobalParameters(builder, false);
return builder.ToString();
}
private IEnumerable> GetGlobalParameters()
{
- if (!String.IsNullOrEmpty(Culture))
+ if (!string.IsNullOrEmpty(Culture))
yield return new KeyValuePair("c", Culture);
if (UserLocation != null)
@@ -295,7 +298,7 @@ private IEnumerable ParseResponse(Json.Response response)
private HttpWebRequest CreateRequest(string url)
{
- HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
+ var request = WebRequest.Create(url) as HttpWebRequest;
request.Proxy = Proxy;
return request;
}
diff --git a/src/Microsoft/Microsoft.csproj b/src/Microsoft/Microsoft.csproj
index f268563..6dac222 100644
--- a/src/Microsoft/Microsoft.csproj
+++ b/src/Microsoft/Microsoft.csproj
@@ -42,25 +42,22 @@
- 3.5
+ 4.0
- 3.0
+ 4.0
- 3.0
+ 4.0
- 3.5
+ 4.0
-
+
-
- Properties\AssemblyInfoCommon.cs
-
@@ -69,7 +66,6 @@
-
diff --git a/src/Tests/.gitignore b/src/Tests/.gitignore
new file mode 100644
index 0000000..b36c8ce
--- /dev/null
+++ b/src/Tests/.gitignore
@@ -0,0 +1,2 @@
+/app.config.temp
+/app.config
diff --git a/src/Tests/AddressAssertionExtensions.cs b/src/Tests/AddressAssertionExtensions.cs
index 61e3e52..6ac30b4 100644
--- a/src/Tests/AddressAssertionExtensions.cs
+++ b/src/Tests/AddressAssertionExtensions.cs
@@ -7,17 +7,24 @@ public static class AddressAssertionExtensions
{
public static void AssertWhiteHouse(this Address address)
{
+ string adr = address.FormattedAddress.ToLower();
Assert.True(
- address.FormattedAddress.Contains("The White House") ||
- address.FormattedAddress.Contains("1600 Pennsylvania Ave NW") ||
- address.FormattedAddress.Contains("1600 Pennsylvania Avenue Northwest")
+ adr.Contains("The White House") ||
+ adr.Contains("1600 pennsylvania ave nw") ||
+ adr.Contains("1600 pennsylvania avenue northwest") ||
+ adr.Contains("1600 pennsylvania avenue nw") ||
+ adr.Contains("1600 pennsylvania ave northwest")
);
AssertWhiteHouseArea(address);
}
public static void AssertWhiteHouseArea(this Address address)
{
- Assert.True(address.FormattedAddress.Contains("Washington, DC"));
+ string adr = address.FormattedAddress.ToLower();
+ Assert.True(
+ adr.Contains("washington") &&
+ (adr.Contains("dc") || adr.Contains("district of columbia"))
+ );
//just hoping that each geocoder implementation gets it somewhere near the vicinity
double lat = Math.Round(address.Coordinates.Latitude, 2);
@@ -29,9 +36,11 @@ public static void AssertWhiteHouseArea(this Address address)
public static void AssertCanadianPrimeMinister(this Address address)
{
- Assert.True(address.FormattedAddress.Contains("24 Sussex"));
- Assert.True(address.FormattedAddress.Contains("Ottawa, ON"));
- Assert.True(address.FormattedAddress.Contains("K1M"));
+ string adr = address.FormattedAddress.ToLower();
+ Assert.True(adr.Contains("24 sussex"));
+ Assert.True(adr.Contains(" ottawa"));
+ Assert.True(adr.Contains(" on"));
+ Assert.True(adr.Contains("k1m"));
}
}
}
\ No newline at end of file
diff --git a/src/Tests/BatchGeocoderTest.cs b/src/Tests/BatchGeocoderTest.cs
new file mode 100644
index 0000000..e00f643
--- /dev/null
+++ b/src/Tests/BatchGeocoderTest.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Threading;
+using System.Text;
+using Xunit;
+using Xunit.Extensions;
+
+namespace Geocoding.Tests
+{
+ public abstract class BatchGeocoderTest
+ {
+ readonly IBatchGeocoder batchGeocoder;
+
+ public BatchGeocoderTest()
+ {
+ Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("en-us");
+
+ batchGeocoder = CreateBatchGeocoder();
+ }
+
+ protected abstract IBatchGeocoder CreateBatchGeocoder();
+
+ [Theory]
+ [PropertyData("BatchGeoCodeData")]
+ public virtual void CanGeoCodeAddress(string[] addresses)
+ {
+ Assert.NotEmpty(addresses);
+
+ ICollection results = batchGeocoder.Geocode(addresses);
+ Assert.NotEmpty(results);
+ Assert.Equal(addresses.Length, results.Count);
+
+ var ahash = new HashSet(addresses);
+ Assert.Equal(ahash.Count, results.Count);
+
+ foreach (ResultItem r in results)
+ {
+ Assert.NotNull(r);
+ Assert.NotNull(r.Request);
+ Assert.NotNull(r.Response);
+
+ Assert.Contains(r.Request.FormattedAddress, ahash);
+
+ Address[] respa = r.Response.ToArray();
+ Assert.NotEmpty(respa);
+
+ ahash.Remove(r.Request.FormattedAddress);
+ }
+ Assert.Empty(ahash);
+ }
+
+ public static IEnumerable