7.3 ¿QUIÉN ES QUIÉN? CARACTERIZACIÓN DE LOS OFICIALES DE LA MILICIA DEL ESTADO SOBERANO DEL TOLIMA
7.4. EL PODER RELATIVO DE LOS OFICIALES DE LA MILICIA EN EL ESTADO DEL TOLIMA
7.4.1. El poder formal de los oficiales de la milicia en el gobierno del Estado: Para establecer el grado y alcance del poder formal de los oficiales de la milicia en el Estado,
The server-side clustering solution involves using the server to further analyze your requests, and it works well for high-density data sets. In this case, the server analyzes the locations you’ve requested along with their proximity, and then clusters markers to provide the maximum amount of information from the fewest number of markers. A cluster is just a normal GMarker, but it represents more than one marker within a close distance and therefore usually has a different icon.
If your data has a very high density and markers are often overlapping, you can reduce the number of markers on the map simply by combining near markers into one single cluster marker. When you zoom the map for a closer look, the cluster marker will expand into several individual markers, or more cluster markers, until the zoom is close enough that no clusters are needed. For data sets of around 1000 points, clustering can be accomplished through JavaScript on the client side, which we’ll discuss in the “Client-Side Clustering” section later in the chapter. Here, you’ll see how to cluster data on the server side when you have hundreds of thousands of points.
To initially filter your data for the request, you can use either the server-side boundary method or the server-side closest to common point method. For this example, we’ve chosen to request all the points within the viewable area of the map (the boundary method), and then we’ve applied clustering to the remaining points, as shown in Figure 7-4.
Figure 7-4. A map with clustered and single points
Combining clustering with either of the previous two methods can overcome some of their limitations. The drawback of the server-side boundary method was its limitation to a “closer to earth” zoom level. Zooming out meant that there would be too many points to dis- play at one time on the map. By clustering the points, you can zoom out and still view the map within the marker limit, but some markers will be combined until you zoom in closer.
To cluster data into common groups, you need to determine which points lay relatively close to each other, and then figure out how much clustering to apply to achieve the correct number of points. There are a variety of ways you can go about this, some simple and others much more complex. For the example here, we’ve chosen a simple method that we like to call the “grid” method.
To cluster using a grid, you take the outer boundary of the data set (for example the view- port), divide the area into equally sized grid cells, and then allocate each of your points to a cell. The size of the grid cells will determine how detailed the map data is. If you use a grid cell that is 100 pixels wide, then all markers within the 100-by-100 block will be combined into one marker. Listing 7-6 uses an incremental grid size starting with one-thirtieth of the longi- tude resolution:
$gridSize+=($nelng-$swlng)/30;
which increases if the total is still too large at the end of the loop: if(count($clustered)>$limit) continue;
By incrementing the size of the cell, you can achieve the best resolution of data for the number of points available. Figure 7-5 shows an example map with grid cells and map areas outlined.
Figure 7-5. A map showing the marked grid cells used for clustering
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
Listings 7-6 and 7-7 (http://googlemapsbook.com/chapter7/ServerCluster/) are modified versions of the server-side boundary method.
Listing 7-6. Cluster Icon Additions to Improve the Server-Side Boundary Method JavaScript var map;
var centerLatitude = 42; var centerLongitude = -72; var startZoom = 10;
//create an icon for the clusters var iconCluster = new GIcon();
iconCluster.image = "http://googlemapsbook.com/chapter7/icons/cluster.png";
iconCluster.shadow = "http://googlemapsbook.com/chapter7/icons/cluster_shadow.png"; iconCluster.iconSize = new GSize(26, 25);
iconCluster.shadowSize = new GSize(22, 20); iconCluster.iconAnchor = new GPoint(13, 25); iconCluster.infoWindowAnchor = new GPoint(13, 1); iconCluster.infoShadowAnchor = new GPoint(26, 13); //create an icon for the pins
var iconSingle = new GIcon();
iconSingle.image = "http://googlemapsbook.com/chapter7/icons/single.png";
iconSingle.shadow = "http://googlemapsbook.com/chapter7/icons/single_shadow.png"; iconSingle.iconSize = new GSize(12, 20);
iconSingle.shadowSize = new GSize(22, 20); iconSingle.iconAnchor = new GPoint(6, 20); iconSingle.infoWindowAnchor = new GPoint(6, 1); iconSingle.infoShadowAnchor = new GPoint(13, 13);
function init() {
map = new GMap2(document.getElementById("map")); map.addControl(new GSmallMapControl());
map.setCenter(new GLatLng(centerLatitude, centerLongitude), startZoom); updateMarkers(); GEvent.addListener(map,'zoomend',function() { updateMarkers(); }); GEvent.addListener(map,'moveend',function() { updateMarkers(); }); } function updateMarkers() {
//remove the existing points map.clearOverlays();
//create the boundary for the data to provide //initial filtering
var bounds = map.getBounds();
var southWest = bounds.getSouthWest(); var northEast = bounds.getNorthEast(); var getVars = 'ne=' + northEast.toUrlValue() + '&sw=' + southWest.toUrlValue()
//log the URL for testing
GLog.writeUrl('server.php?'+getVars); //retrieve the points
var request = GXmlHttp.create();
request.open('GET', 'server.php?'+getVars, true); request.onreadystatechange = function() {
if (request.readyState == 4) {
var jscript = request.responseText; var points;
eval(jscript);
//create each point from the list for (i in points) {
var point = new GLatLng(points[i].lat,points[i].lng); var marker = createMarker(point,points[i].type); map.addOverlay(marker); } } } request.send(null); }
function createMarker(point, type) {
//create the marker with the appropriate icon if(type=='c') {
var marker = new GMarker(point,iconCluster,true); } else {
var marker = new GMarker(point,iconSingle,true); } return marker; } window.onload = init; 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 164
Listing 7-7. Cluster Additions to Improve the Server-Side Boundary Method PHP Script <?php
//This script may require additional memory ini_set('memory_limit',8388608 * 10); //retrieve the variables from the GET vars list($nelat,$nelng) = explode(',',$_GET['ne']); list($swlat,$swlng) = explode(',',$_GET['sw']); //clean the data
$nelng = (float)$nelng; $swlng = (float)$swlng; $nelat = (float)$nelat; $swlat = (float)$swlat; //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 problem.
*/
$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");
//extract all the points from the result into an array $list = array();
$row = mysql_fetch_assoc($result); while($row)
{
//use 'm' to indicate this is a regular (m)arker $list[] = array($row['lat'],$row['lng'],'m'); $row = mysql_fetch_assoc($result);
//close the SQL connection mysql_close($conn); //limit to 30 markers $limit = 30; $gridSize = 0; $listRemove = array(); while(count($list)>$limit) {
//grid size in pixels. if the first pass fails to reduce the //number of markers below the limit, the grid will increase //again and redo the loop.
$gridSize += ($nelng-$swlng)/30; $clustered = array();
reset($list);
//loop through the $list and put each one in a grid square while(list($k,$v) = each($list)) {
//calculate the y position based on the latitude: $v[0] $y = floor(($v[0]-$swlat)/$gridSize);
//calculate the x position based on the longitude: $v[1] $x = floor(($v[1]-$swlng)/$gridSize);
//use the x and y values as the key for the array and append //the points key to the clustered array
$clustered["{$x},{$y}"][] = $k; }
//check if we're below the limit and if not loop again if(count($clustered)>$limit) continue;
//reformat the list array $listRemove = array();
while(list($k,$v) = each($clustered)) {
//only merge if there is more than one marker in a cell if(count($v)>1) {
//create a list of the merged markers $listRemove = array_merge($listRemove,$v); //add a cluster marker to the list
$clusterLat = $list[$v[0]][0]; $clusterLng = $list[$v[0]][1];
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
//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.