diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..04a1c0e
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,8 @@
+root = true
+
+[*.cs]
+end_of_line = lf
+insert_final_newline = true
+indent_style = tab
+tab_width = 4
+charset = utf-8-bom
diff --git a/Geocoding.sln b/Geocoding.sln
index e86f359..6cbe577 100644
--- a/Geocoding.sln
+++ b/Geocoding.sln
@@ -17,6 +17,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Geocoding.Yahoo", "src\Geoc
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Geocoding.Tests", "test\Geocoding.Tests\Geocoding.Tests.csproj", "{B87FBD8D-1E79-49F0-B06D-B4B15C1BA9C4}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Geocoding.Here", "src\Geocoding.Here\Geocoding.Here.csproj", "{41F9E0D3-2094-4CE7-A2CD-F3CAF585A048}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -47,6 +49,10 @@ Global
{B87FBD8D-1E79-49F0-B06D-B4B15C1BA9C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B87FBD8D-1E79-49F0-B06D-B4B15C1BA9C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B87FBD8D-1E79-49F0-B06D-B4B15C1BA9C4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {41F9E0D3-2094-4CE7-A2CD-F3CAF585A048}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {41F9E0D3-2094-4CE7-A2CD-F3CAF585A048}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {41F9E0D3-2094-4CE7-A2CD-F3CAF585A048}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {41F9E0D3-2094-4CE7-A2CD-F3CAF585A048}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -56,6 +62,10 @@ Global
{B46F0315-6CE4-414A-BDDA-186090F5062C} = {F742864D-9400-4CE2-957A-DBD0A0237277}
{27F58640-D424-40F5-945E-42BF8BC872A0} = {F742864D-9400-4CE2-957A-DBD0A0237277}
{9773C7DA-DD1A-490D-B4D8-CEA18804AD1E} = {F742864D-9400-4CE2-957A-DBD0A0237277}
+ {41F9E0D3-2094-4CE7-A2CD-F3CAF585A048} = {F742864D-9400-4CE2-957A-DBD0A0237277}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {085E97E6-56E0-4099-94E9-10F9080F2DD1}
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
StartupItem = Tests\Tests.csproj
diff --git a/README.md b/README.md
index bcec448..5fb2b3e 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,7 @@ Includes a model and interface for communicating with five popular Geocoding pro
* [Bing Maps (aka Virtual Earth)](http://www.microsoft.com/maps/) - [docs](http://msdn.microsoft.com/en-us/library/ff701715.aspx)
* :warning: MapQuest [(Commercial API)](http://www.mapquestapi.com/) - [docs](http://www.mapquestapi.com/geocoding/)
* :warning: MapQuest [(OpenStreetMap)](http://open.mapquestapi.com/) - [docs](http://open.mapquestapi.com/geocoding/)
+ * [HERE](https://www.here.com/) - [docs](https://developer.here.com/documentation)
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.
@@ -29,6 +30,7 @@ Install-Package Geocoding.Google
Install-Package Geocoding.MapQuest
Install-Package Geocoding.Microsoft
Install-Package Geocoding.Yahoo
+Install-Package Geocoding.Here
```
## Example Usage
@@ -36,7 +38,7 @@ Install-Package Geocoding.Yahoo
### Simple Example
```csharp
-IGeocoder geocoder = new GoogleGeocoder() { ApiKey = "this-is-my-optional-google-api-key" };
+IGeocoder geocoder = new GoogleGeocoder() { ApiKey = "this-is-my-google-api-key" };
IEnumerable
addresses = await geocoder.GeocodeAsync("1600 pennsylvania ave washington dc");
Console.WriteLine("Formatted: " + addresses.First().FormattedAddress); //Formatted: 1600 Pennsylvania Ave SE, Washington, DC 20003, USA
Console.WriteLine("Coordinates: " + addresses.First().Coordinates.Latitude + ", " + addresses.First().Coordinates.Longitude); //Coordinates: 38.8791981, -76.9818437
@@ -63,7 +65,7 @@ The Microsoft and Yahoo implementations each provide their own address class as
## API Keys
-Google allows anonymous access to it's API, but if you start hitting rate limits, you must [sign up for a new Server API Key](https://developers.google.com/maps/documentation/javascript/tutorial#api_key).
+Google [requires a new Server API Key](https://developers.google.com/maps/documentation/javascript/tutorial#api_key) to access its service.
Bing [requires an API key](http://msdn.microsoft.com/en-us/library/ff428642.aspx) to access its service.
@@ -71,6 +73,8 @@ You will need a [consumer secret and consumer key](http://developer.yahoo.com/bo
MapQuest API requires a key. Sign up here: (http://developer.mapquest.com/web/products/open)
+HERE requires an [app ID and app Code](https://developer.here.com/?create=Freemium-Basic&keepState=true&step=account)
+
## How to Build from Source
```
diff --git a/src/Geocoding.Core/Bounds.cs b/src/Geocoding.Core/Bounds.cs
index 2ecbec8..212ce48 100644
--- a/src/Geocoding.Core/Bounds.cs
+++ b/src/Geocoding.Core/Bounds.cs
@@ -1,4 +1,5 @@
-using System;
+using Newtonsoft.Json;
+using System;
namespace Geocoding
{
@@ -20,6 +21,7 @@ public Location NorthEast
public Bounds(double southWestLatitude, double southWestLongitude, double northEastLatitude, double northEastLongitude)
: this(new Location(southWestLatitude, southWestLongitude), new Location(northEastLatitude, northEastLongitude)) { }
+ [JsonConstructor]
public Bounds(Location southWest, Location northEast)
{
if (southWest == null)
diff --git a/src/Geocoding.Core/Geocoding.Core.csproj b/src/Geocoding.Core/Geocoding.Core.csproj
index 82b8790..aa73f73 100644
--- a/src/Geocoding.Core/Geocoding.Core.csproj
+++ b/src/Geocoding.Core/Geocoding.Core.csproj
@@ -5,7 +5,7 @@
Geocoding.net Core
4.0.0-beta1
chadly
- netstandard1.3
+ netstandard1.3;net46
Geocoding.Core
Geocoding.Core
geocoding;geocode;geocoder;maps;address;validation;normalization;google-maps;bing-maps;yahoo-placefinder;mapquest
diff --git a/src/Geocoding.Core/GeocodingException.cs b/src/Geocoding.Core/GeocodingException.cs
new file mode 100644
index 0000000..f3c853f
--- /dev/null
+++ b/src/Geocoding.Core/GeocodingException.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Geocoding.Core
+{
+ public class GeocodingException : Exception
+ {
+ public GeocodingException(string message, Exception innerException = null)
+ : base(message, innerException)
+ {
+ }
+ }
+}
diff --git a/src/Geocoding.Core/IBatchGeocoder.cs b/src/Geocoding.Core/IBatchGeocoder.cs
index 7395159..74081b4 100644
--- a/src/Geocoding.Core/IBatchGeocoder.cs
+++ b/src/Geocoding.Core/IBatchGeocoder.cs
@@ -1,11 +1,12 @@
using System.Collections.Generic;
+using System.Threading;
using System.Threading.Tasks;
namespace Geocoding
{
public interface IBatchGeocoder
{
- Task> GeocodeAsync(IEnumerable addresses);
- Task> ReverseGeocodeAsync(IEnumerable locations);
+ Task> GeocodeAsync(IEnumerable addresses, CancellationToken cancellationToken = default(CancellationToken));
+ Task> ReverseGeocodeAsync(IEnumerable locations, CancellationToken cancellationToken = default(CancellationToken));
}
}
diff --git a/src/Geocoding.Core/IGeocoder.cs b/src/Geocoding.Core/IGeocoder.cs
index 08c9bee..10dda71 100644
--- a/src/Geocoding.Core/IGeocoder.cs
+++ b/src/Geocoding.Core/IGeocoder.cs
@@ -1,14 +1,15 @@
using System.Collections.Generic;
+using System.Threading;
using System.Threading.Tasks;
namespace Geocoding
{
public interface IGeocoder
{
- Task> GeocodeAsync(string address);
- Task> GeocodeAsync(string street, string city, string state, string postalCode, string country);
+ Task> GeocodeAsync(string address, CancellationToken cancellationToken = default(CancellationToken));
+ Task> GeocodeAsync(string street, string city, string state, string postalCode, string country, CancellationToken cancellationToken = default(CancellationToken));
- Task> ReverseGeocodeAsync(Location location);
- Task> ReverseGeocodeAsync(double latitude, double longitude);
+ Task> ReverseGeocodeAsync(Location location, CancellationToken cancellationToken = default(CancellationToken));
+ Task> ReverseGeocodeAsync(double latitude, double longitude, CancellationToken cancellationToken = default(CancellationToken));
}
}
\ No newline at end of file
diff --git a/src/Geocoding.Google/Geocoding.Google.csproj b/src/Geocoding.Google/Geocoding.Google.csproj
index 7e5c703..0d93df3 100644
--- a/src/Geocoding.Google/Geocoding.Google.csproj
+++ b/src/Geocoding.Google/Geocoding.Google.csproj
@@ -5,7 +5,7 @@
Geocoding.net Google
4.0.0-beta1
chadly
- netstandard1.3
+ netstandard1.3;net46
Geocoding.Google
Geocoding.Google
geocoding;geocode;geocoder;maps;address;validation;normalization;google-maps;bing-maps;yahoo-placefinder;mapquest
@@ -21,9 +21,13 @@
-
+
+
+
+
+
diff --git a/src/Geocoding.Google/GoogleGeocoder.cs b/src/Geocoding.Google/GoogleGeocoder.cs
index 0861f0b..7125758 100644
--- a/src/Geocoding.Google/GoogleGeocoder.cs
+++ b/src/Geocoding.Google/GoogleGeocoder.cs
@@ -6,6 +6,7 @@
using System.Net;
using System.Net.Http;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
using System.Xml.XPath;
@@ -124,27 +125,27 @@ public string ServiceUrl
}
}
- public async Task> GeocodeAsync(string address)
+ public async Task> GeocodeAsync(string address, CancellationToken cancellationToken = default(CancellationToken))
{
if (string.IsNullOrEmpty(address))
throw new ArgumentNullException("address");
var request = BuildWebRequest("address", WebUtility.UrlEncode(address));
- return await ProcessRequest(request).ConfigureAwait(false);
+ return await ProcessRequest(request, cancellationToken).ConfigureAwait(false);
}
- public async Task> ReverseGeocodeAsync(Location location)
+ public async Task> ReverseGeocodeAsync(Location location, CancellationToken cancellationToken = default(CancellationToken))
{
if (location == null)
throw new ArgumentNullException("location");
- return await ReverseGeocodeAsync(location.Latitude, location.Longitude).ConfigureAwait(false);
+ return await ReverseGeocodeAsync(location.Latitude, location.Longitude, cancellationToken).ConfigureAwait(false);
}
- public async Task> ReverseGeocodeAsync(double latitude, double longitude)
+ public async Task> ReverseGeocodeAsync(double latitude, double longitude, CancellationToken cancellationToken = default(CancellationToken))
{
var request = BuildWebRequest("latlng", BuildGeolocation(latitude, longitude));
- return await ProcessRequest(request).ConfigureAwait(false);
+ return await ProcessRequest(request, cancellationToken).ConfigureAwait(false);
}
private string BuildAddress(string street, string city, string state, string postalCode, string country)
@@ -157,13 +158,13 @@ private string BuildGeolocation(double latitude, double longitude)
return string.Format(CultureInfo.InvariantCulture, "{0:0.00000000},{1:0.00000000}", latitude, longitude);
}
- private async Task> ProcessRequest(HttpRequestMessage request)
+ private async Task> ProcessRequest(HttpRequestMessage request, CancellationToken cancellationToken)
{
try
{
using (var client = BuildClient())
{
- return await ProcessWebResponse(await client.SendAsync(request).ConfigureAwait(false)).ConfigureAwait(false);
+ return await ProcessWebResponse(await client.SendAsync(request, cancellationToken).ConfigureAwait(false)).ConfigureAwait(false);
}
}
catch (GoogleGeocodingException)
@@ -188,24 +189,24 @@ HttpClient BuildClient()
return new HttpClient(handler);
}
- async Task> IGeocoder.GeocodeAsync(string address)
+ async Task> IGeocoder.GeocodeAsync(string address, CancellationToken cancellationToken)
{
- return await GeocodeAsync(address).ConfigureAwait(false);
+ return await GeocodeAsync(address, cancellationToken).ConfigureAwait(false);
}
- async Task> IGeocoder.GeocodeAsync(string street, string city, string state, string postalCode, string country)
+ async Task> IGeocoder.GeocodeAsync(string street, string city, string state, string postalCode, string country, CancellationToken cancellationToken)
{
- return await GeocodeAsync(BuildAddress(street, city, state, postalCode, country)).ConfigureAwait(false);
+ return await GeocodeAsync(BuildAddress(street, city, state, postalCode, country), cancellationToken).ConfigureAwait(false);
}
- async Task> IGeocoder.ReverseGeocodeAsync(Location location)
+ async Task> IGeocoder.ReverseGeocodeAsync(Location location, CancellationToken cancellationToken)
{
- return await ReverseGeocodeAsync(location).ConfigureAwait(false);
+ return await ReverseGeocodeAsync(location, cancellationToken).ConfigureAwait(false);
}
- async Task> IGeocoder.ReverseGeocodeAsync(double latitude, double longitude)
+ async Task> IGeocoder.ReverseGeocodeAsync(double latitude, double longitude, CancellationToken cancellationToken)
{
- return await ReverseGeocodeAsync(latitude, longitude).ConfigureAwait(false);
+ return await ReverseGeocodeAsync(latitude, longitude, cancellationToken).ConfigureAwait(false);
}
private HttpRequestMessage BuildWebRequest(string type, string value)
diff --git a/src/Geocoding.Google/GoogleGeocodingException.cs b/src/Geocoding.Google/GoogleGeocodingException.cs
index 16ed9c6..855e7d1 100644
--- a/src/Geocoding.Google/GoogleGeocodingException.cs
+++ b/src/Geocoding.Google/GoogleGeocodingException.cs
@@ -1,8 +1,9 @@
using System;
+using Geocoding.Core;
namespace Geocoding.Google
{
- public class GoogleGeocodingException : Exception
+ public class GoogleGeocodingException : GeocodingException
{
const string defaultMessage = "There was an error processing the geocoding request. See Status or InnerException for more information.";
diff --git a/src/Geocoding.Here/Geocoding.Here.csproj b/src/Geocoding.Here/Geocoding.Here.csproj
new file mode 100644
index 0000000..93b4da5
--- /dev/null
+++ b/src/Geocoding.Here/Geocoding.Here.csproj
@@ -0,0 +1,37 @@
+
+
+
+ Includes a model and interface for communicating with four popular Geocoding providers. Current implementations include: Google Maps, Yahoo! PlaceFinder, Bing Maps (aka Virtual Earth), and Mapquest. 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.
+ Geocoding.net Here
+ 4.0.0-beta1
+ chadly
+ netstandard1.3;net46
+ Geocoding.Here
+ Geocoding.Here
+ geocoding;geocode;geocoder;maps;address;validation;normalization;google-maps;bing-maps;yahoo-placefinder;mapquest
+ https://github.com/chadly/Geocoding.net/releases/latest
+ https://github.com/chadly/Geocoding.net
+ https://github.com/chadly/Geocoding.net/blob/master/LICENSE
+ 4.0.0
+ https://github.com/chadly/Geocoding.net.git
+ git
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Geocoding.Here/HereAddress.cs b/src/Geocoding.Here/HereAddress.cs
new file mode 100644
index 0000000..5067d5b
--- /dev/null
+++ b/src/Geocoding.Here/HereAddress.cs
@@ -0,0 +1,58 @@
+using System;
+
+namespace Geocoding.Here
+{
+ public class HereAddress : Address
+ {
+ readonly string street, houseNumber, city, state, country, postalCode;
+ readonly HereLocationType type;
+
+ public string AddressLine
+ {
+ get { return street ?? ""; }
+ }
+
+ public string AdminDistrict
+ {
+ get { return houseNumber ?? ""; }
+ }
+
+ public string AdminDistrict2
+ {
+ get { return city ?? ""; }
+ }
+
+ public string CountryRegion
+ {
+ get { return state ?? ""; }
+ }
+
+ public string Neighborhood
+ {
+ get { return country ?? ""; }
+ }
+
+ public string PostalCode
+ {
+ get { return postalCode ?? ""; }
+ }
+
+ public HereLocationType Type
+ {
+ get { return type; }
+ }
+
+ public HereAddress(string formattedAddress, Location coordinates, string street, string houseNumber, string city,
+ string state, string postalCode, string country, HereLocationType type)
+ : base(formattedAddress, coordinates, "HERE")
+ {
+ this.street = street;
+ this.houseNumber = houseNumber;
+ this.city = city;
+ this.state = state;
+ this.postalCode = postalCode;
+ this.country = country;
+ this.type = type;
+ }
+ }
+}
diff --git a/src/Geocoding.Here/HereGeocoder.cs b/src/Geocoding.Here/HereGeocoder.cs
new file mode 100644
index 0000000..79f3dad
--- /dev/null
+++ b/src/Geocoding.Here/HereGeocoder.cs
@@ -0,0 +1,263 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Runtime.Serialization.Json;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Geocoding.Here
+{
+ ///
+ /// https://developer.here.com/documentation/geocoder/topics/request-constructing.html
+ ///
+ public class HereGeocoder : IGeocoder
+ {
+ const string GEOCODING_QUERY = "https://geocoder.api.here.com/6.2/geocode.json?app_id={0}&app_code={1}&{2}";
+ const string REVERSE_GEOCODING_QUERY = "https://reverse.geocoder.api.here.com/6.2/reversegeocode.json?app_id={0}&app_code={1}&mode=retrieveAddresses&{2}";
+ const string SEARCHTEXT = "searchtext={0}";
+ const string PROX = "prox={0}";
+ const string STREET = "street={0}";
+ const string CITY = "city={0}";
+ const string STATE = "state={0}";
+ const string POSTAL_CODE = "postalcode={0}";
+ const string COUNTRY = "country={0}";
+
+ readonly string appId;
+ readonly string appCode;
+
+ public IWebProxy Proxy { get; set; }
+ public Location UserLocation { get; set; }
+ public Bounds UserMapView { get; set; }
+ public int? MaxResults { get; set; }
+
+ public HereGeocoder(string appId, string appCode)
+ {
+ if (string.IsNullOrWhiteSpace(appId))
+ throw new ArgumentException("appId can not be null or empty");
+
+ if (string.IsNullOrWhiteSpace(appCode))
+ throw new ArgumentException("appCode can not be null or empty");
+
+ this.appId = appId;
+ this.appCode = appCode;
+ }
+
+ private string GetQueryUrl(string address)
+ {
+ var parameters = new StringBuilder();
+ var first = AppendParameter(parameters, address, SEARCHTEXT, true);
+ AppendGlobalParameters(parameters, first);
+
+ return string.Format(GEOCODING_QUERY, appId, appCode, parameters.ToString());
+ }
+
+ private string GetQueryUrl(string street, string city, string state, string postalCode, string country)
+ {
+ var parameters = new StringBuilder();
+ var first = AppendParameter(parameters, street, STREET, true);
+ first = AppendParameter(parameters, city, CITY, first);
+ first = AppendParameter(parameters, state, STATE, first);
+ first = AppendParameter(parameters, postalCode, POSTAL_CODE, first);
+ first = AppendParameter(parameters, country, COUNTRY, first);
+ AppendGlobalParameters(parameters, first);
+
+ return string.Format(GEOCODING_QUERY, appId, appCode, parameters.ToString());
+ }
+
+ private string GetQueryUrl(double latitude, double longitude)
+ {
+ var parameters = new StringBuilder();
+ var first = AppendParameter(parameters, string.Format(CultureInfo.InvariantCulture, "{0},{1}", latitude, longitude), PROX, true);
+ AppendGlobalParameters(parameters, first);
+
+ return string.Format(REVERSE_GEOCODING_QUERY, appId, appCode, parameters.ToString());
+ }
+
+ private IEnumerable> GetGlobalParameters()
+ {
+ if (UserLocation != null)
+ yield return new KeyValuePair("prox", UserLocation.ToString());
+
+ if (UserMapView != null)
+ yield return new KeyValuePair("mapview", string.Concat(UserMapView.SouthWest.ToString(), ",", UserMapView.NorthEast.ToString()));
+
+ if (MaxResults != null && MaxResults.Value > 0)
+ yield return new KeyValuePair("maxresults", MaxResults.Value.ToString(CultureInfo.InvariantCulture));
+ }
+
+ private bool AppendGlobalParameters(StringBuilder parameters, bool first)
+ {
+ var values = GetGlobalParameters().ToArray();
+
+ if (!first) parameters.Append("&");
+ parameters.Append(BuildQueryString(values));
+
+ return first && !values.Any();
+ }
+
+ private string BuildQueryString(IEnumerable> parameters)
+ {
+ var builder = new StringBuilder();
+ foreach (var pair in parameters)
+ {
+ if (builder.Length > 0) builder.Append("&");
+
+ builder.Append(UrlEncode(pair.Key));
+ builder.Append("=");
+ builder.Append(UrlEncode(pair.Value));
+ }
+ return builder.ToString();
+ }
+
+ public async Task> GeocodeAsync(string address, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ try
+ {
+ var url = GetQueryUrl(address);
+ var response = await GetResponse(url, cancellationToken).ConfigureAwait(false);
+ return ParseResponse(response);
+ }
+ catch (Exception ex)
+ {
+ throw new HereGeocodingException(ex);
+ }
+ }
+
+ public async Task> GeocodeAsync(string street, string city, string state, string postalCode, string country, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ try
+ {
+ var url = GetQueryUrl(street, city, state, postalCode, country);
+ var response = await GetResponse(url, cancellationToken).ConfigureAwait(false);
+ return ParseResponse(response);
+ }
+ catch (Exception ex)
+ {
+ throw new HereGeocodingException(ex);
+ }
+ }
+
+ public async Task> ReverseGeocodeAsync(Location location, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ if (location == null)
+ throw new ArgumentNullException(nameof(location));
+
+ return await ReverseGeocodeAsync(location.Latitude, location.Longitude, cancellationToken).ConfigureAwait(false);
+ }
+
+ public async Task> ReverseGeocodeAsync(double latitude, double longitude, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ try
+ {
+ var url = GetQueryUrl(latitude, longitude);
+ var response = await GetResponse(url, cancellationToken).ConfigureAwait(false);
+ return ParseResponse(response);
+ }
+ catch (Exception ex)
+ {
+ throw new HereGeocodingException(ex);
+ }
+ }
+
+ async Task> IGeocoder.GeocodeAsync(string address, CancellationToken cancellationToken)
+ {
+ return await GeocodeAsync(address, cancellationToken).ConfigureAwait(false);
+ }
+
+ async Task> IGeocoder.GeocodeAsync(string street, string city, string state, string postalCode, string country, CancellationToken cancellationToken)
+ {
+ return await GeocodeAsync(street, city, state, postalCode, country, cancellationToken).ConfigureAwait(false);
+ }
+
+ async Task> IGeocoder.ReverseGeocodeAsync(Location location, CancellationToken cancellationToken)
+ {
+ return await ReverseGeocodeAsync(location, cancellationToken).ConfigureAwait(false);
+ }
+
+ async Task> IGeocoder.ReverseGeocodeAsync(double latitude, double longitude, CancellationToken cancellationToken)
+ {
+ return await ReverseGeocodeAsync(latitude, longitude, cancellationToken).ConfigureAwait(false);
+ }
+
+ private bool AppendParameter(StringBuilder sb, string parameter, string format, bool first)
+ {
+ if (!string.IsNullOrEmpty(parameter))
+ {
+ if (!first)
+ {
+ sb.Append('&');
+ }
+ sb.Append(string.Format(format, UrlEncode(parameter)));
+ return false;
+ }
+ return first;
+ }
+
+ private IEnumerable ParseResponse(Json.Response response)
+ {
+ foreach (var view in response.View)
+ {
+ foreach (var result in view.Result)
+ {
+ var location = result.Location;
+ yield return new HereAddress(
+ location.Address.Label,
+ new Location(location.DisplayPosition.Latitude, location.DisplayPosition.Longitude),
+ location.Address.Street,
+ location.Address.HouseNumber,
+ location.Address.City,
+ location.Address.State,
+ location.Address.PostalCode,
+ location.Address.Country,
+ (HereLocationType)Enum.Parse(typeof(HereLocationType), location.LocationType, true));
+ }
+ }
+ }
+
+ private HttpRequestMessage CreateRequest(string url)
+ {
+ return new HttpRequestMessage(HttpMethod.Get, url);
+ }
+
+ private HttpClient BuildClient()
+ {
+ if (this.Proxy == null)
+ return new HttpClient();
+
+ var handler = new HttpClientHandler {Proxy = this.Proxy};
+ return new HttpClient(handler);
+ }
+
+ private async Task GetResponse(string queryURL, CancellationToken cancellationToken)
+ {
+ using (var client = BuildClient())
+ {
+ var response = await client.SendAsync(CreateRequest(queryURL), cancellationToken).ConfigureAwait(false);
+ using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
+ {
+ var jsonSerializer = new DataContractJsonSerializer(typeof(Json.ServerResponse));
+ var serverResponse = (Json.ServerResponse)jsonSerializer.ReadObject(stream);
+
+ if (serverResponse.ErrorType != null)
+ {
+ throw new HereGeocodingException(serverResponse.Details, serverResponse.ErrorType, serverResponse.ErrorType);
+ }
+
+ return serverResponse.Response;
+ }
+ }
+ }
+
+ private string UrlEncode(string toEncode)
+ {
+ if (string.IsNullOrEmpty(toEncode))
+ return string.Empty;
+
+ return WebUtility.UrlEncode(toEncode);
+ }
+ }
+}
diff --git a/src/Geocoding.Here/HereGeocodingException.cs b/src/Geocoding.Here/HereGeocodingException.cs
new file mode 100644
index 0000000..e9204bf
--- /dev/null
+++ b/src/Geocoding.Here/HereGeocodingException.cs
@@ -0,0 +1,26 @@
+using System;
+using Geocoding.Core;
+
+namespace Geocoding.Here
+{
+ public class HereGeocodingException : GeocodingException
+ {
+ const string defaultMessage = "There was an error processing the geocoding request. See InnerException for more information.";
+
+ public string ErrorType { get; }
+
+ public string ErrorSubtype { get; }
+
+ public HereGeocodingException(Exception innerException)
+ : base(defaultMessage, innerException)
+ {
+ }
+
+ public HereGeocodingException(string message, string errorType, string errorSubtype)
+ : base(message)
+ {
+ ErrorType = errorType;
+ ErrorSubtype = errorSubtype;
+ }
+ }
+}
diff --git a/src/Geocoding.Here/HereLocationType.cs b/src/Geocoding.Here/HereLocationType.cs
new file mode 100644
index 0000000..9d40fc9
--- /dev/null
+++ b/src/Geocoding.Here/HereLocationType.cs
@@ -0,0 +1,39 @@
+namespace Geocoding.Here
+{
+ ///
+ /// https://developer.here.com/documentation/geocoder/topics/resource-type-response-geocode.html
+ ///
+ public enum HereLocationType
+ {
+ Unknown,
+ Point,
+ Area,
+ Address,
+ Trail,
+ Park,
+ Lake,
+ MountainPeak,
+ Volcano,
+ River,
+ GolfCourse,
+ IndustrialComplex,
+ Island,
+ Woodland,
+ Cemetery,
+ CanalWaterChannel,
+ BayHarbor,
+ Airport,
+ Hospital,
+ SportsComplex,
+ ShoppingCentre,
+ UniversityCollege,
+ NativeAmericanReservation,
+ Railroad,
+ MilitaryBase,
+ ParkingLot,
+ ParkingGarage,
+ AnimalPark,
+ Beach,
+ DistanceMarker
+ }
+}
diff --git a/src/Geocoding.Here/HereMatchType.cs b/src/Geocoding.Here/HereMatchType.cs
new file mode 100644
index 0000000..4d3c2ec
--- /dev/null
+++ b/src/Geocoding.Here/HereMatchType.cs
@@ -0,0 +1,12 @@
+namespace Geocoding.Here
+{
+ ///
+ /// https://developer.here.com/documentation/geocoder/topics/resource-type-response-geocode.html
+ ///
+ public enum HereMatchType
+ {
+ Unknown,
+ PointAddress,
+ Interpolated
+ }
+}
diff --git a/src/Geocoding.Here/HereViewport.cs b/src/Geocoding.Here/HereViewport.cs
new file mode 100644
index 0000000..80229e5
--- /dev/null
+++ b/src/Geocoding.Here/HereViewport.cs
@@ -0,0 +1,8 @@
+namespace Geocoding.Here
+{
+ public class HereViewport
+ {
+ public Location Northeast { get; set; }
+ public Location Southwest { get; set; }
+ }
+}
diff --git a/src/Geocoding.Here/Json.cs b/src/Geocoding.Here/Json.cs
new file mode 100644
index 0000000..9512514
--- /dev/null
+++ b/src/Geocoding.Here/Json.cs
@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace Geocoding.Here.Json
+{
+ [DataContract]
+ public class ServerResponse
+ {
+ [DataMember(Name = "Response")]
+ public Response Response { get; set; }
+ [DataMember(Name = "Details")]
+ public string Details { get; set; }
+ [DataMember(Name = "type")]
+ public string ErrorType { get; set; }
+ [DataMember(Name = "subtype")]
+ public string ErrorSubtype { get; set; }
+ }
+
+ [DataContract]
+ public class Response
+ {
+ [DataMember(Name = "View")]
+ public View[] View { get; set; }
+ }
+
+ [DataContract]
+ public class View
+ {
+ [DataMember(Name = "ViewId")]
+ public int ViewId { get; set; }
+ [DataMember(Name = "Result")]
+ public Result[] Result { get; set; }
+ }
+
+ [DataContract]
+ public class Result
+ {
+ [DataMember(Name = "Relevance")]
+ public float Relevance { get; set; }
+ [DataMember(Name = "MatchLevel")]
+ public string MatchLevel { get; set; }
+ [DataMember(Name = "MatchType")]
+ public string MatchType { get; set; }
+ [DataMember(Name = "Location")]
+ public Location Location { get; set; }
+ }
+
+ [DataContract]
+ public class Location
+ {
+ [DataMember(Name = "LocationId")]
+ public string LocationId { get; set; }
+ [DataMember(Name = "LocationType")]
+ public string LocationType { get; set; }
+ [DataMember(Name = "Name")]
+ public string Name { get; set; }
+ [DataMember(Name = "DisplayPosition")]
+ public GeoCoordinate DisplayPosition { get; set; }
+ [DataMember(Name = "NavigationPosition")]
+ public GeoCoordinate NavigationPosition { get; set; }
+ [DataMember(Name = "Address")]
+ public Address Address { get; set; }
+ }
+
+ [DataContract]
+ public class GeoCoordinate
+ {
+ [DataMember(Name = "Latitude")]
+ public double Latitude { get; set; }
+ [DataMember(Name = "Longitude")]
+ public double Longitude { get; set; }
+ }
+
+ [DataContract]
+ public class GeoBoundingBox
+ {
+ [DataMember(Name = "TopLeft")]
+ public GeoCoordinate TopLeft { get; set; }
+ [DataMember(Name = "BottomRight")]
+ public GeoCoordinate BottomRight { get; set; }
+ }
+
+ [DataContract]
+ public class Address
+ {
+ [DataMember(Name = "Label")]
+ public string Label { get; set; }
+ [DataMember(Name = "Country")]
+ public string Country { get; set; }
+ [DataMember(Name = "State")]
+ public string State { get; set; }
+ [DataMember(Name = "County")]
+ public string County { get; set; }
+ [DataMember(Name = "City")]
+ public string City { get; set; }
+ [DataMember(Name = "District")]
+ public string District { get; set; }
+ [DataMember(Name = "Subdistrict")]
+ public string Subdistrict { get; set; }
+ [DataMember(Name = "Street")]
+ public string Street { get; set; }
+ [DataMember(Name = "HouseNumber")]
+ public string HouseNumber { get; set; }
+ [DataMember(Name = "PostalCode")]
+ public string PostalCode { get; set; }
+ [DataMember(Name = "Building")]
+ public string Building { get; set; }
+ }
+}
diff --git a/src/Geocoding.MapQuest/Geocoding.MapQuest.csproj b/src/Geocoding.MapQuest/Geocoding.MapQuest.csproj
index b73cf90..ae6d998 100644
--- a/src/Geocoding.MapQuest/Geocoding.MapQuest.csproj
+++ b/src/Geocoding.MapQuest/Geocoding.MapQuest.csproj
@@ -5,7 +5,7 @@
Geocoding.net MapQuest
4.0.0-beta1
chadly
- netstandard1.3
+ netstandard1.3;net46
Geocoding.MapQuest
Geocoding.MapQuest
geocoding;geocode;geocoder;maps;address;validation;normalization;google-maps;bing-maps;yahoo-placefinder;mapquest
@@ -21,7 +21,7 @@
-
+
diff --git a/src/Geocoding.MapQuest/MapQuestGeocoder.cs b/src/Geocoding.MapQuest/MapQuestGeocoder.cs
index 94038ff..21c43f6 100644
--- a/src/Geocoding.MapQuest/MapQuestGeocoder.cs
+++ b/src/Geocoding.MapQuest/MapQuestGeocoder.cs
@@ -1,9 +1,10 @@
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
namespace Geocoding.MapQuest
@@ -64,17 +65,17 @@ IEnumerable HandleSingleResponse(IEnumerable locs)
}
}
- public async Task> GeocodeAsync(string address)
+ public async Task> GeocodeAsync(string address, CancellationToken cancellationToken = default(CancellationToken))
{
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 = await Execute(f).ConfigureAwait(false);
+ MapQuestResponse res = await Execute(f, cancellationToken).ConfigureAwait(false);
return HandleSingleResponse(res);
}
- public async Task> GeocodeAsync(string street, string city, string state, string postalCode, string country)
+ public async Task> GeocodeAsync(string street, string city, string state, string postalCode, string country, CancellationToken cancellationToken = default(CancellationToken))
{
var sb = new StringBuilder();
if (!string.IsNullOrWhiteSpace(street))
@@ -98,28 +99,28 @@ public async Task> GeocodeAsync(string street, string city,
if (s.Last() == ',')
s = s.Remove(s.Length - 1);
- return await GeocodeAsync(s).ConfigureAwait(false);
+ return await GeocodeAsync(s, cancellationToken).ConfigureAwait(false);
}
- public async Task> ReverseGeocodeAsync(Location location)
+ public async Task> ReverseGeocodeAsync(Location location, CancellationToken cancellationToken = default(CancellationToken))
{
if (location == null)
throw new ArgumentNullException("location");
var f = new ReverseGeocodeRequest(key, location) { UseOSM = this.UseOSM };
- MapQuestResponse res = await Execute(f).ConfigureAwait(false);
+ MapQuestResponse res = await Execute(f, cancellationToken).ConfigureAwait(false);
return HandleSingleResponse(res);
}
- public async Task> ReverseGeocodeAsync(double latitude, double longitude)
+ public async Task> ReverseGeocodeAsync(double latitude, double longitude, CancellationToken cancellationToken = default(CancellationToken))
{
- return await ReverseGeocodeAsync(new Location(latitude, longitude)).ConfigureAwait(false);
+ return await ReverseGeocodeAsync(new Location(latitude, longitude), cancellationToken).ConfigureAwait(false);
}
- public async Task Execute(BaseRequest f)
+ public async Task Execute(BaseRequest f, CancellationToken cancellationToken = default(CancellationToken))
{
- HttpWebRequest request = await Send(f).ConfigureAwait(false);
- MapQuestResponse r = await Parse(request).ConfigureAwait(false);
+ HttpWebRequest request = await Send(f, cancellationToken).ConfigureAwait(false);
+ MapQuestResponse r = await Parse(request, cancellationToken).ConfigureAwait(false);
if (r != null && !r.Results.IsNullOrEmpty())
{
foreach (MapQuestResult o in r.Results)
@@ -142,7 +143,7 @@ public async Task Execute(BaseRequest f)
return r;
}
- private async Task Send(BaseRequest f)
+ private async Task Send(BaseRequest f, CancellationToken cancellationToken)
{
if (f == null)
throw new ArgumentNullException("f");
@@ -169,7 +170,7 @@ private async Task Send(BaseRequest f)
break;
}
request.Method = f.RequestVerb;
- request.ContentType = "application/" + f.InputFormat;
+ request.ContentType = "application/" + f.InputFormat + "; charset=utf-8";
if (Proxy != null)
request.Proxy = Proxy;
@@ -178,8 +179,10 @@ private async Task Send(BaseRequest f)
{
byte[] buffer = Encoding.UTF8.GetBytes(f.RequestBody);
//request.Headers.ContentLength = buffer.Length;
+ using(cancellationToken.Register(request.Abort, false))
using (Stream rs = await request.GetRequestStreamAsync().ConfigureAwait(false))
{
+ cancellationToken.ThrowIfCancellationRequested();
rs.Write(buffer, 0, buffer.Length);
rs.Flush();
}
@@ -187,7 +190,7 @@ private async Task Send(BaseRequest f)
return request;
}
- private async Task Parse(HttpWebRequest request)
+ private async Task Parse(HttpWebRequest request, CancellationToken cancellationToken)
{
if (request == null)
throw new ArgumentNullException("request");
@@ -198,6 +201,7 @@ private async Task Parse(HttpWebRequest request)
string json;
using (HttpWebResponse response = await request.GetResponseAsync().ConfigureAwait(false) as HttpWebResponse)
{
+ cancellationToken.ThrowIfCancellationRequested();
if ((int)response.StatusCode >= 300) //error
throw new Exception((int)response.StatusCode + " " + response.StatusDescription);
@@ -230,7 +234,7 @@ private async Task Parse(HttpWebRequest request)
}
}
- public async Task> GeocodeAsync(IEnumerable addresses)
+ public async Task> GeocodeAsync(IEnumerable addresses, CancellationToken cancellationToken = default(CancellationToken))
{
if (addresses == null)
throw new ArgumentNullException("addresses");
@@ -243,7 +247,7 @@ group a by a into ag
throw new ArgumentException("Atleast one none blank item is required in addresses");
var f = new BatchGeocodeRequest(key, adr) { UseOSM = this.UseOSM };
- MapQuestResponse res = await Execute(f).ConfigureAwait(false);
+ MapQuestResponse res = await Execute(f, cancellationToken).ConfigureAwait(false);
return HandleBatchResponse(res);
}
@@ -261,9 +265,9 @@ ICollection HandleBatchResponse(MapQuestResponse res)
return new ResultItem[0];
}
- public Task> ReverseGeocodeAsync(IEnumerable locations)
+ public Task> ReverseGeocodeAsync(IEnumerable locations, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotSupportedException("ReverseGeocodeAsync(...) is not available for MapQuestGeocoder.");
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Geocoding.MapQuest/Quality.cs b/src/Geocoding.MapQuest/Quality.cs
index e53cbb8..1940f8d 100644
--- a/src/Geocoding.MapQuest/Quality.cs
+++ b/src/Geocoding.MapQuest/Quality.cs
@@ -31,25 +31,25 @@ public enum Quality : int
///
ZIP = 5,
///
+ /// A6 Admin area. For USA, a neighborhood.
+ ///
+ NEIGHBORHOOD = 6,
+ ///
/// A5 Admin area. For USA, a city.
///
- CITY = 6,
+ CITY = 7,
///
/// A4 Admin area. For USA, a county.
///
- COUNTY = 7,
+ COUNTY = 8,
///
/// A3 Admin area. For USA, a state.
///
- STATE = 8,
+ STATE = 9,
///
/// A1 Admin area, largest. For USA, a country.
///
- COUNTRY = 9,
- ///
- /// Admin area. For USA, a neighborhood.
- ///
- NEIGHBORHOOD = 10,
+ COUNTRY = 10,
UNKNOWN = 11
}
diff --git a/src/Geocoding.Microsoft/BingGeocodingException.cs b/src/Geocoding.Microsoft/BingGeocodingException.cs
index 2a8554a..c90e2de 100644
--- a/src/Geocoding.Microsoft/BingGeocodingException.cs
+++ b/src/Geocoding.Microsoft/BingGeocodingException.cs
@@ -1,8 +1,9 @@
using System;
+using Geocoding.Core;
namespace Geocoding.Microsoft
{
- public class BingGeocodingException : Exception
+ public class BingGeocodingException : GeocodingException
{
const string defaultMessage = "There was an error processing the geocoding request. See InnerException for more information.";
diff --git a/src/Geocoding.Microsoft/BingMapsGeocoder.cs b/src/Geocoding.Microsoft/BingMapsGeocoder.cs
index e0d42bf..3b32c1b 100644
--- a/src/Geocoding.Microsoft/BingMapsGeocoder.cs
+++ b/src/Geocoding.Microsoft/BingMapsGeocoder.cs
@@ -6,6 +6,7 @@
using System.Net.Http;
using System.Runtime.Serialization.Json;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
namespace Geocoding.Microsoft
@@ -23,6 +24,7 @@ public class BingMapsGeocoder : IGeocoder
const string ZIP = "postalCode={0}";
const string CITY = "locality={0}";
const string ADDRESS = "addressLine={0}";
+ const int BINGMAXRESULTSVALUE = 20;
readonly string bingKey;
@@ -32,6 +34,7 @@ public class BingMapsGeocoder : IGeocoder
public Bounds UserMapView { get; set; }
public IPAddress UserIP { get; set; }
public bool IncludeNeighborhood { get; set; }
+ public int? MaxResults { get; set; }
public BingMapsGeocoder(string bingKey)
{
@@ -88,6 +91,9 @@ private IEnumerable> GetGlobalParameters()
if (IncludeNeighborhood)
yield return new KeyValuePair("inclnb", IncludeNeighborhood ? "1" : "0");
+
+ if (MaxResults != null && MaxResults.Value > 0)
+ yield return new KeyValuePair("maxResults", Math.Min(MaxResults.Value, BINGMAXRESULTSVALUE).ToString());
}
private bool AppendGlobalParameters(StringBuilder parameters, bool first)
@@ -114,12 +120,12 @@ private string BuildQueryString(IEnumerable> parame
return builder.ToString();
}
- public async Task> GeocodeAsync(string address)
+ public async Task> GeocodeAsync(string address, CancellationToken cancellationToken = default(CancellationToken))
{
try
{
var url = GetQueryUrl(address);
- var response = await GetResponse(url).ConfigureAwait(false);
+ var response = await GetResponse(url, cancellationToken).ConfigureAwait(false);
return ParseResponse(response);
}
catch (Exception ex)
@@ -128,12 +134,12 @@ public async Task> GeocodeAsync(string address)
}
}
- public async Task> GeocodeAsync(string street, string city, string state, string postalCode, string country)
+ public async Task> GeocodeAsync(string street, string city, string state, string postalCode, string country, CancellationToken cancellationToken = default(CancellationToken))
{
try
{
var url = GetQueryUrl(street, city, state, postalCode, country);
- var response = await GetResponse(url).ConfigureAwait(false);
+ var response = await GetResponse(url, cancellationToken).ConfigureAwait(false);
return ParseResponse(response);
}
catch (Exception ex)
@@ -142,20 +148,20 @@ public async Task> GeocodeAsync(string street, string c
}
}
- public async Task> ReverseGeocodeAsync(Location location)
+ public async Task> ReverseGeocodeAsync(Location location, CancellationToken cancellationToken = default(CancellationToken))
{
if (location == null)
throw new ArgumentNullException("location");
- return await ReverseGeocodeAsync(location.Latitude, location.Longitude).ConfigureAwait(false);
+ return await ReverseGeocodeAsync(location.Latitude, location.Longitude, cancellationToken).ConfigureAwait(false);
}
- public async Task> ReverseGeocodeAsync(double latitude, double longitude)
+ public async Task> ReverseGeocodeAsync(double latitude, double longitude, CancellationToken cancellationToken = default(CancellationToken))
{
try
{
var url = GetQueryUrl(latitude, longitude);
- var response = await GetResponse(url).ConfigureAwait(false);
+ var response = await GetResponse(url, cancellationToken).ConfigureAwait(false);
return ParseResponse(response);
}
catch (Exception ex)
@@ -164,24 +170,24 @@ public async Task> ReverseGeocodeAsync(double latitude,
}
}
- async Task> IGeocoder.GeocodeAsync(string address)
+ async Task> IGeocoder.GeocodeAsync(string address, CancellationToken cancellationToken)
{
- return await GeocodeAsync(address).ConfigureAwait(false);
+ return await GeocodeAsync(address, cancellationToken).ConfigureAwait(false);
}
- async Task> IGeocoder.GeocodeAsync(string street, string city, string state, string postalCode, string country)
+ async Task> IGeocoder.GeocodeAsync(string street, string city, string state, string postalCode, string country, CancellationToken cancellationToken)
{
- return await GeocodeAsync(street, city, state, postalCode, country).ConfigureAwait(false);
+ return await GeocodeAsync(street, city, state, postalCode, country, cancellationToken).ConfigureAwait(false);
}
- async Task> IGeocoder.ReverseGeocodeAsync(Location location)
+ async Task> IGeocoder.ReverseGeocodeAsync(Location location, CancellationToken cancellationToken)
{
- return await ReverseGeocodeAsync(location).ConfigureAwait(false);
+ return await ReverseGeocodeAsync(location, cancellationToken).ConfigureAwait(false);
}
- async Task> IGeocoder.ReverseGeocodeAsync(double latitude, double longitude)
+ async Task> IGeocoder.ReverseGeocodeAsync(double latitude, double longitude, CancellationToken cancellationToken)
{
- return await ReverseGeocodeAsync(latitude, longitude).ConfigureAwait(false);
+ return await ReverseGeocodeAsync(latitude, longitude, cancellationToken).ConfigureAwait(false);
}
private bool AppendParameter(StringBuilder sb, string parameter, string format, bool first)
@@ -237,11 +243,11 @@ HttpClient BuildClient()
return new HttpClient(handler);
}
- private async Task GetResponse(string queryURL)
+ private async Task GetResponse(string queryURL, CancellationToken cancellationToken)
{
using (var client = BuildClient())
{
- var response = await client.SendAsync(CreateRequest(queryURL)).ConfigureAwait(false);
+ var response = await client.SendAsync(CreateRequest(queryURL), cancellationToken).ConfigureAwait(false);
using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
{
DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(Json.Response));
diff --git a/src/Geocoding.Microsoft/Geocoding.Microsoft.csproj b/src/Geocoding.Microsoft/Geocoding.Microsoft.csproj
index 27c9f97..06e74be 100644
--- a/src/Geocoding.Microsoft/Geocoding.Microsoft.csproj
+++ b/src/Geocoding.Microsoft/Geocoding.Microsoft.csproj
@@ -5,7 +5,7 @@
Geocoding.net Microsoft
4.0.0-beta1
chadly
- netstandard1.3
+ netstandard1.3;net46
Geocoding.Microsoft
Geocoding.Microsoft
geocoding;geocode;geocoder;maps;address;validation;normalization;google-maps;bing-maps;yahoo-placefinder;mapquest
@@ -23,8 +23,15 @@
+
+
+
+
+
+
+
diff --git a/src/Geocoding.Yahoo/Geocoding.Yahoo.csproj b/src/Geocoding.Yahoo/Geocoding.Yahoo.csproj
index a9ba228..dfa1e88 100644
--- a/src/Geocoding.Yahoo/Geocoding.Yahoo.csproj
+++ b/src/Geocoding.Yahoo/Geocoding.Yahoo.csproj
@@ -5,7 +5,7 @@
Geocoding.net Yahoo
4.0.0-beta1
chadly
- netstandard1.3
+ netstandard1.3;net46
Geocoding.Yahoo
Geocoding.Yahoo
geocoding;geocode;geocoder;maps;address;validation;normalization;google-maps;bing-maps;yahoo-placefinder;mapquest
@@ -21,7 +21,7 @@
-
+
diff --git a/src/Geocoding.Yahoo/YahooGeocoder.cs b/src/Geocoding.Yahoo/YahooGeocoder.cs
index 279d3dc..7d2a32b 100644
--- a/src/Geocoding.Yahoo/YahooGeocoder.cs
+++ b/src/Geocoding.Yahoo/YahooGeocoder.cs
@@ -4,6 +4,7 @@
using System.IO;
using System.Linq;
using System.Net;
+using System.Threading;
using System.Threading.Tasks;
using System.Xml.XPath;
@@ -44,7 +45,7 @@ public YahooGeocoder(string consumerKey, string consumerSecret)
this.consumerSecret = consumerSecret;
}
- public async Task> GeocodeAsync(string address)
+ public async Task> GeocodeAsync(string address, CancellationToken cancellationToken = default(CancellationToken))
{
if (string.IsNullOrEmpty(address))
throw new ArgumentNullException("address");
@@ -52,39 +53,41 @@ public async Task> GeocodeAsync(string address)
string url = string.Format(ServiceUrl, WebUtility.UrlEncode(address));
HttpWebRequest request = BuildWebRequest(url);
- return await ProcessRequest(request).ConfigureAwait(false);
+ return await ProcessRequest(request, cancellationToken).ConfigureAwait(false);
}
- public async Task> GeocodeAsync(string street, string city, string state, string postalCode, string country)
+ public async Task> GeocodeAsync(string street, string city, string state, string postalCode, string country, CancellationToken cancellationToken = default(CancellationToken))
{
string url = string.Format(ServiceUrlNormal, WebUtility.UrlEncode(street), WebUtility.UrlEncode(city), WebUtility.UrlEncode(state), WebUtility.UrlEncode(postalCode), WebUtility.UrlEncode(country));
HttpWebRequest request = BuildWebRequest(url);
- return await ProcessRequest(request).ConfigureAwait(false);
+ return await ProcessRequest(request, cancellationToken).ConfigureAwait(false);
}
- public async Task> ReverseGeocodeAsync(Location location)
+ public async Task> ReverseGeocodeAsync(Location location, CancellationToken cancellationToken = default(CancellationToken))
{
if (location == null)
throw new ArgumentNullException("location");
- return await ReverseGeocodeAsync(location.Latitude, location.Longitude).ConfigureAwait(false);
+ return await ReverseGeocodeAsync(location.Latitude, location.Longitude, cancellationToken).ConfigureAwait(false);
}
- public async Task> ReverseGeocodeAsync(double latitude, double longitude)
+ public async Task> ReverseGeocodeAsync(double latitude, double longitude, CancellationToken cancellationToken = default(CancellationToken))
{
string url = string.Format(ServiceUrlReverse, string.Format(CultureInfo.InvariantCulture, "{0} {1}", latitude, longitude));
HttpWebRequest request = BuildWebRequest(url);
- return await ProcessRequest(request).ConfigureAwait(false);
+ return await ProcessRequest(request, cancellationToken).ConfigureAwait(false);
}
- private async Task> ProcessRequest(HttpWebRequest request)
+ private async Task> ProcessRequest(HttpWebRequest request, CancellationToken cancellationToken)
{
try
{
+ using(cancellationToken.Register(request.Abort, false))
using (WebResponse response = await request.GetResponseAsync().ConfigureAwait(false))
{
+ cancellationToken.ThrowIfCancellationRequested();
return ProcessWebResponse(response);
}
}
@@ -100,24 +103,24 @@ private async Task> ProcessRequest(HttpWebRequest requ
}
}
- async Task> IGeocoder.GeocodeAsync(string address)
+ async Task> IGeocoder.GeocodeAsync(string address, CancellationToken cancellationToken)
{
- return await GeocodeAsync(address).ConfigureAwait(false);
+ return await GeocodeAsync(address, cancellationToken).ConfigureAwait(false);
}
- async Task> IGeocoder.GeocodeAsync(string street, string city, string state, string postalCode, string country)
+ async Task> IGeocoder.GeocodeAsync(string street, string city, string state, string postalCode, string country, CancellationToken cancellationToken)
{
- return await GeocodeAsync(street, city, state, postalCode, country).ConfigureAwait(false);
+ return await GeocodeAsync(street, city, state, postalCode, country, cancellationToken).ConfigureAwait(false);
}
- async Task> IGeocoder.ReverseGeocodeAsync(Location location)
+ async Task> IGeocoder.ReverseGeocodeAsync(Location location, CancellationToken cancellationToken)
{
- return await ReverseGeocodeAsync(location).ConfigureAwait(false);
+ return await ReverseGeocodeAsync(location, cancellationToken).ConfigureAwait(false);
}
- async Task> IGeocoder.ReverseGeocodeAsync(double latitude, double longitude)
+ async Task> IGeocoder.ReverseGeocodeAsync(double latitude, double longitude, CancellationToken cancellationToken)
{
- return await ReverseGeocodeAsync(latitude, longitude).ConfigureAwait(false);
+ return await ReverseGeocodeAsync(latitude, longitude, cancellationToken).ConfigureAwait(false);
}
private HttpWebRequest BuildWebRequest(string url)
diff --git a/src/Geocoding.Yahoo/YahooGeocodingException.cs b/src/Geocoding.Yahoo/YahooGeocodingException.cs
index 53dcb0c..3d79c10 100644
--- a/src/Geocoding.Yahoo/YahooGeocodingException.cs
+++ b/src/Geocoding.Yahoo/YahooGeocodingException.cs
@@ -1,8 +1,9 @@
using System;
+using Geocoding.Core;
namespace Geocoding.Yahoo
{
- public class YahooGeocodingException : Exception
+ public class YahooGeocodingException : GeocodingException
{
const string defaultMessage = "There was an error processing the geocoding request. See ErrorCode or InnerException for more information.";
diff --git a/test/Geocoding.Tests/AsyncGeocoderTest.cs b/test/Geocoding.Tests/AsyncGeocoderTest.cs
index 226f87b..513ac88 100644
--- a/test/Geocoding.Tests/AsyncGeocoderTest.cs
+++ b/test/Geocoding.Tests/AsyncGeocoderTest.cs
@@ -1,4 +1,5 @@
-using System.Linq;
+using System.Globalization;
+using System.Linq;
using System.Threading.Tasks;
using Xunit;
@@ -11,7 +12,7 @@ public abstract class AsyncGeocoderTest
public AsyncGeocoderTest()
{
- //Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("en-us");
+ CultureInfo.CurrentCulture = new CultureInfo("en-us");
asyncGeocoder = CreateAsyncGeocoder();
}
@@ -19,23 +20,17 @@ public AsyncGeocoderTest()
protected abstract IGeocoder CreateAsyncGeocoder();
[Fact]
- public void CanGeocodeAddress()
+ public async Task CanGeocodeAddress()
{
- asyncGeocoder.GeocodeAsync("1600 pennsylvania ave washington dc").ContinueWith(task =>
- {
- Address[] addresses = task.Result.ToArray();
- addresses[0].AssertWhiteHouse();
- });
+ var addresses = await asyncGeocoder.GeocodeAsync("1600 pennsylvania ave washington dc");
+ addresses.First().AssertWhiteHouse();
}
[Fact]
- public void CanGeocodeNormalizedAddress()
+ public async Task CanGeocodeNormalizedAddress()
{
- asyncGeocoder.GeocodeAsync("1600 pennsylvania ave", "washington", "dc", null, null).ContinueWith(task =>
- {
- Address[] addresses = task.Result.ToArray();
- addresses[0].AssertWhiteHouse();
- });
+ var addresses = await asyncGeocoder.GeocodeAsync("1600 pennsylvania ave", "washington", "dc", null, null);
+ addresses.First().AssertWhiteHouse();
}
[Theory]
@@ -43,11 +38,10 @@ public void CanGeocodeNormalizedAddress()
[InlineData("cs-CZ")]
public async Task CanGeocodeAddressUnderDifferentCultures(string cultureName)
{
- //Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(cultureName);
+ CultureInfo.CurrentCulture = new CultureInfo(cultureName);
- var result = await asyncGeocoder.GeocodeAsync("24 sussex drive ottawa, ontario");
- Address[] addresses = result.ToArray();
- addresses[0].AssertCanadianPrimeMinister();
+ var addresses = await asyncGeocoder.GeocodeAsync("24 sussex drive ottawa, ontario");
+ addresses.First().AssertCanadianPrimeMinister();
}
[Theory]
@@ -55,43 +49,38 @@ public async Task CanGeocodeAddressUnderDifferentCultures(string cultureName)
[InlineData("cs-CZ")]
public async Task CanReverseGeocodeAddressUnderDifferentCultures(string cultureName)
{
- //Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(cultureName);
+ CultureInfo.CurrentCulture = new CultureInfo(cultureName);
- var result = await asyncGeocoder.ReverseGeocodeAsync(38.8976777, -77.036517);
- Address[] addresses = result.ToArray();
- addresses[0].AssertWhiteHouseArea();
+ var addresses = await asyncGeocoder.ReverseGeocodeAsync(38.8976777, -77.036517);
+ addresses.First().AssertWhiteHouseArea();
}
[Fact]
- public void ShouldNotBlowUpOnBadAddress()
+ public async Task ShouldNotBlowUpOnBadAddress()
{
- asyncGeocoder.GeocodeAsync("sdlkf;jasl;kjfldksjfasldf").ContinueWith(task =>
- {
- var addresses = task.Result;
- Assert.Empty(addresses);
- });
+ var addresses = await asyncGeocoder.GeocodeAsync("sdlkf;jasl;kjfldksjfasldf");
+ Assert.Empty(addresses);
}
[Fact]
- public void CanGeocodeWithSpecialCharacters()
+ public async Task CanGeocodeWithSpecialCharacters()
{
- asyncGeocoder.GeocodeAsync("Fried St & 2nd St, Gretna, LA 70053").ContinueWith(task =>
- {
- var addresses = task.Result;
+ var addresses = await asyncGeocoder.GeocodeAsync("Fried St & 2nd St, Gretna, LA 70053");
+ Assert.NotEmpty(addresses);
+ }
- //asserting no exceptions are thrown and that we get something
- Assert.NotEmpty(addresses);
- });
+ [Fact]
+ public async Task CanGeocodeWithUnicodeCharacters()
+ {
+ var addresses = await asyncGeocoder.GeocodeAsync("Étretat, France");
+ Assert.NotEmpty(addresses);
}
[Fact]
- public void CanReverseGeocodeAsync()
+ public async Task CanReverseGeocodeAsync()
{
- asyncGeocoder.ReverseGeocodeAsync(38.8976777, -77.036517).ContinueWith(task =>
- {
- Address[] addresses = task.Result.ToArray();
- addresses[0].AssertWhiteHouseArea();
- });
+ var addresses = await asyncGeocoder.ReverseGeocodeAsync(38.8976777, -77.036517);
+ addresses.First().AssertWhiteHouse();
}
}
}
diff --git a/test/Geocoding.Tests/BingMapsTest.cs b/test/Geocoding.Tests/BingMapsTest.cs
index a99af53..fe13771 100644
--- a/test/Geocoding.Tests/BingMapsTest.cs
+++ b/test/Geocoding.Tests/BingMapsTest.cs
@@ -38,7 +38,7 @@ public async Task ApplyUserLocation(string address, double userLatitude, double
{
geoCoder.UserLocation = new Location(userLatitude, userLongitude);
BingAddress[] addresses = (await geoCoder.GeocodeAsync(address)).ToArray();
- Assert.Equal(country, addresses[0].CountryRegion);
+ Assert.Contains(addresses, x => x.CountryRegion == country);
}
[Theory]
@@ -48,8 +48,9 @@ public async Task ApplyUserLocation(string address, double userLatitude, double
public async Task ApplyUserMapView(string address, double userLatitude1, double userLongitude1, double userLatitude2, double userLongitude2, string country)
{
geoCoder.UserMapView = new Bounds(userLatitude1, userLongitude1, userLatitude2, userLongitude2);
+ geoCoder.MaxResults = 20;
BingAddress[] addresses = (await geoCoder.GeocodeAsync(address)).ToArray();
- Assert.Equal(country, addresses[0].CountryRegion);
+ Assert.Contains(addresses, x => x.CountryRegion == country);
}
[Theory]
diff --git a/test/Geocoding.Tests/GeocoderTest.cs b/test/Geocoding.Tests/GeocoderTest.cs
index 82be0a9..5b0a4e9 100644
--- a/test/Geocoding.Tests/GeocoderTest.cs
+++ b/test/Geocoding.Tests/GeocoderTest.cs
@@ -70,6 +70,7 @@ public virtual async Task ShouldNotBlowUpOnBadAddress()
[InlineData("40 1/2 Road")]
[InlineData("B's Farm RD")]
[InlineData("Wilshire & Bundy Plaza, Los Angeles")]
+ [InlineData("Étretat, France")]
public virtual async Task CanGeocodeWithSpecialCharacters(string address)
{
Address[] addresses = (await geocoder.GeocodeAsync(address)).ToArray();
@@ -106,4 +107,4 @@ public virtual async Task CanGeocodeInvalidZipCodes(string address)
Assert.NotEmpty(addresses);
}
}
-}
\ No newline at end of file
+}
diff --git a/test/Geocoding.Tests/Geocoding.Tests.csproj b/test/Geocoding.Tests/Geocoding.Tests.csproj
index 9a37e78..7bda7e6 100644
--- a/test/Geocoding.Tests/Geocoding.Tests.csproj
+++ b/test/Geocoding.Tests/Geocoding.Tests.csproj
@@ -3,11 +3,11 @@
Geocoding.net Tests
4.0.0-beta1
- netcoreapp1.1
+ netcoreapp2.1
Geocoding.Tests
Geocoding.Tests
true
- 1.1.1
+ 2.1.0
4.0.0
Tests
@@ -20,6 +20,7 @@
+
diff --git a/test/Geocoding.Tests/GoogleAsyncGeocoderTest.cs b/test/Geocoding.Tests/GoogleAsyncGeocoderTest.cs
index 4f99a21..1e317a1 100644
--- a/test/Geocoding.Tests/GoogleAsyncGeocoderTest.cs
+++ b/test/Geocoding.Tests/GoogleAsyncGeocoderTest.cs
@@ -9,14 +9,8 @@ namespace Geocoding.Tests
[Collection("Settings")]
public class GoogleAsyncGeocoderTest : AsyncGeocoderTest
{
- readonly SettingsFixture settings;
GoogleGeocoder geoCoder;
- public GoogleAsyncGeocoderTest(SettingsFixture settings)
- {
- this.settings = settings;
- }
-
protected override IGeocoder CreateAsyncGeocoder()
{
string apiKey = settings.GoogleApiKey;
@@ -38,7 +32,7 @@ protected override IGeocoder CreateAsyncGeocoder()
[InlineData("Illinois, US", GoogleAddressType.AdministrativeAreaLevel1)]
[InlineData("New York, New York", GoogleAddressType.Locality)]
[InlineData("90210, US", GoogleAddressType.PostalCode)]
- [InlineData("1600 pennsylvania ave washington dc", GoogleAddressType.StreetAddress)]
+ [InlineData("1600 pennsylvania ave washington dc", GoogleAddressType.Establishment)]
public async Task CanParseAddressTypes(string address, GoogleAddressType type)
{
var result = await geoCoder.GeocodeAsync(address);
diff --git a/test/Geocoding.Tests/GoogleGeocoderTest.cs b/test/Geocoding.Tests/GoogleGeocoderTest.cs
index ae80190..8cdcc6c 100644
--- a/test/Geocoding.Tests/GoogleGeocoderTest.cs
+++ b/test/Geocoding.Tests/GoogleGeocoderTest.cs
@@ -36,7 +36,7 @@ protected override IGeocoder CreateGeocoder()
[InlineData("Illinois, US", GoogleAddressType.AdministrativeAreaLevel1)]
[InlineData("New York, New York", GoogleAddressType.Locality)]
[InlineData("90210, US", GoogleAddressType.PostalCode)]
- [InlineData("1600 pennsylvania ave washington dc", GoogleAddressType.StreetAddress)]
+ [InlineData("1600 pennsylvania ave washington dc", GoogleAddressType.Establishment)]
[InlineData("muswellbrook 2 New South Wales Australia", GoogleAddressType.Unknown)]
public async Task CanParseAddressTypes(string address, GoogleAddressType type)
{
diff --git a/test/Geocoding.Tests/HereAsyncGeocoderTest.cs b/test/Geocoding.Tests/HereAsyncGeocoderTest.cs
new file mode 100644
index 0000000..7bbded6
--- /dev/null
+++ b/test/Geocoding.Tests/HereAsyncGeocoderTest.cs
@@ -0,0 +1,16 @@
+using Geocoding.Here;
+using Xunit;
+
+namespace Geocoding.Tests
+{
+ [Collection("Settings")]
+ public class HereAsyncGeocoderTest : AsyncGeocoderTest
+ {
+ HereGeocoder geoCoder;
+
+ protected override IGeocoder CreateAsyncGeocoder()
+ {
+ return new HereGeocoder(settings.HereAppId, settings.HereAppCode);
+ }
+ }
+}
diff --git a/test/Geocoding.Tests/MapQuestAsyncGeocoderTest.cs b/test/Geocoding.Tests/MapQuestAsyncGeocoderTest.cs
new file mode 100644
index 0000000..0c29046
--- /dev/null
+++ b/test/Geocoding.Tests/MapQuestAsyncGeocoderTest.cs
@@ -0,0 +1,17 @@
+using Geocoding.MapQuest;
+using Xunit;
+
+namespace Geocoding.Tests
+{
+ [Collection("Settings")]
+ public class MapQuestAsyncGeocoderTest : AsyncGeocoderTest
+ {
+ protected override IGeocoder CreateAsyncGeocoder()
+ {
+ return new MapQuestGeocoder(settings.MapQuestKey)
+ {
+ UseOSM = false
+ };
+ }
+ }
+}
diff --git a/test/Geocoding.Tests/MapQuestGeocoderTest.cs b/test/Geocoding.Tests/MapQuestGeocoderTest.cs
index 688270c..f6cb806 100644
--- a/test/Geocoding.Tests/MapQuestGeocoderTest.cs
+++ b/test/Geocoding.Tests/MapQuestGeocoderTest.cs
@@ -1,4 +1,7 @@
-using Geocoding.MapQuest;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Geocoding.MapQuest;
using Xunit;
namespace Geocoding.Tests
@@ -6,15 +9,27 @@ namespace Geocoding.Tests
[Collection("Settings")]
public class MapQuestGeocoderTest : GeocoderTest
{
+ MapQuestGeocoder geocoder;
+
public MapQuestGeocoderTest(SettingsFixture settings)
: base(settings) { }
protected override IGeocoder CreateGeocoder()
{
- return new MapQuestGeocoder(settings.MapQuestKey)
+ geocoder = new MapQuestGeocoder(settings.MapQuestKey)
{
UseOSM = false
};
+ return geocoder;
}
+
+ [Fact]
+ public virtual async Task CanGeocodeNeighborhood()
+ {
+ // Regression test: Addresses with Quality=NEIGHBORHOOD are not returned
+ Address[] addresses = (await geocoder.GeocodeAsync("North Sydney, New South Wales, Australia")).ToArray();
+ Assert.NotEmpty(addresses);
+ }
+
}
}
diff --git a/test/Geocoding.Tests/SettingsFixture.cs b/test/Geocoding.Tests/SettingsFixture.cs
index 74fdfca..ef8f78e 100644
--- a/test/Geocoding.Tests/SettingsFixture.cs
+++ b/test/Geocoding.Tests/SettingsFixture.cs
@@ -39,6 +39,16 @@ public string MapQuestKey
{
get { return config.GetValue("mapQuestKey"); }
}
+
+ public string HereAppId
+ {
+ get { return config.GetValue("hereAppId"); }
+ }
+
+ public string HereAppCode
+ {
+ get { return config.GetValue("hereAppCode"); }
+ }
}
[CollectionDefinition("Settings")]
diff --git a/test/Geocoding.Tests/settings.json b/test/Geocoding.Tests/settings.json
index 1e5608d..47e5f12 100644
--- a/test/Geocoding.Tests/settings.json
+++ b/test/Geocoding.Tests/settings.json
@@ -6,5 +6,8 @@
"googleApiKey": "",
- "mapQuestKey": ""
+ "mapQuestKey": "",
+
+ "hereAppId": "",
+ "hereAppCode": ""
}