File Manager
/**
* Copyright 2012 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Extends OverlayView to provide a canvas "Layer".
* @author Brendan Kenny
*/
/**
* A map layer that provides a canvas over the slippy map and a callback
* system for efficient animation. Requires canvas and CSS 2D transform
* support.
* @constructor
* @extends google.maps.OverlayView
* @param {CanvasLayerOptions=} opt_options Options to set in this CanvasLayer.
*/
function CanvasLayer(opt_options) {
/**
* If true, canvas is in a map pane and the OverlayView is fully functional.
* See google.maps.OverlayView.onAdd for more information.
* @type {boolean}
* @private
*/
this.isAdded_ = false;
/**
* If true, each update will immediately schedule the next.
* @type {boolean}
* @private
*/
this.isAnimated_ = false;
/**
* The name of the MapPane in which this layer will be displayed.
* @type {string}
* @private
*/
this.paneName_ = CanvasLayer.DEFAULT_PANE_NAME_;
/**
* A user-supplied function called whenever an update is required. Null or
* undefined if a callback is not provided.
* @type {?function=}
* @private
*/
this.updateHandler_ = null;
/**
* A user-supplied function called whenever an update is required and the
* map has been resized since the last update. Null or undefined if a
* callback is not provided.
* @type {?function}
* @private
*/
this.resizeHandler_ = null;
/**
* The LatLng coordinate of the top left of the current view of the map. Will
* be null when this.isAdded_ is false.
* @type {google.maps.LatLng}
* @private
*/
this.topLeft_ = null;
/**
* The map-pan event listener. Will be null when this.isAdded_ is false. Will
* be null when this.isAdded_ is false.
* @type {?function}
* @private
*/
this.centerListener_ = null;
/**
* The map-resize event listener. Will be null when this.isAdded_ is false.
* @type {?function}
* @private
*/
this.resizeListener_ = null;
/**
* If true, the map size has changed and this.resizeHandler_ must be called
* on the next update.
* @type {boolean}
* @private
*/
this.needsResize_ = true;
/**
* A browser-defined id for the currently requested callback. Null when no
* callback is queued.
* @type {?number}
* @private
*/
this.requestAnimationFrameId_ = null;
var canvas = document.createElement('canvas');
canvas.style.position = 'absolute';
canvas.style.top = 0;
canvas.style.left = 0;
canvas.style.pointerEvents = 'none';
/**
* The canvas element.
* @type {!HTMLCanvasElement}
*/
this.canvas = canvas;
/**
* The CSS width of the canvas, which may be different than the width of the
* backing store.
* @private {number}
*/
this.canvasCssWidth_ = 300;
/**
* The CSS height of the canvas, which may be different than the height of
* the backing store.
* @private {number}
*/
this.canvasCssHeight_ = 150;
/**
* A value for scaling the CanvasLayer resolution relative to the CanvasLayer
* display size.
* @private {number}
*/
this.resolutionScale_ = 1;
/**
* Simple bind for functions with no args for bind-less browsers (Safari).
* @param {Object} thisArg The this value used for the target function.
* @param {function} func The function to be bound.
*/
function simpleBindShim(thisArg, func) {
return function() { func.apply(thisArg); };
}
/**
* A reference to this.repositionCanvas_ with this bound as its this value.
* @type {function}
* @private
*/
this.repositionFunction_ = simpleBindShim(this, this.repositionCanvas_);
/**
* A reference to this.resize_ with this bound as its this value.
* @type {function}
* @private
*/
this.resizeFunction_ = simpleBindShim(this, this.resize_);
/**
* A reference to this.update_ with this bound as its this value.
* @type {function}
* @private
*/
this.requestUpdateFunction_ = simpleBindShim(this, this.update_);
// set provided options, if any
if (opt_options) {
this.setOptions(opt_options);
}
}
if(window.google)
CanvasLayer.prototype = new google.maps.OverlayView();
/**
* The default MapPane to contain the canvas.
* @type {string}
* @const
* @private
*/
CanvasLayer.DEFAULT_PANE_NAME_ = 'overlayLayer';
/**
* Transform CSS property name, with vendor prefix if required. If browser
* does not support transforms, property will be ignored.
* @type {string}
* @const
* @private
*/
CanvasLayer.CSS_TRANSFORM_ = (function() {
var div = document.createElement('div');
var transformProps = [
'transform',
'WebkitTransform',
'MozTransform',
'OTransform',
'msTransform'
];
for (var i = 0; i < transformProps.length; i++) {
var prop = transformProps[i];
if (div.style[prop] !== undefined) {
return prop;
}
}
// return unprefixed version by default
return transformProps[0];
})();
/**
* The requestAnimationFrame function, with vendor-prefixed or setTimeout-based
* fallbacks. MUST be called with window as thisArg.
* @type {function}
* @param {function} callback The function to add to the frame request queue.
* @return {number} The browser-defined id for the requested callback.
* @private
*/
CanvasLayer.prototype.requestAnimFrame_ =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
return window.setTimeout(callback, 1000 / 60);
};
/**
* The cancelAnimationFrame function, with vendor-prefixed fallback. Does not
* fall back to clearTimeout as some platforms implement requestAnimationFrame
* but not cancelAnimationFrame, and the cost is an extra frame on onRemove.
* MUST be called with window as thisArg.
* @type {function}
* @param {number=} requestId The id of the frame request to cancel.
* @private
*/
CanvasLayer.prototype.cancelAnimFrame_ =
window.cancelAnimationFrame ||
window.webkitCancelAnimationFrame ||
window.mozCancelAnimationFrame ||
window.oCancelAnimationFrame ||
window.msCancelAnimationFrame ||
function(requestId) {};
/**
* Sets any options provided. See CanvasLayerOptions for more information.
* @param {CanvasLayerOptions} options The options to set.
*/
CanvasLayer.prototype.setOptions = function(options) {
if (options.animate !== undefined) {
this.setAnimate(options.animate);
}
if (options.paneName !== undefined) {
this.setPaneName(options.paneName);
}
if (options.updateHandler !== undefined) {
this.setUpdateHandler(options.updateHandler);
}
if (options.resizeHandler !== undefined) {
this.setResizeHandler(options.resizeHandler);
}
if (options.resolutionScale !== undefined) {
this.setResolutionScale(options.resolutionScale);
}
if (options.map !== undefined) {
this.setMap(options.map);
}
};
/**
* Set the animated state of the layer. If true, updateHandler will be called
* repeatedly, once per frame. If false, updateHandler will only be called when
* a map property changes that could require the canvas content to be redrawn.
* @param {boolean} animate Whether the canvas is animated.
*/
CanvasLayer.prototype.setAnimate = function(animate) {
this.isAnimated_ = !!animate;
if (this.isAnimated_) {
this.scheduleUpdate();
}
};
/**
* @return {boolean} Whether the canvas is animated.
*/
CanvasLayer.prototype.isAnimated = function() {
return this.isAnimated_;
};
/**
* Set the MapPane in which this layer will be displayed, by name. See
* {@code google.maps.MapPanes} for the panes available.
* @param {string} paneName The name of the desired MapPane.
*/
CanvasLayer.prototype.setPaneName = function(paneName) {
this.paneName_ = paneName;
this.setPane_();
};
/**
* @return {string} The name of the current container pane.
*/
CanvasLayer.prototype.getPaneName = function() {
return this.paneName_;
};
/**
* Adds the canvas to the specified container pane. Since this is guaranteed to
* execute only after onAdd is called, this is when paneName's existence is
* checked (and an error is thrown if it doesn't exist).
* @private
*/
CanvasLayer.prototype.setPane_ = function() {
if (!this.isAdded_) {
return;
}
// onAdd has been called, so panes can be used
var panes = this.getPanes();
if (!panes[this.paneName_]) {
throw new Error('"' + this.paneName_ + '" is not a valid MapPane name.');
}
panes[this.paneName_].appendChild(this.canvas);
};
/**
* Set a function that will be called whenever the parent map and the overlay's
* canvas have been resized. If opt_resizeHandler is null or unspecified, any
* existing callback is removed.
* @param {?function=} opt_resizeHandler The resize callback function.
*/
CanvasLayer.prototype.setResizeHandler = function(opt_resizeHandler) {
this.resizeHandler_ = opt_resizeHandler;
};
/**
* Sets a value for scaling the canvas resolution relative to the canvas
* display size. This can be used to save computation by scaling the backing
* buffer down, or to support high DPI devices by scaling it up (by e.g.
* window.devicePixelRatio).
* @param {number} scale
*/
CanvasLayer.prototype.setResolutionScale = function(scale) {
if (typeof scale === 'number') {
this.resolutionScale_ = scale;
this.resize_();
}
};
/**
* Set a function that will be called when a repaint of the canvas is required.
* If opt_updateHandler is null or unspecified, any existing callback is
* removed.
* @param {?function=} opt_updateHandler The update callback function.
*/
CanvasLayer.prototype.setUpdateHandler = function(opt_updateHandler) {
this.updateHandler_ = opt_updateHandler;
};
/**
* @inheritDoc
*/
CanvasLayer.prototype.onAdd = function() {
if (this.isAdded_) {
return;
}
this.isAdded_ = true;
this.setPane_();
this.resizeListener_ = google.maps.event.addListener(this.getMap(),
'resize', this.resizeFunction_);
this.centerListener_ = google.maps.event.addListener(this.getMap(),
'center_changed', this.repositionFunction_);
this.resize_();
this.repositionCanvas_();
};
/**
* @inheritDoc
*/
CanvasLayer.prototype.onRemove = function() {
if (!this.isAdded_) {
return;
}
this.isAdded_ = false;
this.topLeft_ = null;
// remove canvas and listeners for pan and resize from map
this.canvas.parentElement.removeChild(this.canvas);
if (this.centerListener_) {
google.maps.event.removeListener(this.centerListener_);
this.centerListener_ = null;
}
if (this.resizeListener_) {
google.maps.event.removeListener(this.resizeListener_);
this.resizeListener_ = null;
}
// cease canvas update callbacks
if (this.requestAnimationFrameId_) {
this.cancelAnimFrame_.call(window, this.requestAnimationFrameId_);
this.requestAnimationFrameId_ = null;
}
};
/**
* The internal callback for resize events that resizes the canvas to keep the
* map properly covered.
* @private
*/
CanvasLayer.prototype.resize_ = function() {
if (!this.isAdded_) {
return;
}
var map = this.getMap();
var mapWidth = map.getDiv().getElementsByTagName('div')[0].offsetWidth;
var mapHeight = map.getDiv().getElementsByTagName('div')[0].offsetHeight;
var newWidth = mapWidth * this.resolutionScale_;
var newHeight = mapHeight * this.resolutionScale_;
var oldWidth = this.canvas.width;
var oldHeight = this.canvas.height;
// resizing may allocate a new back buffer, so do so conservatively
if (oldWidth !== newWidth || oldHeight !== newHeight) {
this.canvas.width = newWidth;
this.canvas.height = newHeight;
this.needsResize_ = true;
this.scheduleUpdate();
}
// reset styling if new sizes don't match; resize of data not needed
if (this.canvasCssWidth_ !== mapWidth ||
this.canvasCssHeight_ !== mapHeight) {
this.canvasCssWidth_ = mapWidth;
this.canvasCssHeight_ = mapHeight;
this.canvas.style.width = mapWidth + 'px';
this.canvas.style.height = mapHeight + 'px';
}
};
/**
* @inheritDoc
*/
CanvasLayer.prototype.draw = function() {
this.repositionCanvas_();
};
/**
* Internal callback for map view changes. Since the Maps API moves the overlay
* along with the map, this function calculates the opposite translation to
* keep the canvas in place.
* @private
*/
CanvasLayer.prototype.repositionCanvas_ = function() {
// TODO(bckenny): *should* only be executed on RAF, but in current browsers
// this causes noticeable hitches in map and overlay relative
// positioning.
var map = this.getMap();
// topLeft can't be calculated from map.getBounds(), because bounds are
// clamped to -180 and 180 when completely zoomed out. Instead, calculate
// left as an offset from the center, which is an unwrapped LatLng.
var top = map.getBounds().getNorthEast().lat();
var center = map.getCenter();
var scale = Math.pow(2, map.getZoom());
var left = center.lng() - (this.canvasCssWidth_ * 180) / (256 * scale);
this.topLeft_ = new google.maps.LatLng(top, left);
// Canvas position relative to draggable map's container depends on
// overlayView's projection, not the map's. Have to use the center of the
// map for this, not the top left, for the same reason as above.
var projection = this.getProjection();
var divCenter = projection.fromLatLngToDivPixel(center);
var offsetX = -Math.round(this.canvasCssWidth_ / 2 - divCenter.x);
var offsetY = -Math.round(this.canvasCssHeight_ / 2 - divCenter.y);
this.canvas.style[CanvasLayer.CSS_TRANSFORM_] = 'translate(' +
offsetX + 'px,' + offsetY + 'px)';
this.scheduleUpdate();
};
/**
* Internal callback that serves as main animation scheduler via
* requestAnimationFrame. Calls resize and update callbacks if set, and
* schedules the next frame if overlay is animated.
* @private
*/
CanvasLayer.prototype.update_ = function() {
this.requestAnimationFrameId_ = null;
if (!this.isAdded_) {
return;
}
if (this.isAnimated_) {
this.scheduleUpdate();
}
if (this.needsResize_ && this.resizeHandler_) {
this.needsResize_ = false;
this.resizeHandler_();
}
if (this.updateHandler_) {
this.updateHandler_();
}
};
/**
* A convenience method to get the current LatLng coordinate of the top left of
* the current view of the map.
* @return {google.maps.LatLng} The top left coordinate.
*/
CanvasLayer.prototype.getTopLeft = function() {
return this.topLeft_;
};
/**
* Schedule a requestAnimationFrame callback to updateHandler. If one is
* already scheduled, there is no effect.
*/
CanvasLayer.prototype.scheduleUpdate = function() {
if (this.isAdded_ && !this.requestAnimationFrameId_) {
this.requestAnimationFrameId_ =
this.requestAnimFrame_.call(window, this.requestUpdateFunction_);
}
};
File Manager Version 1.0, Coded By Lucas
Email: hehe@yahoo.com