Programster's Blog

Tutorials focusing on Linux, programming, and open source

Mapping A Drive

I wanted to demonstrate how easy it is to record a video as you drive along, and then be able to playback that video with an overlay of your position on a map.. This could enable further functionality, like the ability to report potholes or "code" a road to get a safety rating etc.

The result of my work can be found at routeshoot.programster.org.

Steps

Get Your Hardware Setup

The first thing you need is the ability to record video footage and GPS in an "open" format that we can easily work with. I have yet to find a dashcam that allows this, but luckily the free Routeshoot app does exactly what I need. It's just a shame that my phone is rather cheap with an "okay" camera, limited storage, and a fairly rubbish mounting system.

Record A Drive

Go take a video recording of a drive. I would suggest taking a few short drives trying to do this as you get used to the app. I found that I had thought it was recording when it wasn't more than once.

Pull The Data off the Phone

Once you have successfully recorded a drive, we need to get the data off the phone. I used the Ftp Server android app to turn my phone into an FTP server. I then used Filezilla on my desktop to connect to the phone and fetch the files from /Android/media/com.wilsonpymmay.routeshoot.

You will have an mp4 and kml file for each recording. The kml file that belongs with the mp4 file will have the exact same filename with the .kml extension appended to the end.

Convert the KML File

Now we need to convert the KML file into a more useable JSON format to work with. I created the script below to do this:

$xmlString = file_get_contents('iGV_20170924123608.mp4.kml');
$xmlObj=simplexml_load_string($xmlString);

$placemarks = array();

foreach ($xmlObj as $document)
{
    $counter = 0;

    foreach ($document as $name => $element)
    {
        if ($name == "Placemark")
        {
            $placemark = $element;

            if (isset($element->ExtendedData))
            {
                $extendedData = $element->ExtendedData;

                if (isset($element->ExtendedData->SchemaData->SimpleData))
                {
                    $simpleData = $extendedData->SchemaData->SimpleData;

                    if (isset($simpleData[0]))
                    {
                        $placemarkArray = array(
                            'Lat'      => floatval((string)$simpleData[1]),
                            'Lon'      => floatval((string)$simpleData[2]),
                            'Bearing'  => floatval((string)$simpleData[3]),
                            'Speed'    => floatval((string)$simpleData[4]),
                            'UID'      => intval((string)$simpleData[5]),
                            'Altitude' => floatval((string)$simpleData[6]),
                            'UTC_Date' => (string)$simpleData[7],
                            'UTC_Time' => (string)$simpleData[8],
                            'Distance' => floatval((string)$simpleData[9]),
                            'X'        => floatval((string)$simpleData[10]),
                            'Y'        => floatval((string)$simpleData[11]),
                            'Z'        => floatval((string)$simpleData[12]),
                        );

                        $placemarks[] = $placemarkArray;
                    }
                }
            }
        }
    }
}

$output =  json_encode($placemarks, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
file_put_contents('iGV_20170924123608.mp4.json', $output);

The output of which looks like:

[
    {
        "Lat": 51.34397,
        "Lon": -0.473886,
        "Bearing": 0,
        "Speed": 0,
        "UID": 1,
        "Altitude": 61,
        "UTC_Date": "24/09/2017",
        "UTC_Time": "12:36:09",
        "Distance": 0.278152,
        "X": 10.036493,
        "Y": 0.373495,
        "Z": -0.78051
    },
    {
        "Lat": 51.34397,
        "Lon": -0.473884,
        "Bearing": 0,
        "Speed": 0,
        "UID": 2,
        "Altitude": 61,
        "UTC_Date": "24/09/2017",
        "UTC_Time": "12:36:10",
        "Distance": 0.459539,
        "X": 9.672575,
        "Y": 0.430956,
        "Z": -1.326388
    },

Convert Video

Your phone is probably pretty bad at encoding video in real-time. This means that the video is highly uncompressed and would use a lot of storage and bandwidth if you were to use it directly. The solution for us is to convert the video to VP9 and use that. This format can be played back in a browser, yet is roughly the same compression as the newer h.265/HEVC format. My website is using the video with a 3000kbps bitrate, but 2000 kbps would probably be enough. Using 3000kbps, my 20 minute video went from 1.6GiB to JUST 455 MiB. A 2000kbps version is just 304 MiB.

Get a Google Maps API Key

Now you need to get yourself an API key for google maps.

Deploy Your Website

Install apache/nginx on a server to turn it into a webserver, and put the GPS json file on there along with the encoded video. Then copy and paste the code below to become your index.html file and change it to suit your needs. You just need to update the paths to point to your video file and GPS files and may want to change the CSS styling along with putting your google maps key in where it says MY_GOOGLE_MAPS_KEY.

<!DOCTYPE html>
<html>
    <head>
        <style>
            body {
                margin: 0px;
                padding: 0px;
                background-color: #2C3539;
            }

            .main-box {
                display:block;
                width:1280px;
                height:720px; 
                position: absolute; 
                left: 50%; 
                top: 50%; 
                margin-top: -360px;
                margin-left: -640px;
            }

            #map {
                position: absolute;
                top: 0px;
                left: 0px;
                height: 200px;
                width: 400px;
                border: 1px solid black;
                margin-left: auto;
                margin-right: auto;
                z-index: 1;
            }

            #player {
                border: 1px solid black;
            }
        </style>

        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
        <script async defer src="https://maps.googleapis.com/maps/api/js?key=MY_GOOGLE_MAPS_KEY&callback=initMap"></script>
    </head>

    <body>
        <div class="main-box">
            <div id="map"></div>

            <!-- video of journey -->
            <video id="player" width="1280" height="720" controls>
                <source src="/trips/tesco/full.3000K.webm" type="video/webm">
                Your browser does not support the video tag.
            </video> 

        </div>

        <script>
            var carLocation = {
                "lat": 51.339797,
                "lng":  -0.486179 
            };

            var mapConfig = {
                "zoom": 17,
                "center": carLocation
            };

            var map;
            var markerConfig
            var carMarker;
            var carIcon;
            var g_mapData = [];

            function initMap() 
            {
                map = new google.maps.Map(document.getElementById('map'), mapConfig);

                carIcon = {
                  "path": google.maps.SymbolPath.FORWARD_CLOSED_ARROW,
                  "scale": 5,
                  "rotation": 260.100006,
                  "strokeWeight": 3,
                  "fillColor": "blue",
                  "fillOpacity": 1
                };

                markerConfig = {
                    "position": carLocation,
                    "map": map,
                    "icon": carIcon
                };

                carMarker = new google.maps.Marker(markerConfig);
            }

            //https://www.w3schools.com/tags/av_prop_currenttime.asp -->
            function iteration() 
            {
                // get video position
                var player = document.getElementById("player");
                var relevantData = g_mapData[Math.floor(player.currentTime)];

                var newPosition = {
                    "lat": relevantData.Lat, 
                    "lng": relevantData.Lon
                };

                carIcon.rotation = relevantData.Bearing;
                carMarker.setIcon(carIcon);
                carMarker.setPosition(newPosition);
                map.setCenter(newPosition);
            }

            $(document).ready(function(){
                // get the GPS data for the trip
                $.getJSON("/trips/tesco/gps.json", function(data) {
                    g_mapData = data;
                    setInterval(function(){ iteration(); }, 1000);
                });
            });
        </script>
    </body>
</html>

Conclusion

You have now mapped your own drive. At this point you can easily take the project further by adding functionality like being able to stop the video and press a button to report a pothole, which would send off the relevant GPS coordinates to somebody.