Twitter: raymondcamden


Address: Lafayette, LA, USA

Simple introduction to Google Maps Part 2 - Markers

04-13-2011 11,279 views jQuery, JavaScript, ColdFusion 34 Comments

Back in February I wrote a simple introduction to using Google Maps. ColdFusion makes this relatively easy, but there are times when you may want to use the Google Map service natively. It's also important to note that CFMAP makes use of the previous version of Google Maps. If you want to make use of the new hotness, you have to do it "by hand." As my blog post (hopefully) showed, this isn't terribly difficult at all. I thought I'd post a sequel that dealt with another part of the puzzle - markers. ColdFusion makes this really easy as well with the cfmapitem tag. Here's what I found when digging into the docs.

Google groups markers into an area they called overlays. Overlays include:

  • Markers (duh)
  • Lines
  • Polygons (you see - Math is important)
  • Circles
  • and finally - "Info Windows"

The last item, "Info Windows", is actually something you have already seen if you've played with cfmap. When you create a marker with cfmapitem and supply a value for markerwindowcontent, you are actually creating both a marker and an Info Window. The info window is the little "cartoon voice" window that pops up when you click on the marker. Let's look at a simple example that creates a map and then puts a marker on top of it:

view plain print about
1<!DOCTYPE html>
2<html>
3
4<head>
5<style type="text/css">
6 #map_canvas { width: 450px; height: 450px; }
7</style>
8<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
9<script type="text/javascript">
10
11function initialize() {
12    var myLatlng = new google.maps.LatLng(-25.363882,131.044922);
13    var myOptions = {
14        zoom: 4,
15        center: myLatlng,
16        mapTypeId: google.maps.MapTypeId.ROADMAP,
17    }
18    var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
19    
20    var marker = new google.maps.Marker({
21     position: myLatlng,
22     title:"Hello World!"
23    });
24    
25    // To add the marker to the map, call setMap();
26    marker.setMap(map);
27}
28</script>
29</head>
30
31<body onload="initialize()">
32
33 <div id="map_canvas"></div>
34
35</body>
36</html>

So I assume you read the previous entry and I don't need to explain the code that initializes the map. Immediately after this code is the marker code, let's focus on that:

view plain print about
1var marker = new google.maps.Marker({
2 position: myLatlng,
3 title:"Hello World!"
4});
5    
6// To add the marker to the map, call setMap();
7marker.setMap(map);

I create the marker object with with a position (a longitude and latitude - we've all memorized our long/lat values, right?) and a title. There's more options to the marker object, but this will give you a basic marker with a title. The setMap function simply allows me to set the map for the marker. You can also provide it when you create the marker:

view plain print about
1var marker = new google.maps.Marker({
2 position: myLatlng,
3 title:"Hello World!",
4 map:map
5});

You can run this yourself here: http://www.coldfusionjedi.com/demos/april132011/test1.html

That works - but let's get some ColdFusion involved - check out this next example:

view plain print about
1<cfquery name="getorders" datasource="cfartgallery" maxrows="10">
2select    orderid, total, orderdate, address, city, state, postalcode
3from    orders
4</cfquery>
5
6<!DOCTYPE html>
7<html>
8
9<head>
10<style type="text/css">
11 #map_canvas { width: 450px; height: 450px; }
12</style>
13<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
14<script type="text/javascript">
15var map;
16function initialize() {
17    var latlng = new google.maps.LatLng(38, -90);
18    var myOptions = {
19        zoom: 3,
20        center: latlng,
21        mapTypeId: google.maps.MapTypeId.ROADMAP
22    };
23    map = new google.maps.Map(document.getElementById("map_canvas"),myOptions);
24    
25    geocoder = new google.maps.Geocoder();
26    <cfoutput query="getorders">
27    geocoder.geocode( { 'address': '#address#'}, function(results, status) {
28        if (status == google.maps.GeocoderStatus.OK) {
29            var marker = new google.maps.Marker({
30                map: map,
31                position: results[0].geometry.location,
32                title: "Order #orderid# for #dollarFormat(total)#"
33            });
34        } else {
35            alert("Geocode was not successful for the following reason: " + status);
36        }
37    });
38    </cfoutput>
39}
40</script>
41</head>
42
43<body onload="initialize()">
44
45<div id="map_canvas"></div>
46
47</body>
48</html>

First - I added a query on top to get order information from the database. This includes address information for each order. Now look into our JavaScript. Because my order data isn't geocoded, I have to ask Google to geocode the data for me. I loop over each order and request such data and when I get it back, I use it to create a marker. Note the title includes a bit of information about the order itself. Not terribly complex but closer to a real application. You can see this one here: http://www.coldfusionjedi.com/demos/april132011/test2.cfm In case it isn't obvious, the title for the marker shows on mouse over.

Ok - so what about the cute little info windows? Let's look at our next example:

view plain print about
1<cfquery name="getorders" datasource="cfartgallery" maxrows="10">
2select    orderid, total, orderdate, address, city, state, postalcode
3from    orders
4</cfquery>
5
6<!DOCTYPE html>
7<html>
8
9<head>
10<style type="text/css">
11 #map_canvas { width: 450px; height: 450px; }
12</style>
13<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
14<script type="text/javascript">
15var map;
16function initialize() {
17    var latlng = new google.maps.LatLng(38, -90);
18    var myOptions = {
19        zoom: 3,
20        center: latlng,
21        mapTypeId: google.maps.MapTypeId.ROADMAP
22    };
23    map = new google.maps.Map(document.getElementById("map_canvas"),myOptions);
24    
25    geocoder = new google.maps.Geocoder();
26    <cfoutput query="getorders">
27    geocoder.geocode( { 'address': '#address# #city# #state# #postalcode#'}, function(results, status) {
28        if (status == google.maps.GeocoderStatus.OK) {
29            var marker = new google.maps.Marker({
30                map: map,
31                position: results[0].geometry.location,
32                title: "Order #orderid# for #dollarFormat(total)#"
33            });
34            var infowindow = new google.maps.InfoWindow({
35             content: "<h3>Order #orderid#</h3>Address: #address#, #city#, #state# #postalcode#<br/>Ordered: #dateFormat(orderdate)#"
36            });
37            google.maps.event.addListener(marker, 'click', function() {
38             infowindow.open(map,marker);
39            });
40            
41        } else {
42            alert("Geocode was not successful for the following reason: " + status);
43        }
44    });
45    </cfoutput>
46}
47</script>
48</head>
49
50<body onload="initialize()">
51
52<div id="map_canvas"></div>
53
54</body>
55</html>

I'm going to focus on what's new here and that's the code right after the marker creation. You can see I make a new infowindow object. The content here is totally arbitrary, but I figured order details would be nice. Finally we add a click event to make it open when the marker is clicked. Still relatively simple, right? You can run this one here: http://www.coldfusionjedi.com/demos/april132011/test3.cfm

Time for our final demo. What spurred this blog post was a request from someone for the ability to center a map based on a click elsewhere in the page. Here's how I solved that - and note - there is probably a much nicer way to do this.

view plain print about
1<cfquery name="getorders" datasource="cfartgallery" maxrows="10">
2select    orderid, total, orderdate, address, city, state, postalcode
3from    orders
4where orderid != 4
5</cfquery>
6
7<!DOCTYPE html>
8<html>
9
10<head>
11<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
12<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
13<script type="text/javascript">
14var map;
15var markers = [];
16var lastinfowindow;
17
18function initialize() {
19    var latlng = new google.maps.LatLng(38, -90);
20    var myOptions = {
21        zoom: 3,
22        center: latlng,
23        mapTypeId: google.maps.MapTypeId.ROADMAP
24    };
25    map = new google.maps.Map(document.getElementById("map_canvas"),myOptions);
26    
27    geocoder = new google.maps.Geocoder();
28    <cfoutput query="getorders">
29    geocoder.geocode( { 'address': '#address# #city# #state# #postalcode#'}, function(results, status) {
30        if (status == google.maps.GeocoderStatus.OK) {
31            var marker = new google.maps.Marker({
32                map: map,
33                position: results[0].geometry.location,
34                title: "Order #orderid# for #dollarFormat(total)#"
35            });
36            var infowindow = new google.maps.InfoWindow({
37             content: "<h3>Order #orderid#</h3>Address: #address#, #city#, #state# #postalcode#<br/>Ordered: #dateFormat(orderdate)#"
38            });
39            google.maps.event.addListener(marker, 'click', function() {
40             infowindow.open(map,marker);
41            });
42            marker.orderid = #orderid#;
43            marker.infowindow = infowindow;
44            markers[markers.length] = marker;
45        } else {
46            //alert("Geocode was not successful for the following reason: " + status);
47        }
48    });
49    </cfoutput>
50    
51    $(".order").click(function() {
52        var thisorder = $(this).data("orderid");
53        for(var i=0; i<markers.length; i++) {
54            if(markers[i].orderid == thisorder) {
55                console.log("found my match");
56                //get the latlong
57                if(lastinfowindow instanceof google.maps.InfoWindow) lastinfowindow.close();
58                console.dir(markers[i]);
59                map.panTo(markers[i].getPosition());
60                markers[i].infowindow.open(map, markers[i]);
61                lastinfowindow = markers[i].infowindow;
62            }
63        }
64    });
65}
66
67
68</script>
69<style>
70#map_canvas {
71 width: 450px; height: 450px;
72 float:left;
73}
74#orders {
75    margin-top: 0px;
76    padding-top: 0px;
77    margin-left: 10px;
78    float:left;
79    height: 450px;
80    overflow-y: scroll;
81}
82.order {
83     border-style:solid;
84     border-width:thin;
85        width: 300px;
86        padding: 5px;
87        cursor:pointer;
88        margin-top:0px;
89}
90</style>
91</head>
92
93<body onload="initialize()">
94
95<div id="map_canvas"></div>
96
97<div id="orders">
98<cfloop query="getorders">
99    <cfoutput>
100    <p class="order" data-orderid="#orderid#">
101    <b>Order #orderid#</b><br/>
102    #address#, #city#, #state# #postalcode#
103    </p>
104    </cfoutput>
105</cfloop>
106</div>
107
108</body>
109</html>

So first a quick note - I modified my query to not get orderid 4. That order was consistently not geolocating so I thought i'd just remove it. Right away you may notice something new - I've created an array called markers and a variable called lastinfowindow. The reason why for this will become evident real quick like.

Scroll down a bit to where I create the marker. I wanted a way to associate the order ID with the marker (and again, why will be made clear), so I kinda cheated a bit. Objects in JavaScript can be manipulated much like CFCs in ColdFusion. I can add an arbitrary value - as I've done here - to store the order id. That kind of bugs me. I could have create a new object instead and stuffed the marker as one key and the value as another - but this works. I also store a copy of the infowindow object. Finally this is appended to my array. The end result is that I've now got a global object that contains a reference to all my markers. Apparently (and I could be wrong), Google Maps do not provide an easy way to get all the markers in a map. From what I see they recommend exactly this approach - storing them within an array.

So - next up is a click handler defined using jQuery syntax. If you look down a bit you can see I'm outputting the orders in a set of P blocks. I've asked jQuery to set up a click handler on those paragraphs. When you click, I grab the order id (I love data-* properties!) and then look for it within my markers array. If I find it - I first see if I need to close an existing infowindow. I then pan the map to the location and open the infowindow. All in all - rather simple? If you want to demo this, please be aware of the console.log messages I used to debug. Use in a Chrome browser or Firebug-enabled Firefox.

A big thank you to Todd Sharp for help with the CSS.

Related Blog Entries

34 Comments

  • Commented on 04-13-2011 at 6:57 PM
    Nice followup post. I was curious, since I haven't used the maps API in awhile, does the title property do anything for a marker? I didn't notice "Order #orderid# for #dollarFormat(total)#" displaying anywhere.

    This was the first ORM project I did when CF9 came out (I also chose not to use CF's version of google maps so I could update the maps stuff without upgrading CF).

    I went with making "markers" into an object and using the orderid as properties of the object (and that property in turn was the corresponding marker object). That way when paging through the results I could use the markers object as a cache and only create a new marker if that ID didn't exist as a property of the markers object. I don't know how much memory creating a marker takes but I figured it's still best not to let someone loading them infinitely :)
  • Chris Bowyer #
    Commented on 04-13-2011 at 7:50 PM
    Very nice! Just one issue. It is not working for me in in IE9
  • Commented on 04-13-2011 at 9:35 PM
    @Chris: That's the console.log messages. I warned ya about them. ;)

    @Jeremy: They show up (or should) on mouse over.
  • Commented on 04-13-2011 at 9:39 PM
    @Ray You're totally right, I originally read the post on my Xoom so I wasn't seeing them. Get them fine in desktop browsers, didn't know about that property, very nice.
  • Commented on 04-13-2011 at 9:49 PM
    Beside the mouseover not working, how did it look in the Xoom? Mine is upstairs and I'm too lazy to go get it.
  • Drew Wells #
    Commented on 04-13-2011 at 11:10 PM
    You should try out some of the competition, they don't get enough publicity. http://www.openlayers.org/ http://polymaps.org/ http://tile5.com/
    Here's one I made with polymaps http://drewwells.net/craigmaps/
  • Chris Bowyer #
    Commented on 04-13-2011 at 11:20 PM
    @Drew: Your map doesn't work for me in IE9 and it is very slow. I also much prefer a Google map
  • Commented on 04-14-2011 at 9:16 AM
    @Ray It looked just as good on the Xoom, snappy too.
  • Commented on 04-14-2011 at 9:23 AM
    Sweetness. Will try it today at lunch. Oddly - my upstairs office is wired, not wireless, so it's hard to use devices. Got wifi downstairs in the main part of my house.
  • Commented on 04-14-2011 at 9:49 AM
    Random sidenote, Drew's map doesn't work in IE because of line 687 in polymap.js:

    window.addEventListener("resize", resizer, false);
    window.addEventListener("load", resizer, false);

    which in IE needs to be window.addEvent() because I don't believe IE supports addEventLister() in recent versions, usually you can just conditionally add the events based on browser type.

    http://www.quirksmode.org/js/events_advanced.html
  • Dale Severin #
    Commented on 04-14-2011 at 4:33 PM
    Marker: 12 Gover St, Oakland, CA 45794 displays at Glover Street in San Francisco. Apparantly Google maps gets lost at times.
  • Commented on 04-14-2011 at 4:38 PM
    Interesting. If you input that address in maps.google.com, it can't find it, and the first location it suggests is Glover in San Fran. So in this case I'm guessing the geolocation is just using the best result.
  • Commented on 05-25-2011 at 1:10 PM
    You can use the Google chart api to change up the icon for your map marker (http://code.google.com/apis/chart/image/docs/galle...icons.html) You pass the url of the marker you come up with to the icon parameter of marker. Here's a marker that has a blue pin with a black letter C in it:

           var contactMarker = new google.maps.Marker({
           position: contactLocn,
           map: map,
           title:"Here they are!",
           icon:'https://chart.googleapis.com/chart?chst=d
    mappinletter_withshadow&chld=C|87cefa|000000',
           animation: google.maps.Animation.DROP
           });       
    nb - you can use http or https for maps and charts now.
  • Commented on 05-25-2011 at 1:12 PM
    Wow, that's kinda cool. Thanks Matt!
  • Chris Bowyer #
    Commented on 05-27-2011 at 6:29 AM
    @Ray - Thanks for this article Ray. It has been of great help

    @Dale - Had a play with the Google Chart icons this evening. Yes. Very cool. Only problem is, they are not very accurate, especially the not so standard looking pins and other icons
  • Commented on 09-08-2011 at 7:44 AM
    I have getting error one i fetch number of record in cfoutput
    OVERQUERYLIMIT indicates that you are over your quota.
    Its means when i have above 50 records this will not work.
    Please help me about this

    Thanks in advance


    Regards,

    Tariq
  • Commented on 09-08-2011 at 7:50 AM
    Um - use less markers? Seriously - I can't help you with the Google quota limits. I'm sure you could pay them for more. You could try limiting the number of markers you show at one time.
  • Commented on 09-20-2011 at 3:18 PM
    Thanks again Ray. Indebted as ever.Struggling trying to get the infoWindow to go to the right point. It always picks up the last record even thought the title is correct.

    <cfoutput query="Sightings">
       var latlng = new google.maps.LatLng(#Sightings.lattitude#, #Sightings.longitude#);
       var marker = new google.maps.Marker({
                map: map,
                position:latlng,
                title: "#labelen#- #lattitude#, #longitude#",            
             });         
    var infowindow = new google.maps.InfoWindow({       
             content: "<B>#label
    en#</B><br/><a href='http://127.0.0.1:8500/WildlifeHeroWebCFM/WildlifeH...(binomial)#'>(#binomial#)</a><br/>#eventDate#<br>#city#,#weather#c<p>#eventnotes#"
             });
             google.maps.event.addListener(marker, 'click', function() {
              infowindow.open(map,marker);
             });      
       </cfoutput>
  • Commented on 09-20-2011 at 3:19 PM
    I think you want to remove "Sightings." from here:

    var latlng = new google.maps.LatLng(#Sightings.lattitude#, #Sightings.longitude#);
  • Commented on 09-20-2011 at 3:31 PM
    Wow thanks for coming back so quick. Tried that, it makes no difference at all. The hover over title is correct but when I open the information window it is always displaying the last record.
  • Commented on 09-20-2011 at 3:46 PM
    Is it online where I can see?
  • Commented on 09-22-2011 at 7:03 AM
    Hi Ray, I have it online now at http://www.wildlifehero.com/web/maps/reservemap.cf... Bit infuriating this one. I assume the varables are overwriiten by the time I get to use them in the infoWindow. By the way I am using your galleon forums here too so you are an official Wildlife Hero
  • Commented on 09-22-2011 at 8:20 AM
    Ok - you are doing things a bit differently since you have lat/lng values and don't have to geocode.

    What follows - I stress - should be taken with a grain of salt as it involves scoping issues in JavaScript and I'm still a bit... unsure there.

    If you look at my code, I've got N "blocks" of geocoding going on. Within the block, I use var statements to define markers, strings, etc, and then append a marker to a global object.

    In your code, you don't have those wrappers. So I think the variables are 'going over' each other.

    As a quick fix, try this. I imagine you are looping over a query, right? So you have access to somequery.currentRow. Take that value and add it to the end of

    latlgnS
    image
    contentString
    infowindow
    marker

    Or - conversely - use a loop in JavaScript.

    If you want to give me FTP access, I can take a stab at it.

    Actually.... thinking... try adding the dynamic part ONLY to marker. So

    var marker =

    becomes

    var marker#somequery.currentRow# =

    Hmmm. The more I think about it, the more I know it is a scoping issue. I'd have to really get in to see the best fix though.
  • Commented on 09-22-2011 at 9:10 AM
    Adding current row causes the window not function at all. Can u send me your email address I have lost it. Thanks
  • Commented on 09-22-2011 at 9:14 AM
    I emailed you this morning about a ... issue... on your server. :)
  • Commented on 08-05-2012 at 7:32 PM
    Dang it Ray, I'm having the same problem as Terry. Been working on it for several hours. Wishing I had more brain cells.
  • Commented on 08-06-2012 at 10:04 AM
    If I can help, let me know via email.
  • Shameel #
    Commented on 03-05-2013 at 2:37 AM
    Hi...Thanks for the nice post. I want to know if there is a way to search through the markers..I have a custom map with around a hundred markers and I want to search among them..
  • Commented on 03-05-2013 at 6:21 AM
    You would have to do it via JavaScript. If you have the data for your markers in a JavaScript array, you would just loop over them.
  • jlig #
    Commented on 04-29-2013 at 10:01 AM
    Ray, what about adding an option that allows the end-user to "drag" the pin to a different location that is saved back to Coldfusion (like this example: http://www.putyourlightson.net/coordinates)?
  • Commented on 04-29-2013 at 1:29 PM
    I'm confused - what part do you need help with? In this demo, he gives you the JS. To 'store' it you would just do an AJAX call. If you've never done AJAX with CF, let me know, but if you have, that's all that is involved here.
  • jlig #
    Commented on 04-29-2013 at 2:17 PM
    The example I linked to is using the old v2 the Google API so I needed an example using v3.. This example is v3 & grabs the Lat/Long. (http://www.geocodezip.com/v3GoogleExgeocoding-reversegeocoding-simple.html)

    Question: How do I "grab" those updated Lat/Long values and save to URL variables for posting to SalesForce/ColdFusion?
  • Commented on 04-29-2013 at 3:31 PM
    All I can recommend is doing what I'd do. I'd look at the first example and see how they enabled drag/drop for the markers. Compare that to the docs you can read for v3 and see if they are the same, or if not, close. Make any required changes for v3, and then you are done.

    Wouldn't you agree that should work?
  • Commented on 05-30-2013 at 9:32 AM
    Thank You !!!

Post Reply

Please refrain from posting large blocks of code as a comment. Use Pastebin or Gists instead. Text wrapped in asterisks (*) will be bold and text wrapped in underscores (_) will be italicized.

Leave this field empty