From 3ee0adc265790894057538c3249a633e3d10a827 Mon Sep 17 00:00:00 2001 From: Rodney Richardson Date: Thu, 10 Dec 2020 13:51:38 +0000 Subject: [PATCH 1/3] Update Geocoding.Here to use "Geocoding and Search API v7". --- README.md | 4 +- src/Geocoding.Core/QueryBuilder.cs | 78 +++++++ src/Geocoding.Core/QueryParameter.cs | 29 +++ src/Geocoding.Here/HereAddress.cs | 11 +- src/Geocoding.Here/HereGeocoder.cs | 196 ++++++++--------- src/Geocoding.Here/HereGeocodingException.cs | 17 +- src/Geocoding.Here/HereLocationType.cs | 39 ---- src/Geocoding.Here/Json.cs | 203 +++++++++--------- .../AddressAssertionExtensions.cs | 14 +- test/Geocoding.Tests/HereAsyncGeocoderTest.cs | 3 +- test/Geocoding.Tests/SettingsFixture.cs | 9 +- test/Geocoding.Tests/settings.json | 3 +- 12 files changed, 332 insertions(+), 274 deletions(-) create mode 100644 src/Geocoding.Core/QueryBuilder.cs create mode 100644 src/Geocoding.Core/QueryParameter.cs delete mode 100644 src/Geocoding.Here/HereLocationType.cs diff --git a/README.md b/README.md index 3c515f2..6b5f3ed 100644 --- a/README.md +++ b/README.md @@ -7,7 +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) + * [HERE](https://www.here.com/) - [docs](https://developer.here.com/documentation/geocoding-search-api) 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. @@ -73,7 +73,7 @@ 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) +HERE requires an [API key](https://developer.here.com/?create=Freemium-Basic&keepState=true&step=account). ## How to Build from Source diff --git a/src/Geocoding.Core/QueryBuilder.cs b/src/Geocoding.Core/QueryBuilder.cs new file mode 100644 index 0000000..3cdf672 --- /dev/null +++ b/src/Geocoding.Core/QueryBuilder.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Text; + +namespace Geocoding +{ + /// + /// Provides a custom constructor for Uri query strings. + /// + public class QueryBuilder + { + private readonly List parameters = new List(); + + /// + /// Adds the parameter. + /// + /// The parameter to add. + /// This instance of the Query builder, to allow Fluent calls. + public QueryBuilder AddParameter(QueryParameter parameter) + { + parameters.Add(parameter); + return this; + } + + /// + /// Adds the parameter. + /// + /// The name/key of the parameter. + /// The value of parameter. + /// Both name and value will be Url Encoded. + /// This instance of the Query builder, to allow Fluent calls. + public QueryBuilder AddParameter(String name, String value) + { + return AddParameter(new QueryParameter(name, value)); + } + + /// + /// Adds the parameter if the value is not null or Empty. + /// + /// The name/key of the parameter. + /// The value of parameter. + /// Both name and value will be Url Encoded. + /// This instance of the Query builder, to allow Fluent calls. + public QueryBuilder AddNonEmptyParameter(String name, String value) + { + if (!string.IsNullOrEmpty(value)) + { + AddParameter(new QueryParameter(name, value)); + } + return this; + } + + /// + /// Adds the parameter. + /// + /// The parameter to add. + /// This instance of the Query builder, to allow Fluent calls. + public QueryBuilder AddParameters(IEnumerable parameters) + { + foreach (QueryParameter parameter in parameters) + { + AddParameter(parameter); + } + return this; + } + + /// + /// Gets the query string. + /// + public String GetQuery(String separator = "&") + { + return String.Join(separator, parameters.Select(p => p.Query)); + } + } +} diff --git a/src/Geocoding.Core/QueryParameter.cs b/src/Geocoding.Core/QueryParameter.cs new file mode 100644 index 0000000..2d2f75a --- /dev/null +++ b/src/Geocoding.Core/QueryParameter.cs @@ -0,0 +1,29 @@ +using System; +using System.Globalization; +using System.Net; + +namespace Geocoding +{ + public class QueryParameter + { + public String Name { get; } + public String Value { get; } + + public QueryParameter(String name, String value) + { + Name = name; + Value = value; + } + + public String Query + { + get + { + return String.Format(CultureInfo.InvariantCulture, + "{0}={1}", + WebUtility.UrlEncode(Name), + WebUtility.UrlEncode(Value)); + } + } + } +} diff --git a/src/Geocoding.Here/HereAddress.cs b/src/Geocoding.Here/HereAddress.cs index 5067d5b..900205f 100644 --- a/src/Geocoding.Here/HereAddress.cs +++ b/src/Geocoding.Here/HereAddress.cs @@ -4,8 +4,7 @@ namespace Geocoding.Here { public class HereAddress : Address { - readonly string street, houseNumber, city, state, country, postalCode; - readonly HereLocationType type; + readonly string street, houseNumber, city, state, country, postalCode, resultType; public string AddressLine { @@ -37,13 +36,13 @@ public string PostalCode get { return postalCode ?? ""; } } - public HereLocationType Type + public String ResultType { - get { return type; } + get { return resultType; } } public HereAddress(string formattedAddress, Location coordinates, string street, string houseNumber, string city, - string state, string postalCode, string country, HereLocationType type) + string state, string postalCode, string country, string resultType) : base(formattedAddress, coordinates, "HERE") { this.street = street; @@ -52,7 +51,7 @@ public HereAddress(string formattedAddress, Location coordinates, string street, this.state = state; this.postalCode = postalCode; this.country = country; - this.type = type; + this.resultType = resultType; } } } diff --git a/src/Geocoding.Here/HereGeocoder.cs b/src/Geocoding.Here/HereGeocoder.cs index 79f3dad..d961770 100644 --- a/src/Geocoding.Here/HereGeocoder.cs +++ b/src/Geocoding.Here/HereGeocoder.cs @@ -16,111 +16,99 @@ namespace Geocoding.Here /// 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; + const string URL_GEOCODE = "https://geocode.search.hereapi.com/v1/geocode"; + const string URL_REVERSE_GEOCODE = "https://revgeocode.search.hereapi.com/v1/revgeocode"; + + const string QN_API_KEY = "apiKey"; + const string QN_LIMIT = "limit"; + + const string QN_GEOCODE_QUERY = "q"; + const string QN_GEOCODE_STRUCTURED_QUERY = "qq"; + + const string SQN_STREET = "street"; + const string SQN_CITY = "city"; + const string SQN_STATE = "state"; + const string SQN_POSTAL_CODE = "postalCode"; + const string SQN_COUNTRY = "country"; + + const string QN_REVERSE_GEOCODE_QUERY = "at"; + + readonly string apiKey; 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) + public HereGeocoder(string apiKey) { - 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"); + if (string.IsNullOrWhiteSpace(apiKey)) + throw new ArgumentException("apiKey can not be null or empty"); - this.appId = appId; - this.appCode = appCode; + this.apiKey = apiKey; } - private string GetQueryUrl(string address) + private Uri GetQueryUrl(string address) { - var parameters = new StringBuilder(); - var first = AppendParameter(parameters, address, SEARCHTEXT, true); - AppendGlobalParameters(parameters, first); + UriBuilder uriBuilder = new UriBuilder(URL_GEOCODE); - return string.Format(GEOCODING_QUERY, appId, appCode, parameters.ToString()); - } + QueryBuilder queryBuilder = new QueryBuilder(); + queryBuilder.AddParameter(QN_GEOCODE_QUERY, address) + .AddParameters(GetGlobalParameters()); - 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()); + uriBuilder.Query = queryBuilder.GetQuery(); + return uriBuilder.Uri; } - private string GetQueryUrl(double latitude, double longitude) + private Uri GetQueryUrl(string street, string city, string state, string postalCode, string country) { - var parameters = new StringBuilder(); - var first = AppendParameter(parameters, string.Format(CultureInfo.InvariantCulture, "{0},{1}", latitude, longitude), PROX, true); - AppendGlobalParameters(parameters, first); + UriBuilder uriBuilder = new UriBuilder(URL_GEOCODE); - return string.Format(REVERSE_GEOCODING_QUERY, appId, appCode, parameters.ToString()); - } + QueryBuilder structuredQueryBuilder = new QueryBuilder(); + structuredQueryBuilder.AddNonEmptyParameter(SQN_STREET, street) + .AddNonEmptyParameter(SQN_CITY, city) + .AddNonEmptyParameter(SQN_STATE, state) + .AddNonEmptyParameter(SQN_POSTAL_CODE, postalCode) + .AddNonEmptyParameter(SQN_COUNTRY, country); - private IEnumerable> GetGlobalParameters() - { - if (UserLocation != null) - yield return new KeyValuePair("prox", UserLocation.ToString()); + QueryBuilder queryBuilder = new QueryBuilder(); + queryBuilder.AddParameter(QN_GEOCODE_STRUCTURED_QUERY, structuredQueryBuilder.GetQuery(";")) + .AddParameters(GetGlobalParameters()); - 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)); + uriBuilder.Query = queryBuilder.GetQuery(); + return uriBuilder.Uri; } - private bool AppendGlobalParameters(StringBuilder parameters, bool first) + private Uri GetQueryUrl(double latitude, double longitude) { - var values = GetGlobalParameters().ToArray(); + UriBuilder uriBuilder = new UriBuilder(URL_REVERSE_GEOCODE); - if (!first) parameters.Append("&"); - parameters.Append(BuildQueryString(values)); + QueryBuilder queryBuilder = new QueryBuilder(); + queryBuilder.AddParameter(QN_REVERSE_GEOCODE_QUERY, string.Format(CultureInfo.InvariantCulture, "{0},{1}", latitude, longitude)) + .AddParameters(GetGlobalParameters()); - return first && !values.Any(); + uriBuilder.Query = queryBuilder.GetQuery(); + return uriBuilder.Uri; } - private string BuildQueryString(IEnumerable> parameters) + private IEnumerable GetGlobalParameters() { - var builder = new StringBuilder(); - foreach (var pair in parameters) - { - if (builder.Length > 0) builder.Append("&"); + if (MaxResults != null && MaxResults.Value > 0) + yield return new QueryParameter(QN_LIMIT, MaxResults.Value.ToString(CultureInfo.InvariantCulture)); - builder.Append(UrlEncode(pair.Key)); - builder.Append("="); - builder.Append(UrlEncode(pair.Value)); - } - return builder.ToString(); + yield return new QueryParameter(QN_API_KEY, this.apiKey); } public async Task> GeocodeAsync(string address, CancellationToken cancellationToken = default(CancellationToken)) { try { - var url = GetQueryUrl(address); + Uri url = GetQueryUrl(address); var response = await GetResponse(url, cancellationToken).ConfigureAwait(false); return ParseResponse(response); } + catch (HereGeocodingException) + { + throw; + } catch (Exception ex) { throw new HereGeocodingException(ex); @@ -135,6 +123,10 @@ private string BuildQueryString(IEnumerable> parame var response = await GetResponse(url, cancellationToken).ConfigureAwait(false); return ParseResponse(response); } + catch (HereGeocodingException) + { + throw; + } catch (Exception ex) { throw new HereGeocodingException(ex); @@ -157,6 +149,10 @@ private string BuildQueryString(IEnumerable> parame var response = await GetResponse(url, cancellationToken).ConfigureAwait(false); return ParseResponse(response); } + catch (HereGeocodingException) + { + throw; + } catch (Exception ex) { throw new HereGeocodingException(ex); @@ -183,42 +179,24 @@ async Task> IGeocoder.ReverseGeocodeAsync(double latitude, 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) + private IEnumerable ParseResponse(IEnumerable responseItems) { - foreach (var view in response.View) + foreach (var item in responseItems) { - 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)); - } + yield return new HereAddress( + item.Address.Label, + new Location(item.Position.Latitude, item.Position.Longitude), + item.Address.Street, + item.Address.HouseNumber, + item.Address.City, + item.Address.State, + item.Address.PostalCode, + item.Address.CountryName, + item.ResultType); } } - private HttpRequestMessage CreateRequest(string url) + private HttpRequestMessage CreateRequest(Uri url) { return new HttpRequestMessage(HttpMethod.Get, url); } @@ -232,22 +210,28 @@ private HttpClient BuildClient() return new HttpClient(handler); } - private async Task GetResponse(string queryURL, CancellationToken cancellationToken) + private async Task GetResponse(Uri url, CancellationToken cancellationToken) { using (var client = BuildClient()) { - var response = await client.SendAsync(CreateRequest(queryURL), cancellationToken).ConfigureAwait(false); + HttpResponseMessage response = await client.SendAsync(CreateRequest(url), 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) + if (serverResponse.StatusCode != null) { - throw new HereGeocodingException(serverResponse.Details, serverResponse.ErrorType, serverResponse.ErrorType); + throw new HereGeocodingException( + serverResponse.Title, + serverResponse.StatusCode.Value, + serverResponse.Cause, + serverResponse.Action, + serverResponse.CorrelationId, + serverResponse.RequestId); } - return serverResponse.Response; + return serverResponse.Items; } } } diff --git a/src/Geocoding.Here/HereGeocodingException.cs b/src/Geocoding.Here/HereGeocodingException.cs index e9204bf..d7dc22f 100644 --- a/src/Geocoding.Here/HereGeocodingException.cs +++ b/src/Geocoding.Here/HereGeocodingException.cs @@ -7,20 +7,25 @@ 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 int StatusCode { get; set; } + public string Cause { get; set; } + public string Action { get; set; } + public string CorrelationId { get; set; } + public string RequestId { get; set; } public HereGeocodingException(Exception innerException) : base(defaultMessage, innerException) { } - public HereGeocodingException(string message, string errorType, string errorSubtype) + public HereGeocodingException(string message, int statusCode, string cause, string action, string correlationId, string requestId) : base(message) { - ErrorType = errorType; - ErrorSubtype = errorSubtype; + StatusCode = statusCode; + Cause = cause; + Action = action; + CorrelationId = correlationId; + RequestId = requestId; } } } diff --git a/src/Geocoding.Here/HereLocationType.cs b/src/Geocoding.Here/HereLocationType.cs deleted file mode 100644 index 9d40fc9..0000000 --- a/src/Geocoding.Here/HereLocationType.cs +++ /dev/null @@ -1,39 +0,0 @@ -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/Json.cs b/src/Geocoding.Here/Json.cs index 9512514..c3ab864 100644 --- a/src/Geocoding.Here/Json.cs +++ b/src/Geocoding.Here/Json.cs @@ -4,107 +4,118 @@ 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 ServerResponse + { + // Successful geocode fields + [DataMember(Name = "items")] + public Item[] Items { get; set; } - [DataContract] - public class Response - { - [DataMember(Name = "View")] - public View[] View { get; set; } - } + // Error fields + [DataMember(Name = "status")] + public int? StatusCode { get; set; } + [DataMember(Name = "title")] + public string Title { get; set; } + [DataMember(Name = "cause")] + public string Cause { get; set; } + [DataMember(Name = "action")] + public string Action { get; set; } + [DataMember(Name = "correlationId")] + public string CorrelationId { get; set; } + [DataMember(Name = "requestId")] + public string RequestId { 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 Item + { + [DataMember(Name = "title")] + public String Title { get; set; } + [DataMember(Name = "id")] + public String Id { get; set; } + [DataMember(Name = "resultType")] + public String ResultType { get; set; } + [DataMember(Name = "houseNumberType")] + public String HouseNumberType { get; set; } + [DataMember(Name = "address")] + public Address Address { get; set; } + [DataMember(Name = "position")] + public GeoCoordinate Position { get; set; } + [DataMember(Name = "access")] + public GeoCoordinate[] Access { get; set; } + [DataMember(Name = "mapView")] + public GeoBoundingBox MapView { get; set; } + [DataMember(Name = "scoring")] + public Scoring Scoring { 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 Address + { + [DataMember(Name = "label")] + public string Label { get; set; } + [DataMember(Name = "countryCode")] + public string CountryCode { get; set; } + [DataMember(Name = "countryname")] + public string CountryName { get; set; } + [DataMember(Name = "stateCode")] + public string StateCode { 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 = "cistrict")] + public string District { 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; } + } - [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 = "lat")] + public double Latitude { get; set; } + [DataMember(Name = "lng")] + public double Longitude { 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 = "west")] + public double West { get; set; } + [DataMember(Name = "south")] + public double South { get; set; } + [DataMember(Name = "east")] + public double East { get; set; } + [DataMember(Name = "north")] + public double North { 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 Scoring + { + [DataMember(Name = "queryScore")] + public double QueryScore { get; set; } + [DataMember(Name = "fieldScore")] + public FieldScoring FieldScore { get; set; } + } + + [DataContract] + public class FieldScoring + { + [DataMember(Name = "city")] + public double City { get; set; } + [DataMember(Name = "streets")] + public double[] Streets { get; set; } + [DataMember(Name = "houseNumber")] + public double HouseNumber { 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/test/Geocoding.Tests/AddressAssertionExtensions.cs b/test/Geocoding.Tests/AddressAssertionExtensions.cs index 6ac30b4..f3473d3 100644 --- a/test/Geocoding.Tests/AddressAssertionExtensions.cs +++ b/test/Geocoding.Tests/AddressAssertionExtensions.cs @@ -10,10 +10,8 @@ public static void AssertWhiteHouse(this Address address) string adr = address.FormattedAddress.ToLower(); Assert.True( 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") + adr.Contains("1600 pennsylvania avenue") || + adr.Contains("1600 pennsylvania ave") ); AssertWhiteHouseArea(address); } @@ -27,11 +25,9 @@ public static void AssertWhiteHouseArea(this Address address) ); //just hoping that each geocoder implementation gets it somewhere near the vicinity - double lat = Math.Round(address.Coordinates.Latitude, 2); - Assert.Equal(38.90, lat); + Assert.Equal(38.9, address.Coordinates.Latitude, 1); - double lng = Math.Round(address.Coordinates.Longitude, 2); - Assert.Equal(-77.04, lng); + Assert.Equal(-77.0, address.Coordinates.Longitude, 1); } public static void AssertCanadianPrimeMinister(this Address address) @@ -43,4 +39,4 @@ public static void AssertCanadianPrimeMinister(this Address address) Assert.True(adr.Contains("k1m")); } } -} \ No newline at end of file +} diff --git a/test/Geocoding.Tests/HereAsyncGeocoderTest.cs b/test/Geocoding.Tests/HereAsyncGeocoderTest.cs index 7bbded6..4c249dc 100644 --- a/test/Geocoding.Tests/HereAsyncGeocoderTest.cs +++ b/test/Geocoding.Tests/HereAsyncGeocoderTest.cs @@ -10,7 +10,8 @@ public class HereAsyncGeocoderTest : AsyncGeocoderTest protected override IGeocoder CreateAsyncGeocoder() { - return new HereGeocoder(settings.HereAppId, settings.HereAppCode); + geoCoder = new HereGeocoder(settings.HereApiKey); + return geoCoder; } } } diff --git a/test/Geocoding.Tests/SettingsFixture.cs b/test/Geocoding.Tests/SettingsFixture.cs index ef8f78e..5d96fe1 100644 --- a/test/Geocoding.Tests/SettingsFixture.cs +++ b/test/Geocoding.Tests/SettingsFixture.cs @@ -40,14 +40,9 @@ public string MapQuestKey get { return config.GetValue("mapQuestKey"); } } - public string HereAppId + public string HereApiKey { - get { return config.GetValue("hereAppId"); } - } - - public string HereAppCode - { - get { return config.GetValue("hereAppCode"); } + get { return config.GetValue("hereApiKey"); } } } diff --git a/test/Geocoding.Tests/settings.json b/test/Geocoding.Tests/settings.json index 47e5f12..9e1db03 100644 --- a/test/Geocoding.Tests/settings.json +++ b/test/Geocoding.Tests/settings.json @@ -8,6 +8,5 @@ "mapQuestKey": "", - "hereAppId": "", - "hereAppCode": "" + "hereApiKey": "" } From 5d09f12b78fe02e695689181334afddfce92988b Mon Sep 17 00:00:00 2001 From: Rodney Richardson Date: Thu, 10 Dec 2020 17:42:55 +0000 Subject: [PATCH 2/3] Handle different error response when apiKey is invalid. --- src/Geocoding.Here/HereGeocoder.cs | 11 +- src/Geocoding.Here/HereGeocodingException.cs | 7 + src/Geocoding.Here/Json.cs | 218 ++++++++++--------- 3 files changed, 128 insertions(+), 108 deletions(-) diff --git a/src/Geocoding.Here/HereGeocoder.cs b/src/Geocoding.Here/HereGeocoder.cs index d961770..9abd521 100644 --- a/src/Geocoding.Here/HereGeocoder.cs +++ b/src/Geocoding.Here/HereGeocoder.cs @@ -223,13 +223,20 @@ private HttpClient BuildClient() if (serverResponse.StatusCode != null) { throw new HereGeocodingException( - serverResponse.Title, - serverResponse.StatusCode.Value, + serverResponse.Title, + serverResponse.StatusCode.Value, serverResponse.Cause, serverResponse.Action, serverResponse.CorrelationId, serverResponse.RequestId); } + else if (serverResponse.Error != null) + { + throw new HereGeocodingException( + serverResponse.ErrorDescription, + (int)response.StatusCode, + serverResponse.Error); + } return serverResponse.Items; } diff --git a/src/Geocoding.Here/HereGeocodingException.cs b/src/Geocoding.Here/HereGeocodingException.cs index d7dc22f..15b52c3 100644 --- a/src/Geocoding.Here/HereGeocodingException.cs +++ b/src/Geocoding.Here/HereGeocodingException.cs @@ -18,6 +18,13 @@ public HereGeocodingException(Exception innerException) { } + public HereGeocodingException(string message, int statusCode, string cause) + : base(message) + { + StatusCode = statusCode; + Cause = cause; + } + public HereGeocodingException(string message, int statusCode, string cause, string action, string correlationId, string requestId) : base(message) { diff --git a/src/Geocoding.Here/Json.cs b/src/Geocoding.Here/Json.cs index c3ab864..3d520c1 100644 --- a/src/Geocoding.Here/Json.cs +++ b/src/Geocoding.Here/Json.cs @@ -4,118 +4,124 @@ namespace Geocoding.Here.Json { - [DataContract] - public class ServerResponse - { - // Successful geocode fields - [DataMember(Name = "items")] - public Item[] Items { get; set; } + [DataContract] + public class ServerResponse + { + // Successful geocode fields + [DataMember(Name = "items")] + public Item[] Items { get; set; } - // Error fields - [DataMember(Name = "status")] - public int? StatusCode { get; set; } - [DataMember(Name = "title")] - public string Title { get; set; } - [DataMember(Name = "cause")] - public string Cause { get; set; } - [DataMember(Name = "action")] - public string Action { get; set; } - [DataMember(Name = "correlationId")] - public string CorrelationId { get; set; } - [DataMember(Name = "requestId")] - public string RequestId { get; set; } - } + // Error fields + [DataMember(Name = "status")] + public int? StatusCode { get; set; } + [DataMember(Name = "title")] + public string Title { get; set; } + [DataMember(Name = "cause")] + public string Cause { get; set; } + [DataMember(Name = "action")] + public string Action { get; set; } + [DataMember(Name = "correlationId")] + public string CorrelationId { get; set; } + [DataMember(Name = "requestId")] + public string RequestId { get; set; } - [DataContract] - public class Item - { - [DataMember(Name = "title")] - public String Title { get; set; } - [DataMember(Name = "id")] - public String Id { get; set; } - [DataMember(Name = "resultType")] - public String ResultType { get; set; } - [DataMember(Name = "houseNumberType")] - public String HouseNumberType { get; set; } - [DataMember(Name = "address")] - public Address Address { get; set; } - [DataMember(Name = "position")] - public GeoCoordinate Position { get; set; } - [DataMember(Name = "access")] - public GeoCoordinate[] Access { get; set; } - [DataMember(Name = "mapView")] - public GeoBoundingBox MapView { get; set; } - [DataMember(Name = "scoring")] - public Scoring Scoring { get; set; } - } + // Other error fields + [DataMember(Name = "error")] + public string Error { get; set; } + [DataMember(Name = "error_description")] + public string ErrorDescription { get; set; } + } - [DataContract] - public class Address - { - [DataMember(Name = "label")] - public string Label { get; set; } - [DataMember(Name = "countryCode")] - public string CountryCode { get; set; } - [DataMember(Name = "countryname")] - public string CountryName { get; set; } - [DataMember(Name = "stateCode")] - public string StateCode { 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 = "cistrict")] - public string District { 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; } - } + [DataContract] + public class Item + { + [DataMember(Name = "title")] + public String Title { get; set; } + [DataMember(Name = "id")] + public String Id { get; set; } + [DataMember(Name = "resultType")] + public String ResultType { get; set; } + [DataMember(Name = "houseNumberType")] + public String HouseNumberType { get; set; } + [DataMember(Name = "address")] + public Address Address { get; set; } + [DataMember(Name = "position")] + public GeoCoordinate Position { get; set; } + [DataMember(Name = "access")] + public GeoCoordinate[] Access { get; set; } + [DataMember(Name = "mapView")] + public GeoBoundingBox MapView { get; set; } + [DataMember(Name = "scoring")] + public Scoring Scoring { get; set; } + } - [DataContract] - public class GeoCoordinate - { - [DataMember(Name = "lat")] - public double Latitude { get; set; } - [DataMember(Name = "lng")] - public double Longitude { get; set; } - } + [DataContract] + public class Address + { + [DataMember(Name = "label")] + public string Label { get; set; } + [DataMember(Name = "countryCode")] + public string CountryCode { get; set; } + [DataMember(Name = "countryname")] + public string CountryName { get; set; } + [DataMember(Name = "stateCode")] + public string StateCode { 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 = "cistrict")] + public string District { 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; } + } - [DataContract] - public class GeoBoundingBox - { - [DataMember(Name = "west")] - public double West { get; set; } - [DataMember(Name = "south")] - public double South { get; set; } - [DataMember(Name = "east")] - public double East { get; set; } - [DataMember(Name = "north")] - public double North { get; set; } - } + [DataContract] + public class GeoCoordinate + { + [DataMember(Name = "lat")] + public double Latitude { get; set; } + [DataMember(Name = "lng")] + public double Longitude { get; set; } + } - [DataContract] - public class Scoring - { - [DataMember(Name = "queryScore")] - public double QueryScore { get; set; } - [DataMember(Name = "fieldScore")] - public FieldScoring FieldScore { get; set; } - } + [DataContract] + public class GeoBoundingBox + { + [DataMember(Name = "west")] + public double West { get; set; } + [DataMember(Name = "south")] + public double South { get; set; } + [DataMember(Name = "east")] + public double East { get; set; } + [DataMember(Name = "north")] + public double North { get; set; } + } - [DataContract] - public class FieldScoring - { - [DataMember(Name = "city")] - public double City { get; set; } - [DataMember(Name = "streets")] - public double[] Streets { get; set; } - [DataMember(Name = "houseNumber")] - public double HouseNumber { get; set; } - } + [DataContract] + public class Scoring + { + [DataMember(Name = "queryScore")] + public double QueryScore { get; set; } + [DataMember(Name = "fieldScore")] + public FieldScoring FieldScore { get; set; } + } + + [DataContract] + public class FieldScoring + { + [DataMember(Name = "city")] + public double City { get; set; } + [DataMember(Name = "streets")] + public double[] Streets { get; set; } + [DataMember(Name = "houseNumber")] + public double HouseNumber { get; set; } + } } From 2eecab83a1862773a18d971206ebba2bf7f3f625 Mon Sep 17 00:00:00 2001 From: Rodney Richardson Date: Tue, 15 Feb 2022 17:41:13 +0000 Subject: [PATCH 3/3] Fix potential NullReferenceException when parsing response. --- src/Geocoding.Here/HereGeocoder.cs | 33 +++++++++++++----------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/Geocoding.Here/HereGeocoder.cs b/src/Geocoding.Here/HereGeocoder.cs index 9abd521..97e820d 100644 --- a/src/Geocoding.Here/HereGeocoder.cs +++ b/src/Geocoding.Here/HereGeocoder.cs @@ -181,18 +181,21 @@ async Task> IGeocoder.ReverseGeocodeAsync(double latitude, private IEnumerable ParseResponse(IEnumerable responseItems) { - foreach (var item in responseItems) + if (responseItems != null) { - yield return new HereAddress( - item.Address.Label, - new Location(item.Position.Latitude, item.Position.Longitude), - item.Address.Street, - item.Address.HouseNumber, - item.Address.City, - item.Address.State, - item.Address.PostalCode, - item.Address.CountryName, - item.ResultType); + foreach (var item in responseItems) + { + yield return new HereAddress( + item.Address.Label, + new Location(item.Position.Latitude, item.Position.Longitude), + item.Address.Street, + item.Address.HouseNumber, + item.Address.City, + item.Address.State, + item.Address.PostalCode, + item.Address.CountryName, + item.ResultType); + } } } @@ -242,13 +245,5 @@ private HttpClient BuildClient() } } } - - private string UrlEncode(string toEncode) - { - if (string.IsNullOrEmpty(toEncode)) - return string.Empty; - - return WebUtility.UrlEncode(toEncode); - } } }