├── .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 | [](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 |
--------------------------------------------------------------------------------
/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]