PageSourceSearch

https://www.andersonofstjoseph.com/wp-content/plugins/wp-google-maps-pro/lib/CanvasLayer.js?ver=7.0.6

js andersonofstjoseph.com collected 2026-09-28 15:31:34 UTC 15,501 bytes, 548 lines download raw bytes

1/**
2 * Copyright 2012 Google Inc. All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/**
18 * @fileoverview Extends OverlayView to provide a canvas "Layer".
19 * @author Brendan Kenny
20 */
21
22/**
23 * A map layer that provides a canvas over the slippy map and a callback
24 * system for efficient animation. Requires canvas and CSS 2D transform
25 * support.
26 * @constructor
27 * @extends google.maps.OverlayView
28 * @param {CanvasLayerOptions=} opt_options Options to set in this CanvasLayer.
29 */
30function CanvasLayer(opt_options) {
31  /**
32   * If true, canvas is in a map pane and the OverlayView is fully functional.
33   * See google.maps.OverlayView.onAdd for more information.
34   * @type {boolean}
35   * @private
36   */
37  this.isAdded_ = false;
38
39  /**
40   * If true, each update will immediately schedule the next.
41   * @type {boolean}
42   * @private
43   */
44  this.isAnimated_ = false;
45
46  /**
47   * The name of the MapPane in which this layer will be displayed.
48   * @type {string}
49   * @private
50   */
51  this.paneName_ = CanvasLayer.DEFAULT_PANE_NAME_;
52
53  /**
54   * A user-supplied function called whenever an update is required. Null or
55   * undefined if a callback is not provided.
56   * @type {?function=}
57   * @private
58   */
59  this.updateHandler_ = null;
60
61  /**
62   * A user-supplied function called whenever an update is required and the
63   * map has been resized since the last update. Null or undefined if a
64   * callback is not provided.
65   * @type {?function}
66   * @private
67   */
68  this.resizeHandler_ = null;
69
70  /**
71   * The LatLng coordinate of the top left of the current view of the map. Will
72   * be null when this.isAdded_ is false.
73   * @type {google.maps.LatLng}
74   * @private
75   */
76  this.topLeft_ = null;
77
78  /**
79   * The map-pan event listener. Will be null when this.isAdded_ is false. Will
80   * be null when this.isAdded_ is false.
81   * @type {?function}
82   * @private
83   */
84  this.centerListener_ = null;
85
86  /**
87   * The map-resize event listener. Will be null when this.isAdded_ is false.
88   * @type {?function}
89   * @private
90   */
91  this.resizeListener_ = null;
92
93  /**
94   * If true, the map size has changed and this.resizeHandler_ must be called
95   * on the next update.
96   * @type {boolean}
97   * @private
98   */
99  this.needsResize_ = true;
100
101  /**
102   * A browser-defined id for the currently requested callback. Null when no
103   * callback is queued.
104   * @type {?number}
105   * @private
106   */
107  this.requestAnimationFrameId_ = null;
108
109  var canvas = document.createElement('canvas');
110  canvas.style.position = 'absolute';
111  canvas.style.top = 0;
112  canvas.style.left = 0;
113  canvas.style.pointerEvents = 'none';
114
115  /**
116   * The canvas element.
117   * @type {!HTMLCanvasElement}
118   */
119  this.canvas = canvas;
120
121  /**
122   * The CSS width of the canvas, which may be different than the width of the
123   * backing store.
124   * @private {number}
125   */
126  this.canvasCssWidth_ = 300;
127
128  /**
129   * The CSS height of the canvas, which may be different than the height of
130   * the backing store.
131   * @private {number}
132   */
133  this.canvasCssHeight_ = 150;
134
135  /**
136   * A value for scaling the CanvasLayer resolution relative to the CanvasLayer
137   * display size.
138   * @private {number}
139   */
140  this.resolutionScale_ = 1;
141
142  /**
143   * Simple bind for functions with no args for bind-less browsers (Safari).
144   * @param {Object} thisArg The this value used for the target function.
145   * @param {function} func The function to be bound.
146   */
147  function simpleBindShim(thisArg, func) {
148    return function() { func.apply(thisArg); };
149  }
150
151  /**
152   * A reference to this.repositionCanvas_ with this bound as its this value.
153   * @type {function}
154   * @private
155   */
156  this.repositionFunction_ = simpleBindShim(this, this.repositionCanvas_);
157
158  /**
159   * A reference to this.resize_ with this bound as its this value.
160   * @type {function}
161   * @private
162   */
163  this.resizeFunction_ = simpleBindShim(this, this.resize_);
164
165  /**
166   * A reference to this.update_ with this bound as its this value.
167   * @type {function}
168   * @private
169   */
170  this.requestUpdateFunction_ = simpleBindShim(this, this.update_);
171
172  // set provided options, if any
173  if (opt_options) {
174    this.setOptions(opt_options);
175  }
176}
177
178if(window.google)
179	CanvasLayer.prototype = new google.maps.OverlayView();
180
181/**
182 * The default MapPane to contain the canvas.
183 * @type {string}
184 * @const
185 * @private
186 */
187CanvasLayer.DEFAULT_PANE_NAME_ = 'overlayLayer';
188
189/**
190 * Transform CSS property name, with vendor prefix if required. If browser
191 * does not support transforms, property will be ignored.
192 * @type {string}
193 * @const
194 * @private
195 */
196CanvasLayer.CSS_TRANSFORM_ = (function() {
197  var div = document.createElement('div');
198  var transformProps = [
199    'transform',
200    'WebkitTransform',
201    'MozTransform',
202    'OTransform',
203    'msTransform'
204  ];
205  for (var i = 0; i < transformProps.length; i++) {
206    var prop = transformProps[i];
207    if (div.style[prop] !== undefined) {
208      return prop;
209    }
210  }
211
212  // return unprefixed version by default
213  return transformProps[0];
214})();
215
216/**
217 * The requestAnimationFrame function, with vendor-prefixed or setTimeout-based
218 * fallbacks. MUST be called with window as thisArg.
219 * @type {function}
220 * @param {function} callback The function to add to the frame request queue.
221 * @return {number} The browser-defined id for the requested callback.
222 * @private
223 */
224CanvasLayer.prototype.requestAnimFrame_ =
225    window.requestAnimationFrame ||
226    window.webkitRequestAnimationFrame ||
227    window.mozRequestAnimationFrame ||
228    window.oRequestAnimationFrame ||
229    window.msRequestAnimationFrame ||
230    function(callback) {
231      return window.setTimeout(callback, 1000 / 60);
232    };
233
234/**
235 * The cancelAnimationFrame function, with vendor-prefixed fallback. Does not
236 * fall back to clearTimeout as some platforms implement requestAnimationFrame
237 * but not cancelAnimationFrame, and the cost is an extra frame on onRemove.
238 * MUST be called with window as thisArg.
239 * @type {function}
240 * @param {number=} requestId The id of the frame request to cancel.
241 * @private
242 */
243CanvasLayer.prototype.cancelAnimFrame_ =
244    window.cancelAnimationFrame ||
245    window.webkitCancelAnimationFrame ||
246    window.mozCancelAnimationFrame ||
247    window.oCancelAnimationFrame ||
248    window.msCancelAnimationFrame ||
249    function(requestId) {};
250
251/**
252 * Sets any options provided. See CanvasLayerOptions for more information.
253 * @param {CanvasLayerOptions} options The options to set.
254 */
255CanvasLayer.prototype.setOptions = function(options) {
256  if (options.animate !== undefined) {
257    this.setAnimate(options.animate);
258  }
259
260  if (options.paneName !== undefined) {
261    this.setPaneName(options.paneName);
262  }
263
264  if (options.updateHandler !== undefined) {
265    this.setUpdateHandler(options.updateHandler);
266  }
267
268  if (options.resizeHandler !== undefined) {
269    this.setResizeHandler(options.resizeHandler);
270  }
271
272  if (options.resolutionScale !== undefined) {
273    this.setResolutionScale(options.resolutionScale);
274  }
275
276  if (options.map !== undefined) {
277    this.setMap(options.map);
278  }
279};
280
281/**
282 * Set the animated state of the layer. If true, updateHandler will be called
283 * repeatedly, once per frame. If false, updateHandler will only be called when
284 * a map property changes that could require the canvas content to be redrawn.
285 * @param {boolean} animate Whether the canvas is animated.
286 */
287CanvasLayer.prototype.setAnimate = function(animate) {
288  this.isAnimated_ = !!animate;
289
290  if (this.isAnimated_) {
291    this.scheduleUpdate();
292  }
293};
294
295/**
296 * @return {boolean} Whether the canvas is animated.
297 */
298CanvasLayer.prototype.isAnimated = function() {
299  return this.isAnimated_;
300};
301
302/**
303 * Set the MapPane in which this layer will be displayed, by name. See
304 * {@code google.maps.MapPanes} for the panes available.
305 * @param {string} paneName The name of the desired MapPane.
306 */
307CanvasLayer.prototype.setPaneName = function(paneName) {
308  this.paneName_ = paneName;
309
310  this.setPane_();
311};
312
313/**
314 * @return {string} The name of the current container pane.
315 */
316CanvasLayer.prototype.getPaneName = function() {
317  return this.paneName_;
318};
319
320/**
321 * Adds the canvas to the specified container pane. Since this is guaranteed to
322 * execute only after onAdd is called, this is when paneName's existence is
323 * checked (and an error is thrown if it doesn't exist).
324 * @private
325 */
326CanvasLayer.prototype.setPane_ = function() {
327  if (!this.isAdded_) {
328    return;
329  }
330
331  // onAdd has been called, so panes can be used
332  var panes = this.getPanes();
333  if (!panes[this.paneName_]) {
334    throw new Error('"' + this.paneName_ + '" is not a valid MapPane name.');
335  }
336
337  panes[this.paneName_].appendChild(this.canvas);
338};
339
340/**
341 * Set a function that will be called whenever the parent map and the overlay's
342 * canvas have been resized. If opt_resizeHandler is null or unspecified, any
343 * existing callback is removed.
344 * @param {?function=} opt_resizeHandler The resize callback function.
345 */
346CanvasLayer.prototype.setResizeHandler = function(opt_resizeHandler) {
347  this.resizeHandler_ = opt_resizeHandler;
348};
349
350/**
351 * Sets a value for scaling the canvas resolution relative to the canvas
352 * display size. This can be used to save computation by scaling the backing
353 * buffer down, or to support high DPI devices by scaling it up (by e.g.
354 * window.devicePixelRatio).
355 * @param {number} scale
356 */
357CanvasLayer.prototype.setResolutionScale = function(scale) {
358  if (typeof scale === 'number') {
359    this.resolutionScale_ = scale;
360    this.resize_();
361  }
362};
363
364/**
365 * Set a function that will be called when a repaint of the canvas is required.
366 * If opt_updateHandler is null or unspecified, any existing callback is
367 * removed.
368 * @param {?function=} opt_updateHandler The update callback function.
369 */
370CanvasLayer.prototype.setUpdateHandler = function(opt_updateHandler) {
371  this.updateHandler_ = opt_updateHandler;
372};
373
374/**
375 * @inheritDoc
376 */
377CanvasLayer.prototype.onAdd = function() {
378  if (this.isAdded_) {
379    return;
380  }
381
382  this.isAdded_ = true;
383  this.setPane_();
384
385  this.resizeListener_ = google.maps.event.addListener(this.getMap(),
386      'resize', this.resizeFunction_);
387  this.centerListener_ = google.maps.event.addListener(this.getMap(),
388      'center_changed', this.repositionFunction_);
389
390  this.resize_();
391  this.repositionCanvas_();
392};
393
394/**
395 * @inheritDoc
396 */
397CanvasLayer.prototype.onRemove = function() {
398  if (!this.isAdded_) {
399    return;
400  }
401
402  this.isAdded_ = false;
403  this.topLeft_ = null;
404
405  // remove canvas and listeners for pan and resize from map
406  this.canvas.parentElement.removeChild(this.canvas);
407  if (this.centerListener_) {
408    google.maps.event.removeListener(this.centerListener_);
409    this.centerListener_ = null;
410  }
411  if (this.resizeListener_) {
412    google.maps.event.removeListener(this.resizeListener_);
413    this.resizeListener_ = null;
414  }
415
416  // cease canvas update callbacks
417  if (this.requestAnimationFrameId_) {
418    this.cancelAnimFrame_.call(window, this.requestAnimationFrameId_);
419    this.requestAnimationFrameId_ = null;
420  }
421};
422
423/**
424 * The internal callback for resize events that resizes the canvas to keep the
425 * map properly covered.
426 * @private
427 */
428CanvasLayer.prototype.resize_ = function() {
429  if (!this.isAdded_) {
430    return;
431  }
432
433  var map = this.getMap();
434  var mapWidth = map.getDiv().getElementsByTagName('div')[0].offsetWidth;
435  var mapHeight = map.getDiv().getElementsByTagName('div')[0].offsetHeight;
436
437  var newWidth = mapWidth * this.resolutionScale_;
438  var newHeight = mapHeight * this.resolutionScale_;
439  var oldWidth = this.canvas.width;
440  var oldHeight = this.canvas.height;
441
442  // resizing may allocate a new back buffer, so do so conservatively
443  if (oldWidth !== newWidth || oldHeight !== newHeight) {
444    this.canvas.width = newWidth;
445    this.canvas.height = newHeight;
446
447    this.needsResize_ = true;
448    this.scheduleUpdate();
449  }
450
451  // reset styling if new sizes don't match; resize of data not needed
452  if (this.canvasCssWidth_ !== mapWidth ||
453      this.canvasCssHeight_ !== mapHeight) {
454    this.canvasCssWidth_ = mapWidth;
455    this.canvasCssHeight_ = mapHeight;
456    this.canvas.style.width = mapWidth + 'px';
457    this.canvas.style.height = mapHeight + 'px';
458  }
459};
460
461/**
462 * @inheritDoc
463 */
464CanvasLayer.prototype.draw = function() {
465  this.repositionCanvas_();
466};
467
468/**
469 * Internal callback for map view changes. Since the Maps API moves the overlay
470 * along with the map, this function calculates the opposite translation to
471 * keep the canvas in place.
472 * @private
473 */
474CanvasLayer.prototype.repositionCanvas_ = function() {
475  // TODO(bckenny): *should* only be executed on RAF, but in current browsers
476  //     this causes noticeable hitches in map and overlay relative
477  //     positioning.
478
479  var map = this.getMap();
480
481  // topLeft can't be calculated from map.getBounds(), because bounds are
482  // clamped to -180 and 180 when completely zoomed out. Instead, calculate
483  // left as an offset from the center, which is an unwrapped LatLng.
484  var top = map.getBounds().getNorthEast().lat();
485  var center = map.getCenter();
486  var scale = Math.pow(2, map.getZoom());
487  var left = center.lng() - (this.canvasCssWidth_ * 180) / (256 * scale);
488  this.topLeft_ = new google.maps.LatLng(top, left);
489
490  // Canvas position relative to draggable map's container depends on
491  // overlayView's projection, not the map's. Have to use the center of the
492  // map for this, not the top left, for the same reason as above.
493  var projection = this.getProjection();
494  var divCenter = projection.fromLatLngToDivPixel(center);
495  var offsetX = -Math.round(this.canvasCssWidth_ / 2 - divCenter.x);
496  var offsetY = -Math.round(this.canvasCssHeight_ / 2 - divCenter.y);
497  this.canvas.style[CanvasLayer.CSS_TRANSFORM_] = 'translate(' +
498      offsetX + 'px,' + offsetY + 'px)';
499
500  this.scheduleUpdate();
501};
502
503/**
504 * Internal callback that serves as main animation scheduler via
505 * requestAnimationFrame. Calls resize and update callbacks if set, and
506 * schedules the next frame if overlay is animated.
507 * @private
508 */
509CanvasLayer.prototype.update_ = function() {
510  this.requestAnimationFrameId_ = null;
511
512  if (!this.isAdded_) {
513    return;
514  }
515
516  if (this.isAnimated_) {
517    this.scheduleUpdate();
518  }
519
520  if (this.needsResize_ && this.resizeHandler_) {
521    this.needsResize_ = false;
522    this.resizeHandler_();
523  }
524
525  if (this.updateHandler_) {
526    this.updateHandler_();
527  }
528};
529
530/**
531 * A convenience method to get the current LatLng coordinate of the top left of
532 * the current view of the map.
533 * @return {google.maps.LatLng} The top left coordinate.
534 */
535CanvasLayer.prototype.getTopLeft = function() {
536  return this.topLeft_;
537};
538
539/**
540 * Schedule a requestAnimationFrame callback to updateHandler. If one is
541 * already scheduled, there is no effect.
542 */
543CanvasLayer.prototype.scheduleUpdate = function() {
544  if (this.isAdded_ && !this.requestAnimationFrameId_) {
545    this.requestAnimationFrameId_ =
546        this.requestAnimFrame_.call(window, this.requestUpdateFunction_);
547  }
548};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.