[Coding] GPS Logs to Google Map (Proof of Concept)

Posted by Khatharsis on June 10, 2013

A GPS Visualizer already exists, but I wanted to code my own as an exercise. I didn’t realize the challenges involved would include figuring out a lot of the navigation nuances, but I found it all interesting nonetheless. For example, my camera (a Canon SX260HS) logs GPS info in NMEA format and I would need to do a little bit of fiddling to get the NMEA coordinate values into Google Maps coordinate values. Since I gathered a good handful of resources to reach a proof of concept/early beta state, I decided to document my process so far. (The result can be found here).

The first step is understanding the log files that are created when the GPS tracking and logging is turned on. A sample looks like the following:

$GPGGA,044934.000,2053.1628,N,15641.1191,W,1,04,1.6,43.0,M,,M,,*6C
$GPRMC,044934.000,A,2053.1628,N,15641.1191,W,,,140513,,A*5A
$GPGGA,045104.000,2053.1623,N,15641.1191,W,1,05,1.4,40.0,M,,M,,*6D
$GPRMC,045104.000,A,2053.1623,N,15641.1191,W,,,140513,,A*5B

These four lines are actually just two logging points in two different sentence types, GPGGA and GPRMC. I haven’t yet figured out which is better to use so my current code is based off of GPGGA. This page provides an “API” on how to interpret the sentences, which are comma delimited.

That done, I could write a simple parser to extract the information I want. The Wikipedia page on NMEA has a section on the checksum value and how to obtain it. For integrity, I included the checksum process and will discard sentences which do not match the given checksum (the last argument of the sentence). However, I kept coming up with the wrong checksum values and after a quick search, found that with PHP, I needed to use the ord() method to obtain the ASCII value. That fixed my problem.

So, I now had a parser that would extract the relevant bits of information with a checksum for validity to boot. My next problem was finding the NMEA coordinate format was not a 1-to-1 mapping with Google Maps coordinate format. Simply shifting the decimal two spaces to the left was not a viable solution, either. There was a little bit of math trickery involved.

Then, finally, everything came together and I had a nice map with routes laid out from a trip to Hawaii.

Building on this route visualization, when I tried to integrate markers for the pictures I took containing GPS data (as opposed to the tracking log), I found that format was slightly different from the NMEA sentences and required a little bit of tweaking. I also want to add these pictures to the map markers. I’ve been hesitant to use AJAX, but I believe my next step will involve an overhaul of the existing code to output JSON instead of raw JavaScript statements.

The code is quite messy, even on the front-end. The back-end was fun to code because it was new and there were all these nuances when dealing with navigational data. Writing a proof of concept results in a great feeling when it finally works, especially when it’s sharable, but also quickly shows why the initial approach is not going to be the final solution. Not if I want elegant and nice code. So, at least I have the core functionality done, I just have to tweak it and put the bells and whistles on so it gives a better front-end experience and displays all of the data I want it to show.

Code block (disclaimer: this is a proof of concept and I am aware of how badly it is written; see additional notes below):

function nmeaParser($fileIndex) {
    // Variables
    $r_file = null;
    $log_file_arr = array('data/hawaii_old/1305130.LOG',
    'data/hawaii_old/1305140.LOG', 'data/hawaii_old/1305150.LOG',
    'data/hawaii_old/1305160.LOG', 'data/hawaii_old/1305170.LOG');
    $fileIndex = (is_null($fileIndex)) ? 0 : $fileIndex;
    $coords_arr = array();
    $coords_js_arr = array();

    // File handler
    if ($fh = fopen($log_file_arr[$fileIndex], 'r')) {
        $r_file = file($log_file_arr[$fileIndex]);
    }
    else {
        return;
    }

    // File parser
    if (isset($r_file)) {
        foreach ($r_file as $line) {
            // TODO : determine if GPGGA or GPRMC is more reliable
            if (substr($line, 0, 6) == '$GPGGA') {
                // Obtain checksum value
                $checksum = 0;
                for ($i = 0; $i < strlen($line); $i++) {
                    // Want values between $ and *
                    if ($line[$i] == '$') {
                        // Do nothing
                    }
                    else if ($line[$i] == '*') {
                        // End iteration
                        break;
                    }
                    else {
                        // ord: Want the ASCII numeric value of the char
                        $checksum ^= ord($line[$i]);
                    }
                }
            
                // Only continue if checksum matches posted checksum and
                // fix quality is not 0
                $line_arr = explode(',', $line);
                $str_checksum = $line_arr[count($line_arr)-1];
                $posted_checksum = substr($str_checksum, 1, 2);
                $checksum = sprintf('%2X', $checksum);
                if ($checksum == $posted_checksum && $line_arr[6] != 0) {
                    $coords = new Coordinates($line_arr[2], $line_arr[3],
                    $line_arr[4], $line_arr[5]);
                    $coords_arr[$line_arr[1]] = $coords;
                }
            }
        }
    }

    // Put the coordinates into a JS string for Google Maps API
    if (count($coords_arr) > 0) {
        foreach ($coords_arr as $coord) {
            $coords_js_arr[] = sprintf("new google.maps.LatLng(%F, %F)", $coord->get_latitude(),
            $coord->get_longitude());
        }
    
        if (count($coords_js_arr) > 0) {
            echo implode(",\n", $coords_js_arr);
        }
    }
}


Additional notes:
-I wrote a Coordinate class to store in the array. I mainly wrote it to practice with PHP classes.
-My rewrite of the code is much cleaner and no longer uses the Coordinate class.
-The rewrite, rather than output raw JavaScript lines, outputs JSON which gets parsed client-side.
-I plan on writing a follow up post detailing my changes.


Listing of resources used for this project thus far:
NMEA Wikipedia entry
NMEA sentence information (“API”)
Incentive for using the checksum
NMEA checksum algorithm (for C)
NMEA checksum algorithm (for PHP)
NMEA coordinate conversion for Google Maps
A more practical NMEA coordinate conversation for Google Maps (I found the formula in this answer to work)