HTML5 geolocation API to measure speed and heading of your car
In this article we’ll show you how you can use the W3C geolocation API to measure the speed and the heading of your car while driving. This article further uses SVG to render the speed gauge and heading compass. What we’ll create in this article is the following:
Here you can see two gauges. One will show the heading you’re driving to, and the other shows the speed in kilometers. You can test this out yourself by using the following link: Open this in GPS enable device. Once opened the browser will probably ask you to allow access to your location. If you enable this and start moving, you’ll see the two gauges move appropriately.
Getting all this to work is actually very easy and consists of the following steps:
- Alter the SVG images so we can rotate the needle and add to page.
- Use the geolocation API to determine the current speed and heading
- Update the needle based on the current and previous value
We’ll start with the SVG part.
Alter the SVG images so we can rotate the needle and add to page
For the images I decided to use SVG. SVG has the advantage that it can scale without losing detail, and you can easily manipulate and animate the various parts of a SVG image. Both the SVG images were copied from openclipart.org:
These are vector graphics, both created using illustrator. Before we can rotate the needles in these images we need to make a couple of small changes to the SVG code. With SVG you can apply matrix transformations to each SVG element, with this you can easily rotate, skew, scale or translate a component. Besides the matrix transformation you can also apply the rotation and translation directly using the translate and rotate keywords. In this example I’ve used the translaten and rotate functions directly.
When working with these functions you have to take into account that the rotate function doesn’t rotate around the center of the component, it rotates around point 0,0. So we need to make sure that for our needles the point we want to rotate around is set at 0,0. Without diving into too much details, I removed the two needles from the image, and added them as a seperate group to the svg image. I then made sure the needles we’re drawn relative to the 0,0 point I wanted to rotate around. For the speedometer the needle is now defined as this:
<g transform="translate(171,157) rotate(45)" id="speed">
<rect
y="0"
x="-2.5"
height="100"
width="5"
id="rect5532"
style="fill:url(#linearGradient5547);fill-opacity:1;stroke:none"
/>
</g>
And for the compass the needle is defined like this:
<path
transform="translate(225,231) rotate(135)"
sodipodi:nodetypes="ccccc"
id="compass"
d="M -4.21,88
L -4.21,-88
C -2.77,-72
-1.93,-35
4.21,-20
L -4.21,-21
L -4.21,88 z"
style="fill:#000000;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;
stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
If you know how to read SVG, you can see that these figures are now drawn around their rotation point (the bottom center for the speedomoter and the center for the compass). As you can see we also added a specific id for both these elements. This way we can reference them directly from our javascript later on and update the transform property from a jquery animation. Next we just need to add these to the page. For this I used d3.js, which has all kinds of helper functions for SVG and which you can use to load these elements like this:
function loadGraphics() {
d3.xml("assets/compass.svg", "image/svg+xml", function(xml) {
document.body.appendChild(xml.documentElement);
});
d3.xml("assets/speed.svg", "image/svg+xml", function(xml) {
document.body.appendChild(xml.documentElement);
});
}
And with this we’ve got our visualization components ready.
Use the geolocation API to determine the current speed and heading
The next step is using the geolocation API to access the speed and heading properties. You can get this information from the position object that is provided to you by this API:
interface Position {
readonly attribute Coordinates coords;
readonly attribute DOMTimeStamp timestamp;
};
This object has a Coordinate object that contains the information we’re looking for:
interface Coordinates {
readonly attribute double latitude;
readonly attribute double longitude;
readonly attribute double? altitude;
readonly attribute double accuracy;
readonly attribute double? altitudeAccuracy;
readonly attribute double? heading;
readonly attribute double? speed;
};
A lot of useful attributes, but we’re only interested in these last two. The heading (from 0 to 360) shows the direction we’re moving in, and the speed in meters per second is, as you’ve probably guessed, the speed we’re moving at. There are two different options to get these values. We can poll ourselves for these values (e.g. setInterval) or we can wait wacth our position. In this second case you automatically recieve an update. In this example we use the second approach:
function initGeo() {
navigator.geolocation.watchPosition(
geosuccess,
geofailure,
{
enableHighAccuracy:true,
maximumAge:30000,
timeout:20000
}
);
//moveSpeed(30);
//moveCompassNeedle(56);
}
var count = 0;
function geosuccess(event) {
$("#debugoutput").text("geosuccess: " + count++ + " : " + event.coords.heading + ":" + event.coords.speed);
var heading = event.coords.heading;
var speed = event.coords.speed;
if (heading != null && speed !=null && speed > 0) {
moveCompassNeedle(heading);
}
if (speed != null) {
// update the speed
moveSpeed(speed);
}
}
With this piece of code, we register a callback function on the watchPosition. We also add a couple of properties to the watchPosition function. With these properties we tell the API to use GPS (enableHighAccuracy) and set some timeout and caching values. Whenever we receive an update from the API the geosuccess function is called. This function recieves a position object (shown earlier) that we use to access the speed and the heading. Based on the value of the heading and the speed we update the compass and the speedomoter.
Update the needle based on the current and previous value
To update the needles we use jquery animations for the easing. Normally you use a jquery animation to animatie css properties of an object, but you can also use this to animate arbitrary properties. To animate the speedomoter we use the following:
var currentSpeed = {property: 0};
function moveSpeed(speed) {
// we use a svg transform to move to correct orientation and location
var translateValue = "translate(171,157)";
// to is in the range of 45 to 315, which is 0 to 260 km
var to = {property: Math.round((speed*3.6/250) *270) + 45};
// stop the current animation and run to the new one
$(currentSpeed).stop().animate(to, {
duration: 2000,
step: function() {
$("#speed").attr("transform", translateValue
+ " rotate(" + this.property + ")")
}
});
}
We create a custom object, currentSpeed, with a single property. This property is set to the rotate ratio that reflects the current speed. Next, this property is used in a jquery animation. Note that we stop any existing animations, should we get an update when the current animation is still running. In the step property of the animation we set the transfrom value of the SVG element. This will rotate the needle, in two seconds, from the old value to the new value.
And to animate the compass we do pretty much the same thing:
var currentCompassPosition = {property: 0};
function moveCompassNeedle(heading) {
// we use a svg transform to move to correct orientation and location
var translateValue = "translate(225,231)";
var to = {property: heading};
// stop the current animation and run to the new one
$(currentCompassPosition).stop().animate(to, {
duration: 2000,
step: function() {
$("#compass").attr("transform", translateValue
+ " rotate(" + this.property + ")")
}
});
}
There is a smal bug I ran into with this setup. Sometimes my phone lost its GPS signal (running firefox mobile), and that stopped the dials moving. Refreshing the webpage was enough to get things started again however. I might change this to actively pull the information using the getCurrentLocation API call, to see whether that works better.
Another issue is that there is no way, at least that I found, for you to disable the phone entering sleep mode from the browser. So unless you configure your phone to not go to sleep, the screen will go black.