Duncan’s blog

May 20, 2013

Try, Catch… Ignore!

Filed under: Javascript — duncan @ 8:53 am
Tags: , , ,

I was using JSLint today before committing in a javascript file. It was doing some presentational effects we didn’t expect to work in all browsers, so there was a try-catch wrapped around it. And the catch block was empty.

Something a bit like this in fact:

	try {
		...			
	} catch (exception) {
		//browser does not support doing this, so catch error and continue
	}

JSLint gave me this error message:
Expected ‘ignore’ and instead saw ‘exception’.

A very useful answer on StackOverflow explains what this is about. Douglas Crockford has decided that if you have an empty catch block, your variable should be called ‘ignore’, to make it clearer you’re doing this deliberately and haven’t just forgot to do something with the exception.

So this code works:

	try {
		...			
	} catch (ignore) {
		//browser does not support doing this, so catch error and continue
	}

And conversely it only works if you don’t do anything in that block. i.e. this code errors (with “Unexpected ‘ignore’“):

	try {
		...			
	} catch (ignore) {
		alert('there was an error');
	}

Personally I think this is a great idea, it makes the code much more obvious. I’m going to start doing this in other languages too.

March 1, 2013

Google Maps API – draggable polylines

Filed under: Google Maps,Javascript — duncan @ 6:26 pm
Tags: , , , ,

Update: a lot of the javascript in this article was based on old code I wrote a while ago… thinking about it after I initially published the article I decided to rewrite it slightly. So previously I had one array for destinations, and three global arrays for labels, polylines and markers. It’s much simpler in this case just to have one global array which contains destinations, labels, polylines and markers.

So my previous post in this series added event listeners to polylines, this time I want to make the lines draggable.  In this case I want to have one marker I can move around the map, and the polylines connecting it to the other markers will update to stay elastically-attached to the one draggable marker.  And the distances will update automatically.  Again we’re using the Label code courtesy of Marc Ridey (which I’m not going to repeat here, see my earlier blogpost).  And the rest of the code remains quite similar to previously, with some key differences.

	
var arrDestinations;

function initialize() {
	var map, i, j, latLng, stuDistances, inBetween, labelMarker;

	arrDestinations = [
		{title: 'Place A',	lat: 34.602694,	lng: -106.066132},
		{title: 'Place B',	lat: 33.917153,	lng: -106.869736},
		{title: 'Place C',	lat: 34.254946,	lng: -105.599442}
	];

	var stuHome = {title: 'Where I am', lat: 34.148181, lng: -106.17897};

	var homeLatlng = new google.maps.LatLng(stuHome.lat, stuHome.lng);
	var myOptions = {
		zoom: 9,
		center: homeLatlng,
		mapTypeId: google.maps.MapTypeId.SATELLITE
	};
	map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);

	var homeMarker = new google.maps.Marker({
		position: homeLatlng, 
		map: map, 
		title: stuHome.title,
		draggable: true
	});

	$('#tableNeighbours').append(
		  '<tr>'
		+ '<th>Destination</th>'
		+ '<th colspan="2">' + stuHome.title + '</th>'
		+ '</tr>'
	);

	for (i = 0; i < arrDestinations.length; i++) {
		latLng = new google.maps.LatLng(arrDestinations[i].lat, arrDestinations[i].lng);

		arrDestinations[i].marker = new google.maps.Marker({
			position: latLng,
			map: map, 
			title: arrDestinations[i].title,
			icon: 'http://maps.google.co.uk/intl/en_ALL/mapfiles/ms/micons/green-dot.png'
		});

		// draw lines between each marker and home.  these are curved lines, not as the crow flies, i.e. they take into account the curvature of the earth (only noticable on longer distances)
		arrDestinations[i].polyline = new google.maps.Polyline({
			path: [homeLatlng, latLng],
			strokeColor: "#FF0000",
			strokeOpacity: 0.5,
			strokeWeight: 4,
			geodesic: true,
			map: map
		});


		// calculate the distance between home and this marker
		stuDistances = calculateDistances(homeLatlng, latLng);
		$('#tableNeighbours').append(
			  '<tr id="row' + i + '">' 
			+ '<td>' + arrDestinations[i].title + '</td>'
			+ '<td class="km">' + stuDistances.km + ' km</td>'
			+ '<td class="miles">' + stuDistances.miles + ' miles</td>'
			+ '</tr>'
		);

		// get the point half-way between this marker and the home marker
		inBetween = google.maps.geometry.spherical.interpolate(homeLatlng, latLng, 0.5);  

		// create an invisible marker
		labelMarker = new google.maps.Marker({  
			position: inBetween,  
			map: map,
			visible: false
		});

		arrDestinations[i].label = new Label();

		arrDestinations[i].label.bindTo('position', labelMarker, 'position');
		arrDestinations[i].label.set('text', stuDistances.miles + ' miles');
		arrDestinations[i].label.setMap(map);
	}

	// lets make the home marker draggable, but none of the others, just for illustrating how we can make the polylines move dynamically
	google.maps.event.addListener(homeMarker, 'dragend', function() {
		var i, stuDistance, inBetween, marker, markerLatLng;
		var homeLatLng = homeMarker.getPosition();

		// do this for each of the markers 
		for (i = 0; i < arrDestinations.length; i++) {
			markerLatLng = arrDestinations[i].marker.getPosition();

			arrDestinations[i].polyline.setPath([homeLatLng, markerLatLng]);
			stuDistance = calculateDistances(homeLatLng, markerLatLng);
			arrDestinations[i].label.set('text', stuDistance.miles + ' miles');

			// update the position of the marker which the label is bound to
			inBetween = google.maps.geometry.spherical.interpolate(homeLatLng, markerLatLng, 0.5);
			marker = new google.maps.Marker({  
				position: inBetween,  
				map: map,
				visible: false
			});
			arrDestinations[i].label.set('position', inBetween);

			// update values in table
			$('#row' + i + ' td.km').text(stuDistance.km + ' kilometres');
			$('#row' + i + ' td.miles').text(stuDistance.miles + ' miles');
		}
	});
}

function calculateDistances(start,end) {
	var stuDistances = {};

	stuDistances.metres = google.maps.geometry.spherical.computeDistanceBetween(start, end);	// distance in metres rounded to 1dp
	stuDistances.km = Math.round(stuDistances.metres / 1000 *10)/10;				// distance in km rounded to 1dp
	stuDistances.miles = Math.round(stuDistances.metres / 1000 * 0.6214 *10)/10;			// distance in miles rounded to 1dp

	return stuDistances;
}

google.maps.event.addDomListener(window, 'load', initialize);

This time when creating the ‘homeMarker’ I make it draggable using this option: draggable: true.

I also added a class to each of the kilometres and miles cells, so they’re easy to identify later when I want to dynamically update them.

And the markers are appended into an array so we can use them later when dragging the centre marker.

And this time we make the labels appear automatically by calling label.setMap(map); as we create each marker, and I never remove them.

We then have a new event listener for the ‘dragend‘ event on the homeMarker.  I’ve tried using ‘drag‘ instead, but it seemed slightly processor-intensive to use (although it did automatically redraw lines and distances as I dragged  – now I’m just redrawing them when the user releases the marker).

And this is what I see. Dragging the red marker redraws the polylines and updates the distances on the labels:
new mexico

…and the table underneath displays the distances in miles and km, and automatically updates along with the labels:

Destination Where I am
Place A 51.6 km 32.1 miles
Place B 68.7 km 42.7 miles
Place C 54.7 km 34 miles

February 28, 2013

Google Maps API – polylines and events

Update: a lot of the javascript in this article was based on old code I wrote a while ago… thinking about it after I initially published the article I decided to rewrite it slightly. So previously I had one array for destinations, and two global arrays for labels and polylines. It’s much simpler in this case just to have one global array which contains destinations, labels and polylines.

So following on from the previous post, Google Maps API – polylines and distances, let’s try enhancing that code a bit.

Firstly, as well as having a table showing distances, I want to show a label on each polyline with the distance in miles.  For that we can create a custom overlay by prototyping the OverlayView class.

Secondly, let’s have some events associated with the map – when I click on a polyline I want to see that label.  Also we can have an event listener on the table of distances which triggers the same event on the polylines.

Here’s what I see when I click on a polyline, or mouseover the table row that contains its data.

map label

So, here’s the Label class – this code is almost entirely from a blogpost by Marc Ridey with one tiny change on my part.  Custom Overlays need to prototype google.maps.OverlayView().  It then must define onAdd(), onRemove() and draw() functions.  The only thing I changed here from Marc’s original version was to use var pane = this.getPanes().floatPane; instead of var pane = this.getPanes().overlayLayer;  The OverlayLayer meant the labels appeared underneath polylines and markers.  The floatPane is the equivalent of using a higher z-index, so the labels now float above those items.

// Define the overlay, derived from google.maps.OverlayView
function Label(opt_options) {
	// Initialization
	this.setValues(opt_options);
	
	// Label specific
	var span = this.span_ = document.createElement('span');
	span.style.cssText = 'position: relative; left: -50%; top: -8px; ' +
	                     'white-space: nowrap; border: 1px solid blue; ' +
	                     'padding: 2px; background-color: white';
	
	var div = this.div_ = document.createElement('div');
	div.appendChild(span);
	div.style.cssText = 'position: absolute; display: none';
}
Label.prototype = new google.maps.OverlayView();

// Implement onAdd
Label.prototype.onAdd = function() {
	var pane = this.getPanes().floatPane;
	pane.appendChild(this.div_);
	
	// Ensures the label is redrawn if the text or position is changed.
	var me = this;
	this.listeners_ = [
		google.maps.event.addListener(this, 'position_changed',
			function() { me.draw(); }),
		google.maps.event.addListener(this, 'text_changed',
			function() { me.draw(); })
	];
};

// Implement onRemove
Label.prototype.onRemove = function() {
	var i, I;
	this.div_.parentNode.removeChild(this.div_);
	
	// Label is removed from the map, stop updating its position/text.
	for (i = 0, I = this.listeners_.length; i < I; ++i) {
		google.maps.event.removeListener(this.listeners_[i]);
	}
};

// Implement draw
Label.prototype.draw = function() {
	var projection = this.getProjection();
	var position = projection.fromLatLngToDivPixel(this.get('position'));
	
	var div = this.div_;
	div.style.left = position.x + 'px';
	div.style.top = position.y + 'px';
	div.style.display = 'block';
	
	this.span_.innerHTML = this.get('text').toString();
};

And so then here’s the rest of the javascript:

var arrDestinations;

function initialize() {
	var map, i, j, latLng, stuDistances, inBetween, labelMarker;

	arrDestinations = [
		{title: 'Keswick',		lat: 54.60039,		lng: -3.13632},
		{title: 'Coniston',		lat: 54.36897,		lng: -3.07561},
		{title: 'Lake District',	lat: 54.5003526,	lng: -3.0844116},
		{title: 'Cumbria',		lat: 54.57723,		lng: -2.79748}
	];
	
	var stuHome = {title: 'Ambleside', lat: 54.42838, lng: -2.9623};
	
	var homeLatlng = new google.maps.LatLng(stuHome.lat, stuHome.lng);
	var myOptions = {
		zoom: 10,
		center: homeLatlng,
		mapTypeId: google.maps.MapTypeId.ROADMAP
	};
	map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
			
	var homeMarker = new google.maps.Marker({
		position: homeLatlng, 
		map: map, 
		title: stuHome.title
	});
	
	$('#tableNeighbours').append(
		  '<tr>'
		+ '<th>Destination</th>'
		+ '<th colspan="2">' + stuHome.title + '</th>'
		+ '</tr>'
	);
			  
	for (i = 0; i < arrDestinations.length; i++) {
		latLng = new google.maps.LatLng(arrDestinations[i].lat, arrDestinations[i].lng);
		
		arrDestinations[i].marker = new google.maps.Marker({
			position: latLng,
			map: map, 
			title: arrDestinations[i].title,
			icon: 'http://maps.google.co.uk/intl/en_ALL/mapfiles/ms/micons/green-dot.png'
		});
					
		// draw lines between each marker and home.  these are curved lines, not as the crow flies, i.e. they take into account the curvature of the earth (only noticable on longer distances)
		arrDestinations[i].polyline = new google.maps.Polyline({
			path: [homeLatlng, latLng],
			strokeColor: "#FF0000",
			strokeOpacity: 0.5,
			strokeWeight: 4,
			geodesic: true,
			map: map,
			polylineID: i
		});
					
		// calculate the distance between home and this marker
		stuDistances = calculateDistances(homeLatlng, latLng);
		$('#tableNeighbours').append(
			  '<tr id="row' + i + '">' 
			+ '<td>' + arrDestinations[i].title + '</td>'
			+ '<td>' + stuDistances.km + ' km</td>'
			+ '<td>' + stuDistances.miles + ' miles</td>'
			+ '</tr>'
		);
		
		// get the point half-way between this marker and the home marker
		inBetween = google.maps.geometry.spherical.interpolate(homeLatlng, latLng, 0.5);  
		
		// create an invisible marker
		labelMarker = new google.maps.Marker({  
			position: inBetween,  
			map: map,
			visible: false
		});
		
		arrDestinations[i].label = new Label();
		
		arrDestinations[i].label.bindTo('position', labelMarker, 'position');
		arrDestinations[i].label.set('text', stuDistances.miles + ' miles');
		
		// we'll use this ID later:
		arrDestinations[i].label.polylineID = i;
	
		// lets add an event listener, if you click the line, i'll tell you the distance
		google.maps.event.addListener(arrDestinations[i].polyline, 'click', function() {
			// remove other labels
			for (j = 0; j < arrDestinations.length; j++){
				if (this.polylineID != j) {
					if(typeof(arrDestinations[j].label) != "undefined"){
						arrDestinations[j].label.setMap(null);
					}
				} else {
					arrDestinations[j].label.setMap(map);
				}
			}
		});
	}
}

function calculateDistances(start,end) {
	var stuDistances = {};
	
	stuDistances.metres = google.maps.geometry.spherical.computeDistanceBetween(start, end);	// distance in metres rounded to 1dp
	stuDistances.km = Math.round(stuDistances.metres / 1000 *10)/10;							// distance in km rounded to 1dp
	stuDistances.miles = Math.round(stuDistances.metres / 1000 * 0.6214 *10)/10;				// distance in miles rounded to 1dp

	return stuDistances;
}

function removeAllLabels() {
	var i;
	// remove other markers
	for (i = 0; i < arrDestinations.length; i++){
		// don't remove the current label
		if(typeof(arrDestinations[i].label) != "undefined"){
			arrDestinations[i].label.setMap(null);
		}
	}
}

$(document).ready(function() {
	$('table').delegate("td", "mouseover mouseout", function(e) {
		if (e.type == 'mouseover') {
			var id = $(this).parent().attr('id').replace('row', '');
			google.maps.event.trigger(arrDestinations[id].polyline, 'click');
		} else {
			removeAllLabels();
		}
	});
});

google.maps.event.addDomListener(window, 'load', initialize);

Some other changes worth mentioning from the previous article. We’re using google.maps.geometry.spherical.interpolate to calculate a point 50% of the way along the polyline. This could also be used if you wanted to place markers at regular intervals along the line for instance.

We then create an invisible marker at that position, and bind our Label to it.  And we add all the labels into an array.

Then we have an event listener on the polyline for any ‘click’ events.  When someone clicks on a line, I want to hide all the other labels and only show this one.  Alternatively you might want to not have any event listener, and just show all the labels all the time.

Then there’s another event listener for when we mouseover / mouseout any table cells.  Because we’re adding the table rows dynamically via the JS, I’m using the jQuery .delegate() function.  Whenever you mouseout from a  TD, the marker is removed.  If you mouseover, we can simply trigger a click on that polyline, which will then execute the google maps event listener.

February 14, 2013

Google Maps API – polylines and distances

Filed under: Google Maps,Javascript — duncan @ 6:56 pm
Tags: , , , ,

So after a basic introduction to polylines, let’s try doing a bit more with them. Here’s a simple example based on something I was doing for work. Supposing you have a bunch of different locations, and you want to show the distance between them? In this case I had one central destination, and I wanted to show the distance from there to several nearby towns. I did this initially using polylines, as in this example, although in reality it would be more useful to show driving directions (more on that in a future post).

Here’s what I came up with:

ambleside

And I displayed this table beneath the map:

Destination Distance from Ambleside
Keswick 22.2 km 13.8 miles
Coniston 9.9 km 6.1 miles
Lake District 11.3 km 7 miles
Cumbria 19.7 km 12.2 miles

And here’s the Javascript I used to do it. Originally I was getting my coordinates from the database using ColdFusion, and then writing them into the Javascript. I was also doing some infowindows attached to the markers, and labels on the polylines indicating the distances. But for this post I’ve just kept it simple for now.

<!DOCTYPE html>
<html>
<head>
<title>lines between the nearest destinations showing distance</title>

<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<style type="text/css">
html { height: 100% }
body { height: 100%; margin: 0; padding: 0 }
#map_canvas { width:600px; height:500px }
</style>
<!-- need to load the geometry library for calculating distances -->
<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?libraries=geometry&sensor=false"></script>

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js" type="text/javascript"></script>

<script type="text/javascript">
    function initialize() {
        var map, i, latLng, marker, polyline, stuDistances;
    
        var arrDestinations = [
            {title: 'Keswick',        lat: 54.60039,   lng: -3.13632},
            {title: 'Coniston',       lat: 54.36897,   lng: -3.07561},
            {title: 'Lake District',  lat: 54.5003526, lng: -3.0844116},
            {title: 'Cumbria',        lat: 54.57723,   lng: -2.79748}
        ];
        
        var stuHome = {title: 'Ambleside', lat: 54.42838, lng: -2.9623};
        
        var homeLatlng = new google.maps.LatLng(stuHome.lat, stuHome.lng);
        var myOptions = {
            zoom: 10,
            center: homeLatlng,
            mapTypeId: google.maps.MapTypeId.ROADMAP
        };
        map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
                
        var homeMarker = new google.maps.Marker({
            position: homeLatlng, 
            map: map, 
            title: stuHome.title
        });
        
        $('#tableNeighbours').append(
              '<tr>'
            + '<th>Destination</th>'
            + '<th colspan="2">' + stuHome.title + '</th>'
            + '</tr>'
        );
                  
        for (i = 0; i < arrDestinations.length; i++) {
            latLng = new google.maps.LatLng(arrDestinations[i].lat, arrDestinations[i].lng);
            marker = new google.maps.Marker({
                position: latLng,
                map: map, 
                title: arrDestinations[i].title,
                icon: 'http://maps.google.co.uk/intl/en_ALL/mapfiles/ms/micons/green-dot.png'
            });
                        
            // draw lines between each marker and home.  these are curved lines, not as the crow flies, i.e. they take into account the curvature of the earth (only noticable on longer distances)
            polyline = new google.maps.Polyline({
                path: [homeLatlng, latLng],
                strokeColor: "#FF0000",
                strokeOpacity: 0.5,
                strokeWeight: 4,
                geodesic: true,
                map: map
            });
            
            // calculate the distance between home and this marker
            stuDistances = calculateDistances(homeLatlng, latLng);
            $('#tableNeighbours').append(
                  '<tr>' 
                + '<td>' + arrDestinations[i].title + '</td>'
                + '<td>' + stuDistances.km + ' km</td>'
                + '<td>' + stuDistances.miles + ' miles</td>'
                + '</tr>'
            );
        }
    }
    
    function calculateDistances(start,end) {
        var stuDistances = {};
        
        stuDistances.metres = google.maps.geometry.spherical.computeDistanceBetween(start, end);    // distance in metres
        stuDistances.km = Math.round(stuDistances.metres / 1000 *10)/10;                            // distance in km rounded to 1dp
        stuDistances.miles = Math.round(stuDistances.metres / 1000 * 0.6214 *10)/10;                // distance in miles rounded to 1dp
        
        return stuDistances;
    }
    
    google.maps.event.addDomListener(window, 'load', initialize);
</script>

</head>
<body>
    <div id="map_canvas"></div>
    
    <table id="tableNeighbours" border="1"></table>
</body>
</html>

So I’m dynamically adding the distance information from each location to the table as each marker is added. You’ll notice that in the calculateDistances function, instead of simply dividing the metres distance by 1000 to get it in kilometres, I multiply by 10, then round, then divide by 10. This is in order to round the distances to 1 decimal place.

e.g. the distance between my Ambleside and Keswick markers is 22205.96176105692 metres, according to Google. So to convert that to kilometres if I just divide by 1000 it gives:
22.20596176105692
The Round() function rounds to the nearest integer. So instead let’s only divide by 100 (or divide by 1000 then multiply by 10, as I’m doing, same thing). Which gives:
222.0596176105692
Then we call Math.round(), which gives:
222
And finally divide that by 10:
22.2
There are other ways to achieve this in javascript of course!

Notice as well that we’ve added the optional `libraries=geometry` parameter to the URL for the Google Maps JS file. This is so we can use the Geometry Library to calculate the distance between locations, using the computeDistanceBetween function. Also read this useful article which explains some more about using the Geometry Library.

February 12, 2013

Google Maps API – polylines

Filed under: Google Maps,Javascript — duncan @ 6:41 pm
Tags: , , , ,

So now we’ve created a map, added markers and infowindows. Maybe you want to start drawing lines and shapes on it.

Supposing I have two markers, and I just want to draw a line connecting them.

<script type="text/javascript">
	function initialize() {
		var homeLatlng = new google.maps.LatLng(51.476706,0);

		var map = new google.maps.Map(document.getElementById("map"), {
			zoom: 15,
			center: homeLatlng,
			mapTypeId: google.maps.MapTypeId.ROADMAP
		});

		// add start marker
		var startLatLng = new google.maps.LatLng(51.482051,0.001436);

		var startMarker = new google.maps.Marker({
			position: startLatLng, 
			map: map, 
			icon: 'http://maps.google.co.uk/intl/en_ALL/mapfiles/ms/micons/green-dot.png'
		});

		// add end marker
		var endLatLng = new google.maps.LatLng(51.473271,0.003195);

		var endMarker = new google.maps.Marker({
			position: endLatLng, 
			map: map, 
			icon: 'http://maps.google.co.uk/intl/en_ALL/mapfiles/ms/micons/red-dot.png'
		});

		// draw the route
		var route = new google.maps.Polyline({
			path: [startLatLng, endLatLng],
			strokeColor: "#FF0000",
			strokeOpacity: 1.0,
			strokeWeight: 4,
			map: map
		});	
	}

	google.maps.event.addDomListener(window, 'load', initialize);
</script>

Which gives us this simple map here:
polyline1

The important part to note here is the path attribute in the Polyline Options. The [ ] indicates it is an array, and indeed you can make an array of many coordinates:

	function initialize() {
		var homeLatlng = new google.maps.LatLng(51.476706,0);

		var map = new google.maps.Map(document.getElementById("map"), {
			zoom: 15,
			center: homeLatlng,
			mapTypeId: google.maps.MapTypeId.ROADMAP
		});

		// add start marker
		var startMarker = new google.maps.Marker({
			position: new google.maps.LatLng(51.482238,0.001581), 
			map: map, 
			icon: 'http://maps.google.co.uk/intl/en_ALL/mapfiles/ms/micons/green-dot.png'
		});

		// add end marker
		var endMarker = new google.maps.Marker({
			position: new google.maps.LatLng(51.481577,-0.0022), 
			map: map, 
			icon: 'http://maps.google.co.uk/intl/en_ALL/mapfiles/ms/micons/red-dot.png'
		});

		// create an array of coordinates
		var arrCoords = [
			new google.maps.LatLng(51.482238,0.001581),
			new google.maps.LatLng(51.473364,0.011966),
			new google.maps.LatLng(51.471974,-0.000651),
			new google.maps.LatLng(51.472108,-0.002196),
			new google.maps.LatLng(51.474995,-0.003827),
			new google.maps.LatLng(51.476492,-0.005629),
			new google.maps.LatLng(51.477855,-0.006058),
			new google.maps.LatLng(51.478443,-0.007045),
			new google.maps.LatLng(51.479298,-0.007861),
			new google.maps.LatLng(51.481202,-0.002136),
			new google.maps.LatLng(51.481577,-0.0022)
		];

		// draw the route
		var route = new google.maps.Polyline({
			path: arrCoords,
			strokeColor: "#FF0000",
			strokeOpacity: 1.0,
			strokeWeight: 4,
			map: map
		});	
	}

	google.maps.event.addDomListener(window, 'load', initialize);

Which gives you something more useful (if you were wanting to map the boundary of Greenwich Park that is):

polyline2

One important detail with polylines is if we want the lines to be geodesic or not.  When drawing lines, by default they are in a straight line on a 2D surface.  But the earth’s surface is curved.  What this means is if you want the shortest line between two points, the line might be slightly curved.  This is probably easier to illustrate than to explain.

Example One

polylines1

Here I’m drawing one blue vertical line, and several red diagonal lines, using this code:

	function initialize() {
		var line;
		var homeLatlng = new google.maps.LatLng(0,0);
				
		var map = new google.maps.Map(document.getElementById("map"), {
			zoom: 2,
			center: homeLatlng,
			mapTypeId: google.maps.MapTypeId.ROADMAP
		});
		
		for (var i = 0; i < 60; i+= 5) {
			line = new google.maps.Polyline({
				path: [homeLatlng, new google.maps.LatLng(i,170)],
				strokeColor: "#FF0000",
				strokeOpacity: 1.0,
				strokeWeight: 1,
				geodesic: false,
				map: map
			});
		}
		
		// for vertical line just draw one line
		line = new google.maps.Polyline({
			path: [homeLatlng, new google.maps.LatLng(85,0)],
			strokeColor: "#0000FF",
			strokeOpacity: 1.0,
			strokeWeight: 1,
			geodesic: false,
			map: map
		});
	}
	
	google.maps.event.addDomListener(window, 'load', initialize);

Notice I’m specifying geodesic: false – we don’t want geodesic curved lines.

Also one other difference over earlier examples.  We need to use the Geometry library from Google, so have to add this as a parameter when calling their script:

<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false&libraries=geometry"></script>

Example Two

This time simply change the geodesic attribute to true in both places we create polylines; all the other code is identical.

polylines2

The vertical line and the one horizontal line don’t look any different, but the diagonal lines take a curved route which is approximately the shortest distance, following the earth’s curve, between the two points. Of course on shorter distances you’re unlikely to notice any difference between the straight and curved lines, so for the most part you’ll typically use straight lines.

November 4, 2011

Google Maps API – adding infowindows in response to user clicks

In response to my previous post about adding infowindows with the Google Maps API, Frank B asked if it was possible “to write a script where the user clicks on the map, and an infowindow pops up in that location containing information about that place, say the county it belongs to”. Here’s a quick something I rustled up in response to that.

Firstly, let’s just open an infowindow on a map in response to user clicks on the map.

<script type="text/javascript">
function initialize() {
	var myOptions = {
		zoom: 10,
		center: new google.maps.LatLng(50.820645,-0.137376),
		mapTypeId: google.maps.MapTypeId.ROADMAP
	};
	
	var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
	
	var infowindow =  new google.maps.InfoWindow({
		content: 'Hello World!'
	});

	google.maps.event.addListener(map, 'click', function(event) {
		infowindow.setPosition(event.latLng);
		infowindow.open(map);
	}); 
}

google.maps.event.addDomListener(window, 'load', initialize);
</script>

So we simply have an event listener for any click events on the map itself. We can then find out the position of that click using event.latLng, which we can then pass to the setPosition function on the infowindow. The final .open() simply shows the infowindow; if it’s already open, this won’t do anything (but it causes no harm to call it again).

This opens the same infowindow anywhere we click on the map:

Now, let’s get some content about each location we click on, and use that to dynamically set the content of the infowindow. To do this we need to do a reverse Geocoding request (i.e. getting address details from a latlng coordinates). The usual use of the Geocoding service is to do the opposite – find out the coordinates of an address.

Interestingly Google’s API reference says to use a ‘location’ parameter but their Developer documentation says to use a ‘latLng’ parameter. Both seemed to work for me, but I’d say the API reference should be considered the correct source in general.

<script type="text/javascript">
var map, geocoder, infowindow;
	
function initialize() {
	var myOptions = {
		zoom: 10,
		center: new google.maps.LatLng(50.820645,-0.137376),
		mapTypeId: google.maps.MapTypeId.ROADMAP
	};
	
	map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
	
	geocoder = new google.maps.Geocoder();
	
	infowindow =  new google.maps.InfoWindow({
		content: ''
	});

	google.maps.event.addListener(map, 'click', function(event) {
		doGeocoding(event.latLng);
	});
}

function doGeocoding(latLng) {	
	geocoder.geocode({'location': latLng}, function(results, status) {
		if (status == google.maps.GeocoderStatus.OK) {
			if (results[1]) {	// use 2nd array item, a less-specific address
				infowindow.setContent(results[1].formatted_address);
			} else {
				infowindow.setContent('');
			}
		} else {
			infowindow.setContent('no address found');
		}
	});
	
	infowindow.setPosition(latLng);
	infowindow.open(map);
}

google.maps.event.addDomListener(window, 'load', initialize);
</script>

This code does strange things for me when clicking on the area at sea. If it’s the first click on the map, it doesn’t display any infowindow at all. If I initially click on the land to get an address, subsequent clicks on the sea use that same address, unless I click further out from the land, in which case it gives me ‘no address’. Think it’s an issue with trying to do setContent(”). I’m publishing this article despite it not being quite code-complete, in the hope it helps Frank (and I’m happy to take suggestions on how to get it fully working as it should).

October 8, 2011

Google Maps API – infowindows

So previously I showed how to create a basic map, then how to add markers to it. Now suppose you’re wanting to have one of those little bubbles pop up when you click on your map with some information in it. These are called infowindows in Google’s terminology.

Let’s start with the very basics – creating a map which has an infowindow already visible on it.

function initialize() {
	var homeLatlng = new google.maps.LatLng(51.476706,0);

	var myOptions = {
		zoom: 15,
		center: homeLatlng,
		mapTypeId: google.maps.MapTypeId.ROADMAP
	};

	var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);

	var infowindow =  new google.maps.InfoWindow({
		content: 'Hello World!',
		map: map,
		position: homeLatlng
	});
}

google.maps.event.addDomListener(window, 'load', initialize);

So we create an infowindow, specify some content for it and which map it’s for, and give it a latlong position (which coincidentally also happens to be where I’ve specified this map is to be centered, although that doesn’t have to be the case). The documentation says that “After constructing an InfoWindow, you must call open to display it on the map“; however I find the above code will open the infowindow without needing to explicitly call the open function.

But this shows the infowindow immediately. Perhaps you’d rather it only appeared after some user interaction. Typically you’d put markers on the map, and when the user clicks the marker, then you’d show the infowindow.

So let’s create the infowindow slightly differently. This time, don’t specify the position attribute. Then create a marker as normal, and finally we can open the infowindow, and we pass the marker as the second parameter. So the infowindow gets anchored to the marker’s position.

var infowindow =  new google.maps.InfoWindow({
	content: 'Hello World!',
	map: map
});

var marker = new google.maps.Marker({
	position: homeLatlng, 
	map: map
});

infowindow.open(map, marker);

So we’ve now added a marker to the map, which the infowindow is then anchored to.

You probably only want this to happen after the user has clicked on that marker. For that we need to have an event listener function. There are many of these available for all the different map elements, in this case we want to listen to the mouseclick event on that marker.

google.maps.event.addListener(marker, 'click', function() {
	infowindow.open(map, this);
});

So we say to the Google Maps event handler framework, on the marker, listen for any ‘click’ events. And when that happens, call this anonymous function which currently only has one line, to open the marker.

We could also make this infowindow appear in response to other events, e.g. if you simply hovered the mouse over the marker.

google.maps.event.addListener(marker, 'mouseover', function() {
	infowindow.open(map, this);
});

And then to make the infowindow disappear again when you move the mouse away from the marker, just call the close() function on mouseout.

google.maps.event.addListener(marker, 'mouseout', function() {
	infowindow.close();
});

Suppose you had several markers on your map, each of which would have its own infowindow. Here’s one way to deal with that. Firstly have an array of everything you need for each marker, arrDestinations. Then loop over them, adding a marker for each. We only have one infowindow variable, and we just update the content for it in response to user clicks. We need to delegate the event handler to an external function, bindInfoWindow, otherwise you end up just getting the content of the last array item for each marker.

<script type="text/javascript">
function initialize() {
	var i;
	var arrDestinations = [
		{
			lat: 50.815155, 
			lon: -0.137072, 
			title: "Brighton Pier", 
			description: "Brighton Palace Pier dates to 1899"
		},
		{
			lat: 50.822638, 
			lon: -0.137361, 
			title: "Brighton Pavilion", 
			description: "The Pavilion was built for the Prince of Wales in 1787"
		},
		{
			lat: 50.821226, 
			lon: -0.139372, 
			title: "English's", 
			description: "English's Seafood Restaurant and Oyster Bar"
		}
	];
	
	var myOptions = {
		zoom: 15,
		center: new google.maps.LatLng(50.820645,-0.137376),
		mapTypeId: google.maps.MapTypeId.ROADMAP
	};
	
	var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
	
	var infowindow =  new google.maps.InfoWindow({
		content: ''
	});
	
	// loop over our array
	for (i = 0; i < arrDestinations.length; i++) {
		// create a marker
		var marker = new google.maps.Marker({
			title: arrDestinations[i].title,
			position: new google.maps.LatLng(arrDestinations[i].lat, arrDestinations[i].lon),
			map: map
		});
		
		// add an event listener for this marker
		bindInfoWindow(marker, map, infowindow, "<p>" + arrDestinations[i].description + "</p>");  
	}
}

function bindInfoWindow(marker, map, infowindow, html) { 
	google.maps.event.addListener(marker, 'click', function() { 
		infowindow.setContent(html); 
		infowindow.open(map, marker); 
	}); 
} 

google.maps.event.addDomListener(window, 'load', initialize);
</script>

In production you’d probably use a server-side language to populate the array and just pass it into the initialize function. There are other ways of doing this as well. Another approach is to have multiple infowindows, one for each marker. On click, you close any open infowindows, and then just open the one that corresponds to the current marker. I don’t believe that approach is as efficient as my example though.

September 25, 2011

Google Maps API – adding markers

I previously showed how to create a very simple map. But usually you’ll want to do more than that. Let’s start with adding markers to the map.

So, using the code from the previous post, I just add this to the end of the initialize function:

var homeMarker = new google.maps.Marker({
	position: homeLatlng
});

That’s all you need to create a Marker object! There are many other attributes you can specify here, but only position is required.

But you’ll notice that using this code, no marker will be visible. That’s because we haven’t said what map this marker is for. Sometimes you might want to deliberately do this, e.g. by creating markers in advance for showing/hiding dynamically later on.

So there are two ways to associate a marker with a map. When you create the Marker object, just specify the map attribute. The value of which should refer to the variable name you used when you created the Map object.

var homeMarker = new google.maps.Marker({
	position: homeLatlng,
	map: map
});

Alternatively, you can call the setMap() function:

homeMarker.setMap(map);

If you ever wanted to remove a marker from a map, just call marker.setMap(null).

So at this point you’ve got a map that looks something like this (showing the brilliantly-titled No Name Key in Florida):

Great, right? Well maybe you want to do just a bit more than that. Let’s examine some of the other options.

Firstly, you can specify a title. This will be displayed as a tooltip when you mouse-over the marker.

var homeMarker = new google.maps.Marker({
	position: homeLatlng,
	map: map,
	title: "Check this cool location"
});

Maybe you’re not keen on that shadow. Easy, just set the flat property or use the marker.setFlat() function:

var homeMarker = new google.maps.Marker({
	position: homeLatlng,
	map: map,
	title: "Check this cool location",
	flat: true
});

Perhaps you need users to be able to drag the markers around (this will come in useful later if you’re doing more advanced dynamic things on the map). Simple, just set the draggable property or use the marker.setDraggable() function:

var homeMarker = new google.maps.Marker({
	position: homeLatlng,
	map: map,
	title: "Check this cool location",
	draggable: true
});

Fantastic. But supposing you’re not keen on the default marker image? Not a problem. Google have lots of different marker images you can use:

To use one of these images, just specify the icon property when creating the marker, or use the marker.setIcon() function. You can use either an absolute or relative URL (for images you’re hosting yourself).

var homeMarker = new google.maps.Marker({
	position: homeLatlng,
	map: map,
	title: "Check this cool location"
	icon: "http://maps.google.com/mapfiles/ms/micons/blue.png"
});

One difference you’ll notice from when we just used the default image, is that no shadow is displayed underneath the marker. You might not care, although it’s a nice visual touch that gives the map a bit of depth. If you want to specify a shadow, you need to have a shadow image as well. This is the shadow image for the Google markers:

One way to setup a shadow is just specify the shadow property on the MarkerOptions, or use marker.setShadow().

var homeMarker = new google.maps.Marker({
	position: homeLatlng,
	map: map,
	title: "Check this cool location",
	icon: "http://maps.google.com/mapfiles/ms/micons/blue.png",
	shadow: "http://maps.google.com/mapfiles/ms/micons/msmarker.shadow.png"
});

However this doesn’t actually place the shadow in the correct location for the above example (see image on the right). I think it’s important to understand what’s happening here.
The marker image is 32 x 32 pixels in dimensions. The shadow image is also 32 pixels high, but is wider, 59 pixels. Both images are currently being centred horizontally on the same point on the map.

Shadow:

Marker:

Together:

What we need to do is create MarkerImage objects for both the marker and its shadow, which will allow us to offset the shadow image to be slightly more to the right, so that it lines up nicely with the marker.

var image = new google.maps.MarkerImage(
	'http://maps.google.com/mapfiles/ms/micons/blue-dot.png',
	new google.maps.Size(32, 32),	// size
	new google.maps.Point(0,0),	// origin
	new google.maps.Point(16, 32)	// anchor
);

var shadow = new google.maps.MarkerImage(
	'http://maps.google.com/mapfiles/ms/micons/msmarker.shadow.png',
	new google.maps.Size(59, 32),	// size
	new google.maps.Point(0,0),	// origin
	new google.maps.Point(16, 32)	// anchor
);

So what’s going on here? We define the URL of the image, then the size of the image (width,height). Then the origin, which is the x,y coordinates of where our marker starts on that image (useful if you’re using one sprite image to do lots of markers). The origin locates the top-left corner of our marker. The anchor is the important one here for lining up our marker and our image. This is where the image ‘anchors’ itself to our latlng coordinates on the map. So as the marker comes to a point half-way along it, we say it’s 16 pixels along and 32 points down (i.e. at the bottom of the image). We then repeat this with the shadow, where the tip of the shadow is also 16 pixels along that image, coincidentally.

We then attach the icon and its shadow to the marker like so:

var homeMarker = new google.maps.Marker({
	position: homeLatlng,
	map: map,
	title: "Check this cool location",
	icon: image,
	shadow: shadow
});

You can also get markers from other sources, e.g.

Finally, here’s the complete code showing how to add custom marker images as outlined above:

<!DOCTYPE html>
<html>
<head>
<title>Google Maps API V3 lesson 2</title>

<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<style type="text/css">
html { height: 100% }
body { height: 100%; margin: 0; padding: 0 }
#map_canvas { height: 300px; width:600px }
</style>

<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false"></script>

<script type="text/javascript">
	function initialize() {
		var homeLatlng = new google.maps.LatLng(24.696554,-81.328238);

		var myOptions = {
			zoom: 15,
			center: homeLatlng,
			mapTypeId: google.maps.MapTypeId.ROADMAP
		};

		var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);

		var image = new google.maps.MarkerImage(
			'http://maps.google.com/mapfiles/ms/micons/green-dot.png',
			new google.maps.Size(32, 32),	// size
			new google.maps.Point(0,0),	// origin
			new google.maps.Point(16, 32)	// anchor
		);

		var shadow = new google.maps.MarkerImage(
			'http://maps.google.com/mapfiles/ms/micons/msmarker.shadow.png',
			new google.maps.Size(59, 32),	// size
			new google.maps.Point(0,0),	// origin
			new google.maps.Point(16, 32)	// anchor
		);

		var homeMarker = new google.maps.Marker({
			position: homeLatlng, 
			map: map,
			title: "Check this cool location",
			icon: image,
			shadow: shadow
		});
	}

	google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
	<div id="map_canvas"></div>
</body>
</html>

September 18, 2011

Google Maps API – an introduction

This is the first in a series of posts I intend to do looking into some uses of the Google Maps Javascript API, V3. Let’s start at the very beginning – you want to display a map on a webpage, so what’s required. This is the bare minimum you need to do.

Add an empty <div> to your HTML page, with a suitable ID attribute:

<div id="map_canvas"></div>

Add some CSS like this:

<style type="text/css">
html { height: 100% }
body { height: 100%; margin: 0; padding: 0 }
#map_canvas { height: 100% }
</style>

Include the Google Maps javascript file:

<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false"></script>

Create a javascript function that includes all the next few lines of code; let’s stick to convention and call it initialize.

Create a latlong object which indicates the latitude and longitude where your map is centred:

var homeLatlng = new google.maps.LatLng(51.42838,0.0); // London

If you don’t already know the latitude and longitude for where you want to place your map, there’s a very easy way to find out:

  1. Go to Google Maps
  2. Search for your desired destination, and zoom right in
  3. Right-click on the map at your destination, and choose the “What’s here?” option
  4. The latitude and longitude coordinates will appear in the search box

Create a structure with the following properties:

var myOptions = {
	zoom: 10,
	center: homeLatlng,
	mapTypeId: google.maps.MapTypeId.ROADMAP
};

The zoom value can be any integer between 0 (fully zoomed out) and about 18. Some cities will support zoom up to about 20, and there are isolated cases of parts of maps having a zoom level up to 23 or more! In general though you won’t want to zoom in that far. Typically I’d set this around 7 – 15 depending on what kind of map I’m displaying.

The center value uses the latlong object you already created.

The mapTypeId value can be one of four options:

  1. google.maps.MapTypeId.ROADMAP – normal street map
  2. google.maps.MapTypeId.SATELLITE – satellite photos (might not be available for all locations if you’re zoomed in too far)
  3. google.maps.MapTypeId.HYBRID – combination of a satellite map with streets overlaid on top
  4. google.maps.MapTypeId.TERRAIN – what it sounds like, indicates natural features, height of land etc

I generally stick with ROADMAP, as that’s the most typical map type you see by default. Here’s a demonstration of the different types:

Create a new map with the structure of options you just created, and specifying the DIV where you want the map to appear:

var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);

That’s the end of your initialize function. You could just call this using the onload attribute of the body tag. Or if you were using jQuery, with $(document).ready(). However, Google has an event listener, that does the same thing:

google.maps.event.addDomListener(window, 'load', initialize);

Wrapping it all up, here’s the complete page for simply displaying a map at a given location:

<!DOCTYPE html>
<html>
<head>
<title>Google Maps API V3 lesson 1</title>

<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<style type="text/css">
html { height: 100% }
body { height: 100%; margin: 0; padding: 0 }
#map_canvas { height: 100% }
</style>

<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false"></script>

<script type="text/javascript">
	function initialize() {
		var homeLatlng = new google.maps.LatLng(51.476706,0); // London
		
		var myOptions = {
			zoom: 15,
			center: homeLatlng,
			mapTypeId: google.maps.MapTypeId.ROADMAP
		};
		
		var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
	}
	
	google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
	<div id="map_canvas"></div>
</body>
</html>

September 17, 2011

Using jQuery to animate table rows

Filed under: Javascript,jQuery — duncan @ 8:00 am
Tags: , , ,

jQuery has some nice ways to animate content, e.g. sliding or fading it into view. These don’t really work on <tr> or <td> tags though. I had a situation recently where we wanted to add an extra <tr> into a table, and make it slide in. The answer’s very simple (thanks to stackoverflow) – wrap the table content in a div, and add the slide animation to that instead.

So imagine you have a very simple example table like this:

<table>
	<tr id="row1">
		<td><a href="">Show another row</a></td>
	</tr>
</table>

This javascript would then make the second <tr> slide into view.

<script type="text/javascript">
$(document).ready(function() {
	// hidden content to add after the current row
	$('#row1').after("<tr id='row2'><td><div style='display:none'>There's a voice that keeps on calling me. Down the road, that's where I'll always be. Every stop I make, I make a new friend. Can't stay for long, just turn around and I'm gone again. Maybe tomorrow, I'll want to settle down, Until tomorrow, I'll just keep moving on.</div></td></tr>");

	$('#row1 a').click(function() {
		if ($('#row2 div').is(":visible")) {
			// hide the div
			$('#row2 div').slideUp(700);
			
		} else {
			// slide the div into view
			$('#row2 div').slideDown(700);
		}
		
		// prevent the click on the link from propagating
		return false;
	});
});
</script>

Of course it gets more complicated if you have more than one <td>. For example:

<table>
	<tr id="row1">
		<td>A</td>
		<td>One</td>
		<td>Red</td>
		<td>Apple</td>
		<td>$0.99</td>
		<td><a href="">Show row 2</a></td>
	</tr>
</table>

In that case you can use the .wrapInner function to add a hidden div to all the <td> tags.

<script type="text/javascript">
$(document).ready(function() {
	// content to add after the current row
	var $row2 =	$('<tr id="row2">' +
					'<td>B</td>' +
					'<td>Two</td>' +
					'<td>Yellow</td>' +
					'<td>Banana</td>' +
					'<td>$1.23</td>' +
					'<td> </td>' +
				'</tr>');
	
	// add hidden divs around the content of all the TD tags
	$row2.find('td').wrapInner('<div style="display:none" />');
	
	// add this row after the first row
	$('#row1').after($row2);

	$('#row1 a').click(function() {
		if ($('#row2 div').is(":visible")) {
			// hide the div
			$('#row2 div').slideUp(700);
			
			// update link text
			$('#row1 a').text('show row 2');
			
		} else {
			// slide the div into view
			$('#row2 div').slideDown(700);
			
			// update link text
			$('#row1 a').text('hide row 2');
		}
		
		// prevent the click on the link from propagating
		return false;
	});
});
</script>
Next Page »

Theme: Rubric. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.