Geolocation

Google offers a very unique and underappreciated API called the Geolocation API. This API allows the end user to scan for Wifi and cellular access points to determine the geographical cordinates of the user. Unlike GPS, this form of geolocation is far more accurate and only requires two WiFi access points to determine an approximate location.

What I find most intresting, is that for this API to work, Google must have a huge database of the calculated location of various access points. Whether this data was collected with the devices they sell to users (Android, Google Home, Nest, ect ) or with the Google maps cars I may never know. Regardless, a very unique API in my opinion.

The bash script below uses a few tools such as iw, sed, cURL and jq. These tools are used to interface with the computer's WiFi device to scan for access points and format the data respectivly. JQ formats this data and saves it to a JSON file. Then cURL will make a request to the Geolocation API to determine the computer's geographical cordinates.

 #!/usr/bin/env sh
# author: Tegan Burns
# website: teganburns.com

# Check README.md for details on how to get your API key
api_key="YOUR_API_KEY_HERE"
url="https://www.googleapis.com/geolocation/v1/geolocate?key=$api_key"
json_file="result.json"
jqobj=""

# Remove old json_file
if [[ -e $json_file ]]; then
    rm $json_file
fi

#TODO: This may run into issues with mutiple WiFi devices
device=$( iw dev | sed '/Interface.*$/!d' | sed -e 's/^.*Interface //' )
scan=$( iw dev $device scan | sed -e 's/[ \t]*//' -e 's/[ \t]*$//' )

#BSS: xx:xx:xx:xx:xx:xx(dev whatever)
#signal: -77.00 dBm
#last seen: 1236 ms ago
#DS Parameter set: channel 36

# Filter out queries with sed
macAddr=($(echo "$scan" | sed -r -n 's/BSS ((([0-9a-fA-F]{2}):){5}([0-9a-fA-F]){2}).*$/\1/p' ))
signal=($(echo "$scan" | sed -r -n 's/signal: (-?[0-9]+)\.[0-9]* dBm/\1/p' ))
age=($(echo "$scan" | sed -r -n 's/last seen: ([0-9]*).*$/\1/p' ))
channel=($( echo "$scan" | sed -r -n 's/DS Parameter set: channel ([0-9]+)*.$/\1/p' | sed -r -e 's/^$/NULL/p'))


# Put all entries in JSON file
for i in ${!macAddr[@]}; do

    jqobj+="\"macAddress\": \"${macAddr[$i]}\", "
    jqobj+="\"signalStrength\": ${signal[$i]}, "

    # Ignore NULL key
    if [[ ${channel[$i]} == "NULL" ]]; then
        jqobj+="\"age\": ${age[$i]}"
    else
        jqobj+="\"age\": ${age[$i]}, "
        jqobj+="\"channel\": ${channel[$i]}"
    fi

    # Create new file if we need to
    if [[ ! -e $json_file ]]; then
        echo "{ \"considerIp\": \"false\", \"wifiAccessPoints\": [" > $json_file
    fi

    # If Last Obj for JSON file
    if [[ $i == $(( ${#macAddr[@]}  -1 )) ]]; then
        echo "{$jqobj}]}" >> $json_file
    else
        echo "{$jqobj}," >> $json_file
    fi

    jqobj=""
    continue;

done

# Make Request
result=$(curl -s -d @$json_file -H "Content-Type: application/json" $url)
lat=$(echo "$result" | jq ' .location.lat')
lng=$(echo "$result" | jq ' .location.lng')
acc=$( echo "$result" | jq '.accuracy' )


# Print result or handle error
if [[ $lat == "null" || $lng == "null" ]]; then
	echo "Error: $result"
else
	echo "Estimated Accuracy: $acc meters"
	echo "https://www.google.com/maps/search/?api=1&query=$lat,$lng"
fi

exit


 
Geolocation - on Github