Open user94857 opened 6 years ago
I managed to fix the problem. It has to do with:
private string BingUrlEncode(string toEncode)
{
if (string.IsNullOrEmpty(toEncode))
return string.Empty;
return WebUtility.UrlEncode(toEncode);
}
basically what's happening is the whole url gets incoded and the longitude latitude coordinates get messed up so the request never brings back the results we want.
here's an example of a request url that's messed up by the urlencoder:
/REST/v1/Locations?q=29+E+ST%2C+K8H+2N7)&userLocation=45.322079%2C+-75.668253&userMapView=44.0626490070541%2C+-78.23151953125%2C47.0320948847828%2C+-74.103400390625&inclnb=1&key=TOPSECRET
here's the correct one:
/REST/v1/Locations?q=29%20EAST%20ST%2CK8H%202N7&userMapView=44.0626490070541,-78.23151953125,47.0320948847828,-74.103400390625&inclnb=1&key=TOPSECRETE
I will post a complete rewrite I just finished with UserRegion support for anyone who's interested
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Runtime.Serialization.Json;
using System.Threading.Tasks;
using Geocoding;
using Geocoding.Microsoft;
using Geocoding.Microsoft.Json;
using Address = Geocoding.Address;
using Location = Geocoding.Location;
namespace Geocoding.Microsoft
{
public class BingMapsGeocoder : IGeocoder
{
private const string UnStructuredUrl = "http://dev.virtualearth.net/REST/v1/Locations?{0}&key={1}";
private const string StructuredUrl = "http://dev.virtualearth.net/REST/v1/Locations/{0}?key={1}";
private const string QueryParameter = "q={0}";
private const string AddressParameter = "addressLine={0}";
private const string CityParameter = "locality={0}";
private const string RegionParameter = "adminDistrict={0}";
private const string PostalCodeParameter = "postalCode={0}";
private const string CountryParameter = "countryRegion={0}";
private const string UserMapViewParameter = "userMapView={0}";
private const string UserLocationParameter = "userLocation={0}";
private const string UserIpParameter = "userIp={0}";
private const string UserRegionParameter = "userRegion={0}";
private const string CultureParameter = "culture={0}";
private const string IncludeNeighborhoodbParameter = "inclnb={0}";
private string _bingKey;
public IWebProxy Proxy { get; set; }
public BingCulture Culture { get; set; }
public Location UserLocation { get; set; }
public Bounds UserMapView { get; set; }
public IPAddress UserIp { get; set; }
public BingUserRegion UserRegion { get; set; }
public bool IncludeNeighborhood { get; set; }
public string BingKey
{
get => _bingKey;
set
{
if (string.IsNullOrEmpty(value))
throw new ArgumentException("BingKey can not be null or empty");
_bingKey = value;
}
}
public BingMapsGeocoder(string bingKey) { BingKey = bingKey; }
public async Task<IEnumerable<BingAddress>> GeocodeAsync(string address)
{
IEnumerable<BingAddress> response;
try
{
response = ParseResponse(await GetResponse(BuildQueryUrl(address)).ConfigureAwait(false));
}
catch (Exception ex)
{
throw new BingGeocodingException(ex);
}
return response;
}
public async Task<IEnumerable<BingAddress>> GeocodeAsync(string street, string city, string state, string postalCode, string country)
{
IEnumerable<BingAddress> response;
try
{
response = ParseResponse(await GetResponse(BuildQueryUrl(street, city, state, postalCode, country)).ConfigureAwait(false));
}
catch (Exception ex)
{
throw new BingGeocodingException(ex);
}
return response;
}
public async Task<IEnumerable<BingAddress>> ReverseGeocodeAsync(Location location)
{
if (location == null)
throw new ArgumentNullException(nameof(location));
return await ReverseGeocodeAsync(location.Latitude, location.Longitude).ConfigureAwait(false);
}
public async Task<IEnumerable<BingAddress>> ReverseGeocodeAsync(double latitude, double longitude)
{
IEnumerable<BingAddress> response;
try
{
response = ParseResponse(await GetResponse(BuildQueryUrl(latitude, longitude)).ConfigureAwait(false));
}
catch (Exception ex)
{
throw new BingGeocodingException(ex);
}
return response;
}
async Task<IEnumerable<Address>> IGeocoder.GeocodeAsync(string address)
{
return await GeocodeAsync(address).ConfigureAwait(false);
}
async Task<IEnumerable<Address>> IGeocoder.GeocodeAsync(string street, string city, string state, string postalCode, string country)
{
return await GeocodeAsync(street, city, state, postalCode, country).ConfigureAwait(false);
}
async Task<IEnumerable<Address>> IGeocoder.ReverseGeocodeAsync(Location location)
{
return await ReverseGeocodeAsync(location).ConfigureAwait(false);
}
async Task<IEnumerable<Address>> IGeocoder.ReverseGeocodeAsync(double latitude, double longitude)
{
return await ReverseGeocodeAsync(latitude, longitude).ConfigureAwait(false);
}
private static IEnumerable<BingAddress> ParseResponse(Response response)
{
return (from Geocoding.Microsoft.Json.Location resource in response.ResourceSets[0].Resources
select new BingAddress(resource.Address.FormattedAddress,
new Location(resource.Point.Coordinates[0], resource.Point.Coordinates[1]),
resource.Address.AddressLine,
resource.Address.AdminDistrict, resource.Address.AdminDistrict2, resource.Address.CountryRegion,
resource.Address.Locality, resource.Address.Neighborhood, resource.Address.PostalCode,
(EntityType) Enum.Parse(typeof(EntityType), resource.EntityType),
EvaluateConfidence(resource.Confidence)))
.ToList();
}
private static HttpRequestMessage CreateRequest(string url)
{
return new HttpRequestMessage(HttpMethod.Get, url);
}
private string BuildQueryString(string urlString, List<string> parameters)
{
if (string.IsNullOrEmpty(urlString))
throw new ArgumentNullException(nameof(urlString), "Bing url string must not be empty");
if (!parameters.Any())
throw new ArgumentNullException(nameof(parameters), "Must have at least one query parameter to geocode");
if (Culture != BingCulture.NotSpecified)
parameters.Add(string.Format(CultureParameter, Culture.ToEnumString()));
if (UserLocation != null)
parameters.Add(string.Format(UserLocationParameter, UserLocation.ToString().Replace(" ", "")));
if (UserMapView != null)
parameters.Add(string.Format(UserMapViewParameter,
UserMapView.SouthWest.Latitude + "," +
UserMapView.SouthWest.Longitude + "," +
UserMapView.NorthEast.Latitude + "," +
UserMapView.NorthEast.Longitude));
if (UserIp != null)
parameters.Add(string.Format(UserIpParameter, UserIp));
if (UserRegion != BingUserRegion.NotSpecified)
parameters.Add(string.Format(UserRegionParameter, UserRegion.ToEnumString()));
if (IncludeNeighborhood)
parameters.Add(string.Format(IncludeNeighborhoodbParameter, IncludeNeighborhood ? "1" : "0"));
return string.Format(urlString, string.Join("&", parameters), BingKey);
}
private string BuildQueryUrl(string address)
{
return BuildQueryString(UnStructuredUrl, new List<string> { string.Format(QueryParameter, EncodeParameter(address))});
}
private string BuildQueryUrl(double latitude, double longitude)
{
return BuildQueryString(StructuredUrl, new List<string> { latitude + "," + longitude });
}
private string BuildQueryUrl(string street, string city, string state, string postalCode, string country)
{
var parameters = new List<string>();
if (!string.IsNullOrEmpty(street))
parameters.Add(string.Format(AddressParameter, EncodeParameter(street)));
if (!string.IsNullOrEmpty(city))
parameters.Add(string.Format(CityParameter, EncodeParameter(city)));
if (!string.IsNullOrEmpty(state))
parameters.Add(string.Format(RegionParameter, EncodeParameter(state)));
if (!string.IsNullOrEmpty(postalCode))
parameters.Add(string.Format(PostalCodeParameter, EncodeParameter(postalCode)));
if (!string.IsNullOrEmpty(country))
parameters.Add(string.Format(CountryParameter, EncodeParameter(country)));
return BuildQueryString(UnStructuredUrl, parameters);
}
private HttpClient BuildClient()
{
return Proxy == null ? new HttpClient() : new HttpClient(new HttpClientHandler {Proxy = Proxy});
}
private async Task<Response> GetResponse(string queryUrl)
{
Response response;
using (var httpClient = BuildClient())
{
using (var stream = await (await httpClient.SendAsync(CreateRequest(queryUrl)).ConfigureAwait(false)).Content.ReadAsStreamAsync().ConfigureAwait(false))
response = new DataContractJsonSerializer(typeof(Response)).ReadObject(stream) as Response;
}
return response;
}
private static ConfidenceLevel EvaluateConfidence(string confidence)
{
if (confidence == null)
return ConfidenceLevel.Unknown;
switch (confidence.ToLower())
{
case "low":
return ConfidenceLevel.Low;
case "medium":
return ConfidenceLevel.Medium;
case "high":
return ConfidenceLevel.High;
default:
return ConfidenceLevel.Unknown;
}
}
private static string EncodeParameter(string parameter)
{
if (string.IsNullOrEmpty(parameter))
throw new ArgumentNullException(nameof(parameter));
return WebUtility.UrlEncode(parameter);
}
}
}
you'll need this, was a pain to write but makes things easier :)
public enum BingCulture
{
[Description("NotSpecified")] NotSpecified,
[Description("af")] Afrikaans,
[Description("sq")] Albanian,
[Description("am")] Amharic,
[Description("ar-sa")] Arabic_Saudi_Arabia,
[Description("hy")] Armenian,
[Description("as")] Assamese,
[Description("az-Latn")] Azerbaijani_Latin,
[Description("bn-BD")] Bangla_Bangladesh,
[Description("bn-IN")] Bangla_India,
[Description("eu")] Basque,
[Description("be")] Belarusian,
[Description("bs")] Bosnian_Latin,
[Description("bg")] Bulgarian,
[Description("ca")] Catalan_Spanish,
[Description("ku-Arab")] Central_Kurdish,
[Description("zh-Hans")] Chinese_Simplified,
[Description("zh-Hant")] Chinese_Traditional,
[Description("hr")] Croatian,
[Description("cs")] Czech,
[Description("da")] Danish,
[Description("prs-Arab")] Dari,
[Description("nl")] Dutch_Netherlands,
[Description("nl-BE")] Dutch_Belgium,
[Description("en-GB")] English_United_Kingdom,
[Description("en-US")] English_United_States,
[Description("et")] Estonian,
[Description("fil-Latn")] Filipino,
[Description("fi")] Finnish,
[Description("fr")] French,
[Description("fr-CA")] French_Canada,
[Description("fr-FR")] French_France,
[Description("gl")] Galician,
[Description("ka")] Georgian,
[Description("de-de")] German,
[Description("de")] German_Germany,
[Description("el")] Greek,
[Description("gu")] Gujarati,
[Description("ha-Latn")] Hausa_Latin,
[Description("he")] Hebrew,
[Description("hi")] Hindi,
[Description("hu")] Hungarian,
[Description("is")] Icelandic,
[Description("ig-Latn")] Igbo,
[Description("id")] Indonesian,
[Description("ga")] Irish,
[Description("xh")] IsiXhosa,
[Description("zu")] IsiZulu,
[Description("it")] Italian,
[Description("it-it")] Italian_Italy,
[Description("ja")] Japanese,
[Description("kn")] Kannada,
[Description("kk")] Kazakh,
[Description("km")] Khmer,
[Description("qut-Latn")] Kiche,
[Description("rw")] Kinyarwanda,
[Description("sw")] Kiswahili,
[Description("kok")] Konkani,
[Description("ko")] Korean,
[Description("ky-Cyrl")] Kyrgyz,
[Description("lv")] Latvian,
[Description("lt")] Lithuanian,
[Description("lb")] Luxembourgish,
[Description("mk")] Macedonian,
[Description("ms")] Malay_Malaysia,
[Description("ml")] Malayalam,
[Description("mt")] Maltese,
[Description("mi-Latn")] Maori,
[Description("mr")] Marathi,
[Description("mn-Cyrl")] Mongolian_Cyrillic,
[Description("ne")] Nepali_Nepal,
[Description("nb")] Norwegian_Bokmal,
[Description("nn")] Norwegian_Nynorsk,
[Description("or")] Odia,
[Description("fa")] Persian,
[Description("pl")] Polish,
[Description("pt-BR")] Portuguese_Brazil,
[Description("pt-PT")] Portuguese_Portugal,
[Description("pa-Arab")] Punjabi_Arabic,
[Description("pa")] Punjabi_Gurmukhi,
[Description("quz")] Quechua_Peru,
[Description("ro")] Romanian_Romania,
[Description("ru")] Russian,
[Description("gd-Latn")] Scottish_Gaelic,
[Description("sr-Cyrl-BA")] Serbian_Cyrillic_Bosnia_and_Herzegovina,
[Description("sr-Cyrl-RS")] Serbian_Cyrillic_Serbia,
[Description("sr-Latn-RS")] Serbian_Latin_Serbia,
[Description("nso")] Sesotho_Sa_Leboa,
[Description("tn")] Setswana,
[Description("sd-Arab")] Sindhi_Arabic,
[Description("si")] Sinhala,
[Description("sk")] Slovak,
[Description("sl")] Slovenian,
[Description("es")] Spanish,
[Description("es-MX")] Spanish_Mexico,
[Description("es-ES")] Spanish_Spain,
[Description("es-US")] Spanish_United_States,
[Description("sv")] Swedish_Sweden,
[Description("tg-Cyrl")] Tajik_Cyrillic,
[Description("ta")] Tamil,
[Description("tt-Cyrl")] Tatar_Cyrillic,
[Description("te")] Telugu,
[Description("th")] Thai,
[Description("ti")] Tigrinya,
[Description("tr")] Turkish,
[Description("tk-Latn")] Turkmen_Latin,
[Description("uk")] Ukrainian,
[Description("ur")] Urdu,
[Description("ug-Arab")] Uyghur,
[Description("uz-Latn")] Uzbek_Latin,
[Description("ca-ES-valencia")] Valencian,
[Description("vi")] Vietnamese,
[Description("cy")] Welsh,
[Description("wo")] Wolof,
[Description("yo-Latn")] Yoruba
}
and this was even more of a pain:
public enum BingUserRegion
{
[Description("NotSpecified")] NotSpecified,
[Description("AF")] Afghanistan,
[Description("AX")] Aland_Islands,
[Description("AL")] Albania,
[Description("DZ")] Algeria,
[Description("AS")] American_Samoa,
[Description("AD")] Andorra,
[Description("AO")] Angola,
[Description("AI")] Anguilla,
[Description("AQ")] Antarctica,
[Description("AG")] Antigua_And_Barbuda,
[Description("AR")] Argentina,
[Description("AM")] Armenia,
[Description("AW")] Aruba,
[Description("AU")] Australia,
[Description("AT")] Austria,
[Description("AZ")] Azerbaijan,
[Description("BS")] Bahamas,
[Description("BH")] Bahrain,
[Description("BD")] Bangladesh,
[Description("BB")] Barbados,
[Description("BY")] Belarus,
[Description("BE")] Belgium,
[Description("BZ")] Belize,
[Description("BJ")] Benin,
[Description("BM")] Bermuda,
[Description("BT")] Bhutan,
[Description("BO")] Bolivia,
[Description("BQ")] Bonaire,
[Description("BA")] Bosnia_And_Herzegovina,
[Description("BW")] Botswana,
[Description("BV")] Bouvet_Island,
[Description("BR")] Brazil,
[Description("IO")] British_Indian_Ocean_Territory,
[Description("BN")] Brunei_Darussalam,
[Description("BG")] Bulgaria,
[Description("BF")] Burkina_Faso,
[Description("BI")] Burundi,
[Description("CV")] Cabo_Verde,
[Description("KH")] Cambodia,
[Description("CM")] Cameroon,
[Description("CA")] Canada,
[Description("KY")] Cayman_Islands,
[Description("CF")] Central_African_Republic,
[Description("TD")] Chad,
[Description("CL")] Chile,
[Description("CN")] China,
[Description("CX")] Christmas_Island,
[Description("CC")] Cocos_Keeling_Islands,
[Description("CO")] Colombia,
[Description("KM")] Comoros,
[Description("CG")] Congo,
[Description("CD")] Congo_Democratic_Republic,
[Description("CK")] Cook_Islands,
[Description("CR")] Costa_Rica,
[Description("CI")] Cote_Divoire,
[Description("HR")] Croatia,
[Description("CU")] Cuba,
[Description("CW")] Curacao,
[Description("CY")] Cyprus,
[Description("CZ")] Czechia,
[Description("DK")] Denmark,
[Description("DJ")] Djibouti,
[Description("DM")] Dominica,
[Description("DO")] Dominican_Republic,
[Description("EC")] Ecuador,
[Description("EG")] Egypt,
[Description("SV")] El_Salvador,
[Description("GQ")] Equatorial_Guinea,
[Description("ER")] Eritrea,
[Description("EE")] Estonia,
[Description("ET")] Ethiopia,
[Description("FK")] Falkland_Islands_Malvinas,
[Description("FO")] Faroe_Islands,
[Description("FJ")] Fiji,
[Description("FI")] Finland,
[Description("FR")] France,
[Description("GF")] French_Guiana,
[Description("PF")] French_Polynesia,
[Description("TF")] French_Southern_Territories,
[Description("GA")] Gabon,
[Description("GM")] Gambia,
[Description("GE")] Georgia,
[Description("DE")] Germany,
[Description("GH")] Ghana,
[Description("GI")] Gibraltar,
[Description("GR")] Greece,
[Description("GL")] Greenland,
[Description("GD")] Grenada,
[Description("GP")] Guadeloupe,
[Description("GU")] Guam,
[Description("GT")] Guatemala,
[Description("GG")] Guernsey,
[Description("GN")] Guinea,
[Description("GW")] Guinea_Bissau,
[Description("GY")] Guyana,
[Description("HT")] Haiti,
[Description("HM")] Heard_Island_And_McDonald_Islands,
[Description("VA")] Holy_See,
[Description("HN")] Honduras,
[Description("HK")] Hong_Kong,
[Description("HU")] Hungary,
[Description("IS")] Iceland,
[Description("IN")] India,
[Description("ID")] Indonesia,
[Description("IR")] Iran,
[Description("IQ")] Iraq,
[Description("IE")] Ireland,
[Description("IM")] Isle_of_Man,
[Description("IL")] Israel,
[Description("IT")] Italy,
[Description("JM")] Jamaica,
[Description("JP")] Japan,
[Description("JE")] Jersey,
[Description("JO")] Jordan,
[Description("KZ")] Kazakhstan,
[Description("KE")] Kenya,
[Description("KI")] Kiribati,
[Description("KP")] Korea_Democratic_Peoples_Republic,
[Description("KR")] Korea_Republic,
[Description("KW")] Kuwait,
[Description("KG")] Kyrgyzstan,
[Description("LA")] Lao,
[Description("LV")] Latvia,
[Description("LB")] Lebanon,
[Description("LS")] Lesotho,
[Description("LR")] Liberia,
[Description("LY")] Libya,
[Description("LI")] Liechtenstein,
[Description("LT")] Lithuania,
[Description("LU")] Luxembourg,
[Description("MO")] Macao,
[Description("MK")] Macedonia,
[Description("MG")] Madagascar,
[Description("MW")] Malawi,
[Description("MY")] Malaysia,
[Description("MV")] Maldives,
[Description("ML")] Mali,
[Description("MT")] Malta,
[Description("MH")] Marshall_Islands,
[Description("MQ")] Martinique,
[Description("MR")] Mauritania,
[Description("MU")] Mauritius,
[Description("YT")] Mayotte,
[Description("MX")] Mexico,
[Description("FM")] Micronesia,
[Description("MD")] Moldova,
[Description("MC")] Monaco,
[Description("MN")] Mongolia,
[Description("ME")] Montenegro,
[Description("MS")] Montserrat,
[Description("MA")] Morocco,
[Description("MZ")] Mozambique,
[Description("MM")] Myanmar,
[Description("NA")] Namibia,
[Description("NR")] Nauru,
[Description("NP")] Nepal,
[Description("NL")] Netherlands,
[Description("NC")] New_Caledonia,
[Description("NZ")] New_Zealand,
[Description("NI")] Nicaragua,
[Description("NE")] Niger,
[Description("NG")] Nigeria,
[Description("NU")] Niue,
[Description("NF")] Norfolk_Island,
[Description("MP")] Northern_Mariana_Islands,
[Description("NO")] Norway,
[Description("OM")] Oman,
[Description("PK")] Pakistan,
[Description("PW")] Palau,
[Description("PS")] Palestine,
[Description("PA")] Panama,
[Description("PG")] Papua_New_Guinea,
[Description("PY")] Paraguay,
[Description("PE")] Peru,
[Description("PH")] Philippines,
[Description("PN")] Pitcairn,
[Description("PL")] Poland,
[Description("PT")] Portugal,
[Description("PR")] Puerto_Rico,
[Description("QA")] Qatar,
[Description("RE")] Réunion,
[Description("RO")] Romania,
[Description("RU")] Russian_Federation,
[Description("RW")] Rwanda,
[Description("BL")] Saint_Barthélemy,
[Description("SH")] Saint_Helena,
[Description("KN")] Saint_Kitts_And_Nevis,
[Description("LC")] Saint_Lucia,
[Description("MF")] Saint_Martin_French_part,
[Description("PM")] Saint_Pierre_And_Miquelon,
[Description("VC")] Saint_Vincent_And_The_Grenadines,
[Description("WS")] Samoa,
[Description("SM")] San_Marino,
[Description("ST")] Sao_Tome_And_Principe,
[Description("SA")] Saudi_Arabia,
[Description("SN")] Senegal,
[Description("RS")] Serbia,
[Description("SC")] Seychelles,
[Description("SL")] Sierra_Leone,
[Description("SG")] Singapore,
[Description("SX")] Sint_Maarten_Dutch_part,
[Description("SK")] Slovakia,
[Description("SI")] Slovenia,
[Description("SB")] Solomon_Islands,
[Description("SO")] Somalia,
[Description("ZA")] South_Africa,
[Description("GS")] South_Georgia_And_The_South_Sandwich_Islands,
[Description("SS")] South_Sudan,
[Description("ES")] Spain,
[Description("LK")] Sri_Lanka,
[Description("SD")] Sudan,
[Description("SR")] Suriname,
[Description("SJ")] Svalbard_And_Jan_Mayen,
[Description("SZ")] Swaziland,
[Description("SE")] Sweden,
[Description("CH")] Switzerland,
[Description("SY")] Syrian_Arab_Republic,
[Description("TW")] Taiwan,
[Description("TJ")] Tajikistan,
[Description("TZ")] Tanzania,
[Description("TH")] Thailand,
[Description("TL")] Timor_Leste,
[Description("TG")] Togo,
[Description("TK")] Tokelau,
[Description("TO")] Tonga,
[Description("TT")] Trinidad_And_Tobago,
[Description("TN")] Tunisia,
[Description("TR")] Turkey,
[Description("TM")] Turkmenistan,
[Description("TC")] Turks_And_Caicos_Islands,
[Description("TV")] Tuvalu,
[Description("UG")] Uganda,
[Description("UA")] Ukraine,
[Description("AE")] United_Arab_Emirates,
[Description("GB")] United_Kingdom,
[Description("UM")] United_States_Minor_Outlying_Islands,
[Description("US")] United_States_of_America,
[Description("UY")] Uruguay,
[Description("UZ")] Uzbekistan,
[Description("VU")] Vanuatu,
[Description("VE")] Venezuela,
[Description("VN")] Viet_Nam,
[Description("VG")] Virgin_Islands_British,
[Description("VI")] Virgin_Islands_US,
[Description("WF")] Wallis_And_Futuna,
[Description("EH")] Western_Sahara,
[Description("YE")] Yemen,
[Description("ZM")] Zambia,
[Description("ZW")] Zimbabwe
}
and this:
public static class EnumUtil
{
public static T StringToEnum<T>(string name)
{
return (T)Enum.Parse(typeof(T), name);
}
public static string ToEnumString(this Enum value)
{
var fi = value.GetType().GetField(value.ToString());
var attributes = (DescriptionAttribute[]) fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
return attributes.Length > 0 ? attributes[0].Description : value.ToString();
}
}
and apologies about posting code on here, it's just that i'm not familiar with forks and github and all that fun stuff. Not my thing
Thanks for the writeup. Please consider opening a PR if you fixed a bug.
Here is a tutorial on how to open the PR if you are not familiar.
cool, thanks for the link :)
when specifying the usermapview bounds, here is the exception that is thrown: