├── .php_cs ├── GoogleMaps.php ├── GoogleMapsAsset.php ├── LICENSE.md ├── README.md ├── assets ├── googlemap.js └── markerclusterer_compiled.js └── composer.json /.php_cs: -------------------------------------------------------------------------------- 1 | exclude('vendor') 5 | ->in([__DIR__]); 6 | 7 | $config = PhpCsFixer\Config::create() 8 | ->setUsingCache(false) 9 | ->setRules([ 10 | '@Symfony' => true, 11 | 'phpdoc_align' => false, 12 | 'phpdoc_summary' => false, 13 | 'phpdoc_inline_tag' => false, 14 | 'pre_increment' => false, 15 | 'heredoc_to_nowdoc' => false, 16 | 'cast_spaces' => false, 17 | 'include' => false, 18 | 'phpdoc_no_package' => false, 19 | 'concat_space' => ['spacing' => 'one'], 20 | 'ordered_imports' => true, 21 | 'array_syntax' => ['syntax' => 'short'], 22 | ]) 23 | ->setFinder($finder); 24 | 25 | return $config; 26 | -------------------------------------------------------------------------------- /GoogleMaps.php: -------------------------------------------------------------------------------- 1 | [ 21 | * [ 22 | * 'location' => [ 23 | * 'address' => 'Kharkov', 24 | * 'country' => 'Ukraine', 25 | * ], 26 | * 'htmlContent' => '

Kharkov

' 27 | * ], 28 | * [ 29 | * 'location' => [ 30 | * 'city' => 'New York', 31 | * 'country' => 'Usa', 32 | * ], 33 | * 'htmlContent' => '

New York

' 34 | * ], 35 | * ] 36 | * ]); 37 | * ``` 38 | */ 39 | class GoogleMaps extends Widget 40 | { 41 | /** 42 | * @var array user locations array 43 | */ 44 | public $userLocations = []; 45 | 46 | /** 47 | * @var string main wrapper height 48 | */ 49 | public $wrapperHeight = '500px'; 50 | 51 | /** 52 | * @var string google maps url 53 | */ 54 | public $googleMapsUrl = 'https://maps.googleapis.com/maps/api/js?'; 55 | 56 | /** 57 | * libraries - Example: geometry, places. Default - empty string 58 | * version - 3.exp (Default) 59 | * 60 | * @var array google maps url options(v, language, key, libraries) 61 | */ 62 | public $googleMapsUrlOptions = []; 63 | 64 | /** 65 | * Google Maps options (mapTypeId, tilt, zoom, etc...) 66 | * 67 | * @see https://developers.google.com/maps/documentation/javascript/reference 68 | * 69 | * @var array 70 | */ 71 | public $googleMapsOptions = []; 72 | 73 | /** 74 | * Example listener for infowindow object: 75 | * 76 | * ```php 77 | * [ 78 | * [ 79 | * 'object' => 'infowindow', 80 | * 'event' => 'domready', 81 | * 'handler' => (new \yii\web\JsExpression('function() { 82 | * // your custom js code 83 | * }')) 84 | * ] 85 | * ] 86 | * ``` 87 | * 88 | * @var array google map listeners 89 | */ 90 | public $googleMapsListeners = []; 91 | 92 | /** 93 | * @see https://developers.google.com/maps/documentation/javascript/reference#InfoWindowOptions 94 | * 95 | * @var array 96 | */ 97 | public $infoWindowOptions = []; 98 | 99 | /** 100 | * @var string google maps container id 101 | */ 102 | public $containerId = 'map_canvas'; 103 | 104 | /** 105 | * @var bool render empty map, if userLocations is empty. Defaults to 'true' 106 | */ 107 | public $renderEmptyMap = true; 108 | 109 | /** 110 | * Json array for yii.googleMapManager with users address and html contents 111 | * 112 | * @var array 113 | */ 114 | protected $geocodeData = []; 115 | 116 | /** 117 | * Init widget 118 | */ 119 | public function init() 120 | { 121 | parent::init(); 122 | 123 | if (is_array($this->userLocations) === false) { 124 | throw new InvalidConfigException('The "userLocations" property must be of the type array'); 125 | } 126 | 127 | $this->googleMapsOptions = $this->getGoogleMapsOptions(); 128 | $this->infoWindowOptions = $this->getInfoWindowOptions(); 129 | $this->googleMapsUrlOptions = $this->getGoogleMapsUrlOptions(); 130 | } 131 | 132 | /** 133 | * Executes the widget. 134 | */ 135 | public function run() 136 | { 137 | if (empty($this->userLocations) && $this->renderEmptyMap === false) { 138 | return; 139 | } 140 | 141 | $this->geocodeData = $this->getGeoCodeData(); 142 | 143 | echo Html::beginTag('div', ['id' => $this->getId(), 'style' => "height: {$this->wrapperHeight}"]); 144 | echo Html::tag('div', '', ['id' => $this->containerId]); 145 | echo Html::endTag('div'); 146 | 147 | $this->registerAssets(); 148 | 149 | parent::run(); 150 | } 151 | 152 | /** 153 | * Register assets 154 | */ 155 | protected function registerAssets() 156 | { 157 | $view = $this->getView(); 158 | GoogleMapsAsset::register($view); 159 | $view->registerJsFile($this->getGoogleMapsApiUrl(), ['position' => View::POS_HEAD]); 160 | $options = $this->getClientOptions(); 161 | $view->registerJs("yii.googleMapManager.initModule({$options})", $view::POS_END, 'google-api-js'); 162 | } 163 | 164 | /** 165 | * Get place urls and htmlContent 166 | * 167 | * @return string 168 | */ 169 | protected function getGeoCodeData() 170 | { 171 | $result = []; 172 | foreach ($this->userLocations as $data) { 173 | $result[] = [ 174 | 'country' => ArrayHelper::getValue($data['location'], 'country'), 175 | 'address' => implode(',', ArrayHelper::getValue($data, 'location')), 176 | 'htmlContent' => ArrayHelper::getValue($data, 'htmlContent'), 177 | ]; 178 | } 179 | 180 | return $result; 181 | } 182 | 183 | /** 184 | * Get google maps api url 185 | * 186 | * @return string 187 | */ 188 | protected function getGoogleMapsApiUrl() 189 | { 190 | return $this->googleMapsUrl . http_build_query($this->googleMapsUrlOptions); 191 | } 192 | 193 | /** 194 | * Get google maps url options 195 | * 196 | * @return array 197 | */ 198 | protected function getGoogleMapsUrlOptions() 199 | { 200 | if (isset(Yii::$app->params['googleMapsUrlOptions']) && empty($this->googleMapsUrlOptions)) { 201 | $this->googleMapsUrlOptions = Yii::$app->params['googleMapsUrlOptions']; 202 | } 203 | 204 | return ArrayHelper::merge([ 205 | 'v' => '3.exp', 206 | 'key' => null, 207 | 'libraries' => null, 208 | 'language' => 'en', 209 | ], $this->googleMapsUrlOptions); 210 | } 211 | 212 | /** 213 | * Get google maps options 214 | * 215 | * @return array 216 | */ 217 | protected function getGoogleMapsOptions() 218 | { 219 | if (isset(Yii::$app->params['googleMapsOptions']) && empty($this->googleMapsOptions)) { 220 | $this->googleMapsOptions = Yii::$app->params['googleMapsOptions']; 221 | } 222 | 223 | return ArrayHelper::merge([ 224 | 'mapTypeId' => 'roadmap', 225 | 'tilt' => 45, 226 | 'zoom' => 2, 227 | ], $this->googleMapsOptions); 228 | } 229 | 230 | /** 231 | * Get info window options 232 | * 233 | * @return array 234 | */ 235 | protected function getInfoWindowOptions() 236 | { 237 | if (isset(Yii::$app->params['infoWindowOptions']) && empty($this->infoWindowOptions)) { 238 | $this->infoWindowOptions = Yii::$app->params['infoWindowOptions']; 239 | } 240 | 241 | return ArrayHelper::merge([ 242 | 'content' => '', 243 | 'maxWidth' => 350, 244 | ], $this->infoWindowOptions); 245 | } 246 | 247 | /** 248 | * Get google map client options 249 | * 250 | * @return string 251 | */ 252 | protected function getClientOptions() 253 | { 254 | return Json::encode([ 255 | 'geocodeData' => $this->geocodeData, 256 | 'mapOptions' => $this->googleMapsOptions, 257 | 'listeners' => $this->googleMapsListeners, 258 | 'containerId' => $this->containerId, 259 | 'renderEmptyMap' => $this->renderEmptyMap, 260 | 'infoWindowOptions' => $this->infoWindowOptions, 261 | ]); 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /GoogleMapsAsset.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Google Maps Markers Widget for Yii2

6 |
7 |

8 | 9 | GoogleMaps Widget displays a set of user addresses as markers on the map. 10 | 11 | [![Latest Stable Version](https://poser.pugx.org/yii2mod/yii2-google-maps-markers/v/stable)](https://packagist.org/packages/yii2mod/yii2-google-maps-markers) 12 | [![Total Downloads](https://poser.pugx.org/yii2mod/yii2-google-maps-markers/downloads)](https://packagist.org/packages/yii2mod/yii2-google-maps-markers) 13 | [![License](https://poser.pugx.org/yii2mod/yii2-google-maps-markers/license)](https://packagist.org/packages/yii2mod/yii2-google-maps-markers) 14 | [![Build Status](https://travis-ci.org/yii2mod/yii2-google-maps-markers.svg?branch=master)](https://travis-ci.org/yii2mod/yii2-google-maps-markers) 15 | 16 | Installation 17 | ------------ 18 | 19 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/). 20 | 21 | Either run 22 | 23 | ``` 24 | php composer.phar require yii2mod/yii2-google-maps-markers "*" 25 | ``` 26 | 27 | or add 28 | 29 | ``` 30 | "yii2mod/yii2-google-maps-markers": "*" 31 | ``` 32 | 33 | to the require section of your composer.json. 34 | 35 | Usage 36 | ----- 37 | 38 | To use GoogleMaps, you need to configure its [[userLocations]] property. For example: 39 | 40 | ```php 41 | echo yii2mod\google\maps\markers\GoogleMaps::widget([ 42 | 'userLocations' => [ 43 | [ 44 | 'location' => [ 45 | 'address' => 'Kharkiv', 46 | 'country' => 'Ukraine', 47 | ], 48 | 'htmlContent' => '

Kharkiv

', 49 | ], 50 | [ 51 | 'location' => [ 52 | 'city' => 'New York', 53 | 'country' => 'United States', 54 | ], 55 | 'htmlContent' => '

New York

', 56 | ], 57 | ], 58 | ]); 59 | ``` 60 | 61 | Configuration 62 | ------------- 63 | 64 | To configure the Google Maps key or other options like language, version, library, or map options: 65 | 66 | ```php 67 | echo yii2mod\google\maps\markers\GoogleMaps::widget([ 68 | 'userLocations' => [...], 69 | 'googleMapsUrlOptions' => [ 70 | 'key' => 'this_is_my_key', 71 | 'language' => 'id', 72 | 'version' => '3.1.18', 73 | ], 74 | 'googleMapsOptions' => [ 75 | 'mapTypeId' => 'roadmap', 76 | 'tilt' => 45, 77 | 'zoom' => 5, 78 | ], 79 | ]); 80 | ``` 81 | 82 | OR via yii params configuration. For example: 83 | 84 | ```php 85 | 'params' => [ 86 | 'googleMapsUrlOptions' => [ 87 | 'key' => 'this_is_my_key', 88 | 'language' => 'id', 89 | 'version' => '3.1.18', 90 | ], 91 | 'googleMapsOptions' => [ 92 | 'mapTypeId' => 'roadmap', 93 | 'tilt' => 45, 94 | 'zoom' => 10, 95 | ], 96 | ], 97 | ``` 98 | 99 | To get key, please visit [page](https://developers.google.com/maps/documentation/javascript/get-api-key) 100 | 101 | Google Maps Options 102 | ------------------- 103 | 104 | You can find them on the [options page](https://developers.google.com/maps/documentation/javascript/reference) 105 | 106 | #### Example 107 | ------------ 108 | 109 | ![Alt text](http://res.cloudinary.com/zfort/image/upload/v1441115988/Map_preview_hcwb1x.png "Example") 110 | -------------------------------------------------------------------------------- /assets/googlemap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Google Map manager - renders map and put markers 3 | * Address priority - House Number, Street Direction, Street Name, Street Suffix, City, State, Zip, Country 4 | */ 5 | yii.googleMapManager = (function ($) { 6 | var pub = { 7 | nextAddress: 0, 8 | zeroResult: 0, 9 | delay: 300, 10 | bounds: [], 11 | geocoder: [], 12 | markerClusterer: false, 13 | markers: [], 14 | infoWindow: [], 15 | infoWindowOptions: [], 16 | containerId: 'map_canvas', 17 | geocodeData: [], 18 | mapOptions: { 19 | center: new google.maps.LatLng(53.666464, -2.686693) 20 | }, 21 | listeners: [], 22 | renderEmptyMap: true, 23 | map: null, 24 | init: function () { 25 | }, 26 | initModule: function (options) { 27 | initOptions(options).done(function () { 28 | google.maps.event.addDomListener(window, 'load', initializeMap()); 29 | }); 30 | }, 31 | /** 32 | * Get address and place it on map 33 | */ 34 | getAddress: function (location, htmlContent, loadMap) { 35 | var search = location.address; 36 | pub.geocoder.geocode({'address': search}, function (results, status) { 37 | if (status == google.maps.GeocoderStatus.OK) { 38 | var place = results[0]; 39 | pub.drawMarker(place, htmlContent); 40 | pub.delay = 300; 41 | } 42 | else if (status == google.maps.GeocoderStatus.OVER_QUERY_LIMIT) { 43 | pub.nextAddress--; 44 | pub.delay = 2000; 45 | } 46 | else if (status == google.maps.GeocoderStatus.ZERO_RESULTS) { 47 | //If first query return zero results, then set address the value of the country 48 | if (location.address != location.country) { 49 | pub.nextAddress--; 50 | pub.geocodeData[pub.nextAddress].address = pub.geocodeData[pub.nextAddress].country; 51 | } else { 52 | pub.drawMarker(pub.mapOptions.center, htmlContent); 53 | } 54 | } 55 | loadMap(); 56 | }); 57 | }, 58 | updatePosition: function (position) { 59 | var coordinates = [position]; 60 | if (!pub.isPositionUnique(position)) { 61 | var latitude = position.lat(); 62 | var lngModify = (Math.abs(Math.cos(latitude)) / 111111) * -5; 63 | var iteration = 0; 64 | while (true) { 65 | iteration++; 66 | var newLng = position.lng() + (lngModify * iteration); 67 | position = new google.maps.LatLng(latitude + 0.00001, newLng); 68 | if (pub.isPositionUnique(position)) { 69 | break; 70 | } 71 | lngModify *= -1; 72 | } 73 | } 74 | 75 | coordinates.push(position); 76 | 77 | var path = new google.maps.Polyline({ 78 | path: coordinates, 79 | geodesic: true, 80 | strokeColor: '#00AAFF', 81 | strokeOpacity: 1.0, 82 | strokeWeight: 0.4 83 | }); 84 | path.setMap(pub.map); 85 | 86 | return position; 87 | }, 88 | isPositionUnique: function (position) { 89 | if (pub.markers.length != 0) { 90 | for (var i = 0; i < pub.markers.length; i++) { 91 | var existingMarker = pub.markers[i]; 92 | var pos = existingMarker.getPosition(); 93 | //if a marker already exists in the same position as this marker 94 | if (position.equals(pos)) { 95 | return false; 96 | } 97 | } 98 | } 99 | 100 | return true; 101 | }, 102 | drawMarker: function (place, htmlContent) { 103 | var position = pub.updatePosition(place.geometry.location); 104 | pub.bounds.extend(position); 105 | var marker = new google.maps.Marker({ 106 | map: pub.map, 107 | position: position 108 | }); 109 | bindInfoWindow(marker, pub.map, pub.infoWindow, htmlContent); 110 | pub.markerClusterer.addMarker(marker); 111 | pub.markers.push(marker); 112 | if (pub.nextAddress == pub.geocodeData.length) { 113 | if (pub.userOptions.mapOptions.center) { 114 | pub.map.setCenter(pub.mapOptions.center); 115 | } else { 116 | google.maps.event.addListenerOnce(pub.map, 'bounds_changed', function () { 117 | if (pub.map.getZoom() > 17) { 118 | pub.map.setZoom(17); 119 | } 120 | }); 121 | pub.map.fitBounds(pub.bounds); 122 | } 123 | } 124 | } 125 | }; 126 | 127 | /** 128 | * Setup googleMapManager properties 129 | */ 130 | function initOptions(options) { 131 | var deferred = $.Deferred(); 132 | pub.bounds = new google.maps.LatLngBounds(); 133 | pub.geocoder = new google.maps.Geocoder(); 134 | pub.infoWindow = new google.maps.InfoWindow(pub.infoWindowOptions); 135 | pub.map = null; 136 | pub.markerClusterer = null; 137 | pub.geocodeData = []; 138 | pub.nextAddress = 0; 139 | pub.zeroResult = 0; 140 | pub.markers = []; 141 | pub.userOptions = options; 142 | $.extend(true, pub, options); 143 | deferred.resolve(); 144 | 145 | return deferred; 146 | } 147 | 148 | /** 149 | * Register listeners 150 | */ 151 | function registerListeners() { 152 | for (listener in pub.listeners) { 153 | if (pub.listeners.hasOwnProperty(listener)) { 154 | var object = pub.listeners[listener].object; 155 | var event = pub.listeners[listener].event; 156 | var handler = pub.listeners[listener].handler; 157 | google.maps.event.addListener(pub[object], event, handler); 158 | } 159 | } 160 | } 161 | 162 | /** 163 | * Binds a map marker and infoWindow together on click 164 | * @param marker 165 | * @param map 166 | * @param infoWindow 167 | * @param html 168 | */ 169 | function bindInfoWindow(marker, map, infoWindow, html) { 170 | google.maps.event.addListener(marker, 'click', function () { 171 | infoWindow.setContent(html); 172 | infoWindow.open(map, marker); 173 | }); 174 | } 175 | 176 | function initializeMap() { 177 | var container = document.getElementById(pub.containerId); 178 | container.style.width = '100%'; 179 | container.style.height = '100%'; 180 | pub.map = new google.maps.Map(container, pub.mapOptions); 181 | setTimeout(function () { 182 | pub.markerClusterer = new MarkerClusterer(pub.map, [], {gridSize: 50, maxZoom: 17}); 183 | registerListeners(); 184 | loadMap(); 185 | }, 1000); 186 | } 187 | 188 | /** 189 | * Dynamic call fetchPlaces function with delay 190 | */ 191 | function loadMap() { 192 | setTimeout(function () { 193 | if (pub.nextAddress < pub.geocodeData.length) { 194 | var location = { 195 | country: pub.geocodeData[pub.nextAddress].country, 196 | address: pub.geocodeData[pub.nextAddress].address 197 | }; 198 | var htmlContent = pub.geocodeData[pub.nextAddress].htmlContent; 199 | pub.getAddress(location, htmlContent, loadMap); 200 | pub.nextAddress++; 201 | } 202 | }, pub.delay); 203 | } 204 | 205 | return pub; 206 | })(jQuery); 207 | -------------------------------------------------------------------------------- /assets/markerclusterer_compiled.js: -------------------------------------------------------------------------------- 1 | function MarkerClusterer(t,e,r){this.extend(MarkerClusterer,google.maps.OverlayView),this.map_=t,this.markers_=[],this.clusters_=[],this.sizes=[53,56,66,78,90],this.styles_=[],this.ready_=!1;var s=r||{};this.gridSize_=s.gridSize||60,this.minClusterSize_=s.minimumClusterSize||2,this.maxZoom_=s.maxZoom||null,this.styles_=s.styles||[],this.imagePath_=s.imagePath||this.MARKER_CLUSTER_IMAGE_PATH_,this.imageExtension_=s.imageExtension||this.MARKER_CLUSTER_IMAGE_EXTENSION_,this.zoomOnClick_=!0,void 0!=s.zoomOnClick&&(this.zoomOnClick_=s.zoomOnClick),this.averageCenter_=!1,void 0!=s.averageCenter&&(this.averageCenter_=s.averageCenter),this.setupStyles_(),this.setMap(t),this.prevZoom_=this.map_.getZoom();var o=this;google.maps.event.addListener(this.map_,"zoom_changed",function(){var t=o.map_.getZoom();o.prevZoom_!=t&&(o.prevZoom_=t,o.resetViewport())}),google.maps.event.addListener(this.map_,"idle",function(){o.redraw()}),e&&e.length&&this.addMarkers(e,!1)}function Cluster(t){this.markerClusterer_=t,this.map_=t.getMap(),this.gridSize_=t.getGridSize(),this.minClusterSize_=t.getMinClusterSize(),this.averageCenter_=t.isAverageCenter(),this.center_=null,this.markers_=[],this.bounds_=null,this.clusterIcon_=new ClusterIcon(this,t.getStyles(),t.getGridSize())}function ClusterIcon(t,e,r){t.getMarkerClusterer().extend(ClusterIcon,google.maps.OverlayView),this.styles_=e,this.padding_=r||0,this.cluster_=t,this.center_=null,this.map_=t.getMap(),this.div_=null,this.sums_=null,this.visible_=!1,this.setMap(this.map_)}MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_PATH_="../images/m",MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_EXTENSION_="png",MarkerClusterer.prototype.extend=function(t,e){return function(t){for(var e in t.prototype)this.prototype[e]=t.prototype[e];return this}.apply(t,[e])},MarkerClusterer.prototype.onAdd=function(){this.setReady_(!0)},MarkerClusterer.prototype.draw=function(){},MarkerClusterer.prototype.setupStyles_=function(){if(!this.styles_.length)for(var t,e=0;t=this.sizes[e];e++)this.styles_.push({url:this.imagePath_+(e+1)+"."+this.imageExtension_,height:t,width:t})},MarkerClusterer.prototype.fitMapToMarkers=function(){for(var t,e=this.getMarkers(),r=new google.maps.LatLngBounds,s=0;t=e[s];s++)r.extend(t.getPosition());this.map_.fitBounds(r)},MarkerClusterer.prototype.setStyles=function(t){this.styles_=t},MarkerClusterer.prototype.getStyles=function(){return this.styles_},MarkerClusterer.prototype.isZoomOnClick=function(){return this.zoomOnClick_},MarkerClusterer.prototype.isAverageCenter=function(){return this.averageCenter_},MarkerClusterer.prototype.getMarkers=function(){return this.markers_},MarkerClusterer.prototype.getTotalMarkers=function(){return this.markers_.length},MarkerClusterer.prototype.setMaxZoom=function(t){this.maxZoom_=t},MarkerClusterer.prototype.getMaxZoom=function(){return this.maxZoom_},MarkerClusterer.prototype.calculator_=function(t,e){for(var r=0,s=t.length,o=s;0!==o;)o=parseInt(o/10,10),r++;return r=Math.min(r,e),{text:s,index:r}},MarkerClusterer.prototype.setCalculator=function(t){this.calculator_=t},MarkerClusterer.prototype.getCalculator=function(){return this.calculator_},MarkerClusterer.prototype.addMarkers=function(t,e){for(var r,s=0;r=t[s];s++)this.pushMarkerTo_(r);e||this.redraw()},MarkerClusterer.prototype.pushMarkerTo_=function(t){if(t.isAdded=!1,t.draggable){var e=this;google.maps.event.addListener(t,"dragend",function(){t.isAdded=!1,e.repaint()})}this.markers_.push(t)},MarkerClusterer.prototype.addMarker=function(t,e){this.pushMarkerTo_(t),e||this.redraw()},MarkerClusterer.prototype.removeMarker_=function(t){var e=-1;if(this.markers_.indexOf)e=this.markers_.indexOf(t);else for(var r,s=0;r=this.markers_[s];s++)if(r==t){e=s;break}return e!=-1&&(t.setMap(null),this.markers_.splice(e,1),!0)},MarkerClusterer.prototype.removeMarker=function(t,e){var r=this.removeMarker_(t);return!(e||!r)&&(this.resetViewport(),this.redraw(),!0)},MarkerClusterer.prototype.removeMarkers=function(t,e){for(var r,s=!1,o=0;r=t[o];o++){var i=this.removeMarker_(r);s=s||i}if(!e&&s)return this.resetViewport(),this.redraw(),!0},MarkerClusterer.prototype.setReady_=function(t){this.ready_||(this.ready_=t,this.createClusters_())},MarkerClusterer.prototype.getTotalClusters=function(){return this.clusters_.length},MarkerClusterer.prototype.getMap=function(){return this.map_},MarkerClusterer.prototype.setMap=function(t){this.map_=t},MarkerClusterer.prototype.getGridSize=function(){return this.gridSize_},MarkerClusterer.prototype.setGridSize=function(t){this.gridSize_=t},MarkerClusterer.prototype.getMinClusterSize=function(){return this.minClusterSize_},MarkerClusterer.prototype.setMinClusterSize=function(t){this.minClusterSize_=t},MarkerClusterer.prototype.getExtendedBounds=function(t){var e=this.getProjection(),r=new google.maps.LatLng(t.getNorthEast().lat(),t.getNorthEast().lng()),s=new google.maps.LatLng(t.getSouthWest().lat(),t.getSouthWest().lng()),o=e.fromLatLngToDivPixel(r);o.x+=this.gridSize_,o.y-=this.gridSize_;var i=e.fromLatLngToDivPixel(s);i.x-=this.gridSize_,i.y+=this.gridSize_;var n=e.fromDivPixelToLatLng(o),a=e.fromDivPixelToLatLng(i);return t.extend(n),t.extend(a),t},MarkerClusterer.prototype.isMarkerInBounds_=function(t,e){return e.contains(t.getPosition())},MarkerClusterer.prototype.clearMarkers=function(){this.resetViewport(!0),this.markers_=[]},MarkerClusterer.prototype.resetViewport=function(t){for(var e,r=0;e=this.clusters_[r];r++)e.remove();for(var s,r=0;s=this.markers_[r];r++)s.isAdded=!1,t&&s.setMap(null);this.clusters_=[]},MarkerClusterer.prototype.repaint=function(){var t=this.clusters_.slice();this.clusters_.length=0,this.resetViewport(),this.redraw(),window.setTimeout(function(){for(var e,r=0;e=t[r];r++)e.remove()},0)},MarkerClusterer.prototype.redraw=function(){this.createClusters_()},MarkerClusterer.prototype.distanceBetweenPoints_=function(t,e){if(!t||!e)return 0;var r=6371,s=(e.lat()-t.lat())*Math.PI/180,o=(e.lng()-t.lng())*Math.PI/180,i=Math.sin(s/2)*Math.sin(s/2)+Math.cos(t.lat()*Math.PI/180)*Math.cos(e.lat()*Math.PI/180)*Math.sin(o/2)*Math.sin(o/2),n=2*Math.atan2(Math.sqrt(i),Math.sqrt(1-i)),a=r*n;return a},MarkerClusterer.prototype.addToClosestCluster_=function(t){for(var e,r=4e4,s=null,o=(t.getPosition(),0);e=this.clusters_[o];o++){var i=e.getCenter();if(i){var n=this.distanceBetweenPoints_(i,t.getPosition());n=this.minClusterSize_&&t.setMap(null),this.updateIcon(),!0},Cluster.prototype.getMarkerClusterer=function(){return this.markerClusterer_},Cluster.prototype.getBounds=function(){for(var t,e=new google.maps.LatLngBounds(this.center_,this.center_),r=this.getMarkers(),s=0;t=r[s];s++)e.extend(t.getPosition());return e},Cluster.prototype.remove=function(){this.clusterIcon_.remove(),this.markers_.length=0,delete this.markers_},Cluster.prototype.getSize=function(){return this.markers_.length},Cluster.prototype.getMarkers=function(){return this.markers_},Cluster.prototype.getCenter=function(){return this.center_},Cluster.prototype.calculateBounds_=function(){var t=new google.maps.LatLngBounds(this.center_,this.center_);this.bounds_=this.markerClusterer_.getExtendedBounds(t)},Cluster.prototype.isMarkerInClusterBounds=function(t){return this.bounds_.contains(t.getPosition())},Cluster.prototype.getMap=function(){return this.map_},Cluster.prototype.updateIcon=function(){var t=this.map_.getZoom(),e=this.markerClusterer_.getMaxZoom();if(e&&t>e)for(var r,s=0;r=this.markers_[s];s++)r.setMap(this.map_);else{if(this.markers_.length0&&this.anchor_[0]0&&this.anchor_[1]