|
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.
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.
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.
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.
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.
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!).
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.
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.
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









There are no comments for this entry.
[Add Comment] [Subscribe to Comments]