├── GoogleMapsAsset.php
├── .php_cs
├── LICENSE.md
├── composer.json
├── README.md
├── GoogleMaps.php
└── assets
├── googlemap.js
└── markerclusterer_compiled.js
/GoogleMapsAsset.php:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 yii2mod
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "yii2mod/yii2-google-maps-markers",
3 | "description": "Google Maps Markers displays a set of user addresses as markers on the map.",
4 | "type": "yii2-extension",
5 | "keywords": [
6 | "yii2",
7 | "extension",
8 | "google maps markers",
9 | "google maps"
10 | ],
11 | "license": "MIT",
12 | "authors": [
13 | {
14 | "name": "Igor Chepurnoy",
15 | "email": "igorzfort@gmail.com"
16 | },
17 | {
18 | "name": "Dmitry Semenov",
19 | "email": "disemx@gmail.com"
20 | },
21 | {
22 | "name": "Alexander Harchenko",
23 | "email": "morontt@gmail.com",
24 | "homepage": "https://morontt.info"
25 | }
26 | ],
27 | "require": {
28 | "yiisoft/yii2": "*"
29 | },
30 | "require-dev": {
31 | "friendsofphp/php-cs-fixer": "~2.0"
32 | },
33 | "autoload": {
34 | "psr-4": {
35 | "yii2mod\\google\\maps\\markers\\": ""
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
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 | [](https://packagist.org/packages/yii2mod/yii2-google-maps-markers)
12 | [](https://packagist.org/packages/yii2mod/yii2-google-maps-markers)
13 | [](https://packagist.org/packages/yii2mod/yii2-google-maps-markers)
14 | [](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 | 
110 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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]