Shaun Mccran

My digital playground

15
J
A
N
2011

Dynamically adding markers to Google maps

Following on from a previous article I wrote about (Google maps panning the next step in my Google mapping project is to be able to add markers to a Google map dynamically.

This article deals with how to translate a location into a latitude and longitude using Google, and how to send and add markers from a database into a Google maps via a remote service, using AJAX and JSON.

There is a full example of the finished application here: Demo of dynamically adding markers to Google maps

Much like the previous article about Google map panning we have to load the JQuery and the Google maps API, using an API key related to our domain.

view plain print about
1<s/cript type="text/javascript"
2src="http://www.google.com/jsapi?key= your API key here">

3</script>
4
5<s/cript type="text/javascript">
6    google.load("jquery", '1.3');
7    google.load("maps", "2.x");
8</script>

Next we will pick a point on the map, and set it as the centre, with a predefined zoom level. Below that we are creating references to the Google API 'bounds' object, and the 'Geocoder' object. I am also creating an Array of response codes for remote service responses.

Next the getJSON() JQuery method fires on page load. This communicates with our remote service, in this case a CFC (ColdFusion) and looks for a function called 'listpoints'. I'll drop the code for that in later, but it returns a JSON object of data that contains the name of a location and its lat-long values.

view plain print about
1<s/cript type="text/javascript">
2    $(document).ready(function(){
3
4    // set the element 'map' as container
5    var map = new GMap2($("#map").get(0));
6
7    // set the lat long for the center point
8    var cheltenham = new GLatLng(51.89487062921521,-2.084484100341797);    
9    // value 1 is the center, value 2 is the zoom level
10    map.setCenter(cheltenham, 9);
11
12    var bounds = new GLatLngBounds();
13    var geo = new GClientGeocoder();
14
15    // status codes
16    var reasons=[];
17        reasons[G_GEO_SUCCESS] = "Success";
18        reasons[G_GEO_MISSING_ADDRESS] = "Missing Address";
19        reasons[G_GEO_UNKNOWN_ADDRESS] = "Unknown Address.";
20        reasons[G_GEO_UNAVAILABLE_ADDRESS]= "Unavailable Address";
21        reasons[G_GEO_BAD_KEY] = "Bad API Key";
22        reasons[G_GEO_TOO_MANY_QUERIES] = "Too Many Queries";
23        reasons[G_GEO_SERVER_ERROR] = "Server error";
24
25    // initial load points
26    $.getJSON("map-service.cfc?method=listpoints", function(json) {
27        if (json.Locations.length >
0) {
28            for (i=0; i<json.Locations.length; i++) {
29                var location = json.Locations[i];
30                addLocation(location);
31                }
32            zoomToBounds();
33            }
34        });

The page display itself is very small, there are only three elements, the 'map' div, where our map function will be inserted, a 'list', which will populated with a list of our locations and a 'form' to allow a user to submit new locations.

view plain print about
1<div id="wrapper">
2
3    <div id="map"></div>
4    <ul id="list"></ul>
5    <div id="message" style="display:none;"></div>
6
7<form id="add-point" action="map-service.cfc?method=accept" method="post">
8<input type="hidden" name="action" value="savepoint" id="action">
9
10        <fieldset>
11            <legend>Add a Point to the Map</legend>
12            <div class="error" style="display:none;"></div>
13            <div class="input">
14            <label for="name">Location Name</label>
15            <input type="text" name="name" id="name" value="">
16            </div>
17
18            <div class="input">
19            <label for="address">Address</label>
20            <input type="text" name="address" id="address" value="">
21            </div>
22
23            <button type="submit">Add Point</button>
24        </fieldset>
25</form>
26
27</div>

All the data in my example is stored in a mySQL database. The script to create it is below. There are only a few fields, and they are reasonably easy to recognise.

view plain print about
1CREATE TABLE `locations` (
2 `intId` int(11) NOT NULL AUTO_INCREMENT,
3 `varName` varchar(100) DEFAULT NULL,
4 `varLat` varchar(25) DEFAULT NULL,
5 `varLong` varchar(25) DEFAULT NULL,
6 `varAddress` varchar(55) DEFAULT NULL,
7 PRIMARY KEY (`intId`)
8) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=latin1;

At this point if you were build this app you would have a page displaying a map and a series of map points from a database (if you had some in the table).

The next few code snippets are the parts that accept the form values, and translate them into useful map data to store then display them.

A user will enter the place label and location, this is then geo coded and sent to the remote service to be saved in our database. Here we can perform any validation and actually store the data. The same service returns the newly saved data to our map and we dynamically add the new marker to the map.

The first new function is a JQuery selector triggered when a user submits the form. It simply fires the geoEncode() function below it. The geoCode function tries to lookup the location the user entered from the form. If there is an error the corresponding message is displayed, otherwise the savePoint() function fires, using the location values.

view plain print about
1$("#add-point").submit(function(){
2    geoEncode();
3    return false;
4});
5
6function geoEncode() {
7var address = $("#add-point input[name=address]").val();
8    geo.getLocations(address, function (result){
9    if (result.Status.code == G_GEO_SUCCESS) {
10    geocode = result.Placemark[0].Point.coordinates;
11    savePoint(geocode);
12            } else {
13        var reason="Code "+result.Status.code;
14    if (reasons[result.Status.code]) {
15        reason = reasons[result.Status.code]
16                }
17    $("#add-point .error").html(reason).fadeIn();
18        geocode = false;
19            }
20        });
21    }

The savePoint() function serialises the inputs from the form and performs the Geo encoding. Now we try and post the form. If it fails we show a message, otherwise we run the addLocation() and zoomToBounds() functions. (almost there, stick with it!).

view plain print about
1function savePoint(geocode) {
2    var data = $("#add-point :input").serializeArray();
3     data[data.length] = { name: "lng", value: geocode[0] };
4     data[data.length] = { name: "lat", value: geocode[1] };
5
6    $.post($("#add-point").attr('action'), data, function(json){
7        $("#add-point .error").fadeOut();
8
9        if (json.status == "fail") {
10            $("#add-point .error").html(json.message).fadeIn();
11            }
12        if (json.status == "success") {
13            $("#add-point :input[name!=action]").val("");
14                var location = json.data;
15                addLocation(location);
16                zoomToBounds();
17                }
18            }, "json");
19        }

The addLocation() function below creates a map marker object and adds it to the map. It also adds the new location to the 'list' we created in the display layer above.

The zoomToBounds() function zooms to the new marker, and sets the center of the map on it.

The last function is a click event that pans the map to the marker and displays the marker label when a user clicks on one of the list item locations, or the marker itself.

view plain print about
1function addLocation(location) {
2    var point = new GLatLng(location.lat, location.lng);
3    var marker = new GMarker(point);
4        map.addOverlay(marker);
5        bounds.extend(marker.getPoint());
6                    
7        $("<li />")
8        .html(location.name)
9        .click(function(){
10        showMessage(marker, location.name);
11                        })
12
13        .appendTo("#list");
14    
15            GEvent.addListener(marker, "click", function(){
16            showMessage(this, location.name);
17            });
18        }
19
20function zoomToBounds() {
21    map.setCenter(bounds.getCenter());
22    map.setZoom(7); // map.getBoundsZoomLevel(bounds)-1
23                }
24
25$("#message").appendTo( map.getPane(G_MAP_FLOAT_SHADOW_PANE) );
26
27
28function showMessage(marker, text){
29    var markerOffset = map.fromLatLngToDivPixel(marker.getPoint());
30
31        map.panTo(marker.getLatLng());
32
33    $("#message").hide().fadeIn()
34        .css({ top:markerOffset.y, left:markerOffset.x })
35        .html(text);
36        }
37    });
38</script>

The server side code used in this example is below. I have used ColdFusion to query a database and return JSON objects to the map, but you can use any server side language.

view plain print about
1<cffunction name="accept" access="remote" output="true" returntype="void" hint="handler for google maps api">
2    <cfquery datasource="database-name">
3        INSERT INTO locations
4        (varName, varlat, varlong, varAddress)
5        VALUES(
6<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.name#">,
7<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.lat#">,
8<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.lng#">,
9<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.address#">)
10    </cfquery>
11
12    <cfset response = '{"status":"success","data":{"lat":"#arguments.lat#","lng":"#arguments.lng#","name":"#arguments.name#"}}'>
13        <cfoutput>#response#</cfoutput>
14    </cffunction>
15
16<cffunction name="listpoints" access="remote" output="true" returntype="void" hint="returns data to a Google map request">
17
18    <cfquery datasource="database-name" name="qGetMarkers">
19        SELECT intId, varName, varlat, varlong, varAddress
20        FROM locations
21        ORDER BY intId
22    </cfquery>
23
24    <cfoutput>{"Locations":[<cfloop query="qGetMarkers">{"name":"#qGetMarkers.varName#","lat":"#qGetMarkers.varlat#","lng":"#qGetMarkers.varlong#"}<cfif qGetMarkers.currentRow LT qGetMarkers.recordcount>,</cfif></cfloop>]}</cfoutput>
25    </cffunction>

ColdFusion purists will note that I'm not returning the data in the most efficient JSON way, but I'm running this code on ColdFusion server 7, so I can't specify a returnformat of JSON.

The next (and last) article in this series will bring AJAX polling, map panning and dynamically adding markers together to create the finished dynamic Google map application.

There is a full example of the finished application here: Demo of dynamically adding markers to Google maps

Related Blog Entries

TweetBacks
Comments
Back to top