Openstreetmap Video overlays

So as I mentioned in my last post I’ve been playing with generating map overlays for the cycling videos I’ve been making while out training. I’d run into a rate limiting problem when using Google Maps static map API.

To work round this I thought I’d see what I could do using Openstreetmap. Openstreetmap doesn’t have a static image API so I’m going to try and build something similar using LeafletJS and a way to grab images of the pages generated.

<html>
<head>
	<title>Maps</title>
	<link type="text/css" href="leaflet.css" rel="stylesheet"/>
	<script type="application/javascript" src="leaflet.js"></script>
	<style >
	#map { 
		height: 250px; 
		width: 250px;
	}
	</style>
</head>
<body>
	<div id="map"/>
	<script type="application/javascript">
function getUrlVars()
{
    var vars = [], hash;
    var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
    for(var i = 0; i < hashes.length; i++)
    {
        hash = hashes[i].split('=');
        vars.push(hash[0]);
        vars[hash[0]] = hash[1];
    }
    return vars;
}

var args = getUrlVars();

var line = args["line"].split("|");

var last = line[(line.length - 1)];

var centre = [last.split(',')[0], last.split(',')[1]];

var map = L.map('map',{
	zoomControl: false,
	zoom: 15
});
map.setView(centre, 15);

L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', 
	{
		maxZoom: 20,
	}).addTo(map);

var latlngs = [];

for (var i=0; i<line.length; i++) {
	latlngs.push(L.latLng(line[i].split(',')[0],line[i].split(',')[1]));
}

var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
	</script
</body>
</html>

This generates the map tiles and overlays the route, but it’s as a web page, now I needed a way to convert this into a PNG image. There are two options, html2canvas or PhantomJS. I decided to go with PhantomJS first. The following loads and renders the page and then generates a PNG image.

var page = require('webpage').create();
var system = require('system');


page.onConsoleMessage = function(msg, lineNum, sourceId) {
  //console.log('CONSOLE: ' + msg + ' (from line #' + lineNum + ' in "' + sourceId + '")');
};

page.viewportSize = {
  width: 265,
  height: 250
};

var url = "file:///opt/share/playing/map-overlay/index.html?" + system.args[1];

console.log(url);

page.open(url, function(){
  setTimeout(function() {	
    page.render(system.args[2]);
    phantom.exit();
  },500);
});

The coordinates for the line are passed in on the command line along with the file name to write the file to.

test

phantomjs --local-to-remote-url-access=true --max-disk-cache-size=1024 --disk-cache=true map.js [Path] [file name]

Running PhantomJS with the disk cache enabled should keep the load on the Openstreetmap servers to a minimum but I’m also looking at how easy it is to set up my own tile server.

I can now feed this in to the scripts I spun up last time.

YouTube Preview Image

DIY Video overlay

I got myself a Garmin Virb Elite in the post Christmas sales, the plan was to use it while riding my bike and when snowboarding.

The camera will shoot in full 1080p HD and has a super wide angle lens to grab loads of what’s going on.

I finally managed to get out on my bike at the weekend and remembered to hook the camera up so it was hung under the handle bars. The raw video looks pretty good.

Having got some video I wanted to overlay some of the stats from my Garmin 810 cycling computer like location, speed, elevation, gradient and heart rate. Garmin provide some software called Virb Edit which will do all this, unfortunately it only runs on Windows or OSx. No problem I thought, I’ll just throw it in the same Windows 7 VM I use for Garmin Express, the app I use to upload my workouts. This was going well until I tried to view one of the videos I’d just imported and it complained about not having DirectX 10 support. This is the bit of Windows that handles all the multimedia stuff and for video tends to need access to a GPU to accelerate things. While it is possible to get things like this to work with a VM it is a lot of work and requires the machine your using to have 2 graphics cards1.

Since the standard software wasn’t going to work I thought I’d have a go at trying to build some scripts to do this with Linux. I decided to start with a simple map overlay. I’ve played with Google Map’s static maps API before so I ran up some simple NodeJS code to read a TCX file I generated from the FIT file created by the camera.

var tcx = require('tcx-js');
var http = require('https');
var fs = require('fs');

var parser = new tcx.Parser();
parser.parse_file("test.tcx");

var tail = [];


parser.activity.trackpoints.every(function(element, index, array){
	
	var filename = index + '.png';
	var url = "https://maps.googleapis.com/maps/api/staticmap?center=" + element.lat + "," + element.lng + "&zoom=16&size=300x300";
	if (index != 0) {
		url = url + '&path=color:0x0000ff80|weight:2|' + tail.join('|');
	}
	console.log(url);


	 (function(uri,fn){
	 	http.get(uri, function(res){
	 		//console.log("statusCode: ", res.statusCode);
 //  			//console.log("headers: ", res.headers);
	 		var data = '';
	 		res.setEncoding('binary');
	 		res.on('data', function (chunk){
	 			data += chunk;
	 		});

	 		res.on('end', function(){
	 			fs.writeFile(fn, data, 'binary', function(err) {
	 				if (err) {
	 					console.log(err);
	 				}
	 			});
	 		});
	 	});	
	 })(url, filename);


	tail.push(element.lat + "," + element.lng);
	if (tail.length >= 25) {
		tail.shift();
	}

	if (index != (array.length-1)) {
		var now = new Date(element.time);
		var then = new Date(array[index +1].time);

		var diff = Math.abs((then.getTime() - now.getTime() ) / 1000);
		//console.log('%d', diff);
		for (var i=0; i<diff; i++) {
			fs.appendFile('list.txt',filename + '\n',function(err){});
		}
	}

	return true;
	
});

This worked pretty well until I hit Google’s rate limit so only got the first 100 map titles.

I used the ImageMagick scripts to build a composite image of the map tile on transparent background.

Create an empty 1920×1800 image with a transparent background

$ convert -size 1920x1080 xc:transparent background.png

Overlay the map title in the bottom right hand corner

$ composite -geometry 300x300+1520+700 1.png background.png scaled/1.png

The script also generates a file called list.txt which contains a list of the filenames and how many times to repeat them at 1 frame per second to match up with the orginal video. Using this list with mencoder to generate the video like this.

$ mencoder mf://@list.txt -mf w=1920:h=1080:fps=1:type=png -ovc copy -oac copy -o output.avi

Finally I used OpenShot to generate the composite video.

I’m going to have a look at using OpenStreetmap instead of Google Maps to avoid the rate limiting and also trying to generate some gauges for speed, cadence, altitude and heading.

1 The laptop I’m doing this on does actually have 2 graphics cards, but I’m using the better one to actually run the OS and it’s a royal pain to switch them round.