THIRD-PARTY-RESOURCES += $(shell ls third-party/codemirror-3.15/mode/sql/sql.js)
# Mustache.js
THIRD-PARTY-RESOURCES += $(shell ls third-party/mustache/mustache.js)
-
+# markerclustererplus for the googlemap plugin
+THIRD-PARTY-RESOURCES += $(shell ls third-party/markerclusterer/markerclusterer.js)
+THIRD-PARTY-RESOURCES += $(shell ls third-party/markerclusterer/markerclusterer_packed.js)
thirdparty-js:
@find $(THIRD-PARTY-RESOURCES) -name '*.js'
from django.contrib.auth.models import User
-from manifold.manifoldapi import ManifoldAPI, ManifoldResult
+from manifold.manifoldapi import ManifoldAPI, ManifoldException, ManifoldResult
from manifold.core.query import Query
# Name my backend 'ManifoldBackend'
return
print "first", sessions
session = sessions[0]
- print "SESSION=", session
# Change to session authentication
api.auth = {'AuthMethod': 'session', 'session': session['session']}
print "PERSON=", person
request.session['manifold'] = {'auth': api.auth, 'person': person, 'expires': session['expires']}
+ except ManifoldException, e:
+ print "Caught ManifoldException, returning corresponding ManifoldResult"
+ return e.manifold_result
except Exception, e:
print "E: manifoldbackend", e
import traceback
var Plugin = Class.extend({
- init: function(options, element)
- {
+ init: function(options, element) {
// Mix in the passed in options with the default options
this.options = $.extend({}, this.default_options, options);
return (typeof this.on_filter_added === 'function');
},
- _query_handler: function(prefix, event_type, data)
- {
+ _query_handler: function(prefix, event_type, data) {
// We suppose this.query_handler_prefix has been defined if this
// callback is triggered
var fn;
switch(event_type) {
- case FILTER_ADDED:
- fn = 'filter_added';
- break;
- case FILTER_REMOVED:
- fn = 'filter_removed';
- break;
- case CLEAR_FILTERS:
- fn = 'filter_clear';
- break;
- case FIELD_ADDED:
- fn = 'field_added';
- break;
- case FIELD_REMOVED:
- fn = 'field_removed';
- break;
- case CLEAR_FIELDS:
- fn = 'field_clear';
- break;
- default:
- return;
+ case FILTER_ADDED:
+ fn = 'filter_added';
+ break;
+ case FILTER_REMOVED:
+ fn = 'filter_removed';
+ break;
+ case CLEAR_FILTERS:
+ fn = 'filter_clear';
+ break;
+ case FIELD_ADDED:
+ fn = 'field_added';
+ break;
+ case FIELD_REMOVED:
+ fn = 'field_removed';
+ break;
+ case CLEAR_FIELDS:
+ fn = 'field_clear';
+ break;
+ default:
+ return;
} // switch
fn = 'on_' + prefix + fn;
}
},
- _record_handler: function(prefix, event_type, record)
- {
+ _record_handler: function(prefix, event_type, record) {
// We suppose this.query_handler_prefix has been defined if this
// callback is triggered
var fn;
switch(event_type) {
- case NEW_RECORD:
- fn = 'new_record';
- break;
- case CLEAR_RECORDS:
- fn = 'clear_records';
- break;
- case IN_PROGRESS:
- fn = 'query_in_progress';
- break;
- case DONE:
- fn = 'query_done';
- break;
- case FIELD_STATE_CHANGED:
- fn = 'field_state_changed';
- break;
- default:
- return;
+ case NEW_RECORD:
+ fn = 'new_record';
+ break;
+ case CLEAR_RECORDS:
+ fn = 'clear_records';
+ break;
+ case IN_PROGRESS:
+ fn = 'query_in_progress';
+ break;
+ case DONE:
+ fn = 'query_done';
+ break;
+ case FIELD_STATE_CHANGED:
+ fn = 'field_state_changed';
+ break;
+ default:
+ return;
} // switch
fn = 'on_' + prefix + fn;
}
},
- get_handler_function: function(type, prefix)
- {
+ get_handler_function: function(type, prefix) {
return $.proxy(function(e, event_type, record) {
return this['_' + type + '_handler'](prefix, event_type, record);
}, this);
},
- listen_query: function(query_uuid, prefix)
- {
+ listen_query: function(query_uuid, prefix) {
// default: prefix = ''
prefix = (typeof prefix === 'undefined') ? '' : (prefix + '_');
/* Helper functions for naming HTML elements (ID, classes), with support for filters and fields */
- id: function()
- {
+ id: function() {
var ret = this.options.plugin_uuid;
for (var i = 0; i < arguments.length; i++) {
ret = ret + manifold.separator + arguments[i];
return ret;
},
- el: function()
- {
+ elmt: function() {
if (arguments.length == 0) {
return $('#' + this.id());
} else {
// We make sure to search _inside_ the dom tag of the plugin
- return $('#' + this.id.apply(this, arguments), this.el());
+ return $('#' + this.id.apply(this, arguments), this.elmt());
}
},
- els: function(cls)
- {
- return $('.' + cls, this.el());
+ elts: function(cls) {
+ return $('.' + cls, this.elmt());
},
- id_from_filter: function(filter, use_value)
- {
+ id_from_filter: function(filter, use_value) {
use_value = typeof use_value !== 'undefined' ? use_value : true;
var key = filter[0];
}
},
- str_from_filter: function(filter)
- {
+ str_from_filter: function(filter) {
return filter[0] + ' ' + filter[1] + ' ' + filter[2];
},
- array_from_id: function(id)
- {
+ array_from_id: function(id) {
var ret = id.split(manifold.separator);
ret.shift(); // remove plugin_uuid at the beginning
return ret;
},
- id_from_field: function(field)
- {
+ id_from_field: function(field) {
return 'field' + manifold.separator + field;
},
- field_from_id: function(id)
- {
+ field_from_id: function(id) {
var array;
if (typeof id === 'string') {
array = id.split(manifold.separator);
return array[1];
},
- id_from_key: function(key_field, value)
- {
+ id_from_key: function(key_field, value) {
return key_field + manifold.separator + unfold.escape_id(value).replace(/\\/g, '');
},
- id_from_record: function(method, record)
- {
+ id_from_record: function(method, record) {
var keys = manifold.metadata.get_key(method);
if (!keys)
return;
var key = keys[0];
switch (Object.toType(key)) {
- case 'string':
- if (!(key in record))
- return null;
- return this.id_from_key(key, record[key]);
-
- default:
- throw 'Not implemented';
+ case 'string':
+ if (!(key in record))
+ return null;
+ return this.id_from_key(key, record[key]);
+
+ default:
+ throw 'Not implemented';
}
},
- key_from_id: function(id)
- {
+ key_from_id: function(id) {
// NOTE this works only for simple keys
var array;
/* SPIN */
- spin: function()
- {
+ spin: function() {
manifold.spin(this.element);
},
- unspin: function()
- {
+ unspin: function() {
manifold.spin(this.element, false);
},
/* TEMPLATE */
- load_template: function(name, ctx)
- {
- return Mustache.render(this.el(name).html(), ctx);
+ load_template: function(name, ctx) {
+ return Mustache.render(this.elmt(name).html(), ctx);
},
});
return ResultValue(**result)
except Exception,error:
- # XXX Connection refused for example
- print "** API ERROR **"
+ if "Connection refused" in error:
+ raise ManifoldException ( ManifoldResult (code=ManifoldCode.SERVER_UNREACHABLE,
+ output="%s answered %s"%(self.url,error)))
+ # otherwise
+ print "** MANIFOLD API ERROR **"
import traceback
traceback.print_exc()
if debug: print "KO (unexpected exception)",error
def execute_query(request, query):
if not 'manifold' in request.session or not 'auth' in request.session['manifold']:
- print "W: Used hardcoded demo account for execute_query"
+ print "W: Using hardcoded demo account for execute_query"
manifold_api_session_auth = {'AuthMethod': 'password', 'Username': 'demo', 'AuthString': 'demo'}
else:
manifold_api_session_auth = request.session['manifold']['auth']
return type('Enum', (), enums)
ManifoldCode = enum (
+ UNKNOWN_ERROR=-1,
SUCCESS=0,
SESSION_EXPIRED=1,
NOT_IMPLEMENTED=2,
- UNKNOWN_ERROR=3,
+ SERVER_UNREACHABLE=3,
)
+_messages_ = { -1 : "Unknown", 0: "OK", 1: "Session Expired", 2: "Not Implemented", 3: "Backend server unreachable"}
+
# being a dict this can be used with json.dumps
class ManifoldResult (dict):
def __init__ (self, code=ManifoldCode.SUCCESS, value=None, output=""):
def __repr__ (self):
- result="[[MFresult code=%s"%self['code']
- if self['code']==0:
+ code=self['code']
+ result="[[MFresult %s (code=%s)"%(_messages_.get(code,"???"),code)
+ if code==0:
value=self['value']
if isinstance(value,list): result += " [value=list with %d elts]"%len(value)
elif isinstance(value,dict): result += " [value=dict with keys %s]"%value.keys()
* License: GPLv3
*/
-// NOTE: We are not making use of element, but this.el() instead...
+// NOTE: We are not making use of element, but this.elmt() instead...
(function($){
init: function(options, element) {
this._super(options, element);
- this.els('closeButton').click(function() {
+ this.elts('closeButton').click(function() {
manifold.raise_event(options.query_uuid, FILTER_REMOVED, filter);
});
- this.el('clearFilters').click(function () {
+ this.elmt('clearFilters').click(function () {
manifold.raise_event(options.query_uuid, CLEAR_FILTERS);
});
this.check_and_hide_clear_button();
show_clear_button: function()
{
- this.el('clearFilters').show();
+ this.elmt('clearFilters').show();
},
hide_clear_button: function()
{
- this.el('clearFilters').hide();
+ this.elmt('clearFilters').hide();
},
check_and_hide_clear_button: function()
{
// Count the number of filter _inside_ the current plugin
- var count = this.els('filterButton').length;
+ var count = this.elts('filterButton').length;
if (count == 1) { // Including the template
- this.el('clearFilters').hide();
+ this.elmt('clearFilters').hide();
}
},
add_filter: function(filter)
{
- var template = this.el('template').html();
+ var template = this.elmt('template').html();
var ctx = {
id: this.id(this.id_from_filter(filter, false)),
span: this.str_from_filter(filter)
};
var output = Mustache.render(template, ctx);
- this.el('myActiveFilters').append(output);
+ this.elmt('myActiveFilters').append(output);
// Add an event on click on the close button, call function removeFilter
var self = this;
- this.els('closeButton').last().click(function() {
+ this.elts('closeButton').last().click(function() {
manifold.raise_event(self.options.query_uuid, FILTER_REMOVED, filter);
});
remove_filter: function(filter)
{
- this.el(this.id_from_filter(filter, false)).remove();
+ this.elmt(this.id_from_filter(filter, false)).remove();
this.check_and_hide_clear_button();
},
from unfold.plugin import Plugin
-class GoogleMaps (Plugin):
+class GoogleMap (Plugin):
# set checkboxes if a final column with checkboxes is desired
# pass columns as the initial set of columns
self.zoom=zoom
def template_file (self):
- return "googlemaps.html"
+ return "googlemap.html"
def template_env (self, request):
env={}
def requirements (self):
reqs = {
'js_files' : [ "https://maps.googleapis.com/maps/api/js?sensor=false",
- "/js/googlemaps.js",
+ "/js/googlemap.js",
"/js/markerclusterer.js",
"js/manifold.js", "js/manifold-query.js",
"js/spin.presets.js", "js/spin.min.js", "js/jquery.spin.js",
"js/unfold-helper.js",
],
- 'css_files' : [ "css/googlemaps.css",
+ 'css_files' : [ "css/googlemap.css",
],
}
return reqs
*/
/* BUGS:
- * - infowindow is not properly reopened when the maps has not the focus
+ * - infowindow is not properly reopened when the maps does not have the focus
*/
+googlemap_debug=true;
+googlemap_debug_detailed=false;
+
(function($){
- var GoogleMaps = Plugin.extend({
+ var GoogleMap = Plugin.extend({
- init: function(options, element)
- {
+ init: function(options, element) {
+ if (googlemap_debug) messages.debug("GoogleMap.init");
this._super(options, element);
/* Member variables */
// query status
this.received_all = false;
this.received_set = false;
- this.in_set_buffer = Array();
+ this.in_set_buffer = [];
// key -> { marker, checked }
this.map_markers = {}
/* XXX Events XXX */
- this.el().on('show', this, this.on_show);
+ this.elmt().on('show', this, this.on_show);
// TODO in destructor
// $(window).unbind('Hazelnut');
/* PLUGIN EVENTS */
- on_show: function(e)
- {
+ on_show: function(e) {
+ if (googlemap_debug) messages.debug("on_show");
var self = e.data;
google.maps.event.trigger(self.map, 'resize');
}, // on_show
/**
*/
- initialize_map: function()
- {
+ initialize_map: function() {
+ if (googlemap_debug) messages.debug("initialize_map");
this.markerCluster = null;
this.coords = new Array();
var myOptions = {
zoom: this.options.zoom,
center: myLatlng,
- mapTypeId: google.maps.MapTypeId.ROADMAP
+ mapTypeId: google.maps.MapTypeId.ROADMAP,
}
-
- var id = this.options.plugin_uuid + manifold.separator + 'map';
- this.map = new google.maps.Map(document.getElementById(id), myOptions);
+
+ var domid = this.options.plugin_uuid + manifold.separator + 'map';
+ var el = document.getElementById(domid);
+ if (googlemap_debug) messages.debug("gmap.initialize_map based on domid=" + domid + " el=" + el);
+ this.map = new google.maps.Map(el, myOptions);
this.infowindow = new google.maps.InfoWindow();
}, // initialize_map
- set_checkbox: function(record, checked)
- {
+ set_checkbox: function(record, checked) {
+ if (googlemap_debug_detailed) messages.debug ("set_checkbox");
/* Default: checked = true */
if (typeof checked === 'undefined')
checked = true;
var key_value;
/* The function accepts both records and their key */
switch (manifold.get_type(record)) {
- case TYPE_VALUE:
- key_value = record;
- break;
- case TYPE_RECORD:
- /* XXX Test the key before ? */
- key_value = record[this.key];
- break;
- default:
- throw "Not implemented";
- break;
+ case TYPE_VALUE:
+ key_value = record;
+ break;
+ case TYPE_RECORD:
+ /* XXX Test the key before ? */
+ key_value = record[this.key];
+ break;
+ default:
+ throw "Not implemented";
+ break;
}
// we cannot directly edit html, since nothing but marker is displayed
dict_info.in_set = checked;
dict_info.marker.content = this.get_marker_content(dict_info.record, checked);
- // Update opened infowindow
- // XXX Factor this code
- this.infowindow.close();
- this.infowindow.open(this.map, dict_info.marker);
- this.infowindow.setContent(dict_info.marker.content);
- this.els('map-button').unbind('click').click(this, this._button_click);
-
+ // Thierry - this code seems to cause the googlemap area to go all grayed out
+ // once all the quesries have come back
+ // BEG turning off temporarily
+ // // Update opened infowindow
+ // // XXX Factor this code
+ // this.infowindow.close();
+ // this.infowindow.open(this.map, dict_info.marker);
+ // this.infowindow.setContent(dict_info.marker.content);
+ // this.elts('map-button').unbind('click').click(this, this._button_click);
+ // END turning off temporarily
//var button = this.checkbox(record, checked);
- //this.el('checkbox', this.id_from_record(method, record)).html(button);
+ //this.elmt('checkbox', this.id_from_record(method, record)).html(button);
},
- checkbox: function(record, checked)
- {
+ checkbox: function(record, checked) {
+ if (googlemap_debug_detailed) messages.debug ("checkbox");
if (typeof checked === 'undefined')
checked = false;
return "<div id='" + id + "'>" + button + "</div>";
},
- get_marker_content: function(record, checked)
- {
+ get_marker_content: function(record, checked) {
+ if (googlemap_debug_detailed) messages.debug ("get_marker_content");
return '<p><b>' + this.method + '</b>: ' + get_value(record['resource_hrn']) + '<br/><b>network</b>: ' + get_value(record['network'])+'</p>' + this.checkbox(record, checked);
},
/**
*/
- new_record: function(record)
- {
+ new_record: function(record) {
+ if (googlemap_debug_detailed) messages.debug ("new_record");
// get the coordinates
var latitude=get_value(record['latitude']);
var longitude=get_value(record['longitude']);
//jQuery(".map-button").click(button_click);
//if(jQuery.inArray(record, rows)>-1){
- var marker = new google.maps.Marker({
- position: myLatlng,
- title: get_value(record['hostname']),
- // This should be done by the rendering
- content: this.get_marker_content(record, false),
- });
-
- this.addInfoWindow(marker, this.map);
- var key_value = (this.key in record) ? record[this.key] : null;
- if (!key_value)
- return;
- this.map_markers[unfold.escape_id(key_value).replace(/\\/g, '')] = {
- marker: marker,
- in_set: false,
- record: record,
- value: key_value
- }
+ var marker = new google.maps.Marker({
+ position: myLatlng,
+ title: get_value(record['hostname']),
+ // This should be done by the rendering
+ content: this.get_marker_content(record, false),
+ });
+
+ this.addInfoWindow(marker, this.map);
+ var key_value = (this.key in record) ? record[this.key] : null;
+ if (!key_value)
+ return;
+ this.map_markers[unfold.escape_id(key_value).replace(/\\/g, '')] = {
+ marker: marker,
+ in_set: false,
+ record: record,
+ value: key_value
+ }
//}
}, // new_record
- addInfoWindow: function(marker, map)
- {
+ addInfoWindow: function(marker, map) {
+ if (googlemap_debug_detailed) messages.debug ("addInfoWindow");
var self = this;
google.maps.event.addListener(marker, 'click', function () {
if(self.infowindow){
self.infowindow.open(map, marker);
// onload of the infowindow on the map, bind a click on a button
google.maps.event.addListener(self.infowindow, 'domready', function() {
- self.els('map-button').unbind('click').click(self, self._button_click);
-// jQuery(".map-button").click({instance: instance_, infoWindow: object.infowindow}, button_click);
+ self.elts('map-button').unbind('click').click(self, self._button_click);
+ // jQuery(".map-button").click({instance: instance_, infoWindow: object.infowindow}, button_click);
});
});
}, // addInfoWindow
/*************************** RECORD HANDLER ***************************/
- on_new_record: function(record)
- {
+ on_new_record: function(record) {
+ if (googlemap_debug_detailed) messages.debug("on_new_record");
if (this.received_all)
// update checkbox for record
this.set_checkbox(record);
this.in_set_buffer.push(record);
},
- on_clear_records: function(record)
- {
-
+ on_clear_records: function(record) {
+ if (googlemap_debug_detailed) messages.debug("on_clear_records");
},
// Could be the default in parent
- on_query_in_progress: function()
- {
+ on_query_in_progress: function() {
+ if (googlemap_debug) messages.debug("on_query_in_progress");
this.spin();
},
- on_query_done: function()
- {
+ on_query_done: function() {
+ if (googlemap_debug) messages.debug("on_query_done");
if (this.received_all)
this.unspin();
this.received_set = true;
},
- on_field_state_changed: function(data)
- {
+ on_field_state_changed: function(data) {
+ if (googlemap_debug) messages.debug("on_field_state_changed");
switch(data.request) {
- case FIELD_REQUEST_ADD:
- case FIELD_REQUEST_ADD_RESET:
- this.set_checkbox(data.value, true);
- break;
- case FIELD_REQUEST_REMOVE:
- case FIELD_REQUEST_REMOVE_RESET:
- this.set_checkbox(data.value, false);
- break;
- default:
- break;
+ case FIELD_REQUEST_ADD:
+ case FIELD_REQUEST_ADD_RESET:
+ this.set_checkbox(data.value, true);
+ break;
+ case FIELD_REQUEST_REMOVE:
+ case FIELD_REQUEST_REMOVE_RESET:
+ this.set_checkbox(data.value, false);
+ break;
+ default:
+ break;
}
},
// all
- on_all_new_record: function(record)
- {
+ on_all_new_record: function(record) {
+ if (googlemap_debug_detailed) messages.debug("on_all_new_record");
this.new_record(record);
},
- on_all_clear_records: function()
- {
+ on_all_clear_records: function() {
+ if (googlemap_debug) messages.debug("on_all_clear_records");
},
- on_all_query_in_progress: function()
- {
+ on_all_query_in_progress: function() {
+ if (googlemap_debug) messages.debug("on_all_query_in_progress");
// XXX parent
this.spin();
},
- on_all_query_done: function()
- {
-
+ on_all_query_done: function() {
+ if (googlemap_debug) messages.debug("on_all_query_done");
// MarkerClusterer
var markers = [];
$.each(this.map_markers, function (k, v) { markers.push(v.marker); });
var cluster_markers = cluster.getMarkers();
var bounds = new google.maps.LatLngBounds();
/*
- * date: 24/05/2012
- * author: lbaron
- * Firefox JS Error - replaced $.each by JQuery.each
- */
+ * date: 24/05/2012
+ * author: lbaron
+ * Firefox JS Error - replaced $.each by JQuery.each
+ */
jQuery.each(cluster_markers, function(i, marker){
bounds.extend(marker.getPosition());
});
$.each(this.in_set_buffer, function(i, record) {
self.set_checkbox(record, true);
});
+ // reset
+ self.in_set_buffer = [];
this.unspin();
}
/************************** PRIVATE METHODS ***************************/
- _button_click: function(e)
- {
+ _button_click: function(e) {
+ if (googlemap_debug) messages.debug("_button_click");
var self = e.data;
var escaped_key = self.key_from_id($(this).parent().attr('id'), 'checkbox');
});
- $.plugin('GoogleMaps', GoogleMaps);
+ $.plugin('GoogleMap', GoogleMap);
})(jQuery);
+++ /dev/null
-// ==ClosureCompiler==
-// @compilation_level ADVANCED_OPTIMIZATIONS
-// @externs_url http://closure-compiler.googlecode.com/svn/trunk/contrib/externs/maps/google_maps_api_v3.js
-// ==/ClosureCompiler==
-
-/**
- * @name MarkerClusterer for Google Maps v3
- * @version version 1.0
- * @author Luke Mahe
- * @fileoverview
- * The library creates and manages per-zoom-level clusters for large amounts of
- * markers.
- * <br/>
- * This is a v3 implementation of the
- * <a href="http://gmaps-utility-library-dev.googlecode.com/svn/tags/markerclusterer/"
- * >v2 MarkerClusterer</a>.
- */
-
-/**
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-/**
- * A Marker Clusterer that clusters markers.
- *
- * @param {google.maps.Map} map The Google map to attach to.
- * @param {Array.<google.maps.Marker>} opt_markers Optional markers to add to
- * the cluster.
- * @param {Object} opt_options support the following options:
- * 'gridSize': (number) The grid size of a cluster in pixels.
- * 'maxZoom': (number) The maximum zoom level that a marker can be part of a
- * cluster.
- * 'zoomOnClick': (boolean) Whether the default behaviour of clicking on a
- * cluster is to zoom into it.
- * 'averageCenter': (boolean) Wether the center of each cluster should be
- * the average of all markers in the cluster.
- * 'styles': (object) An object that has style properties:
- * 'url': (string) The image url.
- * 'height': (number) The image height.
- * 'width': (number) The image width.
- * 'anchor': (Array) The anchor position of the label text.
- * 'textColor': (string) The text color.
- * 'textSize': (number) The text size.
- * @constructor
- * @extends google.maps.OverlayView
- */
-function MarkerClusterer(map, opt_markers, opt_options) {
- // MarkerClusterer implements google.maps.OverlayView interface. We use the
- // extend function to extend MarkerClusterer with google.maps.OverlayView
- // because it might not always be available when the code is defined so we
- // look for it at the last possible moment. If it doesn't exist now then
- // there is no point going ahead :)
- this.extend(MarkerClusterer, google.maps.OverlayView);
- this.map_ = map;
-
- /**
- * @type {Array.<google.maps.Marker>}
- * @private
- */
- this.markers_ = [];
-
- /**
- * @type {Array.<Cluster>}
- */
- this.clusters_ = [];
-
- this.sizes = [53, 56, 66, 78, 90];
-
- /**
- * @private
- */
- this.styles_ = [];
-
- /**
- * @type {boolean}
- * @private
- */
- this.ready_ = false;
-
- var options = opt_options || {};
-
- /**
- * @type {number}
- * @private
- */
- this.gridSize_ = options['gridSize'] || 60;
-
- /**
- * @type {?number}
- * @private
- */
- this.maxZoom_ = options['maxZoom'] || null;
-
- this.styles_ = options['styles'] || [];
-
- /**
- * @type {string}
- * @private
- */
- this.imagePath_ = options['imagePath'] ||
- this.MARKER_CLUSTER_IMAGE_PATH_;
-
- /**
- * @type {string}
- * @private
- */
- this.imageExtension_ = options['imageExtension'] ||
- this.MARKER_CLUSTER_IMAGE_EXTENSION_;
-
- /**
- * @type {boolean}
- * @private
- */
- this.zoomOnClick_ = true;
-
- if (options['zoomOnClick'] != undefined) {
- this.zoomOnClick_ = options['zoomOnClick'];
- }
-
- /**
- * @type {boolean}
- * @private
- */
- this.averageCenter_ = false;
-
- if (options['averageCenter'] != undefined) {
- this.averageCenter_ = options['averageCenter'];
- }
-
- this.setupStyles_();
-
- this.setMap(map);
-
- /**
- * @type {number}
- * @private
- */
- this.prevZoom_ = this.map_.getZoom();
-
- // Add the map event listeners
- var that = this;
- google.maps.event.addListener(this.map_, 'zoom_changed', function() {
- var maxZoom = that.map_.mapTypes[that.map_.getMapTypeId()].maxZoom;
- var zoom = that.map_.getZoom();
- if (zoom < 0 || zoom > maxZoom) {
- return;
- }
-
- if (that.prevZoom_ != zoom) {
- that.prevZoom_ = that.map_.getZoom();
- that.resetViewport();
- }
- });
-
- google.maps.event.addListener(this.map_, 'idle', function() {
- that.redraw();
- });
-
- // Finally, add the markers
- if (opt_markers && opt_markers.length) {
- this.addMarkers(opt_markers, false);
- }
-}
-
-
-/**
- * The marker cluster image path.
- *
- * @type {string}
- * @private
- */
-MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_PATH_ =
- 'http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/' +
- 'images/m';
-
-
-/**
- * The marker cluster image path.
- *
- * @type {string}
- * @private
- */
-MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_EXTENSION_ = 'png';
-
-
-/**
- * Extends a objects prototype by anothers.
- *
- * @param {Object} obj1 The object to be extended.
- * @param {Object} obj2 The object to extend with.
- * @return {Object} The new extended object.
- * @ignore
- */
-MarkerClusterer.prototype.extend = function(obj1, obj2) {
- return (function(object) {
- for (property in object.prototype) {
- this.prototype[property] = object.prototype[property];
- }
- return this;
- }).apply(obj1, [obj2]);
-};
-
-
-/**
- * Implementaion of the interface method.
- * @ignore
- */
-MarkerClusterer.prototype.onAdd = function() {
- this.setReady_(true);
-};
-
-
-/**
- * Implementation of the interface.
- * @ignore
- */
-MarkerClusterer.prototype.draw = function() {};
-
-
-/**
- * Sets up the styles object.
- *
- * @private
- */
-MarkerClusterer.prototype.setupStyles_ = function() {
- if (this.styles_.length) {
- return;
- }
-
- for (var i = 0, size; size = this.sizes[i]; i++) {
- this.styles_.push({
- url: this.imagePath_ + (i + 1) + '.' + this.imageExtension_,
- height: size,
- width: size
- });
- }
-};
-
-
-/**
- * Sets the styles.
- *
- * @param {Object} styles The style to set.
- */
-MarkerClusterer.prototype.setStyles = function(styles) {
- this.styles_ = styles;
-};
-
-
-/**
- * Gets the styles.
- *
- * @return {Object} The styles object.
- */
-MarkerClusterer.prototype.getStyles = function() {
- return this.styles_;
-};
-
-
-/**
- * Whether zoom on click is set.
- *
- * @return {boolean} True if zoomOnClick_ is set.
- */
-MarkerClusterer.prototype.isZoomOnClick = function() {
- return this.zoomOnClick_;
-};
-
-/**
- * Whether average center is set.
- *
- * @return {boolean} True if averageCenter_ is set.
- */
-MarkerClusterer.prototype.isAverageCenter = function() {
- return this.averageCenter_;
-};
-
-
-/**
- * Returns the array of markers in the clusterer.
- *
- * @return {Array.<google.maps.Marker>} The markers.
- */
-MarkerClusterer.prototype.getMarkers = function() {
- return this.markers_;
-};
-
-
-/**
- * Returns the array of markers in the clusterer.
- *
- * @return {Array.<google.maps.Marker>} The number of markers.
- */
-MarkerClusterer.prototype.getTotalMarkers = function() {
- return this.markers_;
-};
-
-
-/**
- * Sets the max zoom for the clusterer.
- *
- * @param {number} maxZoom The max zoom level.
- */
-MarkerClusterer.prototype.setMaxZoom = function(maxZoom) {
- this.maxZoom_ = maxZoom;
-};
-
-
-/**
- * Gets the max zoom for the clusterer.
- *
- * @return {number} The max zoom level.
- */
-MarkerClusterer.prototype.getMaxZoom = function() {
- return this.maxZoom_ || this.map_.mapTypes[this.map_.getMapTypeId()].maxZoom;
-};
-
-
-/**
- * The function for calculating the cluster icon image.
- *
- * @param {Array.<google.maps.Marker>} markers The markers in the clusterer.
- * @param {number} numStyles The number of styles available.
- * @return {Object} A object properties: 'text' (string) and 'index' (number).
- * @private
- */
-MarkerClusterer.prototype.calculator_ = function(markers, numStyles) {
- var index = 0;
- var count = markers.length;
- var dv = count;
- while (dv !== 0) {
- dv = parseInt(dv / 10, 10);
- index++;
- }
-
- index = Math.min(index, numStyles);
- return {
- text: count,
- index: index
- };
-};
-
-
-/**
- * Set the calculator function.
- *
- * @param {function(Array, number)} calculator The function to set as the
- * calculator. The function should return a object properties:
- * 'text' (string) and 'index' (number).
- *
- */
-MarkerClusterer.prototype.setCalculator = function(calculator) {
- this.calculator_ = calculator;
-};
-
-
-/**
- * Get the calculator function.
- *
- * @return {function(Array, number)} the calculator function.
- */
-MarkerClusterer.prototype.getCalculator = function() {
- return this.calculator_;
-};
-
-
-/**
- * Add an array of markers to the clusterer.
- *
- * @param {Array.<google.maps.Marker>} markers The markers to add.
- * @param {boolean} opt_nodraw Whether to redraw the clusters.
- */
-MarkerClusterer.prototype.addMarkers = function(markers, opt_nodraw) {
- for (var i = 0, marker; marker = markers[i]; i++) {
- this.pushMarkerTo_(marker);
- }
- if (!opt_nodraw) {
- this.redraw();
- }
-};
-
-
-/**
- * Pushes a marker to the clusterer.
- *
- * @param {google.maps.Marker} marker The marker to add.
- * @private
- */
-MarkerClusterer.prototype.pushMarkerTo_ = function(marker) {
- marker.setVisible(false);
- marker.setMap(null);
- marker.isAdded = false;
- if (marker['draggable']) {
- // If the marker is draggable add a listener so we update the clusters on
- // the drag end.
- var that = this;
- google.maps.event.addListener(marker, 'dragend', function() {
- marker.isAdded = false;
- that.resetViewport();
- that.redraw();
- });
- }
- this.markers_.push(marker);
-};
-
-
-/**
- * Adds a marker to the clusterer and redraws if needed.
- *
- * @param {google.maps.Marker} marker The marker to add.
- * @param {boolean} opt_nodraw Whether to redraw the clusters.
- */
-MarkerClusterer.prototype.addMarker = function(marker, opt_nodraw) {
- this.pushMarkerTo_(marker);
- if (!opt_nodraw) {
- this.redraw();
- }
-};
-
-
-/**
- * Remove a marker from the cluster.
- *
- * @param {google.maps.Marker} marker The marker to remove.
- * @return {boolean} True if the marker was removed.
- */
-MarkerClusterer.prototype.removeMarker = function(marker) {
- var index = -1;
- if (this.markers_.indexOf) {
- index = this.markers_.indexOf(marker);
- } else {
- for (var i = 0, m; m = this.markers_[i]; i++) {
- if (m == marker) {
- index = i;
- continue;
- }
- }
- }
-
- if (index == -1) {
- // Marker is not in our list of markers.
- return false;
- }
-
- this.markers_.splice(index, 1);
- marker.setVisible(false);
- marker.setMap(null);
-
- this.resetViewport();
- this.redraw();
- return true;
-};
-
-
-/**
- * Sets the clusterer's ready state.
- *
- * @param {boolean} ready The state.
- * @private
- */
-MarkerClusterer.prototype.setReady_ = function(ready) {
- if (!this.ready_) {
- this.ready_ = ready;
- this.createClusters_();
- }
-};
-
-
-/**
- * Returns the number of clusters in the clusterer.
- *
- * @return {number} The number of clusters.
- */
-MarkerClusterer.prototype.getTotalClusters = function() {
- return this.clusters_.length;
-};
-
-
-/**
- * Returns the google map that the clusterer is associated with.
- *
- * @return {google.maps.Map} The map.
- */
-MarkerClusterer.prototype.getMap = function() {
- return this.map_;
-};
-
-
-/**
- * Sets the google map that the clusterer is associated with.
- *
- * @param {google.maps.Map} map The map.
- */
-MarkerClusterer.prototype.setMap = function(map) {
- this.map_ = map;
-};
-
-
-/**
- * Returns the size of the grid.
- *
- * @return {number} The grid size.
- */
-MarkerClusterer.prototype.getGridSize = function() {
- return this.gridSize_;
-};
-
-
-/**
- * Returns the size of the grid.
- *
- * @param {number} size The grid size.
- */
-MarkerClusterer.prototype.setGridSize = function(size) {
- this.gridSize_ = size;
-};
-
-
-/**
- * Extends a bounds object by the grid size.
- *
- * @param {google.maps.LatLngBounds} bounds The bounds to extend.
- * @return {google.maps.LatLngBounds} The extended bounds.
- */
-MarkerClusterer.prototype.getExtendedBounds = function(bounds) {
- var projection = this.getProjection();
-
- // Turn the bounds into latlng.
- var tr = new google.maps.LatLng(bounds.getNorthEast().lat(),
- bounds.getNorthEast().lng());
- var bl = new google.maps.LatLng(bounds.getSouthWest().lat(),
- bounds.getSouthWest().lng());
-
- // Convert the points to pixels and the extend out by the grid size.
- var trPix = projection.fromLatLngToDivPixel(tr);
- trPix.x += this.gridSize_;
- trPix.y -= this.gridSize_;
-
- var blPix = projection.fromLatLngToDivPixel(bl);
- blPix.x -= this.gridSize_;
- blPix.y += this.gridSize_;
-
- // Convert the pixel points back to LatLng
- var ne = projection.fromDivPixelToLatLng(trPix);
- var sw = projection.fromDivPixelToLatLng(blPix);
-
- // Extend the bounds to contain the new bounds.
- bounds.extend(ne);
- bounds.extend(sw);
-
- return bounds;
-};
-
-
-/**
- * Determins if a marker is contained in a bounds.
- *
- * @param {google.maps.Marker} marker The marker to check.
- * @param {google.maps.LatLngBounds} bounds The bounds to check against.
- * @return {boolean} True if the marker is in the bounds.
- * @private
- */
-MarkerClusterer.prototype.isMarkerInBounds_ = function(marker, bounds) {
- return bounds.contains(marker.getPosition());
-};
-
-
-/**
- * Clears all clusters and markers from the clusterer.
- */
-MarkerClusterer.prototype.clearMarkers = function() {
- this.resetViewport();
-
- // Set the markers a empty array.
- this.markers_ = [];
-};
-
-
-/**
- * Clears all existing clusters and recreates them.
- */
-MarkerClusterer.prototype.resetViewport = function() {
- // Remove all the clusters
- for (var i = 0, cluster; cluster = this.clusters_[i]; i++) {
- cluster.remove();
- }
-
- // Reset the markers to not be added and to be invisible.
- for (var i = 0, marker; marker = this.markers_[i]; i++) {
- marker.isAdded = false;
- marker.setMap(null);
- marker.setVisible(false);
- }
-
- this.clusters_ = [];
-};
-
-
-/**
- * Redraws the clusters.
- */
-MarkerClusterer.prototype.redraw = function() {
- this.createClusters_();
-};
-
-
-/**
- * Creates the clusters.
- *
- * @private
- */
-MarkerClusterer.prototype.createClusters_ = function() {
- if (!this.ready_) {
- return;
- }
-
- // Get our current map view bounds.
- // Create a new bounds object so we don't affect the map.
- var mapBounds = new google.maps.LatLngBounds(this.map_.getBounds().getSouthWest(),
- this.map_.getBounds().getNorthEast());
- var bounds = this.getExtendedBounds(mapBounds);
-
- for (var i = 0, marker; marker = this.markers_[i]; i++) {
- var added = false;
- if (!marker.isAdded && this.isMarkerInBounds_(marker, bounds)) {
- for (var j = 0, cluster; cluster = this.clusters_[j]; j++) {
- if (!added && cluster.getCenter() &&
- cluster.isMarkerInClusterBounds(marker)) {
- added = true;
- cluster.addMarker(marker);
- break;
- }
- }
-
- if (!added) {
- // Create a new cluster.
- var cluster = new Cluster(this);
- cluster.addMarker(marker);
- this.clusters_.push(cluster);
- }
- }
- }
-};
-
-
-/**
- * A cluster that contains markers.
- *
- * @param {MarkerClusterer} markerClusterer The markerclusterer that this
- * cluster is associated with.
- * @constructor
- * @ignore
- */
-function Cluster(markerClusterer) {
- this.markerClusterer_ = markerClusterer;
- this.map_ = markerClusterer.getMap();
- this.gridSize_ = markerClusterer.getGridSize();
- this.averageCenter_ = markerClusterer.isAverageCenter();
- this.center_ = null;
- this.markers_ = [];
- this.bounds_ = null;
- this.clusterIcon_ = new ClusterIcon(this, markerClusterer.getStyles(),
- markerClusterer.getGridSize());
-}
-
-/**
- * Determins if a marker is already added to the cluster.
- *
- * @param {google.maps.Marker} marker The marker to check.
- * @return {boolean} True if the marker is already added.
- */
-Cluster.prototype.isMarkerAlreadyAdded = function(marker) {
- if (this.markers_.indexOf) {
- return this.markers_.indexOf(marker) != -1;
- } else {
- for (var i = 0, m; m = this.markers_[i]; i++) {
- if (m == marker) {
- return true;
- }
- }
- }
- return false;
-};
-
-
-/**
- * Add a marker the cluster.
- *
- * @param {google.maps.Marker} marker The marker to add.
- * @return {boolean} True if the marker was added.
- */
-Cluster.prototype.addMarker = function(marker) {
- if (this.isMarkerAlreadyAdded(marker)) {
- return false;
- }
-
- if (!this.center_) {
- this.center_ = marker.getPosition();
- this.calculateBounds_();
- } else {
- if (this.averageCenter_) {
- var lat = (this.center_.lat() + marker.getPosition().lat()) / 2;
- var lng = (this.center_.lng() + marker.getPosition().lng()) / 2;
- this.center_ = new google.maps.LatLng(lat, lng);
- this.calculateBounds_();
- }
- }
-
-
- if (this.markers_.length == 0) {
- // Only 1 marker in this cluster so show the marker.
- marker.setMap(this.map_);
- marker.setVisible(true);
- } else if (this.markers_.length == 1) {
- // Hide the 1 marker that was showing.
- this.markers_[0].setMap(null);
- this.markers_[0].setVisible(false);
- }
-
- marker.isAdded = true;
- this.markers_.push(marker);
-
- this.updateIcon();
- return true;
-};
-
-
-/**
- * Returns the marker clusterer that the cluster is associated with.
- *
- * @return {MarkerClusterer} The associated marker clusterer.
- */
-Cluster.prototype.getMarkerClusterer = function() {
- return this.markerClusterer_;
-};
-
-
-/**
- * Returns the bounds of the cluster.
- *
- * @return {google.maps.LatLngBounds} the cluster bounds.
- */
-Cluster.prototype.getBounds = function() {
- this.calculateBounds_();
- return this.bounds_;
-};
-
-
-/**
- * Removes the cluster
- */
-Cluster.prototype.remove = function() {
- this.clusterIcon_.remove();
- this.markers_.length = 0;
- delete this.markers_;
-};
-
-
-/**
- * Returns the center of the cluster.
- *
- * @return {number} The cluster center.
- */
-Cluster.prototype.getSize = function() {
- return this.markers_.length;
-};
-
-
-/**
- * Returns the center of the cluster.
- *
- * @return {Array.<google.maps.Marker>} The cluster center.
- */
-Cluster.prototype.getMarkers = function() {
- return this.markers_;
-};
-
-
-/**
- * Returns the center of the cluster.
- *
- * @return {google.maps.LatLng} The cluster center.
- */
-Cluster.prototype.getCenter = function() {
- return this.center_;
-};
-
-
-/**
- * Calculated the bounds of the cluster with the grid.
- *
- * @private
- */
-Cluster.prototype.calculateBounds_ = function() {
- var bounds = new google.maps.LatLngBounds(this.center_, this.center_);
- this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds);
-};
-
-
-/**
- * Determines if a marker lies in the clusters bounds.
- *
- * @param {google.maps.Marker} marker The marker to check.
- * @return {boolean} True if the marker lies in the bounds.
- */
-Cluster.prototype.isMarkerInClusterBounds = function(marker) {
- return this.bounds_.contains(marker.getPosition());
-};
-
-
-/**
- * Returns the map that the cluster is associated with.
- *
- * @return {google.maps.Map} The map.
- */
-Cluster.prototype.getMap = function() {
- return this.map_;
-};
-
-
-/**
- * Updates the cluster icon
- */
-Cluster.prototype.updateIcon = function() {
- var zoom = this.map_.getZoom();
- var mz = this.markerClusterer_.getMaxZoom();
-
- if (zoom > mz) {
- // The zoom is greater than our max zoom so show all the markers in cluster.
- for (var i = 0, marker; marker = this.markers_[i]; i++) {
- marker.setMap(this.map_);
- marker.setVisible(true);
- }
- return;
- }
-
- if (this.markers_.length < 2) {
- // We have 0 or 1 markers so hide the icon.
- this.clusterIcon_.hide();
- return;
- }
-
- var numStyles = this.markerClusterer_.getStyles().length;
- var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles);
- this.clusterIcon_.setCenter(this.center_);
- this.clusterIcon_.setSums(sums);
- this.clusterIcon_.show();
-};
-
-
-/**
- * A cluster icon
- *
- * @param {Cluster} cluster The cluster to be associated with.
- * @param {Object} styles An object that has style properties:
- * 'url': (string) The image url.
- * 'height': (number) The image height.
- * 'width': (number) The image width.
- * 'anchor': (Array) The anchor position of the label text.
- * 'textColor': (string) The text color.
- * 'textSize': (number) The text size.
- * @param {number} opt_padding Optional padding to apply to the cluster icon.
- * @constructor
- * @extends google.maps.OverlayView
- * @ignore
- */
-function ClusterIcon(cluster, styles, opt_padding) {
- cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView);
-
- this.styles_ = styles;
- this.padding_ = opt_padding || 0;
- this.cluster_ = cluster;
- this.center_ = null;
- this.map_ = cluster.getMap();
- this.div_ = null;
- this.sums_ = null;
- this.visible_ = false;
-
- this.setMap(this.map_);
-}
-
-
-/**
- * Triggers the clusterclick event and zoom's if the option is set.
- */
-ClusterIcon.prototype.triggerClusterClick = function() {
- var markerClusterer = this.cluster_.getMarkerClusterer();
-
- // Trigger the clusterclick event.
- google.maps.event.trigger(markerClusterer, 'clusterclick', this.cluster_);
-
- if (markerClusterer.isZoomOnClick()) {
- // Center the map on this cluster.
- this.map_.panTo(this.cluster_.getCenter());
-
- // Zoom into the cluster.
- this.map_.fitBounds(this.cluster_.getBounds());
- }
-};
-
-
-/**
- * Adding the cluster icon to the dom.
- * @ignore
- */
-ClusterIcon.prototype.onAdd = function() {
- this.div_ = document.createElement('DIV');
- if (this.visible_) {
- var pos = this.getPosFromLatLng_(this.center_);
- this.div_.style.cssText = this.createCss(pos);
- this.div_.innerHTML = this.sums_.text;
- }
-
- var panes = this.getPanes();
- panes.overlayImage.appendChild(this.div_);
-
- var that = this;
- google.maps.event.addDomListener(this.div_, 'click', function() {
- that.triggerClusterClick();
- });
-};
-
-
-/**
- * Returns the position to place the div dending on the latlng.
- *
- * @param {google.maps.LatLng} latlng The position in latlng.
- * @return {google.maps.Point} The position in pixels.
- * @private
- */
-ClusterIcon.prototype.getPosFromLatLng_ = function(latlng) {
- var pos = this.getProjection().fromLatLngToDivPixel(latlng);
- pos.x -= parseInt(this.width_ / 2, 10);
- pos.y -= parseInt(this.height_ / 2, 10);
- return pos;
-};
-
-
-/**
- * Draw the icon.
- * @ignore
- */
-ClusterIcon.prototype.draw = function() {
- if (this.visible_) {
- var pos = this.getPosFromLatLng_(this.center_);
- this.div_.style.top = pos.y + 'px';
- this.div_.style.left = pos.x + 'px';
- }
-};
-
-
-/**
- * Hide the icon.
- */
-ClusterIcon.prototype.hide = function() {
- if (this.div_) {
- this.div_.style.display = 'none';
- }
- this.visible_ = false;
-};
-
-
-/**
- * Position and show the icon.
- */
-ClusterIcon.prototype.show = function() {
- if (this.div_) {
- var pos = this.getPosFromLatLng_(this.center_);
- this.div_.style.cssText = this.createCss(pos);
- this.div_.style.display = '';
- }
- this.visible_ = true;
-};
-
-
-/**
- * Remove the icon from the map
- */
-ClusterIcon.prototype.remove = function() {
- this.setMap(null);
-};
-
-
-/**
- * Implementation of the onRemove interface.
- * @ignore
- */
-ClusterIcon.prototype.onRemove = function() {
- if (this.div_ && this.div_.parentNode) {
- this.hide();
- this.div_.parentNode.removeChild(this.div_);
- this.div_ = null;
- }
-};
-
-
-/**
- * Set the sums of the icon.
- *
- * @param {Object} sums The sums containing:
- * 'text': (string) The text to display in the icon.
- * 'index': (number) The style index of the icon.
- */
-ClusterIcon.prototype.setSums = function(sums) {
- this.sums_ = sums;
- this.text_ = sums.text;
- this.index_ = sums.index;
- if (this.div_) {
- this.div_.innerHTML = sums.text;
- }
-
- this.useStyle();
-};
-
-
-/**
- * Sets the icon to the the styles.
- */
-ClusterIcon.prototype.useStyle = function() {
- var index = Math.max(0, this.sums_.index - 1);
- index = Math.min(this.styles_.length - 1, index);
- var style = this.styles_[index];
- this.url_ = style['url'];
- this.height_ = style['height'];
- this.width_ = style['width'];
- this.textColor_ = style['textColor'];
- this.anchor = style['anchor'];
- this.textSize_ = style['textSize'];
-};
-
-
-/**
- * Sets the center of the icon.
- *
- * @param {google.maps.LatLng} center The latlng to set as the center.
- */
-ClusterIcon.prototype.setCenter = function(center) {
- this.center_ = center;
-};
-
-
-/**
- * Create the css text based on the position of the icon.
- *
- * @param {google.maps.Point} pos The position.
- * @return {string} The css style text.
- */
-ClusterIcon.prototype.createCss = function(pos) {
- var style = [];
- if (document.all) {
- style.push('filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(' +
- 'sizingMethod=scale,src="' + this.url_ + '");');
- } else {
- style.push('background:url(' + this.url_ + ');');
- }
-
- if (typeof this.anchor_ === 'object') {
- if (typeof this.anchor_[0] === 'number' && this.anchor_[0] > 0 &&
- this.anchor_[0] < this.height_) {
- style.push('height:' + (this.height_ - this.anchor_[0]) +
- 'px; padding-top:' + this.anchor_[0] + 'px;');
- } else {
- style.push('height:' + this.height_ + 'px; line-height:' + this.height_ +
- 'px;');
- }
- if (typeof this.anchor_[1] === 'number' && this.anchor_[1] > 0 &&
- this.anchor_[1] < this.width_) {
- style.push('width:' + (this.width_ - this.anchor_[1]) +
- 'px; padding-left:' + this.anchor_[1] + 'px;');
- } else {
- style.push('width:' + this.width_ + 'px; text-align:center;');
- }
- } else {
- style.push('height:' + this.height_ + 'px; line-height:' +
- this.height_ + 'px; width:' + this.width_ + 'px; text-align:center;');
- }
-
- var txtColor = this.textColor_ ? this.textColor_ : 'black';
- var txtSize = this.textSize_ ? this.textSize_ : 11;
-
- style.push('cursor:pointer; top:' + pos.y + 'px; left:' +
- pos.x + 'px; color:' + txtColor + '; position:absolute; font-size:' +
- txtSize + 'px; font-family:Arial,sans-serif; font-weight:bold');
- return style.join('');
-};
-
-
-// Export Symbols for Closure
-// If you are not going to compile with closure then you can remove the
-// code below.
-window['MarkerClusterer'] = MarkerClusterer;
-MarkerClusterer.prototype['addMarker'] = MarkerClusterer.prototype.addMarker;
-MarkerClusterer.prototype['addMarkers'] = MarkerClusterer.prototype.addMarkers;
-MarkerClusterer.prototype['clearMarkers'] =
- MarkerClusterer.prototype.clearMarkers;
-MarkerClusterer.prototype['getCalculator'] =
- MarkerClusterer.prototype.getCalculator;
-MarkerClusterer.prototype['getGridSize'] =
- MarkerClusterer.prototype.getGridSize;
-MarkerClusterer.prototype['getMap'] = MarkerClusterer.prototype.getMap;
-MarkerClusterer.prototype['getMarkers'] = MarkerClusterer.prototype.getMarkers;
-MarkerClusterer.prototype['getMaxZoom'] = MarkerClusterer.prototype.getMaxZoom;
-MarkerClusterer.prototype['getStyles'] = MarkerClusterer.prototype.getStyles;
-MarkerClusterer.prototype['getTotalClusters'] =
- MarkerClusterer.prototype.getTotalClusters;
-MarkerClusterer.prototype['getTotalMarkers'] =
- MarkerClusterer.prototype.getTotalMarkers;
-MarkerClusterer.prototype['redraw'] = MarkerClusterer.prototype.redraw;
-MarkerClusterer.prototype['removeMarker'] =
- MarkerClusterer.prototype.removeMarker;
-MarkerClusterer.prototype['resetViewport'] =
- MarkerClusterer.prototype.resetViewport;
-MarkerClusterer.prototype['setCalculator'] =
- MarkerClusterer.prototype.setCalculator;
-MarkerClusterer.prototype['setGridSize'] =
- MarkerClusterer.prototype.setGridSize;
-MarkerClusterer.prototype['onAdd'] = MarkerClusterer.prototype.onAdd;
-MarkerClusterer.prototype['draw'] = MarkerClusterer.prototype.draw;
-
-Cluster.prototype['getCenter'] = Cluster.prototype.getCenter;
-Cluster.prototype['getSize'] = Cluster.prototype.getSize;
-Cluster.prototype['getMarkers'] = Cluster.prototype.getMarkers;
-
-ClusterIcon.prototype['onAdd'] = ClusterIcon.prototype.onAdd;
-ClusterIcon.prototype['draw'] = ClusterIcon.prototype.draw;
-ClusterIcon.prototype['onRemove'] = ClusterIcon.prototype.onRemove;
+++ /dev/null
-function d(a){return function(b){this[a]=b}}function e(a){return function(){return this[a]}}var h; function i(a,b,c){this.extend(i,google.maps.OverlayView);this.b=a;this.a=[];this.l=[];this.Y=[53,56,66,78,90];this.h=[];this.z=false;c=c||{};this.f=c.gridSize||60;this.U=c.maxZoom||null;this.h=c.styles||[];this.T=c.imagePath||this.M;this.S=c.imageExtension||this.L;this.K=true;if(c.zoomOnClick!=undefined)this.K=c.zoomOnClick;this.p=false;if(c.averageCenter!=undefined)this.p=c.averageCenter;j(this);this.setMap(a);this.G=this.b.getZoom();var f=this;google.maps.event.addListener(this.b,"zoom_changed", function(){var g=f.b.mapTypes[f.b.getMapTypeId()].maxZoom,l=f.b.getZoom();if(!(l<0||l>g))if(f.G!=l){f.G=f.b.getZoom();f.m()}});google.maps.event.addListener(this.b,"idle",function(){f.j()});b&&b.length&&this.B(b,false)}h=i.prototype;h.M="http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/images/m";h.L="png";h.extend=function(a,b){return function(c){for(property in c.prototype)this.prototype[property]=c.prototype[property];return this}.apply(a,[b])}; h.onAdd=function(){if(!this.z){this.z=true;m(this)}};h.R=function(){};h.draw=function(){};function j(a){if(!a.h.length)for(var b=0,c;c=a.Y[b];b++)a.h.push({url:a.T+(b+1)+"."+a.S,height:c,width:c})}h.w=e("h");h.v=e("a");h.Q=e("a");h.F=function(){return this.U||this.b.mapTypes[this.b.getMapTypeId()].maxZoom};h.C=function(a,b){for(var c=0,f=a.length,g=f;g!==0;){g=parseInt(g/10,10);c++}c=Math.min(c,b);return{text:f,index:c}};h.W=d("C");h.D=e("C"); h.B=function(a,b){for(var c=0,f;f=a[c];c++)n(this,f);b||this.j()};function n(a,b){b.setVisible(false);b.setMap(null);b.r=false;b.draggable&&google.maps.event.addListener(b,"dragend",function(){b.r=false;a.m();a.j()});a.a.push(b)}h.o=function(a,b){n(this,a);b||this.j()};h.V=function(a){var b=-1;if(this.a.indexOf)b=this.a.indexOf(a);else for(var c=0,f;f=this.a[c];c++)if(f==a)b=c;if(b==-1)return false;this.a.splice(b,1);a.setVisible(false);a.setMap(null);this.m();this.j();return true};h.P=function(){return this.l.length}; h.getMap=e("b");h.setMap=d("b");h.u=e("f");h.X=d("f");function o(a,b){var c=a.getProjection(),f=new google.maps.LatLng(b.getNorthEast().lat(),b.getNorthEast().lng()),g=new google.maps.LatLng(b.getSouthWest().lat(),b.getSouthWest().lng());f=c.fromLatLngToDivPixel(f);f.x+=a.f;f.y-=a.f;g=c.fromLatLngToDivPixel(g);g.x-=a.f;g.y+=a.f;f=c.fromDivPixelToLatLng(f);c=c.fromDivPixelToLatLng(g);b.extend(f);b.extend(c);return b}h.N=function(){this.m();this.a=[]}; h.m=function(){for(var a=0,b;b=this.l[a];a++)b.remove();for(a=0;b=this.a[a];a++){b.r=false;b.setMap(null);b.setVisible(false)}this.l=[]};h.j=function(){m(this)};function m(a){if(a.z)for(var b=o(a,new google.maps.LatLngBounds(a.b.getBounds().getSouthWest(),a.b.getBounds().getNorthEast())),c=0,f;f=a.a[c];c++){var g=false;if(!f.r&&b.contains(f.getPosition())){for(var l=0,k;k=a.l[l];l++)if(!g&&k.getCenter()&&k.t.contains(f.getPosition())){g=true;k.o(f);break}if(!g){k=new p(a);k.o(f);a.l.push(k)}}}} function p(a){this.i=a;this.b=a.getMap();this.f=a.u();this.p=a.p;this.d=null;this.a=[];this.t=null;this.k=new q(this,a.w(),a.u())}h=p.prototype; h.o=function(a){var b;a:if(this.a.indexOf)b=this.a.indexOf(a)!=-1;else{b=0;for(var c;c=this.a[b];b++)if(c==a){b=true;break a}b=false}if(b)return false;if(this.d){if(this.p){b=(this.d.lat()+a.getPosition().lat())/2;c=(this.d.lng()+a.getPosition().lng())/2;this.d=new google.maps.LatLng(b,c);r(this)}}else{this.d=a.getPosition();r(this)}if(this.a.length==0){a.setMap(this.b);a.setVisible(true)}else if(this.a.length==1){this.a[0].setMap(null);this.a[0].setVisible(false)}a.r=true;this.a.push(a);if(this.b.getZoom()> this.i.F())for(a=0;b=this.a[a];a++){b.setMap(this.b);b.setVisible(true)}else if(this.a.length<2)s(this.k);else{a=this.i.w().length;b=this.i.D()(this.a,a);this.k.setCenter(this.d);a=this.k;a.A=b;a.aa=b.text;a.Z=b.index;if(a.c)a.c.innerHTML=b.text;b=Math.max(0,a.A.index-1);b=Math.min(a.h.length-1,b);b=a.h[b];a.J=b.url;a.g=b.height;a.n=b.width;a.H=b.textColor;a.anchor=b.anchor;a.I=b.textSize;this.k.show()}return true};h.getBounds=function(){r(this);return this.t}; h.remove=function(){this.k.remove();this.a.length=0;delete this.a};h.O=function(){return this.a.length};h.v=e("a");h.getCenter=e("d");function r(a){a.t=o(a.i,new google.maps.LatLngBounds(a.d,a.d))}h.getMap=e("b");function q(a,b,c){a.i.extend(q,google.maps.OverlayView);this.h=b;this.$=c||0;this.q=a;this.d=null;this.b=a.getMap();this.A=this.c=null;this.s=false;this.setMap(this.b)}h=q.prototype; h.onAdd=function(){this.c=document.createElement("DIV");if(this.s){this.c.style.cssText=t(this,u(this,this.d));this.c.innerHTML=this.A.text}this.getPanes().overlayImage.appendChild(this.c);var a=this;google.maps.event.addDomListener(this.c,"click",function(){var b=a.q.i;google.maps.event.trigger(b,"clusterclick",a.q);if(b.K){a.b.panTo(a.q.getCenter());a.b.fitBounds(a.q.getBounds())}})}; function u(a,b){var c=a.getProjection().fromLatLngToDivPixel(b);c.x-=parseInt(a.n/2,10);c.y-=parseInt(a.g/2,10);return c}h.draw=function(){if(this.s){var a=u(this,this.d);this.c.style.top=a.y+"px";this.c.style.left=a.x+"px"}};function s(a){if(a.c)a.c.style.display="none";a.s=false}h.show=function(){if(this.c){this.c.style.cssText=t(this,u(this,this.d));this.c.style.display=""}this.s=true};h.remove=function(){this.setMap(null)}; h.onRemove=function(){if(this.c&&this.c.parentNode){s(this);this.c.parentNode.removeChild(this.c);this.c=null}};h.setCenter=d("d"); function t(a,b){var c=[];document.all?c.push('filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale,src="'+a.J+'");'):c.push("background:url("+a.J+");");if(typeof a.e==="object"){typeof a.e[0]==="number"&&a.e[0]>0&&a.e[0]<a.g?c.push("height:"+(a.g-a.e[0])+"px; padding-top:"+a.e[0]+"px;"):c.push("height:"+a.g+"px; line-height:"+a.g+"px;");typeof a.e[1]==="number"&&a.e[1]>0&&a.e[1]<a.n?c.push("width:"+(a.n-a.e[1])+"px; padding-left:"+a.e[1]+"px;"):c.push("width:"+a.n+"px; text-align:center;")}else c.push("height:"+ a.g+"px; line-height:"+a.g+"px; width:"+a.n+"px; text-align:center;");c.push("cursor:pointer; top:"+b.y+"px; left:"+b.x+"px; color:"+(a.H?a.H:"black")+"; position:absolute; font-size:"+(a.I?a.I:11)+"px; font-family:Arial,sans-serif; font-weight:bold");return c.join("")}window.MarkerClusterer=i;i.prototype.addMarker=i.prototype.o;i.prototype.addMarkers=i.prototype.B;i.prototype.clearMarkers=i.prototype.N;i.prototype.getCalculator=i.prototype.D;i.prototype.getGridSize=i.prototype.u; i.prototype.getMap=i.prototype.getMap;i.prototype.getMarkers=i.prototype.v;i.prototype.getMaxZoom=i.prototype.F;i.prototype.getStyles=i.prototype.w;i.prototype.getTotalClusters=i.prototype.P;i.prototype.getTotalMarkers=i.prototype.Q;i.prototype.redraw=i.prototype.j;i.prototype.removeMarker=i.prototype.V;i.prototype.resetViewport=i.prototype.m;i.prototype.setCalculator=i.prototype.W;i.prototype.setGridSize=i.prototype.X;i.prototype.onAdd=i.prototype.onAdd;i.prototype.draw=i.prototype.draw; i.prototype.idle=i.prototype.R;p.prototype.getCenter=p.prototype.getCenter;p.prototype.getSize=p.prototype.O;p.prototype.getMarkers=p.prototype.v;q.prototype.onAdd=q.prototype.onAdd;q.prototype.draw=q.prototype.draw;q.prototype.onRemove=q.prototype.onRemove;
+++ /dev/null
-eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('6 5(c,d,e){3.Z(5,u.v.23);3.l=c;3.k=[];3.N=[];3.24=[2H,2I,2J,2K,2L];3.F=[];3.1d=r;7 f=e||{};3.G=f[\'2M\']||2N;3.1s=f[\'1t\']||w;3.F=f[\'2O\']||[];3.25=f[\'2P\']||3.26;3.27=f[\'2Q\']||3.28;3.1u=C;8(f[\'29\']!=2a){3.1u=f[\'29\']}3.12=r;8(f[\'2b\']!=2a){3.12=f[\'2b\']}3.2c();3.D(c);3.1v=3.l.1e();7 g=3;u.v.13.1w(3.l,\'2R\',6(){7 a=g.l.2d[g.l.2e()].1t;7 b=g.l.1e();8(b<0||b>a){9}8(g.1v!=b){g.1v=g.l.1e();g.K()}});u.v.13.1w(3.l,\'1f\',6(){g.H()});8(d&&d.A){3.1g(d,r)}}5.4.26=\'2S://u-v-2T-2U-2V.2W.2X/2Y/2Z/30/\'+\'31/m\';5.4.28=\'32\';5.4.Z=6(b,c){9(6(a){E(1x 33 a.4){3.4[1x]=a.4[1x]}9 3}).34(b,[c])};5.4.O=6(){3.2f(C)};5.4.1f=6(){};5.4.P=6(){};5.4.2c=6(){8(3.F.A){9}E(7 i=0,14;14=3.24[i];i++){3.F.z({1y:3.25+(i+1)+\'.\'+3.27,L:14,15:14})}};5.4.35=6(a){3.F=a};5.4.16=6(){9 3.F};5.4.2g=6(){9 3.1u};5.4.2h=6(){9 3.12};5.4.Q=6(){9 3.k};5.4.1z=6(){9 3.k};5.4.36=6(a){3.1s=a};5.4.1h=6(){9 3.1s||3.l.2d[3.l.2e()].1t};5.4.1A=6(a,b){7 c=0;7 d=a.A;7 e=d;37(e!==0){e=1B(e/10,10);c++}c=1C.2i(c,b);9{R:d,1D:c}};5.4.1E=6(a){3.1A=a};5.4.1i=6(){9 3.1A};5.4.1g=6(a,b){E(7 i=0,q;q=a[i];i++){3.1F(q)}8(!b){3.H()}};5.4.1F=6(a){a.S(r);a.D(w);a.17=r;8(a[\'38\']){7 b=3;u.v.13.1w(a,\'39\',6(){a.17=r;b.K();b.H()})}3.k.z(a)};5.4.T=6(a,b){3.1F(a);8(!b){3.H()}};5.4.1G=6(a){7 b=-1;8(3.k.1j){b=3.k.1j(a)}I{E(7 i=0,m;m=3.k[i];i++){8(m==a){b=i;3a}}}8(b==-1){9 r}3.k.3b(b,1);a.S(r);a.D(w);3.K();3.H();9 C};5.4.2f=6(a){8(!3.1d){3.1d=a;3.1H()}};5.4.1I=6(){9 3.N.A};5.4.U=6(){9 3.l};5.4.D=6(a){3.l=a};5.4.18=6(){9 3.G};5.4.1J=6(a){3.G=a};5.4.1K=6(a){7 b=3.2j();7 c=M u.v.1L(a.1M().1k(),a.1M().1l());7 d=M u.v.1L(a.1N().1k(),a.1N().1l());7 e=b.1O(c);e.x+=3.G;e.y-=3.G;7 f=b.1O(d);f.x-=3.G;f.y+=3.G;7 g=b.2k(e);7 h=b.2k(f);a.Z(g);a.Z(h);9 a};5.4.2l=6(a,b){9 b.2m(a.19())};5.4.1P=6(){3.K();3.k=[]};5.4.K=6(){E(7 i=0,1Q;1Q=3.N[i];i++){1Q.1m()}E(7 i=0,q;q=3.k[i];i++){q.17=r;q.D(w);q.S(r)}3.N=[]};5.4.H=6(){3.1H()};5.4.1H=6(){8(!3.1d){9}7 a=M u.v.2n(3.l.1n().1N(),3.l.1n().1M());7 b=3.1K(a);E(7 i=0,q;q=3.k[i];i++){7 c=r;8(!q.17&&3.2l(q,b)){E(7 j=0,d;d=3.N[j];j++){8(!c&&d.1a()&&d.2o(q)){c=C;d.T(q);3c}}8(!c){7 d=M o(3);d.T(q);3.N.z(d)}}}};6 o(a){3.V=a;3.l=a.U();3.G=a.18();3.12=a.2h();3.s=w;3.k=[];3.1o=w;3.W=M n(3,a.16(),a.18())}o.4.2p=6(a){8(3.k.1j){9 3.k.1j(a)!=-1}I{E(7 i=0,m;m=3.k[i];i++){8(m==a){9 C}}}9 r};o.4.T=6(a){8(3.2p(a)){9 r}8(!3.s){3.s=a.19();3.1p()}I{8(3.12){7 b=(3.s.1k()+a.19().1k())/2;7 c=(3.s.1l()+a.19().1l())/2;3.s=M u.v.1L(b,c);3.1p()}}8(3.k.A==0){a.D(3.l);a.S(C)}I 8(3.k.A==1){3.k[0].D(w);3.k[0].S(r)}a.17=C;3.k.z(a);3.2q();9 C};o.4.1R=6(){9 3.V};o.4.1n=6(){3.1p();9 3.1o};o.4.1m=6(){3.W.1m();3.k.A=0;3d 3.k};o.4.1S=6(){9 3.k.A};o.4.Q=6(){9 3.k};o.4.1a=6(){9 3.s};o.4.1p=6(){7 a=M u.v.2n(3.s,3.s);3.1o=3.V.1K(a)};o.4.2o=6(a){9 3.1o.2m(a.19())};o.4.U=6(){9 3.l};o.4.2q=6(){7 a=3.l.1e();7 b=3.V.1h();8(a>b){E(7 i=0,q;q=3.k[i];i++){q.D(3.l);q.S(C)}9}8(3.k.A<2){3.W.1T();9}7 c=3.V.16().A;7 d=3.V.1i()(3.k,c);3.W.2r(3.s);3.W.2s(d);3.W.2t()};6 n(a,b,c){a.1R().Z(n,u.v.23);3.F=b;3.3e=c||0;3.1b=a;3.s=w;3.l=a.U();3.p=w;3.1q=w;3.1c=r;3.D(3.l)}n.4.2u=6(){7 a=3.1b.1R();u.v.13.3f(a,\'3g\',3.1b);8(a.2g()){3.l.3h(3.1b.1a());3.l.3i(3.1b.1n())}};n.4.O=6(){3.p=2v.3j(\'3k\');8(3.1c){7 a=3.1r(3.s);3.p.X.2w=3.1U(a);3.p.2x=3.1q.R}7 b=3.3l();b.3m.3n(3.p);7 c=3;u.v.13.3o(3.p,\'3p\',6(){c.2u()})};n.4.1r=6(a){7 b=3.2j().1O(a);b.x-=1B(3.Y/2,10);b.y-=1B(3.J/2,10);9 b};n.4.P=6(){8(3.1c){7 a=3.1r(3.s);3.p.X.1V=a.y+\'t\';3.p.X.1W=a.x+\'t\'}};n.4.1T=6(){8(3.p){3.p.X.2y=\'3q\'}3.1c=r};n.4.2t=6(){8(3.p){7 a=3.1r(3.s);3.p.X.2w=3.1U(a);3.p.X.2y=\'\'}3.1c=C};n.4.1m=6(){3.D(w)};n.4.1X=6(){8(3.p&&3.p.2z){3.1T();3.p.2z.3r(3.p);3.p=w}};n.4.2s=6(a){3.1q=a;3.3s=a.R;3.3t=a.1D;8(3.p){3.p.2x=a.R}3.2A()};n.4.2A=6(){7 a=1C.3u(0,3.1q.1D-1);a=1C.2i(3.F.A-1,a);7 b=3.F[a];3.1Y=b[\'1y\'];3.J=b[\'L\'];3.Y=b[\'15\'];3.1Z=b[\'3v\'];3.2B=b[\'2B\'];3.20=b[\'3w\']};n.4.2r=6(a){3.s=a};n.4.1U=6(a){7 b=[];8(2v.3x){b.z(\'3y:3z:3A.3B.3C(\'+\'3D=3E,3F="\'+3.1Y+\'");\')}I{b.z(\'3G:1y(\'+3.1Y+\');\')}8(21 3.B===\'3H\'){8(21 3.B[0]===\'2C\'&&3.B[0]>0&&3.B[0]<3.J){b.z(\'L:\'+(3.J-3.B[0])+\'t; 2D-1V:\'+3.B[0]+\'t;\')}I{b.z(\'L:\'+3.J+\'t; 2E-L:\'+3.J+\'t;\')}8(21 3.B[1]===\'2C\'&&3.B[1]>0&&3.B[1]<3.Y){b.z(\'15:\'+(3.Y-3.B[1])+\'t; 2D-1W:\'+3.B[1]+\'t;\')}I{b.z(\'15:\'+3.Y+\'t; R-2F:2G;\')}}I{b.z(\'L:\'+3.J+\'t; 2E-L:\'+3.J+\'t; 15:\'+3.Y+\'t; R-2F:2G;\')}7 c=3.1Z?3.1Z:\'3I\';7 d=3.20?3.20:11;b.z(\'3J:3K; 1V:\'+a.y+\'t; 1W:\'+a.x+\'t; 3L:\'+c+\'; 3M:3N; 22-14:\'+d+\'t; 22-3O:3P,3Q-3R; 22-3S:3T\');9 b.3U(\'\')};3V[\'5\']=5;5.4[\'T\']=5.4.T;5.4[\'1g\']=5.4.1g;5.4[\'1P\']=5.4.1P;5.4[\'1i\']=5.4.1i;5.4[\'18\']=5.4.18;5.4[\'U\']=5.4.U;5.4[\'Q\']=5.4.Q;5.4[\'1h\']=5.4.1h;5.4[\'16\']=5.4.16;5.4[\'1I\']=5.4.1I;5.4[\'1z\']=5.4.1z;5.4[\'H\']=5.4.H;5.4[\'1G\']=5.4.1G;5.4[\'K\']=5.4.K;5.4[\'1E\']=5.4.1E;5.4[\'1J\']=5.4.1J;5.4[\'O\']=5.4.O;5.4[\'P\']=5.4.P;5.4[\'1f\']=5.4.1f;o.4[\'1a\']=o.4.1a;o.4[\'1S\']=o.4.1S;o.4[\'Q\']=o.4.Q;n.4[\'O\']=n.4.O;n.4[\'P\']=n.4.P;n.4[\'1X\']=n.4.1X;',62,244,'|||this|prototype|MarkerClusterer|function|var|if|return|||||||||||markers_|map_||ClusterIcon|Cluster|div_|marker|false|center_|px|google|maps|null|||push|length|anchor_|true|setMap|for|styles_|gridSize_|redraw|else|height_|resetViewport|height|new|clusters_|onAdd|draw|getMarkers|text|setVisible|addMarker|getMap|markerClusterer_|clusterIcon_|style|width_|extend|||averageCenter_|event|size|width|getStyles|isAdded|getGridSize|getPosition|getCenter|cluster_|visible_|ready_|getZoom|idle|addMarkers|getMaxZoom|getCalculator|indexOf|lat|lng|remove|getBounds|bounds_|calculateBounds_|sums_|getPosFromLatLng_|maxZoom_|maxZoom|zoomOnClick_|prevZoom_|addListener|property|url|getTotalMarkers|calculator_|parseInt|Math|index|setCalculator|pushMarkerTo_|removeMarker|createClusters_|getTotalClusters|setGridSize|getExtendedBounds|LatLng|getNorthEast|getSouthWest|fromLatLngToDivPixel|clearMarkers|cluster|getMarkerClusterer|getSize|hide|createCss|top|left|onRemove|url_|textColor_|textSize_|typeof|font|OverlayView|sizes|imagePath_|MARKER_CLUSTER_IMAGE_PATH_|imageExtension_|MARKER_CLUSTER_IMAGE_EXTENSION_|zoomOnClick|undefined|averageCenter|setupStyles_|mapTypes|getMapTypeId|setReady_|isZoomOnClick|isAverageCenter|min|getProjection|fromDivPixelToLatLng|isMarkerInBounds_|contains|LatLngBounds|isMarkerInClusterBounds|isMarkerAlreadyAdded|updateIcon|setCenter|setSums|show|triggerClusterClick|document|cssText|innerHTML|display|parentNode|useStyle|anchor|number|padding|line|align|center|53|56|66|78|90|gridSize|60|styles|imagePath|imageExtension|zoom_changed|http|utility|library|v3|googlecode|com|svn|trunk|markerclusterer|images|png|in|apply|setStyles|setMaxZoom|while|draggable|dragend|continue|splice|break|delete|padding_|trigger|clusterclick|panTo|fitBounds|createElement|DIV|getPanes|overlayImage|appendChild|addDomListener|click|none|removeChild|text_|index_|max|textColor|textSize|all|filter|progid|DXImageTransform|Microsoft|AlphaImageLoader|sizingMethod|scale|src|background|object|black|cursor|pointer|color|position|absolute|family|Arial|sans|serif|weight|bold|join|window'.split('|'),0,{}))
/* XXX Events XXX */
// this.$element.on('show.Datatables', this.on_show);
- this.el().on('show', this, this.on_show);
+ this.elmt().on('show', this, this.on_show);
// Unbind all events using namespacing
// TODO in destructor
// $(window).unbind('Hazelnut');
};
// the intention here is that options.datatables_options as coming from the python object take precedence
// XXX DISABLED by jordan: was causing errors in datatables.js $.extend(actual_options, options.datatables_options );
- this.table = this.el('table').dataTable(actual_options);
+ this.table = this.elmt('table').dataTable(actual_options);
/* Setup the SelectAll button in the dataTable header */
/* xxx not sure this is still working */
* Handle clicks on checkboxes: reassociate checkbox click every time
* the table is redrawn
*/
- this.els('hazelnut-checkbox').unbind('click').click(this, this._check_click);
+ this.elts('hazelnut-checkbox').unbind('click').click(this, this._check_click);
if (!this.table)
return;
show_hide_button: function()
{
- // this.id, this.el, this.cl, this.els
+ // this.id, this.el, this.cl, this.elts
// same output as a jquery selector with some guarantees
},
this.listen_query(options.query_uuid);
- this.els('queryeditor-auto-filter').change(this.event_filter_added('='));
- this.els('queryeditor-filter').change(this.event_filter_added('='));
- this.els('queryeditor-filter-min').change(this.event_filter_added('>'));
- this.els('queryeditor-filter-max').change(this.event_filter_added('<'));
+ this.elts('queryeditor-auto-filter').change(this.event_filter_added('='));
+ this.elts('queryeditor-filter').change(this.event_filter_added('='));
+ this.elts('queryeditor-filter-min').change(this.event_filter_added('>'));
+ this.elts('queryeditor-filter-max').change(this.event_filter_added('<'));
var self = this;
- this.els('queryeditor-check').click(function() {
+ this.elts('queryeditor-check').click(function() {
manifold.raise_event(self.options.query_uuid, this.checked?FIELD_ADDED:FIELD_REMOVED, this.value);
});
nCloneTh.innerHTML = '<b>Info</b>';
nCloneTd.className = "center";
nCloneTh.className = "center";
- this.el('table thead tr').each(function() {
+ this.elmt('table thead tr').each(function() {
this.insertBefore(nCloneTh, this.childNodes[0]);
});
- this.el('table tbody tr').each(function() {
+ this.elmt('table tbody tr').each(function() {
this.insertBefore(nCloneTd.cloneNode( true ), this.childNodes[0]);
});
*/
// We are currently using a DataTable display, but another browsing component could be better
//jQuery('#'+this.options.plugin_uuid+'-table').dataTable...
- var metaTable = this.el('table').dataTable({
+ var metaTable = this.elmt('table').dataTable({
bFilter : false,
bPaginate : false,
bInfo : false,
var self = this;
// Actions on the newly added fields
- this.el('table tbody td span').on('click', function() {
+ this.elmt('table tbody td span').on('click', function() {
var nTr = this.parentNode.parentNode;
// use jQuery UI instead of images to keep a common UI
// class="ui-icon treeclick ui-icon-triangle-1-s tree-minus"
}
});
- this.el('table_wrapper').css({
+ this.elmt('table_wrapper').css({
'padding-top' : '0em',
'padding-bottom': '0em'
});
check_field: function(field)
{
- this.el('check', field).attr('checked', true);
+ this.elmt('check', field).attr('checked', true);
},
uncheck_field: function(field)
{
- this.el('check', field).attr('checked', false);
+ this.elmt('check', field).attr('checked', false);
},
update_filter_value: function(filter, removed)
var id = this.id_from_field(key);
if (op == '=') {
- var element = this.el(id);
+ var element = this.elmt(id);
} else {
var suffix;
if (op == '<') {
- this.el(id, 'max').val(value);
+ this.elmt(id, 'max').val(value);
} else if (op == '>') {
- this.el(id, 'min').val(value);
+ this.elmt(id, 'min').val(value);
} else {
return;
}
- var element = this.el(id, suffix);
+ var element = this.elmt(id, suffix);
}
element.val(removed?null:value);
this._super(options, element);
var self = this;
- this.table = this.el('table').dataTable({
+ this.table = this.elmt('table').dataTable({
//sPaginationType: 'full_numbers', // Use pagination
sPaginationType: 'bootstrap',
//bJQueryUI : true,
});
// XXX This should not be done at init...
- this.el('update').click(this, this.do_update);
- this.el('refresh').click(this, this.do_refresh);
- this.el('reset').click(this, this.do_reset);
- this.el('clear_annotations').click(this, this.do_clear_annotations);
+ this.elmt('update').click(this, this.do_update);
+ this.elmt('refresh').click(this, this.do_refresh);
+ this.elmt('reset').click(this, this.do_reset);
+ this.elmt('clear_annotations').click(this, this.do_clear_annotations);
this.listen_query(options.query_uuid);
},
set_button_state: function(name, state)
{
- this.el(name).attr('disabled', state ? false : 'disabled');
+ this.elmt(name).attr('disabled', state ? false : 'disabled');
},
clear: function()
arm_button: function()
{
- this.el('updater').click(this, this.submit_update_request);
+ this.elmt('updater').click(this, this.submit_update_request);
},
submit_update_request: function (e)
disable_update_button: function()
{
- this.el('updater').attr('disabled', 'disabled');
+ this.elmt('updater').attr('disabled', 'disabled');
},
/*************************** QUERY HANDLER ****************************/
from django.template import RequestContext
from django.shortcuts import render_to_response
+from manifold.manifoldresult import ManifoldResult
from myslice.viewutils import topmenu_items, the_user
from myslice.config import Config
# pass request within the token, so manifold session key can be attached to the request session.
token = {'username': username, 'password': password, 'request': request}
- user = authenticate(token=token)
- if user is not None:
+ # our authenticate function returns either
+ # . a ManifoldResult - when something has gone wrong, like e.g. backend is unreachable
+ # . a django User in case of success
+ # . or None if the backend could be reached but the authentication failed
+ auth_result = authenticate(token=token)
+ # high-level errors, like connection refused or the like
+ if isinstance (auth_result, ManifoldResult):
+ manifoldresult = auth_result
+ # let's use ManifoldResult.__repr__
+ env['state']="%s"%manifoldresult
+ return render_to_response('home-view.html',env, context_instance=RequestContext(request))
+ # user was authenticated at the backend
+ elif auth_result is not None:
+ user=auth_result
if user.is_active:
print "LOGGING IN"
login(request, user)
else:
env['state'] = "Your account is not active, please contact the site admin."
return render_to_response('home-view.html',env, context_instance=RequestContext(request))
+ # otherwise
else:
env['state'] = "Your username and/or password were incorrect."
return render_to_response('home-view.html',env, context_instance=RequestContext(request))
from plugins.tabs.tabs import Tabs
from plugins.hazelnut import Hazelnut
from plugins.resources_selected import ResourcesSelected
-from plugins.googlemaps import GoogleMaps
+from plugins.googlemap import GoogleMap
from plugins.senslabmap.senslabmap import SensLabMap
from plugins.querycode.querycode import QueryCode
from plugins.query_editor import QueryEditor
page = Page(request)
page.add_css_files ('css/slice-view.css')
page.add_js_chunks ('$(function() { console.log("sliceview: jQuery version " + $.fn.jquery); });')
- page.add_js_chunks ('$(function() { console.log("users turned %s"); });'%("on" if do_query_users else "off"))
+ page.add_js_chunks ('$(function() { console.log("sliceview: users turned %s"); });'%("on" if do_query_users else "off"))
page.expose_js_metadata()
metadata = page.get_metadata()
},
)
- resources_as_map = GoogleMaps(
+ resources_as_map = GoogleMap(
page = page,
title = 'Geographic view',
domid = 'gmap',
--- /dev/null
+/*jslint browser: true, confusion: true, sloppy: true, vars: true, nomen: false, plusplus: false, indent: 2 */
+/*global window,google */
+
+/**
+ * @name MarkerClustererPlus for Google Maps V3
+ * @version 2.0.16 [October 18, 2012]
+ * @author Gary Little
+ * @fileoverview
+ * The library creates and manages per-zoom-level clusters for large amounts of markers.
+ * <p>
+ * This is an enhanced V3 implementation of the
+ * <a href="http://gmaps-utility-library-dev.googlecode.com/svn/tags/markerclusterer/"
+ * >V2 MarkerClusterer</a> by Xiaoxi Wu. It is based on the
+ * <a href="http://google-maps-utility-library-v3.googlecode.com/svn/tags/markerclusterer/"
+ * >V3 MarkerClusterer</a> port by Luke Mahe. MarkerClustererPlus was created by Gary Little.
+ * <p>
+ * v2.0 release: MarkerClustererPlus v2.0 is backward compatible with MarkerClusterer v1.0. It
+ * adds support for the <code>ignoreHidden</code>, <code>title</code>, <code>printable</code>,
+ * <code>batchSizeIE</code>, and <code>calculator</code> properties as well as support for
+ * four more events. It also allows greater control over the styling of the text that appears
+ * on the cluster marker. The documentation has been significantly improved and the overall
+ * code has been simplified and polished. Very large numbers of markers can now be managed
+ * without causing Javascript timeout errors on Internet Explorer. Note that the name of the
+ * <code>clusterclick</code> event has been deprecated. The new name is <code>click</code>,
+ * so please change your application code now.
+ */
+
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+/**
+ * @name ClusterIconStyle
+ * @class This class represents the object for values in the <code>styles</code> array passed
+ * to the {@link MarkerClusterer} constructor. The element in this array that is used to
+ * style the cluster icon is determined by calling the <code>calculator</code> function.
+ *
+ * @property {string} url The URL of the cluster icon image file. Required.
+ * @property {number} height The height (in pixels) of the cluster icon. Required.
+ * @property {number} width The width (in pixels) of the cluster icon. Required.
+ * @property {Array} [anchor] The anchor position (in pixels) of the label text to be shown on
+ * the cluster icon, relative to the top left corner of the icon.
+ * The format is <code>[yoffset, xoffset]</code>. The <code>yoffset</code> must be positive
+ * and less than <code>height</code> and the <code>xoffset</code> must be positive and less
+ * than <code>width</code>. The default is to anchor the label text so that it is centered
+ * on the icon.
+ * @property {Array} [anchorIcon] The anchor position (in pixels) of the cluster icon. This is the
+ * spot on the cluster icon that is to be aligned with the cluster position. The format is
+ * <code>[yoffset, xoffset]</code> where <code>yoffset</code> increases as you go down and
+ * <code>xoffset</code> increases to the right. The default anchor position is the center of the
+ * cluster icon.
+ * @property {string} [textColor="black"] The color of the label text shown on the
+ * cluster icon.
+ * @property {number} [textSize=11] The size (in pixels) of the label text shown on the
+ * cluster icon.
+ * @property {number} [textDecoration="none"] The value of the CSS <code>text-decoration</code>
+ * property for the label text shown on the cluster icon.
+ * @property {number} [fontWeight="bold"] The value of the CSS <code>font-weight</code>
+ * property for the label text shown on the cluster icon.
+ * @property {number} [fontStyle="normal"] The value of the CSS <code>font-style</code>
+ * property for the label text shown on the cluster icon.
+ * @property {number} [fontFamily="Arial,sans-serif"] The value of the CSS <code>font-family</code>
+ * property for the label text shown on the cluster icon.
+ * @property {string} [backgroundPosition="0 0"] The position of the cluster icon image
+ * within the image defined by <code>url</code>. The format is <code>"xpos ypos"</code>
+ * (the same format as for the CSS <code>background-position</code> property). You must set
+ * this property appropriately when the image defined by <code>url</code> represents a sprite
+ * containing multiple images.
+ */
+/**
+ * @name ClusterIconInfo
+ * @class This class is an object containing general information about a cluster icon. This is
+ * the object that a <code>calculator</code> function returns.
+ *
+ * @property {string} text The text of the label to be shown on the cluster icon.
+ * @property {number} index The index plus 1 of the element in the <code>styles</code>
+ * array to be used to style the cluster icon.
+ * @property {string} title The tooltip to display when the mouse moves over the cluster icon.
+ * If this value is <code>undefined</code> or <code>""</code>, <code>title</code> is set to the
+ * value of the <code>title</code> property passed to the MarkerClusterer.
+ */
+/**
+ * A cluster icon.
+ *
+ * @constructor
+ * @extends google.maps.OverlayView
+ * @param {Cluster} cluster The cluster with which the icon is to be associated.
+ * @param {Array} [styles] An array of {@link ClusterIconStyle} defining the cluster icons
+ * to use for various cluster sizes.
+ * @private
+ */
+function ClusterIcon(cluster, styles) {
+ cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView);
+
+ this.cluster_ = cluster;
+ this.className_ = cluster.getMarkerClusterer().getClusterClass();
+ this.styles_ = styles;
+ this.center_ = null;
+ this.div_ = null;
+ this.sums_ = null;
+ this.visible_ = false;
+
+ this.setMap(cluster.getMap()); // Note: this causes onAdd to be called
+}
+
+
+/**
+ * Adds the icon to the DOM.
+ */
+ClusterIcon.prototype.onAdd = function () {
+ var cClusterIcon = this;
+ var cMouseDownInCluster;
+ var cDraggingMapByCluster;
+
+ this.div_ = document.createElement("div");
+ this.div_.className = this.className_;
+ if (this.visible_) {
+ this.show();
+ }
+
+ this.getPanes().overlayMouseTarget.appendChild(this.div_);
+
+ // Fix for Issue 157
+ this.boundsChangedListener_ = google.maps.event.addListener(this.getMap(), "bounds_changed", function () {
+ cDraggingMapByCluster = cMouseDownInCluster;
+ });
+
+ google.maps.event.addDomListener(this.div_, "mousedown", function () {
+ cMouseDownInCluster = true;
+ cDraggingMapByCluster = false;
+ });
+
+ google.maps.event.addDomListener(this.div_, "click", function (e) {
+ cMouseDownInCluster = false;
+ if (!cDraggingMapByCluster) {
+ var theBounds;
+ var mz;
+ var mc = cClusterIcon.cluster_.getMarkerClusterer();
+ /**
+ * This event is fired when a cluster marker is clicked.
+ * @name MarkerClusterer#click
+ * @param {Cluster} c The cluster that was clicked.
+ * @event
+ */
+ google.maps.event.trigger(mc, "click", cClusterIcon.cluster_);
+ google.maps.event.trigger(mc, "clusterclick", cClusterIcon.cluster_); // deprecated name
+
+ // The default click handler follows. Disable it by setting
+ // the zoomOnClick property to false.
+ if (mc.getZoomOnClick()) {
+ // Zoom into the cluster.
+ mz = mc.getMaxZoom();
+ theBounds = cClusterIcon.cluster_.getBounds();
+ mc.getMap().fitBounds(theBounds);
+ // There is a fix for Issue 170 here:
+ setTimeout(function () {
+ mc.getMap().fitBounds(theBounds);
+ // Don't zoom beyond the max zoom level
+ if (mz !== null && (mc.getMap().getZoom() > mz)) {
+ mc.getMap().setZoom(mz + 1);
+ }
+ }, 100);
+ }
+
+ // Prevent event propagation to the map:
+ e.cancelBubble = true;
+ if (e.stopPropagation) {
+ e.stopPropagation();
+ }
+ }
+ });
+
+ google.maps.event.addDomListener(this.div_, "mouseover", function () {
+ var mc = cClusterIcon.cluster_.getMarkerClusterer();
+ /**
+ * This event is fired when the mouse moves over a cluster marker.
+ * @name MarkerClusterer#mouseover
+ * @param {Cluster} c The cluster that the mouse moved over.
+ * @event
+ */
+ google.maps.event.trigger(mc, "mouseover", cClusterIcon.cluster_);
+ });
+
+ google.maps.event.addDomListener(this.div_, "mouseout", function () {
+ var mc = cClusterIcon.cluster_.getMarkerClusterer();
+ /**
+ * This event is fired when the mouse moves out of a cluster marker.
+ * @name MarkerClusterer#mouseout
+ * @param {Cluster} c The cluster that the mouse moved out of.
+ * @event
+ */
+ google.maps.event.trigger(mc, "mouseout", cClusterIcon.cluster_);
+ });
+};
+
+
+/**
+ * Removes the icon from the DOM.
+ */
+ClusterIcon.prototype.onRemove = function () {
+ if (this.div_ && this.div_.parentNode) {
+ this.hide();
+ google.maps.event.removeListener(this.boundsChangedListener_);
+ google.maps.event.clearInstanceListeners(this.div_);
+ this.div_.parentNode.removeChild(this.div_);
+ this.div_ = null;
+ }
+};
+
+
+/**
+ * Draws the icon.
+ */
+ClusterIcon.prototype.draw = function () {
+ if (this.visible_) {
+ var pos = this.getPosFromLatLng_(this.center_);
+ this.div_.style.top = pos.y + "px";
+ this.div_.style.left = pos.x + "px";
+ }
+};
+
+
+/**
+ * Hides the icon.
+ */
+ClusterIcon.prototype.hide = function () {
+ if (this.div_) {
+ this.div_.style.display = "none";
+ }
+ this.visible_ = false;
+};
+
+
+/**
+ * Positions and shows the icon.
+ */
+ClusterIcon.prototype.show = function () {
+ if (this.div_) {
+ var pos = this.getPosFromLatLng_(this.center_);
+ this.div_.style.cssText = this.createCss(pos);
+ if (this.cluster_.printable_) {
+ // (Would like to use "width: inherit;" below, but doesn't work with MSIE)
+ this.div_.innerHTML = "<img src='" + this.url_ + "'><div style='position: absolute; top: 0px; left: 0px; width: " + this.width_ + "px;'>" + this.sums_.text + "</div>";
+ } else {
+ this.div_.innerHTML = this.sums_.text;
+ }
+ if (typeof this.sums_.title === "undefined" || this.sums_.title === "") {
+ this.div_.title = this.cluster_.getMarkerClusterer().getTitle();
+ } else {
+ this.div_.title = this.sums_.title;
+ }
+ this.div_.style.display = "";
+ }
+ this.visible_ = true;
+};
+
+
+/**
+ * Sets the icon styles to the appropriate element in the styles array.
+ *
+ * @param {ClusterIconInfo} sums The icon label text and styles index.
+ */
+ClusterIcon.prototype.useStyle = function (sums) {
+ this.sums_ = sums;
+ var index = Math.max(0, sums.index - 1);
+ index = Math.min(this.styles_.length - 1, index);
+ var style = this.styles_[index];
+ this.url_ = style.url;
+ this.height_ = style.height;
+ this.width_ = style.width;
+ this.anchor_ = style.anchor;
+ this.anchorIcon_ = style.anchorIcon || [parseInt(this.height_ / 2, 10), parseInt(this.width_ / 2, 10)];
+ this.textColor_ = style.textColor || "black";
+ this.textSize_ = style.textSize || 11;
+ this.textDecoration_ = style.textDecoration || "none";
+ this.fontWeight_ = style.fontWeight || "bold";
+ this.fontStyle_ = style.fontStyle || "normal";
+ this.fontFamily_ = style.fontFamily || "Arial,sans-serif";
+ this.backgroundPosition_ = style.backgroundPosition || "0 0";
+};
+
+
+/**
+ * Sets the position at which to center the icon.
+ *
+ * @param {google.maps.LatLng} center The latlng to set as the center.
+ */
+ClusterIcon.prototype.setCenter = function (center) {
+ this.center_ = center;
+};
+
+
+/**
+ * Creates the cssText style parameter based on the position of the icon.
+ *
+ * @param {google.maps.Point} pos The position of the icon.
+ * @return {string} The CSS style text.
+ */
+ClusterIcon.prototype.createCss = function (pos) {
+ var style = [];
+ if (!this.cluster_.printable_) {
+ style.push('background-image:url(' + this.url_ + ');');
+ style.push('background-position:' + this.backgroundPosition_ + ';');
+ }
+
+ if (typeof this.anchor_ === 'object') {
+ if (typeof this.anchor_[0] === 'number' && this.anchor_[0] > 0 &&
+ this.anchor_[0] < this.height_) {
+ style.push('height:' + (this.height_ - this.anchor_[0]) +
+ 'px; padding-top:' + this.anchor_[0] + 'px;');
+ } else {
+ style.push('height:' + this.height_ + 'px; line-height:' + this.height_ +
+ 'px;');
+ }
+ if (typeof this.anchor_[1] === 'number' && this.anchor_[1] > 0 &&
+ this.anchor_[1] < this.width_) {
+ style.push('width:' + (this.width_ - this.anchor_[1]) +
+ 'px; padding-left:' + this.anchor_[1] + 'px;');
+ } else {
+ style.push('width:' + this.width_ + 'px; text-align:center;');
+ }
+ } else {
+ style.push('height:' + this.height_ + 'px; line-height:' +
+ this.height_ + 'px; width:' + this.width_ + 'px; text-align:center;');
+ }
+
+ style.push('cursor:pointer; top:' + pos.y + 'px; left:' +
+ pos.x + 'px; color:' + this.textColor_ + '; position:absolute; font-size:' +
+ this.textSize_ + 'px; font-family:' + this.fontFamily_ + '; font-weight:' +
+ this.fontWeight_ + '; font-style:' + this.fontStyle_ + '; text-decoration:' +
+ this.textDecoration_ + ';');
+
+ return style.join("");
+};
+
+
+/**
+ * Returns the position at which to place the DIV depending on the latlng.
+ *
+ * @param {google.maps.LatLng} latlng The position in latlng.
+ * @return {google.maps.Point} The position in pixels.
+ */
+ClusterIcon.prototype.getPosFromLatLng_ = function (latlng) {
+ var pos = this.getProjection().fromLatLngToDivPixel(latlng);
+ pos.x -= this.anchorIcon_[1];
+ pos.y -= this.anchorIcon_[0];
+ return pos;
+};
+
+
+/**
+ * Creates a single cluster that manages a group of proximate markers.
+ * Used internally, do not call this constructor directly.
+ * @constructor
+ * @param {MarkerClusterer} mc The <code>MarkerClusterer</code> object with which this
+ * cluster is associated.
+ */
+function Cluster(mc) {
+ this.markerClusterer_ = mc;
+ this.map_ = mc.getMap();
+ this.gridSize_ = mc.getGridSize();
+ this.minClusterSize_ = mc.getMinimumClusterSize();
+ this.averageCenter_ = mc.getAverageCenter();
+ this.printable_ = mc.getPrintable();
+ this.markers_ = [];
+ this.center_ = null;
+ this.bounds_ = null;
+ this.clusterIcon_ = new ClusterIcon(this, mc.getStyles());
+}
+
+
+/**
+ * Returns the number of markers managed by the cluster. You can call this from
+ * a <code>click</code>, <code>mouseover</code>, or <code>mouseout</code> event handler
+ * for the <code>MarkerClusterer</code> object.
+ *
+ * @return {number} The number of markers in the cluster.
+ */
+Cluster.prototype.getSize = function () {
+ return this.markers_.length;
+};
+
+
+/**
+ * Returns the array of markers managed by the cluster. You can call this from
+ * a <code>click</code>, <code>mouseover</code>, or <code>mouseout</code> event handler
+ * for the <code>MarkerClusterer</code> object.
+ *
+ * @return {Array} The array of markers in the cluster.
+ */
+Cluster.prototype.getMarkers = function () {
+ return this.markers_;
+};
+
+
+/**
+ * Returns the center of the cluster. You can call this from
+ * a <code>click</code>, <code>mouseover</code>, or <code>mouseout</code> event handler
+ * for the <code>MarkerClusterer</code> object.
+ *
+ * @return {google.maps.LatLng} The center of the cluster.
+ */
+Cluster.prototype.getCenter = function () {
+ return this.center_;
+};
+
+
+/**
+ * Returns the map with which the cluster is associated.
+ *
+ * @return {google.maps.Map} The map.
+ * @ignore
+ */
+Cluster.prototype.getMap = function () {
+ return this.map_;
+};
+
+
+/**
+ * Returns the <code>MarkerClusterer</code> object with which the cluster is associated.
+ *
+ * @return {MarkerClusterer} The associated marker clusterer.
+ * @ignore
+ */
+Cluster.prototype.getMarkerClusterer = function () {
+ return this.markerClusterer_;
+};
+
+
+/**
+ * Returns the bounds of the cluster.
+ *
+ * @return {google.maps.LatLngBounds} the cluster bounds.
+ * @ignore
+ */
+Cluster.prototype.getBounds = function () {
+ var i;
+ var bounds = new google.maps.LatLngBounds(this.center_, this.center_);
+ var markers = this.getMarkers();
+ for (i = 0; i < markers.length; i++) {
+ bounds.extend(markers[i].getPosition());
+ }
+ return bounds;
+};
+
+
+/**
+ * Removes the cluster from the map.
+ *
+ * @ignore
+ */
+Cluster.prototype.remove = function () {
+ this.clusterIcon_.setMap(null);
+ this.markers_ = [];
+ delete this.markers_;
+};
+
+
+/**
+ * Adds a marker to the cluster.
+ *
+ * @param {google.maps.Marker} marker The marker to be added.
+ * @return {boolean} True if the marker was added.
+ * @ignore
+ */
+Cluster.prototype.addMarker = function (marker) {
+ var i;
+ var mCount;
+ var mz;
+
+ if (this.isMarkerAlreadyAdded_(marker)) {
+ return false;
+ }
+
+ if (!this.center_) {
+ this.center_ = marker.getPosition();
+ this.calculateBounds_();
+ } else {
+ if (this.averageCenter_) {
+ var l = this.markers_.length + 1;
+ var lat = (this.center_.lat() * (l - 1) + marker.getPosition().lat()) / l;
+ var lng = (this.center_.lng() * (l - 1) + marker.getPosition().lng()) / l;
+ this.center_ = new google.maps.LatLng(lat, lng);
+ this.calculateBounds_();
+ }
+ }
+
+ marker.isAdded = true;
+ this.markers_.push(marker);
+
+ mCount = this.markers_.length;
+ mz = this.markerClusterer_.getMaxZoom();
+ if (mz !== null && this.map_.getZoom() > mz) {
+ // Zoomed in past max zoom, so show the marker.
+ if (marker.getMap() !== this.map_) {
+ marker.setMap(this.map_);
+ }
+ } else if (mCount < this.minClusterSize_) {
+ // Min cluster size not reached so show the marker.
+ if (marker.getMap() !== this.map_) {
+ marker.setMap(this.map_);
+ }
+ } else if (mCount === this.minClusterSize_) {
+ // Hide the markers that were showing.
+ for (i = 0; i < mCount; i++) {
+ this.markers_[i].setMap(null);
+ }
+ } else {
+ marker.setMap(null);
+ }
+
+ this.updateIcon_();
+ return true;
+};
+
+
+/**
+ * Determines if a marker lies within the cluster's bounds.
+ *
+ * @param {google.maps.Marker} marker The marker to check.
+ * @return {boolean} True if the marker lies in the bounds.
+ * @ignore
+ */
+Cluster.prototype.isMarkerInClusterBounds = function (marker) {
+ return this.bounds_.contains(marker.getPosition());
+};
+
+
+/**
+ * Calculates the extended bounds of the cluster with the grid.
+ */
+Cluster.prototype.calculateBounds_ = function () {
+ var bounds = new google.maps.LatLngBounds(this.center_, this.center_);
+ this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds);
+};
+
+
+/**
+ * Updates the cluster icon.
+ */
+Cluster.prototype.updateIcon_ = function () {
+ var mCount = this.markers_.length;
+ var mz = this.markerClusterer_.getMaxZoom();
+
+ if (mz !== null && this.map_.getZoom() > mz) {
+ this.clusterIcon_.hide();
+ return;
+ }
+
+ if (mCount < this.minClusterSize_) {
+ // Min cluster size not yet reached.
+ this.clusterIcon_.hide();
+ return;
+ }
+
+ var numStyles = this.markerClusterer_.getStyles().length;
+ var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles);
+ this.clusterIcon_.setCenter(this.center_);
+ this.clusterIcon_.useStyle(sums);
+ this.clusterIcon_.show();
+};
+
+
+/**
+ * Determines if a marker has already been added to the cluster.
+ *
+ * @param {google.maps.Marker} marker The marker to check.
+ * @return {boolean} True if the marker has already been added.
+ */
+Cluster.prototype.isMarkerAlreadyAdded_ = function (marker) {
+ var i;
+ if (this.markers_.indexOf) {
+ return this.markers_.indexOf(marker) !== -1;
+ } else {
+ for (i = 0; i < this.markers_.length; i++) {
+ if (marker === this.markers_[i]) {
+ return true;
+ }
+ }
+ }
+ return false;
+};
+
+
+/**
+ * @name MarkerClustererOptions
+ * @class This class represents the optional parameter passed to
+ * the {@link MarkerClusterer} constructor.
+ * @property {number} [gridSize=60] The grid size of a cluster in pixels. The grid is a square.
+ * @property {number} [maxZoom=null] The maximum zoom level at which clustering is enabled or
+ * <code>null</code> if clustering is to be enabled at all zoom levels.
+ * @property {boolean} [zoomOnClick=true] Whether to zoom the map when a cluster marker is
+ * clicked. You may want to set this to <code>false</code> if you have installed a handler
+ * for the <code>click</code> event and it deals with zooming on its own.
+ * @property {boolean} [averageCenter=false] Whether the position of a cluster marker should be
+ * the average position of all markers in the cluster. If set to <code>false</code>, the
+ * cluster marker is positioned at the location of the first marker added to the cluster.
+ * @property {number} [minimumClusterSize=2] The minimum number of markers needed in a cluster
+ * before the markers are hidden and a cluster marker appears.
+ * @property {boolean} [ignoreHidden=false] Whether to ignore hidden markers in clusters. You
+ * may want to set this to <code>true</code> to ensure that hidden markers are not included
+ * in the marker count that appears on a cluster marker (this count is the value of the
+ * <code>text</code> property of the result returned by the default <code>calculator</code>).
+ * If set to <code>true</code> and you change the visibility of a marker being clustered, be
+ * sure to also call <code>MarkerClusterer.repaint()</code>.
+ * @property {boolean} [printable=false] Whether to make the cluster icons printable. Do not
+ * set to <code>true</code> if the <code>url</code> fields in the <code>styles</code> array
+ * refer to image sprite files.
+ * @property {string} [title=""] The tooltip to display when the mouse moves over a cluster
+ * marker. (Alternatively, you can use a custom <code>calculator</code> function to specify a
+ * different tooltip for each cluster marker.)
+ * @property {function} [calculator=MarkerClusterer.CALCULATOR] The function used to determine
+ * the text to be displayed on a cluster marker and the index indicating which style to use
+ * for the cluster marker. The input parameters for the function are (1) the array of markers
+ * represented by a cluster marker and (2) the number of cluster icon styles. It returns a
+ * {@link ClusterIconInfo} object. The default <code>calculator</code> returns a
+ * <code>text</code> property which is the number of markers in the cluster and an
+ * <code>index</code> property which is one higher than the lowest integer such that
+ * <code>10^i</code> exceeds the number of markers in the cluster, or the size of the styles
+ * array, whichever is less. The <code>styles</code> array element used has an index of
+ * <code>index</code> minus 1. For example, the default <code>calculator</code> returns a
+ * <code>text</code> value of <code>"125"</code> and an <code>index</code> of <code>3</code>
+ * for a cluster icon representing 125 markers so the element used in the <code>styles</code>
+ * array is <code>2</code>. A <code>calculator</code> may also return a <code>title</code>
+ * property that contains the text of the tooltip to be used for the cluster marker. If
+ * <code>title</code> is not defined, the tooltip is set to the value of the <code>title</code>
+ * property for the MarkerClusterer.
+ * @property {string} [clusterClass="cluster"] The name of the CSS class defining general styles
+ * for the cluster markers. Use this class to define CSS styles that are not set up by the code
+ * that processes the <code>styles</code> array.
+ * @property {Array} [styles] An array of {@link ClusterIconStyle} elements defining the styles
+ * of the cluster markers to be used. The element to be used to style a given cluster marker
+ * is determined by the function defined by the <code>calculator</code> property.
+ * The default is an array of {@link ClusterIconStyle} elements whose properties are derived
+ * from the values for <code>imagePath</code>, <code>imageExtension</code>, and
+ * <code>imageSizes</code>.
+ * @property {number} [batchSize=MarkerClusterer.BATCH_SIZE] Set this property to the
+ * number of markers to be processed in a single batch when using a browser other than
+ * Internet Explorer (for Internet Explorer, use the batchSizeIE property instead).
+ * @property {number} [batchSizeIE=MarkerClusterer.BATCH_SIZE_IE] When Internet Explorer is
+ * being used, markers are processed in several batches with a small delay inserted between
+ * each batch in an attempt to avoid Javascript timeout errors. Set this property to the
+ * number of markers to be processed in a single batch; select as high a number as you can
+ * without causing a timeout error in the browser. This number might need to be as low as 100
+ * if 15,000 markers are being managed, for example.
+ * @property {string} [imagePath=MarkerClusterer.IMAGE_PATH]
+ * The full URL of the root name of the group of image files to use for cluster icons.
+ * The complete file name is of the form <code>imagePath</code>n.<code>imageExtension</code>
+ * where n is the image file number (1, 2, etc.).
+ * @property {string} [imageExtension=MarkerClusterer.IMAGE_EXTENSION]
+ * The extension name for the cluster icon image files (e.g., <code>"png"</code> or
+ * <code>"jpg"</code>).
+ * @property {Array} [imageSizes=MarkerClusterer.IMAGE_SIZES]
+ * An array of numbers containing the widths of the group of
+ * <code>imagePath</code>n.<code>imageExtension</code> image files.
+ * (The images are assumed to be square.)
+ */
+/**
+ * Creates a MarkerClusterer object with the options specified in {@link MarkerClustererOptions}.
+ * @constructor
+ * @extends google.maps.OverlayView
+ * @param {google.maps.Map} map The Google map to attach to.
+ * @param {Array.<google.maps.Marker>} [opt_markers] The markers to be added to the cluster.
+ * @param {MarkerClustererOptions} [opt_options] The optional parameters.
+ */
+function MarkerClusterer(map, opt_markers, opt_options) {
+ // MarkerClusterer implements google.maps.OverlayView interface. We use the
+ // extend function to extend MarkerClusterer with google.maps.OverlayView
+ // because it might not always be available when the code is defined so we
+ // look for it at the last possible moment. If it doesn't exist now then
+ // there is no point going ahead :)
+ this.extend(MarkerClusterer, google.maps.OverlayView);
+
+ opt_markers = opt_markers || [];
+ opt_options = opt_options || {};
+
+ this.markers_ = [];
+ this.clusters_ = [];
+ this.listeners_ = [];
+ this.activeMap_ = null;
+ this.ready_ = false;
+
+ this.gridSize_ = opt_options.gridSize || 60;
+ this.minClusterSize_ = opt_options.minimumClusterSize || 2;
+ this.maxZoom_ = opt_options.maxZoom || null;
+ this.styles_ = opt_options.styles || [];
+ this.title_ = opt_options.title || "";
+ this.zoomOnClick_ = true;
+ if (opt_options.zoomOnClick !== undefined) {
+ this.zoomOnClick_ = opt_options.zoomOnClick;
+ }
+ this.averageCenter_ = false;
+ if (opt_options.averageCenter !== undefined) {
+ this.averageCenter_ = opt_options.averageCenter;
+ }
+ this.ignoreHidden_ = false;
+ if (opt_options.ignoreHidden !== undefined) {
+ this.ignoreHidden_ = opt_options.ignoreHidden;
+ }
+ this.printable_ = false;
+ if (opt_options.printable !== undefined) {
+ this.printable_ = opt_options.printable;
+ }
+ this.imagePath_ = opt_options.imagePath || MarkerClusterer.IMAGE_PATH;
+ this.imageExtension_ = opt_options.imageExtension || MarkerClusterer.IMAGE_EXTENSION;
+ this.imageSizes_ = opt_options.imageSizes || MarkerClusterer.IMAGE_SIZES;
+ this.calculator_ = opt_options.calculator || MarkerClusterer.CALCULATOR;
+ this.batchSize_ = opt_options.batchSize || MarkerClusterer.BATCH_SIZE;
+ this.batchSizeIE_ = opt_options.batchSizeIE || MarkerClusterer.BATCH_SIZE_IE;
+ this.clusterClass_ = opt_options.clusterClass || "cluster";
+
+ if (navigator.userAgent.toLowerCase().indexOf("msie") !== -1) {
+ // Try to avoid IE timeout when processing a huge number of markers:
+ this.batchSize_ = this.batchSizeIE_;
+ }
+
+ this.setupStyles_();
+
+ this.addMarkers(opt_markers, true);
+ this.setMap(map); // Note: this causes onAdd to be called
+}
+
+
+/**
+ * Implementation of the onAdd interface method.
+ * @ignore
+ */
+MarkerClusterer.prototype.onAdd = function () {
+ var cMarkerClusterer = this;
+
+ this.activeMap_ = this.getMap();
+ this.ready_ = true;
+
+ this.repaint();
+
+ // Add the map event listeners
+ this.listeners_ = [
+ google.maps.event.addListener(this.getMap(), "zoom_changed", function () {
+ cMarkerClusterer.resetViewport_(false);
+ // Workaround for this Google bug: when map is at level 0 and "-" of
+ // zoom slider is clicked, a "zoom_changed" event is fired even though
+ // the map doesn't zoom out any further. In this situation, no "idle"
+ // event is triggered so the cluster markers that have been removed
+ // do not get redrawn. Same goes for a zoom in at maxZoom.
+ if (this.getZoom() === (this.get("minZoom") || 0) || this.getZoom() === this.get("maxZoom")) {
+ google.maps.event.trigger(this, "idle");
+ }
+ }),
+ google.maps.event.addListener(this.getMap(), "idle", function () {
+ cMarkerClusterer.redraw_();
+ })
+ ];
+};
+
+
+/**
+ * Implementation of the onRemove interface method.
+ * Removes map event listeners and all cluster icons from the DOM.
+ * All managed markers are also put back on the map.
+ * @ignore
+ */
+MarkerClusterer.prototype.onRemove = function () {
+ var i;
+
+ // Put all the managed markers back on the map:
+ for (i = 0; i < this.markers_.length; i++) {
+ if (this.markers_[i].getMap() !== this.activeMap_) {
+ this.markers_[i].setMap(this.activeMap_);
+ }
+ }
+
+ // Remove all clusters:
+ for (i = 0; i < this.clusters_.length; i++) {
+ this.clusters_[i].remove();
+ }
+ this.clusters_ = [];
+
+ // Remove map event listeners:
+ for (i = 0; i < this.listeners_.length; i++) {
+ google.maps.event.removeListener(this.listeners_[i]);
+ }
+ this.listeners_ = [];
+
+ this.activeMap_ = null;
+ this.ready_ = false;
+};
+
+
+/**
+ * Implementation of the draw interface method.
+ * @ignore
+ */
+MarkerClusterer.prototype.draw = function () {};
+
+
+/**
+ * Sets up the styles object.
+ */
+MarkerClusterer.prototype.setupStyles_ = function () {
+ var i, size;
+ if (this.styles_.length > 0) {
+ return;
+ }
+
+ for (i = 0; i < this.imageSizes_.length; i++) {
+ size = this.imageSizes_[i];
+ this.styles_.push({
+ url: this.imagePath_ + (i + 1) + "." + this.imageExtension_,
+ height: size,
+ width: size
+ });
+ }
+};
+
+
+/**
+ * Fits the map to the bounds of the markers managed by the clusterer.
+ */
+MarkerClusterer.prototype.fitMapToMarkers = function () {
+ var i;
+ var markers = this.getMarkers();
+ var bounds = new google.maps.LatLngBounds();
+ for (i = 0; i < markers.length; i++) {
+ bounds.extend(markers[i].getPosition());
+ }
+
+ this.getMap().fitBounds(bounds);
+};
+
+
+/**
+ * Returns the value of the <code>gridSize</code> property.
+ *
+ * @return {number} The grid size.
+ */
+MarkerClusterer.prototype.getGridSize = function () {
+ return this.gridSize_;
+};
+
+
+/**
+ * Sets the value of the <code>gridSize</code> property.
+ *
+ * @param {number} gridSize The grid size.
+ */
+MarkerClusterer.prototype.setGridSize = function (gridSize) {
+ this.gridSize_ = gridSize;
+};
+
+
+/**
+ * Returns the value of the <code>minimumClusterSize</code> property.
+ *
+ * @return {number} The minimum cluster size.
+ */
+MarkerClusterer.prototype.getMinimumClusterSize = function () {
+ return this.minClusterSize_;
+};
+
+/**
+ * Sets the value of the <code>minimumClusterSize</code> property.
+ *
+ * @param {number} minimumClusterSize The minimum cluster size.
+ */
+MarkerClusterer.prototype.setMinimumClusterSize = function (minimumClusterSize) {
+ this.minClusterSize_ = minimumClusterSize;
+};
+
+
+/**
+ * Returns the value of the <code>maxZoom</code> property.
+ *
+ * @return {number} The maximum zoom level.
+ */
+MarkerClusterer.prototype.getMaxZoom = function () {
+ return this.maxZoom_;
+};
+
+
+/**
+ * Sets the value of the <code>maxZoom</code> property.
+ *
+ * @param {number} maxZoom The maximum zoom level.
+ */
+MarkerClusterer.prototype.setMaxZoom = function (maxZoom) {
+ this.maxZoom_ = maxZoom;
+};
+
+
+/**
+ * Returns the value of the <code>styles</code> property.
+ *
+ * @return {Array} The array of styles defining the cluster markers to be used.
+ */
+MarkerClusterer.prototype.getStyles = function () {
+ return this.styles_;
+};
+
+
+/**
+ * Sets the value of the <code>styles</code> property.
+ *
+ * @param {Array.<ClusterIconStyle>} styles The array of styles to use.
+ */
+MarkerClusterer.prototype.setStyles = function (styles) {
+ this.styles_ = styles;
+};
+
+
+/**
+ * Returns the value of the <code>title</code> property.
+ *
+ * @return {string} The content of the title text.
+ */
+MarkerClusterer.prototype.getTitle = function () {
+ return this.title_;
+};
+
+
+/**
+ * Sets the value of the <code>title</code> property.
+ *
+ * @param {string} title The value of the title property.
+ */
+MarkerClusterer.prototype.setTitle = function (title) {
+ this.title_ = title;
+};
+
+
+/**
+ * Returns the value of the <code>zoomOnClick</code> property.
+ *
+ * @return {boolean} True if zoomOnClick property is set.
+ */
+MarkerClusterer.prototype.getZoomOnClick = function () {
+ return this.zoomOnClick_;
+};
+
+
+/**
+ * Sets the value of the <code>zoomOnClick</code> property.
+ *
+ * @param {boolean} zoomOnClick The value of the zoomOnClick property.
+ */
+MarkerClusterer.prototype.setZoomOnClick = function (zoomOnClick) {
+ this.zoomOnClick_ = zoomOnClick;
+};
+
+
+/**
+ * Returns the value of the <code>averageCenter</code> property.
+ *
+ * @return {boolean} True if averageCenter property is set.
+ */
+MarkerClusterer.prototype.getAverageCenter = function () {
+ return this.averageCenter_;
+};
+
+
+/**
+ * Sets the value of the <code>averageCenter</code> property.
+ *
+ * @param {boolean} averageCenter The value of the averageCenter property.
+ */
+MarkerClusterer.prototype.setAverageCenter = function (averageCenter) {
+ this.averageCenter_ = averageCenter;
+};
+
+
+/**
+ * Returns the value of the <code>ignoreHidden</code> property.
+ *
+ * @return {boolean} True if ignoreHidden property is set.
+ */
+MarkerClusterer.prototype.getIgnoreHidden = function () {
+ return this.ignoreHidden_;
+};
+
+
+/**
+ * Sets the value of the <code>ignoreHidden</code> property.
+ *
+ * @param {boolean} ignoreHidden The value of the ignoreHidden property.
+ */
+MarkerClusterer.prototype.setIgnoreHidden = function (ignoreHidden) {
+ this.ignoreHidden_ = ignoreHidden;
+};
+
+
+/**
+ * Returns the value of the <code>imageExtension</code> property.
+ *
+ * @return {string} The value of the imageExtension property.
+ */
+MarkerClusterer.prototype.getImageExtension = function () {
+ return this.imageExtension_;
+};
+
+
+/**
+ * Sets the value of the <code>imageExtension</code> property.
+ *
+ * @param {string} imageExtension The value of the imageExtension property.
+ */
+MarkerClusterer.prototype.setImageExtension = function (imageExtension) {
+ this.imageExtension_ = imageExtension;
+};
+
+
+/**
+ * Returns the value of the <code>imagePath</code> property.
+ *
+ * @return {string} The value of the imagePath property.
+ */
+MarkerClusterer.prototype.getImagePath = function () {
+ return this.imagePath_;
+};
+
+
+/**
+ * Sets the value of the <code>imagePath</code> property.
+ *
+ * @param {string} imagePath The value of the imagePath property.
+ */
+MarkerClusterer.prototype.setImagePath = function (imagePath) {
+ this.imagePath_ = imagePath;
+};
+
+
+/**
+ * Returns the value of the <code>imageSizes</code> property.
+ *
+ * @return {Array} The value of the imageSizes property.
+ */
+MarkerClusterer.prototype.getImageSizes = function () {
+ return this.imageSizes_;
+};
+
+
+/**
+ * Sets the value of the <code>imageSizes</code> property.
+ *
+ * @param {Array} imageSizes The value of the imageSizes property.
+ */
+MarkerClusterer.prototype.setImageSizes = function (imageSizes) {
+ this.imageSizes_ = imageSizes;
+};
+
+
+/**
+ * Returns the value of the <code>calculator</code> property.
+ *
+ * @return {function} the value of the calculator property.
+ */
+MarkerClusterer.prototype.getCalculator = function () {
+ return this.calculator_;
+};
+
+
+/**
+ * Sets the value of the <code>calculator</code> property.
+ *
+ * @param {function(Array.<google.maps.Marker>, number)} calculator The value
+ * of the calculator property.
+ */
+MarkerClusterer.prototype.setCalculator = function (calculator) {
+ this.calculator_ = calculator;
+};
+
+
+/**
+ * Returns the value of the <code>printable</code> property.
+ *
+ * @return {boolean} the value of the printable property.
+ */
+MarkerClusterer.prototype.getPrintable = function () {
+ return this.printable_;
+};
+
+
+/**
+ * Sets the value of the <code>printable</code> property.
+ *
+ * @param {boolean} printable The value of the printable property.
+ */
+MarkerClusterer.prototype.setPrintable = function (printable) {
+ this.printable_ = printable;
+};
+
+
+/**
+ * Returns the value of the <code>batchSizeIE</code> property.
+ *
+ * @return {number} the value of the batchSizeIE property.
+ */
+MarkerClusterer.prototype.getBatchSizeIE = function () {
+ return this.batchSizeIE_;
+};
+
+
+/**
+ * Sets the value of the <code>batchSizeIE</code> property.
+ *
+ * @param {number} batchSizeIE The value of the batchSizeIE property.
+ */
+MarkerClusterer.prototype.setBatchSizeIE = function (batchSizeIE) {
+ this.batchSizeIE_ = batchSizeIE;
+};
+
+
+/**
+ * Returns the value of the <code>clusterClass</code> property.
+ *
+ * @return {string} the value of the clusterClass property.
+ */
+MarkerClusterer.prototype.getClusterClass = function () {
+ return this.clusterClass_;
+};
+
+
+/**
+ * Sets the value of the <code>clusterClass</code> property.
+ *
+ * @param {string} clusterClass The value of the clusterClass property.
+ */
+MarkerClusterer.prototype.setClusterClass = function (clusterClass) {
+ this.clusterClass_ = clusterClass;
+};
+
+
+/**
+ * Returns the array of markers managed by the clusterer.
+ *
+ * @return {Array} The array of markers managed by the clusterer.
+ */
+MarkerClusterer.prototype.getMarkers = function () {
+ return this.markers_;
+};
+
+
+/**
+ * Returns the number of markers managed by the clusterer.
+ *
+ * @return {number} The number of markers.
+ */
+MarkerClusterer.prototype.getTotalMarkers = function () {
+ return this.markers_.length;
+};
+
+
+/**
+ * Returns the current array of clusters formed by the clusterer.
+ *
+ * @return {Array} The array of clusters formed by the clusterer.
+ */
+MarkerClusterer.prototype.getClusters = function () {
+ return this.clusters_;
+};
+
+
+/**
+ * Returns the number of clusters formed by the clusterer.
+ *
+ * @return {number} The number of clusters formed by the clusterer.
+ */
+MarkerClusterer.prototype.getTotalClusters = function () {
+ return this.clusters_.length;
+};
+
+
+/**
+ * Adds a marker to the clusterer. The clusters are redrawn unless
+ * <code>opt_nodraw</code> is set to <code>true</code>.
+ *
+ * @param {google.maps.Marker} marker The marker to add.
+ * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing.
+ */
+MarkerClusterer.prototype.addMarker = function (marker, opt_nodraw) {
+ this.pushMarkerTo_(marker);
+ if (!opt_nodraw) {
+ this.redraw_();
+ }
+};
+
+
+/**
+ * Adds an array of markers to the clusterer. The clusters are redrawn unless
+ * <code>opt_nodraw</code> is set to <code>true</code>.
+ *
+ * @param {Array.<google.maps.Marker>} markers The markers to add.
+ * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing.
+ */
+MarkerClusterer.prototype.addMarkers = function (markers, opt_nodraw) {
+ var i;
+ for (i = 0; i < markers.length; i++) {
+ this.pushMarkerTo_(markers[i]);
+ }
+ if (!opt_nodraw) {
+ this.redraw_();
+ }
+};
+
+
+/**
+ * Pushes a marker to the clusterer.
+ *
+ * @param {google.maps.Marker} marker The marker to add.
+ */
+MarkerClusterer.prototype.pushMarkerTo_ = function (marker) {
+ // If the marker is draggable add a listener so we can update the clusters on the dragend:
+ if (marker.getDraggable()) {
+ var cMarkerClusterer = this;
+ google.maps.event.addListener(marker, "dragend", function () {
+ if (cMarkerClusterer.ready_) {
+ this.isAdded = false;
+ cMarkerClusterer.repaint();
+ }
+ });
+ }
+ marker.isAdded = false;
+ this.markers_.push(marker);
+};
+
+
+/**
+ * Removes a marker from the cluster. The clusters are redrawn unless
+ * <code>opt_nodraw</code> is set to <code>true</code>. Returns <code>true</code> if the
+ * marker was removed from the clusterer.
+ *
+ * @param {google.maps.Marker} marker The marker to remove.
+ * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing.
+ * @return {boolean} True if the marker was removed from the clusterer.
+ */
+MarkerClusterer.prototype.removeMarker = function (marker, opt_nodraw) {
+ var removed = this.removeMarker_(marker);
+
+ if (!opt_nodraw && removed) {
+ this.repaint();
+ }
+
+ return removed;
+};
+
+
+/**
+ * Removes an array of markers from the cluster. The clusters are redrawn unless
+ * <code>opt_nodraw</code> is set to <code>true</code>. Returns <code>true</code> if markers
+ * were removed from the clusterer.
+ *
+ * @param {Array.<google.maps.Marker>} markers The markers to remove.
+ * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing.
+ * @return {boolean} True if markers were removed from the clusterer.
+ */
+MarkerClusterer.prototype.removeMarkers = function (markers, opt_nodraw) {
+ var i, r;
+ var removed = false;
+
+ for (i = 0; i < markers.length; i++) {
+ r = this.removeMarker_(markers[i]);
+ removed = removed || r;
+ }
+
+ if (!opt_nodraw && removed) {
+ this.repaint();
+ }
+
+ return removed;
+};
+
+
+/**
+ * Removes a marker and returns true if removed, false if not.
+ *
+ * @param {google.maps.Marker} marker The marker to remove
+ * @return {boolean} Whether the marker was removed or not
+ */
+MarkerClusterer.prototype.removeMarker_ = function (marker) {
+ var i;
+ var index = -1;
+ if (this.markers_.indexOf) {
+ index = this.markers_.indexOf(marker);
+ } else {
+ for (i = 0; i < this.markers_.length; i++) {
+ if (marker === this.markers_[i]) {
+ index = i;
+ break;
+ }
+ }
+ }
+
+ if (index === -1) {
+ // Marker is not in our list of markers, so do nothing:
+ return false;
+ }
+
+ marker.setMap(null);
+ this.markers_.splice(index, 1); // Remove the marker from the list of managed markers
+ return true;
+};
+
+
+/**
+ * Removes all clusters and markers from the map and also removes all markers
+ * managed by the clusterer.
+ */
+MarkerClusterer.prototype.clearMarkers = function () {
+ this.resetViewport_(true);
+ this.markers_ = [];
+};
+
+
+/**
+ * Recalculates and redraws all the marker clusters from scratch.
+ * Call this after changing any properties.
+ */
+MarkerClusterer.prototype.repaint = function () {
+ var oldClusters = this.clusters_.slice();
+ this.clusters_ = [];
+ this.resetViewport_(false);
+ this.redraw_();
+
+ // Remove the old clusters.
+ // Do it in a timeout to prevent blinking effect.
+ setTimeout(function () {
+ var i;
+ for (i = 0; i < oldClusters.length; i++) {
+ oldClusters[i].remove();
+ }
+ }, 0);
+};
+
+
+/**
+ * Returns the current bounds extended by the grid size.
+ *
+ * @param {google.maps.LatLngBounds} bounds The bounds to extend.
+ * @return {google.maps.LatLngBounds} The extended bounds.
+ * @ignore
+ */
+MarkerClusterer.prototype.getExtendedBounds = function (bounds) {
+ var projection = this.getProjection();
+
+ // Turn the bounds into latlng.
+ var tr = new google.maps.LatLng(bounds.getNorthEast().lat(),
+ bounds.getNorthEast().lng());
+ var bl = new google.maps.LatLng(bounds.getSouthWest().lat(),
+ bounds.getSouthWest().lng());
+
+ // Convert the points to pixels and the extend out by the grid size.
+ var trPix = projection.fromLatLngToDivPixel(tr);
+ trPix.x += this.gridSize_;
+ trPix.y -= this.gridSize_;
+
+ var blPix = projection.fromLatLngToDivPixel(bl);
+ blPix.x -= this.gridSize_;
+ blPix.y += this.gridSize_;
+
+ // Convert the pixel points back to LatLng
+ var ne = projection.fromDivPixelToLatLng(trPix);
+ var sw = projection.fromDivPixelToLatLng(blPix);
+
+ // Extend the bounds to contain the new bounds.
+ bounds.extend(ne);
+ bounds.extend(sw);
+
+ return bounds;
+};
+
+
+/**
+ * Redraws all the clusters.
+ */
+MarkerClusterer.prototype.redraw_ = function () {
+ this.createClusters_(0);
+};
+
+
+/**
+ * Removes all clusters from the map. The markers are also removed from the map
+ * if <code>opt_hide</code> is set to <code>true</code>.
+ *
+ * @param {boolean} [opt_hide] Set to <code>true</code> to also remove the markers
+ * from the map.
+ */
+MarkerClusterer.prototype.resetViewport_ = function (opt_hide) {
+ var i, marker;
+ // Remove all the clusters
+ for (i = 0; i < this.clusters_.length; i++) {
+ this.clusters_[i].remove();
+ }
+ this.clusters_ = [];
+
+ // Reset the markers to not be added and to be removed from the map.
+ for (i = 0; i < this.markers_.length; i++) {
+ marker = this.markers_[i];
+ marker.isAdded = false;
+ if (opt_hide) {
+ marker.setMap(null);
+ }
+ }
+};
+
+
+/**
+ * Calculates the distance between two latlng locations in km.
+ *
+ * @param {google.maps.LatLng} p1 The first lat lng point.
+ * @param {google.maps.LatLng} p2 The second lat lng point.
+ * @return {number} The distance between the two points in km.
+ * @see http://www.movable-type.co.uk/scripts/latlong.html
+*/
+MarkerClusterer.prototype.distanceBetweenPoints_ = function (p1, p2) {
+ var R = 6371; // Radius of the Earth in km
+ var dLat = (p2.lat() - p1.lat()) * Math.PI / 180;
+ var dLon = (p2.lng() - p1.lng()) * Math.PI / 180;
+ var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
+ Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) *
+ Math.sin(dLon / 2) * Math.sin(dLon / 2);
+ var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+ var d = R * c;
+ return d;
+};
+
+
+/**
+ * Determines if a marker is contained in a bounds.
+ *
+ * @param {google.maps.Marker} marker The marker to check.
+ * @param {google.maps.LatLngBounds} bounds The bounds to check against.
+ * @return {boolean} True if the marker is in the bounds.
+ */
+MarkerClusterer.prototype.isMarkerInBounds_ = function (marker, bounds) {
+ return bounds.contains(marker.getPosition());
+};
+
+
+/**
+ * Adds a marker to a cluster, or creates a new cluster.
+ *
+ * @param {google.maps.Marker} marker The marker to add.
+ */
+MarkerClusterer.prototype.addToClosestCluster_ = function (marker) {
+ var i, d, cluster, center;
+ var distance = 40000; // Some large number
+ var clusterToAddTo = null;
+ for (i = 0; i < this.clusters_.length; i++) {
+ cluster = this.clusters_[i];
+ center = cluster.getCenter();
+ if (center) {
+ d = this.distanceBetweenPoints_(center, marker.getPosition());
+ if (d < distance) {
+ distance = d;
+ clusterToAddTo = cluster;
+ }
+ }
+ }
+
+ if (clusterToAddTo && clusterToAddTo.isMarkerInClusterBounds(marker)) {
+ clusterToAddTo.addMarker(marker);
+ } else {
+ cluster = new Cluster(this);
+ cluster.addMarker(marker);
+ this.clusters_.push(cluster);
+ }
+};
+
+
+/**
+ * Creates the clusters. This is done in batches to avoid timeout errors
+ * in some browsers when there is a huge number of markers.
+ *
+ * @param {number} iFirst The index of the first marker in the batch of
+ * markers to be added to clusters.
+ */
+MarkerClusterer.prototype.createClusters_ = function (iFirst) {
+ var i, marker;
+ var mapBounds;
+ var cMarkerClusterer = this;
+ if (!this.ready_) {
+ return;
+ }
+
+ // Cancel previous batch processing if we're working on the first batch:
+ if (iFirst === 0) {
+ /**
+ * This event is fired when the <code>MarkerClusterer</code> begins
+ * clustering markers.
+ * @name MarkerClusterer#clusteringbegin
+ * @param {MarkerClusterer} mc The MarkerClusterer whose markers are being clustered.
+ * @event
+ */
+ google.maps.event.trigger(this, "clusteringbegin", this);
+
+ if (typeof this.timerRefStatic !== "undefined") {
+ clearTimeout(this.timerRefStatic);
+ delete this.timerRefStatic;
+ }
+ }
+
+ // Get our current map view bounds.
+ // Create a new bounds object so we don't affect the map.
+ //
+ // See Comments 9 & 11 on Issue 3651 relating to this workaround for a Google Maps bug:
+ if (this.getMap().getZoom() > 3) {
+ mapBounds = new google.maps.LatLngBounds(this.getMap().getBounds().getSouthWest(),
+ this.getMap().getBounds().getNorthEast());
+ } else {
+ mapBounds = new google.maps.LatLngBounds(new google.maps.LatLng(85.02070771743472, -178.48388434375), new google.maps.LatLng(-85.08136444384544, 178.00048865625));
+ }
+ var bounds = this.getExtendedBounds(mapBounds);
+
+ var iLast = Math.min(iFirst + this.batchSize_, this.markers_.length);
+
+ for (i = iFirst; i < iLast; i++) {
+ marker = this.markers_[i];
+ if (!marker.isAdded && this.isMarkerInBounds_(marker, bounds)) {
+ if (!this.ignoreHidden_ || (this.ignoreHidden_ && marker.getVisible())) {
+ this.addToClosestCluster_(marker);
+ }
+ }
+ }
+
+ if (iLast < this.markers_.length) {
+ this.timerRefStatic = setTimeout(function () {
+ cMarkerClusterer.createClusters_(iLast);
+ }, 0);
+ } else {
+ delete this.timerRefStatic;
+
+ /**
+ * This event is fired when the <code>MarkerClusterer</code> stops
+ * clustering markers.
+ * @name MarkerClusterer#clusteringend
+ * @param {MarkerClusterer} mc The MarkerClusterer whose markers are being clustered.
+ * @event
+ */
+ google.maps.event.trigger(this, "clusteringend", this);
+ }
+};
+
+
+/**
+ * Extends an object's prototype by another's.
+ *
+ * @param {Object} obj1 The object to be extended.
+ * @param {Object} obj2 The object to extend with.
+ * @return {Object} The new extended object.
+ * @ignore
+ */
+MarkerClusterer.prototype.extend = function (obj1, obj2) {
+ return (function (object) {
+ var property;
+ for (property in object.prototype) {
+ this.prototype[property] = object.prototype[property];
+ }
+ return this;
+ }).apply(obj1, [obj2]);
+};
+
+
+/**
+ * The default function for determining the label text and style
+ * for a cluster icon.
+ *
+ * @param {Array.<google.maps.Marker>} markers The array of markers represented by the cluster.
+ * @param {number} numStyles The number of marker styles available.
+ * @return {ClusterIconInfo} The information resource for the cluster.
+ * @constant
+ * @ignore
+ */
+MarkerClusterer.CALCULATOR = function (markers, numStyles) {
+ var index = 0;
+ var title = "";
+ var count = markers.length.toString();
+
+ var dv = count;
+ while (dv !== 0) {
+ dv = parseInt(dv / 10, 10);
+ index++;
+ }
+
+ index = Math.min(index, numStyles);
+ return {
+ text: count,
+ index: index,
+ title: title
+ };
+};
+
+
+/**
+ * The number of markers to process in one batch.
+ *
+ * @type {number}
+ * @constant
+ */
+MarkerClusterer.BATCH_SIZE = 2000;
+
+
+/**
+ * The number of markers to process in one batch (IE only).
+ *
+ * @type {number}
+ * @constant
+ */
+MarkerClusterer.BATCH_SIZE_IE = 500;
+
+
+/**
+ * The default root name for the marker cluster images.
+ *
+ * @type {string}
+ * @constant
+ */
+MarkerClusterer.IMAGE_PATH = "http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclustererplus/images/m";
+
+
+/**
+ * The default extension name for the marker cluster images.
+ *
+ * @type {string}
+ * @constant
+ */
+MarkerClusterer.IMAGE_EXTENSION = "png";
+
+
+/**
+ * The default array of sizes for the marker cluster images.
+ *
+ * @type {Array.<number>}
+ * @constant
+ */
+MarkerClusterer.IMAGE_SIZES = [53, 56, 66, 78, 90];
--- /dev/null
+http://google-maps-utility-library-v3.googlecode.com/svn/tags/markerclustererplus/2.0.16/src/markerclusterer.js
--- /dev/null
+eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('5 H(b,a){b.17().Z(H,o.n.31);4.I=b;4.2E=b.17().2w();4.P=a;4.A=t;4.q=t;4.W=t;4.1s=s;4.L(b.v())}H.6.2D=5(){7 d=4;7 g;7 f;4.q=3E.3y("24");4.q.5n=4.2E;9(4.1s){4.2a()}4.4H().4E.4z(4.q);4.2X=o.n.u.1E(4.v(),"4b",5(){f=g});o.n.u.1D(4.q,"41",5(){g=K;f=s});o.n.u.1D(4.q,"2M",5(e){g=s;9(!f){7 c;7 b;7 a=d.I.17();o.n.u.14(a,"2M",d.I);o.n.u.14(a,"3Y",d.I);9(a.2B()){b=a.1K();c=d.I.1y();a.v().1O(c);1U(5(){a.v().1O(c);9(b!==t&&(a.v().1c()>b)){a.v().3I(b+1)}},3D)}e.3A=K;9(e.2h){e.2h()}}});o.n.u.1D(4.q,"2f",5(){7 a=d.I.17();o.n.u.14(a,"2f",d.I)});o.n.u.1D(4.q,"3m",5(){7 a=d.I.17();o.n.u.14(a,"3m",d.I)})};H.6.2Q=5(){9(4.q&&4.q.3j){4.1M();o.n.u.3f(4.2X);o.n.u.4Z(4.q);4.q.3j.4S(4.q);4.q=t}};H.6.39=5(){9(4.1s){7 a=4.28(4.A);4.q.13.1J=a.y+"B";4.q.13.1F=a.x+"B"}};H.6.1M=5(){9(4.q){4.q.13.3b="2V"}4.1s=s};H.6.2a=5(){9(4.q){7 a=4.28(4.A);4.q.13.4l=4.2T(a);9(4.I.18){4.q.2R="<49 42=\'"+4.22+"\'><24 13=\'20: 2P; 1J: 2N; 1F: 2N; 1a: "+4.X+"B;\'>"+4.W.19+"</24>"}C{4.q.2R=4.W.19}9(1q 4.W.15==="1g"||4.W.15===""){4.q.15=4.I.17().2H()}C{4.q.15=4.W.15}4.q.13.3b=""}4.1s=K};H.6.2F=5(a){4.W=a;7 b=z.3X(0,a.2A-1);b=z.2e(4.P.p-1,b);7 c=4.P[b];4.22=c.1W;4.Q=c.V;4.X=c.1a;4.J=c.3R;4.1T=c.3M||[1P(4.Q/2,10),1P(4.X/2,10)];4.2k=c.3F||"3C";4.2x=c.3z||11;4.2n=c.3w||"2V";4.2C=c.3u||"3t";4.2U=c.3p||"3n";4.3k=c.5j||"5f,5c-59";4.3e=c.4X||"0 0"};H.6.3d=5(a){4.A=a};H.6.2T=5(b){7 a=[];9(!4.I.18){a.F(\'3a-4N:1W(\'+4.22+\');\');a.F(\'3a-20:\'+4.3e+\';\')}9(1q 4.J===\'4L\'){9(1q 4.J[0]===\'36\'&&4.J[0]>0&&4.J[0]<4.Q){a.F(\'V:\'+(4.Q-4.J[0])+\'B; 35-1J:\'+4.J[0]+\'B;\')}C{a.F(\'V:\'+4.Q+\'B; 34-V:\'+4.Q+\'B;\')}9(1q 4.J[1]===\'36\'&&4.J[1]>0&&4.J[1]<4.X){a.F(\'1a:\'+(4.X-4.J[1])+\'B; 35-1F:\'+4.J[1]+\'B;\')}C{a.F(\'1a:\'+4.X+\'B; 19-32:1e;\')}}C{a.F(\'V:\'+4.Q+\'B; 34-V:\'+4.Q+\'B; 1a:\'+4.X+\'B; 19-32:1e;\')}a.F(\'4D:4B; 1J:\'+b.y+\'B; 1F:\'+b.x+\'B; 4A:\'+4.2k+\'; 20:2P; 1A-1l:\'+4.2x+\'B; 1A-4x:\'+4.3k+\'; 1A-4v:\'+4.2C+\'; 1A-13:\'+4.2U+\'; 19-4u:\'+4.2n+\';\');j a.4r("")};H.6.28=5(b){7 a=4.3l().2d(b);a.x-=4.1T[1];a.y-=4.1T[0];j a};5 E(a){4.16=a;4.U=a.v();4.S=a.2S();4.12=a.3i();4.1d=a.3g();4.18=a.3h();4.k=[];4.A=t;4.23=t;4.Y=G H(4,a.21())}E.6.40=5(){j 4.k.p};E.6.1C=5(){j 4.k};E.6.2O=5(){j 4.A};E.6.v=5(){j 4.U};E.6.17=5(){j 4.16};E.6.1y=5(){7 i;7 b=G o.n.1r(4.A,4.A);7 a=4.1C();w(i=0;i<a.p;i++){b.Z(a[i].T())}j b};E.6.1B=5(){4.Y.L(t);4.k=[];1Z 4.k};E.6.1G=5(e){7 i;7 c;7 b;9(4.2L(e)){j s}9(!4.A){4.A=e.T();4.1Y()}C{9(4.1d){7 l=4.k.p+1;7 a=(4.A.O()*(l-1)+e.T().O())/l;7 d=(4.A.1f()*(l-1)+e.T().1f())/l;4.A=G o.n.1u(a,d);4.1Y()}}e.1p=K;4.k.F(e);c=4.k.p;b=4.16.1K();9(b!==t&&4.U.1c()>b){9(e.v()!==4.U){e.L(4.U)}}C 9(c<4.12){9(e.v()!==4.U){e.L(4.U)}}C 9(c===4.12){w(i=0;i<c;i++){4.k[i].L(t)}}C{e.L(t)}4.2K();j K};E.6.2J=5(a){j 4.23.2I(a.T())};E.6.1Y=5(){7 a=G o.n.1r(4.A,4.A);4.23=4.16.1X(a)};E.6.2K=5(){7 c=4.k.p;7 a=4.16.1K();9(a!==t&&4.U.1c()>a){4.Y.1M();j}9(c<4.12){4.Y.1M();j}7 b=4.16.21().p;7 d=4.16.2G()(4.k,b);4.Y.3d(4.A);4.Y.2F(d);4.Y.2a()};E.6.2L=5(a){7 i;9(4.k.1o){j 4.k.1o(a)!==-1}C{w(i=0;i<4.k.p;i++){9(a===4.k[i]){j K}}}j s};5 8(a,c,b){4.Z(8,o.n.31);c=c||[];b=b||{};4.k=[];4.D=[];4.1n=[];4.1h=t;4.1j=s;4.S=b.3W||3V;4.12=b.3U||2;4.2c=b.2y||t;4.P=b.3T||[];4.1V=b.15||"";4.1L=K;9(b.2v!==1g){4.1L=b.2v}4.1d=s;9(b.2u!==1g){4.1d=b.2u}4.1b=s;9(b.2t!==1g){4.1b=b.2t}4.18=s;9(b.2s!==1g){4.18=b.2s}4.1z=b.3S||8.2r;4.1v=b.3Q||8.2p;4.1i=b.3L||8.2i;4.1S=b.3J||8.2m;4.1R=b.3H||8.2l;4.1x=b.3G||8.2j;4.1Q=b.3K||"N";9(3B.3N.3O().1o("3P")!==-1){4.1R=4.1x}4.2q();4.2o(c,K);4.L(a)}8.6.2D=5(){7 a=4;4.1h=4.v();4.1j=K;4.1k();4.1n=[o.n.u.1E(4.v(),"3x",5(){a.1w(s);9(4.1c()===(4.2g("3v")||0)||4.1c()===4.2g("2y")){o.n.u.14(4,"2z")}}),o.n.u.1E(4.v(),"2z",5(){a.1m()})]};8.6.2Q=5(){7 i;w(i=0;i<4.k.p;i++){9(4.k[i].v()!==4.1h){4.k[i].L(4.1h)}}w(i=0;i<4.D.p;i++){4.D[i].1B()}4.D=[];w(i=0;i<4.1n.p;i++){o.n.u.3f(4.1n[i])}4.1n=[];4.1h=t;4.1j=s};8.6.39=5(){};8.6.2q=5(){7 i,1l;9(4.P.p>0){j}w(i=0;i<4.1i.p;i++){1l=4.1i[i];4.P.F({1W:4.1z+(i+1)+"."+4.1v,V:1l,1a:1l})}};8.6.3Z=5(){7 i;7 a=4.1C();7 b=G o.n.1r();w(i=0;i<a.p;i++){b.Z(a[i].T())}4.v().1O(b)};8.6.2S=5(){j 4.S};8.6.3s=5(a){4.S=a};8.6.3i=5(){j 4.12};8.6.3r=5(a){4.12=a};8.6.1K=5(){j 4.2c};8.6.3q=5(a){4.2c=a};8.6.21=5(){j 4.P};8.6.43=5(a){4.P=a};8.6.2H=5(){j 4.1V};8.6.3o=5(a){4.1V=a};8.6.2B=5(){j 4.1L};8.6.45=5(a){4.1L=a};8.6.3g=5(){j 4.1d};8.6.46=5(a){4.1d=a};8.6.47=5(){j 4.1b};8.6.5m=5(a){4.1b=a};8.6.5k=5(){j 4.1v};8.6.5i=5(a){4.1v=a};8.6.5h=5(){j 4.1z};8.6.5e=5(a){4.1z=a};8.6.5d=5(){j 4.1i};8.6.5b=5(a){4.1i=a};8.6.2G=5(){j 4.1S};8.6.5a=5(a){4.1S=a};8.6.3h=5(){j 4.18};8.6.57=5(a){4.18=a};8.6.54=5(){j 4.1x};8.6.51=5(a){4.1x=a};8.6.2w=5(){j 4.1Q};8.6.50=5(a){4.1Q=a};8.6.1C=5(){j 4.k};8.6.4V=5(){j 4.k.p};8.6.4U=5(){j 4.D};8.6.4T=5(){j 4.D.p};8.6.1G=5(b,a){4.2b(b);9(!a){4.1m()}};8.6.2o=5(b,a){7 i;w(i=0;i<b.p;i++){4.2b(b[i])}9(!a){4.1m()}};8.6.2b=5(b){9(b.4R()){7 a=4;o.n.u.1E(b,"4P",5(){9(a.1j){4.1p=s;a.1k()}})}b.1p=s;4.k.F(b)};8.6.4O=5(c,a){7 b=4.29(c);9(!a&&b){4.1k()}j b};8.6.4M=5(a,c){7 i,r;7 b=s;w(i=0;i<a.p;i++){r=4.29(a[i]);b=b||r}9(!c&&b){4.1k()}j b};8.6.29=5(b){7 i;7 a=-1;9(4.k.1o){a=4.k.1o(b)}C{w(i=0;i<4.k.p;i++){9(b===4.k[i]){a=i;4K}}}9(a===-1){j s}b.L(t);4.k.4I(a,1);j K};8.6.4G=5(){4.1w(K);4.k=[]};8.6.1k=5(){7 a=4.D.4F();4.D=[];4.1w(s);4.1m();1U(5(){7 i;w(i=0;i<a.p;i++){a[i].1B()}},0)};8.6.1X=5(d){7 f=4.3l();7 c=G o.n.1u(d.25().O(),d.25().1f());7 a=G o.n.1u(d.26().O(),d.26().1f());7 e=f.2d(c);e.x+=4.S;e.y-=4.S;7 g=f.2d(a);g.x-=4.S;g.y+=4.S;7 b=f.33(e);7 h=f.33(g);d.Z(b);d.Z(h);j d};8.6.1m=5(){4.27(0)};8.6.1w=5(a){7 i,M;w(i=0;i<4.D.p;i++){4.D[i].1B()}4.D=[];w(i=0;i<4.k.p;i++){M=4.k[i];M.1p=s;9(a){M.L(t)}}};8.6.30=5(b,e){7 R=4C;7 g=(e.O()-b.O())*z.1I/1H;7 f=(e.1f()-b.1f())*z.1I/1H;7 a=z.1N(g/2)*z.1N(g/2)+z.3c(b.O()*z.1I/1H)*z.3c(e.O()*z.1I/1H)*z.1N(f/2)*z.1N(f/2);7 c=2*z.4J(z.2Z(a),z.2Z(1-a));7 d=R*c;j d};8.6.37=5(b,a){j a.2I(b.T())};8.6.2Y=5(c){7 i,d,N,1e;7 a=4y;7 b=t;w(i=0;i<4.D.p;i++){N=4.D[i];1e=N.2O();9(1e){d=4.30(1e,c.T());9(d<a){a=d;b=N}}}9(b&&b.2J(c)){b.1G(c)}C{N=G E(4);N.1G(c);4.D.F(N)}};8.6.27=5(e){7 i,M;7 d;7 c=4;9(!4.1j){j}9(e===0){o.n.u.14(4,"4w",4);9(1q 4.1t!=="1g"){4Q(4.1t);1Z 4.1t}}9(4.v().1c()>3){d=G o.n.1r(4.v().1y().26(),4.v().1y().25())}C{d=G o.n.1r(G o.n.1u(38.4t,-2W.4s),G o.n.1u(-38.4q,2W.4W))}7 a=4.1X(d);7 b=z.2e(e+4.1R,4.k.p);w(i=e;i<b;i++){M=4.k[i];9(!M.1p&&4.37(M,a)){9(!4.1b||(4.1b&&M.4p())){4.2Y(M)}}}9(b<4.k.p){4.1t=1U(5(){c.27(b)},0)}C{1Z 4.1t;o.n.u.14(4,"4Y",4)}};8.6.Z=5(d,c){j(5(b){7 a;w(a 4o b.6){4.6[a]=b.6[a]}j 4}).4n(d,[c])};8.2m=5(a,c){7 f=0;7 b="";7 d=a.p.4m();7 e=d;52(e!==0){e=1P(e/10,10);f++}f=z.2e(f,c);j{19:d,2A:f,15:b}};8.2l=4k;8.2j=55;8.2r="4j://o-n-58-4i-4h.4g.4f/4e/4d/4c/5g/m";8.2p="4a";8.2i=[53,56,48,5l,44];',62,334,'||||this|function|prototype|var|MarkerClusterer|if||||||||||return|markers_|||maps|google|length|div_||false|null|event|getMap|for|||Math|center_|px|else|clusters_|Cluster|push|new|ClusterIcon|cluster_|anchor_|true|setMap|marker|cluster|lat|styles_|height_||gridSize_|getPosition|map_|height|sums_|width_|clusterIcon_|extend|||minClusterSize_|style|trigger|title|markerClusterer_|getMarkerClusterer|printable_|text|width|ignoreHidden_|getZoom|averageCenter_|center|lng|undefined|activeMap_|imageSizes_|ready_|repaint|size|redraw_|listeners_|indexOf|isAdded|typeof|LatLngBounds|visible_|timerRefStatic|LatLng|imageExtension_|resetViewport_|batchSizeIE_|getBounds|imagePath_|font|remove|getMarkers|addDomListener|addListener|left|addMarker|180|PI|top|getMaxZoom|zoomOnClick_|hide|sin|fitBounds|parseInt|clusterClass_|batchSize_|calculator_|anchorIcon_|setTimeout|title_|url|getExtendedBounds|calculateBounds_|delete|position|getStyles|url_|bounds_|div|getNorthEast|getSouthWest|createClusters_|getPosFromLatLng_|removeMarker_|show|pushMarkerTo_|maxZoom_|fromLatLngToDivPixel|min|mouseover|get|stopPropagation|IMAGE_SIZES|BATCH_SIZE_IE|textColor_|BATCH_SIZE|CALCULATOR|textDecoration_|addMarkers|IMAGE_EXTENSION|setupStyles_|IMAGE_PATH|printable|ignoreHidden|averageCenter|zoomOnClick|getClusterClass|textSize_|maxZoom|idle|index|getZoomOnClick|fontWeight_|onAdd|className_|useStyle|getCalculator|getTitle|contains|isMarkerInClusterBounds|updateIcon_|isMarkerAlreadyAdded_|click|0px|getCenter|absolute|onRemove|innerHTML|getGridSize|createCss|fontStyle_|none|178|boundsChangedListener_|addToClosestCluster_|sqrt|distanceBetweenPoints_|OverlayView|align|fromDivPixelToLatLng|line|padding|number|isMarkerInBounds_|85|draw|background|display|cos|setCenter|backgroundPosition_|removeListener|getAverageCenter|getPrintable|getMinimumClusterSize|parentNode|fontFamily_|getProjection|mouseout|normal|setTitle|fontStyle|setMaxZoom|setMinimumClusterSize|setGridSize|bold|fontWeight|minZoom|textDecoration|zoom_changed|createElement|textSize|cancelBubble|navigator|black|100|document|textColor|batchSizeIE|batchSize|setZoom|calculator|clusterClass|imageSizes|anchorIcon|userAgent|toLowerCase|msie|imageExtension|anchor|imagePath|styles|minimumClusterSize|60|gridSize|max|clusterclick|fitMapToMarkers|getSize|mousedown|src|setStyles|90|setZoomOnClick|setAverageCenter|getIgnoreHidden|66|img|png|bounds_changed|markerclustererplus|trunk|svn|com|googlecode|v3|library|http|2000|cssText|toString|apply|in|getVisible|08136444384544|join|48388434375|02070771743472|decoration|weight|clusteringbegin|family|40000|appendChild|color|pointer|6371|cursor|overlayMouseTarget|slice|clearMarkers|getPanes|splice|atan2|break|object|removeMarkers|image|removeMarker|dragend|clearTimeout|getDraggable|removeChild|getTotalClusters|getClusters|getTotalMarkers|00048865625|backgroundPosition|clusteringend|clearInstanceListeners|setClusterClass|setBatchSizeIE|while||getBatchSizeIE|500||setPrintable|utility|serif|setCalculator|setImageSizes|sans|getImageSizes|setImagePath|Arial|images|getImagePath|setImageExtension|fontFamily|getImageExtension|78|setIgnoreHidden|className'.split('|'),0,{}))
\ No newline at end of file
--- /dev/null
+http://google-maps-utility-library-v3.googlecode.com/svn/tags/markerclustererplus/2.0.16/src/markerclusterer_packed.js
--- /dev/null
+markerclusterer-2.0.16.js
\ No newline at end of file
--- /dev/null
+markerclusterer-2.0.16_packed.js
\ No newline at end of file