Hands on

Reverse Geocoding Coordinates to Addresses with the Go Programming Language

By Nic Raboy | 15 October 2018

When working with GPS devices and popular software, you will typically find yourself with information around latitude and longitude coordinate information, which is great, but not exactly human readable. A more readable format would be address information with streets, cities, countries, etc., something that we can make sense of. HERE offers a reverse geocoder API that allows you to take latitude and longitude information collected from elsewhere and convert it into address information.

We’re going to see how to use the HERE Reverse Geocoder API with REST and the Go programming language (Golang) to convert coordinate information to address information, quickly and easily.

To get a better idea what we’re trying to accomplish, take a look at the following image:

go-reverse-geocode

In the above image you’ll notice that a CLI was created. When running the application, a latitude and longitude was passed and we got address information as a response. The command is more complex than it needs to be because we’re pretty printing the JSON data with a Python script, which isn’t completely necessary.

Mapping the HERE Geocoder API with Native Go Data Structures

Before we start using the HERE Geocoder API, we need to add our boilerplate Golang code and create the data structures that will hold the results to our requests. Within your $GOPATH, create a main.go file and include the following:

package main

import (
    "encoding/json"
    "flag"
    "fmt"
    "io/ioutil"
    "net/http"
    "net/url"
)

type GeoData struct {
    Response struct {
        MetaInfo struct {
            TimeStamp string `json:"TimeStamp"`
        } `json:"MetaInfo"`
        View []struct {
            Result []struct {
                MatchLevel string `json:"MatchLevel"`
                Location   struct {
                    Address struct {
                        Label       string `json:"Label"`
                        Country     string `json:"Country"`
                        State       string `json:"State"`
                        County      string `json:"County"`
                        City        string `json:"City"`
                        District    string `json:"District"`
                        Street      string `json:"Street"`
                        HouseNumber string `json:"HouseNumber"`
                        PostalCode  string `json:"PostalCode"`
                    } `json:"Address"`
                } `json:"Location"`
            } `json:"Result"`
        } `json:"View"`
    } `json:"Response"`
}

type Position struct {
    Latitude  string `json:"latitude"`
    Longitude string `json:"longitude"`
}

type Geocoder struct {
    AppId   string `json:"app_id"`
    AppCode string `json:"app_code"`
}

func main() {}

In the above code you’ll notice we have our import packages, a main function, and three data structures. The GeoData data structure is probably the most important for this project. Essentially what I’ve done with GeoData is model the REST response defined in the HERE documentation. If you look closely, you’ll notice that I haven’t mapped the response property for property. In fact, I left half of the response out. I did this because I only really care about the address information. However, the format of the response and model must match. You’ll also notice the JSON annotations in the Go data structure. These JSON annotations match the property names in the response.

The Position data structure will allow us to keep track of latitude and longitude coordinates, and the Geocoder data structure will allow us to keep track of our API token information. The AppId and AppCode can both be obtained after creating a free account with HERE.

Now that we have a means to store our data in our application, we can start making requests against the REST API that HERE provides.

Using REST to Convert Between Positions and Addresses

To use the HERE Geocoder API, all we really need to do is create an HTTP request that includes our token information, along with coordinate information to be converted into addresses. The response will be stored as a GeoData object to be used however we wish.

Within the project’s main.go file, include the following function:

func (geocoder *Geocoder) reverse(position Position) (GeoData, error) {
    endpoint, _ := url.Parse("https://reverse.geocoder.api.here.com/6.2/reversegeocode.json")
    queryParams := endpoint.Query()
    queryParams.Set("app_id", geocoder.AppId)
    queryParams.Set("app_code", geocoder.AppCode)
    queryParams.Set("mode", "retrieveAddresses")
    queryParams.Set("prox", position.Latitude+","+position.Longitude)
    endpoint.RawQuery = queryParams.Encode()
    response, err := http.Get(endpoint.String())
    if err != nil {
        return GeoData{}, err
    } else {
        data, _ := ioutil.ReadAll(response.Body)
        var geoData GeoData
        json.Unmarshal(data, &geoData)
        return geoData, nil
    }
}

The function is quite large, but most of it is around formatting a request. The function will use the Geocoder information as well as passed Position information. When creating a request, we provide the correct URL, and construct a query string with the appropriate information. After we execute the request, we check to see if there was an error or success. If there was success, we can read the response, which is byte data, and convert it into GeoData to be returned. The conversions happen based on our JSON annotation mapping.

Building a CLI for Accepting Data Defined by the User

With the REST function in place, we can bring our application together so it can be used like a command line interface (CLI). The goal here is to just take user defined information and pass it into our function.

Open the project’s main.go file and include the following:

func main() {
    latitude := flag.String("lat", "37.7397", "Latitude")
    longitude := flag.String("lng", "-121.4252", "Longitude")
    flag.Parse()
    geocoder := Geocoder{AppId: "APP-ID-HERE", AppCode: "APP-CODE-HERE"}
    result, err := geocoder.reverse(Position{Latitude: *latitude, Longitude: *longitude})
    if err != nil {
        fmt.Printf("The HTTP request failed with error %s\n", err)
        return
    }
    if len(result.Response.View) > 0 && len(result.Response.View[0].Result) > 0 {
        data, _ := json.Marshal(result.Response.View[0].Result[0])
        fmt.Println(string(data))
    }
}

In the main function we are anticipating two flags to be passed when launching the application. If these flags are not passed, the default value will be used. Once we parse the flags, we can set our app id and app code information and then call our REST function. Upon a successful request, if there is data found, we will display only the first result which is the nearest result to our coordinates. There can potentially be other results returned in the request.

Conclusion

You just saw how to use Golang to make requests against the HERE Reverse Geocoder API to convert latitude and longitude coordinate information into address information that is much easier to read.

A next step to this might include taking a CSV file of data and converting it into addresses.