function GoogleMapV3Interface(element) {

	console.debug('creating gmapv3')

	this.element = element;
	this.defaultZoom = 9;
	this.markerMap = {};
	this._rangeBoundMap = {};
	this._areaMap = {};
	
	this._zoomed = false;
	
//	this._boudsUpdateHelper = new UpdateHelper(this, this._BoundsChange);
}

GoogleMapV3Interface.prototype = {

	_notifyMapMove : function() {
		console.debug('google move');
		if(this.mapChangeHandler) {
			var center = this.map.getCenter();
			this.mapChangeHandler.handler.call(this.mapChangeHandler.scope, center.lat(), center.lng());
		}
	},

	_notifyBoundsChange : function() {

//		this._boudsUpdateHelper.commit();

		if(this._stopBoundsNotify) {
			console.debug('suppress _notifyBoundsChange');
			return;
		}
	
//		console.debug('google bounds');
		if(this.mapBoundsChangeHandler) {
			var center = this.map.getCenter();
			var bounds = this.map.getBounds();
			var sw = bounds.getSouthWest();
			var ne = bounds.getNorthEast();
			var dlat = (ne.lat()-sw.lat())*0.5;
			var dlng = (ne.lng()-sw.lng())*0.5;
//			this.mapBoundsChangeHandler.handler.call(this.mapBoundsChangeHandler.scope, sw.lat(), sw.lng(), ne.lat(), ne.lng());
			this.mapBoundsChangeHandler.handler.call(
				this.mapBoundsChangeHandler.scope,
				center.lat()-dlat, center.lng()-dlng, center.lat()+dlat, center.lng()+dlng
			);
		}
		
	},
	
	_notifyDragEnd : function() {
		if(this.dragEndHandler) this.dragEndHandler.handler.call(this.dragEndHandler.scope);
	},
	
	_notifyMapClick : function(latLng) {
		if(this.mapClickHandler) this.mapClickHandler.handler.call(this.mapClickHandler.scope, latLng.lat(), latLng.lng());
	},
	
	_handleClick : function(marker, point, overpoint) {
		if(point) {
			this._notifyMapClick(point.lat(), point.lng());
			return false;
		}
		
		console.debug('Marker clicked!');
		
		return false;
	},
	
	_notifyMarkerMove : function(name) {
		if(this.mapMarkerMoveHandler) {
			var pos = this.markerMap[name].item.getPosition();
			this.mapMarkerMoveHandler.handler.call(this.mapClickHandler.scope, name, pos.lat(), pos.lng());
		}
	},	

	_notifyMarkerClick : function(name) {
		console.debug('Google marker click', name);
		
		var marker = this.markerMap[name];
		if(marker.options && marker.options.html) {
			if(this._balloon) this._balloon.close();
			this._balloon = new google.maps.InfoWindow({ content : marker.options.html });
			this._balloon.open(this.map, marker.item);
			return;
		}
		
		if(this.mapMarkerClickHandler) {
			this.mapMarkerClickHandler.handler.call(this.mapClickHandler.scope, name);
		}
	},
	
	_notifyMarkerOver : function(name, over) {
//		console.debug('Google marker over', name, over);
		
		if(this.mapMarkerOverHandler) {
			this.mapMarkerOverHandler.handler.call(this.mapClickHandler.scope, name, over);
		}
	},	
	
	_notifyBalloonClose : function() {
		this.mapBalloonCloseHandler.handler.call(this.mapBalloonCloseHandler.scope);
	},	

	_notifyViewModeChange : function() {
		var type = this.map.getMapTypeId();
		var mode;
		
		if(type==google.maps.MapTypeId.HYBRID) {
			mode = "satellite";
		} else if(type==google.maps.MapTypeId.ROADMAP) {
			mode = "normal";
		} else {
			mode = type;
		}
		
		var map = this;
		
		$(document).oneTime(0, null, 
//		$(document).ready(
		function() {
			map._clearCopyright();
			if(mode == "osm") map._addOSMCopyright();
		});
		
		console.debug('Switch google view mode to', mode);
		
		this.viewModeChangeHandler.handler.call(this.viewModeChangeHandler.scope, mode);		
	},

	_setCenter : function() {
		if(!this.mapCenter || !this.map) return;
		if(this.defaultZoom) {
			this.map.setCenter(new GLatLng(this.mapCenter.lat, this.mapCenter.lng), this.defaultZoom);
			delete this.defaultZoom;
		} else {
			this.map.setCenter(new GLatLng(this.mapCenter.lat, this.mapCenter.lng));
		}
	},
	
	_shiftCenter : function() {
		if(!this.mapCenter || !this.map) return;
		this.map.panTo(new google.maps.LatLng(this.mapCenter.lat, this.mapCenter.lng));
	},
	
	_setBounds : function() {
		if(!this.mapBounds || !this.map) return;
		
//		this._stopBoundsNotify = true;
		
		var clat = (this.mapBounds.south+this.mapBounds.north)*0.5;
		var clng = (this.mapBounds.west+this.mapBounds.east)*0.5;
		var dlat = this.mapBounds.south-this.mapBounds.north;
		var dlng = this.mapBounds.east-this.mapBounds.west;

		var bounds = new google.maps.LatLngBounds(
			new google.maps.LatLng(clat-dlat*0.25, clng-dlng*0.25),
			new google.maps.LatLng(clat+dlat*0.25, clng+dlng*0.25)
		);


/*		
		var bounds = new google.maps.LatLngBounds(
				new google.maps.LatLng(this.mapBounds.south, this.mapBounds.west),
				new google.maps.LatLng(this.mapBounds.north, this.mapBounds.east)
			);
*/		
//		this.map.setZoom(5);

		if(this._zoomed) { // zoom workaround
			this._zoom = true;
		}

		this.map.fitBounds(bounds);

//		this.map.setZoom(this.map.getZoom()+1);
//		this.map.setZoom(this.map.getZoom()+1);


		this.map.setCenter(
				new google.maps.LatLng(
					(this.mapBounds.south+this.mapBounds.north)*0.5,
					(this.mapBounds.west+this.mapBounds.east)*0.5
				)
			);

		console.debug('!!!!!!!!!!!!!!!!!!!!!!!! FIX ZOOM 2');

	
//		this._stopBoundsNotify = false;
//		this._notifyBoundsChange();
	},	

	setCenter : function(lat, lng) {
		this.mapCenter = { lat : lat, lng : lng };
		this._setCenter();
	},
	
	shiftCenter : function(lat,lng) {
		this.mapCenter = { lat : lat, lng : lng };
		this._shiftCenter();
	},	

	setBounds : function(south, west, north, east) {
		this.mapCenter = { lat : 0.5 * (south+north), lng : 0.5 * (west+east) };
		this.mapBounds = { south : south, west : west, north : north, east : east };
		this._setBounds();
	},

	getViewState : function() {
		var bounds = this.map.getBounds();
		
		var ne = bounds.getNorthEast();
		var sw = bounds.getSouthWest();
		var center = bounds.getCenter();
		
		return { south : sw.lat(), west : sw.lng(), north : ne.lat(), east : ne.lng(), lat : center.lat(), lng : center.lng() };
	},

	setMapMoveHandler : function(scope,handler) {
		this.mapChangeHandler = { scope : scope, handler : handler };
	},

	setBoundsChangeHandler : function(scope,handler) {
		this.mapBoundsChangeHandler = { scope : scope, handler : handler };
	},

	setDragEndHandler : function(scope, handler) {
		this.dragEndHandler = { scope : scope, handler : handler };
	},

	setClickHandler : function(scope,handler) {
		this.mapClickHandler = { scope : scope, handler : handler };
	},

	setMarkerMoveHandler : function(scope,handler) {
		this.mapMarkerMoveHandler = { scope : scope, handler : handler };
	},

	setMarkerClickHandler : function(scope,handler) {
		this.mapMarkerClickHandler = { scope : scope, handler : handler };
	},

	setMarkerOverHandler : function(scope,handler) {
		this.mapOverHandler = { scope : scope, handler : handler };
	},
	
	setViewModeChangeHandler : function(scope, handler) {
		this.viewModeChangeHandler = { scope : scope, handler : handler };
	},
	
	setBalloonCloseHandler : function(scope,handler) {
		this.mapBalloonCloseHandler = { scope : scope, handler : handler };
	},	
	
/*
	setMarker : function(name, lat, lng, moveable) {
		var point = new GLatLng(lat, lng);
		if(name in this.markerMap) {
			var marker = this.markerMap[name];
			marker.setLatLng(point);
		} else {
			var marker = new GMarker(point, { draggable : moveable, clickable : true, title : name });
			this.markerMap[name] = marker;
			this.map.addOverlay(marker);
			if(moveable) {
				var scope = this;
				GEvent.addListener(marker, "dragend", function() { scope._notifyMarkerMove(name); });
			}
			GEvent.addListener(marker, "click", function() { scope._notifyMarkerClick(name); });			
		}
	},
*/	

	removeMarker : function(name) {

		console.debug('REMOVE GOOGLE MAP MARKER', name);

		if(name in this.markerMap) {
			var marker = this.markerMap[name].item;
			marker.setMap(null);
			delete this.markerMap[name];
		}
		
	},

	setupMarker : function(name, lat, lng, options) {
	
		console.debug('Setup marker',name,'at',lat,lng);
	
		var marker;

		if(name in this.markerMap) {
			marker = this.markerMap[name];
			marker.item.setMap(null);
			console.debug('Cancel old marker');
		} else {
			marker = {};
			console.debug('Create new marker');
		}
		
		nativeOptions = {
			draggable : (options.moveable) ? true : false,
			clickable : true,
			position : new google.maps.LatLng(lat, lng),
			map : this.map
		};
		
/*		
		options.image = {
			height: 27,
			url: "http://static.streetjournal.org/images/mark_1.png",
			width: 37,
			x: 13,
			y: -14
		}
*/		
		if(options.image) {
		
//			console.debug('--- --- Setup marker style', options.image);
		
			nativeOptions.icon = new google.maps.MarkerImage(
					options.image.url,
					new google.maps.Size(options.image.width, options.image.height),
					new google.maps.Point(0, 0),
					new google.maps.Point(options.image.width*0.5-options.image.x, options.image.height*0.5-options.image.y)
				);
				
			if(options.shadow) {
				nativeOptions.shadow = new google.maps.MarkerImage(
						options.shadow.url,
						new google.maps.Size(options.shadow.width, options.shadow.height),
						new google.maps.Point(0, 0),
						new google.maps.Point(options.shadow.width*0.5-options.shadow.x, options.shadow.height*0.5-options.shadow.y)
					);
			}
		}
		
		marker.options = (options) ? options : null;
		
		if(options.title) nativeOptions.title = options.title;
		
		marker.item = new google.maps.Marker(nativeOptions);
		this.markerMap[name] = marker;
		
//		marker.item.setMap(this.map);
			
		var scope = this;	
		
		if(options.moveable) {
			google.maps.event.addListener(marker.item, "dragend", function() { scope._notifyMarkerMove(name); });
		}
			
		google.maps.event.addListener(marker.item, "click", function() { scope._notifyMarkerClick(name); });
		google.maps.event.addListener(marker.item, "mouseover", function(point) { return scope._notifyMarkerOver(name, true); });
		google.maps.event.addListener(marker.item, "mouseout", function(point) { return scope._notifyMarkerOver(name, false); });
	},
	
	setRangeBound : function(name, south, west, north, east, color) {
		console.debug('GOOGLE setRangeBound');
	
		var object = this._rangeBoundMap[name];
		if(object) {
			object.setMap(null);
		}

		object = new google.maps.Polyline(
			{
				map : this.map,
				path : [
					new google.maps.LatLng(south, west),
					new google.maps.LatLng(south, east),
					new google.maps.LatLng(north, east),
					new google.maps.LatLng(north, west),
					new google.maps.LatLng(south, west)
				],
				strokeColor : color,
				strokeWeight : 5
			}
		);

		this._rangeBoundMap[name] = object;
	},

	setArea : function(name, points) {
		var object = this._areaMap[name];
		if(object) {
			object.setMap(null);
//			this.map.removeOverlay(object);
		}

		var data = new Array(points.length/2);
		for(var i=0; i<points.length; i+=2) data[i/2] = new google.maps.LatLng(points[i], points[i+1]);
		
		object = new google.maps.Polyline( { map : this.map, path : data, strokeColor : "red", strokeWeight : 5 } );
		this._areaMap[name] = object;
		
//		object.setMap(this.map);
//		this.map.addOverlay(object);
	},

	_addOSMCopyright : function () {
		this.map.controls[google.maps.ControlPosition.BOTTOM_CENTER].push($('<div style="font-size:8pt;margin-bottom:0.1em;border:1px solid white;border-radius:1em;opacity:0.7;background-color:#ffff80;color:black;width:10em;text-align:center">© Участники<br><a href="http://www.openstreetmap.org/" target="_blank">OpenStreetMap</a>,<br>CC-BY-SA</div>')[0]);
	},
	
	_clearCopyright : function() {
		this.map.controls[google.maps.ControlPosition.BOTTOM_CENTER].clear();
	},

	clear : function() {
	
		console.debug('CLEAR GOOGLE MAP OVERLAYS');
		
		for(i in this._areaMap) {
			console.debug('Remove area',i);
			this._areaMap[i].setMap(null);
		}
		
		for(i in this.markerMap) {
			console.debug('Remove marker',i);
			this.markerMap[i].item.setMap(null);
		}		
		
		for(i in this._rangeBoundMap) {
			this._rangeBoundMap[i].setMap(null);
		}
	
//		this.map.clearOverlays();
		this.markerMap = {};
		this._rangeBoundMap = {};
		this._areaMap = {};
	},

	show : function() {
		this._visible = true;
		$(this.element).css('left', 0);
//		this.element.style.display = "block";
		if(!this.map) {

				// TOP_LEFT

			var myOptions = {
//				zoom: 8,
				navigationControl: true,
				navigationControlOptions: { style: google.maps.NavigationControlStyle.SMALL, position : google.maps.ControlPosition.LEFT_TOP },
//				center: latlng,
//				disableDefaultUI : true,
				streetViewControl: false,
				mapTypeId: google.maps.MapTypeId.ROADMAP,
				mapTypeControl : false
//				mapTypeControlOptions : { style : google.maps.MapTypeControlStyle.DROPDOWN_MENU }
			};

			this.map = new google.maps.Map(this.element, myOptions);

			this.map.mapTypes.set("osm", {
				tileSize : new google.maps.Size(256,256),
				maxZoom : 20,
				getTile : function(pos, zoom, parent) {
						var img = parent.createElement('img');
						img.src = 'http://tile.openstreetmap.org/'+zoom+'/'+pos.x+'/'+pos.y+'.png';
						img.style.width = this.tileSize.width + 'px';
						img.style.height = this.tileSize.height + 'px';
						img.style.border = 'none';
						return img;
					}
				}
			);
			
/*		
			this.map = new GMap2(this.element);
			this.map.setUIToDefault();
//			this.map.addControl(new GOverviewMapControl());
			this.map.enableContinuousZoom();
			this.map.disableScrollWheelZoom();
//			this.map.disableDoubleClickZoom()
			this._setBounds();
			this._setCenter();
*/			
			var scope = this;
			
			google.maps.event.addListener( this.map, 'bounds_changed',
				function() {
					scope._zoomed = true;				
					if(scope._zoom) {
//						scope.map.setZoom(scope.map.getZoom()+1);
						scope._zoom = false;
					}
				
					if(scope._visible)
//						scope._boudsUpdateHelper.update();
						scope._notifyBoundsChange(); 
				}
			);
			
			google.maps.event.addListener( this.map, 'dragend',
				function(event) {
					if(scope._visible)
						scope._notifyDragEnd();
				}
			);


			google.maps.event.addListener( this.map, 'click',
				function(event) {
					if(scope._visible)
						scope._notifyMapClick(event.latLng);
				}
			);

			google.maps.event.addListener( this.map, 'maptypeid_changed',
				function() {
					if(scope._visible)
						scope._notifyViewModeChange();
				}
			);
			
			this.map.controls[google.maps.ControlPosition.TOP_RIGHT].push($('<div style="width:160px;height:31px"></div>')[0]);
			
//			google.maps.event.addListener(this.map, "infowindowclose", function() { scope._notifyBalloonClose(); });
//			google.maps.event.addListener(this.map, "moveend", function() { scope._notifyMapMove(); driver._notifyBoundsChange(); });
//			google.maps.event.addListener(this.map, "zoomend", function() { scope._notifyBoundsChange(); });
//			google.maps.event.addListener(this.map, "click", function(marker, point, overpoint) { return scope._handleClick(marker, point, overpoint); });
		}
	},
	
	hide : function() {
		this._visible = false;
		$(this.element).css('left', -$(this.element).outerWidth());
//		this.element.style.display = "none";
	},
	
	setViewMode : function(mode) {
		if(mode=='normal') {
			this.map.setMapTypeId(google.maps.MapTypeId.ROADMAP);
		} else if(mode=='satellite') {
			this.map.setMapTypeId(google.maps.MapTypeId.HYBRID);
		} else {
			this.map.setMapTypeId(mode);
		}
	},
	
	showBalloon : function(content, lat, lng) {
		if(this._balloon) this._balloon.close();
		this._balloon = new google.maps.InfoWindow({ content : content, position : new google.maps.LatLng(lat, lng) });
		this._balloon.open(this.map);
	},

	clearBalloon : function() {
		if(this._balloon) {
			this._balloon.close();
			this._balloon = null;
		}
	}

}
