• Tidak ada hasil yang ditemukan

Custom Detail Overlay Method

//use 'c' to indicate this is a (c)luster marker

$list[] = array($clusterLat,$clusterLng,'c');

} }

//unset all the merged pins

//reverse to start with highest key rsort($listRemove);

while(list($k,$v) = each($listRemove)) { unset($list[$v]);

}

//we're done!

break;

}

reset($list);

$json = array();

while(list($key,$values) = each($list)) {

$i++;

$json[] = "p{$i}:{lat:{$values[0]},lng:{$values[1]},type:'{$values[2]}'}";

}

//echo back the JavaScript object header('content-type:text/plain;');

echo "var points = {\n\t".join(",\n\t",$json)."\n}";

?>

These are good starting points for your clustering script. To make it even better, you could make some improvements. For example, you could calculate an average position of the mark- ers within one grid cell so that the cluster marker better represents the actual location of the points in that cell. You could also develop an algorithm that would allow you to cluster based on relative positions, so only dense groups would cluster rather than the entire page.

The advantages of the cluster method are that it isn’t restricted to zoom levels and it works for any sized data set. Its disadvantage is that the data is clustered over possibly large areas, so you will still need to zoom in for more detail.

opens up a realm of possibilities for creating overlays such as simple shapes or even your own info window object. Here, we present the possibility of including a detail overlay for a speci- fied area of the map.

The custom overlay you create can contain any information you want. For example, the Google Maps API documentation gives the example of aRectangleoverlay, as listed in Listing 7-8 (from http://www.google.com/apis/maps/documentation/#Custom_Overlays).

Listing 7-8. Google’s Example Rectangle Overlay

// A Rectangle is a simple overlay that outlines a lat/lng bounds on the // map. It has a border of the given weight and color and can optionally // have a semi-transparent background color.

function Rectangle(bounds, opt_weight, opt_color) { this.bounds_ = bounds;

this.weight_ = opt_weight || 2;

this.color_ = opt_color || "#888888";

}

Rectangle.prototype = new GOverlay();

// Creates the DIV representing this rectangle.

Rectangle.prototype.initialize = function(map) { // Create the DIV representing our rectangle var div = document.createElement("div");

div.style.border = this.weight_ + "px solid " + this.color_;

div.style.position = "absolute";

// Our rectangle is flat against the map, so we add our selves to the // MAP_PANE pane, which is at the same z-index as the map itself (i.e., // below the marker shadows)

map.getPane(G_MAP_MAP_PANE).appendChild(div);

this.map_ = map;

this.div_ = div;

}

// Remove the main DIV from the map pane Rectangle.prototype.remove = function() {

this.div_.parentNode.removeChild(this.div_);

}

// Copy our data to a new Rectangle Rectangle.prototype.copy = function() {

return new Rectangle(this.bounds_, this.weight_, this.color_, this.backgroundColor_, this.opacity_);

}

// Redraw the rectangle based on the current projection and zoom level Rectangle.prototype.redraw = function(force) {

C H A P T E R 7■ O P T I M I Z I N G A N D S C A L I N G F O R L A R G E D ATA S E T S 168

// We only need to redraw if the coordinate system has changed if (!force) return;

// Calculate the DIV coordinates of two opposite corners of our bounds to // get the size and position of our rectangle

var c1 = this.map_.fromLatLngToDivPixel(this.bounds_.getSouthWest());

var c2 = this.map_.fromLatLngToDivPixel(this.bounds_.getNorthEast());

// Now position our DIV based on the DIV coordinates of our bounds this.div_.style.width = Math.abs(c2.x - c1.x) + "px";

this.div_.style.height = Math.abs(c2.y - c1.y) + "px";

this.div_.style.left = (Math.min(c2.x, c1.x) - this.weight_) + "px";

this.div_.style.top = (Math.min(c2.y, c1.y) - this.weight_) + "px";

}

function load() {

if (GBrowserIsCompatible()) {

var map = new GMap2(document.getElementById("map"));

map.addControl(new GSmallMapControl());

map.addControl(new GMapTypeControl());

map.setCenter(new GLatLng(37.4419, -122.1419), 13);

// Display a rectangle in the center of the map at about a quarter of // the size of the main map

var bounds = map.getBounds();

var southWest = bounds.getSouthWest();

var northEast = bounds.getNorthEast();

var lngDelta = (northEast.lng() - southWest.lng()) / 4;

var latDelta = (northEast.lat() - southWest.lat()) / 4;

var rectBounds = new GLatLngBounds(

new GLatLng(southWest.lat() + latDelta, southWest.lng() + lngDelta), new GLatLng(northEast.lat() - latDelta,

northEast.lng() - lngDelta));

map.addOverlay(new Rectangle(rectBounds));

} }

window.onload = load;

The Rectangleoverlay simply creates adivobject on the map and applies a border to it. To create a detail overlay, you can use the Rectangleobject in Listing 7-8, but add one additional property to the div: a background image. The background image can contain any information you want, from pictures and icons to lines and shapes, and can be created on the fly using a server-side script. The new custom detail overlay can then be placed on the map in the appropriate area on top of the existing Google Maps tiles.

Using an overlay is best for data sets that are high density but cover a relatively small portion of the map. If your data set contains hundreds of millions of points, creating the overlay is going

to take some time, and your application will still feel sluggish. If you have massive data sets spread across the world, you’ll need to use custom tiles, which we’ll discuss in the next section.

For the custom detail overlay example, suppose you want to mark all the FCC tower loca- tions in Hawaii, as you did in Chapter 6. There are about 286 towers—too many for one map using just the GMarkerobject. Using a custom overlay, you can simply create a transparent GIF or PNG that covers all of Hawaii and mark each of the locations in whatever way you like. You can even add text, shapes, or photos. What you include in your image is up to you.

Listing 7-9 shows the client-side JavaScript for the custom overlay method.

Listing 7-9. Client-Side JavaScript for the Custom Overlay Method var map;

var centerLatitude = 19.9;

var centerLongitude = -156;

var startZoom = 7;

//create the Detail overlay object

function Detail(bounds, opt_weight, opt_color) { this.bounds_ = bounds;

this.weight_ = opt_weight || 2;

this.color_ = opt_color || "#000";

}

Detail.prototype = new GOverlay();

Detail.prototype.initialize = function(map) { //create the div representing the Detail var div = document.createElement("div");

div.style.border = this.weight_ + "px dotted " + this.color_;

div.style.position = "absolute";

//the Detail is flat against the map, so we add it to the

//MAP_PANE pane, which is at the same z-index as the map itself (i.e., //below the marker shadows)

map.getPane(G_MAP_MAP_PANE).appendChild(div);

this.map_ = map;

this.div_ = div;

//load the background image this.loadBackground();

}

Detail.prototype.remove = function() {

this.div_.parentNode.removeChild(this.div_);

}

Detail.prototype.copy = function() {

return new Detail(this.bounds_, this.weight_, this.color_, C H A P T E R 7■ O P T I M I Z I N G A N D S C A L I N G F O R L A R G E D ATA S E T S

170

this.backgroundColor_, this.opacity_);

}

Detail.prototype.redraw = function(force) { if (!force) return;

this.bounds_ = this.map_.getBounds();

var c1 = this.map_.fromLatLngToDivPixel(this.bounds_.getSouthWest());

var c2 = this.map_.fromLatLngToDivPixel(this.bounds_.getNorthEast());

this.div_.style.width = Math.abs(c2.x - c1.x) + "px";

this.div_.style.height = Math.abs(c2.y - c1.y) + "px";

this.div_.style.left = (Math.min(c2.x, c1.x) - this.weight_) + "px";

this.div_.style.top = (Math.min(c2.y, c1.y) - this.weight_) + "px";

//the position or zoom has changed so reload the background image this.loadBackground();

}

Detail.prototype.loadBackground = function() { //retrieve the bounds of the detail area var southWest = this.bounds_.getSouthWest();

var northEast = this.bounds_.getNorthEast();

//determine the pixel position of the corners

var swPixels = this.map_.fromLatLngToDivPixel(this.bounds_.getSouthWest());

var nePixels = this.map_.fromLatLngToDivPixel(this.bounds_.getNorthEast());

//send the lat/lng as well as x/y and zoom to the server var getVars = 'ne=' + northEast.toUrlValue()

+ '&sw=' + southWest.toUrlValue()

+ '&nePixels=' + nePixels.x + ',' + nePixels.y + '&swPixels=' + swPixels.x + ',' + swPixels.y + '&z=' + this.map_.getZoom()

+ '';

//log the URL for testing

GLog.writeUrl('server.php?'+getVars);

//set the background image of the div

this.div_.style.background='transparent url(server.php?'+getVars+')';

}

function init() {

map = new GMap2(document.getElementById("map"));

map.addControl(new GSmallMapControl());

map.setCenter(new GLatLng(centerLatitude, centerLongitude), startZoom);

var bounds = map.getBounds();

map.addOverlay(new Detail(bounds));

}

window.onload = init;

Tip

For examples of the mathematical formulas for different maps such as the Mercator projection maps, visit MathWorld at http://mathworld.wolfram.com/MercatorProjection.html.

Looking at Listing 7-9, you can see the Rectangleobject renamed to Detailand the addi- tion of aloadBackgroundmethod, which modifies the backgroundstyle property of the Detail object:

Detail.prototype.loadBackground = function() { //retrieve the bounds of the detail area var southWest = this.bounds_.getSouthWest();

var northEast = this.bounds_.getNorthEast();

//determine the pixel position of the corners

var swPixels = this.map_.fromLatLngToDivPixel(this.bounds_.getSouthWest());

var nePixels = this.map_.fromLatLngToDivPixel(this.bounds_.getNorthEast());

var getVars = 'ne=' + northEast.toUrlValue() + '&sw=' + southWest.toUrlValue()

+ '&nePixels=' + nePixels.x + ',' + nePixels.y + '&swPixels=' + swPixels.x + ',' + swPixels.y + '&z=' + this.map_.getZoom()

+ '';

this.div_.style.background='transparent url(server.php?'+getVars+')';

}

When loading your background image, you’ll need to include several variables for your server-side script, including the northeast and southwest corners in latitude and longi- tude, as well as the northeast and southwest corners in pixel values. You also need to pass the current zoom level for the map. This will allow you to perform the necessary calculations on the server side and also allow you to modify your image, depending on how far your users have zoomed in on the map. You can then use the server-side script in Listing 7-10 to create the appropriately sized image with the appropriate information for the boundary. For the example in Listing 7-10 (http://googlemapsbook.com/chapter7/ServerCustomOverlay/), we’ve C H A P T E R 7■ O P T I M I Z I N G A N D S C A L I N G F O R L A R G E D ATA S E T S

172

chosen to create a GIF with a small circle marking each tower location within the northeast and southwest boundary.

Listing 7-10. Server-Side PHP for the Custom Overlay Method

<?php

//retrieve the variables from the GET vars list($nelat,$nelng) = explode(',',$_GET['ne']);

list($swlat,$swlng) = explode(',',$_GET['sw']);

list($neX,$neY) = explode(',',$_GET['nePixels']);

list($swX,$swY) = explode(',',$_GET['swPixels']);

//clean the data

$nelng = (float)$nelng;

$swlng = (float)$swlng;

$nelat = (float)$nelat;

$swlat = (float)$swlat;

$w = (int)abs($neX - $swX);

$h = (int)abs($neY - $swY);

$z = (int)$_GET['z'];

//connect to the database

require($_SERVER['DOCUMENT_ROOT'] . '/db_credentials.php');

$conn = mysql_connect("localhost", $db_name, $db_pass);

mysql_select_db("googlemapsbook", $conn);

/*

* Retrieve the points within the boundary of the map.

* For the FCC data, all the points are within the US so we

* don't need to worry about the meridian.

*/

$result = mysql_query(

"SELECT

longitude as lng,latitude as lat,struc_height,struc_elevation FROM

fcc_towers WHERE

(longitude > $swlng AND longitude < $nelng) AND (latitude <= $nelat AND latitude >= $swlat) ORDER BY

lat");

$count = mysql_num_rows($result);

//calculate the Mercator coordinate position of the top //latitude and normalize from 0-1

$mercTop = 0.5-(asinh(tan(deg2rad($nelat))) / M_PI / 2);

//calculate the scale and y position on the Google Map

$scale = (1 << ($z)) * 256;

$yTop = $mercTop * $scale;

//calculate the pixels per degree of longitude

$lngSpan = $nelng-$swlng;

$pixelsPerDegLng = abs($w/$lngSpan);

//create the image

$im = imagecreate($w,$h);

$trans = imagecolorallocate($im,0,0,255);

$black = imagecolorallocate($im,0,0,0);

$white = imagecolorallocate($im,255,255,255);

imagefill($im,0,0,$trans);

imagecolortransparent($im, $trans);

//label the number of points for testing

imagestring($im,1,0,0,$count.' points in this area:',$black);

$row = mysql_fetch_assoc($result);

while($row) {

extract($row);

$lng = $row['lng'];

$lat = $row['lat'];

$x = ceil(abs($lng-$swlng)*$pixelsPerDegLng);

//calculate the mercator cordinate position of this point //latitude and normalize from 0-1

$yMerc = 0.5-(asinh(tan(deg2rad($lat))) / M_PI / 2);

//calculate the y position on the Google Map

$yMap = $yMerc * $scale;

//calculate the y position in the overlay

$y = $yMap-$yTop;

//draw the marker, a dot in this case

imagefilledellipse($im, $x, $y, $z+1, $z+1, $black );

imageellipse($im, $x, $y, $z+1, $z+1, $white );

$row = mysql_fetch_assoc($result);

}

//echo a GIF

header('content-type:image/gif;');

imagegif($im);

?>

C H A P T E R 7■ O P T I M I Z I N G A N D S C A L I N G F O R L A R G E D ATA S E T S 174