
//Events Constants
var MWMAP_MAP_CLICK = 1;
var MWMAP_MAP_DBL_CLICK = 2;
var MWMAP_MAP_SINGLE_RIGHT_CLICK = 3;
var MWMAP_MAP_MOVE_END = 4;
var MWMAP_AREA_MARKER_DBL_CLICK = 5;
var MWMAP_AREA_MARKER_DRAG_END = 6;
var MWMAP_AREA_CLICK = 7;
var MWMAP_MARKER_DRAG_END = 8;
var MWMAP_MARKER_CLICK = 9;
var MWMAP_DIRECTION_CLICK = 10;
var MWMAP_MARCO_CLICK = 11;
var MWMAP_MARCO_WINDOW_CLOSE = 12;
var MWMAP_ONMOUSEOVER_MARCO = 13;
var MWMAP_ONMOUSEOUT_MARCO = 14;
var MWMAP_MARKER_DRAG_START = 15;

/* //Icons Types Constants
var MWMARKER_TYPE_BASE           = 0;
var MWMARKER_TYPE_MARKER         = 1;
var MWMARKER_TYPE_MY_MARKER      = 2;
var MWMARKER_TYPE_AREA_MARKER    = 3;
var MWMARKER_TYPE_MY_AREA_MARKER = 4;
*/
var MW = {};

/*
 * Map object
 */

MW.Map = function( contentDiv, callback, clustered ) {
	// Optional parameter
	if ( typeof clustered == 'undefined' ) {
		clustered = false;
	}
	
	this.map = new GMap2( contentDiv );
	this.clustered = clustered;
	
	if ( this.clustered ) {
		/*
		 * Clusterer Config.
		 */
		this.clustererCat1 = new Clusterer( this.map );
		this.clustererCat2 = new Clusterer( this.map );
		this.clustererCat3 = new Clusterer( this.map );
		this.clustererCat4 = new Clusterer( this.map );
		this.clustererCat5 = new Clusterer( this.map );
		
		var clustererGridSize = 7;
		this.clustererCat1.setGridSize( clustererGridSize );
		this.clustererCat2.setGridSize( clustererGridSize );
		this.clustererCat3.setGridSize( clustererGridSize );
		this.clustererCat4.setGridSize( clustererGridSize );
		this.clustererCat5.setGridSize( clustererGridSize );
		
		var clustererMinMarkersPerCluster = 2;
		this.clustererCat1.setMinMarkersPerCluster( clustererMinMarkersPerCluster );
		this.clustererCat2.setMinMarkersPerCluster( clustererMinMarkersPerCluster );
		this.clustererCat3.setMinMarkersPerCluster( clustererMinMarkersPerCluster );
		this.clustererCat4.setMinMarkersPerCluster( clustererMinMarkersPerCluster );
		this.clustererCat5.setMinMarkersPerCluster( clustererMinMarkersPerCluster );
		
		var clustererMaxVisibleMarkers = 3;
		this.clustererCat1.setMaxVisibleMarkers( clustererMaxVisibleMarkers );
		this.clustererCat2.setMaxVisibleMarkers( clustererMaxVisibleMarkers );
		this.clustererCat3.setMaxVisibleMarkers( clustererMaxVisibleMarkers );
		this.clustererCat4.setMaxVisibleMarkers( clustererMaxVisibleMarkers );
		this.clustererCat5.setMaxVisibleMarkers( clustererMaxVisibleMarkers );
		
		var clustererIconCat1 = new GIcon();
		clustererIconCat1.image = "/img/ico_group1.gif";
		clustererIconCat1.shadow = "/img/ico_group1.gif";
		clustererIconCat1.iconSize = new GSize( 14, 22 );
		clustererIconCat1.shadowSize = new GSize( 0, 0 );
		clustererIconCat1.iconAnchor = new GPoint( 5, 14 );
		clustererIconCat1.infoWindowAnchor = new GPoint( 5, 14 );
		clustererIconCat1.imageMap = [ 0, 0, 0, 10, 10, 10, 10, 0 ];
		
		var clustererIconCat2 = new GIcon();
		clustererIconCat2.image = "/img/ico_group2.gif";
		clustererIconCat2.shadow = "/img/ico_group2.gif";
		clustererIconCat2.iconSize = new GSize( 14, 22 );
		clustererIconCat2.shadowSize = new GSize( 0, 0 );
		clustererIconCat2.iconAnchor = new GPoint( 5, 14 );
		clustererIconCat2.infoWindowAnchor = new GPoint( 5, 14 );
		clustererIconCat2.imageMap = [ 0, 0, 0, 10, 10, 10, 10, 0 ];
		
		var clustererIconCat3 = new GIcon();
		clustererIconCat3.image = "/img/ico_group3.gif";
		clustererIconCat3.shadow = "/img/ico_group3.gif";
		clustererIconCat3.iconSize = new GSize( 14, 22 );
		clustererIconCat3.shadowSize = new GSize( 0, 0 );
		clustererIconCat3.iconAnchor = new GPoint( 5, 14 );
		clustererIconCat3.infoWindowAnchor = new GPoint( 5, 14 );
		clustererIconCat3.imageMap = [ 0, 0, 0, 10, 10, 10, 10, 0 ];
		
		var clustererIconCat4 = new GIcon();
		clustererIconCat4.image = "/img/ico_group4.gif";
		clustererIconCat4.shadow = "/img/ico_group4.gif";
		clustererIconCat4.iconSize = new GSize( 14, 22 );
		clustererIconCat4.shadowSize = new GSize( 0, 0 );
		clustererIconCat4.iconAnchor = new GPoint( 5, 14 );
		clustererIconCat4.infoWindowAnchor = new GPoint( 5, 14 );
		clustererIconCat4.imageMap = [ 0, 0, 0, 10, 10, 10, 10, 0 ];
		
		var clustererIconCat5 = new GIcon();
		clustererIconCat5.image = "/img/ico_group5.gif";
		clustererIconCat5.shadow = "/img/ico_group5.gif";
		clustererIconCat5.iconSize = new GSize( 14, 22 );
		clustererIconCat5.shadowSize = new GSize( 0, 0 );
		clustererIconCat5.iconAnchor = new GPoint( 5, 14 );
		clustererIconCat5.infoWindowAnchor = new GPoint( 5, 14 );
		clustererIconCat5.imageMap = [ 0, 0, 0, 10, 10, 10, 10, 0 ];
		
		this.clustererCat1.setIcon( clustererIconCat1 );
		this.clustererCat2.setIcon( clustererIconCat2 );
		this.clustererCat3.setIcon( clustererIconCat3 );
		this.clustererCat4.setIcon( clustererIconCat4 );
		this.clustererCat5.setIcon( clustererIconCat5 );
		/*
		 * Clusterer Config.
		 */
	}
	
	this.map.disableDoubleClickZoom();
//	this.map.enableScrollWheelZoom();
	this.map.disableScrollWheelZoom();
	
	this.callback = callback;
	
	GEvent.addListener( this.map, 'click', function(overlay, point) { callback(MWMAP_MAP_CLICK, overlay, point); });
	GEvent.addListener( this.map, 'dblclick', function(overlay, point) { callback(MWMAP_MAP_DBL_CLICK, overlay, point); });
	GEvent.addListener( this.map, 'singlerightclick', function(point, src, overlay) { callback(MWMAP_MAP_SINGLE_RIGHT_CLICK, overlay, point, {src : src}); });
	GEvent.addListener( this.map, 'moveend', function() { callback(MWMAP_MAP_MOVE_END); });
	
    this.map.addControl( new GLargeMapControl3D() );	// Controles de Zoom, movimento
    this.map.addControl( new GMapTypeControl() );	// Controle de tipo de mapa
	var ov = new GOverviewMapControl();				// Mapinha pequeno no canto
	this.map.addControl( ov );
//	GEvent.addListener( ov.getOverviewMap(), 'load', function() { ov.hide(); } );
	
	this.icons = new Array();
//	this.initIcons();
	
	this.markers = new Array();
	this.areas = new Array();
	
//	this.directions = new Array();
};

MW.Map.prototype.setCenter = function(point, zoom) {
	this.zoom = zoom;
	this.map.setCenter(point, zoom);
};

MW.Map.prototype.getCenter = function() {
	return this.map.getCenter();
};

MW.Map.prototype.unload = function() {
	GUnload();
};

MW.Map.prototype.addOverlay = function( overlay ) {
	if ( this.clustered && overlay instanceof MW.Marker ) {
		switch ( overlay.getType() ) {
			case "1":
				this.clustererCat1.addMarker( overlay, overlay.getTitle() );
				break;
			case "2":
				this.clustererCat2.addMarker( overlay, overlay.getTitle() );
				break;
			case "3":
				this.clustererCat3.addMarker( overlay, overlay.getTitle() );
				break;
			case "4":
				this.clustererCat4.addMarker( overlay, overlay.getTitle() );
				break;
			case "5":
				this.clustererCat5.addMarker( overlay, overlay.getTitle() );
				break;
			default:
				this.map.addOverlay( overlay );
		}
	} else {
		this.map.addOverlay( overlay );
	}
};

MW.Map.prototype.removeOverlay = function( overlay ) {
	if ( this.clustered && overlay instanceof MW.Marker ) {
		switch ( overlay.getType() ) {
			case "1":
				this.clustererCat1.removeMarker( overlay );
				break;
			case "2":
				this.clustererCat2.removeMarker( overlay );
				break;
			case "3":
				this.clustererCat3.removeMarker( overlay );
				break;
			case "4":
				this.clustererCat4.removeMarker( overlay );
				break;
			case "5":
				this.clustererCat5.removeMarker( overlay );
				break;
			default:
				this.map.removeOverlay( overlay );
		}
	} else {
		this.map.removeOverlay( overlay );
	}
};

MW.Map.prototype.getSize = function() {
	return this.map.getSize();
}

MW.Map.prototype.setZoom = function( level ) {
	this.zoom = level;
	this.map.setZoom( level );
};

MW.Map.prototype.getZoom = function() {
	return this.map.getZoom();
}

MW.Map.prototype.getBoundsZoomLevel = function( bounds ) {
	return this.map.getBoundsZoomLevel( bounds );
};

MW.Map.prototype.getInfoWindow = function() {
	return this.map.getInfoWindow();
};

MW.Map.prototype.initIcons = function() {
	baseIcon = new GIcon();
	baseIcon.shadow = "/imagens/im_sombra.png";
	baseIcon.iconSize = new GSize(18, 51);
	baseIcon.iconAnchor = new GPoint(8, 51);
	baseIcon.shadowSize = new GSize(36, 38);
	baseIcon.infoWindowAnchor = new GPoint(8, 51);
	this.icons[MWMARKER_TYPE_BASE] = baseIcon;

	markerIcon = new GIcon(baseIcon);
	markerIcon.image = "/imagens/ic_pontoGeral.png";
	this.icons[MWMARKER_TYPE_MARKER] = markerIcon;

	myMarkerIcon = new GIcon(baseIcon);
	myMarkerIcon.image = "/imagens/ic_meuPonto.png";
	this.icons[MWMARKER_TYPE_MY_MARKER] = myMarkerIcon;

	areaMarkerIcon = new GIcon(baseIcon);
	areaMarkerIcon.image = "/imagens/ic_marcArea.png";
	areaMarkerIcon.iconSize = new GSize(15, 34);
	areaMarkerIcon.iconAnchor = new GPoint(8, 34);
	this.icons[MWMARKER_TYPE_AREA_MARKER] = areaMarkerIcon;
	this.icons[MWMARKER_TYPE_MY_AREA_MARKER] = areaMarkerIcon;
};

MW.Map.prototype.addIcon = function(index, data) {
	var icon = new GIcon();
	icon.shadow = data.shadow.file;
	icon.shadowSize = new GSize(data.shadow.width, data.shadow.height);
	icon.image = data.icon.file;
	icon.iconSize = new GSize(data.icon.width, data.icon.height);
	icon.iconAnchor = new GPoint(data.anchor.x, data.anchor.y);
	icon.infoWindowAnchor = new GPoint(data.windowAnchor.x, data.windowAnchor.y);

	this.icons[index] = icon;
};

MW.Map.prototype.getIcon = function( index ) {
	return this.icons[ index ];
}

MW.Map.prototype.hasArea = function(id) {
	ret = false;
	for (i = 0; i < this.areas.length; i++) {
//		area = this.areas[i];
		if (this.areas[i].id == id) {
			ret = true;
		}
	}
	return ret;
};

MW.Map.prototype.getArea = function( areaId ) {
	for ( var i = 0 ; i < this.areas.length ; i++ ) {
		if ( this.areas[ i ].id == areaId ) {
			return this.areas[ i ];
		}
	}
	return null;
};

MW.Map.prototype.addAreaMarkers = function(lats, longs, owner, type, ids) {
	var points = new Array();
	var markers = new Array();

	for (var i = 0; i < lats.length; i++) {

		o = this.addAreaMarker(lats[i], longs[i], owner, type, ids[i]);
		points.push(o.point);
		markers.push(o.marker);
	}
	return {points: points, markers: markers};
};

MW.Map.prototype.addMarcoArea = function(area) {
	var o = this;
	this.map.addOverlay(area);
//	GEvent.addListener(area, 'click', function(point) { o.callback(MWMAP_AREA_CLICK, area, point); });

	this.areas.push(area);
};

MW.Map.prototype.addAreaMarker = function(lat, lng, owner, type, id) {
	var o = this;
	var options = { id: id, icon: this.icons[type], draggable: true, name: "areaNode", type: type };
	var point = new GLatLng(lat, lng);

	var areaMarker = new MW.AreaMarker(point, options);

	areaMarker.disableDragging();
	if (owner) {
		areaMarker.enableDragging();
	}
	GEvent.addListener(areaMarker, 'dblclick', function() { o.callback(MWMAP_AREA_MARKER_DBL_CLICK, areaMarker); } );
	GEvent.addListener(areaMarker, 'dragend', function() { o.callback(MWMAP_AREA_MARKER_DRAG_END, areaMarker); });

	GEvent.addListener(areaMarker, 'click', function() { o.callback(MWMAP_MARKER_CLICK, areaMarker); });

	this.map.addOverlay(areaMarker);

	this.markers.push(areaMarker);

	return {point: point, marker: areaMarker};
};

MW.Map.prototype.addArea = function(area) {
	var o = this;
	this.map.addOverlay(area);
	GEvent.addListener(area, 'click', function(point) { o.callback(MWMAP_AREA_CLICK, area, point); });

	this.areas.push(area);
};

MW.Map.prototype.redrawArea = function(area) {
	var o = this;
 	var poly = new Array();

 	//Removing area draw
	this.map.removeOverlay(area);
 	if (area.gPol) {
	 	this.map.removeOverlay(area.gPol);
 	}

 	//populates the poly variable with all vertices
	for (var i = 0; i < area.markers.length; i++){
		poly.push(area.markers[i].getPoint());
	}

	//creates a new polygon, put it on the map and push it on the areas array
	area.gPol = new GPolygon(poly, "#ff0000", 1, 0.8, "#00ff00", 0.4);
	this.map.addOverlay(area.gPol);
	GEvent.addListener(area.gPol, 'click', function(point) { o.callback(MWMAP_AREA_CLICK, area, point); });
};

MW.Map.prototype.removeArea = function(area){
	var o = this;
	this.map.removeOverlay(area);
};

MW.Map.prototype.hasMarker = function(id) {
	ret = false;
	for (var i = 0; i < this.markers.length; i++) {
		m = this.markers[i];
		if (m.id == id) {
			ret = true;
		}
	}
	return ret;
};

MW.Map.prototype.addMarco = function( point, data ) {
	var o = this;
	var type = data.type || MWMARKER_TYPE_DEFAULT_MARCO;
	var arguments = { title: data.name, type: type, draggable: true, dragCrossMove: true, icon: this.icons[ type ] };
	var marco = new MW.Marker( point, arguments );	
//	var marco = new MW.Marco( point, arguments );
	marco.name = data.name;
	
	o.addOverlay( marco );
//	this.map.addOverlay( marker );
	
	GEvent.addListener(marco, 'dragstart', function() { o.callback(MWMAP_MARKER_DRAG_START, marco); });
	GEvent.addListener(marco, 'dragend', function() { o.callback(MWMAP_MARKER_DRAG_END, marco); });
	GEvent.addListener(marco, 'click', function() { o.callback(MWMAP_MARCO_CLICK, marco); });
	GEvent.addListener(marco, 'mouseout', function() { o.callback(MWMAP_ONMOUSEOUT_MARCO, marco); });
	
//	var data = { labelText : name , labelClass : "markerLabel" , labelOffset : new GSize( 0, 0 ) , icon : labeledIcon , dragCrossMove : false , clickable : false , draggable : false };
	
//	var label = new MW.LabeledMarker( point, data );
	
	this.markers.push(marco);
//	this.marcos.push(marco);
	return marco;
};

// pcmnac
MW.Map.prototype.getMarker = function (id)
{
  var marker = false;
	for (var i = 0; i < this.markers.length; i++) {
		m = this.markers[i];
		if (m.id == id) {
			marker = m;
		}
	}
	return marker;
}

MW.Map.prototype.addMarker = function( point, data ) {
	var o = this;
	var type = data.type || MWMARKER_TYPE_MARKER;
	var arguments = { title: data.name, type: type, draggable: true, dragCrossMove: true, icon: this.icons[ type ] };
	var marker = new MW.Marker( point, arguments );
	marker.name = data.name;
	
	o.addOverlay( marker );
//	this.map.addOverlay( marker );
	
	GEvent.addListener(marker, 'dragend', function() { o.callback(MWMAP_MARKER_DRAG_END, marker); });
	GEvent.addListener(marker, 'click', function() { o.callback(MWMAP_MARKER_CLICK, marker); });
	
	
//	var data = { labelText : name , labelClass : "markerLabel" , labelOffset : new GSize( 0, 0 ) , icon : labeledIcon , dragCrossMove : false , clickable : false , draggable : false };
	
//	var label = new MW.LabeledMarker( point, data );
	
	
	this.markers.push(marker);
	return marker;
};

MW.Map.prototype.addOnlyShowMarker = function(point) {
	var o = this;
	var type = MWMARKER_TYPE_MARKER;
	var arguments = { type: type, draggable: false, dragCrossMove: true, icon: this.icons[type]};
	var marker = new MW.Marker(point, arguments);

	this.map.addOverlay(marker);
};

MW.Map.prototype.disableMarkersDrag = function(type) {
	for (i = 0; i < this.markers.length; i++) {
		m = this.markers[i];
		if (m.type == type) {
			m.disableDragging();
		}
	}
};

MW.Map.prototype.enableMarkersDrag = function(type) {
	for (i = 0; i < this.markers.length; i++) {
		m = this.markers[i];
		if (m.type == type) {
			m.enableDragging();
		}
	}
};

MW.Map.prototype.removeMarker = function(marker) {
  var o = this;
	//this.map.removeOverlay(marker);
  o.removeOverlay(marker);
	temp = new Array();
	for (i = 0; i < this.markers.length; i++) {
		if (this.markers[i].id != marker.id) {
			temp.push(this.markers[i]);
		}
	}
	this.markers = temp;
};

MW.Map.prototype.openWindow = function(point, data) {
	this.map.openInfoWindowHtml(point, data);
};

MW.Map.prototype.closeWindow = function() {
	this.map.closeInfoWindow();
};

MW.Map.prototype.goToMarker = function( id, zoomLevel ) {
	
	if ( typeof zoomLevel == 'undefined' ) {
		zoomLevel = 15;
	}
	
	var marker = null;
	var ret = false;
	for (i = 0; i < this.markers.length; i++) {
		if (this.markers[i].id == id) {
			marker = this.markers[i];
			break;
		}
	}
	if (marker) {
		marker.show();
		this.map.setCenter( marker.getPoint(), zoomLevel );
		this.map.setZoom( zoomLevel );
		GEvent.trigger(marker, 'click');
		ret = true;
	}
	return ret;
};

MW.Map.prototype.moveTo = function(x, y, z){
	var point = new GLatLng(x, y);
	this.map.setCenter(point, this.zoom);
};

MW.Map.prototype.hideByCategory = function(id) {
	for (var i = 0; i < this.markers.length; i++) {
		if (this.markers[i].category == id) {
			this.markers[i].hide();
		}
	}
};

MW.Map.prototype.showByCategory = function(id) {
	for (var i = 0; i < this.markers.length; i++) {
		if (this.markers[i].category == id) {
			this.markers[i].show();
		}
	}
};

MW.Map.prototype.hideByOwner = function() {
	for (var i = 0; i < this.markers.length; i++) {
		if(this.markers[i].owner == false){
			this.markers[i].hide();
		}
	}
};

MW.Map.prototype.showAllMarkers = function() {
	for (var i = 0; i < this.markers.length; i++) {
			this.markers[i].show();
	}
};

MW.Map.prototype.hideByDatePeriod = function(startDate, finishDate) {
	for (var i = 0; i < this.markers.length; i++) {
		if ((this.markers[i].data_crime < startDate) || (this.markers[i].data_crime > finishDate)) {
			this.markers[i].hide();
		}
	}
};

MW.Map.prototype.showByDatePeriod = function(startDate, finishDate) {
	for (var i = 0; i < this.markers.length; i++) {
		if ((this.markers[i].data_crime >= startDate) && (this.markers[i].data_crime <= finishDate)) {
			this.markers[i].show();
		}
	}
};

MW.Map.prototype.hideBy = function(categories, startDate, finishDate) {
	for (var i = 0; i < this.markers.length; i++) {
		if (categories.inArray(this.markers[i].category)) {
			this.markers[i].hide();
		} else if ((startDate !== null) && (this.markers[i].data_crime !== null) && (this.markers[i].data_crime < startDate)) {
			this.markers[i].hide();
		} else if ((finishDate !== null) && (this.markers[i].data_crime !== null) && (this.markers[i].data_crime > finishDate)) {
			this.markers[i].hide();
		} else {
			this.markers[i].show();
		}
	}
};

MW.Map.prototype.hideByHourPeriod = function(categories, startDate, startHour, endHour) {
	for (var i = 0; i < this.markers.length; i++) {
		hour = null;
		if (this.markers[i].data_crime !== null && this.markers[i].data_crime != undefined) {
			hour = (this.markers[i].data_crime.getHours() >= 10 ? this.markers[i].data_crime.getHours() : '0' + this.markers[i].data_crime.getHours()) 
				+':'+ (this.markers[i].data_crime.getMinutes() >= 10 ? this.markers[i].data_crime.getMinutes() : '0' + this.markers[i].data_crime.getMinutes()); 
		}
		if (categories.inArray(this.markers[i].category)) {
			this.markers[i].hide();
		} else if ((startDate !== null) && (this.markers[i].data_crime !== null) && 
				   (this.markers[i].data_crime < startDate)) {
			this.markers[i].hide();
		} else if ((hour !== null) && ((hour < startHour) || (hour > endHour))) {
			this.markers[i].hide();
		} else {
			this.markers[i].show();
		}		
	}
};

MW.Map.prototype.getBounds = function() {
	return this.map.getBounds();
};

MW.Map.prototype.getMarkersInArea = function(area) {
	var ret = new Array();
	for (var i = 0; i < this.markers.length; i++) {
		if ((!(this.markers[i] instanceof MW.AreaMarker)) && (!this.markers[i].isHidden())) {
			if (inside(this.markers[i], area.markers)) {
				ret.push(this.markers[i]);
			}
		}
	}
	return ret;
};

MW.Map.prototype.getMarkersInBoundaries = function() {
	var ret = new Array();
	var area = new Array();
	
	var bounds = this.getBounds();
	area[0] = bounds.getNorthEast().lat();
	area[1] = bounds.getSouthWest().lat();
	area[2] = bounds.getNorthEast().lng();
	area[3] = bounds.getSouthWest().lng();
	
	for (var i = 0; i < this.markers.length; i++) {
		if ( ( ! ( this.markers[i] instanceof MW.AreaMarker ) )
				&& ( ! this.markers[i].isHidden() ) ) {
			if ( this.markers[i].getLat() <= area[0]
					&& this.markers[i].getLat() >= area[1] 
					&& this.markers[i].getLong() <= area[2]
					&& this.markers[i].getLong() >= area[3] ) {
				ret.push( this.markers[i] );
			}
		}
	}
	return ret;
};

MW.Map.prototype.getMarkersInMarcoBoundaries = function(marco) {
	var lat = marco.getLat();
	var	lng = marco.getLong();
	var ret = new Array();
	var markersInBounds = map.getMarkersInBoundaries();


	var point = new GLatLng(lat, lng);

//	radius 1,5 km
	var r1 = 1500; 
	
	var vertexCount = 50 //number of vertex 
	 
// calculate Circle points	
	var points = new Array();
	var latConv = point.distanceFrom(new GLatLng(point.lat() + 0.1, point.lng())) * 10;
	var lngConv = point.distanceFrom(new GLatLng(point.lat(), point.lng() + 0.1)) * 10;
	var step = (360/vertexCount) || 10;

	
	for(var i = 0; i <= 360.001; i += step) {
		var y = r1 * Math.cos(i * Math.PI/180);
		var x = r1 * Math.sin(i * Math.PI/180);
		var lng = (x * Math.cos(0) - y * Math.sin(0))/lngConv;
		var lat = (y * Math.cos(0) + x * Math.sin(0))/latConv;

		points.push(new GLatLng(point.lat() + lat, point.lng() + lng));
	}

	for (var k = 0; k < markersInBounds.length; k++) {
			if (inside(markersInBounds[k], points)) {
				ret.push(markersInBounds[k]);
			}
	}

	return ret;
};

MW.Map.prototype.getAreasInBoundaries = function() {
	var ret = new Array();
	var viewArea = new Array();
	
	var bounds = this.getBounds();
	viewArea[ 0 ] = bounds.getNorthEast().lat();
	viewArea[ 1 ] = bounds.getSouthWest().lat();
	viewArea[ 2 ] = bounds.getNorthEast().lng();
	viewArea[ 3 ] = bounds.getSouthWest().lng();
	
	for ( var i = 0 ; i < this.areas.length ; i++ ) {
		var markers = this.areas[ i ].markers;
		for ( var j = 0 ; j < markers.length ; j++ ) {
			if ( markers[ j ].getLat() <= viewArea[ 0 ]
					&& markers[ j ].getLat() >= viewArea[ 1 ]
					&& markers[ j ].getLong() <= viewArea[ 2 ]
					&& markers[ j ].getLong() >= viewArea[ 3 ] ) {
				ret.push( this.areas[ i ] );
				break;
			}
		}
	}
	return ret;
};

MW.Map.prototype.getMarcosInBoundaries = function() {
	var ret = new Array();
	var area = new Array();
	
	var bounds = this.getBounds();
	area[0] = bounds.getNorthEast().lat();
	area[1] = bounds.getSouthWest().lat();
	area[2] = bounds.getNorthEast().lng();
	area[3] = bounds.getSouthWest().lng();
	
	for (var i = 0; i < this.markers.length; i++) {
	
		if(this.markers[i].type == 12 || this.markers[i].type == 13 ||
		   this.markers[i].type == 14 || this.markers[i].type == 15 ){
		   	
		   	if ( ( ! ( this.markers[i] instanceof MW.AreaMarker ) )
					&& ( ! this.markers[i].isHidden() ) ) {
				
				if ( this.markers[i].getLat() <= area[0]
					&& this.markers[i].getLat() >= area[1] 
					&& this.markers[i].getLong() <= area[2]
					&& this.markers[i].getLong() >= area[3] ) {
					
					ret.push( this.markers[i] );
				}	   	   
		   	}   
		}		
	}
	return ret;
};

MW.Map.prototype.getDirectionsInBoundaries = function( directions ) {
	
	var ROUTES         = 3;
	var START_MARKER   = 1;
	var END_MARKER     = 2;
	
	var ret = new Array();
	var viewArea = new Array();
	
	var bounds = this.getBounds();
	viewArea[ 0 ] = bounds.getNorthEast().lat();
	viewArea[ 1 ] = bounds.getSouthWest().lat();
	viewArea[ 2 ] = bounds.getNorthEast().lng();
	viewArea[ 3 ] = bounds.getSouthWest().lng();
	
	for ( var i = 0 ; i < directions.length ; i++ ) {
		
		for ( var j = 0 ; j < directions[ i ][ ROUTES ].length ; j++ ) {
			
			var startMarker = directions[ i ][ ROUTES ][ j ][ START_MARKER ];
			var endMarker   = directions[ i ][ ROUTES ][ j ][ END_MARKER ];
			
			if ( ( startMarker.getLat() <= viewArea[ 0 ]
					&& startMarker.getLat() >= viewArea[ 1 ]
					&& startMarker.getLong() <= viewArea[ 2 ]
					&& startMarker.getLong() >= viewArea[ 3 ] )
					|| ( endMarker.getLat() <= viewArea[ 0 ]
					&& endMarker.getLat() >= viewArea[ 1 ]
					&& endMarker.getLong() <= viewArea[ 2 ]
					&& endMarker.getLong() >= viewArea[ 3 ] ) ) {
				ret.push( directions[ i ] );
				break;
			}
		}
	}
	return ret;
};

MW.Map.prototype.getPoint = function(lat, lng){
	return new GLatLng(lat, lng);
};

/**
 * Marker object
 */

MW.Marker = function(point, data) {
	this.id = null;
	this.name = data.name || '';
	this.data_crime = null;
	this.sec_id = null;
	this.type = data.type;
	this.category = data.category;
	this.parent_category = data.parent_category;
	this.direction_id = null;
	this.direction_name = '';
	this.owner = false;
	
	GMarker.apply(this, arguments);
};
MW.Marker.prototype = new GMarker(new GLatLng(0, 0));
MW.Marker.prototype.getLat = function() {
	return parseFloat( this.getPoint().lat() );
};
MW.Marker.prototype.getLong = function() {
	return parseFloat( this.getPoint().lng() );
};
MW.Marker.prototype.showWindow = function(content) {
	this.openInfoWindowHtml( content );
	
	// If this marker is temporary, add a listener to close button of the info window,
	// removing the marker from map.
	if ( this.getTitle() == 'temp' ) {
		//var infoWindow = map.getInfoWindow();
		var marker = this;
		/*
		GEvent.clearListeners( this, 'infowindowclose' );
		
		GLog.write( 'Teste!' );
		
		GEvent.addListener( infoWindow, 'closeclick'     , function() {
					alert( 'closeclick' );
					map.removeMarker( marker );
				}
		);
		*/
		GEvent.addListener( marker, 'infowindowclose', function() {
//					alert( 'infowindowclose' );
					map.removeMarker( marker );
				}
		);
	}
	//destroy area around marco when infowindo closes
	GEvent.addListener( marker, 'infowindowclose', function() {map.callback(MWMAP_MARCO_WINDOW_CLOSE, marker);});
};

MW.Marker.prototype.hideWindow = function() {
	this.closeInfoWindow();
};

MW.Marker.prototype.changeIcon = function(iconIndex) {
	this.setImage(map.icons[iconIndex].image);
}

MW.Marker.prototype.getTitle = function() {
	/*
	var title = '';
	if ( this.X && this.X.title && this.X.title != 'undefined' ) {
		title = this.X.title;
	} else if ( this.O && this.O.title && this.O.title != 'undefined' ) {
		title = this.O.title;
	}
	return title;
	*/
	return this.name;
}

MW.Marker.prototype.getType = function() {
	return this.type;
}

/*
 * Area marker object
 */

MW.AreaMarker = function(point, data){
	this.id = data.id || '';
	this.name = data.name || '';
	this.type = data.type || MWMARKER_TYPE_AREA_MARKER;
	this.area = null;

	GMarker.apply(this, arguments);
};
MW.AreaMarker.prototype = new GMarker(new GLatLng(0, 0));
MW.AreaMarker.prototype.getLat = function() {
	return parseFloat( this.getPoint().lat() );
};
MW.AreaMarker.prototype.getLong = function() {
	return parseFloat( this.getPoint().lng() );
};

/*
 * Area object
 */

MW.Area = function(points, strokeColor,  strokeWeight,  strokeOpacity,  fillColor,  fillOpacity) {
	this.id = null;
	this.name = null;
	this.markers = new Array();
	
	GPolygon.apply( this, arguments );
};

MW.Area.prototype = new GPolygon(new Array(), '', '', '', '', '');

MW.Area.prototype.setName = function( name ) {
	
	if ( this.name != null ) {
		map.removeOverlay( this.name );
	}
	
	var avgLat = 0;
	var avgLng = 0;
	for (var i = 0; i < this.markers.length; i++) {
		avgLat += this.markers[ i ].getLat();
		avgLng += this.markers[ i ].getLong();
	}
	avgLat = avgLat / this.markers.length;
	avgLng = avgLng / this.markers.length;
	
	var labeledIcon = new GIcon();
	labeledIcon.image = "/imagens/ico_visualizar.gif";
	labeledIcon.shadow = "/imagens/ico_visualizar.gif";
	labeledIcon.iconSize = new GSize( 0, 0 );
	labeledIcon.shadowSize = new GSize( 0, 0 );
	labeledIcon.iconAnchor = new GPoint( 5, 5 );
	labeledIcon.infoWindowAnchor = new GPoint( 5, 5 );
	labeledIcon.imageMap = [ 0, 0, 0, 10, 10, 10, 10, 0 ];
	
	var firstPoint = new GLatLng( avgLat, avgLng );
	var data = { labelText : name , labelClass : "markerLabel" , labelOffset : new GSize( 0, 0 ) , icon : labeledIcon , dragCrossMove : false , clickable : false , draggable : false };
	
	this.name = new MW.LabeledMarker( firstPoint, data );
	
	map.addOverlay( this.name );
};

MW.Area.prototype.getName = function() {
	return this.name.labelText;
}

MW.Marker.prototype.getName = function() {
	return this.name;
}

/*
 * Labeled marker object
 */
MW.LabeledMarker = function( point, data ) {
	this.point = point;
	this.labelText = data.labelText || "";
	this.labelClass = data.labelClass || "markerLabel";
	this.labelOffset = data.labelOffset || new GSize( 0, 0 );
	
	GMarker.apply( this, arguments );
};

MW.LabeledMarker.prototype = new GMarker( new GLatLng( 0, 0 ) );

MW.LabeledMarker.prototype.initialize = function( map ) {
	GMarker.prototype.initialize.call( this, map );
	
	var div = document.createElement( "div" );
	div.className = this.labelClass;
	div.innerHTML = this.labelText;
	div.style.position = "absolute";
	map.getPane( G_MAP_MARKER_PANE ).appendChild( div );
	
	this.map = map;
	this.div = div;
}

MW.LabeledMarker.prototype.redraw = function( force ) {
	GMarker.prototype.redraw.call( this, map );
	
	// We only need to do anything if the coordinate system has changed
	if (!force) return;
	
	var p = this.map.fromLatLngToDivPixel( this.point );
	var z = GOverlay.getZIndex( this.point.lat() );
	
	this.div.style.left = ( p.x + this.labelOffset.width ) + "px";
	this.div.style.top = ( p.y + this.labelOffset.height ) + "px";
	this.div.style.zIndex = z + 1; // Directly in front of the marker image
}

MW.LabeledMarker.prototype.remove = function() {
	this.div.parentNode.removeChild( this.div );
	this.div = null;
	GMarker.prototype.remove.call( this );
}

/*
 * Directions object
 */
MW.Directions = function( loadDirectionCallback, errorDirectionCallback ) {
	this.id   = null;
	this.name = null;
	
	this.directions = new GDirections();
	
	GEvent.addListener( this.directions, 'load' , function() { loadDirectionCallback()  } );
	GEvent.addListener( this.directions, 'error', function() { errorDirectionCallback() } );
	
//	GDirections.apply( this, arguments );
}

//MW.Directions.prototype = new GDirections();

MW.Directions.prototype.getPolyline = function() {
	return this.directions.getPolyline();
}

MW.Directions.prototype.getNumRoutes = function() {
	return this.directions.getNumRoutes();
}

MW.Directions.prototype.getRoute = function( i ) {
	return this.directions.getRoute( i );
}

MW.Directions.prototype.getStatus = function() {
	return this.directions.getStatus();
}

MW.Directions.prototype.load = function( query, queryOpts ) {
	this.directions.load( query, queryOpts );
}

/*
 * Polyline object
 */
MW.Polyline = function( latlngs,  color,  weight,  opacity,  opts ) {
	GPolyline.apply( this, arguments );
}

MW.Polyline.prototype = new GPolyline( new Array(), '', '', '', '' );

MW.Polyline.prototype.contains = function( map, point, offSet ) {
	//Get tolerance in lat lng terms
	var latLength = map.getBounds().getNorthEast().lat() - map.getBounds().getSouthWest().lat();
	var lngLength = map.getBounds().getNorthEast().lng() - map.getBounds().getSouthWest().lng();
	var latPP = Math.abs(latLength/map.getSize().height);
	var lngPP = Math.abs(lngLength/map.getSize().width);
	var offSetFactor = offSet;	//6;	// 4
	var uLimitx1 = point.lng() + ( offSetFactor * lngPP );
	var uLimity1 = point.lat() + ( offSetFactor * latPP );
	var lLimitx1 = point.lng() - ( offSetFactor * lngPP );
	var lLimity1 = point.lat() - ( offSetFactor * latPP );
	var uLimitx2 = point.lng() + ( offSetFactor * lngPP );
	var uLimity2 = point.lat() - ( offSetFactor * latPP );
	var lLimitx2 = point.lng() - ( offSetFactor * lngPP );
	var lLimity2 = point.lat() + ( offSetFactor * latPP );
	
	/*
	var vertexes = new Array();
	vertexes.push( new GLatLng( uLimity1, uLimitx1 ) );
	vertexes.push( new GLatLng( lLimity1, lLimitx1 ) );
	vertexes.push( new GLatLng( lLimity2, lLimitx2 ) );
	vertexes.push( new GLatLng( uLimity2, uLimitx2 ) );
	map.addOverlay( new GPolygon( vertexes, "#ff0000", 1, 0.8, "#00ff00", 0.4 ) );
	*/
	
	var j = 0;
	var oddNodes1 = false;
	//First Check Upper Limit - oddNodes should be true for this one
	for (var i=0; i < this.getVertexCount(); i++) {
		j++;
		if (j == this.getVertexCount()) {j = 0;}
		
		if ( ( ( this.getVertex(i).lat() < uLimity1 )
				&& ( this.getVertex(j).lat() >= uLimity1 ) )
				|| ( ( this.getVertex(j).lat() < uLimity1 )
				&& ( this.getVertex(i).lat() >= uLimity1 ) ) ) {
			if ( this.getVertex(i).lng() + ( uLimity1 - this.getVertex(i).lat() )
					/ ( this.getVertex(j).lat() - this.getVertex(i).lat() )
					* ( this.getVertex(j).lng() - this.getVertex(i).lng() ) < uLimitx1 ) {
				oddNodes1 = !oddNodes1;
			}
		}
	}
	
	j = 0;
	var oddNodes2 = false;
	//Check lower limit - oddNodes should be false for this
	for (var i=0; i < this.getVertexCount(); i++) {
		j++;
		if (j == this.getVertexCount()) {j = 0;}
		
		if ( ( ( this.getVertex(i).lat() < lLimity1 )
				&& ( this.getVertex(j).lat() >= lLimity1 ) )
				|| ( ( this.getVertex(j).lat() < lLimity1 )
				&& ( this.getVertex(i).lat() >= lLimity1 ) ) ) {
			if ( this.getVertex(i).lng() + ( lLimity1 - this.getVertex(i).lat() )
					/ ( this.getVertex(j).lat() - this.getVertex(i).lat() )
					* ( this.getVertex(j).lng() - this.getVertex(i).lng() ) < lLimitx1 ) {
				oddNodes2 = !oddNodes2;
			}
		}
	}
	
	if ( oddNodes1 != oddNodes2 ) {
		return(true);
	} else {
		j = 0;
		var oddNodes3 = false;
		//First Check Upper Limit - oddNodes should be true for this one
		for (var i=0; i < this.getVertexCount(); i++) {
			j++;
			if (j == this.getVertexCount()) {j = 0;}
			
			if ( ( ( this.getVertex(i).lat() < uLimity2 )
					&& ( this.getVertex(j).lat() >= uLimity2 ) )
					|| ( ( this.getVertex(j).lat() < uLimity2 )
					&& ( this.getVertex(i).lat() >= uLimity2 ) ) ) {
				if ( this.getVertex(i).lng() + ( uLimity2 - this.getVertex(i).lat() )
						/ ( this.getVertex(j).lat() - this.getVertex(i).lat() )
						* ( this.getVertex(j).lng() - this.getVertex(i).lng() ) < uLimitx2 ) {
					oddNodes3 = !oddNodes3;
				}
			}
		}
		
		j = 0;
		var oddNodes4 = false;
		//Check lower limit - oddNodes should be false for this
		for (var i=0; i < this.getVertexCount(); i++) {
			j++;
			if (j == this.getVertexCount()) {j = 0;}
			
			if ( ( ( this.getVertex(i).lat() < lLimity2 )
					&& ( this.getVertex(j).lat() >= lLimity2 ) )
					|| ( ( this.getVertex(j).lat() < lLimity2 )
					&& ( this.getVertex(i).lat() >= lLimity2 ) ) ) {
				if ( this.getVertex(i).lng() + ( lLimity2 - this.getVertex(i).lat() )
						/ ( this.getVertex(j).lat() - this.getVertex(i).lat() )
						* ( this.getVertex(j).lng() - this.getVertex(i).lng() ) < lLimitx2 ) {
					oddNodes4 = !oddNodes4;
				}
			}
		}
		
		if ( oddNodes3 != oddNodes4 ) {
			return(true);
		} else {
			return(false);
		}
	}
}

/*
 * Util functions
 */

 function inside (point, points) {
		var ret = false;
		
		//This algorithm only works to positive numbers, so latitude values will be added 90,
		//and longitude, 180.
		var x = parseFloat( point.getLong() ) + 180;
		var y = parseFloat( point.getLat() ) + 90;
		
		var ix = 0;
		var iy = 0;
		var jx = 0;
		var jy = 0;
		
		var j = points.length - 1;
		for (var i = 0; i < points.length; i++) {
			ix = parseFloat( points[i].lng() ) + 180;
			iy = parseFloat( points[i].lat() ) + 90;
			jx = parseFloat( points[j].lng() ) + 180;
			jy = parseFloat( points[j].lat() ) + 90;

			if (((iy < y) && (jy >= y))	|| ((jy < y) && (iy >= y))) {
				if (x < (jx - ix) * (y - iy) / (jy - iy) + ix) {
					ret = !ret;
				}
			}
			j = i;
		}
		return ret;
	}

Array.prototype.inArray = function (value) {
	var ret = false;
    for (var i = 0; i < this.length; i++) {
        // Matches identical (===), not just similar (==).
        if (this[i] === value) {
            ret = true;
			break;
        }
    }
    return ret;
};

/*
 * Clusterer object
 */

// Clusterer.js - marker clustering routines for Google Maps apps
//
// Using these routines is very easy.
//
// 1) Load the routines into your code:
//
//        <script src="http://www.acme.com/javascript/Clusterer.js" type="text/javascript"></script>
//
// 2) Create a Clusterer object, passing it your map object:
//
//        var clusterer = new Clusterer( map );
//
// 3) Wherever you now do map.addOverlay( marker ), instead call
//    clusterer.addMarker( marker, title ).  The title is just a
//    short descriptive string to use in the cluster info-boxes.
//
// 4) If you are doing any map.removeOverlay( marker ) calls, change those
//    to clusterer.removeMarker( marker ).
//
// That's it!  Everything else happens automatically.
//
//
// The current version of this code is always available at:
// http://www.acme.com/javascript/
//
//
// Copyright © 2005,2006 by Jef Poskanzer <jef@mail.acme.com>.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
// For commentary on this license please see http://www.acme.com/license.html


// Constructor.
Clusterer = function ( map ) {
    this.map              = map;
    this.markers          = [];
    this.clusters         = [];
    this.timeout          = null;
    this.currentZoomLevel = map.getZoom();
	
    this.maxVisibleMarkers = Clusterer.defaultMaxVisibleMarkers;
    this.gridSize = Clusterer.defaultGridSize;
    this.minMarkersPerCluster = Clusterer.defaultMinMarkersPerCluster;
    this.maxLinesPerInfoBox = Clusterer.defaultMaxLinesPerInfoBox;
    this.icon = Clusterer.defaultIcon;
	
    GEvent.addListener( map, 'zoomend', Clusterer.makeCaller( Clusterer.display, this ) );
    GEvent.addListener( map, 'moveend', Clusterer.makeCaller( Clusterer.display, this ) );
    GEvent.addListener( map, 'infowindowclose', Clusterer.makeCaller( Clusterer.popDown, this ) );
};

Clusterer.defaultMaxVisibleMarkers = 150;
Clusterer.defaultGridSize = 5;
Clusterer.defaultMinMarkersPerCluster = 5;
Clusterer.defaultMaxLinesPerInfoBox = 10;

Clusterer.defaultIcon = new GIcon();
Clusterer.defaultIcon.image = 'http://www.acme.com/resources/images/markers/blue_large.PNG';
Clusterer.defaultIcon.shadow = 'http://www.acme.com/resources/images/markers/shadow_large.PNG';
Clusterer.defaultIcon.iconSize = new GSize( 30, 51 );
Clusterer.defaultIcon.shadowSize = new GSize( 56, 51 );
Clusterer.defaultIcon.iconAnchor = new GPoint( 13, 34 );
Clusterer.defaultIcon.infoWindowAnchor = new GPoint( 13, 3 );
Clusterer.defaultIcon.infoShadowAnchor = new GPoint( 27, 37 );

// Call this to change the cluster icon.
Clusterer.prototype.setIcon = function ( icon ) {
    this.icon = icon;
};


// Changes the maximum number of visible markers before clustering kicks in.
Clusterer.prototype.setMaxVisibleMarkers = function ( n ) {
    this.maxVisibleMarkers = n;
};


// Changes the grid size.
Clusterer.prototype.setGridSize = function( n ) {
	this.gridSize = n;
}


// Sets the minumum number of markers for a cluster.
Clusterer.prototype.setMinMarkersPerCluster = function ( n ) {
    this.minMarkersPerCluster = n;
};


// Sets the maximum number of lines in an info box.
Clusterer.prototype.setMaxLinesPerInfoBox = function ( n ) {
    this.maxLinesPerInfoBox = n;
};


// Returns Clusters array.
Clusterer.prototype.getClusters = function() {
	return this.clusters;
}

// Call this to add a marker.
Clusterer.prototype.addMarker = function ( marker, title ) {
    if ( marker.setMap != null ) {
    	marker.setMap( this.map );
    }
	
    marker.title = title;
    marker.onMap = false;
    this.markers.push( marker );
    this.displayLater();
};


// Call this to remove a marker.
Clusterer.prototype.removeMarker = function ( marker ) {
    for ( var i = 0; i < this.markers.length; ++i ) {
		if ( this.markers[i] == marker ) {
		    if ( marker.onMap ) {
		    	this.map.removeOverlay( marker );
		    }
			for ( var j = 0; j < this.clusters.length; ++j ) {
				var cluster = this.clusters[j];
				if ( cluster != null ) {
				    for ( var k = 0; k < cluster.markers.length; ++k ) {
						if ( cluster.markers[k] == marker ) {
						    cluster.markers[k] = null;
						    --cluster.markerCount;
						    break;
					    }
					}
				    if ( cluster.markerCount == 0 ) {
						this.clearCluster( cluster );
						this.clusters[j] = null;
					} else if ( cluster == this.poppedUpCluster ) {
						Clusterer.rePop( this );
					}
				}
			}
		    this.markers[i] = null;
		    break;
		    }
	}
    this.displayLater();
};


Clusterer.prototype.displayLater = function () {
    if ( this.timeout != null ) {
		clearTimeout( this.timeout );
	}
    this.timeout = setTimeout( Clusterer.makeCaller( Clusterer.display, this ), 50 );
};


Clusterer.display = function ( clusterer ) {
	var i, j, marker, cluster;
	
	clearTimeout( clusterer.timeout );
	
    var newZoomLevel = clusterer.map.getZoom();
    if ( newZoomLevel != clusterer.currentZoomLevel ) {
		// When the zoom level changes, we have to remove all the clusters.
		for ( i = 0; i < clusterer.clusters.length; ++i ) {
		    if ( clusterer.clusters[i] != null ) {
				clusterer.clearCluster( clusterer.clusters[i] );
				clusterer.clusters[i] = null;
			}
		}
		clusterer.clusters.length = 0;
		clusterer.currentZoomLevel = newZoomLevel;
	}
	
    // Get the current bounds of the visible area.
    var bounds = clusterer.map.getBounds();
	
    // Expand the bounds a little, so things look smoother when scrolling
    // by small amounts.
    var sw = bounds.getSouthWest();
    var ne = bounds.getNorthEast();
    var dx = ne.lng() - sw.lng();
    var dy = ne.lat() - sw.lat();
    if ( dx < 300 && dy < 150 )	{
		dx *= 0.10;
		dy *= 0.10;
		bounds = new GLatLngBounds(
				new GLatLng( sw.lat() - dy, sw.lng() - dx ),
				new GLatLng( ne.lat() + dy, ne.lng() + dx ) );
	}
	
    // Partition the markers into visible and non-visible lists.
    var visibleMarkers = [];
    var nonvisibleMarkers = [];
    for ( i = 0; i < clusterer.markers.length; ++i ) {
		marker = clusterer.markers[i];
		if ( marker != null ) {
//		    if ( bounds.contains( marker.getPoint() ) ) {
		    if ( bounds.contains( marker.getPoint() )
		    		&& ( ! marker.isHidden() ) ) {
				visibleMarkers.push( marker );
			} else {
				nonvisibleMarkers.push( marker );
			}
		}
	}
	
	// Take down the non-visible markers.
    for ( i = 0; i < nonvisibleMarkers.length; ++i ) {
		marker = nonvisibleMarkers[i];
		if ( marker.onMap ) {
		    clusterer.map.removeOverlay( marker );
		    marker.onMap = false;
		}
	}
	
    // Take down the non-visible clusters.
    for ( i = 0; i < clusterer.clusters.length; ++i ) {
		cluster = clusterer.clusters[i];
		if ( cluster != null && ! bounds.contains( cluster.marker.getPoint() ) && cluster.onMap ) {
		    clusterer.map.removeOverlay( cluster.marker );
		    cluster.onMap = false;
		}
	}
	
    // Clustering!  This is some complicated stuff.  We have three goals
    // here.  One, limit the number of markers & clusters displayed, so the
    // maps code doesn't slow to a crawl.  Two, when possible keep existing
    // clusters instead of replacing them with new ones, so that the app pans
    // better.  And three, of course, be CPU and memory efficient.
    if ( visibleMarkers.length > clusterer.maxVisibleMarkers ) {
		// Add to the list of clusters by splitting up the current bounds
		// into a grid.
		var latRange = bounds.getNorthEast().lat() - bounds.getSouthWest().lat();
		var latInc = latRange / clusterer.gridSize;
		var lngInc = latInc / Math.cos( ( bounds.getNorthEast().lat() + bounds.getSouthWest().lat() ) / 2.0 * Math.PI / 180.0 );
		for ( var lat = bounds.getSouthWest().lat(); lat <= bounds.getNorthEast().lat(); lat += latInc ) {
		    for ( var lng = bounds.getSouthWest().lng(); lng <= bounds.getNorthEast().lng(); lng += lngInc ) {
				cluster = new Object();
				cluster.clusterer = clusterer;
				cluster.bounds = new GLatLngBounds( new GLatLng( lat, lng ), new GLatLng( lat + latInc, lng + lngInc ) );
				
				/*
				var vertexes = [];
				vertexes.push( new GLatLng( lat, lng ) );
				vertexes.push( new GLatLng( lat, ( lng + lngInc ) ) );
				vertexes.push( new GLatLng( ( lat + latInc ), ( lng + lngInc ) ) );
				vertexes.push( new GLatLng( ( lat + latInc ), lng ) );
				
				var polygon = new GPolygon( vertexes, "#ff0000", 1, 0.8, "#00ff00", 0.4 );
				clusterer.map.addOverlay( polygon );
				*/
				
				cluster.markers = [];
				cluster.markerCount = 0;
				cluster.onMap = false;
				cluster.marker = null;
				clusterer.clusters.push( cluster );
			}
		}
		
		// Put all the unclustered visible markers into a cluster - the first
		// one it fits in, which favors pre-existing clusters.
		for ( i = 0; i < visibleMarkers.length; ++i ) {
		    marker = visibleMarkers[i];
		    if ( marker != null && ! marker.inCluster ) {
				for ( j = 0; j < clusterer.clusters.length; ++j ) {
				    cluster = clusterer.clusters[j];
				    if ( cluster != null && cluster.bounds.contains( marker.getPoint() ) ) {
						cluster.markers.push( marker );
						++cluster.markerCount;
						marker.inCluster = true;
					}
				}
			}
		}
		
		// Get rid of any clusters containing only a few markers.
		for ( i = 0; i < clusterer.clusters.length; ++i ) {
		    if ( clusterer.clusters[i] != null && clusterer.clusters[i].markerCount < clusterer.minMarkersPerCluster ) {
				clusterer.clearCluster( clusterer.clusters[i] );
				clusterer.clusters[i] = null;
			}
		}
	
		// Shrink the clusters list.
		for ( i = clusterer.clusters.length - 1; i >= 0; --i ) {
		    if ( clusterer.clusters[i] != null ) {
		    	break;
		    } else {
		    	--clusterer.clusters.length;
		    }
		}
		
		// Ok, we have our clusters.  Go through the markers in each
		// cluster and remove them from the map if they are currently up.
		for ( i = 0; i < clusterer.clusters.length; ++i ) {
		    cluster = clusterer.clusters[i];
		    if ( cluster != null ) {
				for ( j = 0; j < cluster.markers.length; ++j ) {
				    marker = cluster.markers[j];
				    if ( marker != null && marker.onMap ) {
						clusterer.map.removeOverlay( marker );
						marker.onMap = false;
					}
				}
			}
		}
		
		// Now make cluster-markers for any clusters that need one.
		for ( i = 0; i < clusterer.clusters.length; ++i ) {
		    cluster = clusterer.clusters[i];
		    if ( cluster != null && cluster.marker == null ) {
				// Figure out the average coordinates of the markers in this
				// cluster.
				var xTotal = 0.0, yTotal = 0.0;
				for ( j = 0; j < cluster.markers.length; ++j ) {
				    marker = cluster.markers[j];
				    if ( marker != null ) {
						xTotal += ( + marker.getPoint().lng() );
						yTotal += ( + marker.getPoint().lat() );
					}
				}
				var location = new GLatLng( yTotal / cluster.markerCount, xTotal / cluster.markerCount );
				marker = new GMarker( location, { icon: clusterer.icon } );
				cluster.marker = marker;
				GEvent.addListener( marker, 'click', Clusterer.makeCaller( Clusterer.popUp, cluster ) );
			}
		}
	}
	
    // Display the visible markers not already up and not in clusters.
    for ( i = 0; i < visibleMarkers.length; ++i ) {
		marker = visibleMarkers[i];
		if ( marker != null && ! marker.onMap && ! marker.inCluster ) {
		    clusterer.map.addOverlay( marker );
		    if ( marker.addedToMap != null )
			marker.addedToMap();
		    marker.onMap = true;
		}
	}
	
    // Display the visible clusters not already up.
    for ( i = 0; i < clusterer.clusters.length; ++i ) {
		cluster = clusterer.clusters[i];
		if ( cluster != null && ! cluster.onMap && bounds.contains( cluster.marker.getPoint() ) ) {
		    clusterer.map.addOverlay( cluster.marker );
		    cluster.onMap = true;
		}
	}
	
    // In case a cluster is currently popped-up, re-pop to get any new
    // markers into the infobox.
    Clusterer.rePop( clusterer );
};


Clusterer.popUp = function ( cluster ) {
    var clusterer = cluster.clusterer;
    var html = '<ul id="group">';
    var n = 0;
    for ( var i = 0; i < cluster.markers.length; ++i ) {
		var marker = cluster.markers[i];
		if ( marker != null ) {
		    ++n;
		    html += '<li>';
		    if ( marker.getIcon().smallImage != null ) {
				html += '<img src="' + marker.getIcon().smallImage + '">';
		    } else {
				html += '<img src="' + marker.getIcon().image + '" width="' + ( marker.getIcon().iconSize.width / 1.5 ) + '" height="' + ( marker.getIcon().iconSize.height / 1.5 ) + '">';
			}
		    html += '<a href="#" onclick="goToPoint(' + marker.id + ');">' + marker.title + '</a></li>';
		    if ( n == clusterer.maxLinesPerInfoBox - 1 && cluster.markerCount > clusterer.maxLinesPerInfoBox  ) {
				html += '<li>... e ' + ( cluster.markerCount - n ) + ' mais</li>';
				break;
			}
		}
	}
    html += '</ul>';
    clusterer.map.closeInfoWindow();
    cluster.marker.openInfoWindowHtml( html );
    clusterer.poppedUpCluster = cluster;
};


Clusterer.rePop = function ( clusterer ) {
    if ( clusterer.poppedUpCluster != null ) {
		Clusterer.popUp( clusterer.poppedUpCluster );
	}
};


Clusterer.popDown = function ( clusterer ) {
    clusterer.poppedUpCluster = null;
};


Clusterer.prototype.clearCluster = function ( cluster ) {
    var i, marker;
	
    for ( i = 0; i < cluster.markers.length; ++i ) {
		if ( cluster.markers[i] != null ) {
		    cluster.markers[i].inCluster = false;
		    cluster.markers[i] = null;
		}
	}
    cluster.markers.length = 0;
    cluster.markerCount = 0;
    if ( cluster == this.poppedUpCluster ) {
		this.map.closeInfoWindow();
	}
    if ( cluster.onMap ) {
		this.map.removeOverlay( cluster.marker );
		cluster.onMap = false;
	}
};


// This returns a function closure that calls the given routine with the
// specified arg.
Clusterer.makeCaller = function ( func, arg ) {
    return function () { func( arg ); };
};


// Augment GMarker so it handles markers that have been created but
// not yet addOverlayed.

GMarker.prototype.setMap = function ( map ) {
    this.map = map;
};

GMarker.prototype.addedToMap = function () {
    this.map = null;
};

GMarker.prototype.origOpenInfoWindow = GMarker.prototype.openInfoWindow;
GMarker.prototype.openInfoWindow = function ( node, opts ) {
    if ( this.map != null ) {
		return this.map.openInfoWindow( this.getPoint(), node, opts );
	} else {
		return this.origOpenInfoWindow( node, opts );
	}
};

GMarker.prototype.origOpenInfoWindowHtml = GMarker.prototype.openInfoWindowHtml;
GMarker.prototype.openInfoWindowHtml = function ( html, opts ) {
    if ( this.map != null ) {
		return this.map.openInfoWindowHtml( this.getPoint(), html, opts );
	} else {
		return this.origOpenInfoWindowHtml( html, opts );
	}
};

GMarker.prototype.origOpenInfoWindowTabs = GMarker.prototype.openInfoWindowTabs;
GMarker.prototype.openInfoWindowTabs = function ( tabNodes, opts ) {
    if ( this.map != null ) {
		return this.map.openInfoWindowTabs( this.getPoint(), tabNodes, opts );
	} else {
		return this.origOpenInfoWindowTabs( tabNodes, opts );
	}
};

GMarker.prototype.origOpenInfoWindowTabsHtml = GMarker.prototype.openInfoWindowTabsHtml;
GMarker.prototype.openInfoWindowTabsHtml = function ( tabHtmls, opts ) {
    if ( this.map != null ) {
		return this.map.openInfoWindowTabsHtml( this.getPoint(), tabHtmls, opts );
	} else {
		return this.origOpenInfoWindowTabsHtml( tabHtmls, opts );
	}
};

GMarker.prototype.origShowMapBlowup = GMarker.prototype.showMapBlowup;
GMarker.prototype.showMapBlowup = function ( opts ) {
    if ( this.map != null ) {
		return this.map.showMapBlowup( this.getPoint(), opts );
	} else {
		return this.origShowMapBlowup( opts );
	}
};
