Ruler for Google Maps v3 to measure distance on map

I’ve made a ruler to measure distances on a Google Map V3. The file Ruler.js contains a two function: one to calculate the distance between two points on the map with their position expressed in decimal degrees, and one function that add the ruler. Ther “ruler” is composed with two markers, a poly and two labels which show the distance. The labels are placed on the map with the Labels.js class from Marc Ridley, downloaded from his blog.

Here is the link to the demo and here is the link for download it.

ruler

Here is the code for ruler.js addruler function:

function addruler() {

	ruler1 = new google.maps.Marker({
		position: map.getCenter() ,
		map: map,
		draggable: true
	});

	ruler2 = new google.maps.Marker({
		position: map.getCenter() ,
		map: map,
		draggable: true
	});
	
	var ruler1label = new Label({ map: map });
	var ruler2label = new Label({ map: map });
	ruler1label.bindTo('position', ruler1, 'position');
	ruler2label.bindTo('position', ruler2, 'position');

	rulerpoly = new google.maps.Polyline({
		path: [ruler1.position, ruler2.position] ,
		strokeColor: "#FFFF00",
		strokeOpacity: .7,
		strokeWeight: 8
	});
	rulerpoly.setMap(map);

	ruler1label.set('text',"0m");
	ruler2label.set('text',"0m");

	google.maps.event.addListener(ruler1, 'drag', function() {
		rulerpoly.setPath([ruler1.getPosition(), ruler2.getPosition()]);
		ruler1label.set('text',distance( ruler1.getPosition().lat(), ruler1.getPosition().lng(), ruler2.getPosition().lat(), ruler2.getPosition().lng()));
		ruler2label.set('text',distance( ruler1.getPosition().lat(), ruler1.getPosition().lng(), ruler2.getPosition().lat(), ruler2.getPosition().lng()));
	});

	google.maps.event.addListener(ruler2, 'drag', function() {
		rulerpoly.setPath([ruler1.getPosition(), ruler2.getPosition()]);
		ruler1label.set('text',distance( ruler1.getPosition().lat(), ruler1.getPosition().lng(), ruler2.getPosition().lat(), ruler2.getPosition().lng()));
		ruler2label.set('text',distance( ruler1.getPosition().lat(), ruler1.getPosition().lng(), ruler2.getPosition().lat(), ruler2.getPosition().lng()));
	});

}

And this is the function to calculate distances:

function distance(lat1,lon1,lat2,lon2) {
	var R = 6371; // km (change this constant to get miles)
	var dLat = (lat2-lat1) * Math.PI / 180;
	var dLon = (lon2-lon1) * Math.PI / 180; 
	var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
		Math.cos(lat1 * Math.PI / 180 ) * Math.cos(lat2 * Math.PI / 180 ) * 
		Math.sin(dLon/2) * Math.sin(dLon/2); 
	var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
	var d = R * c;
	if (d>1) return Math.round(d)+"km";
	else if (d<=1) return Math.round(d*1000)+"m";
	return d;
}

21 comments

  1. bob

    “var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));” can be simplified to “var c = 2 * Math.asin(Math.sqrt(a));”

  2. ABM Adnan

    Thanks a lot man! You saved my life.

    Just one little thing, there is a very little bug in labels.js (I know you didn’t write it)
    On line 44:
    maps.google.event.removeListener(this.listeners_[i]);

    this should be:
    google.maps.event.removeListener(this.listeners_[i]);

    Regards
    Adnan

  3. piero

    Excellent. Good job: my congratulation.
    It would useful also to have a couple of additional features:
    1)replace the couple of labels with a single label placed in the middle of the ruler line, alway on top (I tried using zIndex with no success? any idea ?)
    2)the rule line binded to the objects you’re measuring the distance. Suppose we would like to continously track the distance changes among a couple of moving markers (i.e. vehicles).

    PG Ornolio

  4. phicarre

    Hello,
    If I attribute an icon to the rulers then the behaviour changes !
    How to change the standard icons ?

    Thank you.

  5. pani100

    Really good and handy code.
    If i wanted to change the distance to miles would this be correct?
    function distance(lat1,lon1,lat2,lon2) {
    var R = 3959; // km (change this constant to get miles. 3959 for m, 6371 for km also change 1000 to 1760)
    var dLat = (lat2-lat1) * Math.PI / 180;
    var dLon = (lon2-lon1) * Math.PI / 180;
    var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
    Math.cos(lat1 * Math.PI / 180 ) * Math.cos(lat2 * Math.PI / 180 ) *
    Math.sin(dLon/2) * Math.sin(dLon/2);
    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
    var d = R * c;
    if (d>1) return Math.round(d)+”miles”;
    else if (d<=1) return Math.round(d*1760)+"yards";
    return d;

    Regards
    Pani

  6. Marius

    I changed the script a bit, and now you can remove the ruler. Simply pass a ‘true’ or ‘false’ to the addruler call

    var ruler1;
    var ruler2;
    var ruler1label;
    var ruler2label;
    var rulerpoly;

    function addruler(remove) {

    if (remove) {
    google.maps.event.clearListeners(ruler1, ‘drag’);
    ruler1.setMap(null);
    google.maps.event.clearListeners(ruler2, ‘drag’);
    ruler2.setMap(null);
    ruler1label.setMap(null);
    ruler2label.setMap(null);
    rulerpoly.setMap(null);
    }
    else {
    ruler1 = new google.maps.Marker({
    position: map.getCenter(),
    map: map,
    title: ‘Starting Point’,
    draggable: true
    });

    ruler2 = new google.maps.Marker({
    position: map.getCenter(),
    map: map,
    title: ‘End Point’,
    draggable: true
    });
    ruler1label = new Label({ map: map });
    ruler2label = new Label({ map: map });
    ruler1label.bindTo(‘position’, ruler1, ‘position’);
    ruler2label.bindTo(‘position’, ruler2, ‘position’);

    rulerpoly = new google.maps.Polyline({
    path: [ruler1.position, ruler2.position],
    strokeColor: “#FFFF00″,
    strokeOpacity: .7,
    strokeWeight: 7
    });

    rulerpoly.setMap(map);

    ruler1label.set(‘text’, distance(ruler1.getPosition().lat(), ruler1.getPosition().lng(), ruler2.getPosition().lat(), ruler2.getPosition().lng()));
    ruler2label.set(‘text’, distance(ruler1.getPosition().lat(), ruler1.getPosition().lng(), ruler2.getPosition().lat(), ruler2.getPosition().lng()));

    google.maps.event.addListener(ruler1, ‘drag’, function() {
    rulerpoly.setPath([ruler1.getPosition(), ruler2.getPosition()]);
    ruler1label.set(‘text’, distance(ruler1.getPosition().lat(), ruler1.getPosition().lng(), ruler2.getPosition().lat(), ruler2.getPosition().lng()));
    ruler2label.set(‘text’, distance(ruler1.getPosition().lat(), ruler1.getPosition().lng(), ruler2.getPosition().lat(), ruler2.getPosition().lng()));
    });

    google.maps.event.addListener(ruler2, ‘drag’, function() {
    rulerpoly.setPath([ruler1.getPosition(), ruler2.getPosition()]);
    ruler1label.set(‘text’, distance(ruler1.getPosition().lat(), ruler1.getPosition().lng(), ruler2.getPosition().lat(), ruler2.getPosition().lng()));
    ruler2label.set(‘text’, distance(ruler1.getPosition().lat(), ruler1.getPosition().lng(), ruler2.getPosition().lat(), ruler2.getPosition().lng()));
    });
    }

    }

    function distance(lat1,lon1,lat2,lon2) {
    var R = 6371; // km (change this constant to get miles)
    var dLat = (lat2-lat1) * Math.PI / 180;
    var dLon = (lon2-lon1) * Math.PI / 180;
    var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
    Math.cos(lat1 * Math.PI / 180 ) * Math.cos(lat2 * Math.PI / 180 ) *
    Math.sin(dLon/2) * Math.sin(dLon/2);
    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
    var d = R * c;
    if (d>1) return Math.round(d)+”km”;
    else if (d<=1) return Math.round(d*1000)+"m";
    return d;
    }

  7. Marius

    I am a dumb ass – It still had the old version in cache, so make sure you clear the cache so it gets the new js file, then it works :)

  8. Jason Maggard

    Thank you very much for the code. We have a situation here where we need to do site surveys, and to help our salespeople I have set up a Google map they can add markers to. However, I took your code one step further, and thought I’d share.

    This version of the code stores the rulers in an array and outputs a line for each one where our salesmen can put in the other information we need. At the end of each line, there is a “Delete Ruler” button. It deletes the line and the ruler from the page.


    // Quasi global array of all of the lines we've added.
    var lines = new Array();

    function addruler() {
    var ruler1 = new google.maps.Marker({
    position: map.getCenter() ,
    map: map,
    draggable: true
    });
    var ruler2 = new google.maps.Marker({
    position: map.getCenter() ,
    map: map,
    draggable: true
    });
    var ruler1label = new Label({ map: map });
    var ruler2label = new Label({ map: map });
    ruler1label.bindTo('position', ruler1, 'position');
    ruler2label.bindTo('position', ruler2, 'position');
    var rulerpoly = new google.maps.Polyline({
    path: [ruler1.position, ruler2.position] ,
    strokeColor: "#0098b5",
    strokeOpacity: .7,
    strokeWeight: 7
    });
    rulerpoly.setMap(map);
    ruler1label.set('text',distance( ruler1.getPosition().lat(), ruler1.getPosition().lng(), ruler2.getPosition().lat(), ruler2.getPosition().lng()));
    ruler2label.set('text',distance( ruler1.getPosition().lat(), ruler1.getPosition().lng(), ruler2.getPosition().lat(), ruler2.getPosition().lng()));
    google.maps.event.addListener(ruler1, 'drag', function() {
    rulerpoly.setPath([ruler1.getPosition(), ruler2.getPosition()]);
    ruler1label.set('text',distance( ruler1.getPosition().lat(), ruler1.getPosition().lng(), ruler2.getPosition().lat(), ruler2.getPosition().lng()));
    ruler2label.set('text',distance( ruler1.getPosition().lat(), ruler1.getPosition().lng(), ruler2.getPosition().lat(), ruler2.getPosition().lng()));
    });
    google.maps.event.addListener(ruler2, 'drag', function() {
    rulerpoly.setPath([ruler1.getPosition(), ruler2.getPosition()]);
    ruler1label.set('text',distance( ruler1.getPosition().lat(), ruler1.getPosition().lng(), ruler2.getPosition().lat(), ruler2.getPosition().lng()));
    ruler2label.set('text',distance( ruler1.getPosition().lat(), ruler1.getPosition().lng(), ruler2.getPosition().lat(), ruler2.getPosition().lng()));
    });
    // Add our new ruler to an array for later reference
    lines.push([ruler1, ruler2, ruler1label, ruler2label, rulerpoly]);
    addLine(lines.length - 1);
    }

    function addLine (num) {
    // This function adds a line to our page.
    var div = document.getElementById('latlon');
    var oldHTML = document.getElementById('latlon').innerHTML;
    div.innerHTML = oldHTML + "YesNo Delete Ruler";
    // Set up the event handler for the remove ruler button
    document.getElementById('delruler' + num).onclick = function() {removeLine(num); return false;}
    }

    function removeLine (num) {
    // Removes the line from our HTML page
    var div = document.getElementById('ruler' + num);
    div.parentNode.removeChild(div);
    removeRuler(lines[num]);
    }

    function removeRuler (r) {
    // Now we remove the ruler.
    // I've unpacked the variables for readability.
    var ruler1=r[0]; var ruler2=r[1]; var ruler1label=r[2]; var ruler2label=r[3]; var rulerpoly=r[4];
    google.maps.event.clearListeners(ruler1, 'drag');
    ruler1.setMap(null);
    google.maps.event.clearListeners(ruler2, 'drag');
    ruler2.setMap(null);
    ruler1label.setMap(null);
    ruler2label.setMap(null);
    rulerpoly.setMap(null);
    }

    function distance(lat1,lon1,lat2,lon2) {
    var R = 3959; // Here's the right settings for miles and feet
    var dLat = (lat2-lat1) * Math.PI / 180;
    var dLon = (lon2-lon1) * Math.PI / 180;
    var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
    Math.cos(lat1 * Math.PI / 180 ) * Math.cos(lat2 * Math.PI / 180 ) *
    Math.sin(dLon/2) * Math.sin(dLon/2);
    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
    var d = R * c;
    if (d>1) return Math.round(d)+"mi";
    else if (d<=1) return Math.round(d*5280)+"ft";
    return d;
    }

    Again, thanks for publishing this. You saved me a day or two.

  9. Jason Maggard

    Ooops, this ate my HTML…


    div.innerHTML = oldHTML + "YesNo Delete Ruler";

    Long story short, there was a bunch of HTML in here. Longer story shorter, you don’t need it.

    The trick that makes this work however, is that the button id is

    id='delruler" + num + "'

    So that the event handler attaches the button to the right ruler.


    document.getElementById('delruler' + num).onclick = function() {removeLine(num); return false;}

    Notice that in the getElementById, we name each div delruler1, delruler2, etc…

  10. Bernhard Konrad

    Nice code, thanks for sharing! As noted in the comments above, it would be nice to have the option to delete a ruler. My solution to this was that a double-click on one of the markers deletes the ruler. Simply add


    google.maps.event.addListener(ruler1, 'dblclick', function() {
    ruler1.setMap(null);
    ruler2.setMap(null);
    ruler1label.setMap(null);
    ruler2label.setMap(null);
    rulerpoly.setMap(null);
    });

    google.maps.event.addListener(ruler2, 'dblclick', function() {
    ruler1.setMap(null);
    ruler2.setMap(null);
    ruler1label.setMap(null);
    ruler2label.setMap(null);
    rulerpoly.setMap(null);
    });

    to the end of the addruler function.

  11. LacDeMar

    Hey, this code does not work for me.
    It only gets markers into the map, but does not draw the polyline or lables.
    Any idea why?