Hands on

Searching Location with the Command Line, WLAN Information, and the HERE Positioning API

By Nic Raboy | 20 November 2018

If you’ve been keeping up with my tutorials, you’ll probably remember that I recently demonstrated how to find the users location using a web browser and the HTML5 Geolocation API. Have you ever wondered how it was possible to do such a thing from only a web browser?

Jayson DeLancey had written a tutorial titled, Know Your Location — Voiding My Firefox Warranty that dug into what the web browser was doing behind the scenes. In summary, the HTML5 Geolocation API, at least for Firefox, gathers information from nearby WLAN broadcasts and sends it to the Google Geolocation API to be converted to latitude and longitude coordinates.

Did you know that HERE can accomplish the same with the HERE Positioning API?

We’re going to see how to collect WLAN data from outside of a web browser and send it to the HERE Positioning API to be converted into a position estimate. This is all going to be done from the command line.

Before going forward, it is important to note that some of these commands will be specific to macOS. The idea can be easily translated to Windows or Linux.

Using the HERE Positioning API

The idea behind the HERE Positioning API is that if you give it information around WLAN, cellular, or Bluetooth, it will return an estimate of your latitude and longitude coordinates. You can mix and match this input data or use strictly one or the other. Each will give you varying results in terms of accuracy, but to me it is pure magic in how it works.

So let’s assume that the only information we have is WLAN information. Per the documentation, we can create a request like the following:

curl -X POST \
    'https://pos.api.here.com/positioning/v1/locate?app_id=APP-ID-HERE&app_code=APP-CODE-HERE' \
    -H 'Content-Type: application/json' \
    -d '{
        "wlan": [
            {"mac": "88-e9-fe-79-be-20"},
            {"mac": "8C-1A-BF-20-66-AD"},
            {"mac": "A0-E4-53-E9-66-A7"},
            {"mac": "AC-4B-C8-34-F7-01"},
            {"mac": "A0-21-95-57-79-06"},
            {"mac": "00-18-56-51-54-FB"},
            {"mac": "10-30-47-D2-54-55"},
            {"mac": "B8-6B-23-09-87-B1"},
            {"mac": "F4-55-95-11-2C-C1"}
        ]
    }
'

In the above example, make sure you swap the app_id and app_code with your own found in your HERE Developer Dashboard. However, notice that we’ve provided the hardware address (MAC) for numerous WLAN access points. To be clear, these are access points or routers, not the hardware addresses to the network interfaces found in your devices.

When sent to the HERE Positioning API, an estimate of your location will be returned based on the registered location of each of these access points. A more accurate estimate can be determined if the power level is provided with each hardware address. The power level helps determine how far you are from each access point.

Upon success, the following might be returned:

{
    "location": {
        "lat": 52.5185857,
        "lng": 13.37622989,
        "accuracy": 100
    }
}

This is great, but we’ve just used data that was provided to us. We want to find our actual location and we want to do it without using a web browser. So what do we do?

Finding WLAN Information from Nearby Access Points

As a user of pretty much any modern computer, we can view all the networks that are in range to us, even if we can’t connect to them due to password reasons. This means that we can gather meta information about how those networks are exposed.

This is where that macOS specific logic comes into play. Take the following command:

/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -s

The above command will scan for all nearby WLAN access points, hence the -s flag. If ran successfully, you should end up with results similar to the following:

SSID        BSSID               RSSI    CHANNEL HT CC SECURITY (auth/unicast/group)
EXAMPLE1    XX:XX:XX:XX:XX:XX   -83     11      Y  US WPA2(PSK/AES/AES)
EXAMPLE2    YY:YY:YY:YY:YY:YY   -84     64      Y  US WPA2(PSK/AES/AES)
EXAMPLE3    ZZ:ZZ:ZZ:ZZ:ZZ:ZZ   -80     11      Y  US NONE
EXAMPLE4    AA:AA:AA:AA:AA:AA   -79     11      Y  US WPA(PSK/AES,TKIP/TKIP) WPA2(PSK/AES,TKIP/TKIP)

As you can tell the SSID and BSSID information above was fabricated. I’m not going to share where I live, even if it is just a really good estimate. In reality, when I ran the scan, I ended up with about 20 results. Yours may vary depending on where you live.

So the above output is great. What’s great as well is that there are similar commands for Linux and Windows. However, we know what the HERE Positioning API expects and the above output isn’t quite that.

Parsing Access Point Data for Hardware Addresses and Power Levels

Since we’re restricting this example to the command line, we should probably pull out the books and start looking at parsing our data with some shell commands. The only information we care about is the BSSID and the RSSI value, where RSSI is the power value.

There are many ways to parse data with the command line, but we’re going to make use of awk and sed for the job.

Take a look at the following awk command:

awk -F ' ' 'match($2, /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/) {printf "{ \"mac\": \"%s\", \"powrx\": %s },", $2, $3}'

The above command would potentially take the scan output and parse it by the space characters. This will give us some nice column data to work with. With column data, we can do a regular expression on the second column, which is the BSSID values. We want to make sure the BSSID is a proper hardware address separated by colon or hyphen characters. If our second column matches this criteria, we want to format the line as a JSON object.

To be fair, I snagged the regular expression from Stack Overflow. I want to make sure I give credit where credit is due because I am terrible at regular expressions.

So we’ve potentially got a result now that looks like this:

{"mac": "XX:XX:XX:XX:XX:XX", "powrx": -83},
{"mac": "YY:YY:YY:YY:YY:YY", "powrx": -84},
{"mac": "ZZ:ZZ:ZZ:ZZ:ZZ:ZZ", "powrx": -80},
{"mac": "AA:AA:AA:AA:AA:AA", "powrx": -79},

This is great, but we have a problem. The last object has a training comma character which will give us some bad POST bodies in the future. We need to strip out the training comma to be on good terms.

To strip out the trailing comma we can use a variety of tricks, but the following should work:

sed '$s/,$//'

The sed command will strip out the last comma and return the rest. Again, to be fair, I ripped the above sed command from another Stack Overflow answer.

So as of now we probably have something that looked like the previous output, but without the trailing comma. This is great, but we’re still not where we need to be in terms of a request.

With the output, we can use it in the following awk command:

awk '{printf "{ \"wlan\": [ %s ] }", $0}'

The above command will take our output, which is only one column, and wrap it in the other necessary parts of the request. This means that as of now, provided you ran each of the commands in order with the data, we have a body that can be sent to the HERE Positioning API.

Search Your Location in a Single Command Line Request

We have a bunch of script commands now. To finish things, we want to tie everything together and make a request all with a single command. To do this, we can execute the following from the command line:

/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -s | awk -F ' ' 'match($2, /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/) {printf "{ \"mac\": \"%s\", \"powrx\": %s },", $2, $3}' | sed '$s/,$//' | awk '{printf "{ \"wlan\": [ %s ] }", $0}' | curl --silent -H "content-type: application/json" -X POST -d @- "https://pos.api.here.com/positioning/v1/locate?app_id=APP-ID-HERE&app_code=APP-CODE-HERE"

In the above command, notice that we’ve taken each of our script commands and used a pipe character. The results of each command will be piped into the next, leading us to the actual cURL request:

curl --silent \
    -H "content-type: application/json" \
    -X POST \
    -d @- \
    "https://pos.api.here.com/positioning/v1/locate?app_id=APP-ID-HERE&app_code=APP-CODE-HERE"

Assuming you’re using a Mac and assuming that you’ve replaced the app_id and app_code values, you should get a response with an estimate of your location.

Conclusion

You just saw how to use the HERE Positioning API to calculate an estimate of your location using nothing more than the command line and surrounding WLAN access points.

So how could this be useful? Maybe you are building some kind of Internet of Things (IoT) project where GPS and cellular aren’t options, but you have WiFi and need a location estimate. There are plenty of use cases, all of which won’t have too difficult of time with WLAN positioning.