Add support for ClusterMarker and MarkerManager (without the G) marker managers. 5.x-1.1-rc1
authorBrandon Bergren
Fri, 13 Feb 2009 00:01:52 +0000 (00:01 +0000)
committerBrandon Bergren
Fri, 13 Feb 2009 00:01:52 +0000 (00:01 +0000)
Fix #155104 by Scott Falconer: Add "Zoom to cluster area" support.

Allow falling back to the default Clusterer marker if desired.

Allow entering larger numbers in some marker manager settings.

Allow choosing between the original and the new "zoom to marker area" behavior on Clusterer.

gmap.install
gmap.module
gmap_settings_ui.inc
js/clusterer_marker.js
js/clustermarker_marker.js [new file with mode: 0644]
js/markermanager_marker.js [new file with mode: 0644]
thirdparty/README.txt

index c6d167b..8fbcba0 100644 (file)
@@ -85,3 +85,18 @@ function gmap_update_5002() {
   variable_del('gmap_load_zoom_plugin');
   return $ret;
 }
+
+/**
+ * Mini-update -- slight tweak to marker manager settings format.
+ */
+function gmap_update_5003() {
+  $ret = array();
+  $val = variable_get('gmap_markermanager', array());
+  if (isset($val['clusterer']) && isset($val['clusterer']['clusterer_file']) && !empty($val['clusterer']['clusterer_file'])) {
+    $val['clusterer']['filename'] = $val['clusterer']['clusterer_file'];
+    unset($val['clusterer']['clusterer_file']);
+    variable_set('gmap_markermanager', $val);
+  }
+  drupal_set_message(t('Border Padding on marker managers is now defaulting to the recommended 256 pixels, rather than 0 pixels. You may wish to change your marker manager settings on the <a href="@url">GMap settings page</a> to match.', array('@url' => url('admin/settings/gmap'))));
+  return $ret;
+}
index 7d0db6e..2c41b30 100644 (file)
@@ -248,21 +248,24 @@ function _gmap_doheader() {
   drupal_add_css($gmap_path .'/gmap.css');
   drupal_add_js($gmap_path .'/js/gmap.js');
   $mm = variable_get('gmap_mm_type', 'gmap');
+  $mms = variable_get('gmap_markermanager', array());
+  if (empty($mms[$mm])) {
+    $mms[$mm] = array();
+  }
   // If you really really want to override the marker manager, implement
   // this, take $mm by ref, and have fun. --Bdragon
   if (function_exists('_gmap_markermanager_override')) {
-    _gmap_markermanager_override($mm);
+    _gmap_markermanager_override($mm, $mms);
   }
-  if ($mm=='clusterer') {
+  if ($mm == 'clusterer' || $mm == 'clustermarker') {
+    // Needed for access to clusterer marker.
     drupal_add_js($gmap_path .'/js/icon.js');
-    drupal_add_js($gmap_path .'/thirdparty/Clusterer2.js');
+  }
+  if (isset($mms[$mm]['filename'])) {
+    drupal_add_js($gmap_path .'/thirdparty/'. $mms[$mm]['filename']);
   }
   drupal_add_js($gmap_path .'/js/marker.js');
   drupal_add_js($gmap_path .'/js/'. $mm .'_marker.js');
-  $mms = variable_get('gmap_markermanager', array());
-  if (empty($mms[$mm])) {
-    $mms[$mm] = array();
-  }
   drupal_add_js(array('gmap_markermanager' => $mms[$mm]), 'setting');
 // @@@
 drupal_add_js($gmap_path .'/js/poly.js');
index cccf2fc..e1d99b5 100644 (file)
@@ -272,30 +272,56 @@ function _gmap_admin_settings() {
   if (!isset($opts['gmarkermanager']) || !is_array($opts['gmarkermanager'])) {
     $opts['gmarkermanager'] = array();
   }
+  if (!isset($opts['markermanager']) || !is_array($opts['markermanager'])) {
+    $opts['markermanager'] = array();
+  }
   if (!isset($opts['clusterer']) || !is_array($opts['clusterer'])) {
     $opts['clusterer'] = array();
   }
+  if (!isset($opts['clustermarker']) || !is_array($opts['clustermarker'])) {
+    $opts['clustermarker'] = array();
+  }
 
   $opts['gmap'] = array_merge(array(
     // None.
   ), $opts['gmap']);
 
   $opts['gmarkermanager'] = array_merge(array(
-    'borderPadding' => 0,
+    'borderPadding' => 256,
     'maxZoom' => 4,
     'trackMarkers' => FALSE,
     'markerMinZoom' => 4,
     'markerMaxZoom' => 0,
   ), $opts['gmarkermanager']);
 
+  $opts['markermanager'] = array_merge(array(
+    'filename' => 'markermanager_packed.js',
+    'borderPadding' => 256,
+    'maxZoom' => 4,
+    'trackMarkers' => FALSE,
+    'markerMinZoom' => 4,
+    'markerMaxZoom' => 0,
+  ), $opts['markermanager']);
+
   $opts['clusterer'] = array_merge(array(
-    'clusterer_file' => 'Clusterer2.js',
+    'filename' => 'Clusterer2.js',
     'marker' => 'cluster',
     'max_nocluster' => 150,
     'cluster_min' => 5,
     'max_lines' => 10,
+    'popup_mode' => 'orig',
   ), $opts['clusterer']);
 
+  $opts['clustermarker'] = array_merge(array(
+    'filename' => 'ClusterMarker.js',
+    'borderPadding' => 256,
+    'clusteringEnabled' => TRUE,
+    'clusterMarkerIcon' => '',
+    'clusterMarkerTitle' => '',
+    'fitMapMaxZoom' => 0,
+    'intersectPadding' => 0,
+  ), $opts['clustermarker']);
+
   $form['gmap_markermanager'] = array(
     '#type' => 'fieldset',
     '#title' => t('Marker manager'),
@@ -308,30 +334,37 @@ function _gmap_admin_settings() {
     '#options' => array(
       'gmap' => t('No manager (use GMap API directly)'),
       'gmarkermanager' => t("Google's GMarkerManager"),
+      'markermanager' => t('Gmaps Utility Library MarkerManager'),
       'clusterer' => t("Jef Poskanzer's Clusterer"),
+      'clustermarker' => t("Martin Pearman's ClusterMarker"),
     ),
     '#default_value' => variable_get('gmap_mm_type', 'gmap'),
     '#description' => t('If you are planning on using many markers on a single map, you may want to consider using a marker manager to speed up map rendering.'),
   );
 
+  // No Manager
   $form['gmap_markermanager']['gmap'] = array(
     '#type' => 'fieldset',
     '#title' => t('Unmanaged marker settings'),
     '#description' => t('There are no settings for unmanaged markers.'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
   );
 
+  // Google Maps API GMarkerManager
   $form['gmap_markermanager']['gmarkermanager'] = array(
     '#type' => 'fieldset',
     '#title' => t('GMarkerManager settings'),
     '#description' => t('GMarkerManager is a new part of the official Google Maps API that provides a marker manager.'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
   );
-
   $form['gmap_markermanager']['gmarkermanager']['borderPadding'] = array(
     '#type' => 'textfield',
     '#title' => t('Border padding'),
     '#description' => t('Markers located less than this number of pixels from the viewport will be added to the map by the manager (even if they would be fully invisible.)'),
-    '#size' => 3,
-    '#maxlength' => 3,
+    '#size' => 4,
+    '#maxlength' => 4,
     '#default_value' => $opts['gmarkermanager']['borderPadding'],
   );
   $form['gmap_markermanager']['gmarkermanager']['maxZoom'] = array(
@@ -360,7 +393,6 @@ function _gmap_admin_settings() {
     '#default_value' => $opts['gmarkermanager']['markerMinZoom'],
     '#parents' => array('gmap_markermanager', 'gmarkermanager', 'markerMinZoom'),
   );
-
   $form['gmap_markermanager']['gmarkermanager']['defaults']['markerMaxZoom'] = array(
     '#type' => 'select',
     '#title' => t('Maximum zoom'),
@@ -370,16 +402,77 @@ function _gmap_admin_settings() {
     '#parents' => array('gmap_markermanager', 'gmarkermanager', 'markerMaxZoom'),
   );
 
+  // Gmaps Utility Library -- MarkerManager
+  $form['gmap_markermanager']['markermanager'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('MarkerManager settings'),
+    '#description' => t('MarkerManager is the improved open source version of GMarkerManager. To use, you must download it from <a href="@url">here</a> and place it in the <em>thirdparty</em> folder.', array('@url' => 'http://gmaps-utility-library.googlecode.com/svn/trunk/markermanager/release/src/')),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  );
+  $form['gmap_markermanager']['markermanager']['filename'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Filename'),
+    '#description' => t('Name of downloaded file in the thirdparty folder. Default: %default', array('%default' => 'markermanager_packed.js')),
+    '#default_value' => $opts['markermanager']['filename'],
+  );
+  $form['gmap_markermanager']['markermanager']['borderPadding'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Border padding'),
+    '#description' => t('Markers located less than this number of pixels from the viewport will be added to the map by the manager (even if they would be fully invisible.)'),
+    '#size' => 4,
+    '#maxlength' => 4,
+    '#default_value' => $opts['markermanager']['borderPadding'],
+  );
+  $form['gmap_markermanager']['markermanager']['maxZoom'] = array(
+    '#type' => 'select',
+    '#title' => t('Maximum zoom'),
+    '#options' => drupal_map_assoc(range(0, 17)),
+    '#description' => t('At the specified zoom level and above, the marker manager will disable itself for additional speed.'),
+    '#default_value' => $opts['markermanager']['maxZoom'],
+  );
+  $form['gmap_markermanager']['markermanager']['trackMarkers'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Track markers'),
+    '#description' => t('If enabled, the marker manager will track marker movements. Leave off unless you need to move markers around with setPoint.'),
+    '#default_value' => $opts['markermanager']['trackMarkers'],
+  );
+  $form['gmap_markermanager']['markermanager']['defaults'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Marker defaults'),
+    '#description' => t('Default marker-specific settings for MarkerManager. Markers will appear when the current zoom level is between minZoom and maxZoom.'),
+  );
+  $form['gmap_markermanager']['markermanager']['defaults']['markerMinZoom'] = array(
+    '#type' => 'select',
+    '#title' => t('Minimum zoom'),
+    '#options' => drupal_map_assoc(range(0, 17)),
+    '#description' => t('At the specified zoom level and above (i.e. zooming in,) the marker will be shown. Choose 0 to show markers at all zoom levels by default.'),
+    '#default_value' => $opts['markermanager']['markerMinZoom'],
+    '#parents' => array('gmap_markermanager', 'markermanager', 'markerMinZoom'),
+  );
+
+  $form['gmap_markermanager']['markermanager']['defaults']['markerMaxZoom'] = array(
+    '#type' => 'select',
+    '#title' => t('Maximum zoom'),
+    '#options' => drupal_map_assoc(range(0, 17)),
+    '#description' => t('At the specified zoom level and above (i.e. zooming in,) the marker will be hidden. Choose 0 to disable by default.'),
+    '#default_value' => $opts['markermanager']['markerMaxZoom'],
+    '#parents' => array('gmap_markermanager', 'markermanager', 'markerMaxZoom'),
+  );
+
+  // Jef Poskanzer's Clusterer
   $form['gmap_markermanager']['clusterer'] = array(
     '#type' => 'fieldset',
     '#title' => t('Clusterer settings'),
     '#description' => t("Clusterer is a marker manager written by Jef Poskanzer of acme.com. To use, you must place Clusterer2.js (available ".'<a href="@url">here</a>) into the "thirdparty" folder.', array('@url' => 'http://acme.com/javascript/Clusterer2.js')),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
   );
-  $form['gmap_markermanager']['clusterer']['clusterer_file'] = array(
+  $form['gmap_markermanager']['clusterer']['filename'] = array(
     '#type' => 'textfield',
-    '#title' => t('Clusterer filename'),
-    '#description' => t('Set the name of the Clusterer file in the thirdparty folder.'),
-    '#default_value' => $opts['clusterer']['clusterer_file'],
+    '#title' => t('Filename'),
+    '#description' => t('Name of downloaded file in the thirdparty folder. Default: %default', array('%default' => 'Clusterer2.js')),
+    '#default_value' => $opts['clusterer']['filename'],
   );
   $form['gmap_markermanager']['clusterer']['marker'] = array(
     '#type' => 'gmap_markerchooser',
@@ -392,8 +485,8 @@ function _gmap_admin_settings() {
     '#title' => t('Activate on'),
     '#field_suffix' => t('or more markers'),
     '#description' => t("Clustering is enabled when more than the specified number of markers are visible at the same time."),
-    '#size' => 3,
-    '#maxlength' => 3,
+    '#size' => 4,
+    '#maxlength' => 4,
     '#default_value' => $opts['clusterer']['max_nocluster'],
   );
   $form['gmap_markermanager']['clusterer']['cluster_min'] = array(
@@ -401,8 +494,8 @@ function _gmap_admin_settings() {
     '#title' => t('Cluster on'),
     '#field_suffix' => t('or more markers'),
     '#description' => t("Minimal number of markers per cluster"),
-    '#size' => 2,
-    '#maxlength' => 2,
+    '#size' => 3,
+    '#maxlength' => 3,
     '#default_value' => $opts['clusterer']['cluster_min'],
   );
   $form['gmap_markermanager']['clusterer']['max_lines'] = array(
@@ -411,10 +504,72 @@ function _gmap_admin_settings() {
     '#field_prefix' => t('at most'),
     '#field_suffix' => t('lines'),
     '#description' => t("Maximum number of lines per info box"),
-    '#size' => 2,
-    '#maxlength' => 2,
+    '#size' => 3,
+    '#maxlength' => 3,
     '#default_value' => $opts['clusterer']['max_lines'],
   );
+  $form['gmap_markermanager']['clusterer']['popup_mode'] = array(
+    '#type' => 'radios',
+    '#title' => t('Popup mode'),
+    '#options' => array('orig' => t('Original'), 'zoom' => t('Zoom to Cluster')),
+    '#default_value' => $opts['clusterer']['popup_mode'],
+  );
+
+  // Martin Pearman's ClusterMarker
+  $form['gmap_markermanager']['clustermarker'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('ClusterMarker settings'),
+    '#description' => t('ClusterMarker is a marker manager written by Martin Pearman. To use, you must download it from <a href="@url">here</a> and extract the appropriate file to the <em>thirdparty</em> folder.', array('@url' => 'http://googlemapsapi.martinpearman.co.uk/downloads.php?cat_id=1')),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  );
+  $form['gmap_markermanager']['clustermarker']['filename'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Filename'),
+    '#description' => t('Name of downloaded file in the thirdparty folder. Default: %default', array('%default' => 'ClusterMarker.js')),
+    '#default_value' => $opts['clustermarker']['filename'],
+  );
+  $form['gmap_markermanager']['clustermarker']['borderPadding'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Border padding'),
+    '#description' => t('Markers located less than this number of pixels from the viewport will be added to the map by the manager (even if they would be fully invisible.)'),
+    '#size' => 4,
+    '#maxlength' => 4,
+    '#default_value' => $opts['clustermarker']['borderPadding'],
+  );
+  $form['gmap_markermanager']['clustermarker']['clusteringEnabled'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Clustering enabled'),
+    '#description' => t('Whether to use clustering or not.'),
+    '#default_value' => $opts['clustermarker']['clusteringEnabled'],
+  );
+  $form['gmap_markermanager']['clustermarker']['clusterMarkerIcon'] = array(
+    '#type' => 'gmap_markerchooser',
+    '#title' => t('Marker for clusters'),
+    '#description' => t('The marker to use when creating a cluster.'),
+    '#default_value' => $opts['clustermarker']['clusterMarkerIcon'],
+  );
+  $form['gmap_markermanager']['clustermarker']['clusterMarkerTitle'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Title for clusters'),
+    '#description' => t("The title to use for clusters. %count will be replaced with the number of markers in the cluster."),
+    '#default_value' => $opts['clustermarker']['clusterMarkerTitle'],
+  );
+  $form['gmap_markermanager']['clustermarker']['fitMapMaxZoom'] = array(
+    '#type' => 'select',
+    '#title' => t('Maximum zoom for zooming to cluster'),
+    '#options' => drupal_map_assoc(range(0, 17)),
+    '#description' => t('When clicking on a cluster, the map will not zoom in farther than this.'),
+    '#default_value' => $opts['clustermarker']['fitMapMaxZoom'],
+  );
+  $form['gmap_markermanager']['clustermarker']['intersectPadding'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Intersect Padding'),
+    '#description' => t('Number of pixels to pad marker icons by when determining whether they intersect.'),
+    '#default_value' => $opts['clustermarker']['intersectPadding'],
+    '#size' => 4,
+    '#maxlength' => 4,
+  );
 
   $form['#validate']['gmap_admin_settings_validate'] = array();
 
index 6a3a48b..bb300a2 100644 (file)
@@ -20,15 +20,15 @@ Drupal.gmap.addHandler('gmap', function (elem) {
     obj.clusterer = new Clusterer(obj.map);
     var s = Drupal.settings.gmap_markermanager;
     if (s) {
-      obj.clusterer.SetMaxVisibleMarkers(s.max_nocluster);
-      obj.clusterer.SetMinMarkersPerCluster(s.cluster_min);
-      obj.clusterer.SetMaxLinesPerInfoBox(s.max_lines);
+      obj.clusterer.SetMaxVisibleMarkers(+s.max_nocluster);
+      obj.clusterer.SetMinMarkersPerCluster(+s.cluster_min);
+      obj.clusterer.SetMaxLinesPerInfoBox(+s.max_lines);
     }
   });
 
   obj.bind('iconsready', function () {
     var s = Drupal.settings.gmap_markermanager;
-    if (s) {
+    if (s.marker.length) {
       obj.clusterer.SetIcon(Drupal.gmap.getIcon(s.marker, 0));
     }
   });
@@ -53,3 +53,36 @@ Drupal.gmap.addHandler('gmap', function (elem) {
     obj.map.clearOverlays();
   });
 });
+
+////////////////// Clusterer overrides section //////////////////
+
+// Store original implementations of overridden functions
+Clusterer.origFunctions = {};
+
+// Alternate popup code from: http://drupal.org/node/155104#comment-574696
+Clusterer.origFunctions.PopUp = Clusterer.PopUp;
+Clusterer.PopUp = function (cluster) {
+  var mode = Drupal.settings.gmap_markermanager.popup_mode;
+  if (mode === 'orig') {
+    return Clusterer.origFunctions.PopUp(cluster);
+  }
+  else if (mode === 'zoom') {
+    var bounds = new GLatLngBounds();
+    for (var k in cluster.markers)
+      bounds.extend(cluster.markers[k].getPoint());
+
+    var sw = bounds.getSouthWest();
+    var ne = bounds.getNorthEast();
+    var rect = [
+      sw,
+      new GLatLng(sw.lat(), ne.lng()),
+      ne,
+      new GLatLng(ne.lat(), sw.lng()),
+      sw
+    ];
+
+    var center = bounds.getCenter();
+    var zoom = cluster.clusterer.map.getBoundsZoomLevel(bounds);
+    cluster.clusterer.map.setCenter(new GLatLng(+center.lat(), +center.lng()), +zoom);
+  }
+};
diff --git a/js/clustermarker_marker.js b/js/clustermarker_marker.js
new file mode 100644 (file)
index 0000000..25798b2
--- /dev/null
@@ -0,0 +1,50 @@
+/* $Id$ */
+
+/**
+ * @file
+ * GMap Markers
+ * Martin Pearman's ClusterMarker version
+ */
+
+/*global ClusterMarker, Drupal, GMarker */
+
+// Replace to override marker creation
+Drupal.gmap.factory.marker = function (loc, opts) {
+  return new GMarker(loc, opts);
+};
+
+Drupal.gmap.addHandler('gmap', function (elem) {
+  var obj = this;
+
+  obj.bind('init', function () {
+    obj.clustermarker = 0;
+  });
+
+  obj.bind('iconsready', function () {
+    if (!obj.clustermarker) {
+      var options = Drupal.settings.gmap_markermanager;
+      if (options.clusterMarkerIcon.length) {
+        options.clusterMarkerIcon = Drupal.gmap.getIcon(options.clusterMarkerIcon, 0);
+      }
+      else {
+        delete options.clusterMarkerIcon;
+      }
+      options.borderPadding = +options.borderPadding;
+      options.fitMapMaxZoom = +options.fitMapMaxZoom;
+      options.intersectPadding = +options.intersectPadding;
+      obj.clusterMarker = new ClusterMarker(obj.map, options);
+    }
+  });
+
+  obj.bind('addmarker', function (marker) {
+    obj.clusterMarker.addMarkers([marker.marker]);
+  });
+
+  obj.bind('delmarker', function (marker) {
+    // @@@TODO: Implement this!
+  });
+
+  obj.bind('clearmarkers', function () {
+    obj.clusterMarker.removeMarkers();
+  });
+});
diff --git a/js/markermanager_marker.js b/js/markermanager_marker.js
new file mode 100644 (file)
index 0000000..31cb732
--- /dev/null
@@ -0,0 +1,49 @@
+/* $Id$ */
+
+/**
+ * @file
+ * GMap Markers
+ * Gmaps Utility Library MarkerManager API version
+ */
+
+/*global Drupal, GMarker, MarkerManager */
+
+// Replace to override marker creation
+Drupal.gmap.factory.marker = function (loc, opts) {
+  return new GMarker(loc, opts);
+};
+
+Drupal.gmap.addHandler('gmap', function (elem) {
+  var obj = this;
+
+  obj.bind('init', function () {
+    // Set up the markermanager.
+    obj.mm = new MarkerManager(obj.map, Drupal.settings.gmap_markermanager);
+  });
+
+  obj.bind('addmarker', function (marker) {
+    var minzoom = Drupal.settings.gmap_markermanager.markerMinZoom;
+    var maxzoom = Drupal.settings.gmap_markermanager.markerMaxZoom;
+    if (marker.minzoom) {
+      minzoom = marker.minzoom;
+    }
+    if (marker.maxzoom) {
+      maxzoom = marker.maxzoom;
+    }
+    if (maxzoom > 0) {
+      obj.mm.addMarker(marker.marker, minzoom, maxzoom);
+    }
+    else {
+      obj.mm.addMarker(marker.marker, minzoom);
+    }
+    obj.mm.refresh();
+  });
+
+  obj.bind('delmarker', function (marker) {
+    obj.mm.removeMarker(marker.marker);
+  });
+
+  obj.bind('clearmarkers', function () {
+    obj.mm.clearMarkers();
+  });
+});
index ba24077..1079d68 100755 (executable)
@@ -5,7 +5,7 @@ Place downloaded third party code in this folder if instructed to by GMap.
 
 (todo: document better)
 
-clusterer2.js
+Clusterer2.js
 ------------
 Marker clusterer addon by Jef Poskanzer
 This was the original "marker clustering" solution for viewing large sets of markers without
@@ -22,4 +22,18 @@ The specifics are still up in the air at this point.
 http://geoserver.org/display/GEOSDOC/Google+Maps
 http://chignik.berkeley.edu/google/wms236.js
 http://dist.codehaus.org/geoserver/gmaps-geoserver_scripts/wms-gs-1_0_0.js
-http://dist.codehaus.org/geoserver/gmaps-geoserver_scripts/wms-gs-1_0_1.js
\ No newline at end of file
+http://dist.codehaus.org/geoserver/gmaps-geoserver_scripts/wms-gs-1_0_1.js
+
+ClusterMarker.js
+----------------
+Marker clusterer addon by Martin Pearman
+This is a new "marker clustering" solution similar to Clusterer2.js.
+Website: http://googlemapsapi.martinpearman.co.uk/articles.php?cat_id=1
+Tested version: 1.3.2
+
+MarkerManager.js
+----------------
+Google Maps API Utility Library addon
+This is an improved version of GMarkerManager.
+Website: http://code.google.com/p/gmaps-utility-library/
+Tested version: 1.0