PageRenderTime 58ms CodeModel.GetById 7ms RepoModel.GetById 0ms app.codeStats 0ms

/Artem.GoogleMap.Extensions/Scripts/markermanager.js

#
JavaScript | 979 lines | 441 code | 140 blank | 398 comment | 81 complexity | 598e0d99e0875d5935bd6c692eab90d5 MD5 | raw file
Possible License(s): MIT
  1. /**
  2. * @name MarkerManager v3
  3. * @version 1.1
  4. * @copyright (c) 2007 Google Inc.
  5. * @author Doug Ricket, Bjorn Brala (port to v3), others,
  6. *
  7. * @fileoverview Marker manager is an interface between the map and the user,
  8. * designed to manage adding and removing many points when the viewport changes.
  9. * <br /><br />
  10. * <b>How it Works</b>:<br/>
  11. * The MarkerManager places its markers onto a grid, similar to the map tiles.
  12. * When the user moves the viewport, it computes which grid cells have
  13. * entered or left the viewport, and shows or hides all the markers in those
  14. * cells.
  15. * (If the users scrolls the viewport beyond the markers that are loaded,
  16. * no markers will be visible until the <code>EVENT_moveend</code>
  17. * triggers an update.)
  18. * In practical consequences, this allows 10,000 markers to be distributed over
  19. * a large area, and as long as only 100-200 are visible in any given viewport,
  20. * the user will see good performance corresponding to the 100 visible markers,
  21. * rather than poor performance corresponding to the total 10,000 markers.
  22. * Note that some code is optimized for speed over space,
  23. * with the goal of accommodating thousands of markers.
  24. */
  25. /*
  26. * Licensed under the Apache License, Version 2.0 (the "License");
  27. * you may not use this file except in compliance with the License.
  28. * You may obtain a copy of the License at
  29. *
  30. * http://www.apache.org/licenses/LICENSE-2.0
  31. *
  32. * Unless required by applicable law or agreed to in writing, software
  33. * distributed under the License is distributed on an "AS IS" BASIS,
  34. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  35. * See the License for the specific language governing permissions and
  36. * limitations under the License.
  37. */
  38. /**
  39. * @name MarkerManagerOptions
  40. * @class This class represents optional arguments to the {@link MarkerManager}
  41. * constructor.
  42. * @property {Number} maxZoom Sets the maximum zoom level monitored by a
  43. * marker manager. If not given, the manager assumes the maximum map zoom
  44. * level. This value is also used when markers are added to the manager
  45. * without the optional {@link maxZoom} parameter.
  46. * @property {Number} borderPadding Specifies, in pixels, the extra padding
  47. * outside the map's current viewport monitored by a manager. Markers that
  48. * fall within this padding are added to the map, even if they are not fully
  49. * visible.
  50. * @property {Boolean} trackMarkers=false Indicates whether or not a marker
  51. * manager should track markers' movements. If you wish to move managed
  52. * markers using the {@link setPoint}/{@link setLatLng} methods,
  53. * this option should be set to {@link true}.
  54. */
  55. /**
  56. * Creates a new MarkerManager that will show/hide markers on a map.
  57. *
  58. * Events:
  59. * @event changed (Parameters: shown bounds, shown markers) Notify listeners when the state of what is displayed changes.
  60. * @event loaded MarkerManager has succesfully been initialized.
  61. *
  62. * @constructor
  63. * @param {Map} map The map to manage.
  64. * @param {Object} opt_opts A container for optional arguments:
  65. * {Number} maxZoom The maximum zoom level for which to create tiles.
  66. * {Number} borderPadding The width in pixels beyond the map border,
  67. * where markers should be display.
  68. * {Boolean} trackMarkers Whether or not this manager should track marker
  69. * movements.
  70. */
  71. function MarkerManager(map, opt_opts) {
  72. var me = this;
  73. me.map_ = map;
  74. me.mapZoom_ = map.getZoom();
  75. me.projectionHelper_ = new ProjectionHelperOverlay(map);
  76. google.maps.event.addListener(me.projectionHelper_, 'ready', function () {
  77. me.projection_ = this.getProjection();
  78. me.initialize(map, opt_opts);
  79. });
  80. }
  81. MarkerManager.prototype.initialize = function (map, opt_opts) {
  82. var me = this;
  83. opt_opts = opt_opts || {};
  84. me.tileSize_ = MarkerManager.DEFAULT_TILE_SIZE_;
  85. var mapTypes = map.mapTypes;
  86. // Find max zoom level
  87. var mapMaxZoom = 1;
  88. for (var sType in mapTypes ) {
  89. if (typeof map.mapTypes.get(sType) === 'object' && typeof map.mapTypes.get(sType).maxZoom === 'number') {
  90. var mapTypeMaxZoom = map.mapTypes.get(sType).maxZoom;
  91. if (mapTypeMaxZoom > mapMaxZoom) {
  92. mapMaxZoom = mapTypeMaxZoom;
  93. }
  94. }
  95. }
  96. me.maxZoom_ = opt_opts.maxZoom || 19;
  97. me.trackMarkers_ = opt_opts.trackMarkers;
  98. me.show_ = opt_opts.show || true;
  99. var padding;
  100. if (typeof opt_opts.borderPadding === 'number') {
  101. padding = opt_opts.borderPadding;
  102. } else {
  103. padding = MarkerManager.DEFAULT_BORDER_PADDING_;
  104. }
  105. // The padding in pixels beyond the viewport, where we will pre-load markers.
  106. me.swPadding_ = new google.maps.Size(-padding, padding);
  107. me.nePadding_ = new google.maps.Size(padding, -padding);
  108. me.borderPadding_ = padding;
  109. me.gridWidth_ = {};
  110. me.grid_ = {};
  111. me.grid_[me.maxZoom_] = {};
  112. me.numMarkers_ = {};
  113. me.numMarkers_[me.maxZoom_] = 0;
  114. google.maps.event.addListener(map, 'dragend', function () {
  115. me.onMapMoveEnd_();
  116. });
  117. google.maps.event.addListener(map, 'idle', function () {
  118. me.onMapMoveEnd_();
  119. });
  120. google.maps.event.addListener(map, 'zoom_changed', function () {
  121. me.onMapMoveEnd_();
  122. });
  123. /**
  124. * This closure provide easy access to the map.
  125. * They are used as callbacks, not as methods.
  126. * @param GMarker marker Marker to be removed from the map
  127. * @private
  128. */
  129. me.removeOverlay_ = function (marker) {
  130. marker.setMap(null);
  131. me.shownMarkers_--;
  132. };
  133. /**
  134. * This closure provide easy access to the map.
  135. * They are used as callbacks, not as methods.
  136. * @param GMarker marker Marker to be added to the map
  137. * @private
  138. */
  139. me.addOverlay_ = function (marker) {
  140. if (me.show_) {
  141. marker.setMap(me.map_);
  142. me.shownMarkers_++;
  143. }
  144. };
  145. me.resetManager_();
  146. me.shownMarkers_ = 0;
  147. me.shownBounds_ = me.getMapGridBounds_();
  148. google.maps.event.trigger(me, 'loaded');
  149. };
  150. /**
  151. * Default tile size used for deviding the map into a grid.
  152. */
  153. MarkerManager.DEFAULT_TILE_SIZE_ = 1024;
  154. /*
  155. * How much extra space to show around the map border so
  156. * dragging doesn't result in an empty place.
  157. */
  158. MarkerManager.DEFAULT_BORDER_PADDING_ = 100;
  159. /**
  160. * Default tilesize of single tile world.
  161. */
  162. MarkerManager.MERCATOR_ZOOM_LEVEL_ZERO_RANGE = 256;
  163. /**
  164. * Initializes MarkerManager arrays for all zoom levels
  165. * Called by constructor and by clearAllMarkers
  166. */
  167. MarkerManager.prototype.resetManager_ = function () {
  168. var mapWidth = MarkerManager.MERCATOR_ZOOM_LEVEL_ZERO_RANGE;
  169. for (var zoom = 0; zoom <= this.maxZoom_; ++zoom) {
  170. this.grid_[zoom] = {};
  171. this.numMarkers_[zoom] = 0;
  172. this.gridWidth_[zoom] = Math.ceil(mapWidth / this.tileSize_);
  173. mapWidth <<= 1;
  174. }
  175. };
  176. /**
  177. * Removes all markers in the manager, and
  178. * removes any visible markers from the map.
  179. */
  180. MarkerManager.prototype.clearMarkers = function () {
  181. this.processAll_(this.shownBounds_, this.removeOverlay_);
  182. this.resetManager_();
  183. };
  184. /**
  185. * Gets the tile coordinate for a given latlng point.
  186. *
  187. * @param {LatLng} latlng The geographical point.
  188. * @param {Number} zoom The zoom level.
  189. * @param {google.maps.Size} padding The padding used to shift the pixel coordinate.
  190. * Used for expanding a bounds to include an extra padding
  191. * of pixels surrounding the bounds.
  192. * @return {GPoint} The point in tile coordinates.
  193. *
  194. */
  195. MarkerManager.prototype.getTilePoint_ = function (latlng, zoom, padding) {
  196. var pixelPoint = this.projectionHelper_.LatLngToPixel(latlng, zoom);
  197. var point = new google.maps.Point(
  198. Math.floor((pixelPoint.x + padding.width) / this.tileSize_),
  199. Math.floor((pixelPoint.y + padding.height) / this.tileSize_)
  200. );
  201. return point;
  202. };
  203. /**
  204. * Finds the appropriate place to add the marker to the grid.
  205. * Optimized for speed; does not actually add the marker to the map.
  206. * Designed for batch-processing thousands of markers.
  207. *
  208. * @param {Marker} marker The marker to add.
  209. * @param {Number} minZoom The minimum zoom for displaying the marker.
  210. * @param {Number} maxZoom The maximum zoom for displaying the marker.
  211. */
  212. MarkerManager.prototype.addMarkerBatch_ = function (marker, minZoom, maxZoom) {
  213. var me = this;
  214. var mPoint = marker.getPosition();
  215. marker.MarkerManager_minZoom = minZoom;
  216. // Tracking markers is expensive, so we do this only if the
  217. // user explicitly requested it when creating marker manager.
  218. if (this.trackMarkers_) {
  219. google.maps.event.addListener(marker, 'changed', function (a, b, c) {
  220. me.onMarkerMoved_(a, b, c);
  221. });
  222. }
  223. var gridPoint = this.getTilePoint_(mPoint, maxZoom, new google.maps.Size(0, 0, 0, 0));
  224. for (var zoom = maxZoom; zoom >= minZoom; zoom--) {
  225. var cell = this.getGridCellCreate_(gridPoint.x, gridPoint.y, zoom);
  226. cell.push(marker);
  227. gridPoint.x = gridPoint.x >> 1;
  228. gridPoint.y = gridPoint.y >> 1;
  229. }
  230. };
  231. /**
  232. * Returns whether or not the given point is visible in the shown bounds. This
  233. * is a helper method that takes care of the corner case, when shownBounds have
  234. * negative minX value.
  235. *
  236. * @param {Point} point a point on a grid.
  237. * @return {Boolean} Whether or not the given point is visible in the currently
  238. * shown bounds.
  239. */
  240. MarkerManager.prototype.isGridPointVisible_ = function (point) {
  241. var vertical = this.shownBounds_.minY <= point.y &&
  242. point.y <= this.shownBounds_.maxY;
  243. var minX = this.shownBounds_.minX;
  244. var horizontal = minX <= point.x && point.x <= this.shownBounds_.maxX;
  245. if (!horizontal && minX < 0) {
  246. // Shifts the negative part of the rectangle. As point.x is always less
  247. // than grid width, only test shifted minX .. 0 part of the shown bounds.
  248. var width = this.gridWidth_[this.shownBounds_.z];
  249. horizontal = minX + width <= point.x && point.x <= width - 1;
  250. }
  251. return vertical && horizontal;
  252. };
  253. /**
  254. * Reacts to a notification from a marker that it has moved to a new location.
  255. * It scans the grid all all zoom levels and moves the marker from the old grid
  256. * location to a new grid location.
  257. *
  258. * @param {Marker} marker The marker that moved.
  259. * @param {LatLng} oldPoint The old position of the marker.
  260. * @param {LatLng} newPoint The new position of the marker.
  261. */
  262. MarkerManager.prototype.onMarkerMoved_ = function (marker, oldPoint, newPoint) {
  263. // NOTE: We do not know the minimum or maximum zoom the marker was
  264. // added at, so we start at the absolute maximum. Whenever we successfully
  265. // remove a marker at a given zoom, we add it at the new grid coordinates.
  266. var zoom = this.maxZoom_;
  267. var changed = false;
  268. var oldGrid = this.getTilePoint_(oldPoint, zoom, new google.maps.Size(0, 0, 0, 0));
  269. var newGrid = this.getTilePoint_(newPoint, zoom, new google.maps.Size(0, 0, 0, 0));
  270. while (zoom >= 0 && (oldGrid.x !== newGrid.x || oldGrid.y !== newGrid.y)) {
  271. var cell = this.getGridCellNoCreate_(oldGrid.x, oldGrid.y, zoom);
  272. if (cell) {
  273. if (this.removeFromArray_(cell, marker)) {
  274. this.getGridCellCreate_(newGrid.x, newGrid.y, zoom).push(marker);
  275. }
  276. }
  277. // For the current zoom we also need to update the map. Markers that no
  278. // longer are visible are removed from the map. Markers that moved into
  279. // the shown bounds are added to the map. This also lets us keep the count
  280. // of visible markers up to date.
  281. if (zoom === this.mapZoom_) {
  282. if (this.isGridPointVisible_(oldGrid)) {
  283. if (!this.isGridPointVisible_(newGrid)) {
  284. this.removeOverlay_(marker);
  285. changed = true;
  286. }
  287. } else {
  288. if (this.isGridPointVisible_(newGrid)) {
  289. this.addOverlay_(marker);
  290. changed = true;
  291. }
  292. }
  293. }
  294. oldGrid.x = oldGrid.x >> 1;
  295. oldGrid.y = oldGrid.y >> 1;
  296. newGrid.x = newGrid.x >> 1;
  297. newGrid.y = newGrid.y >> 1;
  298. --zoom;
  299. }
  300. if (changed) {
  301. this.notifyListeners_();
  302. }
  303. };
  304. /**
  305. * Removes marker from the manager and from the map
  306. * (if it's currently visible).
  307. * @param {GMarker} marker The marker to delete.
  308. */
  309. MarkerManager.prototype.removeMarker = function (marker) {
  310. var zoom = this.maxZoom_;
  311. var changed = false;
  312. var point = marker.getPosition();
  313. var grid = this.getTilePoint_(point, zoom, new google.maps.Size(0, 0, 0, 0));
  314. while (zoom >= 0) {
  315. var cell = this.getGridCellNoCreate_(grid.x, grid.y, zoom);
  316. if (cell) {
  317. this.removeFromArray_(cell, marker);
  318. }
  319. // For the current zoom we also need to update the map. Markers that no
  320. // longer are visible are removed from the map. This also lets us keep the count
  321. // of visible markers up to date.
  322. if (zoom === this.mapZoom_) {
  323. if (this.isGridPointVisible_(grid)) {
  324. this.removeOverlay_(marker);
  325. changed = true;
  326. }
  327. }
  328. grid.x = grid.x >> 1;
  329. grid.y = grid.y >> 1;
  330. --zoom;
  331. }
  332. if (changed) {
  333. this.notifyListeners_();
  334. }
  335. this.numMarkers_[marker.MarkerManager_minZoom]--;
  336. };
  337. /**
  338. * Add many markers at once.
  339. * Does not actually update the map, just the internal grid.
  340. *
  341. * @param {Array of Marker} markers The markers to add.
  342. * @param {Number} minZoom The minimum zoom level to display the markers.
  343. * @param {Number} opt_maxZoom The maximum zoom level to display the markers.
  344. */
  345. MarkerManager.prototype.addMarkers = function (markers, minZoom, opt_maxZoom) {
  346. var maxZoom = this.getOptMaxZoom_(opt_maxZoom);
  347. for (var i = markers.length - 1; i >= 0; i--) {
  348. this.addMarkerBatch_(markers[i], minZoom, maxZoom);
  349. }
  350. this.numMarkers_[minZoom] += markers.length;
  351. };
  352. /**
  353. * Returns the value of the optional maximum zoom. This method is defined so
  354. * that we have just one place where optional maximum zoom is calculated.
  355. *
  356. * @param {Number} opt_maxZoom The optinal maximum zoom.
  357. * @return The maximum zoom.
  358. */
  359. MarkerManager.prototype.getOptMaxZoom_ = function (opt_maxZoom) {
  360. return opt_maxZoom || this.maxZoom_;
  361. };
  362. /**
  363. * Calculates the total number of markers potentially visible at a given
  364. * zoom level.
  365. *
  366. * @param {Number} zoom The zoom level to check.
  367. */
  368. MarkerManager.prototype.getMarkerCount = function (zoom) {
  369. var total = 0;
  370. for (var z = 0; z <= zoom; z++) {
  371. total += this.numMarkers_[z];
  372. }
  373. return total;
  374. };
  375. /**
  376. * Returns a marker given latitude, longitude and zoom. If the marker does not
  377. * exist, the method will return a new marker. If a new marker is created,
  378. * it will NOT be added to the manager.
  379. *
  380. * @param {Number} lat - the latitude of a marker.
  381. * @param {Number} lng - the longitude of a marker.
  382. * @param {Number} zoom - the zoom level
  383. * @return {GMarker} marker - the marker found at lat and lng
  384. */
  385. MarkerManager.prototype.getMarker = function (lat, lng, zoom) {
  386. var mPoint = new google.maps.LatLng(lat, lng);
  387. var gridPoint = this.getTilePoint_(mPoint, zoom, new google.maps.Size(0, 0, 0, 0));
  388. var marker = new google.maps.Marker({position: mPoint});
  389. var cellArray = this.getGridCellNoCreate_(gridPoint.x, gridPoint.y, zoom);
  390. if (cellArray !== undefined) {
  391. for (var i = 0; i < cellArray.length; i++)
  392. {
  393. if (lat === cellArray[i].getPosition().lat() && lng === cellArray[i].getPosition().lng()) {
  394. marker = cellArray[i];
  395. }
  396. }
  397. }
  398. return marker;
  399. };
  400. /**
  401. * Add a single marker to the map.
  402. *
  403. * @param {Marker} marker The marker to add.
  404. * @param {Number} minZoom The minimum zoom level to display the marker.
  405. * @param {Number} opt_maxZoom The maximum zoom level to display the marker.
  406. */
  407. MarkerManager.prototype.addMarker = function (marker, minZoom, opt_maxZoom) {
  408. var maxZoom = this.getOptMaxZoom_(opt_maxZoom);
  409. this.addMarkerBatch_(marker, minZoom, maxZoom);
  410. var gridPoint = this.getTilePoint_(marker.getPosition(), this.mapZoom_, new google.maps.Size(0, 0, 0, 0));
  411. if (this.isGridPointVisible_(gridPoint) &&
  412. minZoom <= this.shownBounds_.z &&
  413. this.shownBounds_.z <= maxZoom) {
  414. this.addOverlay_(marker);
  415. this.notifyListeners_();
  416. }
  417. this.numMarkers_[minZoom]++;
  418. };
  419. /**
  420. * Helper class to create a bounds of INT ranges.
  421. * @param bounds Array.<Object.<string, number>> Bounds object.
  422. * @constructor
  423. */
  424. function GridBounds(bounds) {
  425. // [sw, ne]
  426. this.minX = Math.min(bounds[0].x, bounds[1].x);
  427. this.maxX = Math.max(bounds[0].x, bounds[1].x);
  428. this.minY = Math.min(bounds[0].y, bounds[1].y);
  429. this.maxY = Math.max(bounds[0].y, bounds[1].y);
  430. }
  431. /**
  432. * Returns true if this bounds equal the given bounds.
  433. * @param {GridBounds} gridBounds GridBounds The bounds to test.
  434. * @return {Boolean} This Bounds equals the given GridBounds.
  435. */
  436. GridBounds.prototype.equals = function (gridBounds) {
  437. if (this.maxX === gridBounds.maxX && this.maxY === gridBounds.maxY && this.minX === gridBounds.minX && this.minY === gridBounds.minY) {
  438. return true;
  439. } else {
  440. return false;
  441. }
  442. };
  443. /**
  444. * Returns true if this bounds (inclusively) contains the given point.
  445. * @param {Point} point The point to test.
  446. * @return {Boolean} This Bounds contains the given Point.
  447. */
  448. GridBounds.prototype.containsPoint = function (point) {
  449. var outer = this;
  450. return (outer.minX <= point.x && outer.maxX >= point.x && outer.minY <= point.y && outer.maxY >= point.y);
  451. };
  452. /**
  453. * Get a cell in the grid, creating it first if necessary.
  454. *
  455. * Optimization candidate
  456. *
  457. * @param {Number} x The x coordinate of the cell.
  458. * @param {Number} y The y coordinate of the cell.
  459. * @param {Number} z The z coordinate of the cell.
  460. * @return {Array} The cell in the array.
  461. */
  462. MarkerManager.prototype.getGridCellCreate_ = function (x, y, z) {
  463. var grid = this.grid_[z];
  464. if (x < 0) {
  465. x += this.gridWidth_[z];
  466. }
  467. var gridCol = grid[x];
  468. if (!gridCol) {
  469. gridCol = grid[x] = [];
  470. return (gridCol[y] = []);
  471. }
  472. var gridCell = gridCol[y];
  473. if (!gridCell) {
  474. return (gridCol[y] = []);
  475. }
  476. return gridCell;
  477. };
  478. /**
  479. * Get a cell in the grid, returning undefined if it does not exist.
  480. *
  481. * NOTE: Optimized for speed -- otherwise could combine with getGridCellCreate_.
  482. *
  483. * @param {Number} x The x coordinate of the cell.
  484. * @param {Number} y The y coordinate of the cell.
  485. * @param {Number} z The z coordinate of the cell.
  486. * @return {Array} The cell in the array.
  487. */
  488. MarkerManager.prototype.getGridCellNoCreate_ = function (x, y, z) {
  489. var grid = this.grid_[z];
  490. if (x < 0) {
  491. x += this.gridWidth_[z];
  492. }
  493. var gridCol = grid[x];
  494. return gridCol ? gridCol[y] : undefined;
  495. };
  496. /**
  497. * Turns at geographical bounds into a grid-space bounds.
  498. *
  499. * @param {LatLngBounds} bounds The geographical bounds.
  500. * @param {Number} zoom The zoom level of the bounds.
  501. * @param {google.maps.Size} swPadding The padding in pixels to extend beyond the
  502. * given bounds.
  503. * @param {google.maps.Size} nePadding The padding in pixels to extend beyond the
  504. * given bounds.
  505. * @return {GridBounds} The bounds in grid space.
  506. */
  507. MarkerManager.prototype.getGridBounds_ = function (bounds, zoom, swPadding, nePadding) {
  508. zoom = Math.min(zoom, this.maxZoom_);
  509. var bl = bounds.getSouthWest();
  510. var tr = bounds.getNorthEast();
  511. var sw = this.getTilePoint_(bl, zoom, swPadding);
  512. var ne = this.getTilePoint_(tr, zoom, nePadding);
  513. var gw = this.gridWidth_[zoom];
  514. // Crossing the prime meridian requires correction of bounds.
  515. if (tr.lng() < bl.lng() || ne.x < sw.x) {
  516. sw.x -= gw;
  517. }
  518. if (ne.x - sw.x + 1 >= gw) {
  519. // Computed grid bounds are larger than the world; truncate.
  520. sw.x = 0;
  521. ne.x = gw - 1;
  522. }
  523. var gridBounds = new GridBounds([sw, ne]);
  524. gridBounds.z = zoom;
  525. return gridBounds;
  526. };
  527. /**
  528. * Gets the grid-space bounds for the current map viewport.
  529. *
  530. * @return {Bounds} The bounds in grid space.
  531. */
  532. MarkerManager.prototype.getMapGridBounds_ = function () {
  533. return this.getGridBounds_(this.map_.getBounds(), this.mapZoom_, this.swPadding_, this.nePadding_);
  534. };
  535. /**
  536. * Event listener for map:movend.
  537. * NOTE: Use a timeout so that the user is not blocked
  538. * from moving the map.
  539. *
  540. * Removed this because a a lack of a scopy override/callback function on events.
  541. */
  542. MarkerManager.prototype.onMapMoveEnd_ = function () {
  543. this.objectSetTimeout_(this, this.updateMarkers_, 0);
  544. };
  545. /**
  546. * Call a function or evaluate an expression after a specified number of
  547. * milliseconds.
  548. *
  549. * Equivalent to the standard window.setTimeout function, but the given
  550. * function executes as a method of this instance. So the function passed to
  551. * objectSetTimeout can contain references to this.
  552. * objectSetTimeout(this, function () { alert(this.x) }, 1000);
  553. *
  554. * @param {Object} object The target object.
  555. * @param {Function} command The command to run.
  556. * @param {Number} milliseconds The delay.
  557. * @return {Boolean} Success.
  558. */
  559. MarkerManager.prototype.objectSetTimeout_ = function (object, command, milliseconds) {
  560. return window.setTimeout(function () {
  561. command.call(object);
  562. }, milliseconds);
  563. };
  564. /**
  565. * Is this layer visible?
  566. *
  567. * Returns visibility setting
  568. *
  569. * @return {Boolean} Visible
  570. */
  571. MarkerManager.prototype.visible = function () {
  572. return this.show_ ? true : false;
  573. };
  574. /**
  575. * Returns true if the manager is hidden.
  576. * Otherwise returns false.
  577. * @return {Boolean} Hidden
  578. */
  579. MarkerManager.prototype.isHidden = function () {
  580. return !this.show_;
  581. };
  582. /**
  583. * Shows the manager if it's currently hidden.
  584. */
  585. MarkerManager.prototype.show = function () {
  586. this.show_ = true;
  587. this.refresh();
  588. };
  589. /**
  590. * Hides the manager if it's currently visible
  591. */
  592. MarkerManager.prototype.hide = function () {
  593. this.show_ = false;
  594. this.refresh();
  595. };
  596. /**
  597. * Toggles the visibility of the manager.
  598. */
  599. MarkerManager.prototype.toggle = function () {
  600. this.show_ = !this.show_;
  601. this.refresh();
  602. };
  603. /**
  604. * Refresh forces the marker-manager into a good state.
  605. * <ol>
  606. * <li>If never before initialized, shows all the markers.</li>
  607. * <li>If previously initialized, removes and re-adds all markers.</li>
  608. * </ol>
  609. */
  610. MarkerManager.prototype.refresh = function () {
  611. if (this.shownMarkers_ > 0) {
  612. this.processAll_(this.shownBounds_, this.removeOverlay_);
  613. }
  614. // An extra check on this.show_ to increase performance (no need to processAll_)
  615. if (this.show_) {
  616. this.processAll_(this.shownBounds_, this.addOverlay_);
  617. }
  618. this.notifyListeners_();
  619. };
  620. /**
  621. * After the viewport may have changed, add or remove markers as needed.
  622. */
  623. MarkerManager.prototype.updateMarkers_ = function () {
  624. this.mapZoom_ = this.map_.getZoom();
  625. var newBounds = this.getMapGridBounds_();
  626. // If the move does not include new grid sections,
  627. // we have no work to do:
  628. if (newBounds.equals(this.shownBounds_) && newBounds.z === this.shownBounds_.z) {
  629. return;
  630. }
  631. if (newBounds.z !== this.shownBounds_.z) {
  632. this.processAll_(this.shownBounds_, this.removeOverlay_);
  633. if (this.show_) { // performance
  634. this.processAll_(newBounds, this.addOverlay_);
  635. }
  636. } else {
  637. // Remove markers:
  638. this.rectangleDiff_(this.shownBounds_, newBounds, this.removeCellMarkers_);
  639. // Add markers:
  640. if (this.show_) { // performance
  641. this.rectangleDiff_(newBounds, this.shownBounds_, this.addCellMarkers_);
  642. }
  643. }
  644. this.shownBounds_ = newBounds;
  645. this.notifyListeners_();
  646. };
  647. /**
  648. * Notify listeners when the state of what is displayed changes.
  649. */
  650. MarkerManager.prototype.notifyListeners_ = function () {
  651. google.maps.event.trigger(this, 'changed', this.shownBounds_, this.shownMarkers_);
  652. };
  653. /**
  654. * Process all markers in the bounds provided, using a callback.
  655. *
  656. * @param {Bounds} bounds The bounds in grid space.
  657. * @param {Function} callback The function to call for each marker.
  658. */
  659. MarkerManager.prototype.processAll_ = function (bounds, callback) {
  660. for (var x = bounds.minX; x <= bounds.maxX; x++) {
  661. for (var y = bounds.minY; y <= bounds.maxY; y++) {
  662. this.processCellMarkers_(x, y, bounds.z, callback);
  663. }
  664. }
  665. };
  666. /**
  667. * Process all markers in the grid cell, using a callback.
  668. *
  669. * @param {Number} x The x coordinate of the cell.
  670. * @param {Number} y The y coordinate of the cell.
  671. * @param {Number} z The z coordinate of the cell.
  672. * @param {Function} callback The function to call for each marker.
  673. */
  674. MarkerManager.prototype.processCellMarkers_ = function (x, y, z, callback) {
  675. var cell = this.getGridCellNoCreate_(x, y, z);
  676. if (cell) {
  677. for (var i = cell.length - 1; i >= 0; i--) {
  678. callback(cell[i]);
  679. }
  680. }
  681. };
  682. /**
  683. * Remove all markers in a grid cell.
  684. *
  685. * @param {Number} x The x coordinate of the cell.
  686. * @param {Number} y The y coordinate of the cell.
  687. * @param {Number} z The z coordinate of the cell.
  688. */
  689. MarkerManager.prototype.removeCellMarkers_ = function (x, y, z) {
  690. this.processCellMarkers_(x, y, z, this.removeOverlay_);
  691. };
  692. /**
  693. * Add all markers in a grid cell.
  694. *
  695. * @param {Number} x The x coordinate of the cell.
  696. * @param {Number} y The y coordinate of the cell.
  697. * @param {Number} z The z coordinate of the cell.
  698. */
  699. MarkerManager.prototype.addCellMarkers_ = function (x, y, z) {
  700. this.processCellMarkers_(x, y, z, this.addOverlay_);
  701. };
  702. /**
  703. * Use the rectangleDiffCoords_ function to process all grid cells
  704. * that are in bounds1 but not bounds2, using a callback, and using
  705. * the current MarkerManager object as the instance.
  706. *
  707. * Pass the z parameter to the callback in addition to x and y.
  708. *
  709. * @param {Bounds} bounds1 The bounds of all points we may process.
  710. * @param {Bounds} bounds2 The bounds of points to exclude.
  711. * @param {Function} callback The callback function to call
  712. * for each grid coordinate (x, y, z).
  713. */
  714. MarkerManager.prototype.rectangleDiff_ = function (bounds1, bounds2, callback) {
  715. var me = this;
  716. me.rectangleDiffCoords_(bounds1, bounds2, function (x, y) {
  717. callback.apply(me, [x, y, bounds1.z]);
  718. });
  719. };
  720. /**
  721. * Calls the function for all points in bounds1, not in bounds2
  722. *
  723. * @param {Bounds} bounds1 The bounds of all points we may process.
  724. * @param {Bounds} bounds2 The bounds of points to exclude.
  725. * @param {Function} callback The callback function to call
  726. * for each grid coordinate.
  727. */
  728. MarkerManager.prototype.rectangleDiffCoords_ = function (bounds1, bounds2, callback) {
  729. var minX1 = bounds1.minX;
  730. var minY1 = bounds1.minY;
  731. var maxX1 = bounds1.maxX;
  732. var maxY1 = bounds1.maxY;
  733. var minX2 = bounds2.minX;
  734. var minY2 = bounds2.minY;
  735. var maxX2 = bounds2.maxX;
  736. var maxY2 = bounds2.maxY;
  737. var x, y;
  738. for (x = minX1; x <= maxX1; x++) { // All x in R1
  739. // All above:
  740. for (y = minY1; y <= maxY1 && y < minY2; y++) { // y in R1 above R2
  741. callback(x, y);
  742. }
  743. // All below:
  744. for (y = Math.max(maxY2 + 1, minY1); // y in R1 below R2
  745. y <= maxY1; y++) {
  746. callback(x, y);
  747. }
  748. }
  749. for (y = Math.max(minY1, minY2);
  750. y <= Math.min(maxY1, maxY2); y++) { // All y in R2 and in R1
  751. // Strictly left:
  752. for (x = Math.min(maxX1 + 1, minX2) - 1;
  753. x >= minX1; x--) { // x in R1 left of R2
  754. callback(x, y);
  755. }
  756. // Strictly right:
  757. for (x = Math.max(minX1, maxX2 + 1); // x in R1 right of R2
  758. x <= maxX1; x++) {
  759. callback(x, y);
  760. }
  761. }
  762. };
  763. /**
  764. * Removes value from array. O(N).
  765. *
  766. * @param {Array} array The array to modify.
  767. * @param {any} value The value to remove.
  768. * @param {Boolean} opt_notype Flag to disable type checking in equality.
  769. * @return {Number} The number of instances of value that were removed.
  770. */
  771. MarkerManager.prototype.removeFromArray_ = function (array, value, opt_notype) {
  772. var shift = 0;
  773. for (var i = 0; i < array.length; ++i) {
  774. if (array[i] === value || (opt_notype && array[i] === value)) {
  775. array.splice(i--, 1);
  776. shift++;
  777. }
  778. }
  779. return shift;
  780. };
  781. /**
  782. * Projection overlay helper. Helps in calculating
  783. * that markers get into the right grid.
  784. * @constructor
  785. * @param {Map} map The map to manage.
  786. **/
  787. function ProjectionHelperOverlay(map) {
  788. this.setMap(map);
  789. var TILEFACTOR = 8;
  790. var TILESIDE = 1 << TILEFACTOR;
  791. var RADIUS = 7;
  792. this._map = map;
  793. this._zoom = -1;
  794. this._X0 =
  795. this._Y0 =
  796. this._X1 =
  797. this._Y1 = -1;
  798. }
  799. ProjectionHelperOverlay.prototype = new google.maps.OverlayView();
  800. /**
  801. * Helper function to convert Lng to X
  802. * @private
  803. * @param {float} lng
  804. **/
  805. ProjectionHelperOverlay.prototype.LngToX_ = function (lng) {
  806. return (1 + lng / 180);
  807. };
  808. /**
  809. * Helper function to convert Lat to Y
  810. * @private
  811. * @param {float} lat
  812. **/
  813. ProjectionHelperOverlay.prototype.LatToY_ = function (lat) {
  814. var sinofphi = Math.sin(lat * Math.PI / 180);
  815. return (1 - 0.5 / Math.PI * Math.log((1 + sinofphi) / (1 - sinofphi)));
  816. };
  817. /**
  818. * Old school LatLngToPixel
  819. * @param {LatLng} latlng google.maps.LatLng object
  820. * @param {Number} zoom Zoom level
  821. * @return {position} {x: pixelPositionX, y: pixelPositionY}
  822. **/
  823. ProjectionHelperOverlay.prototype.LatLngToPixel = function (latlng, zoom) {
  824. var map = this._map;
  825. var div = this.getProjection().fromLatLngToDivPixel(latlng);
  826. var abs = {x: ~~(0.5 + this.LngToX_(latlng.lng()) * (2 << (zoom + 6))), y: ~~(0.5 + this.LatToY_(latlng.lat()) * (2 << (zoom + 6)))};
  827. return abs;
  828. };
  829. /**
  830. * Draw function only triggers a ready event for
  831. * MarkerManager to know projection can proceed to
  832. * initialize.
  833. */
  834. ProjectionHelperOverlay.prototype.draw = function () {
  835. if (!this.ready) {
  836. this.ready = true;
  837. google.maps.event.trigger(this, 'ready');
  838. }
  839. };