Duncan’s blog

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 21, 2011

ColdFusion LSCurrencyFormat inserts hard spaces?

Filed under: Coldfusion — duncan @ 8:31 pm
Tags: , , , , , ,

Today I was doing some work with the LSCurrencyFormat function for the very first time. Along the way I discovered something slightly unexpected. If the locale you specify uses spaces for a thousands separator (e.g. French), then Adobe ColdFusion (no idea for Railo or BlueDragon), doesn’t just add a normal space, it uses a ‘hard’ space.

I wouldn’t have found this out at all if it wasn’t for the fact I was doing some unit tests with MXUnit at the time. My test case was something like this:

<cfset strPrice = lsCurrencyFormat("12345678.9", "none", "fr_FR")>
<cfset assertEquals("12 345 678,90",  strPrice)>

So initially that failed, giving me the useful message that
Expected ’12 345 678,90′ but received ’12 345 678,90′. These values should be the same.
Puzzling. I was doing this in Firefox; if I selected just the error message and then chose View Selection Source, I could see it actually said
Expected ’12 345 678,90′ but received ’12&nbsp;345&nbsp;678,90′.

Ahah, so I assumed it was inserting the ‘&nbsp;’ non-breaking space HTML entity into the value instead of just a normal single space. So I updated my unit test assertion to be like this:

<cfset assertEquals("12&nbsp;345&nbsp;678,90",  strPrice)>

But that also failed, and this time when checking the source, it said
Expected ’12&nbsp;345&nbsp;678,90′ but received ’12&nbsp;345&nbsp;678,90′.
Curiouser and curiouser!

So at this point, I decided to loop over the actual string returned by lsCurrencyFormat to examine it more closely:

<cfoutput>
	<cfset french = lsCurrencyFormat("12345678.90", "none", "fr_FR")>
	
	<cfloop index="i" from="1" to="#Len(french)#">
		<cfset char = mid(french, i, 1)>
		
		#char#:#Asc(char)#<br/>
	</cfloop>
</cfoutput>

Which produced output that looked like:

1:49
2:50
 :160
3:51
4:52
5:53
 :160
6:54
7:55
8:56
,:44
9:57
0:48

The normal HTML space is ASCII character 32. But in this case it’s inserting ASCII character 160, which the browser interprets as &nbsp;. So for my unit test to work, I’ll have to do something like:

<cfset assertEquals("12" & Chr(160) & "345" & Chr(160) & "678,90",  strPrice)>

Dunno why Adobe in their infinite wisdom (to be honest this probably dates back to Allaire or Macromedia) would use character 160 instead just a normal single space character 32. And I expect other locale-specific formatting functions must do the same thing.
I’m getting exactly the same results with java.text.DecimalFormatSymbols.getGroupingSeparator(), so I expect ColdFusion is using that functionality in the background.

I suppose it shouldn’t really cause any problems, apart from perhaps when you’re needing to unit test (or perhaps if parsing some HTML you’ve received by CFHTTP). Just, unexpected is all.

September 19, 2011

Adobe survey

Filed under: Coldfusion — duncan @ 6:57 pm
Tags: , ,

I was sent a link to a survey on behalf of Adobe, asking about use of ColdFusion. The website the survey is on doesn’t get off to a promising start:

uh, ok. So I proceed to the next page, which made me swear out loud.

Yes, that’s right, that bit in the blue text at the bottom, scrolling along the page, it’s the <marquee> tag in use! And not even ironically. It’s like 1999 all over again…

<p><span class="style1">
  <marquee>
  This survey is best viewed with Internet Explorer
  </marquee>
</span></p>

I ended up giving up on it fairly quickly after that.

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>

Theme: Rubric. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.