[7732] | 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;
|
---|
[7733] | 81 | var maxZoom_ = 15;
|
---|
[7732] | 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) {
|
---|
[7733] | 556 | clusterMarker_ = new ClusterMarker_(center_, this.getTotalMarkers(), markerClusterer_.getStyles_(), markerClusterer_.getGridSize_(), markers_);
|
---|
[7732] | 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 | */
|
---|
[7733] | 607 | function ClusterMarker_(latlng, count, styles, padding, markerArray) {
|
---|
[7732] | 608 | var index = 0;
|
---|
[7733] | 609 | this.markerArray = markerArray;
|
---|
[7732] | 610 | var dv = count;
|
---|
| 611 | while (dv !== 0) {
|
---|
| 612 | dv = parseInt(dv / 10, 10);
|
---|
| 613 | index ++;
|
---|
| 614 | }
|
---|
| 615 |
|
---|
| 616 | if (styles.length < index) {
|
---|
| 617 | index = styles.length;
|
---|
| 618 | }
|
---|
| 619 | this.url_ = styles[index - 1].url;
|
---|
| 620 | this.height_ = styles[index - 1].height;
|
---|
| 621 | this.width_ = styles[index - 1].width;
|
---|
| 622 | this.textColor_ = styles[index - 1].opt_textColor;
|
---|
| 623 | this.anchor_ = styles[index - 1].opt_anchor;
|
---|
| 624 | this.latlng_ = latlng;
|
---|
| 625 | this.index_ = index;
|
---|
| 626 | this.styles_ = styles;
|
---|
| 627 | this.text_ = count;
|
---|
| 628 | this.padding_ = padding;
|
---|
| 629 | }
|
---|
| 630 |
|
---|
| 631 | ClusterMarker_.prototype = new GOverlay();
|
---|
| 632 |
|
---|
| 633 | /**
|
---|
| 634 | * Initialize cluster marker.
|
---|
| 635 | * @private
|
---|
| 636 | */
|
---|
| 637 | ClusterMarker_.prototype.initialize = function (map) {
|
---|
| 638 | this.map_ = map;
|
---|
[7733] | 639 | var markerArray = this.markerArray;
|
---|
[7732] | 640 | var div = document.createElement("div");
|
---|
| 641 | var latlng = this.latlng_;
|
---|
| 642 | var pos = map.fromLatLngToDivPixel(latlng);
|
---|
| 643 | pos.x -= parseInt(this.width_ / 2, 10);
|
---|
| 644 | pos.y -= parseInt(this.height_ / 2, 10);
|
---|
| 645 | var mstyle = "";
|
---|
| 646 | if (document.all) {
|
---|
| 647 | mstyle = 'filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale,src="' + this.url_ + '");';
|
---|
| 648 | } else {
|
---|
| 649 | mstyle = "background:url(" + this.url_ + ");";
|
---|
| 650 | }
|
---|
| 651 | if (typeof this.anchor_ === "object") {
|
---|
| 652 | if (typeof this.anchor_[0] === "number" && this.anchor_[0] > 0 && this.anchor_[0] < this.height_) {
|
---|
| 653 | mstyle += 'height:' + (this.height_ - this.anchor_[0]) + 'px;padding-top:' + this.anchor_[0] + 'px;';
|
---|
| 654 | } else {
|
---|
| 655 | mstyle += 'height:' + this.height_ + 'px;line-height:' + this.height_ + 'px;';
|
---|
| 656 | }
|
---|
| 657 | if (typeof this.anchor_[1] === "number" && this.anchor_[1] > 0 && this.anchor_[1] < this.width_) {
|
---|
| 658 | mstyle += 'width:' + (this.width_ - this.anchor_[1]) + 'px;padding-left:' + this.anchor_[1] + 'px;';
|
---|
| 659 | } else {
|
---|
| 660 | mstyle += 'width:' + this.width_ + 'px;text-align:center;';
|
---|
| 661 | }
|
---|
| 662 | } else {
|
---|
| 663 | mstyle += 'height:' + this.height_ + 'px;line-height:' + this.height_ + 'px;';
|
---|
| 664 | mstyle += 'width:' + this.width_ + 'px;text-align:center;';
|
---|
| 665 | }
|
---|
| 666 | var txtColor = this.textColor_ ? this.textColor_ : 'black';
|
---|
| 667 |
|
---|
| 668 | div.style.cssText = mstyle + 'cursor:pointer;top:' + pos.y + "px;left:" +
|
---|
| 669 | pos.x + "px;color:" + txtColor + ";position:absolute;font-size:11px;" +
|
---|
| 670 | 'font-family:Arial,sans-serif;font-weight:bold';
|
---|
| 671 | div.innerHTML = this.text_;
|
---|
| 672 | map.getPane(G_MAP_MAP_PANE).appendChild(div);
|
---|
| 673 | var padding = this.padding_;
|
---|
[7733] | 674 | GEvent.addDomListener(div, "doubleclick", function () {
|
---|
[7732] | 675 | var pos = map.fromLatLngToDivPixel(latlng);
|
---|
| 676 | var sw = new GPoint(pos.x - padding, pos.y + padding);
|
---|
| 677 | sw = map.fromDivPixelToLatLng(sw);
|
---|
| 678 | var ne = new GPoint(pos.x + padding, pos.y - padding);
|
---|
| 679 | ne = map.fromDivPixelToLatLng(ne);
|
---|
| 680 | var zoom = map.getBoundsZoomLevel(new GLatLngBounds(sw, ne), map.getSize());
|
---|
| 681 | map.setCenter(latlng, zoom);
|
---|
| 682 | });
|
---|
[7733] | 683 | //Jan: We add our own mouseover listener for the cluster.
|
---|
| 684 | GEvent.addDomListener(div, "mouseover", function() {
|
---|
| 685 | mouseOverCluster(markerArray);
|
---|
| 686 | });
|
---|
[7738] | 687 |
|
---|
| 688 | GEvent.addDomListener(div, "click", function() {
|
---|
| 689 | clickCluster(markerArray);
|
---|
| 690 | });
|
---|
[7732] | 691 | this.div_ = div;
|
---|
| 692 | };
|
---|
| 693 |
|
---|
| 694 | /**
|
---|
| 695 | * Remove this overlay.
|
---|
| 696 | * @private
|
---|
| 697 | */
|
---|
| 698 | ClusterMarker_.prototype.remove = function () {
|
---|
| 699 | this.div_.parentNode.removeChild(this.div_);
|
---|
| 700 | };
|
---|
| 701 |
|
---|
| 702 | /**
|
---|
| 703 | * Copy this overlay.
|
---|
| 704 | * @private
|
---|
| 705 | */
|
---|
| 706 | ClusterMarker_.prototype.copy = function () {
|
---|
| 707 | return new ClusterMarker_(this.latlng_, this.index_, this.text_, this.styles_, this.padding_);
|
---|
| 708 | };
|
---|
| 709 |
|
---|
| 710 | /**
|
---|
| 711 | * Redraw this overlay.
|
---|
| 712 | * @private
|
---|
| 713 | */
|
---|
| 714 | ClusterMarker_.prototype.redraw = function (force) {
|
---|
| 715 | if (!force) {
|
---|
| 716 | return;
|
---|
| 717 | }
|
---|
| 718 | var pos = this.map_.fromLatLngToDivPixel(this.latlng_);
|
---|
| 719 | pos.x -= parseInt(this.width_ / 2, 10);
|
---|
| 720 | pos.y -= parseInt(this.height_ / 2, 10);
|
---|
| 721 | this.div_.style.top = pos.y + "px";
|
---|
| 722 | this.div_.style.left = pos.x + "px";
|
---|
| 723 | };
|
---|
| 724 |
|
---|
| 725 | /**
|
---|
| 726 | * Hide this cluster marker.
|
---|
| 727 | */
|
---|
| 728 | ClusterMarker_.prototype.hide = function () {
|
---|
| 729 | this.div_.style.display = "none";
|
---|
| 730 | };
|
---|
| 731 |
|
---|
| 732 | /**
|
---|
| 733 | * Show this cluster marker.
|
---|
| 734 | */
|
---|
| 735 | ClusterMarker_.prototype.show = function () {
|
---|
| 736 | this.div_.style.display = "";
|
---|
| 737 | };
|
---|
| 738 |
|
---|
| 739 | /**
|
---|
| 740 | * Get whether the cluster marker is hidden.
|
---|
| 741 | * @return {Boolean}
|
---|
| 742 | */
|
---|
| 743 | ClusterMarker_.prototype.isHidden = function () {
|
---|
| 744 | return this.div_.style.display === "none";
|
---|
[7733] | 745 | };
|
---|