+44 (0) 7918 168 992

Online Solutions Development Blog   |  

RSS

Google Maps MarkerClusterer and Custom Markers

posted by ,
Categories: Ajax, HTML, JavaScript, PHP
Taggs , , , , ,

The last article I’ve wrote was about Dynamic jQuery Slider search. In this article I will search within some estates using a zip code and a slider for radius and in the end show the results in a map (grouping them in clusters). The final result will look similar with the following image:

search_screenshot

We will use a HTML file, a Javascript file and a PHP file requested with AJAX, just as in the previous articles, Dynamic jQuery Datepicker calendar and Dynamic jQuery Slider search.

1. In the HTML file we will add the jQuery libraries and the stylesheets.

<!-- stylesheets -->
<link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery-ui.css" rel="stylesheet" type="text/css"/>
<link rel="stylesheet" href="http://static.jquery.com/ui/css/demo-docs-theme/ui.theme.css" type="text/css" media="all"/>

<!-- js files -->
<script type="mce-text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5/jquery.min.js"></script>
<script type="mce-text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js"></script>
<script type="mce-text/javascript" src="http://www.google.com/jsapi"></script>
<script type="mce-text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="mce-text/javascript" src="http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/src/markerclusterer_compiled.js"></script>
<script type="mce-text/javascript" src="script.js"></script>

As I said in the previous article, we need a div tag for the slider (radius_slider), a label (or, if you prefer, a span) to show the results from it in a nice format (radius_label) and a input that stores the actual result from the slider (with the ID the_radius).
Because the search with a radius would be useless without a zip code, we will need a input for a zip code (with the ID the_zipcode). Same as the slider, the map will need a container in which it will be displayed (with the ID map_canvas).

<form>
	<label for="zipcode">Zipcode:</label>
	<input id="the_zipcode" type="text" name="the_zipcode" value="" />
	<label id="radius_label">Radius:<strong></strong></label>
	<input id="the_radius" type="hidden" name="the_radius" value="" />
	<div id="radius_slider"></div>
	<div id="map_canvas"></div>
</form>

2. The JavaScript file (as I said in the previous articles, you can also put this code in the html file between the “<script>” tags).
This script will have 3 parts:

  • The first part is getting the locations with zip codes that match the search criteria (the given radius and zip code). This part makes the AJAX call to the PHP file to get the desired locations, the average longitude and average latitude (we will need this 2 at the initialization of the map).
    function get_locations(the_radius, zipcode)
    {
    	var result = new Array();
    	$.ajax(
    	{
    		type: "POST",
    		url: 'search.php',
    		dataType: 'json',
    		data: {'radius': the_radius, 'zipcode':zipcode},
    		async: false,
    		success: function(data)
    		{
    			result = data;
    		}
    	});
    	return result;
    }
    
  • The second part consists of initialization of the map.
    google.load('maps', '3',
    {
    	other_params: 'sensor=false'
    });
    google.setOnLoadCallback(initialize);
    
    function initialize()
    {
    	var the_radius 	= $('#the_radius').val();
    	var zipcode 	= $('#the_zipcode').val();
    	var ajax_result	= get_locations(the_radius, zipcode);
    
    	if(ajax_result[0] != "none")
    	{
    		var locations 	= ajax_result[0]; // The locations
    		var med_lat 	= ajax_result[1]; // Average latitude of all locations
    		var med_long 	= ajax_result[2]; // Average longitude of all locations
    
    		// Set the center of the map
    		var myLatlng = new google.maps.LatLng(med_lat, med_long);
    		// Initialize the map
    		var myOptions = {
    			zoom: 10,
    			center: myLatlng,
    			mapTypeId: google.maps.MapTypeId.ROADMAP
    		}
    
    		// Set the map to a desired div
    		map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
    		// Initialize the markers array - is used to make the cluster
    		var markers = [];
    		// Parse the array with the locations
    		for(id in locations)
    		{
    			// Set a maxWitdh to the window (optional)
    			var infoWindow = new google.maps.InfoWindow({maxWidth : 200});
    
    			// Set the coordonates of the new point
    			var latLng = new google.maps.LatLng(locations[id].estate_lat, locations[id].estate_long);
    
    			// Initialize the new marker
    			var marker = new google.maps.Marker(
    			{
    				position: latLng,
    				map: map,
    				title: locations[id].estate_name,
    				icon:'small_icon.png'
    			});
    
    			// The HTML that is shown in the window of each item (when the icon it's clicked)
    			var html = "<div><h3>" + locations[id].estate_name + "</h3>Zipcode: " + locations[id].estate_zipcode + "</div>";
    
    			// Binds the infoWindow to the point
    			bindInfoWindow(marker, map, infoWindow, html);
    			// Add the marker to the array
    			markers.push(marker);
    		}
    		// Make a cluster with the markers from the array
    		var markerCluster = new MarkerClusterer(map, markers);
    	}
    	else
    	{
    		alert('No estates found!');
    	}
    
    }
    

    This part is the most difficult one. As I said on the previous part of the JavaScript file, I need to set the center of the map. For that we will create a “point” with a custom latitude and longitude and set that as the center of the map. I chose to work with ROADMAP map type and put the map in a div named “map_canvas”. You can see the other types on the Google Maps JavaScript API v3 official page.
    Next we need to parse the location array and put each location in a marker. Each marker will have a title (it’s optional) and a custom icon (“small_icon.png” – it’s also optional).

    Here we also make a little HTML code for each of the windows that will open when clicking on the icon.

    In the end we need to add the markers array in a cluster (if there are a lot of items in the same place, they will be grouped under a cluster and the number of items in that cluster will be shown instead of a conglomaration of icons).

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

    The window it’s opening when the icon is clicked. You can add other events if you need to. You can find the list of the available events here: https://developers.google.com/maps/documentation/javascript/events.

  • The initialization of the radius is similar as the one from the previous article. For more details, please read the Dynamic jQuery Slider search.
    Here we also added a event to the zipcode input so each time this changes, we reinitialize the map with the new zipcode.

    $(document).ready(function()
    {
    	$( "#radius_slider" ).slider({
    		range: "min",
    		min: 1,
    		max: 100,
    		step:1,
    		value: 1,
    		slide: function( event, ui )
    		{
    			// set the radius value
    			$( "#the_radius" ).val(ui.value);
    			// set the label with the new radius - Only for visual effect
    			$( "#radius_label" ).html("Radius: <strong>" + ui.value + "km </strong>");
    		},
    		stop: function( event, ui ) {
    			// reinitialize the map only when the user stopped sliding
    			initialize();
    		},
    	});
    	$('#the_zipcode').change(function()
    	{
    		// reinitialize the map when the zipcode changes
    		initialize();
    	});
    });
    

3. In the AJAX call from the JavaScript file, we have a PHP document requested. For this example I’ve chose to search the estates that have the zipcode in the given radius. First of all, for the give zipcode, we need to find the latitude and longitude. Only for the purpose of this example, I’ve used Google Geolocation API V3, but you can use a Geolocation table or whatever is suitable for you.

function get_coordinates($zipcode)
{
	$the_result = array();

	$url = "http://maps.googleapis.com/maps/api/geocode/xml?address=" . urlencode($zipcode.",FR") . "&sensor=true";
	$xml = simplexml_load_file($url);
	$the_result['LONGITUDE'] 	= '';
	$the_result['LATITUDE'] 	= '';

	if($xml->result->geometry->location->lng)
	{
		$the_result['LONGITUDE'] 	= (string) $xml->result->geometry->location->lng[0];
	}
	if($xml->result->geometry->location->lat)
	{
		$the_result['LATITUDE'] 	= (string) $xml->result->geometry->location->lat[0];
	}

	return $the_result;
}

For this example, I’ve used a table defined as:

CREATE TABLE IF NOT EXISTS `Estates` (
  `estate_id` int(11) NOT NULL AUTO_INCREMENT,
  `estate_zipcode` varchar(10) NOT NULL,
  `estate_long` double NOT NULL,
  `estate_lat` double NOT NULL,
  `estate_name` varchar(255) NOT NULL,
  PRIMARY KEY (`estate_id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1;

To search the estates with the zip code that match your criteria you can use the following code:

if(isset($_POST['zipcode']) && isset($_POST['radius']))
{
	$estates_results = array();

	$zipcode	= filter_var($_POST['zipcode']	, FILTER_SANITIZE_STRING);
	$radius		= filter_var($_POST['radius']	, FILTER_VALIDATE_INT);
	$med_lat 	= 0;
	$med_long 	= 0;

	// get the coordinates of the given zipcode
	$estate_coordinates = get_coordinates($zipcode);
	$long = $estate_coordinates['LONGITUDE'];
	$lat = $estate_coordinates['LATITUDE'];

	if($long && $lat)
	{
		$query	= "
					SELECT
						estate_lat,
						estate_long,
						estate_name,
						estate_zipcode,
						((ACOS(SIN( ".$lat." * PI() / 180) * SIN(estate_lat * PI() / 180) + COS(".$lat." * PI() / 180) * COS(estate_lat * PI() / 180) * COS((".$long." - estate_long) * PI() / 180)) * 180 / PI()) * 60 * 1.1515) AS distance
					FROM
						Estates
					HAVING
						distance <= :radius 					"; 		$stmt = $dbh->prepare($query);

		try
		{
			$stmt->bindParam(':radius', $radius);
			$stmt->execute();
		}
		catch (PDOException $e)
		{
			print($e->getMessage());
			die;
		}

		while($row = $stmt->fetch(PDO::FETCH_ASSOC))
		{
			$estates_results[] = $row;
		}

		if(count($estates_results))
		{
			// we need to make a sum of the latitudes and the longitudes of the estates that are in the given distance from the given zipcode
			foreach($estates_results as $key=>$value)
			{
				$med_lat += $value['estate_lat'];
				$med_long += $value['estate_long'];
			}

			$result[0] = $estates_results; // this is going to be the location array
			$result[1] = $med_lat/count($estates_results); // this is going to be the average latitude (used for centering the map)
			$result[2] = $med_long/count($estates_results); // this is going to be the average longitude (also used for centering the map)
		}
		else
		{
			$result[0] = 'none';
		}
	}
}
echo json_encode($result);

You can download an archive with the code here.
If you have questions or suggestions please write them in a comment bellow.

If you liked this post
you can buy me a beer

Add a Comment

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Also, if you want to display source code you can enclose it between [html] and [/html], [js] and [/js], [php] and [/php] etc