/**
 * jQuery googleMap v1.3.4 
 * 
 * @author Thomas van de Put <thomas_put@hotmail.com>
 * @version 1.3.4
 * @date 20-04-2011
 *
 * @desc jQuery plugin for Google Maps
 * @example $(element).googleMap({key:value})
 * @requires jQuery 1.4.3 or higher
 */

(function($) {
	// The variables, methods and objects for this plugin
	var ver = '1.3.4',
		// Aviable maptypes and travelmodes
		maptypes = ['roadmap','hybrid','satellite','terrain','streetview'],
		travelmodes = ['driving','walking'], // cycling not yet available outside the US
		// Google services
		directionsservice, elevationservice, geocoder, streetviewservice, elevationservice,
		apiloaded = false,
		clickedlatlng,
		clickedmarker,
		options = {},
		methods = {
			
			init: function(opts){	
				
				methods.handlekeys();
				
				// Output version no to console.log
				methods.log(methods.version());
				
				// Merge options with default
				options = $.extend(defaults, opts);
				
				// Fist run the fixversion
				methods.fixversion();
				
				// Always return this
				return this.each(function(){
				
					var map = new google.maps.Map(document.getElementById('google'));
					var $this = this;
					
					// Keep reference to the map object
					$(this).data('map',map);
					$(this).data('ref',{
						'bounds': new google.maps.LatLngBounds(),
						'markers': options.markers || new Array(),
						'routedistance':0,
						'contextmenu':methods.getcontextmenu.apply(this)
					});
					
					// Load the markers
					methods.loadmarkers.apply($this);
					
					// We define the standard values
					methods.setmapclickevent.apply($this);
					methods.setmapevents.apply($this);
					methods.setmaptype.apply($this);
					methods.setcenter.apply($this);
					methods.setcontrols.apply($this);
					methods.settravelmode.apply($this);
					methods.setzoom.apply($this);
						
					// Wait until map is fully loaded
					google.maps.event.addListener(map,'idle',function(){
						methods.setcontrolreferences.apply($this);
					});
																	
				});
			},
			
			/**
			 * Perform search on the map
			 */
			search: function(value, addmarker){
				if(!value) 
					return;
					
				if(typeof addmarker == 'undefined')
					addmarker = options.addmarkeronsearch;
				
				var target = this;
				
				// First try to find the location
				methods.geocode.apply(this,[value,function(result){ 
					var latlng = result.geometry.location;
					if(addmarker)
						methods.addmarker.apply(target,[{latlng:{lat:latlng.lat(),lng:latlng.lng()},iscenter:true}]);
					else 
						methods.setcenter.apply(target,[{lat:latlng.lat(),lng:latlng.lng()}]);
				}]);
				
				return this;
			},
			
			/**
			 * Find markers within a range of * km
			 */
			searchclosest: function(address, range){
				var target = this,
					range = range || 20;

				if(!address)
					return methods.showclosest.apply(target,['all',range]);
				
				// Nederland toevoegen aan het address
				address += address.indexOf('Nederland')!= -1 ? '' : ', Nederland';
				
				methods.geocode.apply(this,[address, function(result){
					methods.showclosest.apply(target,[result,range]);
				}]);
			},
			
			/** 
			 * Show the closest markers in range
			 */ 
			showclosest: function(result, range){

				var target = this,
					lat = typeof result == 'object' ? result.geometry.location.lat() : '',
					lng = typeof result == 'object' ? result.geometry.location.lng() : '',
					R = 6371,
					closest = new Array(),
					success = false;

				// Remove all markers and clear bounds	
				methods.removeallmarkers.apply(target);
				target.data('ref').bounds = new google.maps.LatLngBounds();
						
				$.each(target.data('ref').markers, function(index,value){
		
					if(typeof result == 'object'){
						
						value = typeof value.i == 'undefined' ? value : value.i;
			
						var mlat = typeof value.position != 'undefined' ? value.position.lat() : value.latlng.lat,
							mlng = typeof value.position != 'undefined' ? value.position.lng() : value.latlng.lng,
							dLat = (mlat - lat)*Math.PI/180,
							dLong = (mlng - lng)*Math.PI/180,
							a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(lat*Math.PI/180) * Math.cos(lat*Math.PI/180) * Math.sin(dLong/2) * Math.sin(dLong/2),
							c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)),
							d = R * c;
	
						if(d <= range && value.setMap){
							value.setMap(target.data('map'));
							target.data('ref').bounds.extend(value.position);
							target.data('map').fitBounds(target.data('ref').bounds);
							success = true;
						}
					} else {
						value.i.setMap(target.data('map'));
						target.data('ref').bounds.extend(value.i.position);
						target.data('map').fitBounds(target.data('ref').bounds);
						success = true;
					}
				});
				
				if(!success){
					alert('Geen resultaten gevonden');
				}
				
				return this;
			},
			
			/**
			 * @params String or latlng object
			 */
			geocode: function(address, callback){
				if(!address || !callback) 
					return;
										
				if(address.lat && address.lng)
					return callback(new google.maps.LatLng(address.lat, address.lng), true);	

				var find = typeof address == 'string' ? {address:address} : {latLng: new google.maps.LatLng(address.lat, address.lng)};
				
				if(typeof geocoder == 'undefined')
					geocoder = new google.maps.Geocoder();
											
				// Send a request to google and on 
				geocoder.geocode(find, 
					function(results, status){
						if (status == google.maps.GeocoderStatus.OK){
							callback(results[0]);
						} else if (status == google.maps.GeocoderStatus.ZERO_RESULTS) {
							methods.log('Sorry, found no results : ' + status);
						} else {
							methods.log('Sorry, found no results : ' + status);
						}
					});
			},
			
			usleep: function(microseconds) {  
	
				var start = new Date().getTime();  
				while (new Date() < (start + microseconds/1000));  
				return true;  
			},
			
			/**
			 * Load markers from given array or file
			 */
			loadmarkers: function(){
				var target = $(this),
					markers = $(target).data('ref').markers;
				
				if(typeof markers != 'string' && !$.isArray(markers) && typeof markers != 'boolean')
					return methods.log("Markers are of incorrect type!");
				
				if(!markers) 
					return;
				
				if($.isArray(markers)){
					// Settings directions on the fly
					if(options.travelmode && options.travelonload)
						return methods.setdirections.apply(target);
					
					// Loading the markers from the options.markers Array
					$.each(markers, function(index,value){
						value = methods.convertkeys(value);
						methods.addmarker.apply(target, [value]);
					});
					return;
				}
				
				// If given markers is an xml file: check it and load it
				if(!/xml/.test(markers))
					return methods.log("[googleMap] File: '" + markers + "' is not a valid xml file");
				
				// Load these markers using Ajax
				$.ajax({
					url: markers, type:'GET', dataType:'xml', success:function(xml){ 
						$(target).data('ref').markers = new Array();
						$(xml).find('marker').each(function(){
							// Read its contents
							var childNode = new Object();
							$.each($(this).children(),function(){
								var nodeName = $(this).context.nodeName.toLowerCase(),
									text = $(this).text();

								if(nodeName == 'latlng')
									childNode[nodeName] = {lat:text.split(',')[0],lng:text.split(',')[1]};
								else
									childNode[nodeName] = text;
							});
							target.data('ref').markers.push(childNode);
						});
						methods.loadmarkers.apply(target);
					}
				});
			},
			
			/**
			 * @params object with at least latlng or address (String)
			 */
			addmarker: function(marker){
				var target = $(this);
				
				// If no address or latlng is specified
				if(!marker.latlng && !marker.address)
					return;
							
				// Make request
				methods.geocode(marker.latlng||marker.address,function(result, onlylatlng){
					// Attach latlng position to marker
					marker.latlng = onlylatlng ? result : result.geometry.location;	
					// Attach content for infowindow
					marker.content = marker.content || result.formatted_address;
					// Creating new marker
					marker.i = methods.newmarker.apply(target,[marker]);
					// Formated address to marker
					marker.formatted_address = onlylatlng ? '' : result.formatted_address;
					// Keeping track of the bounds
					target.data('ref').bounds.extend(marker.latlng);
					// If options allow to fit bounds
					if(options.fitbounds)
						target.data('map').fitBounds(target.data('ref').bounds);
					if(options.routepanel)
						methods.setroutepanel.apply(target);
						
					//
					setTimeout(function(){
						target.data('ref').markers.shift();
						target.data('ref').markers.push(marker);
					},100);
				});
				
				return marker;
			},
			
			/**
			 * @usage Handle marker click
			 * @usage Give marker a contenttype of 'function' or 'url' or just plain text (will appear in the infowindow)
			 */
			setmarkerclickevent: function(marker,map){
				
				// Check if marker needs to open an infowindow of handle another function
				var type = marker.contenttype || false;
				// Handle the function or url
				if(type && (type.toLowerCase() == 'function' || type.toLowerCase() == 'url')){
					var dothis = marker.content;
					if(typeof dothis == 'function'){
						dothis();
						return;
					}
					// Handle URLS
					dothis = "https://" + dothis.replace("https://","");
					if(marker.window == '_blank')
						window.open(dothis);
					else 
						document.location.href = dothis;
					return;
				}

				// Open the infowindow
				if(typeof map.infowindow == 'undefined')
					map.infowindow = new google.maps.InfoWindow();
				map.infowindow.setContent(marker.content);
				map.infowindow.open(map,marker.i);
			},
			
			/**
			 * @usage Creates new marker
			 * @params Object marker with at least latlng
			 */ 
			newmarker: function(marker){
				if(!marker.latlng)
					return;
				
				var map = $(this).data('map'),
					m = new google.maps.Marker({		
						draggable: options.markersdraggable,
						map: typeof directionsservice != 'undefined' ? null : map, 
						position: marker.latlng
						//icon: 'https://maps.gstatic.com/intl/nl_ALL/mapfiles/marker_greenA.png'
					}),
					menu = $(this).data('ref').contextmenu;
								
				if(options.icons){
					// Check if options.icons contains the icon defined in the marker
					if(marker.icon > 0 && marker.icon <= options.icons.length){
						var opts = options.icons[marker.icon-1];
						if(!opts.anchor)
							opts.anchor = {x:0,y:32};
						
						m.icon = new google.maps.MarkerImage(opts.file, null, null,
							// Ancher (x,y)
							new google.maps.Point(opts.anchor.x, opts.anchor.y)
						)
						
						// Optioneel een schaduw toevoegen
						if(opts.shadow)
							m.shadow = new google.maps.MarkerImage(opts.shadow, null, null,
								// Ancher (x,y)
								new google.maps.Point(0, opts.anchor.y)
							)
					}
				}		
				
				// Make a marker bounce or drop from te sky
				if(marker.animate || options.markeranimation)
					m.setAnimation(google.maps.Animation[(options.markeranimation||marker.animate).toUpperCase()]);
				
				// Setting center around this marker if setCenter is defined
				if(marker.iscenter && !options.fitbounds || options.markers.length == 1)
					methods.setcenter.apply($(this),[{lat:m.position.lat(),lng:m.position.lng()}]);
				
				// Bind mouseclick to this marker to open up an infowindow (left click)
				google.maps.event.addListener(m, 'click', function(event){ methods.setmarkerclickevent(marker,map) });
				// Right click
				google.maps.event.addListener(m, 'rightclick', function(event){ 
					if(!options.travelmode)
						return;
				
					var pixeloffset = methods.getpixeloffset(map,this);
					
					$(menu).show();
					$(menu).trigger('visible',[pixeloffset]);
					
					$('#contextmenu #removemarker').show();
					$('#contextmenu #routefrom, #contextmenu #routeto, #contextmenu #addmarker').hide();
					clickedmarker = m;
				});
				
				return m;
			},
			
			centeronmarker: function(index){
				google.maps.event.trigger(this.data('ref').markers[index].i,'click');
				var latlng = this.data('ref').markers[index].i.position;
				methods.showclosest.apply(this,['all'])
				methods.setcenter.apply(this,[{lat:latlng.lat(), lng:latlng.lng()}]);
				methods.setzoom.apply(this,[13]);
				document.location.hash = '';
				document.location.hash = '#gomap';
			},
			
			removeallmarkers: function(){
				var markers = $(this).data('ref').markers;
				if(!$.isArray(markers))
					return;
				
				$.each(markers, function(index,value){
					if(value.latlng && typeof value.latlng.lat == 'function')
						value.i.setMap(null);
				});
				
				return this;
			},
			
			/**
			 * Calculate pixel offset of anything on the map 
			 */
			getpixeloffset: function(map, element){
				var scale = Math.pow(2, map.getZoom()),
					nw = new google.maps.LatLng(
						map.getBounds().getNorthEast().lat(),
						map.getBounds().getSouthWest().lng()
					),
					worldCoordinateNW = map.getProjection().fromLatLngToPoint(nw),
					worldCoordinate = map.getProjection().fromLatLngToPoint(element.position),
					pixeloffset = new google.maps.Point(
						Math.floor((worldCoordinate.x - worldCoordinateNW.x) * scale),
						Math.floor((worldCoordinate.y - worldCoordinateNW.y) * scale)
					);			
				return pixeloffset;
			},
			/**
			 * Getters and setters for map
			 * @params String ,int (zoom) or object (controls)
			 */
			getcenter: function(){ return $(this).data('map').getCenter(); },
			setcenter: function(value){
				var map = $(this).data('map'),
					latlng = value || options.defaultcenter;

				map.setCenter(new google.maps.LatLng(latlng.lat, latlng.lng));

				if(streetviewservice)
					streetviewservice.setPosition(map.getCenter());
				
				return this;
			},
			
			/**
			 * Hacking the google maps controls to set custom x and y positions
			 */
			setcontrolreferences: function(){
				var map = $(this).data('map');
				
				// Find controls depending there position
				$(this).find('.gmnoprint').each(function(index,value){
					if($(this).position().left == 0 && $(this).position().top == 0 && $(this).children().length == 2){
						$(this).attr('id','pancontrol');
						$(this).next().attr('id','streetviewcontrol');
					}
					if($(this).position().left == 27 && $(this).position().top == 85)
						$(this).attr('id','zoomcontrol');
					if($(this).position().left == ($(map.getDiv()).width() - 117) && $(this).position().top == 0){
						$(this).attr('id','maptypecontrol');
						//$(this).find('div:not(:visible)').remove();
					}
					if($(this).position().left == 69 && $(this).position().top == ($(map.getDiv()).height() - 29))
						$(this).attr('id','scalecontrol');
				});
				
				methods.setcontrols.apply(this);
			},
			
			setcontrols: function(control){
				var map = $(this).data('map'),
					target = this;
				
				if(control === false){
					map.disableDefaultUI = true;
					return;
				} else if(typeof control == 'object') {
					// Disable all controls if 'all:false' is specified
					if(typeof control.all != 'undefined'){
						$.each(options.controls,function(index,value){
							options.controls[index] = control.all;
						});
					} else {
						$.each(control,function(index,value){
							options.controls[index.toLowerCase()] = value;
						});
					}
				}
				
				map.disableDoubleClickZoom = options.controls.disabledoubleclickzoom;	
				map.mapTypeControl = options.controls.maptypecontrol;
				map.mapTypeControlOptions = { 					
					position: google.maps.ControlPosition[options.controls.maptypecontrolposition],
					style: google.maps.MapTypeControlStyle[options.controls.maptypecontrolstyle] 
				}
					
				// Overviewmap (small map on the right)
				map.overviewMapControl = options.controls.overviewmapcontrol;
				map.overviewMapControlOptions = { 
					opened: options.controls.maptypecontrolopened
				};
				map.panControl = options.controls.pancontrol;
				/*map.panControlOptions = { 
					position: google.maps.ControlPosition[options.controls.pancontrolposition] 
				}*/
				//map.rotateControl = optopns.controls.rotatecontrol;// Not available yet
				map.scaleControl = options.controls.scalecontrol;
				map.scaleControlOptions = { 
					position: google.maps.ControlPosition[options.controls.scalecontrolposition] 
				}
				map.scrollwheel = options.controls.scrollwheel;
				map.streetViewControl = options.controls.streetviewcontrol;
				map.streetViewControlOptions = { 
					position: google.maps.ControlPosition[options.controls.streetviewcontrolposition] 
				}	
				map.zoomControl = options.controls.zoomcontrol;
				/*map.zoomControlOptions = { 
					position: google.maps.ControlPosition[options.controls.zoomcontrolposition],
					style : google.maps.ZoomControlStyle[options.controls.zoomcontrolstyle] 
				}*/
				
				// Applying custom positions
				setTimeout(function(){
					var controls = new Array();
					controls.push({id:'maptypecontrol',pos:options.controls.maptypecontrolposition});
					controls.push({id:'pancontrol',pos:options.controls.pancontrolposition});
					controls.push({id:'scalecontrol',pos:options.controls.scalecontrolposition});
					controls.push({id:'streeviewcontrol',pos:options.controls.streetviewcontrolposition});
					controls.push({id:'zoomcontrol',pos:options.controls.zoomcontrolposition});
					
					$.each(controls, function(index,value){
						if(typeof value.pos != 'object')
							return;
						
						var pos = value.pos;
						// Override left if right is given
						if(pos.right) pos.left = 'auto';
						// Override top if bottom is given
						if(pos.bottom) pos.top = 'auto';
						
						pos.top = parseInt(pos.top) + 'px !important';
						pos.left = parseInt(pos.left) + 'px !important';
						console.log(pos);
						// Css
						$(target).find('#' + value.id).css(pos);
					});
				},200);
				
				return this;
			},
						
			getmaptype: function(){ return $(this).data('map').getMapTypeId(); },
			
			/**
			 * @params String (see line 17 for available types)
			 */
			setmaptype: function(value){
				var type = (value || options.maptype || '').toLowerCase(),
					map = $(this).data('map');
						
				// Check if given maptype is available
				if(jQuery.inArray(type,maptypes))
					type = maptypes[0];
				
				// Setting up streetview
				if(!streetviewservice)
					streetviewservice = map.getStreetView();
				streetviewservice.setVisible(type=='streetview');
				
				// Map can't handle streetview as a maptype so we convert it to roadmap
				type = type.replace('streetview','roadmap');
				map.setMapTypeId(type);
				
				return this;
			},
			
			/**
			 * Add listener to map click
			 */
			setmapclickevent: function(){
				var target = this,
					map = $(this).data('map'),
					menu = $(this).data('ref').contextmenu;
					
				// What to do with right click
				google.maps.event.addListener(map, 'rightclick', function(event) {
					if(options.contextmenu)
						$(menu).delay(300).fadeIn('fast');
					$(menu).trigger('visible',[event]);
				});
				
				// Left click
				google.maps.event.addListener(map, 'click', function(event) {
					$(menu).hide();
				});
				
			},		
			
			/**
			 * Add other events to map
			 */
			setmapevents: function(){
				var target = this,
					map = $(this).data('map'),
					menu = $(this).data('ref').contextmenu;
					
				google.maps.event.addListener(map, 'zoom_changed', function(event) {
					$(menu).stop().hide();
				})
				return this;
			},
			
			/** 
			 * Create context menu for right click
			 */
			getcontextmenu: function(){

				var map = $(this).data('map'),
					menu = $('<ul></ul>').attr('id','contextmenu').css({
						listStyle:'none',
						background:'#FFF', 
						margin:'0px', 
						padding:'5px 0px', 
						border:'1px solid #CCC', 
						position:'absolute', 
						zIndex:1000, 
						display:'none'
					}),
					items = new Array(),
					target = this;
					
				// These items only show up if there is a travelmode
				if(options.travelmode){
					items.push({label:methods.get('regional.routefrom'),action:'routefrom'});
					items.push({label:methods.get('regional.routeto'),action:'routeto'});				
					items.push({label:methods.get('regional.addmarker'),action:'addmarker',display:'none'});
					items.push({label:methods.get('regional.removemarker'),action:'removemarker',display:'none'});
					items.push('-');
				} 
				
				// These will always show up if contextmenu is defined
				items.push({label:methods.get('regional.zoomin'),action:'zoomin'});
				items.push({label:methods.get('regional.zoomout'),action:'zoomout'});
				items.push({label:methods.get('regional.setcenter'),action:'setcenter'});
				items.push('-');
				items.push({label:methods.get('regional.getelevation'),action:'getelevation'});
				
				$.each(items, function(index,value){
					
					var action = value.action,
						item = $('<li></li>').css('display',value.display?value.display:'block');
						
						if(typeof value == 'object'){
							if(action)
								item.attr('id',action);
							
							item = item.append($('<a></a>').bind('click',function(){ 
							
								var latlng = clickedlatlng ? {lat:clickedlatlng.lat(),lng:clickedlatlng.lng()} : false,	
									m;
								
								//  Handle actions
								switch(action){
									case 'addmarker':
										m = methods.addmarker.apply(target,[{latlng:latlng}]);
										$(target).data('ref').markers.push(m);
										break;
									case 'getelevation':
										methods.getelevation.apply(target,[clickedlatlng]); 
										break;
									case 'removemarker':
										$('#contextmenu #removemarker').hide();
										$('#contextmenu #routefrom, #contextmenu #routeto, #contextmenu #addmarker').show();
										// Remove markers from array
										$.each($(target).data('ref').markers,function(index,value){
											if(value.i && value.i.__gm_id == clickedmarker.__gm_id){
												if(index < 2)
													$(target).data('ref').markers[index] = new Object();
												else
													$(target).data('ref').markers.splice(index,1);
												return;
											}
										});
										clickedmarker.setMap(null);
										methods.setroutepanel.apply(target);
										break;
									case 'routefrom':
									case 'routeto':
										if($(target).data('ref').markers.length == 0){
											$(target).data('ref').markers[0] = new Object();
											$(target).data('ref').markers[1] = new Object();
										}
										if(action=='routefrom' && $(target).data('ref').markers[0].i)
											$(target).data('ref').markers[0].i.setMap(null);
										if(action=='routeto' && $(target).data('ref').markers[1].i)
											$(target).data('ref').markers[1].i.setMap(null);
										m = methods.addmarker.apply(target,[{latlng:latlng}]);
										$(target).data('ref').markers[action=='routefrom'?0:1] = m;
										$('#contextmenu #addmarker').show();
										break;
									case 'setcenter': 
										methods.setcenter.apply(target,[latlng]); 
										break;
									case 'zoomin': 
										methods.setzoom.apply(target,[map.getZoom()+1]); 
										return map.panTo(clickedlatlng); 
									case 'zoomout': 
										methods.setzoom.apply(target,[map.getZoom()-1]); 
										return map.panTo(clickedlatlng); 
								}
								
								// These actions need the same methods
								if(action == 'addmarker' || action == 'routefrom' || action == 'routeto')
									methods.setdirections.apply(target);
										
								// After a click event hide the context menu
								menu.hide();
							
							}).text(value.label).css({
								padding:'3px 15px 3px 8px', 
								display:'block', 
								textdecoration:'none', 
								backgroundColor:'#FFF', 
								color:'#000', 
								fontFamily:'arial,helvetica,sans-serif', 
								fontSize:'13px', 
								cursor:'default'
							}))
							
							if(action){
								// Hover function
								item.find('a').hover(function(){
									$(this).css({backgroundColor:'#d6e9f8'}); 
								},function(){
									$(this).css({backgroundColor:'#FFF'})
								});
							}
							
						} else {
							item = item.append($('<span></span>').css({	
										borderBottom:'1px solid #CCC', 
										display:'block', 
										margin:'5px 0px 5px 0px'}));
						}
						
					// Add item to context menu
					menu.append(item);
				});
				
				menu.bind('contextmenu', function() { return false; });
				// If menu is triggered to be visible, handle the x and y coordinates
				menu.bind('visible', function(event,passedevent){
				
					var d = $(map.getDiv()),
						pixel = passedevent.pixel || passedevent,
						x = pixel.x + 2 - (pixel.x > d.width() - $(menu).width() ? $(menu).width() : 0),
						y = pixel.y + 2 - (pixel.y > d.height() - $(menu).height() ? $(menu).height() : 0);
					
					$(menu).css('left',x);
					$(menu).css('top',y);
					
					clickedlatlng = passedevent.latLng;
				});
				
				// Add context menu to google maps api
				$(map.getDiv()).append(menu);
				
				return menu;
			},
			/**
			 * @params String (see line 18 for available types)
			 */
			settravelmode: function(value){
				var type = (value || options.travelmode || ''),
					map = $(this).data('map');
				
				if(!type)
					return;
				
				if(typeof type != 'boolean')
					type = type.toLowerCase();
				
				// Check if given maptype is available
				if(jQuery.inArray(type,travelmodes) || typeof type == 'boolean')
					type = travelmodes[0];
				
				options.travelmode = type;
				// Make a new request (todo!)
				
				return this;
			},
			getzoom: function(){ return $(this).data('map').getZoom(); },
			setzoom: function(value){
				var zoom = value || options.zoom;
				$(this).data('map').setZoom(zoom);
			},
			
			setroutepanel: function(){
				
				var target = $(this),
					filled = 0;
				
				$.each($(this).data('ref').markers, function(index,marker){
					
					var char 	= String.fromCharCode(65+index),
						button 	= $('<input type="button" />').val(methods.get('regional.routedescriptionbutton')).css({
										display:'block', 
										clear:'both',
										float:'right',
										margin:'20px 13px 0 0'
								}).bind('click',function(){ methods.setdirections.apply(target); }),
						point 	= $('<div></div>').css({
										display:'block',
										height:'22px',
										marginTop:'4px',
										background:'url(\'httpss://maps.gstatic.com/mapfiles/circle_marker'+char+'.png\') 4px left no-repeat'
								}).addClass('point'),
						input	= $('<input type="text" />').attr('id','route_' + char).css({
										width:'210px',
										float:'left',
										margin:'0 0 0 20px'
									}).val(marker.address||marker.formatted_address||'').keyup(function(event){
										if(event.which == 13)
											methods.setdirections.apply(target);
										// Altering input will alter the markers 
										$(target).data('ref').markers[$(this).parent().index()].address = $(this).val();
									}),
						link 	= $('<a href="javascript:void(0)"></a>').css({
										display:'none',
										width:'10px',
										margin:'5px 0 0 4px',
										color:'#CCC',
										textDecoration:'none', 
										fontFamily:'Verdana',
										fontWeight:'bold',
										fontSize:'11px',
										float:'left'
								}).bind('click',function(){ 
									// Remove marker from marker array
									$(target).data('ref').markers.splice($(this).parent().index(),1);
									// Remove from map
									if($(this).data('marker').i)
										$(this).data('marker').i.setMap(null);
									// Apply directions and make a new form
									methods.setdirections.apply(target);
									methods.setroutepanel.apply(target);
								}).html('x').data('marker',marker);
								
					point.append(input).append(link);
					
					// Check if input field is filled
					filled += input.val() ? 1 : 0;
					
					// If this is the first time we call this function, add some css and append the button
					if(index==0){
						// Add a new location (button to do so)
						var addlocationbutton = $('<a></a>').html(methods.get('regional.addmarker')).bind('click',
							function(){
								$(target).data('ref').markers.push(new Object());
								methods.setroutepanel.apply(target);
							}).css({
								color:'#2200C1',
								fontSize:'11px',
								fontFamily:'Arial',
								cursor:'pointer'
							});
						$(options.routepanel.selector).show().empty().css({width:'248px'}).append(button).append(addlocationbutton);
					}
						
					// Insert the point
					$(point).insertBefore($(options.routepanel.selector).find('input[type="button"]'));
					
					// Set focus to field that doesn't have an input yet
					if(input.val().length == 0)
						$('#route_'+char).focus();	
				});
				
				// Hide route form if no more then 1 point is given
				if(filled < 1)
					$(options.routepanel.selector).hide();
				// Show delete buttons
				if($(options.routepanel.selector + ' .point').length > 2)
					$(options.routepanel.selector).find('a').show();
					
				// If jquery sortable is included in this page we can drag the points around en alter de direction
				if(typeof $.fn.sortable == 'function'){
					var tempindex;
					$(options.routepanel.selector).sortable({
						items: '.point',
						cursor:'crosshair',
						start: function(event,ui){
							// Get tempindex (drag)
							tempindex = ui.item.prevAll().length;
						},
						stop: function(event,ui){
							// New index (drop)
							var newindex = ui.item.prevAll().length;
							// Keep marker in temp var
							var tempmarker = $(target).data('ref').markers[newindex];
							// Switch the markers
							$(target).data('ref').markers[newindex] = $(target).data('ref').markers[tempindex];
							$(target).data('ref').markers[tempindex] = tempmarker;
							// Apply directions and make a new form
							if(filled > 1)
								methods.setdirections.apply(target);
							methods.setroutepanel.apply(target);
						}
					});
				}
			},
			
			/** 
			 * @usage Handling directions 
			 * @usage First address (or latlng) in Array will be starting and last in array wil be ending
			 * @usage Markers can be given a point parameter, waypoints then will be sorted
			 * @param Array with address or latlng coordinates
			 * @returns Draws direction on the map
			 */
			setdirections: function(waypoints){				
				// First remove all old markers
				methods.removeallmarkers.apply(this,[]);
				
				var end_address,
					markers = $(this).data('ref').markers,
					map = $(this).data('map'),
					menu = $(this).data('ref').contextmenu,
					request = $(this).data('ref').request,
					start_address,
					target = this;
				
				// Loop the given waypoints
				if($.isArray(waypoints)){
					$.each(waypoints,function(index,value){
						waypoints[index] = {location:value};
					});
				} else {
					// Getting the waypoints out of the options
					var waypoints = new Array();
					// Sort on points	(optional parameter in markers array/xml file)				
					markers.sort(function(a,b){ 
						return a.point-b.point;
					});
					$.each(markers,function(index,value){
						var loc = new Object();
						if(value.address)
							loc.location = value.address;
						else if(value.latlng){
							// Because some markers aren't ready when we call this function
							// some markers have an Object that stores the latlng,
							if(typeof value.latlng.lat == 'number')
								loc.location = new google.maps.LatLng(value.latlng.lat, value.latlng.lng);
							// others have a function called lat() or lng() to get the latlng
							else if(typeof value.latlng.lat == 'function')
								loc.location = new google.maps.LatLng(value.latlng.lat(), value.latlng.lng());
						}
						if(loc.location)
							waypoints.push(loc);
					});
				}
				
				// Route needs at least 2 points
				if(waypoints.length < 2) return;
				
				start_address = waypoints.shift();
				end_address = waypoints.pop();
						
				if(!start_address.location || !end_address.location)
					return methods.log('[googleMap] No starting and/or ending address given!');
				
				methods.settravelmode();
				// Make request
				request = {
					origin: start_address.location, 
					destination: end_address.location,
					waypoints: waypoints,
					optimizeWaypoints: true,
					travelMode: google.maps.DirectionsTravelMode[options.travelmode.toUpperCase()],
					provideRouteAlternatives: true
				};

				// first clear map (else it keeps old markers on map)
				if($(target).data('ref').directionsdisplay)
					$(target).data('ref').directionsdisplay.setMap(null);
				// If there is no directionsrenderer
				if(typeof directionsdisplay == 'undefined')
					$(target).data('ref').directionsdisplay = new google.maps.DirectionsRenderer({draggable:true});
				$(target).data('ref').directionsdisplay.setMap(map);
				
				// Google maps provides route, show this in the panel of provided in options
				if(options.directionspanel)
					$(target).data('ref').directionsdisplay.setPanel(document.getElementById($(options.directionspanel.selector).attr('id')));
				
				// Requesting route
				if(typeof directionsservice == 'undefined')
					directionsservice = new google.maps.DirectionsService();
					
				directionsservice.route(request, function(result, status) {
					if (status == google.maps.DirectionsStatus.OK) {
						// Draw directions on the map
						$(target).data('ref').directionsdisplay.setDirections(result);
						methods.showroutesteps.apply(target, [result]);
						methods.setroutepanel.apply(target);
					} else if (status == google.maps.DirectionsStatus.MAX_WAYPOINTS_EXCEEDED){
						methods.log('Max waypoints exceeded');
					} else { 
						methods.log('Sorry, found no results : ' + status); 
					}
				});
				
				return this;
			},
					
			// Show directions steps (where to turn left or right etc.)
			showroutesteps: function(result){
				if(!result) return;
				
				var map = $(this).data('map'),
					target = this,
					route = result.routes[0].legs[0];
					
				$.each(route.steps, function(index,value){
					// Only if we want to see the steps
					if(options.showroutesteps){
						var marker = new google.maps.Marker({
							position: value.start_point,
							map: map,
							instructions: value.instructions
						});
						
						(function(_marker){
							google.maps.event.addListener(_marker, 'click', function() {	
								if(typeof map.infowindow == 'undefined')
								map.infowindow = new google.maps.InfoWindow();
								map.infowindow.setContent(_marker.instructions);
								map.infowindow.open(map, _marker);
							});
						})(marker);
					
						$(target).data('ref').markers.push(marker);
					}
					
					// Add route distance (can be requested with method getroutedistance)
					$(target).data('ref').routedistance += value.distance.value;
					
				});
			},
			
			/**
			 * @returns distance (calculated in showroutesteps function)
			 * @params returnvalue String ('km' or 'm')
			 */
			getroutedistance: function(returnvalue, suffix){
				var distance = $(this).data('ref').routedistance;
				
				// Set 'km' as default returnvalue
				if(!returnvalue) returnvalue = 'km';
				
				// Set suffix by default if none specified
				if(typeof suffix == 'undefined')
					suffix = true;
				
				if(!distance)
					return methods.log('Sorry, we haven\'t got a distance yet.');
				
				if(returnvalue == 'km')
					return Number(distance/1000).toFixed(2) + (suffix ? ' kilometer' : '');
				
				return Math.floor(distance) + (suffix ? ' meters' : '');
			},
			
			/**
			 * Calculate elevation at given latlng position
			 */
			getelevation: function(location, callback){
				if(!location)
					return
				
				var find = {locations:[location]};
				
				if(typeof elevationservice == 'undefined')
					elevationservice = new google.maps.ElevationService();
					
				// Send a request to google and on
				elevationservice.getElevationForLocations(find, 
					function(results, status){
						if (status == google.maps.ElevationStatus.OK){
							if(callback)
								callback(results[0].elevation.toFixed(2));
							else 
								methods.log(methods.get('regional.elevationalert').replace('##',results[0].elevation.toFixed(2)));
						} else if (status == google.maps.ElevationStatus.ZERO_RESULTS) {
							// Reduce the search query to find more results (not working yet?)
							//self.reduce(find); 
						} else {
							methods.log('Sorry, found no results : ' + status)
						}
					});
			},

			/**
			 * Toggle panoramio (service that shows images on the google map)
			 */
			panoramiotoggle: function(value){
				var map = $(this).data('map');
				options.panoramio = value || !options.panoramio;
				
				if(typeof map.panoramiolayer == 'undefined')
					map.panoramiolayer = new google.maps.panoramio.PanoramioLayer();
				map.panoramiolayer.setMap(options.panoramio ? map : null);
				
				return this;
			},
			/**
			 * @usage Filters images
			 * @param String 
			 */
			panoramiofilter: function(value){
				var map = $(this).data('map');
				methods.panoramiotoggle.apply(this,[true]);
				map.panoramiolayer.setTag(value);
				
				return this;
			},
			
			// Log function
			log: function(){
				if (window.console && window.console.log && defaults.log)
					window.console.log('[googleMap] ' + Array.prototype.join.call(arguments,' '));
			},
			
			/**
			 * @usage Automatically fixes previous versions of methods and options
			 */
			fixversion: function(){
				// First convert al options to lowercase
				options = methods.convertkeys(options);
				
				if(options.hasstreetview){
					options.controls.streetviewcontrol = options.hasStreetview;
					delete options.hasstreetview;
				}
				
				if(options.hastravelmode && !options.travelmode){
					options.travelmode = 'default';
					delete options.hastravelmode;
				}
				
				if(options.markerisdraggable){
					options.draggablemarkers = options.markerisdraggable;
					delete options.markerisdraggable;
				}
					
			},
			
			// Return version number
			version: function(){
				return 'Version no. ' + ver + '';
			},
			
			/**
			 * @returns a converted Object
			 * @params Object, _case:null,'lowercase'or'uppercase'
			 */
			convertkeys: function(obj,_case){
				var newobj = new Object();
				for(var string in obj){
					if(!_case || _case == 'lowercase')
						newobj[string.toLowerCase()] = obj[string];
					else
						newobj[string.toUpperCase()] = obj[string];
				}
				return newobj;
			},
			
			/**
			 * getting options
			 */
			get: function(name){
				if(!name)
					return;
					
				var value = name.split('.');
				if(value.length == 1)
					return options[value[0]];
				if(value.length == 2)
					return options[value[0]][value[1]];
			},
			
			/**
			 * Changing default language
			 */
			setregional: function(regional){
				$.extend(defaults.regional,regional);
			},
			
			/**
			 * Handle key strokes but shortnend
			 * For example : put 'onenter="$.('#element').search('address')"' in a input field and it searches the map
			 */
			handlekeys: function(){
				// Attributes and keys to search for (possible to add more)
				var keycodes = new Array();
				keycodes.push({keycode:13,attr:'onenter'});
				keycodes.push({keycode:16,attr:'onshift'});
				keycodes.push({keycode:17,attr:'onctrl'});
				keycodes.push({keycode:20,attr:'oncaps'});
				keycodes.push({keycode:27,attr:'onesc'});

				$.each(keycodes,function(index,value){
					$(document).find('*['+value.attr+']').keyup(function(event){
						if(event.keyCode == value.keycode){
							event.preventDefault();
							eval($(this).attr(value.attr));
						}
					});	
				});
			}
			
		}
	
	// Main plugin function
	$.fn.googleMap = function(method){
		// We wait until google maps api is loaded
		if(!apiloaded){
			var target = this,
				args = arguments;
				
			setTimeout(function(){
				$.fn.googleMap.apply(target,args);
			},100);
			return false;
		}
		// If given method is an existing function, call this function
		if(methods[method])
			return methods[method].apply(this,Array.prototype.slice.call(arguments,1));
		// If given method is an object or no method is given, call init function
		else if(typeof method == 'object' || !method)
			return methods.init.apply(this,arguments);
		// If none of the above applies, call an error
		else 
			$.error('Method: \'' + method + '\' does not exist on [googleMap]');		
	}
	
	// URL where we can find the api
	var googleapiurl = "https://maps.google.com/maps/api/js?sensor=true&language=nl";
	// Default settings
	var defaults = {
		addmarkeronsearch:		false, 		// If set to true, it adds markers if you search for a location, if set to false it removes if you search for another item
		contextmenu: 			false, 		// Menu on mouse right click (language is customizable), true or false
		controls: {
				disabledoubleclickzoom:		false,			// true or false
				maptypecontrol: 			true,			// true or false
				maptypecontrolstyle: 		'default', 		// 'default','horizontal_bar','dropdown_menu'
				maptypecontrolposition: 	'default', 		// {top:x,left:x},'top_left','top_center','top_right','left_top','right_top','left_center', 'right_center','left_bottom','right_bottom','bottom_left','bottom_center','bottom_right'
				overviewmapcontrol:			false,			// true or false
				overviewmapcontrolopened:	true,			// true or false
				pancontrol: 				true,			// true or false
				pancontrolposition: 		{top:70,left:0},		// see maptypecontrolposition
				scalecontrol: 				true,			// true or false
				scalecontrolposition: 		'default',		// see maptypecontrolposition
				scrollwheel:				true,			// true or false
				streetviewcontrol: 			false,			// true or false
				streetviewcontrolposition: 	'default', 		// see maptypecontrolposition
				zoomcontrol: 				true,			// true or false
				zoomcontrolstyle: 			'default', 		// 'default','small','large'
				zoomcontrolposition: 		{top:150,left:27}		// see maptypecontrolposition
		},
		defaultcenter: 			{lat:52.116626, lng:5.114136}, 	// Default center if none is specifief (latlng is center of Utrecht)
		directionspanel:		false,		// Provide an object like $('#directionspanel');
		draggablemarkers: 		false,		// true or false
		fitbounds: 				false,		// true or false
		icons: 					[{file:'images/chrome.png'}],
		log: 					false,		// Set to true for warnings or errors (debug)
		maptype: 				'roadmap', 	// 'roadmap', 'satellite', 'hybrid' or 'terrain'
		markeranimation: 		false, 		// 'bounce' or 'drop' 
		markers: 				false, 		// can be either an array or an xml file (see below for an example of both)
		panoramio: 				false, 		// Adds photos to google maps
		regional:{
				addmarker: 'Bestemming toevoegen',
				elevationalert: 'Hoogte is hier ## meter', // Leave the ## for reference
				getelevation: 'Hoe hoog is het hier?',
				removemarker: 'Deze bestemming verwijderen',
				routedescriptionbutton: 'Route beschrijving',
				routefrom: 'Route vanaf dit punt',
				routeto: 'Route naar dit punt',
				setcenter: 'Kaart hier centreren',
				zoomin: 'Inzoomen',
				zoomout: 'Uitzoomen'
		},
		routepanel:				false, 		// if this is provided you get a form with route points 
		showroutesteps: 		false, 		// Shows points where to turn left or right with directions
		travelmode: 			false, 		// driving or cycling (walking isn't supported  yet)	
		travelonload: 			false, 		// true or false
		zoom: 					14			// between 0 and 21 where 0 is zoomed out 
	}
		
	// Called when google api loading is complete
	$.fn.googleready = function(){
		apiloaded = true;
	}
	
	// Load the google api Mozilla can't handle getscript without callback i guess
	if($.browser.mozilla && $.browser.version != '2.0.1' && $.browser.version != '5.0')
		$.getScript(googleapiurl, $.fn.googleready);	
	else
		$.getScript(googleapiurl + "&callback=$.fn.googleready", null);	
	
})(jQuery);
