/** * jQuery gMap v3 * * @url http://www.smashinglabs.pl/gmap * @author Sebastian Poreba * @version 3.3.2 * @date 16.03.2011 */ /*jslint white: false, undef: true, regexp: true, plusplus: true, bitwise: true, newcap: true, strict: true, devel: true, maxerr: 50, indent: 4 */ /*global window, jQuery, $, google, $googlemaps */ (function ($) { "use strict"; /** * Internals and experimental section */ var Cluster = function () { this.markers = []; this.mainMarker = false; this.icon = "http://www.google.com/mapfiles/marker.png"; }; /** * For iterating over all clusters to find if any is close enough to be merged with marker * * @param marker * @param currentSize - calculated as viewport percentage (opts.clusterSize) * @return bool */ Cluster.prototype.dist = function (marker) { return Math.sqrt(Math.pow(this.markers[0].latitude - marker.latitude, 2) + Math.pow(this.markers[0].longitude - marker.longitude, 2)); }; Cluster.prototype.setIcon = function (icon) { this.icon = icon; }; Cluster.prototype.addMarker = function (marker) { this.markers[this.markers.length] = marker; }; /** * returns one marker if there is only one or * returns special cloister marker if there are more */ Cluster.prototype.getMarker = function () { if (this.mainmarker) {return this.mainmarker; } var gicon, title; if (this.markers.length > 1) { gicon = new $googlemaps.MarkerImage("http://thydzik.com/thydzikGoogleMap/markerlink.php?text=" + this.markers.length + "&color=EF9D3F"); title = "cluster of " + this.markers.length + " markers"; } else { gicon = new $googlemaps.MarkerImage(this.icon); title = this.markers[0].title; } this.mainmarker = new $googlemaps.Marker({ position: new $googlemaps.LatLng(this.markers[0].latitude, this.markers[0].longitude), icon: gicon, title: title, map: null }); return this.mainmarker; }; // global google maps objects var $googlemaps = google.maps, $geocoder = new $googlemaps.Geocoder(), $markersToLoad = 0, overQueryLimit = 0, methods = {}; methods = { /** * initialisation/internals */ init: function (options) { var k, // Build main options before element iteration opts = $.extend({}, $.fn.gMap.defaults, options); // recover icon array for (k in $.fn.gMap.defaults.icon) { if(!opts.icon[k]) { opts.icon[k] = $.fn.gMap.defaults.icon[k]; } } // Iterate through each element return this.each(function () { var $this = $(this), center = methods._getMapCenter.apply($this, [opts]), i, $data; if (opts.zoom == "fit") { opts.zoomFit = true; opts.zoom = methods._autoZoom.apply($this, [opts]); } var mapOptions = { zoom: opts.zoom, center: center, mapTypeControl: opts.mapTypeControl, mapTypeControlOptions: {}, zoomControl: opts.zoomControl, zoomControlOptions: {}, panControl : opts.panControl, panControlOptions: {}, scaleControl : opts.scaleControl, scaleControlOptions: {}, streetViewControl: opts.streetViewControl, streetViewControlOptions: {}, mapTypeId: opts.maptype, scrollwheel: opts.scrollwheel, maxZoom: opts.maxZoom, minZoom: opts.minZoom }; if(opts.controlsPositions.mapType) {mapOptions.mapTypeControlOptions.position = opts.controlsPositions.mapType}; if(opts.controlsPositions.zoom) {mapOptions.zoomControlOptions.position = opts.controlsPositions.zoom}; if(opts.controlsPositions.pan) {mapOptions.panControlOptions.position = opts.controlsPositions.pan}; if(opts.controlsPositions.scale) {mapOptions.scaleControlOptions.position = opts.controlsPositions.scale}; if(opts.controlsPositions.streetView) {mapOptions.streetViewControlOptions.position = opts.controlsPositions.streetView}; mapOptions.mapTypeControlOptions.style = opts.controlsStyle.mapType; mapOptions.zoomControlOptions.style = opts.controlsStyle.zoom; // Create map and set initial options var $gmap = new $googlemaps.Map(this, mapOptions); if (opts.log) {console.log('map center is:'); } if (opts.log) {console.log(center); } // Create map and set initial options $this.data("$gmap", $gmap); $this.data('gmap', { 'opts': opts, 'gmap': $gmap, 'markers': [], 'markerKeys' : {}, 'infoWindow': null, 'clusters': [] }); // Check for map controls if (opts.controls.length !== 0) { // Add custom map controls for (i = 0; i < opts.controls.length; i += 1) { $gmap.controls[opts.controls[i].pos].push(opts.controls[i].div); } } if (opts.clustering.enabled) { $data = $this.data('gmap'); (function(markers) {$data.markers = markers;}(opts.markers)); methods._renderCluster.apply($this, []); $googlemaps.event.addListener($gmap, 'bounds_changed', function () { methods._renderCluster.apply($this, []); }); } else { if (opts.markers.length !== 0) { methods.addMarkers.apply($this, [opts.markers]); } } methods._onComplete.apply($this, []); }); }, _delayedMode: false, /** * Check every 100ms if all markers were loaded, then call onComplete */ _onComplete: function () { var $data = this.data('gmap'), that = this; if ($markersToLoad !== 0) { window.setTimeout(function () {methods._onComplete.apply(that, []); }, 100); return; } if(methods._delayedMode) { var center = methods._getMapCenter.apply(this, [$data.opts, true]); methods._setMapCenter.apply(this, [center]); if($data.opts.zoomFit) { var zoom = methods._autoZoom.apply(this, [$data.opts, true]); $data.gmap.setZoom(zoom); } } $data.opts.onComplete(); }, /** * set map center when map is loaded (check every 100ms) */ _setMapCenter: function (center) { var $data = this.data('gmap'); if ($data.opts.log) {console.log('delayed setMapCenter called'); } if ($data.gmap !== undefined && $markersToLoad == 0) { $data.gmap.setCenter(center); } else { var that = this; window.setTimeout(function () {methods._setMapCenter.apply(that, [center]); }, 100); } }, /** * calculate boundaries, optimised and independent from Google Maps */ _boundaries: null, _getBoundaries: function (opts) { // if(methods._boundaries) {return methods._boundaries; } var markers = opts.markers, i; var mostN = 1000, mostE = -1000, mostW = 1000, mostS = -1000; if(markers) { for (i = 0; i < markers.length; i += 1) { if(!markers[i].latitude || !markers[i].longitude) continue; if(mostN > markers[i].latitude) {mostN = markers[i].latitude; } if(mostE < markers[i].longitude) {mostE = markers[i].longitude; } if(mostW > markers[i].longitude) {mostW = markers[i].longitude; } if(mostS < markers[i].latitude) {mostS = markers[i].latitude; } console.log(markers[i].latitude, markers[i].longitude, mostN, mostE, mostW, mostS); } methods._boundaries = {N: mostN, E: mostE, W: mostW, S: mostS}; } if(mostN == -1000) methods._boundaries = {N: 0, E: 0, W: 0, S: 0}; return methods._boundaries; }, _getBoundariesFromMarkers: function () { var markers = this.data('gmap').markers, i; var mostN = 1000, mostE = -1000, mostW = 1000, mostS = -1000; if(markers) { for (i = 0; i < markers.length; i += 1) { if(mostN > markers[i].getPosition().lat()) {mostN = markers[i].getPosition().lat(); } if(mostE < markers[i].getPosition().lng()) {mostE = markers[i].getPosition().lng(); } if(mostW > markers[i].getPosition().lng()) {mostW = markers[i].getPosition().lng(); } if(mostS < markers[i].getPosition().lat()) {mostS = markers[i].getPosition().lat(); } } methods._boundaries = {N: mostN, E: mostE, W: mostW, S: mostS}; } if(mostN == -1000) methods._boundaries = {N: 0, E: 0, W: 0, S: 0}; return methods._boundaries; }, /** * Priorities order: * - latitude & longitude in options * - address in options * - latitude & longitude of first marker having it * - address of first marker having it * - failsafe (0,0) * * Note: with geocoding returned value is (0,0) and callback sets map center. It's not very nice nor efficient. * It is quite good idea to use only first option */ _getMapCenter: function (opts, fromMarkers) { // Create new object to geocode addresses var center, that = this, // 'that' scope fix in geocoding i, selectedToCenter, most; //hoisting if (opts.markers.length && (opts.latitude == "fit" || opts.longitude == "fit")) { if(fromMarkers) most = methods._getBoundariesFromMarkers.apply(this); else most = methods._getBoundaries(opts); center = new $googlemaps.LatLng((most.N + most.S)/2, (most.E + most.W)/2); console.log(fromMarkers, most, center); return center; } if (opts.latitude && opts.longitude) { // lat & lng available, return center = new $googlemaps.LatLng(opts.latitude, opts.longitude); return center; } else { center = new $googlemaps.LatLng(0, 0); } // Check for address to center on if (opts.address) { // Get coordinates for given address and center the map $geocoder.geocode( {address: opts.address}, function (result, status) { if (status === google.maps.GeocoderStatus.OK) { methods._setMapCenter.apply(that, [result[0].geometry.location]); } else { if (opts.log) {console.log("Geocode was not successful for the following reason: " + status); } } } ); return center; } // Check for a marker to center on (if no coordinates given) if (opts.markers.length > 0) { selectedToCenter = null; for (i = 0; i < opts.markers.length; i += 1) { if(opts.markers[i].setCenter) { selectedToCenter = opts.markers[i]; break; } } if (selectedToCenter === null) { for (i = 0; i < opts.markers.length; i += 1) { if (opts.markers[i].latitude && opts.markers[i].longitude) { selectedToCenter = opts.markers[i]; break; } if (opts.markers[i].address) { selectedToCenter = opts.markers[i]; } } } // failed to find any reasonable marker (it's quite impossible BTW) if (selectedToCenter === null) { return center; } if (selectedToCenter.latitude && selectedToCenter.longitude) { return new $googlemaps.LatLng(selectedToCenter.latitude, selectedToCenter.longitude); } // Check if the marker has an address if (selectedToCenter.address) { // Get the coordinates for given marker address and center $geocoder.geocode( {address: selectedToCenter.address}, function (result, status) { if (status === google.maps.GeocoderStatus.OK) { methods._setMapCenter.apply(that, [result[0].geometry.location]); } else { if (opts.log) {console.log("Geocode was not successful for the following reason: " + status); } } } ); } } return center; }, /** * clustering */ _renderCluster: function () { var $data = this.data('gmap'), markers = $data.markers, clusters = $data.clusters, opts = $data.opts, i, j, viewport; for (i = 0; i < clusters.length; i += 1) { clusters[i].getMarker().setMap(null); } clusters.length = 0; viewport = $data.gmap.getBounds(); if (!viewport) { var that = this; window.setTimeout(function () {methods._renderCluster.apply(that); }, 1000); return; } var ne = viewport.getNorthEast(), sw = viewport.getSouthWest(), width = ne.lat() - sw.lat(), // height = ne.lng() - sw.lng(), // unused clusterable = [], best, bestDist, maxSize = width * opts.clustering.clusterSize / 100, dist, newCluster; for (i = 0; i < markers.length; i += 1) { if (markers[i].latitude < ne.lat() && markers[i].latitude > sw.lat() && markers[i].longitude < ne.lng() && markers[i].longitude > sw.lng()) { clusterable[clusterable.length] = markers[i]; } } if (opts.log) {console.log("number of markers " + clusterable.length + "/" + markers.length); } if (opts.log) {console.log('cluster radius: ' + maxSize); } for (i = 0; i < clusterable.length; i += 1) { bestDist = 10000; best = -1; for (j = 0; j < clusters.length; j += 1) { dist = clusters[j].dist(clusterable[i]); if (dist < maxSize) { bestDist = dist; best = j; if (opts.clustering.fastClustering) {break; } } } if (best === -1) { newCluster = new Cluster(); newCluster.addMarker(clusterable[i]); clusters[clusters.length] = newCluster; } else { clusters[best].addMarker(clusterable[i]); } } if (opts.log) {console.log("Total clusters in viewport: " + clusters.length); } for (j = 0; j < clusters.length; j += 1) { clusters[j].getMarker().setMap($data.gmap); } }, _processMarker: function (marker, gicon, gshadow, location) { var $data = this.data('gmap'), $gmap = $data.gmap, opts = $data.opts, gmarker, markeropts; if (location === undefined) { location = new $googlemaps.LatLng(marker.latitude, marker.longitude); } if (!gicon) { // Set icon properties from global options var _gicon = { image: opts.icon.image, iconSize: new $googlemaps.Size(opts.icon.iconsize[0], opts.icon.iconsize[1]), iconAnchor: new $googlemaps.Point(opts.icon.iconanchor[0], opts.icon.iconanchor[1]), infoWindowAnchor: new $googlemaps.Size(opts.icon.infowindowanchor[0], opts.icon.infowindowanchor[1]) }; gicon = new $googlemaps.MarkerImage(_gicon.image, _gicon.iconSize, null, _gicon.iconAnchor); } if (!gshadow) { var _gshadow = { image: opts.icon.shadow, iconSize: new $googlemaps.Size(opts.icon.shadowsize[0], opts.icon.shadowsize[1]), anchor: (_gicon && _gicon.iconAnchor)?_gicon.iconAnchor:new $googlemaps.Point(opts.icon.iconanchor[0], opts.icon.iconanchor[1]) }; } markeropts = { position: location, icon: gicon, title: marker.title, map: null, draggable: ((marker.draggable === true) ? true : false) }; if (!opts.clustering.enabled) {markeropts.map = $gmap; } gmarker = new $googlemaps.Marker(markeropts); gmarker.setShadow(gshadow); $data.markers.push(gmarker); if(marker.key) {$data.markerKeys[marker.key] = gmarker; } // Set HTML and check if info window should be opened var infoWindow; if (marker.html) { var infoContent = typeof(marker.html) === "string" ? opts.html_prepend + marker.html + opts.html_append : marker.html; var infoOpts = { content: infoContent, pixelOffset: marker.infoWindowAnchor }; if (opts.log) {console.log('setup popup with data'); } if (opts.log) {console.log(infoOpts); } infoWindow = new $googlemaps.InfoWindow(infoOpts); $googlemaps.event.addListener(gmarker, 'click', function () { if (opts.log) {console.log('opening popup ' + marker.html); } if (opts.singleInfoWindow && $data.infoWindow) {$data.infoWindow.close(); } infoWindow.open($gmap, gmarker); $data.infoWindow = infoWindow; }); } if (marker.html && marker.popup) { if (opts.log) {console.log('opening popup ' + marker.html); } infoWindow.open($gmap, gmarker); $data.infoWindow = infoWindow; } if (marker.onDragEnd){ $googlemaps.event.addListener(gmarker, 'dragend', function(event) { if (opts.log) {console.log('drag end');} marker.onDragEnd(event); }); } }, _geocodeMarker: function (marker, gicon, gshadow) { var that = this; $geocoder.geocode({'address': marker.address}, function (results, status) { if (status === $googlemaps.GeocoderStatus.OK) { $markersToLoad -= 1; if (that.data('gmap').opts.log) {console.log("Geocode was successful with point: ", results[0].geometry.location); } methods._processMarker.apply(that, [marker, gicon, gshadow, results[0].geometry.location]); } else { if(status === $googlemaps.GeocoderStatus.OVER_QUERY_LIMIT) { if ((!that.data('gmap').opts.noAlerts) && (overQueryLimit === 0)) {alert('Error: too many geocoded addresses! Switching to 1 marker/s mode.'); } overQueryLimit+=1000; window.setTimeout(function() { methods._geocodeMarker.apply(that, [marker, gicon, gshadow]); }, overQueryLimit); } if (that.data('gmap').opts.log) {console.log("Geocode was not successful for the following reason: " + status); } } }); }, _autoZoom: function (options, fromMarkers){ var data = $(this).data('gmap'), opts = $.extend({}, data?data.opts:{}, options), i, boundaries, resX, resY, baseScale = 39135.758482; if (opts.log) {console.log("autozooming map");} if(fromMarkers) boundaries = methods._getBoundariesFromMarkers.apply(this); else boundaries = methods._getBoundaries(opts); resX = (boundaries.E - boundaries.W) * 111000 / this.width(); resY = (boundaries.S - boundaries.N) * 111000 / this.height(); for(i = 2; i < 20; i += 1) { if (resX > baseScale || resY > baseScale) { break; } baseScale = baseScale / 2; } return i - 2; }, /** * public methods section */ /** * add array of markers * @param markers */ addMarkers: function (markers){ var opts = this.data('gmap').opts; if (markers.length !== 0) { if (opts.log) {console.log("adding " + markers.length +" markers");} // Loop through marker array for (var i = 0; i < markers.length; i+= 1) { methods.addMarker.apply(this,[markers[i]]); } } }, /** * add single marker * @param marker */ addMarker: function (marker) { var opts = this.data('gmap').opts; if (opts.log) {console.log("putting marker at " + marker.latitude + ', ' + marker.longitude + " with address " + marker.address + " and html " + marker.html); } // Create new icon // Set icon properties from global options var _gicon = { image: opts.icon.image, iconSize: new $googlemaps.Size(opts.icon.iconsize[0], opts.icon.iconsize[1]), iconAnchor: new $googlemaps.Point(opts.icon.iconanchor[0], opts.icon.iconanchor[1]), infoWindowAnchor: new $googlemaps.Size(opts.icon.infowindowanchor[0], opts.icon.infowindowanchor[1]) }, _gshadow = { image: opts.icon.shadow, iconSize: new $googlemaps.Size(opts.icon.shadowsize[0], opts.icon.shadowsize[1]), anchor: new $googlemaps.Point(opts.icon.shadowanchor[0], opts.icon.shadowanchor[1]) }; // not very nice, but useful marker.infoWindowAnchor = _gicon.infoWindowAnchor; if (marker.icon) { // Overwrite global options if (marker.icon.image) { _gicon.image = marker.icon.image; } if (marker.icon.iconsize) { _gicon.iconSize = new $googlemaps.Size(marker.icon.iconsize[0], marker.icon.iconsize[1]); } if (marker.icon.iconanchor) { _gicon.iconAnchor = new $googlemaps.Point(marker.icon.iconanchor[0], marker.icon.iconanchor[1]); } if (marker.icon.infowindowanchor) { _gicon.infoWindowAnchor = new $googlemaps.Size(marker.icon.infowindowanchor[0], marker.icon.infowindowanchor[1]); } if (marker.icon.shadow) { _gshadow.image = marker.icon.shadow; } if (marker.icon.shadowsize) { _gshadow.iconSize = new $googlemaps.Size(marker.icon.shadowsize[0], marker.icon.shadowsize[1]); } if (marker.icon.shadowanchor) { _gshadow.anchor = new $googlemaps.Point(marker.icon.shadowanchor[0], marker.icon.shadowanchor[1]); } } var gicon = new $googlemaps.MarkerImage(_gicon.image, _gicon.iconSize, null, _gicon.iconAnchor); var gshadow = new $googlemaps.MarkerImage( _gshadow.image,_gshadow.iconSize, null, _gshadow.anchor); // Check if address is available if (marker.address) { // Check for reference to the marker's address if (marker.html === '_address') { marker.html = marker.address; } if (marker.title == '_address') { marker.title = marker.address; } if (opts.log) {console.log('geocoding marker: ' + marker.address); } // Get the point for given address $markersToLoad += 1; methods._delayedMode = true; methods._geocodeMarker.apply(this, [marker, gicon, gshadow]); } else { // Check for reference to the marker's latitude/longitude if (marker.html === '_latlng') { marker.html = marker.latitude + ', ' + marker.longitude; } if (marker.title == '_latlng') { marker.title = marker.latitude + ', ' + marker.longitude; } // Create marker var gpoint = new $googlemaps.LatLng(marker.latitude, marker.longitude); methods._processMarker.apply(this, [marker, gicon, gshadow, gpoint]); } }, /** * */ removeAllMarkers: function () { var markers = this.data('gmap').markers, i; for (i = 0; i < markers.length; i += 1) { markers[i].setMap(null); delete markers[i]; } markers.length = 0; }, /** * get marker by key, if set previously * @param key */ getMarker: function (key) { return this.data('gmap').markerKeys[key]; }, /** * should be called if DOM element was resized * @param nasty */ fixAfterResize: function (nasty) { var data = this.data('gmap'); $googlemaps.event.trigger(data.gmap, 'resize'); if(nasty) { data.gmap.panTo(new google.maps.LatLng(0,0)); } data.gmap.panTo(this.gMap('_getMapCenter', data.opts)); }, /** * change zoom, works with 'fit' option as well * @param zoom */ setZoom: function (zoom, opts, fromMarkers) { var $map = this.data('gmap').gmap; if (zoom === "fit"){ zoom = methods._autoZoom.apply(this, [opts, fromMarkers]); } $map.setZoom(parseInt(zoom)); }, changeSettings: function (options) { var data = this.data('gmap'), markers = [], i; for (i = 0; i < data.markers.length; i += 1) { markers[i] = { latitude: data.markers[i].getPosition().lat(), longitude: data.markers[i].getPosition().lng() } } options.markers = markers; if(options.zoom) methods.setZoom.apply(this,[options.zoom, options]); if(options.latitude || options.longitude) { data.gmap.panTo(methods._getMapCenter.apply(this,[options])); } // add controls and maptype }, mapclick: function(callback) { google.maps.event.addListener(this.data('gmap').gmap, 'click', function(event) { callback(event.latLng); }); }, geocode: function(address, callback, errorCallback) { $geocoder.geocode({'address': address}, function (results, status) { if (status === $googlemaps.GeocoderStatus.OK) { callback(results[0].geometry.location); } else if(errorCallback) { errorCallback(results, status); } }); }, getRoute: function (options) { var $data = this.data('gmap'), $gmap = $data.gmap, $directionsDisplay = new $googlemaps.DirectionsRenderer(), $directionsService = new $googlemaps.DirectionsService(), $travelModes = { 'BYCAR': $googlemaps.DirectionsTravelMode.DRIVING, 'BYBICYCLE': $googlemaps.DirectionsTravelMode.BICYCLING, 'BYFOOT': $googlemaps.DirectionsTravelMode.WALKING }, $travelUnits = { 'MILES': $googlemaps.DirectionsUnitSystem.IMPERIAL, 'KM': $googlemaps.DirectionsUnitSystem.METRIC }, displayObj = null, travelMode = null, travelUnit = null, unitSystem = null; // look if there is an individual or otherwise a default object for this call to display route text informations if(options.routeDisplay !== undefined){ displayObj = (options.routeDisplay instanceof jQuery) ? options.routeDisplay[0] : ((typeof options.routeDisplay == "string") ? $(options.routeDisplay)[0] : null); } else if($data.opts.routeFinder.routeDisplay !== null){ displayObj = ($data.opts.routeFinder.routeDisplay instanceof jQuery) ? $data.opts.routeFinder.routeDisplay[0] : ((typeof $data.opts.routeFinder.routeDisplay == "string") ? $($data.opts.routeFinder.routeDisplay)[0] : null); } // set route renderer to map $directionsDisplay.setMap($gmap); if(displayObj !== null){ $directionsDisplay.setPanel(displayObj); } // get travel mode and unit travelMode = ($travelModes[$data.opts.routeFinder.travelMode] !== undefined) ? $travelModes[$data.opts.routeFinder.travelMode] : $travelModes['BYCAR']; travelUnit = ($travelUnits[$data.opts.routeFinder.travelUnit] !== undefined) ? $travelUnits[$data.opts.routeFinder.travelUnit] : $travelUnits['KM']; // build request var request = { origin: options.from, destination: options.to, travelMode: travelMode, unitSystem: travelUnit }; // send request $directionsService.route(request, function(result, status) { // show the rout or otherwise show an error message in a defined container for route text information if (status == $googlemaps.DirectionsStatus.OK) { $directionsDisplay.setDirections(result); } else if(displayObj !== null){ $(displayObj).html($data.opts.routeFinder.routeErrors[status]); } }); return this; } }; // Main plugin function $.fn.gMap = function (method) { // Method calling logic if (methods[method]) { return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); } else if (typeof method === 'object' || !method) { return methods.init.apply(this, arguments); } else { $.error('Method ' + method + ' does not exist on jQuery.gmap'); } }; // Default settings $.fn.gMap.defaults = { log: false, address: '', latitude: null, longitude: null, zoom: 3, maxZoom: null, minZoom: null, markers: [], controls: {}, scrollwheel: true, maptype: google.maps.MapTypeId.ROADMAP, mapTypeControl: true, zoomControl: true, panControl: false, scaleControl: false, streetViewControl: true, controlsPositions: { mapType: null, zoom: null, pan: null, scale: null, streetView: null }, controlsStyle: { mapType: google.maps.MapTypeControlStyle.DEFAULT, zoom: google.maps.ZoomControlStyle.DEFAULT }, singleInfoWindow: true, html_prepend: '
', html_append: '
', icon: { image: "http://www.google.com/mapfiles/marker.png", iconsize: [20, 34], iconanchor: [9, 34], infowindowanchor: [9, 2], shadow: "http://www.google.com/mapfiles/shadow50.png", shadowsize: [37, 34], shadowanchor: [9, 34] }, onComplete: function () {}, routeFinder: { travelMode: 'BYCAR', travelUnit: 'KM', routeDisplay: null, routeErrors: { 'INVALID_REQUEST': 'The provided request is invalid.', 'NOT_FOUND': 'One or more of the given addresses could not be found.', 'OVER_QUERY_LIMIT': 'A temporary error occured. Please try again in a few minutes.', 'REQUEST_DENIED': 'An error occured. Please contact us.', 'UNKNOWN_ERROR': 'An unknown error occured. Please try again.', 'ZERO_RESULTS': 'No route could be found within the given addresses.' } }, clustering: { enabled: false, fastClustering: false, clusterCount: 10, clusterSize: 40 //radius as % of viewport width } }; }(jQuery));