466 |
jpm |
1 |
/**
|
|
|
2 |
* @name MarkerClusterer
|
|
|
3 |
* @version 1.0
|
|
|
4 |
* @author Xiaoxi Wu
|
|
|
5 |
* @copyright (c) 2009 Xiaoxi Wu
|
|
|
6 |
* @fileoverview
|
|
|
7 |
* This javascript library creates and manages per-zoom-level
|
|
|
8 |
* clusters for large amounts of markers (hundreds or thousands).
|
|
|
9 |
* This library was inspired by the <a href="http://www.maptimize.com">
|
|
|
10 |
* Maptimize</a> hosted clustering solution.
|
|
|
11 |
* <br /><br/>
|
|
|
12 |
* <b>How it works</b>:<br/>
|
|
|
13 |
* The <code>MarkerClusterer</code> will group markers into clusters according to
|
|
|
14 |
* their distance from a cluster's center. When a marker is added,
|
|
|
15 |
* the marker cluster will find a position in all the clusters, and
|
|
|
16 |
* if it fails to find one, it will create a new cluster with the marker.
|
|
|
17 |
* The number of markers in a cluster will be displayed
|
|
|
18 |
* on the cluster marker. When the map viewport changes,
|
|
|
19 |
* <code>MarkerClusterer</code> will destroy the clusters in the viewport
|
|
|
20 |
* and regroup them into new clusters.
|
|
|
21 |
*
|
|
|
22 |
*/
|
|
|
23 |
|
|
|
24 |
/*
|
|
|
25 |
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
26 |
* you may not use this file except in compliance with the License.
|
|
|
27 |
* You may obtain a copy of the License at
|
|
|
28 |
*
|
|
|
29 |
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
30 |
*
|
|
|
31 |
* Unless required by applicable law or agreed to in writing, software
|
|
|
32 |
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
33 |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
34 |
* See the License for the specific language governing permissions and
|
|
|
35 |
* limitations under the License.
|
|
|
36 |
*/
|
|
|
37 |
|
|
|
38 |
|
|
|
39 |
/**
|
|
|
40 |
* @name MarkerClustererOptions
|
|
|
41 |
* @class This class represents optional arguments to the {@link MarkerClusterer}
|
|
|
42 |
* constructor.
|
|
|
43 |
* @property {Number} [maxZoom] The max zoom level monitored by a
|
|
|
44 |
* marker cluster. If not given, the marker cluster assumes the maximum map
|
|
|
45 |
* zoom level. When maxZoom is reached or exceeded all markers will be shown
|
|
|
46 |
* without cluster.
|
|
|
47 |
* @property {Number} [gridSize=60] The grid size of a cluster in pixel. Each
|
|
|
48 |
* cluster will be a square. If you want the algorithm to run faster, you can set
|
|
|
49 |
* this value larger.
|
|
|
50 |
* @property {Array of MarkerStyleOptions} [styles]
|
|
|
51 |
* Custom styles for the cluster markers.
|
|
|
52 |
* The array should be ordered according to increasing cluster size,
|
|
|
53 |
* with the style for the smallest clusters first, and the style for the
|
|
|
54 |
* largest clusters last.
|
|
|
55 |
*/
|
|
|
56 |
|
|
|
57 |
/**
|
|
|
58 |
* @name MarkerStyleOptions
|
|
|
59 |
* @class An array of these is passed into the {@link MarkerClustererOptions}
|
|
|
60 |
* styles option.
|
|
|
61 |
* @property {String} [url] Image url.
|
|
|
62 |
* @property {Number} [height] Image height.
|
|
|
63 |
* @property {Number} [height] Image width.
|
|
|
64 |
* @property {Array of Number} [opt_anchor] Anchor for label text, like [24, 12].
|
|
|
65 |
* If not set, the text will align center and middle.
|
|
|
66 |
* @property {String} [opt_textColor="black"] Text color.
|
|
|
67 |
*/
|
|
|
68 |
|
|
|
69 |
/**
|
|
|
70 |
* Creates a new MarkerClusterer to cluster markers on the map.
|
|
|
71 |
*
|
|
|
72 |
* @constructor
|
|
|
73 |
* @param {GMap2} map The map that the markers should be added to.
|
|
|
74 |
* @param {Array of GMarker} opt_markers Initial set of markers to be clustered.
|
|
|
75 |
* @param {MarkerClustererOptions} opt_opts A container for optional arguments.
|
|
|
76 |
*/
|
|
|
77 |
function MarkerClusterer(map, opt_markers, opt_opts) {
|
|
|
78 |
// private members
|
|
|
79 |
var clusters_ = [];
|
|
|
80 |
var map_ = map;
|
|
|
81 |
var maxZoom_ = null;
|
|
|
82 |
var me_ = this;
|
|
|
83 |
var gridSize_ = 60;
|
|
|
84 |
var sizes = [53, 56, 66, 78, 90];
|
|
|
85 |
var styles_ = [];
|
|
|
86 |
var leftMarkers_ = [];
|
|
|
87 |
var mcfn_ = null;
|
|
|
88 |
|
|
|
89 |
var i = 0;
|
|
|
90 |
for (i = 1; i <= 5; ++i) {
|
|
|
91 |
styles_.push({
|
|
|
92 |
'url': "http://gmaps-utility-library.googlecode.com/svn/trunk/markerclusterer/images/m" + i + ".png",
|
|
|
93 |
'height': sizes[i - 1],
|
|
|
94 |
'width': sizes[i - 1]
|
|
|
95 |
});
|
|
|
96 |
}
|
|
|
97 |
|
|
|
98 |
if (typeof opt_opts === "object" && opt_opts !== null) {
|
|
|
99 |
if (typeof opt_opts.gridSize === "number" && opt_opts.gridSize > 0) {
|
|
|
100 |
gridSize_ = opt_opts.gridSize;
|
|
|
101 |
}
|
|
|
102 |
if (typeof opt_opts.maxZoom === "number") {
|
|
|
103 |
maxZoom_ = opt_opts.maxZoom;
|
|
|
104 |
}
|
|
|
105 |
if (typeof opt_opts.styles === "object" && opt_opts.styles !== null && opt_opts.styles.length !== 0) {
|
|
|
106 |
styles_ = opt_opts.styles;
|
|
|
107 |
}
|
|
|
108 |
}
|
|
|
109 |
|
|
|
110 |
/**
|
|
|
111 |
* When we add a marker, the marker may not in the viewport of map, then we don't deal with it, instead
|
|
|
112 |
* we add the marker into a array called leftMarkers_. When we reset MarkerClusterer we should add the
|
|
|
113 |
* leftMarkers_ into MarkerClusterer.
|
|
|
114 |
*/
|
|
|
115 |
function addLeftMarkers_() {
|
|
|
116 |
if (leftMarkers_.length === 0) {
|
|
|
117 |
return;
|
|
|
118 |
}
|
|
|
119 |
var leftMarkers = [];
|
|
|
120 |
for (i = 0; i < leftMarkers_.length; ++i) {
|
|
|
121 |
me_.addMarker(leftMarkers_[i], true, null, null, true);
|
|
|
122 |
}
|
|
|
123 |
leftMarkers_ = leftMarkers;
|
|
|
124 |
}
|
|
|
125 |
|
|
|
126 |
/**
|
|
|
127 |
* Get cluster marker images of this marker cluster. Mostly used by {@link Cluster}
|
|
|
128 |
* @private
|
|
|
129 |
* @return {Array of String}
|
|
|
130 |
*/
|
|
|
131 |
this.getStyles_ = function () {
|
|
|
132 |
return styles_;
|
|
|
133 |
};
|
|
|
134 |
|
|
|
135 |
/**
|
|
|
136 |
* Remove all markers from MarkerClusterer.
|
|
|
137 |
*/
|
|
|
138 |
this.clearMarkers = function () {
|
|
|
139 |
for (var i = 0; i < clusters_.length; ++i) {
|
|
|
140 |
if (typeof clusters_[i] !== "undefined" && clusters_[i] !== null) {
|
|
|
141 |
clusters_[i].clearMarkers();
|
|
|
142 |
}
|
|
|
143 |
}
|
|
|
144 |
clusters_ = [];
|
|
|
145 |
leftMarkers_ = [];
|
|
|
146 |
GEvent.removeListener(mcfn_);
|
|
|
147 |
};
|
|
|
148 |
|
|
|
149 |
/**
|
|
|
150 |
* Check a marker, whether it is in current map viewport.
|
|
|
151 |
* @private
|
|
|
152 |
* @return {Boolean} if it is in current map viewport
|
|
|
153 |
*/
|
|
|
154 |
function isMarkerInViewport_(marker) {
|
|
|
155 |
return map_.getBounds().containsLatLng(marker.getLatLng());
|
|
|
156 |
}
|
|
|
157 |
|
|
|
158 |
/**
|
|
|
159 |
* When reset MarkerClusterer, there will be some markers get out of its cluster.
|
|
|
160 |
* These markers should be add to new clusters.
|
|
|
161 |
* @param {Array of GMarker} markers Markers to add.
|
|
|
162 |
*/
|
|
|
163 |
function reAddMarkers_(markers) {
|
|
|
164 |
var len = markers.length;
|
|
|
165 |
var clusters = [];
|
|
|
166 |
for (var i = len - 1; i >= 0; --i) {
|
|
|
167 |
me_.addMarker(markers[i].marker, true, markers[i].isAdded, clusters, true);
|
|
|
168 |
}
|
|
|
169 |
addLeftMarkers_();
|
|
|
170 |
}
|
|
|
171 |
|
|
|
172 |
/**
|
|
|
173 |
* Add a marker.
|
|
|
174 |
* @private
|
|
|
175 |
* @param {GMarker} marker Marker you want to add
|
|
|
176 |
* @param {Boolean} opt_isNodraw Whether redraw the cluster contained the marker
|
|
|
177 |
* @param {Boolean} opt_isAdded Whether the marker is added to map. Never use it.
|
|
|
178 |
* @param {Array of Cluster} opt_clusters Provide a list of clusters, the marker
|
|
|
179 |
* cluster will only check these cluster where the marker should join.
|
|
|
180 |
*/
|
|
|
181 |
this.addMarker = function (marker, opt_isNodraw, opt_isAdded, opt_clusters, opt_isNoCheck) {
|
|
|
182 |
if (opt_isNoCheck !== true) {
|
|
|
183 |
if (!isMarkerInViewport_(marker)) {
|
|
|
184 |
leftMarkers_.push(marker);
|
|
|
185 |
return;
|
|
|
186 |
}
|
|
|
187 |
}
|
|
|
188 |
|
|
|
189 |
var isAdded = opt_isAdded;
|
|
|
190 |
var clusters = opt_clusters;
|
|
|
191 |
var pos = map_.fromLatLngToDivPixel(marker.getLatLng());
|
|
|
192 |
|
|
|
193 |
if (typeof isAdded !== "boolean") {
|
|
|
194 |
isAdded = false;
|
|
|
195 |
}
|
|
|
196 |
if (typeof clusters !== "object" || clusters === null) {
|
|
|
197 |
clusters = clusters_;
|
|
|
198 |
}
|
|
|
199 |
|
|
|
200 |
var length = clusters.length;
|
|
|
201 |
var cluster = null;
|
|
|
202 |
for (var i = length - 1; i >= 0; i--) {
|
|
|
203 |
cluster = clusters[i];
|
|
|
204 |
var center = cluster.getCenter();
|
|
|
205 |
if (center === null) {
|
|
|
206 |
continue;
|
|
|
207 |
}
|
|
|
208 |
center = map_.fromLatLngToDivPixel(center);
|
|
|
209 |
|
|
|
210 |
// Found a cluster which contains the marker.
|
|
|
211 |
if (pos.x >= center.x - gridSize_ && pos.x <= center.x + gridSize_ &&
|
|
|
212 |
pos.y >= center.y - gridSize_ && pos.y <= center.y + gridSize_) {
|
|
|
213 |
cluster.addMarker({
|
|
|
214 |
'isAdded': isAdded,
|
|
|
215 |
'marker': marker
|
|
|
216 |
});
|
|
|
217 |
if (!opt_isNodraw) {
|
|
|
218 |
cluster.redraw_();
|
|
|
219 |
}
|
|
|
220 |
return;
|
|
|
221 |
}
|
|
|
222 |
}
|
|
|
223 |
|
|
|
224 |
// No cluster contain the marker, create a new cluster.
|
|
|
225 |
cluster = new Cluster(this, map);
|
|
|
226 |
cluster.addMarker({
|
|
|
227 |
'isAdded': isAdded,
|
|
|
228 |
'marker': marker
|
|
|
229 |
});
|
|
|
230 |
if (!opt_isNodraw) {
|
|
|
231 |
cluster.redraw_();
|
|
|
232 |
}
|
|
|
233 |
|
|
|
234 |
// Add this cluster both in clusters provided and clusters_
|
|
|
235 |
clusters.push(cluster);
|
|
|
236 |
if (clusters !== clusters_) {
|
|
|
237 |
clusters_.push(cluster);
|
|
|
238 |
}
|
|
|
239 |
};
|
|
|
240 |
|
|
|
241 |
/**
|
|
|
242 |
* Remove a marker.
|
|
|
243 |
*
|
|
|
244 |
* @param {GMarker} marker The marker you want to remove.
|
|
|
245 |
*/
|
|
|
246 |
|
|
|
247 |
this.removeMarker = function (marker) {
|
|
|
248 |
for (var i = 0; i < clusters_.length; ++i) {
|
|
|
249 |
if (clusters_[i].remove(marker)) {
|
|
|
250 |
clusters_[i].redraw_();
|
|
|
251 |
return;
|
|
|
252 |
}
|
|
|
253 |
}
|
|
|
254 |
};
|
|
|
255 |
|
|
|
256 |
/**
|
|
|
257 |
* Redraw all clusters in viewport.
|
|
|
258 |
*/
|
|
|
259 |
this.redraw_ = function () {
|
|
|
260 |
var clusters = this.getClustersInViewport_();
|
|
|
261 |
for (var i = 0; i < clusters.length; ++i) {
|
|
|
262 |
clusters[i].redraw_(true);
|
|
|
263 |
}
|
|
|
264 |
};
|
|
|
265 |
|
|
|
266 |
/**
|
|
|
267 |
* Get all clusters in viewport.
|
|
|
268 |
* @return {Array of Cluster}
|
|
|
269 |
*/
|
|
|
270 |
this.getClustersInViewport_ = function () {
|
|
|
271 |
var clusters = [];
|
|
|
272 |
var curBounds = map_.getBounds();
|
|
|
273 |
for (var i = 0; i < clusters_.length; i ++) {
|
|
|
274 |
if (clusters_[i].isInBounds(curBounds)) {
|
|
|
275 |
clusters.push(clusters_[i]);
|
|
|
276 |
}
|
|
|
277 |
}
|
|
|
278 |
return clusters;
|
|
|
279 |
};
|
|
|
280 |
|
|
|
281 |
/**
|
|
|
282 |
* Get max zoom level.
|
|
|
283 |
* @private
|
|
|
284 |
* @return {Number}
|
|
|
285 |
*/
|
|
|
286 |
this.getMaxZoom_ = function () {
|
|
|
287 |
return maxZoom_;
|
|
|
288 |
};
|
|
|
289 |
|
|
|
290 |
/**
|
|
|
291 |
* Get map object.
|
|
|
292 |
* @private
|
|
|
293 |
* @return {GMap2}
|
|
|
294 |
*/
|
|
|
295 |
this.getMap_ = function () {
|
|
|
296 |
return map_;
|
|
|
297 |
};
|
|
|
298 |
|
|
|
299 |
/**
|
|
|
300 |
* Get grid size
|
|
|
301 |
* @private
|
|
|
302 |
* @return {Number}
|
|
|
303 |
*/
|
|
|
304 |
this.getGridSize_ = function () {
|
|
|
305 |
return gridSize_;
|
|
|
306 |
};
|
|
|
307 |
|
|
|
308 |
/**
|
|
|
309 |
* Get total number of markers.
|
|
|
310 |
* @return {Number}
|
|
|
311 |
*/
|
|
|
312 |
this.getTotalMarkers = function () {
|
|
|
313 |
var result = 0;
|
|
|
314 |
for (var i = 0; i < clusters_.length; ++i) {
|
|
|
315 |
result += clusters_[i].getTotalMarkers();
|
|
|
316 |
}
|
|
|
317 |
return result;
|
|
|
318 |
};
|
|
|
319 |
|
|
|
320 |
/**
|
|
|
321 |
* Get total number of clusters.
|
|
|
322 |
* @return {int}
|
|
|
323 |
*/
|
|
|
324 |
this.getTotalClusters = function () {
|
|
|
325 |
return clusters_.length;
|
|
|
326 |
};
|
|
|
327 |
|
|
|
328 |
/**
|
|
|
329 |
* Collect all markers of clusters in viewport and regroup them.
|
|
|
330 |
*/
|
|
|
331 |
this.resetViewport = function () {
|
|
|
332 |
var clusters = this.getClustersInViewport_();
|
|
|
333 |
var tmpMarkers = [];
|
|
|
334 |
var removed = 0;
|
|
|
335 |
|
|
|
336 |
for (var i = 0; i < clusters.length; ++i) {
|
|
|
337 |
var cluster = clusters[i];
|
|
|
338 |
var oldZoom = cluster.getCurrentZoom();
|
|
|
339 |
if (oldZoom === null) {
|
|
|
340 |
continue;
|
|
|
341 |
}
|
|
|
342 |
var curZoom = map_.getZoom();
|
|
|
343 |
if (curZoom !== oldZoom) {
|
|
|
344 |
|
|
|
345 |
// If the cluster zoom level changed then destroy the cluster
|
|
|
346 |
// and collect its markers.
|
|
|
347 |
var mks = cluster.getMarkers();
|
|
|
348 |
for (var j = 0; j < mks.length; ++j) {
|
|
|
349 |
var newMarker = {
|
|
|
350 |
'isAdded': false,
|
|
|
351 |
'marker': mks[j].marker
|
|
|
352 |
};
|
|
|
353 |
tmpMarkers.push(newMarker);
|
|
|
354 |
}
|
|
|
355 |
cluster.clearMarkers();
|
|
|
356 |
removed++;
|
|
|
357 |
for (j = 0; j < clusters_.length; ++j) {
|
|
|
358 |
if (cluster === clusters_[j]) {
|
|
|
359 |
clusters_.splice(j, 1);
|
|
|
360 |
}
|
|
|
361 |
}
|
|
|
362 |
}
|
|
|
363 |
}
|
|
|
364 |
|
|
|
365 |
// Add the markers collected into marker cluster to reset
|
|
|
366 |
reAddMarkers_(tmpMarkers);
|
|
|
367 |
this.redraw_();
|
|
|
368 |
};
|
|
|
369 |
|
|
|
370 |
|
|
|
371 |
/**
|
|
|
372 |
* Add a set of markers.
|
|
|
373 |
*
|
|
|
374 |
* @param {Array of GMarker} markers The markers you want to add.
|
|
|
375 |
*/
|
|
|
376 |
this.addMarkers = function (markers) {
|
|
|
377 |
for (var i = 0; i < markers.length; ++i) {
|
|
|
378 |
this.addMarker(markers[i], true);
|
|
|
379 |
}
|
|
|
380 |
this.redraw_();
|
|
|
381 |
};
|
|
|
382 |
|
|
|
383 |
// initialize
|
|
|
384 |
if (typeof opt_markers === "object" && opt_markers !== null) {
|
|
|
385 |
this.addMarkers(opt_markers);
|
|
|
386 |
}
|
|
|
387 |
|
|
|
388 |
// when map move end, regroup.
|
|
|
389 |
mcfn_ = GEvent.addListener(map_, "moveend", function () {
|
|
|
390 |
me_.resetViewport();
|
|
|
391 |
});
|
|
|
392 |
}
|
|
|
393 |
|
|
|
394 |
/**
|
|
|
395 |
* Create a cluster to collect markers.
|
|
|
396 |
* A cluster includes some markers which are in a block of area.
|
|
|
397 |
* If there are more than one markers in cluster, the cluster
|
|
|
398 |
* will create a {@link ClusterMarker_} and show the total number
|
|
|
399 |
* of markers in cluster.
|
|
|
400 |
*
|
|
|
401 |
* @constructor
|
|
|
402 |
* @private
|
|
|
403 |
* @param {MarkerClusterer} markerClusterer The marker cluster object
|
|
|
404 |
*/
|
|
|
405 |
function Cluster(markerClusterer) {
|
|
|
406 |
var center_ = null;
|
|
|
407 |
var markers_ = [];
|
|
|
408 |
var markerClusterer_ = markerClusterer;
|
|
|
409 |
var map_ = markerClusterer.getMap_();
|
|
|
410 |
var clusterMarker_ = null;
|
|
|
411 |
var zoom_ = map_.getZoom();
|
|
|
412 |
|
|
|
413 |
/**
|
|
|
414 |
* Get markers of this cluster.
|
|
|
415 |
*
|
|
|
416 |
* @return {Array of GMarker}
|
|
|
417 |
*/
|
|
|
418 |
this.getMarkers = function () {
|
|
|
419 |
return markers_;
|
|
|
420 |
};
|
|
|
421 |
|
|
|
422 |
/**
|
|
|
423 |
* If this cluster intersects certain bounds.
|
|
|
424 |
*
|
|
|
425 |
* @param {GLatLngBounds} bounds A bounds to test
|
|
|
426 |
* @return {Boolean} Is this cluster intersects the bounds
|
|
|
427 |
*/
|
|
|
428 |
this.isInBounds = function (bounds) {
|
|
|
429 |
if (center_ === null) {
|
|
|
430 |
return false;
|
|
|
431 |
}
|
|
|
432 |
|
|
|
433 |
if (!bounds) {
|
|
|
434 |
bounds = map_.getBounds();
|
|
|
435 |
}
|
|
|
436 |
var sw = map_.fromLatLngToDivPixel(bounds.getSouthWest());
|
|
|
437 |
var ne = map_.fromLatLngToDivPixel(bounds.getNorthEast());
|
|
|
438 |
|
|
|
439 |
var centerxy = map_.fromLatLngToDivPixel(center_);
|
|
|
440 |
var inViewport = true;
|
|
|
441 |
var gridSize = markerClusterer.getGridSize_();
|
|
|
442 |
if (zoom_ !== map_.getZoom()) {
|
|
|
443 |
var dl = map_.getZoom() - zoom_;
|
|
|
444 |
gridSize = Math.pow(2, dl) * gridSize;
|
|
|
445 |
}
|
|
|
446 |
if (ne.x !== sw.x && (centerxy.x + gridSize < sw.x || centerxy.x - gridSize > ne.x)) {
|
|
|
447 |
inViewport = false;
|
|
|
448 |
}
|
|
|
449 |
if (inViewport && (centerxy.y + gridSize < ne.y || centerxy.y - gridSize > sw.y)) {
|
|
|
450 |
inViewport = false;
|
|
|
451 |
}
|
|
|
452 |
return inViewport;
|
|
|
453 |
};
|
|
|
454 |
|
|
|
455 |
/**
|
|
|
456 |
* Get cluster center.
|
|
|
457 |
*
|
|
|
458 |
* @return {GLatLng}
|
|
|
459 |
*/
|
|
|
460 |
this.getCenter = function () {
|
|
|
461 |
return center_;
|
|
|
462 |
};
|
|
|
463 |
|
|
|
464 |
/**
|
|
|
465 |
* Add a marker.
|
|
|
466 |
*
|
|
|
467 |
* @param {Object} marker An object of marker you want to add:
|
|
|
468 |
* {Boolean} isAdded If the marker is added on map.
|
|
|
469 |
* {GMarker} marker The marker you want to add.
|
|
|
470 |
*/
|
|
|
471 |
this.addMarker = function (marker) {
|
|
|
472 |
if (center_ === null) {
|
|
|
473 |
/*var pos = marker['marker'].getLatLng();
|
|
|
474 |
pos = map.fromLatLngToContainerPixel(pos);
|
|
|
475 |
pos.x = parseInt(pos.x - pos.x % (GRIDWIDTH * 2) + GRIDWIDTH);
|
|
|
476 |
pos.y = parseInt(pos.y - pos.y % (GRIDWIDTH * 2) + GRIDWIDTH);
|
|
|
477 |
center = map.fromContainerPixelToLatLng(pos);*/
|
|
|
478 |
center_ = marker.marker.getLatLng();
|
|
|
479 |
}
|
|
|
480 |
markers_.push(marker);
|
|
|
481 |
};
|
|
|
482 |
|
|
|
483 |
/**
|
|
|
484 |
* Remove a marker from cluster.
|
|
|
485 |
*
|
|
|
486 |
* @param {GMarker} marker The marker you want to remove.
|
|
|
487 |
* @return {Boolean} Whether find the marker to be removed.
|
|
|
488 |
*/
|
|
|
489 |
this.removeMarker = function (marker) {
|
|
|
490 |
for (var i = 0; i < markers_.length; ++i) {
|
|
|
491 |
if (marker === markers_[i].marker) {
|
|
|
492 |
if (markers_[i].isAdded) {
|
|
|
493 |
map_.removeOverlay(markers_[i].marker);
|
|
|
494 |
}
|
|
|
495 |
markers_.splice(i, 1);
|
|
|
496 |
return true;
|
|
|
497 |
}
|
|
|
498 |
}
|
|
|
499 |
return false;
|
|
|
500 |
};
|
|
|
501 |
|
|
|
502 |
/**
|
|
|
503 |
* Get current zoom level of this cluster.
|
|
|
504 |
* Note: the cluster zoom level and map zoom level not always the same.
|
|
|
505 |
*
|
|
|
506 |
* @return {Number}
|
|
|
507 |
*/
|
|
|
508 |
this.getCurrentZoom = function () {
|
|
|
509 |
return zoom_;
|
|
|
510 |
};
|
|
|
511 |
|
|
|
512 |
/**
|
|
|
513 |
* Redraw a cluster.
|
|
|
514 |
* @private
|
|
|
515 |
* @param {Boolean} isForce If redraw by force, no matter if the cluster is
|
|
|
516 |
* in viewport.
|
|
|
517 |
*/
|
|
|
518 |
this.redraw_ = function (isForce) {
|
|
|
519 |
if (!isForce && !this.isInBounds()) {
|
|
|
520 |
return;
|
|
|
521 |
}
|
|
|
522 |
|
|
|
523 |
// Set cluster zoom level.
|
|
|
524 |
zoom_ = map_.getZoom();
|
|
|
525 |
var i = 0;
|
|
|
526 |
var mz = markerClusterer.getMaxZoom_();
|
|
|
527 |
if (mz === null) {
|
|
|
528 |
mz = map_.getCurrentMapType().getMaximumResolution();
|
|
|
529 |
}
|
|
|
530 |
if (zoom_ >= mz || this.getTotalMarkers() === 1) {
|
|
|
531 |
|
|
|
532 |
// If current zoom level is beyond the max zoom level or the cluster
|
|
|
533 |
// have only one marker, the marker(s) in cluster will be showed on map.
|
|
|
534 |
for (i = 0; i < markers_.length; ++i) {
|
|
|
535 |
if (markers_[i].isAdded) {
|
|
|
536 |
if (markers_[i].marker.isHidden()) {
|
|
|
537 |
markers_[i].marker.show();
|
|
|
538 |
}
|
|
|
539 |
} else {
|
|
|
540 |
map_.addOverlay(markers_[i].marker);
|
|
|
541 |
markers_[i].isAdded = true;
|
|
|
542 |
}
|
|
|
543 |
}
|
|
|
544 |
if (clusterMarker_ !== null) {
|
|
|
545 |
clusterMarker_.hide();
|
|
|
546 |
}
|
|
|
547 |
} else {
|
|
|
548 |
// Else add a cluster marker on map to show the number of markers in
|
|
|
549 |
// this cluster.
|
|
|
550 |
for (i = 0; i < markers_.length; ++i) {
|
|
|
551 |
if (markers_[i].isAdded && (!markers_[i].marker.isHidden())) {
|
|
|
552 |
markers_[i].marker.hide();
|
|
|
553 |
}
|
|
|
554 |
}
|
|
|
555 |
if (clusterMarker_ === null) {
|
|
|
556 |
clusterMarker_ = new ClusterMarker_(center_, this.getTotalMarkers(), markerClusterer_.getStyles_(), markerClusterer_.getGridSize_());
|
|
|
557 |
map_.addOverlay(clusterMarker_);
|
|
|
558 |
} else {
|
|
|
559 |
if (clusterMarker_.isHidden()) {
|
|
|
560 |
clusterMarker_.show();
|
|
|
561 |
}
|
|
|
562 |
clusterMarker_.redraw(true);
|
|
|
563 |
}
|
|
|
564 |
}
|
|
|
565 |
};
|
|
|
566 |
|
|
|
567 |
/**
|
|
|
568 |
* Remove all the markers from this cluster.
|
|
|
569 |
*/
|
|
|
570 |
this.clearMarkers = function () {
|
|
|
571 |
if (clusterMarker_ !== null) {
|
|
|
572 |
map_.removeOverlay(clusterMarker_);
|
|
|
573 |
}
|
|
|
574 |
for (var i = 0; i < markers_.length; ++i) {
|
|
|
575 |
if (markers_[i].isAdded) {
|
|
|
576 |
map_.removeOverlay(markers_[i].marker);
|
|
|
577 |
}
|
|
|
578 |
}
|
|
|
579 |
markers_ = [];
|
|
|
580 |
};
|
|
|
581 |
|
|
|
582 |
/**
|
|
|
583 |
* Get number of markers.
|
|
|
584 |
* @return {Number}
|
|
|
585 |
*/
|
|
|
586 |
this.getTotalMarkers = function () {
|
|
|
587 |
return markers_.length;
|
|
|
588 |
};
|
|
|
589 |
}
|
|
|
590 |
|
|
|
591 |
/**
|
|
|
592 |
* ClusterMarker_ creates a marker that shows the number of markers that
|
|
|
593 |
* a cluster contains.
|
|
|
594 |
*
|
|
|
595 |
* @constructor
|
|
|
596 |
* @private
|
|
|
597 |
* @param {GLatLng} latlng Marker's lat and lng.
|
|
|
598 |
* @param {Number} count Number to show.
|
|
|
599 |
* @param {Array of Object} styles The image list to be showed:
|
|
|
600 |
* {String} url Image url.
|
|
|
601 |
* {Number} height Image height.
|
|
|
602 |
* {Number} width Image width.
|
|
|
603 |
* {Array of Number} anchor Text anchor of image left and top.
|
|
|
604 |
* {String} textColor text color.
|
|
|
605 |
* @param {Number} padding Padding of marker center.
|
|
|
606 |
*/
|
|
|
607 |
function ClusterMarker_(latlng, count, styles, padding) {
|
|
|
608 |
var index = 0;
|
|
|
609 |
var dv = count;
|
|
|
610 |
while (dv !== 0) {
|
|
|
611 |
dv = parseInt(dv / 10, 10);
|
|
|
612 |
index ++;
|
|
|
613 |
}
|
|
|
614 |
|
|
|
615 |
if (styles.length < index) {
|
|
|
616 |
index = styles.length;
|
|
|
617 |
}
|
|
|
618 |
this.url_ = styles[index - 1].url;
|
|
|
619 |
this.height_ = styles[index - 1].height;
|
|
|
620 |
this.width_ = styles[index - 1].width;
|
|
|
621 |
this.textColor_ = styles[index - 1].opt_textColor;
|
|
|
622 |
this.anchor_ = styles[index - 1].opt_anchor;
|
|
|
623 |
this.latlng_ = latlng;
|
|
|
624 |
this.index_ = index;
|
|
|
625 |
this.styles_ = styles;
|
|
|
626 |
this.text_ = count;
|
|
|
627 |
this.padding_ = padding;
|
|
|
628 |
}
|
|
|
629 |
|
|
|
630 |
ClusterMarker_.prototype = new GOverlay();
|
|
|
631 |
|
|
|
632 |
/**
|
|
|
633 |
* Initialize cluster marker.
|
|
|
634 |
* @private
|
|
|
635 |
*/
|
|
|
636 |
ClusterMarker_.prototype.initialize = function (map) {
|
|
|
637 |
this.map_ = map;
|
|
|
638 |
var div = document.createElement("div");
|
|
|
639 |
var latlng = this.latlng_;
|
|
|
640 |
var pos = map.fromLatLngToDivPixel(latlng);
|
|
|
641 |
pos.x -= parseInt(this.width_ / 2, 10);
|
|
|
642 |
pos.y -= parseInt(this.height_ / 2, 10);
|
|
|
643 |
var mstyle = "";
|
|
|
644 |
if (document.all) {
|
|
|
645 |
mstyle = 'filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale,src="' + this.url_ + '");';
|
|
|
646 |
} else {
|
|
|
647 |
mstyle = "background:url(" + this.url_ + ");";
|
|
|
648 |
}
|
|
|
649 |
if (typeof this.anchor_ === "object") {
|
|
|
650 |
if (typeof this.anchor_[0] === "number" && this.anchor_[0] > 0 && this.anchor_[0] < this.height_) {
|
|
|
651 |
mstyle += 'height:' + (this.height_ - this.anchor_[0]) + 'px;padding-top:' + this.anchor_[0] + 'px;';
|
|
|
652 |
} else {
|
|
|
653 |
mstyle += 'height:' + this.height_ + 'px;line-height:' + this.height_ + 'px;';
|
|
|
654 |
}
|
|
|
655 |
if (typeof this.anchor_[1] === "number" && this.anchor_[1] > 0 && this.anchor_[1] < this.width_) {
|
|
|
656 |
mstyle += 'width:' + (this.width_ - this.anchor_[1]) + 'px;padding-left:' + this.anchor_[1] + 'px;';
|
|
|
657 |
} else {
|
|
|
658 |
mstyle += 'width:' + this.width_ + 'px;text-align:center;';
|
|
|
659 |
}
|
|
|
660 |
} else {
|
|
|
661 |
mstyle += 'height:' + this.height_ + 'px;line-height:' + this.height_ + 'px;';
|
|
|
662 |
mstyle += 'width:' + this.width_ + 'px;text-align:center;';
|
|
|
663 |
}
|
|
|
664 |
var txtColor = this.textColor_ ? this.textColor_ : 'black';
|
|
|
665 |
|
|
|
666 |
div.style.cssText = mstyle + 'cursor:pointer;top:' + pos.y + "px;left:" +
|
|
|
667 |
pos.x + "px;color:" + txtColor + ";position:absolute;font-size:11px;" +
|
|
|
668 |
'font-family:Arial,sans-serif;font-weight:bold';
|
|
|
669 |
div.innerHTML = this.text_;
|
|
|
670 |
map.getPane(G_MAP_MAP_PANE).appendChild(div);
|
|
|
671 |
var padding = this.padding_;
|
|
|
672 |
GEvent.addDomListener(div, "click", function () {
|
|
|
673 |
var pos = map.fromLatLngToDivPixel(latlng);
|
|
|
674 |
var sw = new GPoint(pos.x - padding, pos.y + padding);
|
|
|
675 |
sw = map.fromDivPixelToLatLng(sw);
|
|
|
676 |
var ne = new GPoint(pos.x + padding, pos.y - padding);
|
|
|
677 |
ne = map.fromDivPixelToLatLng(ne);
|
|
|
678 |
var zoom = map.getBoundsZoomLevel(new GLatLngBounds(sw, ne), map.getSize());
|
|
|
679 |
map.setCenter(latlng, zoom);
|
|
|
680 |
});
|
|
|
681 |
this.div_ = div;
|
|
|
682 |
};
|
|
|
683 |
|
|
|
684 |
/**
|
|
|
685 |
* Remove this overlay.
|
|
|
686 |
* @private
|
|
|
687 |
*/
|
|
|
688 |
ClusterMarker_.prototype.remove = function () {
|
|
|
689 |
this.div_.parentNode.removeChild(this.div_);
|
|
|
690 |
};
|
|
|
691 |
|
|
|
692 |
/**
|
|
|
693 |
* Copy this overlay.
|
|
|
694 |
* @private
|
|
|
695 |
*/
|
|
|
696 |
ClusterMarker_.prototype.copy = function () {
|
|
|
697 |
return new ClusterMarker_(this.latlng_, this.index_, this.text_, this.styles_, this.padding_);
|
|
|
698 |
};
|
|
|
699 |
|
|
|
700 |
/**
|
|
|
701 |
* Redraw this overlay.
|
|
|
702 |
* @private
|
|
|
703 |
*/
|
|
|
704 |
ClusterMarker_.prototype.redraw = function (force) {
|
|
|
705 |
if (!force) {
|
|
|
706 |
return;
|
|
|
707 |
}
|
|
|
708 |
var pos = this.map_.fromLatLngToDivPixel(this.latlng_);
|
|
|
709 |
pos.x -= parseInt(this.width_ / 2, 10);
|
|
|
710 |
pos.y -= parseInt(this.height_ / 2, 10);
|
|
|
711 |
this.div_.style.top = pos.y + "px";
|
|
|
712 |
this.div_.style.left = pos.x + "px";
|
|
|
713 |
};
|
|
|
714 |
|
|
|
715 |
/**
|
|
|
716 |
* Hide this cluster marker.
|
|
|
717 |
*/
|
|
|
718 |
ClusterMarker_.prototype.hide = function () {
|
|
|
719 |
this.div_.style.display = "none";
|
|
|
720 |
};
|
|
|
721 |
|
|
|
722 |
/**
|
|
|
723 |
* Show this cluster marker.
|
|
|
724 |
*/
|
|
|
725 |
ClusterMarker_.prototype.show = function () {
|
|
|
726 |
this.div_.style.display = "";
|
|
|
727 |
};
|
|
|
728 |
|
|
|
729 |
/**
|
|
|
730 |
* Get whether the cluster marker is hidden.
|
|
|
731 |
* @return {Boolean}
|
|
|
732 |
*/
|
|
|
733 |
ClusterMarker_.prototype.isHidden = function () {
|
|
|
734 |
return this.div_.style.display === "none";
|
|
|
735 |
};
|