How to get Coordinates when Address is Known?

B. Clay Shannon picture B. Clay Shannon · Dec 18, 2012 · Viewed 16.9k times · Source

I derived/adapted the following code from Adam Freeman's book "Metro Revealed: Building Windows 8 apps with XAML and C#" to get the Address when the Coordinates are known:

public static async Task<string> GetAddressForCoordinates(double latitude, double longitude)
{
    HttpClient httpClient = new HttpClient {BaseAddress = new Uri("http://nominatim.openstreetmap.org")};
    HttpResponseMessage httpResult = await httpClient.GetAsync(
        String.Format("reverse?format=json&lat={0}&lon={1}", latitude, longitude));
JsonObject jsonObject = JsonObject.Parse(await httpResult.Content.ReadAsStringAsync());

return jsonObject.GetNamedObject("address").GetNamedString("road");

}

How can I get the opposite (the Coordinates if the Address is known)?

UPDATE

I'm adding a bounty to this; what I've got already (shown above) is the reverse geocoding (getting the address for the coordinates); what I need is geocoding (getting the coordinates for the address).

Based on my reverse geocoding code above, I'm guessing it might be something like this:

public static async Task<string> GetCoordinatesForAddress(string address)
{
    HttpClient httpClient = new HttpClient {BaseAddress = new Uri("http://nominatim.openstreetmap.org")};
    HttpResponseMessage httpResult = await httpClient.GetAsync(
        String.Format("format=json&address={0}", address));

    JsonObject jsonObject = JsonObject.Parse(await httpResult.Content.ReadAsStringAsync());

    return jsonObject.GetNamedObject("address").GetNamedString("lat"); // <-- what about "lon"?
}

...but I don't know how to combine the two coordinate (longitude and latitude) values (assuming this is correct, or close to being correct). Can anybody verify this, clean it up, or provide a better example (either using nominatim or otherwise)?

UPDATE 2

To answer Peter Ritchie's question/comment below:

In the original (reverse geocoding code), I've got:

return jsonObject.GetNamedObject("address").GetNamedString("road");

It simply returns the road; so something like "157 Riverside Avenue" I assume.

But for geocoding (needing two values, a longitude and a latitude), I've got this pseudocode:

return jsonObject.GetNamedObject("address").GetNamedString("lat"); // <-- what about "lon"?

So I don't know if I need to change the return value from Task<string> to Task<List and call (verbose pseudocode) [Note: I'm having a hard time escaping the angle brackets for Task with a List of string]:

var latitude jsonObject.GetNamedObject("address").GetNamedString("lat");
var longitude jsonObject.GetNamedObject("address").GetNamedString("lat");
List<string> listCoordinates = new List<string>();
listCoordinates.Add(latitude);
listCoordinates.Add(longitude);
return listCoordinates;

...or like so:

string latitude jsonObject.GetNamedObject("address").GetNamedString("lat");
string longtude jsonObject.GetNamedObject("address").GetNamedString("long");
return string.Format("{0};{1}", latitude, longitude);

...or ???

UPDATE 3

In response to the proffered Json code for geocoding:

Based on the original reverse geocode code, shouldn't the call be more like this:

HttpClient httpClient = new HttpClient { BaseAddress = new Uri("http://nominatim.openstreetmap.org/") };
var httpResult = await httpClient.GetAsync(
    String.Format("search?format=json&addressdetails={0}", address);

...but at any rate: The JArray type is not recognized, though JsonArray is. The JValue type is not recognized, though JsonValue is. The JsonConverter type is not recognized; perhaps part of Json.Net?

The closest I can come to getting the proferred code to compile is:

var result = await httpResult.Content.ReadAsStringAsync();
var r = (JsonArray)JsonConverter.DeserializeObject(result);//<-- JsonConvert[er] not recognized; part of Json.NET?
var latString = ((JsonValue)r[0]["lat"]).ValueType as string;
var longString = ((JsonValue)r[0]["lon"]).ValueType as string;

...but even with this (close but no Bob Seger), JsonConvert as well as JsonConverter are not recognized.

UPDATE 4

After slogging more concertedly through the documentation at http://wiki.openstreetmap.org/wiki/Nominatim#Search, I think my original (reverse geocode) method might be better as:

public static async Task`<string`> GetAddressForCoordinates(double latitude, double longitude)
{
    HttpClient httpClient = new HttpClient {BaseAddress = new Uri("http://nominatim.openstreetmap.org/")};
    HttpResponseMessage httpResult = await httpClient.GetAsync(
        String.Format("reverse?format=json&lat={0}&lon={1}", latitude, longitude));

    JsonObject jsonObject = JsonObject.Parse(await httpResult.Content.ReadAsStringAsync());

    string house = jsonObject.GetNamedObject("addressparts").GetNamedString("house");
    string road = jsonObject.GetNamedObject("addressparts").GetNamedString("road");
    string city = jsonObject.GetNamedObject("addressparts").GetNamedString("city");
    string state = jsonObject.GetNamedObject("addressparts").GetNamedString("state");
    string postcode = jsonObject.GetNamedObject("addressparts").GetNamedString("postcode");
    string country = jsonObject.GetNamedObject("addressparts").GetNamedString("country");
    return string.Format("{0} {1}, {2}, {3} {4} ({5})", house, road, city, state, postcode, country);
}

This would return, for the corresponding coordinate args passed in, something like: "157 Riverside Avenue, Champaign, IL 55555 (USA)"

What I find odd about the documentation is there is no "state" element among the address parts; if that is really true, and not just a documentation oversight, my code above would fail on the call to GetNamedString("state").

I'm still not sure what the right syntax, etc., should be for the opposite (geocode) method, getting coordinates back after passing in an address.

UPDATE 5

Okay, I downloaded Json.NET and got it compiling. I haven't tested yet, but I've marked Peter Ritchie's as THE (50-point) answer.

This is the code I'm using:

public static async Task<string> GetCoordinatesForAddress(string address)
{
    HttpClient httpClient = new HttpClient { BaseAddress = new Uri("http://nominatim.openstreetmap.org/") };
    HttpResponseMessage httpResult = await httpClient.GetAsync(
        String.Format("search?q={0}&format=json&addressdetails=1", Pluggify(address))); // In my Pluggify() method, I replace spaces with + and then lowercase it all

    var result = await httpResult.Content.ReadAsStringAsync();
    var r = (JArray)JsonConvert.DeserializeObject(result);
    var latString = ((JValue)r[0]["lat"]).Value as string;
    var longString = ((JValue)r[0]["lon"]).Value as string;
    return string.Format("{0};{1}", latString, longString);
}

Also: A funny thing happened on the way back to this forum: while installing Json.NET via NuGet, I also saw ".NET's fastest JSON Serializer by ServiceStack" which claims to be 3X faster than Json.NET. FIWW, it has been updated more recently than Json.NET. Thoughts/reaction?

UPDATE 6

I have this code to implement this (app id and code have been changed to protect the semi-innocent (me)):

// If address has not been explicitly entered, try to suss it out:
                    address = textBoxAddress1.Text.Trim();
                    lat = textBoxLatitude1.Text.Trim();
                    lng = textBoxLongitude1.Text.Trim();
                    if (string.IsNullOrWhiteSpace(address))
                    {
                        address = await SOs_Classes.SOs_Utils.GetAddressForCoordinates(lat, lng);
                    }

. . .

        public async static Task<string> GetAddressForCoordinates(string latitude, string longitude)
        {
            string currentgeoLoc = string.Format("{0},{1}", latitude, longitude);
            string queryString = string.Empty;
            string nokiaAppID = "j;dsfj;fasdkdf";
            object nokiaAppCode = "-14-14-1-7-47-178-78-4";
            var hereNetUrl = string.Format(
                "http://demo.places.nlp.nokia.com/places/v1/discover/search?at={0}&q={1}&app_id={2}    
&app_code={3}&accept=application/json",
                    currentgeoLoc, queryString, nokiaAppID, nokiaAppCode);    
            // get data from HERE.net REST API
            var httpClient = new HttpClient();
            var hereNetResponse = await httpClient.GetStringAsync(hereNetUrl);    
            // deseralize JSON from Here.net 
            using (var tr = new StringReader(hereNetResponse))
            using (var jr = new JsonTextReader(tr))
            {
                var rootObjectResponse = new JsonSerializer    
().Deserialize<JsonDOTNetHelperClasses.RootObject>(jr);    
                var firstplace = rootObjectResponse.results.items.First();
                return HtmlUtilities.ConvertToText(firstplace.vicinity);
                // NOTE: There is also a title (such as "Donut Shop", "Fire stations", etc.?) and type (such as "residence" or "business", etc.?)
            }
        }

...but on this line in GetAddressForCoordinates():

        var firstplace = rootObjectResponse.results.items.First();

...I get this err msg: "*System.InvalidOperationException was unhandled by user code HResult=-2146233079 Message=Sequence contains no elements Source=System.Core StackTrace: at System.Linq.Enumerable.First[TSource](IEnumerable`1 source) at SpaceOverlays.SOs_Classes.SOs_Utils.d__12.MoveNext() in c:...*"

The value of hereNetResponse is:

{"results":{"items":[]},"search":{"context":{"location":{"position":[38.804967,-90.113183],"address":
{"postalCode":"62048","city":"Hartford","stateCode":"IL","county":"Madison","countryCode":"USA","country":"
USA","text":"Hartford IL 62048
USA"}},"type":"urn:nlp-types:place","href":"http://demo.places.nlp.nokia.com/places/v1/places/loc-
dmVyc2lvbj0xO3RpdGxlPUhhcnRmb3JkO2xhdD0zOC44MDQ5Njc7bG9uPS05MC4xMTMxODM7Y2l0eT1IY
XJ0Zm9yZDtwb3N0YWxDb2RlPTYyMDQ4O2NvdW50cnk9VVNBO3N0YXRlQ29kZT1JTDtjb3VudHk9TWFka
XNvbjtjYXRlZ29yeUlkPWNpdHktdG93bi12aWxsYWdl;context=Zmxvdy1pZD02YmUzZDM4Yi0wNGVhLTUyM
jgtOWZmNy1kNWNkZGM0ODI5OThfMTM1NzQyMDI1NTg1M18wXzE2MA?
app_id=F6zpNc3TjnkiCLwl_Xmh&app_code=QoAM_5BaVDZvkE2jRvc0mw"}}}

...so it would appear that there is valid info inside there, such as should return "Hartford, IL"

And at any rate, a blank return value shouldn't throw an exception, I would think...

Answer

Peter Ritchie picture Peter Ritchie · Dec 23, 2012

What you're asking about is simply "geocoding". If you want to use Nominatim specifically, they call this "Search". This is, to a certain extent, address validation; but part of the "validation" is including coordinates (bounding box, lat/long, etc.; depending on what is searched for and what the type of result). There's lots of details about the results, too much to simply post here; but this detail can be found here: http://wiki.openstreetmap.org/wiki/Nominatim#Search (including examles).

You'll have to parse the results (XML, JSON, or HTML) to get the fields you're interested it.

Update 1:

As to what to do with the actual values: it depends. If you want to view the coordinates in a form, you can simply put the lat and long strings into individual controls. If you want to put it in a single control, could use string.Format("{0}, {1}", latString, longString). If you want to use the coords with various methods/types for a Windows Store app, you might need to use the Microsoft.Maps.MapControl.Location class. For example:

  Double latNumber;
  Double longNumber;
  if(false == Double.TryParse(latString, out latNumber)) throw new InvalidOperationException();
  if(false == Double.TryParse(longString, out longNumber)) throw new InvalidOperationException();
  var location = new Location(latNumber, longNumber);

The above assumes you've extracted the lat and long from the response and put them in latString, longString respectively.

Some interfaces may require lat/long as separate double values, in which case just use latNumber and longNumber above.

Over and above that, it really depends specifically on the interfaces you want to use. But, the above should give you enough to use most interfaces.

Update 2:

If the question isn't "how to get coordinates" but "how to parse json objects" then I'd recommend using JSon.Net to get at the lat/long strings in the json result. For example:

    var httpClient = new HttpClient();
    var httpResult = await httpClient.GetAsync(
        "http://nominatim.openstreetmap.org/search?q=135+pilkington+avenue,+birmingham&format=json&polygon=1&addressdetails=1");

    var result = await httpResult.Content.ReadAsStringAsync();
    var r = (JArray) JsonConvert.DeserializeObject(result);
    var latString = ((JValue) r[0]["lat"]).Value as string;
    var longString = ((JValue)r[0]["lon"]).Value as string;

...see above w.r.t. what to do with latString and longString