Use Google-Geocode API to get location string from coordinates

@patrik_gfeller the code version as posted by @peteraquino works just fine. Maybe it would be worth it updating the initial posting. I’d add a few commented log lines to help users while debugging.

rule "iPhone Location Address"
when
  System started or
  Item iPhone_Coordinates changed
then
  //logInfo("rule", "iPhone Location to Address")
  val geocodeURL = "https://maps.googleapis.com/maps/api/geocode/json?latlng=" + iPhone_Coordinates.state.toString + "&language=german&sensor=true"
  val String geocodeJson = sendHttpGetRequest(geocodeURL)
  //logInfo("rule", geocodeJson)
  var String formattedAddress = transform("JSONPATH", "$.results[0].formatted_address", geocodeJson)
  formattedAddress = formattedAddress.replace(", Germany", "")
  //logInfo("rule", formattedAddress)

  iPhone_Location_Address.postUpdate(formattedAddress)
end
1 Like

thanks @ThomDietrich @peteraquino @patrik_gfeller

“thoms” code works fine by adding the API Key

...
logInfo("rule", "iPhone Location to Address")
val _apiKey = "ABCDEFGHI12341234-APIKEY-ABCDEFGHI1234"
val geocodeURL = "https://maps.googleapis.com/maps/api/geocode/json?latlng=" + ...
...

For the more privacy conscious (like me) I have created an alternative the uses OpenStreetMap.
It uses a generic Lambda function, that can be used across different rules.
And there is one generic rule for any item that follows the naming convention.

// Reverse address lookup with OpenStreetmap
// https://wiki.openstreetmap.org/wiki/Nominatim#Reverse_Geocoding
// Function called to lookup address from a Location type item
// It assumes that the item
val Functions$Function1<GenericItem, String> lookupAddress= [ given_location |

    // get lat lon from item
    val PointType location = given_location.state as PointType
    val String lat = location.latitude.toString
    val String lon = location.longitude.toString

    // build request
    val String endpoint = 'https://nominatim.openstreetmap.org/reverse'
    val String lang     = 'nl-NL'
    val String query    = 'lat=' + lat + '&lon=' + lon + '&accept-language=' + lang + '&format=json&addressdetails=1'
    val String request  = endpoint + '?' + query

    // get result
    val String result  = sendHttpGetRequest(request)
    // logInfo("lookupAddress", result)
    val String road    = transform("JSONPATH", "$.address.road", result)
    val String house   = transform("JSONPATH", "$.address.house_number", result)
    val String town    = transform("JSONPATH", "$.address.town", result)
    var String country = transform("JSONPATH", "$.address.country", result)
    val String hide    = 'Nederland' // in result language; hide your home country

    // build 
    val  String address  = road + ' ' + house + ', ' + town
    if (country != hide) {
        address = address + ', ' + country
    }

    return address
]

rule "Lookup"
when
    Item Robert_Phone_Location changed or
    Item Robert_OwnTracks_Location changed
then

    // Get name of String (Address) Item from the name of the Location Item
    val String updateItem = triggeringItem.name.toString.split("_Location").get(0) + '_Address'

    // post the new value to the String Item
    postUpdate(updateItem, lookupAddress.apply(triggeringItem))

end

4 Likes

is the code mentioned here still working in some form, ive tried a couple of variations with no data or errors ?

Which code? The Google Maps API stuff or Openstreetmap? The latter is working just fine with me.

Thanks, I tried the google maps api, as we use locations on our handsets, i will recheck the code

Hi Everyone!

Someone could help me? I used this method to get the Location of my phone. But now it shows only ‘null’. I use it with my iPhone (iCloud Binding).
I get an error, which I think belongs to this rule:
2018-03-28 19:16:07.768 [ERROR] [ore.transform.actions.Transformation] - Error executing the transformation ‘JSONPATH’: the given parameters ‘JSonPath’ and ‘source’ must not be null

Can anyone help?

Thanks

I like your opinion about it. :grin:
And will use OpenStreetMap instead of google :slight_smile:

I experienced the same json issue.
First try to find out, if the response is really „null“ as a string.
This is interpreted as NULL by openhab (no response instead of string „null“).

You will find some help in the thread about my original issue:

Hope this helps

Thanks, I’ll have a look at it!

Hi @rkrisi
Can you post the JSON you have a problem with, please?

Where to place the lamda function?
Is this like a script in /scripts and called by the rule?
if so, How?

Or is this supposed to be placed within a rule?

Are you using the OpenStreetMap varient?

In that case I might have found the root cause.
The problem is, that OSM sometimes changes the format of the response.
Means: house_number is not always included. This can happen if the location is close to a restaurant or another POI.
In this case "address does not contain house_number, but “restaurant”.

Obviously OH cannot handle this (!?)

No, I’m using Google Maps. However it happens very rarely, so I could also think that internet might be out when this happens or the server didn’t respond… I haven’t tried resolving it yet…

At the top of the file where the rule resides. Please have a look at the official docs.

Hi Robert,

thanks for your work!
As Google now wants a payment for every usage of the Maps API i used your version of reverse address lookup with OSM.
I had some issue with the transformation of town because it´s not used in my result.
Instead i had to use city and now it works like a charm.

regards
Michael

1 Like

Hi I am having the same issue that NCO has when the street name, city or town is missing from the OSM data.
The code is from rtvb (above).
The data is sometimes changed to suburb or district so throws all the data into the text line of the address.

I looked at checking the length of the returned string for each of the JSON transform result and it seems to partially work when the length is >20 I can see the logInfo at this point (restaurant is used as an example).

if(restaurant.length > 20) {
logInfo(“lookupAddress”,“no restaurant”)
val String restaurant = ’ ’
}

2019-01-04 16:57:46.520 [INFO ] [smarthome.model.script.lookupAddress] - no restaurant

However the rule throws an error that the variables are not used in the rule

2019-01-04 16:58:53.947 [INFO ] [el.core.internal.ModelRepositoryImpl] - Validation issues found in configuration model ‘where_am_i.rules’, using it anyway:
The value of the local variable restaurant is not used

and then it puts all of the JSON data in the text string.

2019-01-04 16:57:46.526 [vent.ItemStateChangedEvent] - Address changed from correct address to {“place_id”:“xxxx1761”,“licence”:“Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright",“osm_type”:“way”,“osm_id”:“xxxx6483”,“lat”:“xx.4189xxxx9024”,“lon”:“-x.056xx383674659”,“display_name”:"xxxxx Road, Lxxxxrd, Wxxxl, Nxxxxd, England, xx44 xxJ, United Kingdom”,“address”:{“road”:“xxxxx”,“suburb”:“xxx”,“city”:“xxxx”,“state_district”:“xxxd”,“state”:“England”,“postcode”:“xx44 2BJ”,“country”:“United Kingdom”,“country_code”:“gb”},“boundingbox”:[“xx.4xx636”,“xx.41xx08”,“-x.058xx246”,“-xx.05xx029”]}, xxxxx Road, Wxxx

I need a way to change the incorrect data returned by

val String restaurant = transform(“JSONPATH”, “$.address.restaurant”, result)

to something like

val String restaurant = ‘’

then combine it back to return the address string with a blank value for the road name (or any other that is missing from the JSON transform).

val String address = restaurant + ', ’ + road + ', ’ + city
return address

Is there any quick way to check the JSON transform data is correct and ignore it if returns an error?

Here´s what i build based on the work of @rtvb
@NCO this could help you too :slight_smile:

// Reverse address lookup with OpenStreetmap
// https://wiki.openstreetmap.org/wiki/Nominatim#Reverse_Geocoding
// Function called to lookup address from a Location type item
// It assumes that the item
val Functions$Function1<GenericItem, String> lookupAddress= [ iPMichael_Location |

    // get lat lon from item
    val PointType location = iPMichael_Location.state as PointType
    val String lat = location.latitude.toString
    val String lon = location.longitude.toString

    // build request
    val String endpoint = 'https://nominatim.openstreetmap.org/reverse'
    val String lang     = 'de-DE'
    val String query    = 'lat=' + lat + '&lon=' + lon + '&accept-language=' + lang + '&format=json&addressdetails=1'
    val String request  = endpoint + '?' + query

    // get result
    val String result  = sendHttpGetRequest(request)
    // logInfo("lookupAddress", result)
    var String road    = transform("JSONPATH", "$.address.road", result)
    var String house   = transform("JSONPATH", "$.address.house_number", result)
    var String city    = transform("JSONPATH", "$.address.city", result)
    var String town    = transform("JSONPATH", "$.address.town", result)
    var String country = transform("JSONPATH", "$.address.country", result)
    var String hide    = 'Deutschland' // in result language; hide your home country

    // Strings leeren wenn kein Wert gefunden wurde und die Rohdaten enthalten sind
    if(road.startsWith("{"))
    {
        road = ''
    }
    if(house.startsWith("{"))
    {
        house = ''
    }
    if(town.startsWith("{"))
    {
        town = ''
    }
    if(city.startsWith("{"))
    {
        city = ''
    }
    if(country.startsWith("{"))
    {
        country = ''
    }

    // Prüfen ob City leer ist und Town einfüllen
    if(city == '')
    {
        city = town
    }

    // Prüfen ob City weiterhin leer ist und Unbekannt eintragen
    if(city == '')
    {
        city = 'Unbekannt'
    }

    var String address  = road + ' ' + house + ', ' + city

    if(address.startsWith(", "))
    {
        address = '' + city
    }

    if (country != hide) {
        address = address + ', ' + country
    }

    return address
]

I just check every Strings first character for the { and empty that String.
I also added city as town as this value isn´t always filled.
Don´t ask me why but at home it´s city but at work it´s town…
If city is empty fill the result from town into the city string.
If city is still empty change the String to “Unkown”.
If house is empty build the address only with street and city.

So for your example it means:

val String restaurant = transform(“JSONPATH”, “$.address.restaurant”, result)
if(restaurant.startsWith("{"))
{
  logInfo("lookupAddress","No restaurant")
  restaurant = ''
}

if(restaurant == '')
{
  val String address = road + ', ' + city
}
else
{
  val String address = restaurant + ', ' + road + ', ' + city
}

kind regards
Michael

2 Likes

Thanks a lot for sharing, Michael.

I will use you approach to improve my existing code :slight_smile:

However, I found already some “new” location properties which came up:
pedestrian, hamlet (small village obviously) which I handle like this:

// check whether pedestrian is available or not
	if(result.contains("pedestrian")) {
		pedestrian = transform("JSONPATH", "$.address.pedestrian", result)
		address =  address + ", " + pedestrian
	}
	else {
		pedestrian = "-"
	}
// check whether hamlet (small village) is available or not	
	if(result.contains("hamlet")) {
		hamlet = transform("JSONPATH", "$.address.hamlet", result)
		address = address + ", " + hamlet
	}
	else {
		hamlet = "-"
	} 

And I guess there are many more “special phrases” to be found travelling around :wink:

By th way:
In the json response you will find “name” as well, which replaces sometimes street and house_number

1 Like

Yeah there´s a display name but i think it´s not usable.

There are even addresses where the house number is already in the street String…