/*!
VCO
*/
(function (root) {
root.VCO = {
VERSION: '0.1',
_originalL: root.VCO
};
}(this));
/* VCO.Debug
Debug mode
================================================== */
VCO.debug = true;
/* VCO.Bind
================================================== */
VCO.Bind = function (/*Function*/ fn, /*Object*/ obj) /*-> Object*/ {
return function () {
return fn.apply(obj, arguments);
};
};
/* Trace (console.log)
================================================== */
trace = function( msg ) {
if (VCO.debug) {
if (window.console) {
console.log(msg);
} else if ( typeof( jsTrace ) != 'undefined' ) {
jsTrace.send( msg );
} else {
//alert(msg);
}
}
}
/* VCO.Util
Class of utilities
================================================== */
VCO.Util = {
extend: function (/*Object*/ dest) /*-> Object*/ { // merge src properties into dest
var sources = Array.prototype.slice.call(arguments, 1);
for (var j = 0, len = sources.length, src; j < len; j++) {
src = sources[j] || {};
for (var i in src) {
if (src.hasOwnProperty(i)) {
dest[i] = src[i];
}
}
}
return dest;
},
setOptions: function (obj, options) {
obj.options = VCO.Util.extend({}, obj.options, options);
if (obj.options.uniqueid === "") {
obj.options.uniqueid = VCO.Util.unique_ID(6);
}
},
findArrayNumberByUniqueID: function(id, array, prop) {
var _n = 0;
for (var i = 0; i < array.length; i++) {
if (array[i].data[prop] == id) {
trace(array[i].data[prop]);
_n = i;
}
};
return _n;
},
convertUnixTime: function(str) { // created for Instagram. It's ISO8601-ish
// 2013-12-09 01:56:28
var pattern = /^(\d{4})-(\d{2})-(\d{2})[T\s](\d{2}):(\d{2}):(\d{2})/;
if (str.match(pattern)) {
var date_parts = str.match(pattern).slice(1);
}
var date_array = [];
for(var i = 0; i < date_parts.length; i++) {
var val = parseInt(date_parts[i]);
if (i == 1) { val = val - 1 } // stupid javascript months
date_array.push( val )
}
date = new Date(date_array[0], date_array[1], date_array[2], date_array[3], date_array[4], date_array[5]);
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
year = date.getFullYear();
month = months[date.getMonth()];
day = date.getDate();
time = month + ', ' + day + ' ' + year;
return time;
},
setData: function (obj, data) {
obj.data = VCO.Util.extend({}, obj.data, data);
if (obj.data.uniqueid === "") {
obj.data.uniqueid = VCO.Util.unique_ID(6);
}
},
mergeData: function(data_main, data_to_merge) {
var x;
for (x in data_to_merge) {
if (Object.prototype.hasOwnProperty.call(data_to_merge, x)) {
data_main[x] = data_to_merge[x];
}
}
return data_main;
},
stamp: (function () {
var lastId = 0, key = '_vco_id';
return function (/*Object*/ obj) {
obj[key] = obj[key] || ++lastId;
return obj[key];
};
}()),
isArray: (function () {
// Use compiler's own isArray when available
if (Array.isArray) {
return Array.isArray;
}
// Retain references to variables for performance
// optimization
var objectToStringFn = Object.prototype.toString,
arrayToStringResult = objectToStringFn.call([]);
return function (subject) {
return objectToStringFn.call(subject) === arrayToStringResult;
};
}()),
unique_ID: function(size, prefix) {
var getRandomNumber = function(range) {
return Math.floor(Math.random() * range);
};
var getRandomChar = function() {
var chars = "abcdefghijklmnopqurstuvwxyz";
return chars.substr( getRandomNumber(32), 1 );
};
var randomID = function(size) {
var str = "";
for(var i = 0; i < size; i++) {
str += getRandomChar();
}
return str;
};
if (prefix) {
return prefix + "-" + randomID(size);
} else {
return "vco-" + randomID(size);
}
},
htmlify: function(str) {
//if (str.match(/<\s*p[^>]*>([^<]*)<\s*\/\s*p\s*>/)) {
if (VCO.Browser.chrome) {
str = VCO.Emoji(str);
}
if (str.match(/
[\s\S]*?<\/p>/)) {
return str;
} else {
return "
" + str + "
";
}
},
getParamString: function (obj) {
var params = [];
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
params.push(i + '=' + obj[i]);
}
}
return '?' + params.join('&');
},
formatNum: function (num, digits) {
var pow = Math.pow(10, digits || 5);
return Math.round(num * pow) / pow;
},
falseFn: function () {
return false;
},
requestAnimFrame: (function () {
function timeoutDefer(callback) {
window.setTimeout(callback, 1000 / 60);
}
var requestFn = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
timeoutDefer;
return function (callback, context, immediate, contextEl) {
callback = context ? VCO.Util.bind(callback, context) : callback;
if (immediate && requestFn === timeoutDefer) {
callback();
} else {
requestFn(callback, contextEl);
}
};
}()),
bind: function (/*Function*/ fn, /*Object*/ obj) /*-> Object*/ {
return function () {
return fn.apply(obj, arguments);
};
},
template: function (str, data) {
return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) {
var value = data[key];
if (!data.hasOwnProperty(key)) {
throw new Error('No value provided for variable ' + str);
}
return value;
});
},
hexToRgb: function(hex) {
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shorthandRegex, function(m, r, g, b) {
return r + r + g + g + b + b;
});
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
},
ratio: {
square: function(size) {
var s = {
w: 0,
h: 0
}
if (size.w > size.h && size.h > 0) {
s.h = size.h;
s.w = size.h;
} else {
s.w = size.w;
s.h = size.w;
}
return s;
},
r16_9: function(size) {
if (size.w !== null && size.w !== "") {
return Math.round((size.w / 16) * 9);
} else if (size.h !== null && size.h !== "") {
return Math.round((size.h / 9) * 16);
} else {
return 0;
}
},
r4_3: function(size) {
if (size.w !== null && size.w !== "") {
return Math.round((size.w / 4) * 3);
} else if (size.h !== null && size.h !== "") {
return Math.round((size.h / 3) * 4);
}
}
},
getObjectAttributeByIndex: function(obj, index) {
if(typeof obj != 'undefined') {
var i = 0;
for (var attr in obj){
if (index === i){
return obj[attr];
}
i++;
}
return "";
} else {
return "";
}
},
getUrlVars: function(string) {
var str,
vars = [],
hash,
hashes;
str = string.toString();
if (str.match('&')) {
str = str.replace("&", "&");
} else if (str.match('&')) {
str = str.replace("&", "&");
} else if (str.match('&')) {
str = str.replace("&", "&");
}
hashes = str.slice(str.indexOf('?') + 1).split('&');
for(var i = 0; i < hashes.length; i++) {
hash = hashes[i].split('=');
vars.push(hash[0]);
vars[hash[0]] = hash[1];
}
return vars;
}
};
// Expects VCO to be visible in scope
;(function(VCO){
/* Zepto v1.1.2-15-g59d3fe5 - zepto event ajax form ie - zeptojs.com/license */
var Zepto = (function() {
var undefined, key, $, classList, emptyArray = [], slice = emptyArray.slice, filter = emptyArray.filter,
document = window.document,
elementDisplay = {}, classCache = {},
cssNumber = { 'column-count': 1, 'columns': 1, 'font-weight': 1, 'line-height': 1,'opacity': 1, 'z-index': 1, 'zoom': 1 },
fragmentRE = /^\s*<(\w+|!)[^>]*>/,
singleTagRE = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
rootNodeRE = /^(?:body|html)$/i,
capitalRE = /([A-Z])/g,
// special attributes that should be get/set via method calls
methodAttributes = ['val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset'],
adjacencyOperators = [ 'after', 'prepend', 'before', 'append' ],
table = document.createElement('table'),
tableRow = document.createElement('tr'),
containers = {
'tr': document.createElement('tbody'),
'tbody': table, 'thead': table, 'tfoot': table,
'td': tableRow, 'th': tableRow,
'*': document.createElement('div')
},
readyRE = /complete|loaded|interactive/,
classSelectorRE = /^\.([\w-]+)$/,
idSelectorRE = /^#([\w-]*)$/,
simpleSelectorRE = /^[\w-]*$/,
class2type = {},
toString = class2type.toString,
zepto = {},
camelize, uniq,
tempParent = document.createElement('div'),
propMap = {
'tabindex': 'tabIndex',
'readonly': 'readOnly',
'for': 'htmlFor',
'class': 'className',
'maxlength': 'maxLength',
'cellspacing': 'cellSpacing',
'cellpadding': 'cellPadding',
'rowspan': 'rowSpan',
'colspan': 'colSpan',
'usemap': 'useMap',
'frameborder': 'frameBorder',
'contenteditable': 'contentEditable'
},
isArray = Array.isArray ||
function(object){ return object instanceof Array }
zepto.matches = function(element, selector) {
if (!selector || !element || element.nodeType !== 1) return false
var matchesSelector = element.webkitMatchesSelector || element.mozMatchesSelector ||
element.oMatchesSelector || element.matchesSelector
if (matchesSelector) return matchesSelector.call(element, selector)
// fall back to performing a selector:
var match, parent = element.parentNode, temp = !parent
if (temp) (parent = tempParent).appendChild(element)
match = ~zepto.qsa(parent, selector).indexOf(element)
temp && tempParent.removeChild(element)
return match
}
function type(obj) {
return obj == null ? String(obj) :
class2type[toString.call(obj)] || "object"
}
function isFunction(value) { return type(value) == "function" }
function isWindow(obj) { return obj != null && obj == obj.window }
function isDocument(obj) { return obj != null && obj.nodeType == obj.DOCUMENT_NODE }
function isObject(obj) { return type(obj) == "object" }
function isPlainObject(obj) {
return isObject(obj) && !isWindow(obj) && Object.getPrototypeOf(obj) == Object.prototype
}
function likeArray(obj) { return typeof obj.length == 'number' }
function compact(array) { return filter.call(array, function(item){ return item != null }) }
function flatten(array) { return array.length > 0 ? $.fn.concat.apply([], array) : array }
camelize = function(str){ return str.replace(/-+(.)?/g, function(match, chr){ return chr ? chr.toUpperCase() : '' }) }
function dasherize(str) {
return str.replace(/::/g, '/')
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
.replace(/([a-z\d])([A-Z])/g, '$1_$2')
.replace(/_/g, '-')
.toLowerCase()
}
uniq = function(array){ return filter.call(array, function(item, idx){ return array.indexOf(item) == idx }) }
function classRE(name) {
return name in classCache ?
classCache[name] : (classCache[name] = new RegExp('(^|\\s)' + name + '(\\s|$)'))
}
function maybeAddPx(name, value) {
return (typeof value == "number" && !cssNumber[dasherize(name)]) ? value + "px" : value
}
function defaultDisplay(nodeName) {
var element, display
if (!elementDisplay[nodeName]) {
element = document.createElement(nodeName)
document.body.appendChild(element)
display = getComputedStyle(element, '').getPropertyValue("display")
element.parentNode.removeChild(element)
display == "none" && (display = "block")
elementDisplay[nodeName] = display
}
return elementDisplay[nodeName]
}
function children(element) {
return 'children' in element ?
slice.call(element.children) :
$.map(element.childNodes, function(node){ if (node.nodeType == 1) return node })
}
// `$.zepto.fragment` takes a html string and an optional tag name
// to generate DOM nodes nodes from the given html string.
// The generated DOM nodes are returned as an array.
// This function can be overriden in plugins for example to make
// it compatible with browsers that don't support the DOM fully.
zepto.fragment = function(html, name, properties) {
var dom, nodes, container
// A special case optimization for a single tag
if (singleTagRE.test(html)) dom = $(document.createElement(RegExp.$1))
if (!dom) {
if (html.replace) html = html.replace(tagExpanderRE, "<$1>$2>")
if (name === undefined) name = fragmentRE.test(html) && RegExp.$1
if (!(name in containers)) name = '*'
container = containers[name]
container.innerHTML = '' + html
dom = $.each(slice.call(container.childNodes), function(){
container.removeChild(this)
})
}
if (isPlainObject(properties)) {
nodes = $(dom)
$.each(properties, function(key, value) {
if (methodAttributes.indexOf(key) > -1) nodes[key](value)
else nodes.attr(key, value)
})
}
return dom
}
// `$.zepto.Z` swaps out the prototype of the given `dom` array
// of nodes with `$.fn` and thus supplying all the Zepto functions
// to the array. Note that `__proto__` is not supported on Internet
// Explorer. This method can be overriden in plugins.
zepto.Z = function(dom, selector) {
dom = dom || []
dom.__proto__ = $.fn
dom.selector = selector || ''
return dom
}
// `$.zepto.isZ` should return `true` if the given object is a Zepto
// collection. This method can be overriden in plugins.
zepto.isZ = function(object) {
return object instanceof zepto.Z
}
// `$.zepto.init` is Zepto's counterpart to jQuery's `$.fn.init` and
// takes a CSS selector and an optional context (and handles various
// special cases).
// This method can be overriden in plugins.
zepto.init = function(selector, context) {
var dom
// If nothing given, return an empty Zepto collection
if (!selector) return zepto.Z()
// Optimize for string selectors
else if (typeof selector == 'string') {
selector = selector.trim()
// If it's a html fragment, create nodes from it
// Note: In both Chrome 21 and Firefox 15, DOM error 12
// is thrown if the fragment doesn't begin with <
if (selector[0] == '<' && fragmentRE.test(selector))
dom = zepto.fragment(selector, RegExp.$1, context), selector = null
// If there's a context, create a collection on that context first, and select
// nodes from there
else if (context !== undefined) return $(context).find(selector)
// If it's a CSS selector, use it to select nodes.
else dom = zepto.qsa(document, selector)
}
// If a function is given, call it when the DOM is ready
else if (isFunction(selector)) return $(document).ready(selector)
// If a Zepto collection is given, just return it
else if (zepto.isZ(selector)) return selector
else {
// normalize array if an array of nodes is given
if (isArray(selector)) dom = compact(selector)
// Wrap DOM nodes.
else if (isObject(selector))
dom = [selector], selector = null
// If it's a html fragment, create nodes from it
else if (fragmentRE.test(selector))
dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null
// If there's a context, create a collection on that context first, and select
// nodes from there
else if (context !== undefined) return $(context).find(selector)
// And last but no least, if it's a CSS selector, use it to select nodes.
else dom = zepto.qsa(document, selector)
}
// create a new Zepto collection from the nodes found
return zepto.Z(dom, selector)
}
// `$` will be the base `Zepto` object. When calling this
// function just call `$.zepto.init, which makes the implementation
// details of selecting nodes and creating Zepto collections
// patchable in plugins.
$ = function(selector, context){
return zepto.init(selector, context)
}
function extend(target, source, deep) {
for (key in source)
if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
if (isPlainObject(source[key]) && !isPlainObject(target[key]))
target[key] = {}
if (isArray(source[key]) && !isArray(target[key]))
target[key] = []
extend(target[key], source[key], deep)
}
else if (source[key] !== undefined) target[key] = source[key]
}
// Copy all but undefined properties from one or more
// objects to the `target` object.
$.extend = function(target){
var deep, args = slice.call(arguments, 1)
if (typeof target == 'boolean') {
deep = target
target = args.shift()
}
args.forEach(function(arg){ extend(target, arg, deep) })
return target
}
// `$.zepto.qsa` is Zepto's CSS selector implementation which
// uses `document.querySelectorAll` and optimizes for some special cases, like `#id`.
// This method can be overriden in plugins.
zepto.qsa = function(element, selector){
var found,
maybeID = selector[0] == '#',
maybeClass = !maybeID && selector[0] == '.',
nameOnly = maybeID || maybeClass ? selector.slice(1) : selector, // Ensure that a 1 char tag name still gets checked
isSimple = simpleSelectorRE.test(nameOnly)
return (isDocument(element) && isSimple && maybeID) ?
( (found = element.getElementById(nameOnly)) ? [found] : [] ) :
(element.nodeType !== 1 && element.nodeType !== 9) ? [] :
slice.call(
isSimple && !maybeID ?
maybeClass ? element.getElementsByClassName(nameOnly) : // If it's simple, it could be a class
element.getElementsByTagName(selector) : // Or a tag
element.querySelectorAll(selector) // Or it's not simple, and we need to query all
)
}
function filtered(nodes, selector) {
return selector == null ? $(nodes) : $(nodes).filter(selector)
}
$.contains = function(parent, node) {
return parent !== node && parent.contains(node)
}
function funcArg(context, arg, idx, payload) {
return isFunction(arg) ? arg.call(context, idx, payload) : arg
}
function setAttribute(node, name, value) {
value == null ? node.removeAttribute(name) : node.setAttribute(name, value)
}
// access className property while respecting SVGAnimatedString
function className(node, value){
var klass = node.className,
svg = klass && klass.baseVal !== undefined
if (value === undefined) return svg ? klass.baseVal : klass
svg ? (klass.baseVal = value) : (node.className = value)
}
// "true" => true
// "false" => false
// "null" => null
// "42" => 42
// "42.5" => 42.5
// "08" => "08"
// JSON => parse if valid
// String => self
function deserializeValue(value) {
var num
try {
return value ?
value == "true" ||
( value == "false" ? false :
value == "null" ? null :
!/^0/.test(value) && !isNaN(num = Number(value)) ? num :
/^[\[\{]/.test(value) ? $.parseJSON(value) :
value )
: value
} catch(e) {
return value
}
}
$.type = type
$.isFunction = isFunction
$.isWindow = isWindow
$.isArray = isArray
$.isPlainObject = isPlainObject
$.isEmptyObject = function(obj) {
var name
for (name in obj) return false
return true
}
$.inArray = function(elem, array, i){
return emptyArray.indexOf.call(array, elem, i)
}
$.camelCase = camelize
$.trim = function(str) {
return str == null ? "" : String.prototype.trim.call(str)
}
// plugin compatibility
$.uuid = 0
$.support = { }
$.expr = { }
$.map = function(elements, callback){
var value, values = [], i, key
if (likeArray(elements))
for (i = 0; i < elements.length; i++) {
value = callback(elements[i], i)
if (value != null) values.push(value)
}
else
for (key in elements) {
value = callback(elements[key], key)
if (value != null) values.push(value)
}
return flatten(values)
}
$.each = function(elements, callback){
var i, key
if (likeArray(elements)) {
for (i = 0; i < elements.length; i++)
if (callback.call(elements[i], i, elements[i]) === false) return elements
} else {
for (key in elements)
if (callback.call(elements[key], key, elements[key]) === false) return elements
}
return elements
}
$.grep = function(elements, callback){
return filter.call(elements, callback)
}
if (window.JSON) $.parseJSON = JSON.parse
// Populate the class2type map
$.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
class2type[ "[object " + name + "]" ] = name.toLowerCase()
})
// Define methods that will be available on all
// Zepto collections
$.fn = {
// Because a collection acts like an array
// copy over these useful array functions.
forEach: emptyArray.forEach,
reduce: emptyArray.reduce,
push: emptyArray.push,
sort: emptyArray.sort,
indexOf: emptyArray.indexOf,
concat: emptyArray.concat,
// `map` and `slice` in the jQuery API work differently
// from their array counterparts
map: function(fn){
return $($.map(this, function(el, i){ return fn.call(el, i, el) }))
},
slice: function(){
return $(slice.apply(this, arguments))
},
ready: function(callback){
// need to check if document.body exists for IE as that browser reports
// document ready when it hasn't yet created the body element
if (readyRE.test(document.readyState) && document.body) callback($)
else document.addEventListener('DOMContentLoaded', function(){ callback($) }, false)
return this
},
get: function(idx){
return idx === undefined ? slice.call(this) : this[idx >= 0 ? idx : idx + this.length]
},
toArray: function(){ return this.get() },
size: function(){
return this.length
},
remove: function(){
return this.each(function(){
if (this.parentNode != null)
this.parentNode.removeChild(this)
})
},
each: function(callback){
emptyArray.every.call(this, function(el, idx){
return callback.call(el, idx, el) !== false
})
return this
},
filter: function(selector){
if (isFunction(selector)) return this.not(this.not(selector))
return $(filter.call(this, function(element){
return zepto.matches(element, selector)
}))
},
add: function(selector,context){
return $(uniq(this.concat($(selector,context))))
},
is: function(selector){
return this.length > 0 && zepto.matches(this[0], selector)
},
not: function(selector){
var nodes=[]
if (isFunction(selector) && selector.call !== undefined)
this.each(function(idx){
if (!selector.call(this,idx)) nodes.push(this)
})
else {
var excludes = typeof selector == 'string' ? this.filter(selector) :
(likeArray(selector) && isFunction(selector.item)) ? slice.call(selector) : $(selector)
this.forEach(function(el){
if (excludes.indexOf(el) < 0) nodes.push(el)
})
}
return $(nodes)
},
has: function(selector){
return this.filter(function(){
return isObject(selector) ?
$.contains(this, selector) :
$(this).find(selector).size()
})
},
eq: function(idx){
return idx === -1 ? this.slice(idx) : this.slice(idx, + idx + 1)
},
first: function(){
var el = this[0]
return el && !isObject(el) ? el : $(el)
},
last: function(){
var el = this[this.length - 1]
return el && !isObject(el) ? el : $(el)
},
find: function(selector){
var result, $this = this
if (typeof selector == 'object')
result = $(selector).filter(function(){
var node = this
return emptyArray.some.call($this, function(parent){
return $.contains(parent, node)
})
})
else if (this.length == 1) result = $(zepto.qsa(this[0], selector))
else result = this.map(function(){ return zepto.qsa(this, selector) })
return result
},
closest: function(selector, context){
var node = this[0], collection = false
if (typeof selector == 'object') collection = $(selector)
while (node && !(collection ? collection.indexOf(node) >= 0 : zepto.matches(node, selector)))
node = node !== context && !isDocument(node) && node.parentNode
return $(node)
},
parents: function(selector){
var ancestors = [], nodes = this
while (nodes.length > 0)
nodes = $.map(nodes, function(node){
if ((node = node.parentNode) && !isDocument(node) && ancestors.indexOf(node) < 0) {
ancestors.push(node)
return node
}
})
return filtered(ancestors, selector)
},
parent: function(selector){
return filtered(uniq(this.pluck('parentNode')), selector)
},
children: function(selector){
return filtered(this.map(function(){ return children(this) }), selector)
},
contents: function() {
return this.map(function() { return slice.call(this.childNodes) })
},
siblings: function(selector){
return filtered(this.map(function(i, el){
return filter.call(children(el.parentNode), function(child){ return child!==el })
}), selector)
},
empty: function(){
return this.each(function(){ this.innerHTML = '' })
},
// `pluck` is borrowed from Prototype.js
pluck: function(property){
return $.map(this, function(el){ return el[property] })
},
show: function(){
return this.each(function(){
this.style.display == "none" && (this.style.display = '')
if (getComputedStyle(this, '').getPropertyValue("display") == "none")
this.style.display = defaultDisplay(this.nodeName)
})
},
replaceWith: function(newContent){
return this.before(newContent).remove()
},
wrap: function(structure){
var func = isFunction(structure)
if (this[0] && !func)
var dom = $(structure).get(0),
clone = dom.parentNode || this.length > 1
return this.each(function(index){
$(this).wrapAll(
func ? structure.call(this, index) :
clone ? dom.cloneNode(true) : dom
)
})
},
wrapAll: function(structure){
if (this[0]) {
$(this[0]).before(structure = $(structure))
var children
// drill down to the inmost element
while ((children = structure.children()).length) structure = children.first()
$(structure).append(this)
}
return this
},
wrapInner: function(structure){
var func = isFunction(structure)
return this.each(function(index){
var self = $(this), contents = self.contents(),
dom = func ? structure.call(this, index) : structure
contents.length ? contents.wrapAll(dom) : self.append(dom)
})
},
unwrap: function(){
this.parent().each(function(){
$(this).replaceWith($(this).children())
})
return this
},
clone: function(){
return this.map(function(){ return this.cloneNode(true) })
},
hide: function(){
return this.css("display", "none")
},
toggle: function(setting){
return this.each(function(){
var el = $(this)
;(setting === undefined ? el.css("display") == "none" : setting) ? el.show() : el.hide()
})
},
prev: function(selector){ return $(this.pluck('previousElementSibling')).filter(selector || '*') },
next: function(selector){ return $(this.pluck('nextElementSibling')).filter(selector || '*') },
html: function(html){
return arguments.length === 0 ?
(this.length > 0 ? this[0].innerHTML : null) :
this.each(function(idx){
var originHtml = this.innerHTML
$(this).empty().append( funcArg(this, html, idx, originHtml) )
})
},
text: function(text){
return arguments.length === 0 ?
(this.length > 0 ? this[0].textContent : null) :
this.each(function(){ this.textContent = (text === undefined) ? '' : ''+text })
},
attr: function(name, value){
var result
return (typeof name == 'string' && value === undefined) ?
(this.length == 0 || this[0].nodeType !== 1 ? undefined :
(name == 'value' && this[0].nodeName == 'INPUT') ? this.val() :
(!(result = this[0].getAttribute(name)) && name in this[0]) ? this[0][name] : result
) :
this.each(function(idx){
if (this.nodeType !== 1) return
if (isObject(name)) for (key in name) setAttribute(this, key, name[key])
else setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(name)))
})
},
removeAttr: function(name){
return this.each(function(){ this.nodeType === 1 && setAttribute(this, name) })
},
prop: function(name, value){
name = propMap[name] || name
return (value === undefined) ?
(this[0] && this[0][name]) :
this.each(function(idx){
this[name] = funcArg(this, value, idx, this[name])
})
},
data: function(name, value){
var data = this.attr('data-' + name.replace(capitalRE, '-$1').toLowerCase(), value)
return data !== null ? deserializeValue(data) : undefined
},
val: function(value){
return arguments.length === 0 ?
(this[0] && (this[0].multiple ?
$(this[0]).find('option').filter(function(){ return this.selected }).pluck('value') :
this[0].value)
) :
this.each(function(idx){
this.value = funcArg(this, value, idx, this.value)
})
},
offset: function(coordinates){
if (coordinates) return this.each(function(index){
var $this = $(this),
coords = funcArg(this, coordinates, index, $this.offset()),
parentOffset = $this.offsetParent().offset(),
props = {
top: coords.top - parentOffset.top,
left: coords.left - parentOffset.left
}
if ($this.css('position') == 'static') props['position'] = 'relative'
$this.css(props)
})
if (this.length==0) return null
var obj = this[0].getBoundingClientRect()
return {
left: obj.left + window.pageXOffset,
top: obj.top + window.pageYOffset,
width: Math.round(obj.width),
height: Math.round(obj.height)
}
},
css: function(property, value){
if (arguments.length < 2) {
var element = this[0], computedStyle = getComputedStyle(element, '')
if(!element) return
if (typeof property == 'string')
return element.style[camelize(property)] || computedStyle.getPropertyValue(property)
else if (isArray(property)) {
var props = {}
$.each(isArray(property) ? property: [property], function(_, prop){
props[prop] = (element.style[camelize(prop)] || computedStyle.getPropertyValue(prop))
})
return props
}
}
var css = ''
if (type(property) == 'string') {
if (!value && value !== 0)
this.each(function(){ this.style.removeProperty(dasherize(property)) })
else
css = dasherize(property) + ":" + maybeAddPx(property, value)
} else {
for (key in property)
if (!property[key] && property[key] !== 0)
this.each(function(){ this.style.removeProperty(dasherize(key)) })
else
css += dasherize(key) + ':' + maybeAddPx(key, property[key]) + ';'
}
return this.each(function(){ this.style.cssText += ';' + css })
},
index: function(element){
return element ? this.indexOf($(element)[0]) : this.parent().children().indexOf(this[0])
},
hasClass: function(name){
if (!name) return false
return emptyArray.some.call(this, function(el){
return this.test(className(el))
}, classRE(name))
},
addClass: function(name){
if (!name) return this
return this.each(function(idx){
classList = []
var cls = className(this), newName = funcArg(this, name, idx, cls)
newName.split(/\s+/g).forEach(function(klass){
if (!$(this).hasClass(klass)) classList.push(klass)
}, this)
classList.length && className(this, cls + (cls ? " " : "") + classList.join(" "))
})
},
removeClass: function(name){
return this.each(function(idx){
if (name === undefined) return className(this, '')
classList = className(this)
funcArg(this, name, idx, classList).split(/\s+/g).forEach(function(klass){
classList = classList.replace(classRE(klass), " ")
})
className(this, classList.trim())
})
},
toggleClass: function(name, when){
if (!name) return this
return this.each(function(idx){
var $this = $(this), names = funcArg(this, name, idx, className(this))
names.split(/\s+/g).forEach(function(klass){
(when === undefined ? !$this.hasClass(klass) : when) ?
$this.addClass(klass) : $this.removeClass(klass)
})
})
},
scrollTop: function(value){
if (!this.length) return
var hasScrollTop = 'scrollTop' in this[0]
if (value === undefined) return hasScrollTop ? this[0].scrollTop : this[0].pageYOffset
return this.each(hasScrollTop ?
function(){ this.scrollTop = value } :
function(){ this.scrollTo(this.scrollX, value) })
},
scrollLeft: function(value){
if (!this.length) return
var hasScrollLeft = 'scrollLeft' in this[0]
if (value === undefined) return hasScrollLeft ? this[0].scrollLeft : this[0].pageXOffset
return this.each(hasScrollLeft ?
function(){ this.scrollLeft = value } :
function(){ this.scrollTo(value, this.scrollY) })
},
position: function() {
if (!this.length) return
var elem = this[0],
// Get *real* offsetParent
offsetParent = this.offsetParent(),
// Get correct offsets
offset = this.offset(),
parentOffset = rootNodeRE.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset()
// Subtract element margins
// note: when an element has margin: auto the offsetLeft and marginLeft
// are the same in Safari causing offset.left to incorrectly be 0
offset.top -= parseFloat( $(elem).css('margin-top') ) || 0
offset.left -= parseFloat( $(elem).css('margin-left') ) || 0
// Add offsetParent borders
parentOffset.top += parseFloat( $(offsetParent[0]).css('border-top-width') ) || 0
parentOffset.left += parseFloat( $(offsetParent[0]).css('border-left-width') ) || 0
// Subtract the two offsets
return {
top: offset.top - parentOffset.top,
left: offset.left - parentOffset.left
}
},
offsetParent: function() {
return this.map(function(){
var parent = this.offsetParent || document.body
while (parent && !rootNodeRE.test(parent.nodeName) && $(parent).css("position") == "static")
parent = parent.offsetParent
return parent
})
}
}
// for now
$.fn.detach = $.fn.remove
// Generate the `width` and `height` functions
;['width', 'height'].forEach(function(dimension){
var dimensionProperty =
dimension.replace(/./, function(m){ return m[0].toUpperCase() })
$.fn[dimension] = function(value){
var offset, el = this[0]
if (value === undefined) return isWindow(el) ? el['inner' + dimensionProperty] :
isDocument(el) ? el.documentElement['scroll' + dimensionProperty] :
(offset = this.offset()) && offset[dimension]
else return this.each(function(idx){
el = $(this)
el.css(dimension, funcArg(this, value, idx, el[dimension]()))
})
}
})
function traverseNode(node, fun) {
fun(node)
for (var key in node.childNodes) traverseNode(node.childNodes[key], fun)
}
// Generate the `after`, `prepend`, `before`, `append`,
// `insertAfter`, `insertBefore`, `appendTo`, and `prependTo` methods.
adjacencyOperators.forEach(function(operator, operatorIndex) {
var inside = operatorIndex % 2 //=> prepend, append
$.fn[operator] = function(){
// arguments can be nodes, arrays of nodes, Zepto objects and HTML strings
var argType, nodes = $.map(arguments, function(arg) {
argType = type(arg)
return argType == "object" || argType == "array" || arg == null ?
arg : zepto.fragment(arg)
}),
parent, copyByClone = this.length > 1
if (nodes.length < 1) return this
return this.each(function(_, target){
parent = inside ? target : target.parentNode
// convert all methods to a "before" operation
target = operatorIndex == 0 ? target.nextSibling :
operatorIndex == 1 ? target.firstChild :
operatorIndex == 2 ? target :
null
nodes.forEach(function(node){
if (copyByClone) node = node.cloneNode(true)
else if (!parent) return $(node).remove()
traverseNode(parent.insertBefore(node, target), function(el){
if (el.nodeName != null && el.nodeName.toUpperCase() === 'SCRIPT' &&
(!el.type || el.type === 'text/javascript') && !el.src)
window['eval'].call(window, el.innerHTML)
})
})
})
}
// after => insertAfter
// prepend => prependTo
// before => insertBefore
// append => appendTo
$.fn[inside ? operator+'To' : 'insert'+(operatorIndex ? 'Before' : 'After')] = function(html){
$(html)[operator](this)
return this
}
})
zepto.Z.prototype = $.fn
// Export internal API functions in the `$.zepto` namespace
zepto.uniq = uniq
zepto.deserializeValue = deserializeValue
$.zepto = zepto
return $
})()
window.Zepto = Zepto
window.$ === undefined && (window.$ = Zepto)
;(function($){
var $$ = $.zepto.qsa, _zid = 1, undefined,
slice = Array.prototype.slice,
isFunction = $.isFunction,
isString = function(obj){ return typeof obj == 'string' },
handlers = {},
specialEvents={},
focusinSupported = 'onfocusin' in window,
focus = { focus: 'focusin', blur: 'focusout' },
hover = { mouseenter: 'mouseover', mouseleave: 'mouseout' }
specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents'
function zid(element) {
return element._zid || (element._zid = _zid++)
}
function findHandlers(element, event, fn, selector) {
event = parse(event)
if (event.ns) var matcher = matcherFor(event.ns)
return (handlers[zid(element)] || []).filter(function(handler) {
return handler
&& (!event.e || handler.e == event.e)
&& (!event.ns || matcher.test(handler.ns))
&& (!fn || zid(handler.fn) === zid(fn))
&& (!selector || handler.sel == selector)
})
}
function parse(event) {
var parts = ('' + event).split('.')
return {e: parts[0], ns: parts.slice(1).sort().join(' ')}
}
function matcherFor(ns) {
return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)')
}
function eventCapture(handler, captureSetting) {
return handler.del &&
(!focusinSupported && (handler.e in focus)) ||
!!captureSetting
}
function realEvent(type) {
return hover[type] || (focusinSupported && focus[type]) || type
}
function add(element, events, fn, data, selector, delegator, capture){
var id = zid(element), set = (handlers[id] || (handlers[id] = []))
events.split(/\s/).forEach(function(event){
if (event == 'ready') return $(document).ready(fn)
var handler = parse(event)
handler.fn = fn
handler.sel = selector
// emulate mouseenter, mouseleave
if (handler.e in hover) fn = function(e){
var related = e.relatedTarget
if (!related || (related !== this && !$.contains(this, related)))
return handler.fn.apply(this, arguments)
}
handler.del = delegator
var callback = delegator || fn
handler.proxy = function(e){
e = compatible(e)
if (e.isImmediatePropagationStopped()) return
e.data = data
var result = callback.apply(element, e._args == undefined ? [e] : [e].concat(e._args))
if (result === false) e.preventDefault(), e.stopPropagation()
return result
}
handler.i = set.length
set.push(handler)
if ('addEventListener' in element)
element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
})
}
function remove(element, events, fn, selector, capture){
var id = zid(element)
;(events || '').split(/\s/).forEach(function(event){
findHandlers(element, event, fn, selector).forEach(function(handler){
delete handlers[id][handler.i]
if ('removeEventListener' in element)
element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
})
})
}
$.event = { add: add, remove: remove }
$.proxy = function(fn, context) {
if (isFunction(fn)) {
var proxyFn = function(){ return fn.apply(context, arguments) }
proxyFn._zid = zid(fn)
return proxyFn
} else if (isString(context)) {
return $.proxy(fn[context], fn)
} else {
throw new TypeError("expected function")
}
}
$.fn.bind = function(event, data, callback){
return this.on(event, data, callback)
}
$.fn.unbind = function(event, callback){
return this.off(event, callback)
}
$.fn.one = function(event, selector, data, callback){
return this.on(event, selector, data, callback, 1)
}
var returnTrue = function(){return true},
returnFalse = function(){return false},
ignoreProperties = /^([A-Z]|returnValue$|layer[XY]$)/,
eventMethods = {
preventDefault: 'isDefaultPrevented',
stopImmediatePropagation: 'isImmediatePropagationStopped',
stopPropagation: 'isPropagationStopped'
}
function compatible(event, source) {
if (source || !event.isDefaultPrevented) {
source || (source = event)
$.each(eventMethods, function(name, predicate) {
var sourceMethod = source[name]
event[name] = function(){
this[predicate] = returnTrue
return sourceMethod && sourceMethod.apply(source, arguments)
}
event[predicate] = returnFalse
})
if (source.defaultPrevented !== undefined ? source.defaultPrevented :
'returnValue' in source ? source.returnValue === false :
source.getPreventDefault && source.getPreventDefault())
event.isDefaultPrevented = returnTrue
}
return event
}
function createProxy(event) {
var key, proxy = { originalEvent: event }
for (key in event)
if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key]
return compatible(proxy, event)
}
$.fn.delegate = function(selector, event, callback){
return this.on(event, selector, callback)
}
$.fn.undelegate = function(selector, event, callback){
return this.off(event, selector, callback)
}
$.fn.live = function(event, callback){
$(document.body).delegate(this.selector, event, callback)
return this
}
$.fn.die = function(event, callback){
$(document.body).undelegate(this.selector, event, callback)
return this
}
$.fn.on = function(event, selector, data, callback, one){
var autoRemove, delegator, $this = this
if (event && !isString(event)) {
$.each(event, function(type, fn){
$this.on(type, selector, data, fn, one)
})
return $this
}
if (!isString(selector) && !isFunction(callback) && callback !== false)
callback = data, data = selector, selector = undefined
if (isFunction(data) || data === false)
callback = data, data = undefined
if (callback === false) callback = returnFalse
return $this.each(function(_, element){
if (one) autoRemove = function(e){
remove(element, e.type, callback)
return callback.apply(this, arguments)
}
if (selector) delegator = function(e){
var evt, match = $(e.target).closest(selector, element).get(0)
if (match && match !== element) {
evt = $.extend(createProxy(e), {currentTarget: match, liveFired: element})
return (autoRemove || callback).apply(match, [evt].concat(slice.call(arguments, 1)))
}
}
add(element, event, callback, data, selector, delegator || autoRemove)
})
}
$.fn.off = function(event, selector, callback){
var $this = this
if (event && !isString(event)) {
$.each(event, function(type, fn){
$this.off(type, selector, fn)
})
return $this
}
if (!isString(selector) && !isFunction(callback) && callback !== false)
callback = selector, selector = undefined
if (callback === false) callback = returnFalse
return $this.each(function(){
remove(this, event, callback, selector)
})
}
$.fn.trigger = function(event, args){
event = (isString(event) || $.isPlainObject(event)) ? $.Event(event) : compatible(event)
event._args = args
return this.each(function(){
// items in the collection might not be DOM elements
if('dispatchEvent' in this) this.dispatchEvent(event)
else $(this).triggerHandler(event, args)
})
}
// triggers event handlers on current element just as if an event occurred,
// doesn't trigger an actual event, doesn't bubble
$.fn.triggerHandler = function(event, args){
var e, result
this.each(function(i, element){
e = createProxy(isString(event) ? $.Event(event) : event)
e._args = args
e.target = element
$.each(findHandlers(element, event.type || event), function(i, handler){
result = handler.proxy(e)
if (e.isImmediatePropagationStopped()) return false
})
})
return result
}
// shortcut methods for `.bind(event, fn)` for each event type
;('focusin focusout load resize scroll unload click dblclick '+
'mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave '+
'change select keydown keypress keyup error').split(' ').forEach(function(event) {
$.fn[event] = function(callback) {
return callback ?
this.bind(event, callback) :
this.trigger(event)
}
})
;['focus', 'blur'].forEach(function(name) {
$.fn[name] = function(callback) {
if (callback) this.bind(name, callback)
else this.each(function(){
try { this[name]() }
catch(e) {}
})
return this
}
})
$.Event = function(type, props) {
if (!isString(type)) props = type, type = props.type
var event = document.createEvent(specialEvents[type] || 'Events'), bubbles = true
if (props) for (var name in props) (name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name])
event.initEvent(type, bubbles, true)
return compatible(event)
}
})(Zepto)
;(function($){
var jsonpID = 0,
document = window.document,
key,
name,
rscript = /";
// API Call
this._el.content_item.innerHTML = content;
// After Loaded
this.onLoaded();
},
// Update Media Display
_updateMediaDisplay: function() {
this._el.content_item.style.height = this.options.height + "px";
}
});
VCO.Media.Text = VCO.Class.extend({
includes: [VCO.Events],
// DOM ELEMENTS
_el: {
container: {},
content_container: {},
content: {},
headline: {},
date: {},
start_btn: {}
},
// Data
data: {
uniqueid: "",
headline: "headline",
text: "text"
},
// Options
options: {
title: false
},
/* Constructor
================================================== */
initialize: function(data, options, add_to_container) {
VCO.Util.setData(this, data);
// Merge Options
VCO.Util.mergeData(this.options, options);
this._el.container = VCO.Dom.create("div", "vco-text");
this._el.container.id = this.data.uniqueid;
this._initLayout();
if (add_to_container) {
add_to_container.appendChild(this._el.container);
};
},
/* Adding, Hiding, Showing etc
================================================== */
show: function() {
},
hide: function() {
},
addTo: function(container) {
container.appendChild(this._el.container);
//this.onAdd();
},
removeFrom: function(container) {
container.removeChild(this._el.container);
},
headlineHeight: function() {
return this._el.headline.offsetHeight + 40;
},
addDateText: function(str) {
this._el.date.innerHTML = str;
},
/* Events
================================================== */
onLoaded: function() {
this.fire("loaded", this.data);
},
onAdd: function() {
this.fire("added", this.data);
},
onRemove: function() {
this.fire("removed", this.data);
},
/* Private Methods
================================================== */
_initLayout: function () {
// Create Layout
this._el.content_container = VCO.Dom.create("div", "vco-text-content-container", this._el.container);
// Date
this._el.date = VCO.Dom.create("h3", "vco-headline-date", this._el.content_container);
// Headline
if (this.data.headline != "") {
var headline_class = "vco-headline";
if (this.options.title) {
headline_class = "vco-headline vco-headline-title";
}
this._el.headline = VCO.Dom.create("h2", headline_class, this._el.content_container);
this._el.headline.innerHTML = this.data.headline;
}
// Text
if (this.data.text != "") {
var text_content = "";
text_content += VCO.Util.htmlify(this.data.text);
// Date
if (this.data.date && this.data.date.created_time && this.data.date.created_time != "") {
if (this.data.date.created_time.length > 10) {
if (typeof(moment) !== 'undefined') {
text_content += "" + moment(this.data.date.created_time, 'YYYY-MM-DD h:mm:ss').fromNow() + "
";
} else {
text_content += "" + VCO.Util.convertUnixTime(this.data.date.created_time) + "
";
}
}
}
this._el.content = VCO.Dom.create("div", "vco-text-content", this._el.content_container);
this._el.content.innerHTML = text_content;
}
// Fire event that the slide is loaded
this.onLoaded();
}
});
/* VCO.Media.Twitter
Produces Twitter Display
================================================== */
VCO.Media.Twitter = VCO.Media.extend({
includes: [VCO.Events],
/* Load the media
================================================== */
_loadMedia: function() {
var api_url,
self = this;
// Loading Message
this.message.updateMessage(VCO.Language.messages.loading + " " + this.options.media_name);
// Create Dom element
this._el.content_item = VCO.Dom.create("div", "vco-media-twitter", this._el.content);
// Get Media ID
if (this.data.url.match("status\/")) {
this.media_id = this.data.url.split("status\/")[1];
} else if (url.match("statuses\/")) {
this.media_id = this.data.url.split("statuses\/")[1];
} else {
this.media_id = "";
}
// API URL
api_url = "https://api.twitter.com/1/statuses/oembed.json?id=" + this.media_id + "&omit_script=true&include_entities=true&callback=?";
// API Call
VCO.ajax({
type: 'GET',
url: api_url,
dataType: 'json', //json data type
success: function(d){
self.createMedia(d);
},
error:function(xhr, type){
var error_text = "";
error_text += "Unable to load Tweet.
" + self.media_id + "
" + type;
self.loadErrorDisplay(error_text);
}
});
},
createMedia: function(d) {
trace("create_media")
var tweet = "",
tweet_text = "",
tweetuser = "",
tweet_status_temp = "",
tweet_status_url = "",
tweet_status_date = "";
// TWEET CONTENT
tweet_text = d.html.split("<\/p>\—")[0] + "";
tweetuser = d.author_url.split("twitter.com\/")[1];
tweet_status_temp = d.html.split("<\/p>\—")[1].split("")[0];
tweet_status_date = tweet_status_temp.split("\"\>")[1].split("<\/a>")[0];
// TWEET CONTENT
tweet += tweet_text;
// TWEET AUTHOR
tweet += "";
tweet += "";
tweet += "
";
tweet += "
";
// Add to DOM
this._el.content_item.innerHTML = tweet;
// After Loaded
this.onLoaded();
},
updateMediaDisplay: function() {
},
_updateMediaDisplay: function() {
}
});
/* VCO.Media.Vimeo
================================================== */
VCO.Media.Vimeo = VCO.Media.extend({
includes: [VCO.Events],
/* Load the media
================================================== */
_loadMedia: function() {
var api_url,
self = this;
// Loading Message
this.message.updateMessage(VCO.Language.messages.loading + " " + this.options.media_name);
// Create Dom element
this._el.content_item = VCO.Dom.create("div", "vco-media-item vco-media-iframe vco-media-vimeo vco-media-shadow", this._el.content);
// Get Media ID
this.media_id = this.data.url.split(/video\/|\/\/vimeo\.com\//)[1].split(/[?&]/)[0];
// API URL
api_url = "http://player.vimeo.com/video/" + this.media_id + "?api=1&title=0&byline=0&portrait=0&color=ffffff";
this.player = VCO.Dom.create("iframe", "", this._el.content_item);
this.player.width = "100%";
this.player.height = "100%";
this.player.frameBorder = "0";
this.player.src = api_url;
// After Loaded
this.onLoaded();
},
// Update Media Display
_updateMediaDisplay: function() {
this._el.content_item.style.height = VCO.Util.ratio.r16_9({w:this._el.content_item.offsetWidth}) + "px";
},
_stopMedia: function() {
try {
this.player.contentWindow.postMessage(JSON.stringify({method: "pause"}), "http://player.vimeo.com");
}
catch(err) {
trace(err);
}
}
});
/* VCO.Media.DailyMotion
================================================== */
VCO.Media.DailyMotion = VCO.Media.extend({
includes: [VCO.Events],
/* Load the media
================================================== */
_loadMedia: function() {
var api_url,
self = this;
// Loading Message
this.message.updateMessage(VCO.Language.messages.loading + " " + this.options.media_name);
// Create Dom element
this._el.content_item = VCO.Dom.create("div", "vco-media-item vco-media-iframe vco-media-dailymotion", this._el.content);
// Get Media ID
if (this.data.url.match("video")) {
this.media_id = this.data.url.split("video\/")[1].split(/[?&]/)[0];
} else {
this.media_id = this.data.url.split("embed\/")[1].split(/[?&]/)[0];
}
// API URL
api_url = "http://www.dailymotion.com/embed/video/" + this.media_id;
// API Call
this._el.content_item.innerHTML = ""
// After Loaded
this.onLoaded();
},
// Update Media Display
_updateMediaDisplay: function() {
this._el.content_item.style.height = VCO.Util.ratio.r16_9({w:this._el.content_item.offsetWidth}) + "px";
}
});
/* VCO.Media.Vine
================================================== */
VCO.Media.Vine = VCO.Media.extend({
includes: [VCO.Events],
/* Load the media
================================================== */
_loadMedia: function() {
var api_url,
self = this;
// Loading Message
this.message.updateMessage(VCO.Language.messages.loading + " " + this.options.media_name);
// Create Dom element
this._el.content_item = VCO.Dom.create("div", "vco-media-item vco-media-iframe vco-media-vine vco-media-shadow", this._el.content);
// Get Media ID
this.media_id = this.data.url.split("vine.co/v/")[1];
// API URL
api_url = "https://vine.co/v/" + this.media_id + "/embed/simple";
// API Call
this._el.content_item.innerHTML = ""
// After Loaded
this.onLoaded();
},
// Update Media Display
_updateMediaDisplay: function() {
var size = VCO.Util.ratio.square({w:this._el.content_item.offsetWidth , h:this.options.height});
this._el.content_item.style.height = size.h + "px";
}
});
/* VCO.Media.Website
================================================== */
VCO.Media.Website = VCO.Media.extend({
includes: [VCO.Events],
/* Load the media
================================================== */
_loadMedia: function() {
},
createMedia: function(d) {
// After Loaded
this.onLoaded();
}
});
/* VCO.Media.Wikipedia
================================================== */
VCO.Media.Wikipedia = VCO.Media.extend({
includes: [VCO.Events],
/* Load the media
================================================== */
_loadMedia: function() {
var api_url,
api_language,
self = this;
// Loading Message
this.message.updateMessage(VCO.Language.messages.loading + " " + this.options.media_name);
// Create Dom element
this._el.content_item = VCO.Dom.create("div", "vco-media-item vco-media-wikipedia", this._el.content);
// Get Media ID
this.media_id = this.data.url.split("wiki\/")[1].split("#")[0].replace("_", " ");
this.media_id = this.media_id.replace(" ", "%20");
api_language = this.data.url.split("//")[1].split(".wikipedia")[0];
// API URL
api_url = "http://" + api_language + ".wikipedia.org/w/api.php?action=query&prop=extracts&redirects=&titles=" + this.media_id + "&exintro=1&format=json&callback=?";
// API Call
VCO.ajax({
type: 'GET',
url: api_url,
dataType: 'json', //json data type
success: function(d){
self.createMedia(d);
},
error:function(xhr, type){
var error_text = "";
error_text += "Unable to load Wikipedia entry.
" + self.media_id + "
" + type;
self.loadErrorDisplay(error_text);
}
});
},
createMedia: function(d) {
var wiki = "";
if (d.query) {
var content,
wiki = {
entry: {},
title: "",
text: "",
extract: "",
paragraphs: 1,
text_array: []
};
wiki.entry = VCO.Util.getObjectAttributeByIndex(d.query.pages, 0);
wiki.extract = wiki.entry.extract;
wiki.title = wiki.entry.title;
if (wiki.extract.match("")) {
wiki.text_array = wiki.extract.split("
");
} else {
wiki.text_array.push(wiki.extract);
}
for(var i = 0; i < wiki.text_array.length; i++) {
if (i+1 <= wiki.paragraphs && i+1 < wiki.text_array.length) {
wiki.text += "
" + wiki.text_array[i+1];
}
}
content = "
";
content += "" + VCO.Language.messages.wikipedia + "";
content += wiki.text;
if (wiki.extract.match("REDIRECT")) {
} else {
// Add to DOM
this._el.content_item.innerHTML = content;
// After Loaded
this.onLoaded();
}
}
},
updateMediaDisplay: function() {
},
_updateMediaDisplay: function() {
}
});
/* VCO.Media.YouTube
================================================== */
VCO.Media.YouTube = VCO.Media.extend({
includes: [VCO.Events],
/* Load the media
================================================== */
_loadMedia: function() {
var self = this,
url_vars;
// Loading Message
this.message.updateMessage(VCO.Language.messages.loading + " " + this.options.media_name);
this.youtube_loaded = false;
// Create Dom element
this._el.content_item = VCO.Dom.create("div", "vco-media-item vco-media-youtube vco-media-shadow", this._el.content);
this._el.content_item.id = VCO.Util.unique_ID(7)
// URL Vars
url_vars = VCO.Util.getUrlVars(this.data.url);
// Get Media ID
this.media_id = {};
if (this.data.url.match('v=')) {
this.media_id.id = url_vars["v"];
} else if (this.data.url.match('\/embed\/')) {
this.media_id.id = this.data.url.split("embed\/")[1].split(/[?&]/)[0];
} else if (this.data.url.match(/v\/|v=|youtu\.be\//)){
this.media_id.id = this.data.url.split(/v\/|v=|youtu\.be\//)[1].split(/[?&]/)[0];
} else {
trace("YOUTUBE IN URL BUT NOT A VALID VIDEO");
}
this.media_id.start = url_vars["t"];
this.media_id.hd = url_vars["hd"];
// API Call
VCO.Load.js('https://www.youtube.com/player_api', function() {
self.createMedia();
});
},
// Update Media Display
_updateMediaDisplay: function() {
this._el.content_item.style.height = VCO.Util.ratio.r16_9({w:this._el.content_item.offsetWidth}) + "px";
},
_stopMedia: function() {
if (this.youtube_loaded) {
try {
this.player.pauseVideo();
}
catch(err) {
trace(err);
}
}
},
createMedia: function() {
var self = this;
// Determine Start of Media
if (typeof(this.media_id.start) != 'undefined') {
var vidstart = this.media_id.start.toString(),
vid_start_minutes = 0,
vid_start_seconds = 0;
if (vidstart.match('m')) {
vid_start_minutes = parseInt(vidstart.split("m")[0], 10);
vid_start_seconds = parseInt(vidstart.split("m")[1].split("s")[0], 10);
this.media_id.start = (vid_start_minutes * 60) + vid_start_seconds;
} else {
this.media_id.start = 0;
}
} else {
this.media_id.start = 0;
}
// Determine HD
if (typeof(this.media_id.hd) != 'undefined') {
this.media_id.hd = true;
} else {
this.media_id.hd = false;
}
this.createPlayer();
},
createPlayer: function() {
var self = this;
clearTimeout(this.timer);
if(typeof YT != 'undefined' && typeof YT.Player != 'undefined') {
// Create Player
this.player = new YT.Player(this._el.content_item.id, {
playerVars: {
enablejsapi: 1,
color: 'white',
autohide: 1,
showinfo: 0,
theme: 'light',
start: this.media_id.start,
fs: 0,
rel: 0
},
videoId: this.media_id.id,
events: {
onReady: function() {
self.onPlayerReady();
// After Loaded
//self.onLoaded();
},
'onStateChange': self.onStateChange
}
});
} else {
this.timer = setTimeout(function() {
self.createPlayer();
}, 1000);
}
this.onLoaded();
},
/* Events
================================================== */
onPlayerReady: function(e) {
this.youtube_loaded = true;
this._el.content_item = document.getElementById(this._el.content_item.id);
this.onMediaLoaded();
this.onLoaded();
},
onStateChange: function(e) {
}
});
/* VCO.Media.SLider
Produces a Slider
Takes a data object and populates a dom object
TODO
Placeholder
================================================== */
VCO.Media.Slider = VCO.Media.extend({
includes: [VCO.Events],
/* Load the media
================================================== */
_loadMedia: function() {
this._el.content_item = VCO.Dom.create("img", "vco-media-item vco-media-image", this._el.content);
this._el.content_item.src = this.data.url;
this.onLoaded();
}
});
/* VCO.Slide
Creates a slide. Takes a data object and
populates the slide with content.
================================================== */
VCO.Slide = VCO.Class.extend({
includes: [VCO.Events, VCO.DomMixins],
_el: {},
/* Constructor
================================================== */
initialize: function(data, options, title_slide) {
// DOM Elements
this._el = {
container: {},
scroll_container: {},
background: {},
content_container: {},
content: {},
call_to_action: null
};
// Components
this._media = null;
this._mediaclass = {};
this._text = {};
// State
this._state = {
loaded: false
};
this.has = {
headline: false,
text: false,
media: false,
title: false,
background: {
image: false,
color: false,
color_value :""
}
}
this.has.title = title_slide;
this.title = "";
// Data
this.data = {
uniqueid: null,
background: null,
date: null,
location: null,
text: null,
media: null
};
// Options
this.options = {
// animation
duration: 1000,
slide_padding_lr: 40,
ease: VCO.Ease.easeInSpline,
width: 600,
height: 600,
skinny_size: 650,
media_name: ""
};
// Actively Displaying
this.active = false;
// Animation Object
this.animator = {};
// Merge Data and Options
VCO.Util.mergeData(this.options, options);
VCO.Util.mergeData(this.data, data);
this._initLayout();
this._initEvents();
},
/* Adding, Hiding, Showing etc
================================================== */
show: function() {
this.animator = VCO.Animate(this._el.slider_container, {
left: -(this._el.container.offsetWidth * n) + "px",
duration: this.options.duration,
easing: this.options.ease
});
},
hide: function() {
},
setActive: function(is_active) {
this.active = is_active;
if (this.active) {
if (this.data.background) {
this.fire("background_change", this.has.background);
}
this.loadMedia();
} else {
this.stopMedia();
}
},
addTo: function(container) {
container.appendChild(this._el.container);
//this.onAdd();
},
removeFrom: function(container) {
container.removeChild(this._el.container);
},
updateDisplay: function(w, h, l) {
this._updateDisplay(w, h, l);
},
loadMedia: function() {
if (this._media && !this._state.loaded) {
this._media.loadMedia();
this._state.loaded = true;
}
},
stopMedia: function() {
if (this._media && this._state.loaded) {
this._media.stopMedia();
}
},
getBackground: function() {
return this.has.background;
},
scrollToTop: function() {
this._el.container.scrollTop = 0;
},
addCallToAction: function(str) {
this._el.call_to_action = VCO.Dom.create("div", "vco-slide-calltoaction", this._el.content_container);
this._el.call_to_action.innerHTML = "" + str + "";
VCO.DomEvent.addListener(this._el.call_to_action, 'click', this._onCallToAction, this);
},
/* Events
================================================== */
_onCallToAction: function(e) {
this.fire("call_to_action", e);
},
/* Private Methods
================================================== */
_initLayout: function () {
// Create Layout
this._el.container = VCO.Dom.create("div", "vco-slide");
if (this.data.uniqueid) {
this._el.container.id = this.data.uniqueid;
}
this._el.scroll_container = VCO.Dom.create("div", "vco-slide-scrollable-container", this._el.container);
this._el.content_container = VCO.Dom.create("div", "vco-slide-content-container", this._el.scroll_container);
this._el.content = VCO.Dom.create("div", "vco-slide-content", this._el.content_container);
this._el.background = VCO.Dom.create("div", "vco-slide-background", this._el.container);
// Style Slide Background
if (this.data.background) {
if (this.data.background.url) {
this.has.background.image = true;
this._el.container.className += ' vco-full-image-background';
//this._el.container.style.backgroundImage="url('" + this.data.background.url + "')";
this.has.background.color_value = "#000";
this._el.background.style.backgroundImage = "url('" + this.data.background.url + "')";
this._el.background.style.display = "block";
}
if (this.data.background.color) {
this.has.background.color = true;
this._el.container.className += ' vco-full-color-background';
this.has.background.color_value = this.data.background.color;
//this._el.container.style.backgroundColor = this.data.background.color;
//this._el.background.style.backgroundColor = this.data.background.color;
//this._el.background.style.display = "block";
}
if (this.data.background.text_background) {
this._el.container.className += ' vco-text-background';
}
}
// Determine Assets for layout and loading
if (this.data.media && this.data.media.url && this.data.media.url != "") {
this.has.media = true;
}
if (this.data.text && this.data.text.text) {
this.has.text = true;
}
if (this.data.text && this.data.text.headline) {
this.has.headline = true;
this.title = this.data.text.headline;
}
// Create Media
if (this.has.media) {
// Determine the media type
this.data.media.mediatype = VCO.MediaType(this.data.media);
this.options.media_name = this.data.media.mediatype.name;
this.options.media_type = this.data.media.mediatype.type;
// Create a media object using the matched class name
this._media = new this.data.media.mediatype.cls(this.data.media, this.options);
}
// Create Text
if (this.has.text || this.has.headline) {
this._text = new VCO.Media.Text(this.data.text, {title:this.has.title});
}
// Add to DOM
if (!this.has.text && !this.has.headline && this.has.media) {
this._el.container.className += ' vco-slide-media-only';
this._media.addTo(this._el.content);
} else if (this.has.headline && this.has.media && !this.has.text) {
this._el.container.className += ' vco-slide-media-only';
this._text.addTo(this._el.content);
this._media.addTo(this._el.content);
} else if (this.has.text && this.has.media) {
this._media.addTo(this._el.content);
this._text.addTo(this._el.content);
} else if (this.has.text || this.has.headline) {
this._el.container.className += ' vco-slide-text-only';
this._text.addTo(this._el.content);
}
// Fire event that the slide is loaded
this.onLoaded();
},
_initEvents: function() {
},
// Update Display
_updateDisplay: function(width, height, layout) {
var pad_left, pad_right, new_width;
if (width) {
this.options.width = width;
} else {
this.options.width = this._el.container.offsetWidth;
}
if(VCO.Browser.mobile && (this.options.width <= this.options.skinny_size)) {
pad_left = 0 + "px";
pad_right = 0 + "px";
new_width = this.options.width - 0 + "px";
} else if (layout == "landscape") {
pad_left = 40 + "px";
pad_right = 75 + "px";
new_width = this.options.width - (75 + 40) + "px";
} else if (this.options.width <= this.options.skinny_size) {
pad_left = this.options.slide_padding_lr + "px";
pad_right = this.options.slide_padding_lr + "px";
new_width = this.options.width - (this.options.slide_padding_lr * 2) + "px";
} else {
pad_left = this.options.slide_padding_lr + "px";
pad_right = this.options.slide_padding_lr + "px";
new_width = this.options.width - (this.options.slide_padding_lr * 2) + "px";
}
this._el.content.style.paddingLeft = pad_left;
this._el.content.style.paddingRight = pad_right;
this._el.content.style.width = new_width;
if (this._el.call_to_action) {
this._el.call_to_action.style.paddingLeft = pad_left;
this._el.call_to_action.style.paddingRight = pad_right;
this._el.call_to_action.style.width = new_width;
}
if (height) {
this.options.height = height;
//this._el.scroll_container.style.height = this.options.height + "px";
} else {
this.options.height = this._el.container.offsetHeight;
}
if (this._media) {
if (!this.has.text && this.has.headline) {
this._media.updateDisplay(this.options.width, (this.options.height - this._text.headlineHeight()), layout);
} else {
this._media.updateDisplay(this.options.width, this.options.height, layout);
}
}
}
});
/* VCO.SlideNav
Navigation for Slideshows
================================================== */
// TODO null out data
VCO.SlideNav = VCO.Class.extend({
includes: [VCO.Events, VCO.DomMixins],
_el: {},
/* Constructor
================================================== */
initialize: function(data, options, add_to_container) {
// DOM ELEMENTS
this._el = {
container: {},
content_container: {},
icon: {},
title: {},
description: {}
};
// Media Type
this.mediatype = {};
// Data
this.data = {
title: "Navigation",
description: "Description"
};
//Options
this.options = {
direction: "previous"
};
this.animator = null;
this.animator_position = null;
// Merge Data and Options
VCO.Util.mergeData(this.options, options);
VCO.Util.mergeData(this.data, data);
this._el.container = VCO.Dom.create("div", "vco-slidenav-" + this.options.direction);
if (VCO.Browser.mobile) {
this._el.container.setAttribute("ontouchstart"," ");
}
this._initLayout();
this._initEvents();
if (add_to_container) {
add_to_container.appendChild(this._el.container);
};
},
/* Update Content
================================================== */
update: function(d) {
this._update(d);
},
/* Color
================================================== */
setColor: function(inverted) {
if (inverted) {
this._el.content_container.className = 'vco-slidenav-content-container vco-slidenav-inverted';
} else {
this._el.content_container.className = 'vco-slidenav-content-container';
}
},
/* Position
================================================== */
updatePosition: function(pos, use_percent, duration, ease, start_value, return_to_default) {
var self = this,
ani = {
duration: duration,
easing: ease,
complete: function() {
self._onUpdatePositionComplete(return_to_default);
}
};
var _start_value = start_value;
for (var name in pos) {
if (pos.hasOwnProperty(name)) {
if (use_percent) {
ani[name] = pos[name] + "%";
} else {
ani[name] = pos[name] + "px";
}
}
}
if (this.animator_position) {
this.animator_position.stop();
}
var prop_to_set;
if (ani.right) {
prop_to_set = "right";
} else {
prop_to_set = "left";
}
if (use_percent) {
this._el.container.style[prop_to_set] = _start_value + "%";
} else {
this._el.container.style[prop_to_set] = _start_value + "px";
}
this.animator_position = VCO.Animate(this._el.container, ani);
},
_onUpdatePositionComplete: function(return_to_default) {
if (return_to_default) {
this._el.container.style.left = "";
this._el.container.style.right = "";
}
},
/* Events
================================================== */
_onMouseClick: function() {
this.fire("clicked", this.options);
},
/* Private Methods
================================================== */
_update: function(d) {
// update data
this.data = VCO.Util.mergeData(this.data, d);
// Title
if (this.data.title != "") {
this._el.title.innerHTML = this.data.title;
}
// Date
if (this.data.date != "") {
this._el.description.innerHTML = this.data.description;
}
},
_initLayout: function () {
// Create Layout
this._el.content_container = VCO.Dom.create("div", "vco-slidenav-content-container", this._el.container);
this._el.icon = VCO.Dom.create("div", "vco-slidenav-icon", this._el.content_container);
this._el.title = VCO.Dom.create("div", "vco-slidenav-title", this._el.content_container);
this._el.description = VCO.Dom.create("div", "vco-slidenav-description", this._el.content_container);
this._el.icon.innerHTML = " "
this._update();
},
_initEvents: function () {
VCO.DomEvent.addListener(this._el.container, 'click', this._onMouseClick, this);
}
});
/* StorySlider
is the central class of the API - it is used to create a StorySlider
Events:
nav_next
nav_previous
slideDisplayUpdate
loaded
slideAdded
slideLoaded
slideRemoved
================================================== */
VCO.StorySlider = VCO.Class.extend({
includes: VCO.Events,
/* Private Methods
================================================== */
initialize: function (elem, data, options, init) {
// DOM ELEMENTS
this._el = {
container: {},
background: {},
slider_container_mask: {},
slider_container: {},
slider_item_container: {}
};
this._nav = {};
this._nav.previous = {};
this._nav.next = {};
// Slide Spacing
this.slide_spacing = 0;
// Slides Array
this._slides = [];
// Swipe Object
this._swipable;
// Preload Timer
this.preloadTimer;
// Message
this._message;
// Current Slide
this.current_slide = 0;
// Current Background Color
this.current_bg_color = null;
// Data Object
this.data = {};
this.options = {
id: "",
layout: "portrait",
width: 600,
height: 600,
default_bg_color: {r:256, g:256, b:256},
slide_padding_lr: 40, // padding on slide of slide
start_at_slide: 1,
slide_default_fade: "0%", // landscape fade
// animation
duration: 1000,
ease: VCO.Ease.easeInOutQuint,
// interaction
dragging: true,
trackResize: true
};
// Main element ID
if (typeof elem === 'object') {
this._el.container = elem;
this.options.id = VCO.Util.unique_ID(6, "vco");
} else {
this.options.id = elem;
this._el.container = VCO.Dom.get(elem);
}
if (!this._el.container.id) {
this._el.container.id = this.options.id;
}
// Animation Object
this.animator = null;
this.animator_background = null;
// Merge Data and Options
VCO.Util.mergeData(this.options, options);
VCO.Util.mergeData(this.data, data);
if (init) {
this.init();
}
},
init: function() {
this._initLayout();
this._initEvents();
this._initData();
this._updateDisplay();
// Go to initial slide
this.goTo(this.options.start_at_slide);
this._onLoaded();
this._introInterface();
},
/* Public
================================================== */
updateDisplay: function(w, h, a, l) {
this._updateDisplay(w, h, a, l);
},
// Create a slide
createSlide: function(d) {
this._createSlide(d);
},
// Create Many Slides from an array
createSlides: function(array) {
this._createSlides(array);
},
/* Create Slides
================================================== */
_createSlides: function(array) {
for (var i = 0; i < array.length; i++) {
if (array[i].uniqueid == "") {
array[i].uniqueid = VCO.Util.unique_ID(6, "vco-slide");
}
if (i == 0) {
this._createSlide(array[i], true);
} else {
this._createSlide(array[i], false);
}
};
},
_createSlide: function(d, title_slide) {
var slide = new VCO.Slide(d, this.options, title_slide);
this._addSlide(slide);
this._slides.push(slide);
},
_destroySlide: function(slide) {
this._removeSlide(slide);
for (var i = 0; i < this._slides.length; i++) {
if (this._slides[i] == slide) {
this._slides.splice(i, 1);
}
}
},
_addSlide:function(slide) {
slide.addTo(this._el.slider_item_container);
slide.on('added', this._onSlideAdded, this);
slide.on('background_change', this._onBackgroundChange, this);
},
_removeSlide: function(slide) {
slide.removeFrom(this._el.slider_item_container);
slide.off('added', this._onSlideAdded, this);
slide.off('background_change', this._onBackgroundChange);
},
/* Message
================================================== */
/* Navigation
================================================== */
goToId: function(n, fast, displayupdate) {
if (typeof n == 'string' || n instanceof String) {
_n = VCO.Util.findArrayNumberByUniqueID(n, this._slides, "uniqueid");
} else {
_n = n;
}
this.goTo(_n, fast, displayupdate);
},
goTo: function(n, fast, displayupdate) {
var self = this;
this.changeBackground({color_value:"", image:false});
// Clear Preloader Timer
if (this.preloadTimer) {
clearTimeout(this.preloadTimer);
}
// Set Slide Active State
for (var i = 0; i < this._slides.length; i++) {
this._slides[i].setActive(false);
}
if (n < this._slides.length && n >= 0) {
this.current_slide = n;
// Stop animation
if (this.animator) {
this.animator.stop();
}
if (this._swipable) {
this._swipable.stopMomentum();
}
if (fast) {
this._el.slider_container.style.left = -(this.slide_spacing * n) + "px";
this._onSlideChange(displayupdate);
} else {
this.animator = VCO.Animate(this._el.slider_container, {
left: -(this.slide_spacing * n) + "px",
duration: this.options.duration,
easing: this.options.ease,
complete: this._onSlideChange(displayupdate)
});
}
// Set Slide Active State
this._slides[this.current_slide].setActive(true);
// Update Navigation and Info
if (this._slides[this.current_slide + 1]) {
this.showNav(this._nav.next, true);
this._nav.next.update(this.getNavInfo(this._slides[this.current_slide + 1]));
} else {
this.showNav(this._nav.next, false);
}
if (this._slides[this.current_slide - 1]) {
this.showNav(this._nav.previous, true);
this._nav.previous.update(this.getNavInfo(this._slides[this.current_slide - 1]));
} else {
this.showNav(this._nav.previous, false);
}
// Preload Slides
this.preloadTimer = setTimeout(function() {
self.preloadSlides();
}, this.options.duration);
}
},
preloadSlides: function() {
if (this._slides[this.current_slide + 1]) {
this._slides[this.current_slide + 1].loadMedia();
this._slides[this.current_slide + 1].scrollToTop();
}
if (this._slides[this.current_slide + 2]) {
this._slides[this.current_slide + 2].loadMedia();
this._slides[this.current_slide + 2].scrollToTop();
}
if (this._slides[this.current_slide - 1]) {
this._slides[this.current_slide - 1].loadMedia();
this._slides[this.current_slide - 1].scrollToTop();
}
if (this._slides[this.current_slide - 2]) {
this._slides[this.current_slide - 2].loadMedia();
this._slides[this.current_slide - 2].scrollToTop();
}
},
getNavInfo: function(slide) {
var n = {
title: "",
description: ""
};
if (slide.data.text) {
if (slide.data.text.headline) {
n.title = slide.data.text.headline;
}
/*
// Disabling location in description for now.
if (slide.data.location) {
if (slide.data.location.name) {
n.description = slide.data.location.name;
}
}
*/
}
return n;
},
next: function() {
if ((this.current_slide +1) < (this._slides.length)) {
this.goTo(this.current_slide +1);
} else {
this.goTo(this.current_slide);
}
},
previous: function() {
if (this.current_slide -1 >= 0) {
this.goTo(this.current_slide -1);
} else {
this.goTo(this.current_slide);
}
},
showNav: function(nav_obj, show) {
if (this.options.width <= 500 && VCO.Browser.mobile) {
} else {
if (show) {
nav_obj.show();
} else {
nav_obj.hide();
}
}
},
changeBackground: function(bg) {
var self = this,
do_animation = false;
var bg_color = {r:256, g:256, b:256},
bg_color_rgb,
bg_percent_start = this.options.slide_default_fade,
bg_percent_end = "15%",
bg_alpha_end = "0.87",
bg_css = "",
bg_old = this._el.background.getAttribute('style');
if (bg.color_value) {
bg_color = VCO.Util.hexToRgb(bg.color_value);
} else {
bg_color = this.options.default_bg_color;
}
// Stop animation
if (this.animator_background) {
this.animator_background.stop();
}
bg_color_rgb = bg_color.r + "," + bg_color.g + "," + bg_color.b;
if (!this.current_bg_color || this.current_bg_color != bg_color_rgb) {
this.current_bg_color = bg_color_rgb;
do_animation = true;
}
if (do_animation) {
// Figure out CSS
if (this.options.layout == "landscape") {
this._nav.next.setColor(false);
this._nav.previous.setColor(false);
// If background is not white, less fade is better
if (bg_color.r < 255 && bg_color.g < 255 && bg_color.b < 255) {
bg_percent_start = "15%";
}
if (bg.image) {
bg_percent_start = "0%";
}
bg_css += "opacity:0;"
bg_css += "background-image: -webkit-linear-gradient(left, color-stop(rgba(" + bg_color_rgb + ",0.0001 ) " + bg_percent_start + "), color-stop(rgba(" + bg_color_rgb + "," + bg_alpha_end + ") " + bg_percent_end + "));";
bg_css += "background-image: linear-gradient(to right, rgba(" + bg_color_rgb + ",0.0001 ) "+ bg_percent_start + ", rgba(" + bg_color_rgb + "," + bg_alpha_end + ") " + bg_percent_end + ");";
bg_css += "background-repeat: repeat-x;";
bg_css += "filter: e(%('progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)',argb(" + bg_color_rgb + ", 0.0001),argb(" + bg_color_rgb + ",0.80)));";
} else {
if (bg.color_value) {
bg_css += 'background-color:' + bg.color_value + ";";
} else {
bg_css += "background-color:#FFF;";
}
if (bg_color.r < 255 && bg_color.g < 255 && bg_color.b < 255 || bg.image) {
this._nav.next.setColor(true);
this._nav.previous.setColor(true);
} else {
this._nav.next.setColor(false);
this._nav.previous.setColor(false);
}
}
// FADE OUT IN
this.animator_background = VCO.Animate(this._el.background, {
opacity: 0,
duration: this.options.duration/2,
easing: this.options.ease,
complete: function() {
self.fadeInBackground(bg_css);
}
});
}
},
fadeInBackground: function(bg_css) {
if (this.animator_background) {
this.animator_background.stop();
}
if (bg_css) {
this._el.background.setAttribute("style", bg_css);
}
this.animator_background = VCO.Animate(this._el.background, {
opacity: 1,
duration: this.options.duration/2,
easing: this.options.ease
});
},
/* Private Methods
================================================== */
// Update Display
_updateDisplay: function(width, height, animate, layout) {
var nav_pos, _layout;
if(typeof layout === 'undefined'){
_layout = this.options.layout;
} else {
_layout = layout;
}
this.options.layout = _layout;
this.slide_spacing = this.options.width*2;
if (width) {
this.options.width = width;
} else {
this.options.width = this._el.container.offsetWidth;
}
if (height) {
this.options.height = height;
} else {
this.options.height = this._el.container.offsetHeight;
}
//this._el.container.style.height = this.options.height;
// position navigation
nav_pos = (this.options.height/2);
this._nav.next.setPosition({top:nav_pos});
this._nav.previous.setPosition({top:nav_pos});
// Position slides
for (var i = 0; i < this._slides.length; i++) {
this._slides[i].updateDisplay(this.options.width, this.options.height, _layout);
this._slides[i].setPosition({left:(this.slide_spacing * i), top:0});
};
// Go to the current slide
this.goTo(this.current_slide, true, true);
},
_introInterface: function() {
if (this.options.call_to_action) {
var _str = VCO.Language.messages.start;
if (this.options.call_to_action_text != "") {
_str = this.options.call_to_action_text;
}
this._slides[0].addCallToAction(_str);
this._slides[0].on('call_to_action', this.next, this);
}
if (this.options.width <= this.options.skinny_size) {
} else {
this._nav.next.updatePosition({right:"130"}, false, this.options.duration*3, this.options.ease, -100, true);
this._nav.previous.updatePosition({left:"-100"}, true, this.options.duration*3, this.options.ease, -200, true);
}
},
/* Init
================================================== */
_initLayout: function () {
this._el.container.className += ' vco-storyslider';
// Create Layout
this._el.slider_container_mask = VCO.Dom.create('div', 'vco-slider-container-mask', this._el.container);
this._el.background = VCO.Dom.create('div', 'vco-slider-background', this._el.container);
this._el.slider_container = VCO.Dom.create('div', 'vco-slider-container vcoanimate', this._el.slider_container_mask);
this._el.slider_item_container = VCO.Dom.create('div', 'vco-slider-item-container', this._el.slider_container);
// Update Size
this.options.width = this._el.container.offsetWidth;
this.options.height = this._el.container.offsetHeight;
// Create Navigation
this._nav.previous = new VCO.SlideNav({title: "Previous", description: "description"}, {direction:"previous"});
this._nav.next = new VCO.SlideNav({title: "Next",description: "description"}, {direction:"next"});
// add the navigation to the dom
this._nav.next.addTo(this._el.container);
this._nav.previous.addTo(this._el.container);
this._el.slider_container.style.left="0px";
if (VCO.Browser.touch) {
//this._el.slider_touch_mask = VCO.Dom.create('div', 'vco-slider-touch-mask', this._el.slider_container_mask);
this._swipable = new VCO.Swipable(this._el.slider_container_mask, this._el.slider_container, {
enable: {x:true, y:false},
snap: true
});
this._swipable.enable();
// Message
this._message = new VCO.Message({}, {
message_class: "vco-message-full",
message_icon_class: "vco-icon-swipe-left"
});
this._message.updateMessage("Swipe to Navigate
OK");
this._message.addTo(this._el.container);
}
},
_initEvents: function () {
this._nav.next.on('clicked', this._onNavigation, this);
this._nav.previous.on('clicked', this._onNavigation, this);
if (this._message) {
this._message.on('clicked', this._onMessageClick, this);
}
if (this._swipable) {
this._swipable.on('swipe_left', this._onNavigation, this);
this._swipable.on('swipe_right', this._onNavigation, this);
this._swipable.on('swipe_nodirection', this._onSwipeNoDirection, this);
}
},
_initData: function() {
// Create Slides and then add them
this._createSlides(this.data.slides);
},
/* Events
================================================== */
_onBackgroundChange: function(e) {
var slide_background = this._slides[this.current_slide].getBackground();
this.changeBackground(e);
this.fire("colorchange", slide_background);
},
_onMessageClick: function(e) {
this._message.hide();
},
_onSwipeNoDirection: function(e) {
this.goTo(this.current_slide);
},
_onNavigation: function(e) {
if (e.direction == "next" || e.direction == "left") {
this.next();
} else if (e.direction == "previous" || e.direction == "right") {
this.previous();
}
this.fire("nav_" + e.direction, this.data);
},
_onSlideAdded: function(e) {
trace("slideadded")
this.fire("slideAdded", this.data);
},
_onSlideRemoved: function(e) {
this.fire("slideAdded", this.data);
},
_onSlideChange: function(displayupdate) {
if (!displayupdate) {
this.fire("change", {current_slide:this.current_slide, uniqueid:this._slides[this.current_slide].data.uniqueid});
}
},
_onMouseClick: function(e) {
},
_fireMouseEvent: function (e) {
if (!this._loaded) {
return;
}
var type = e.type;
type = (type === 'mouseenter' ? 'mouseover' : (type === 'mouseleave' ? 'mouseout' : type));
if (!this.hasEventListeners(type)) {
return;
}
if (type === 'contextmenu') {
VCO.DomEvent.preventDefault(e);
}
this.fire(type, {
latlng: "something", //this.mouseEventToLatLng(e),
layerPoint: "something else" //this.mouseEventToLayerPoint(e)
});
},
_onLoaded: function() {
this.fire("loaded", this.data);
this.fire("title", {title:this._slides[0].title});
}
});
var oldL = window.L,
L = {};
L.version = '0.7.2';
// define Leaflet for Node module pattern loaders, including Browserify
if (typeof module === 'object' && typeof module.exports === 'object') {
module.exports = L;
// define Leaflet as an AMD module
} else if (typeof define === 'function' && define.amd) {
define(L);
}
// define Leaflet as a global L variable, saving the original L to restore later if needed
L.noConflict = function () {
window.L = oldL;
return this;
};
window.L = L;
/*
* L.Util contains various utility functions used throughout Leaflet code.
*/
L.Util = {
extend: function (dest) { // (Object[, Object, ...]) ->
var sources = Array.prototype.slice.call(arguments, 1),
i, j, len, src;
for (j = 0, len = sources.length; j < len; j++) {
src = sources[j] || {};
for (i in src) {
if (src.hasOwnProperty(i)) {
dest[i] = src[i];
}
}
}
return dest;
},
bind: function (fn, obj) { // (Function, Object) -> Function
var args = arguments.length > 2 ? Array.prototype.slice.call(arguments, 2) : null;
return function () {
return fn.apply(obj, args || arguments);
};
},
stamp: (function () {
var lastId = 0,
key = '_leaflet_id';
return function (obj) {
obj[key] = obj[key] || ++lastId;
return obj[key];
};
}()),
invokeEach: function (obj, method, context) {
var i, args;
if (typeof obj === 'object') {
args = Array.prototype.slice.call(arguments, 3);
for (i in obj) {
method.apply(context, [i, obj[i]].concat(args));
}
return true;
}
return false;
},
limitExecByInterval: function (fn, time, context) {
var lock, execOnUnlock;
return function wrapperFn() {
var args = arguments;
if (lock) {
execOnUnlock = true;
return;
}
lock = true;
setTimeout(function () {
lock = false;
if (execOnUnlock) {
wrapperFn.apply(context, args);
execOnUnlock = false;
}
}, time);
fn.apply(context, args);
};
},
falseFn: function () {
return false;
},
formatNum: function (num, digits) {
var pow = Math.pow(10, digits || 5);
return Math.round(num * pow) / pow;
},
trim: function (str) {
return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
},
splitWords: function (str) {
return L.Util.trim(str).split(/\s+/);
},
setOptions: function (obj, options) {
obj.options = L.extend({}, obj.options, options);
return obj.options;
},
getParamString: function (obj, existingUrl, uppercase) {
var params = [];
for (var i in obj) {
params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
}
return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
},
template: function (str, data) {
return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) {
var value = data[key];
if (value === undefined) {
throw new Error('No value provided for variable ' + str);
} else if (typeof value === 'function') {
value = value(data);
}
return value;
});
},
isArray: Array.isArray || function (obj) {
return (Object.prototype.toString.call(obj) === '[object Array]');
},
emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
};
(function () {
// inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
function getPrefixed(name) {
var i, fn,
prefixes = ['webkit', 'moz', 'o', 'ms'];
for (i = 0; i < prefixes.length && !fn; i++) {
fn = window[prefixes[i] + name];
}
return fn;
}
var lastTime = 0;
function timeoutDefer(fn) {
var time = +new Date(),
timeToCall = Math.max(0, 16 - (time - lastTime));
lastTime = time + timeToCall;
return window.setTimeout(fn, timeToCall);
}
var requestFn = window.requestAnimationFrame ||
getPrefixed('RequestAnimationFrame') || timeoutDefer;
var cancelFn = window.cancelAnimationFrame ||
getPrefixed('CancelAnimationFrame') ||
getPrefixed('CancelRequestAnimationFrame') ||
function (id) { window.clearTimeout(id); };
L.Util.requestAnimFrame = function (fn, context, immediate, element) {
fn = L.bind(fn, context);
if (immediate && requestFn === timeoutDefer) {
fn();
} else {
return requestFn.call(window, fn, element);
}
};
L.Util.cancelAnimFrame = function (id) {
if (id) {
cancelFn.call(window, id);
}
};
}());
// shortcuts for most used utility functions
L.extend = L.Util.extend;
L.bind = L.Util.bind;
L.stamp = L.Util.stamp;
L.setOptions = L.Util.setOptions;
/*
* L.Class powers the OOP facilities of the library.
* Thanks to John Resig and Dean Edwards for inspiration!
*/
L.Class = function () {};
L.Class.extend = function (props) {
// extended class with the new prototype
var NewClass = function () {
// call the constructor
if (this.initialize) {
this.initialize.apply(this, arguments);
}
// call all constructor hooks
if (this._initHooks) {
this.callInitHooks();
}
};
// instantiate class without calling constructor
var F = function () {};
F.prototype = this.prototype;
var proto = new F();
proto.constructor = NewClass;
NewClass.prototype = proto;
//inherit parent's statics
for (var i in this) {
if (this.hasOwnProperty(i) && i !== 'prototype') {
NewClass[i] = this[i];
}
}
// mix static properties into the class
if (props.statics) {
L.extend(NewClass, props.statics);
delete props.statics;
}
// mix includes into the prototype
if (props.includes) {
L.Util.extend.apply(null, [proto].concat(props.includes));
delete props.includes;
}
// merge options
if (props.options && proto.options) {
props.options = L.extend({}, proto.options, props.options);
}
// mix given properties into the prototype
L.extend(proto, props);
proto._initHooks = [];
var parent = this;
// jshint camelcase: false
NewClass.__super__ = parent.prototype;
// add method for calling all hooks
proto.callInitHooks = function () {
if (this._initHooksCalled) { return; }
if (parent.prototype.callInitHooks) {
parent.prototype.callInitHooks.call(this);
}
this._initHooksCalled = true;
for (var i = 0, len = proto._initHooks.length; i < len; i++) {
proto._initHooks[i].call(this);
}
};
return NewClass;
};
// method for adding properties to prototype
L.Class.include = function (props) {
L.extend(this.prototype, props);
};
// merge new default options to the Class
L.Class.mergeOptions = function (options) {
L.extend(this.prototype.options, options);
};
// add a constructor hook
L.Class.addInitHook = function (fn) { // (Function) || (String, args...)
var args = Array.prototype.slice.call(arguments, 1);
var init = typeof fn === 'function' ? fn : function () {
this[fn].apply(this, args);
};
this.prototype._initHooks = this.prototype._initHooks || [];
this.prototype._initHooks.push(init);
};
/*
* L.Mixin.Events is used to add custom events functionality to Leaflet classes.
*/
var eventsKey = '_leaflet_events';
L.Mixin = {};
L.Mixin.Events = {
addEventListener: function (types, fn, context) { // (String, Function[, Object]) or (Object[, Object])
// types can be a map of types/handlers
if (L.Util.invokeEach(types, this.addEventListener, this, fn, context)) { return this; }
var events = this[eventsKey] = this[eventsKey] || {},
contextId = context && context !== this && L.stamp(context),
i, len, event, type, indexKey, indexLenKey, typeIndex;
// types can be a string of space-separated words
types = L.Util.splitWords(types);
for (i = 0, len = types.length; i < len; i++) {
event = {
action: fn,
context: context || this
};
type = types[i];
if (contextId) {
// store listeners of a particular context in a separate hash (if it has an id)
// gives a major performance boost when removing thousands of map layers
indexKey = type + '_idx';
indexLenKey = indexKey + '_len';
typeIndex = events[indexKey] = events[indexKey] || {};
if (!typeIndex[contextId]) {
typeIndex[contextId] = [];
// keep track of the number of keys in the index to quickly check if it's empty
events[indexLenKey] = (events[indexLenKey] || 0) + 1;
}
typeIndex[contextId].push(event);
} else {
events[type] = events[type] || [];
events[type].push(event);
}
}
return this;
},
hasEventListeners: function (type) { // (String) -> Boolean
var events = this[eventsKey];
return !!events && ((type in events && events[type].length > 0) ||
(type + '_idx' in events && events[type + '_idx_len'] > 0));
},
removeEventListener: function (types, fn, context) { // ([String, Function, Object]) or (Object[, Object])
if (!this[eventsKey]) {
return this;
}
if (!types) {
return this.clearAllEventListeners();
}
if (L.Util.invokeEach(types, this.removeEventListener, this, fn, context)) { return this; }
var events = this[eventsKey],
contextId = context && context !== this && L.stamp(context),
i, len, type, listeners, j, indexKey, indexLenKey, typeIndex, removed;
types = L.Util.splitWords(types);
for (i = 0, len = types.length; i < len; i++) {
type = types[i];
indexKey = type + '_idx';
indexLenKey = indexKey + '_len';
typeIndex = events[indexKey];
if (!fn) {
// clear all listeners for a type if function isn't specified
delete events[type];
delete events[indexKey];
delete events[indexLenKey];
} else {
listeners = contextId && typeIndex ? typeIndex[contextId] : events[type];
if (listeners) {
for (j = listeners.length - 1; j >= 0; j--) {
if ((listeners[j].action === fn) && (!context || (listeners[j].context === context))) {
removed = listeners.splice(j, 1);
// set the old action to a no-op, because it is possible
// that the listener is being iterated over as part of a dispatch
removed[0].action = L.Util.falseFn;
}
}
if (context && typeIndex && (listeners.length === 0)) {
delete typeIndex[contextId];
events[indexLenKey]--;
}
}
}
}
return this;
},
clearAllEventListeners: function () {
delete this[eventsKey];
return this;
},
fireEvent: function (type, data) { // (String[, Object])
if (!this.hasEventListeners(type)) {
return this;
}
var event = L.Util.extend({}, data, { type: type, target: this });
var events = this[eventsKey],
listeners, i, len, typeIndex, contextId;
if (events[type]) {
// make sure adding/removing listeners inside other listeners won't cause infinite loop
listeners = events[type].slice();
for (i = 0, len = listeners.length; i < len; i++) {
listeners[i].action.call(listeners[i].context, event);
}
}
// fire event for the context-indexed listeners as well
typeIndex = events[type + '_idx'];
for (contextId in typeIndex) {
listeners = typeIndex[contextId].slice();
if (listeners) {
for (i = 0, len = listeners.length; i < len; i++) {
listeners[i].action.call(listeners[i].context, event);
}
}
}
return this;
},
addOneTimeEventListener: function (types, fn, context) {
if (L.Util.invokeEach(types, this.addOneTimeEventListener, this, fn, context)) { return this; }
var handler = L.bind(function () {
this
.removeEventListener(types, fn, context)
.removeEventListener(types, handler, context);
}, this);
return this
.addEventListener(types, fn, context)
.addEventListener(types, handler, context);
}
};
L.Mixin.Events.on = L.Mixin.Events.addEventListener;
L.Mixin.Events.off = L.Mixin.Events.removeEventListener;
L.Mixin.Events.once = L.Mixin.Events.addOneTimeEventListener;
L.Mixin.Events.fire = L.Mixin.Events.fireEvent;
/*
* L.Browser handles different browser and feature detections for internal Leaflet use.
*/
(function () {
var ie = 'ActiveXObject' in window,
ielt9 = ie && !document.addEventListener,
// terrible browser detection to work around Safari / iOS / Android browser bugs
ua = navigator.userAgent.toLowerCase(),
webkit = ua.indexOf('webkit') !== -1,
chrome = ua.indexOf('chrome') !== -1,
phantomjs = ua.indexOf('phantom') !== -1,
android = ua.indexOf('android') !== -1,
android23 = ua.search('android [23]') !== -1,
gecko = ua.indexOf('gecko') !== -1,
mobile = typeof orientation !== undefined + '',
msPointer = window.navigator && window.navigator.msPointerEnabled &&
window.navigator.msMaxTouchPoints && !window.PointerEvent,
pointer = (window.PointerEvent && window.navigator.pointerEnabled && window.navigator.maxTouchPoints) ||
msPointer,
retina = ('devicePixelRatio' in window && window.devicePixelRatio > 1) ||
('matchMedia' in window && window.matchMedia('(min-resolution:144dpi)') &&
window.matchMedia('(min-resolution:144dpi)').matches),
doc = document.documentElement,
ie3d = ie && ('transition' in doc.style),
webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23,
gecko3d = 'MozPerspective' in doc.style,
opera3d = 'OTransition' in doc.style,
any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d) && !phantomjs;
// PhantomJS has 'ontouchstart' in document.documentElement, but doesn't actually support touch.
// https://github.com/Leaflet/Leaflet/pull/1434#issuecomment-13843151
var touch = !window.L_NO_TOUCH && !phantomjs && (function () {
var startName = 'ontouchstart';
// IE10+ (We simulate these into touch* events in L.DomEvent and L.DomEvent.Pointer) or WebKit, etc.
if (pointer || (startName in doc)) {
return true;
}
// Firefox/Gecko
var div = document.createElement('div'),
supported = false;
if (!div.setAttribute) {
return false;
}
div.setAttribute(startName, 'return;');
if (typeof div[startName] === 'function') {
supported = true;
}
div.removeAttribute(startName);
div = null;
return supported;
}());
L.Browser = {
ie: ie,
ielt9: ielt9,
webkit: webkit,
gecko: gecko && !webkit && !window.opera && !ie,
android: android,
android23: android23,
chrome: chrome,
ie3d: ie3d,
webkit3d: webkit3d,
gecko3d: gecko3d,
opera3d: opera3d,
any3d: any3d,
mobile: mobile,
mobileWebkit: mobile && webkit,
mobileWebkit3d: mobile && webkit3d,
mobileOpera: mobile && window.opera,
touch: touch,
msPointer: msPointer,
pointer: pointer,
retina: retina
};
}());
/*
* L.Point represents a point with x and y coordinates.
*/
L.Point = function (/*Number*/ x, /*Number*/ y, /*Boolean*/ round) {
this.x = (round ? Math.round(x) : x);
this.y = (round ? Math.round(y) : y);
};
L.Point.prototype = {
clone: function () {
return new L.Point(this.x, this.y);
},
// non-destructive, returns a new point
add: function (point) {
return this.clone()._add(L.point(point));
},
// destructive, used directly for performance in situations where it's safe to modify existing point
_add: function (point) {
this.x += point.x;
this.y += point.y;
return this;
},
subtract: function (point) {
return this.clone()._subtract(L.point(point));
},
_subtract: function (point) {
this.x -= point.x;
this.y -= point.y;
return this;
},
divideBy: function (num) {
return this.clone()._divideBy(num);
},
_divideBy: function (num) {
this.x /= num;
this.y /= num;
return this;
},
multiplyBy: function (num) {
return this.clone()._multiplyBy(num);
},
_multiplyBy: function (num) {
this.x *= num;
this.y *= num;
return this;
},
round: function () {
return this.clone()._round();
},
_round: function () {
this.x = Math.round(this.x);
this.y = Math.round(this.y);
return this;
},
floor: function () {
return this.clone()._floor();
},
_floor: function () {
this.x = Math.floor(this.x);
this.y = Math.floor(this.y);
return this;
},
distanceTo: function (point) {
point = L.point(point);
var x = point.x - this.x,
y = point.y - this.y;
return Math.sqrt(x * x + y * y);
},
equals: function (point) {
point = L.point(point);
return point.x === this.x &&
point.y === this.y;
},
contains: function (point) {
point = L.point(point);
return Math.abs(point.x) <= Math.abs(this.x) &&
Math.abs(point.y) <= Math.abs(this.y);
},
toString: function () {
return 'Point(' +
L.Util.formatNum(this.x) + ', ' +
L.Util.formatNum(this.y) + ')';
}
};
L.point = function (x, y, round) {
if (x instanceof L.Point) {
return x;
}
if (L.Util.isArray(x)) {
return new L.Point(x[0], x[1]);
}
if (x === undefined || x === null) {
return x;
}
return new L.Point(x, y, round);
};
/*
* L.Bounds represents a rectangular area on the screen in pixel coordinates.
*/
L.Bounds = function (a, b) { //(Point, Point) or Point[]
if (!a) { return; }
var points = b ? [a, b] : a;
for (var i = 0, len = points.length; i < len; i++) {
this.extend(points[i]);
}
};
L.Bounds.prototype = {
// extend the bounds to contain the given point
extend: function (point) { // (Point)
point = L.point(point);
if (!this.min && !this.max) {
this.min = point.clone();
this.max = point.clone();
} else {
this.min.x = Math.min(point.x, this.min.x);
this.max.x = Math.max(point.x, this.max.x);
this.min.y = Math.min(point.y, this.min.y);
this.max.y = Math.max(point.y, this.max.y);
}
return this;
},
getCenter: function (round) { // (Boolean) -> Point
return new L.Point(
(this.min.x + this.max.x) / 2,
(this.min.y + this.max.y) / 2, round);
},
getBottomLeft: function () { // -> Point
return new L.Point(this.min.x, this.max.y);
},
getTopRight: function () { // -> Point
return new L.Point(this.max.x, this.min.y);
},
getSize: function () {
return this.max.subtract(this.min);
},
contains: function (obj) { // (Bounds) or (Point) -> Boolean
var min, max;
if (typeof obj[0] === 'number' || obj instanceof L.Point) {
obj = L.point(obj);
} else {
obj = L.bounds(obj);
}
if (obj instanceof L.Bounds) {
min = obj.min;
max = obj.max;
} else {
min = max = obj;
}
return (min.x >= this.min.x) &&
(max.x <= this.max.x) &&
(min.y >= this.min.y) &&
(max.y <= this.max.y);
},
intersects: function (bounds) { // (Bounds) -> Boolean
bounds = L.bounds(bounds);
var min = this.min,
max = this.max,
min2 = bounds.min,
max2 = bounds.max,
xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
return xIntersects && yIntersects;
},
isValid: function () {
return !!(this.min && this.max);
}
};
L.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[])
if (!a || a instanceof L.Bounds) {
return a;
}
return new L.Bounds(a, b);
};
/*
* L.Transformation is an utility class to perform simple point transformations through a 2d-matrix.
*/
L.Transformation = function (a, b, c, d) {
this._a = a;
this._b = b;
this._c = c;
this._d = d;
};
L.Transformation.prototype = {
transform: function (point, scale) { // (Point, Number) -> Point
return this._transform(point.clone(), scale);
},
// destructive transform (faster)
_transform: function (point, scale) {
scale = scale || 1;
point.x = scale * (this._a * point.x + this._b);
point.y = scale * (this._c * point.y + this._d);
return point;
},
untransform: function (point, scale) {
scale = scale || 1;
return new L.Point(
(point.x / scale - this._b) / this._a,
(point.y / scale - this._d) / this._c);
}
};
/*
* L.DomUtil contains various utility functions for working with DOM.
*/
L.DomUtil = {
get: function (id) {
return (typeof id === 'string' ? document.getElementById(id) : id);
},
getStyle: function (el, style) {
var value = el.style[style];
if (!value && el.currentStyle) {
value = el.currentStyle[style];
}
if ((!value || value === 'auto') && document.defaultView) {
var css = document.defaultView.getComputedStyle(el, null);
value = css ? css[style] : null;
}
return value === 'auto' ? null : value;
},
getViewportOffset: function (element) {
var top = 0,
left = 0,
el = element,
docBody = document.body,
docEl = document.documentElement,
pos;
do {
top += el.offsetTop || 0;
left += el.offsetLeft || 0;
//add borders
top += parseInt(L.DomUtil.getStyle(el, 'borderTopWidth'), 10) || 0;
left += parseInt(L.DomUtil.getStyle(el, 'borderLeftWidth'), 10) || 0;
pos = L.DomUtil.getStyle(el, 'position');
if (el.offsetParent === docBody && pos === 'absolute') { break; }
if (pos === 'fixed') {
top += docBody.scrollTop || docEl.scrollTop || 0;
left += docBody.scrollLeft || docEl.scrollLeft || 0;
break;
}
if (pos === 'relative' && !el.offsetLeft) {
var width = L.DomUtil.getStyle(el, 'width'),
maxWidth = L.DomUtil.getStyle(el, 'max-width'),
r = el.getBoundingClientRect();
if (width !== 'none' || maxWidth !== 'none') {
left += r.left + el.clientLeft;
}
//calculate full y offset since we're breaking out of the loop
top += r.top + (docBody.scrollTop || docEl.scrollTop || 0);
break;
}
el = el.offsetParent;
} while (el);
el = element;
do {
if (el === docBody) { break; }
top -= el.scrollTop || 0;
left -= el.scrollLeft || 0;
el = el.parentNode;
} while (el);
return new L.Point(left, top);
},
documentIsLtr: function () {
if (!L.DomUtil._docIsLtrCached) {
L.DomUtil._docIsLtrCached = true;
L.DomUtil._docIsLtr = L.DomUtil.getStyle(document.body, 'direction') === 'ltr';
}
return L.DomUtil._docIsLtr;
},
create: function (tagName, className, container) {
var el = document.createElement(tagName);
el.className = className;
if (container) {
container.appendChild(el);
}
return el;
},
hasClass: function (el, name) {
if (el.classList !== undefined) {
return el.classList.contains(name);
}
var className = L.DomUtil._getClass(el);
return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
},
addClass: function (el, name) {
if (el.classList !== undefined) {
var classes = L.Util.splitWords(name);
for (var i = 0, len = classes.length; i < len; i++) {
el.classList.add(classes[i]);
}
} else if (!L.DomUtil.hasClass(el, name)) {
var className = L.DomUtil._getClass(el);
L.DomUtil._setClass(el, (className ? className + ' ' : '') + name);
}
},
removeClass: function (el, name) {
if (el.classList !== undefined) {
el.classList.remove(name);
} else {
L.DomUtil._setClass(el, L.Util.trim((' ' + L.DomUtil._getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
}
},
_setClass: function (el, name) {
if (el.className.baseVal === undefined) {
el.className = name;
} else {
// in case of SVG element
el.className.baseVal = name;
}
},
_getClass: function (el) {
return el.className.baseVal === undefined ? el.className : el.className.baseVal;
},
setOpacity: function (el, value) {
if ('opacity' in el.style) {
el.style.opacity = value;
} else if ('filter' in el.style) {
var filter = false,
filterName = 'DXImageTransform.Microsoft.Alpha';
// filters collection throws an error if we try to retrieve a filter that doesn't exist
try {
filter = el.filters.item(filterName);
} catch (e) {
// don't set opacity to 1 if we haven't already set an opacity,
// it isn't needed and breaks transparent pngs.
if (value === 1) { return; }
}
value = Math.round(value * 100);
if (filter) {
filter.Enabled = (value !== 100);
filter.Opacity = value;
} else {
el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
}
}
},
testProp: function (props) {
var style = document.documentElement.style;
for (var i = 0; i < props.length; i++) {
if (props[i] in style) {
return props[i];
}
}
return false;
},
getTranslateString: function (point) {
// on WebKit browsers (Chrome/Safari/iOS Safari/Android) using translate3d instead of translate
// makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care
// (same speed either way), Opera 12 doesn't support translate3d
var is3d = L.Browser.webkit3d,
open = 'translate' + (is3d ? '3d' : '') + '(',
close = (is3d ? ',0' : '') + ')';
return open + point.x + 'px,' + point.y + 'px' + close;
},
getScaleString: function (scale, origin) {
var preTranslateStr = L.DomUtil.getTranslateString(origin.add(origin.multiplyBy(-1 * scale))),
scaleStr = ' scale(' + scale + ') ';
return preTranslateStr + scaleStr;
},
setPosition: function (el, point, disable3D) { // (HTMLElement, Point[, Boolean])
// jshint camelcase: false
el._leaflet_pos = point;
if (!disable3D && L.Browser.any3d) {
el.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(point);
} else {
el.style.left = point.x + 'px';
el.style.top = point.y + 'px';
}
},
getPosition: function (el) {
// this method is only used for elements previously positioned using setPosition,
// so it's safe to cache the position for performance
// jshint camelcase: false
return el._leaflet_pos;
}
};
// prefix style property names
L.DomUtil.TRANSFORM = L.DomUtil.testProp(
['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
// webkitTransition comes first because some browser versions that drop vendor prefix don't do
// the same for the transitionend event, in particular the Android 4.1 stock browser
L.DomUtil.TRANSITION = L.DomUtil.testProp(
['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
L.DomUtil.TRANSITION_END =
L.DomUtil.TRANSITION === 'webkitTransition' || L.DomUtil.TRANSITION === 'OTransition' ?
L.DomUtil.TRANSITION + 'End' : 'transitionend';
(function () {
if ('onselectstart' in document) {
L.extend(L.DomUtil, {
disableTextSelection: function () {
L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);
},
enableTextSelection: function () {
L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
}
});
} else {
var userSelectProperty = L.DomUtil.testProp(
['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
L.extend(L.DomUtil, {
disableTextSelection: function () {
if (userSelectProperty) {
var style = document.documentElement.style;
this._userSelect = style[userSelectProperty];
style[userSelectProperty] = 'none';
}
},
enableTextSelection: function () {
if (userSelectProperty) {
document.documentElement.style[userSelectProperty] = this._userSelect;
delete this._userSelect;
}
}
});
}
L.extend(L.DomUtil, {
disableImageDrag: function () {
L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault);
},
enableImageDrag: function () {
L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault);
}
});
})();
/*
* L.LatLng represents a geographical point with latitude and longitude coordinates.
*/
L.LatLng = function (lat, lng, alt) { // (Number, Number, Number)
lat = parseFloat(lat);
lng = parseFloat(lng);
if (isNaN(lat) || isNaN(lng)) {
throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
}
this.lat = lat;
this.lng = lng;
if (alt !== undefined) {
this.alt = parseFloat(alt);
}
};
L.extend(L.LatLng, {
DEG_TO_RAD: Math.PI / 180,
RAD_TO_DEG: 180 / Math.PI,
MAX_MARGIN: 1.0E-9 // max margin of error for the "equals" check
});
L.LatLng.prototype = {
equals: function (obj) { // (LatLng) -> Boolean
if (!obj) { return false; }
obj = L.latLng(obj);
var margin = Math.max(
Math.abs(this.lat - obj.lat),
Math.abs(this.lng - obj.lng));
return margin <= L.LatLng.MAX_MARGIN;
},
toString: function (precision) { // (Number) -> String
return 'LatLng(' +
L.Util.formatNum(this.lat, precision) + ', ' +
L.Util.formatNum(this.lng, precision) + ')';
},
// Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula
// TODO move to projection code, LatLng shouldn't know about Earth
distanceTo: function (other) { // (LatLng) -> Number
other = L.latLng(other);
var R = 6378137, // earth radius in meters
d2r = L.LatLng.DEG_TO_RAD,
dLat = (other.lat - this.lat) * d2r,
dLon = (other.lng - this.lng) * d2r,
lat1 = this.lat * d2r,
lat2 = other.lat * d2r,
sin1 = Math.sin(dLat / 2),
sin2 = Math.sin(dLon / 2);
var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2);
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
},
wrap: function (a, b) { // (Number, Number) -> LatLng
var lng = this.lng;
a = a || -180;
b = b || 180;
lng = (lng + b) % (b - a) + (lng < a || lng === b ? b : a);
return new L.LatLng(this.lat, lng);
}
};
L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Number)
if (a instanceof L.LatLng) {
return a;
}
if (L.Util.isArray(a)) {
if (typeof a[0] === 'number' || typeof a[0] === 'string') {
return new L.LatLng(a[0], a[1], a[2]);
} else {
return null;
}
}
if (a === undefined || a === null) {
return a;
}
if (typeof a === 'object' && 'lat' in a) {
return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon);
}
if (b === undefined) {
return null;
}
return new L.LatLng(a, b);
};
/*
* L.LatLngBounds represents a rectangular area on the map in geographical coordinates.
*/
L.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[])
if (!southWest) { return; }
var latlngs = northEast ? [southWest, northEast] : southWest;
for (var i = 0, len = latlngs.length; i < len; i++) {
this.extend(latlngs[i]);
}
};
L.LatLngBounds.prototype = {
// extend the bounds to contain the given point or bounds
extend: function (obj) { // (LatLng) or (LatLngBounds)
if (!obj) { return this; }
var latLng = L.latLng(obj);
if (latLng !== null) {
obj = latLng;
} else {
obj = L.latLngBounds(obj);
}
if (obj instanceof L.LatLng) {
if (!this._southWest && !this._northEast) {
this._southWest = new L.LatLng(obj.lat, obj.lng);
this._northEast = new L.LatLng(obj.lat, obj.lng);
} else {
this._southWest.lat = Math.min(obj.lat, this._southWest.lat);
this._southWest.lng = Math.min(obj.lng, this._southWest.lng);
this._northEast.lat = Math.max(obj.lat, this._northEast.lat);
this._northEast.lng = Math.max(obj.lng, this._northEast.lng);
}
} else if (obj instanceof L.LatLngBounds) {
this.extend(obj._southWest);
this.extend(obj._northEast);
}
return this;
},
// extend the bounds by a percentage
pad: function (bufferRatio) { // (Number) -> LatLngBounds
var sw = this._southWest,
ne = this._northEast,
heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
return new L.LatLngBounds(
new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
},
getCenter: function () { // -> LatLng
return new L.LatLng(
(this._southWest.lat + this._northEast.lat) / 2,
(this._southWest.lng + this._northEast.lng) / 2);
},
getSouthWest: function () {
return this._southWest;
},
getNorthEast: function () {
return this._northEast;
},
getNorthWest: function () {
return new L.LatLng(this.getNorth(), this.getWest());
},
getSouthEast: function () {
return new L.LatLng(this.getSouth(), this.getEast());
},
getWest: function () {
return this._southWest.lng;
},
getSouth: function () {
return this._southWest.lat;
},
getEast: function () {
return this._northEast.lng;
},
getNorth: function () {
return this._northEast.lat;
},
contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
obj = L.latLng(obj);
} else {
obj = L.latLngBounds(obj);
}
var sw = this._southWest,
ne = this._northEast,
sw2, ne2;
if (obj instanceof L.LatLngBounds) {
sw2 = obj.getSouthWest();
ne2 = obj.getNorthEast();
} else {
sw2 = ne2 = obj;
}
return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
(sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
},
intersects: function (bounds) { // (LatLngBounds)
bounds = L.latLngBounds(bounds);
var sw = this._southWest,
ne = this._northEast,
sw2 = bounds.getSouthWest(),
ne2 = bounds.getNorthEast(),
latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
return latIntersects && lngIntersects;
},
toBBoxString: function () {
return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
},
equals: function (bounds) { // (LatLngBounds)
if (!bounds) { return false; }
bounds = L.latLngBounds(bounds);
return this._southWest.equals(bounds.getSouthWest()) &&
this._northEast.equals(bounds.getNorthEast());
},
isValid: function () {
return !!(this._southWest && this._northEast);
}
};
//TODO International date line?
L.latLngBounds = function (a, b) { // (LatLngBounds) or (LatLng, LatLng)
if (!a || a instanceof L.LatLngBounds) {
return a;
}
return new L.LatLngBounds(a, b);
};
/*
* L.Projection contains various geographical projections used by CRS classes.
*/
L.Projection = {};
/*
* Spherical Mercator is the most popular map projection, used by EPSG:3857 CRS used by default.
*/
L.Projection.SphericalMercator = {
MAX_LATITUDE: 85.0511287798,
project: function (latlng) { // (LatLng) -> Point
var d = L.LatLng.DEG_TO_RAD,
max = this.MAX_LATITUDE,
lat = Math.max(Math.min(max, latlng.lat), -max),
x = latlng.lng * d,
y = lat * d;
y = Math.log(Math.tan((Math.PI / 4) + (y / 2)));
return new L.Point(x, y);
},
unproject: function (point) { // (Point, Boolean) -> LatLng
var d = L.LatLng.RAD_TO_DEG,
lng = point.x * d,
lat = (2 * Math.atan(Math.exp(point.y)) - (Math.PI / 2)) * d;
return new L.LatLng(lat, lng);
}
};
/*
* Simple equirectangular (Plate Carree) projection, used by CRS like EPSG:4326 and Simple.
*/
L.Projection.LonLat = {
project: function (latlng) {
return new L.Point(latlng.lng, latlng.lat);
},
unproject: function (point) {
return new L.LatLng(point.y, point.x);
}
};
/*
* L.CRS is a base object for all defined CRS (Coordinate Reference Systems) in Leaflet.
*/
L.CRS = {
latLngToPoint: function (latlng, zoom) { // (LatLng, Number) -> Point
var projectedPoint = this.projection.project(latlng),
scale = this.scale(zoom);
return this.transformation._transform(projectedPoint, scale);
},
pointToLatLng: function (point, zoom) { // (Point, Number[, Boolean]) -> LatLng
var scale = this.scale(zoom),
untransformedPoint = this.transformation.untransform(point, scale);
return this.projection.unproject(untransformedPoint);
},
project: function (latlng) {
return this.projection.project(latlng);
},
scale: function (zoom) {
return 256 * Math.pow(2, zoom);
},
getSize: function (zoom) {
var s = this.scale(zoom);
return L.point(s, s);
}
};
/*
* A simple CRS that can be used for flat non-Earth maps like panoramas or game maps.
*/
L.CRS.Simple = L.extend({}, L.CRS, {
projection: L.Projection.LonLat,
transformation: new L.Transformation(1, 0, -1, 0),
scale: function (zoom) {
return Math.pow(2, zoom);
}
});
/*
* L.CRS.EPSG3857 (Spherical Mercator) is the most common CRS for web mapping
* and is used by Leaflet by default.
*/
L.CRS.EPSG3857 = L.extend({}, L.CRS, {
code: 'EPSG:3857',
projection: L.Projection.SphericalMercator,
transformation: new L.Transformation(0.5 / Math.PI, 0.5, -0.5 / Math.PI, 0.5),
project: function (latlng) { // (LatLng) -> Point
var projectedPoint = this.projection.project(latlng),
earthRadius = 6378137;
return projectedPoint.multiplyBy(earthRadius);
}
});
L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
code: 'EPSG:900913'
});
/*
* L.CRS.EPSG4326 is a CRS popular among advanced GIS specialists.
*/
L.CRS.EPSG4326 = L.extend({}, L.CRS, {
code: 'EPSG:4326',
projection: L.Projection.LonLat,
transformation: new L.Transformation(1 / 360, 0.5, -1 / 360, 0.5)
});
/*
* L.Map is the central class of the API - it is used to create a map.
*/
L.Map = L.Class.extend({
includes: L.Mixin.Events,
options: {
crs: L.CRS.EPSG3857,
/*
center: LatLng,
zoom: Number,
layers: Array,
*/
fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android23,
trackResize: true,
markerZoomAnimation: L.DomUtil.TRANSITION && L.Browser.any3d
},
initialize: function (id, options) { // (HTMLElement or String, Object)
options = L.setOptions(this, options);
this._initContainer(id);
this._initLayout();
// hack for https://github.com/Leaflet/Leaflet/issues/1980
this._onResize = L.bind(this._onResize, this);
this._initEvents();
if (options.maxBounds) {
this.setMaxBounds(options.maxBounds);
}
if (options.center && options.zoom !== undefined) {
this.setView(L.latLng(options.center), options.zoom, {reset: true});
}
this._handlers = [];
this._layers = {};
this._zoomBoundLayers = {};
this._tileLayersNum = 0;
this.callInitHooks();
this._addLayers(options.layers);
},
// public methods that modify map state
// replaced by animation-powered implementation in Map.PanAnimation.js
setView: function (center, zoom) {
zoom = zoom === undefined ? this.getZoom() : zoom;
this._resetView(L.latLng(center), this._limitZoom(zoom));
return this;
},
setZoom: function (zoom, options) {
if (!this._loaded) {
this._zoom = this._limitZoom(zoom);
return this;
}
return this.setView(this.getCenter(), zoom, {zoom: options});
},
zoomIn: function (delta, options) {
return this.setZoom(this._zoom + (delta || 1), options);
},
zoomOut: function (delta, options) {
return this.setZoom(this._zoom - (delta || 1), options);
},
setZoomAround: function (latlng, zoom, options) {
var scale = this.getZoomScale(zoom),
viewHalf = this.getSize().divideBy(2),
containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng),
centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
return this.setView(newCenter, zoom, {zoom: options});
},
fitBounds: function (bounds, options) {
options = options || {};
bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds);
var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]),
paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]),
zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR)),
paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
swPoint = this.project(bounds.getSouthWest(), zoom),
nePoint = this.project(bounds.getNorthEast(), zoom),
center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
zoom = options && options.maxZoom ? Math.min(options.maxZoom, zoom) : zoom;
return this.setView(center, zoom, options);
},
fitWorld: function (options) {
return this.fitBounds([[-90, -180], [90, 180]], options);
},
panTo: function (center, options) { // (LatLng)
return this.setView(center, this._zoom, {pan: options});
},
panBy: function (offset) { // (Point)
// replaced with animated panBy in Map.PanAnimation.js
this.fire('movestart');
this._rawPanBy(L.point(offset));
this.fire('move');
return this.fire('moveend');
},
setMaxBounds: function (bounds) {
bounds = L.latLngBounds(bounds);
this.options.maxBounds = bounds;
if (!bounds) {
return this.off('moveend', this._panInsideMaxBounds, this);
}
if (this._loaded) {
this._panInsideMaxBounds();
}
return this.on('moveend', this._panInsideMaxBounds, this);
},
panInsideBounds: function (bounds, options) {
var center = this.getCenter(),
newCenter = this._limitCenter(center, this._zoom, bounds);
if (center.equals(newCenter)) { return this; }
return this.panTo(newCenter, options);
},
addLayer: function (layer) {
// TODO method is too big, refactor
var id = L.stamp(layer);
if (this._layers[id]) { return this; }
this._layers[id] = layer;
// TODO getMaxZoom, getMinZoom in ILayer (instead of options)
if (layer.options && (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom))) {
this._zoomBoundLayers[id] = layer;
this._updateZoomLevels();
}
// TODO looks ugly, refactor!!!
if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
this._tileLayersNum++;
this._tileLayersToLoad++;
layer.on('load', this._onTileLayerLoad, this);
}
if (this._loaded) {
this._layerAdd(layer);
}
return this;
},
removeLayer: function (layer) {
var id = L.stamp(layer);
if (!this._layers[id]) { return this; }
if (this._loaded) {
layer.onRemove(this);
}
delete this._layers[id];
if (this._loaded) {
this.fire('layerremove', {layer: layer});
}
if (this._zoomBoundLayers[id]) {
delete this._zoomBoundLayers[id];
this._updateZoomLevels();
}
// TODO looks ugly, refactor
if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
this._tileLayersNum--;
this._tileLayersToLoad--;
layer.off('load', this._onTileLayerLoad, this);
}
return this;
},
hasLayer: function (layer) {
if (!layer) { return false; }
return (L.stamp(layer) in this._layers);
},
eachLayer: function (method, context) {
for (var i in this._layers) {
method.call(context, this._layers[i]);
}
return this;
},
invalidateSize: function (options) {
if (!this._loaded) { return this; }
options = L.extend({
animate: false,
pan: true
}, options === true ? {animate: true} : options);
var oldSize = this.getSize();
this._sizeChanged = true;
this._initialCenter = null;
var newSize = this.getSize(),
oldCenter = oldSize.divideBy(2).round(),
newCenter = newSize.divideBy(2).round(),
offset = oldCenter.subtract(newCenter);
if (!offset.x && !offset.y) { return this; }
if (options.animate && options.pan) {
this.panBy(offset);
} else {
if (options.pan) {
this._rawPanBy(offset);
}
this.fire('move');
if (options.debounceMoveend) {
clearTimeout(this._sizeTimer);
this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
} else {
this.fire('moveend');
}
}
return this.fire('resize', {
oldSize: oldSize,
newSize: newSize
});
},
// TODO handler.addTo
addHandler: function (name, HandlerClass) {
if (!HandlerClass) { return this; }
var handler = this[name] = new HandlerClass(this);
this._handlers.push(handler);
if (this.options[name]) {
handler.enable();
}
return this;
},
remove: function () {
if (this._loaded) {
this.fire('unload');
}
this._initEvents('off');
try {
// throws error in IE6-8
delete this._container._leaflet;
} catch (e) {
this._container._leaflet = undefined;
}
this._clearPanes();
if (this._clearControlPos) {
this._clearControlPos();
}
this._clearHandlers();
return this;
},
// public methods for getting map state
getCenter: function () { // (Boolean) -> LatLng
this._checkIfLoaded();
if (this._initialCenter && !this._moved()) {
return this._initialCenter;
}
return this.layerPointToLatLng(this._getCenterLayerPoint());
},
getZoom: function () {
return this._zoom;
},
getBounds: function () {
var bounds = this.getPixelBounds(),
sw = this.unproject(bounds.getBottomLeft()),
ne = this.unproject(bounds.getTopRight());
return new L.LatLngBounds(sw, ne);
},
getMinZoom: function () {
return this.options.minZoom === undefined ?
(this._layersMinZoom === undefined ? 0 : this._layersMinZoom) :
this.options.minZoom;
},
getMaxZoom: function () {
return this.options.maxZoom === undefined ?
(this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
this.options.maxZoom;
},
getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
bounds = L.latLngBounds(bounds);
var zoom = this.getMinZoom() - (inside ? 1 : 0),
maxZoom = this.getMaxZoom(),
size = this.getSize(),
nw = bounds.getNorthWest(),
se = bounds.getSouthEast(),
zoomNotFound = true,
boundsSize;
padding = L.point(padding || [0, 0]);
do {
zoom++;
boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)).add(padding);
zoomNotFound = !inside ? size.contains(boundsSize) : boundsSize.x < size.x || boundsSize.y < size.y;
} while (zoomNotFound && zoom <= maxZoom);
if (zoomNotFound && inside) {
return null;
}
return inside ? zoom : zoom - 1;
},
getSize: function () {
if (!this._size || this._sizeChanged) {
this._size = new L.Point(
this._container.clientWidth,
this._container.clientHeight);
this._sizeChanged = false;
}
return this._size.clone();
},
getPixelBounds: function () {
var topLeftPoint = this._getTopLeftPoint();
return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
},
getPixelOrigin: function () {
this._checkIfLoaded();
return this._initialTopLeftPoint;
},
getPanes: function () {
return this._panes;
},
getContainer: function () {
return this._container;
},
// TODO replace with universal implementation after refactoring projections
getZoomScale: function (toZoom) {
var crs = this.options.crs;
return crs.scale(toZoom) / crs.scale(this._zoom);
},
getScaleZoom: function (scale) {
return this._zoom + (Math.log(scale) / Math.LN2);
},
// conversion methods
project: function (latlng, zoom) { // (LatLng[, Number]) -> Point
zoom = zoom === undefined ? this._zoom : zoom;
return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
},
unproject: function (point, zoom) { // (Point[, Number]) -> LatLng
zoom = zoom === undefined ? this._zoom : zoom;
return this.options.crs.pointToLatLng(L.point(point), zoom);
},
layerPointToLatLng: function (point) { // (Point)
var projectedPoint = L.point(point).add(this.getPixelOrigin());
return this.unproject(projectedPoint);
},
latLngToLayerPoint: function (latlng) { // (LatLng)
var projectedPoint = this.project(L.latLng(latlng))._round();
return projectedPoint._subtract(this.getPixelOrigin());
},
containerPointToLayerPoint: function (point) { // (Point)
return L.point(point).subtract(this._getMapPanePos());
},
layerPointToContainerPoint: function (point) { // (Point)
return L.point(point).add(this._getMapPanePos());
},
containerPointToLatLng: function (point) {
var layerPoint = this.containerPointToLayerPoint(L.point(point));
return this.layerPointToLatLng(layerPoint);
},
latLngToContainerPoint: function (latlng) {
return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
},
mouseEventToContainerPoint: function (e) { // (MouseEvent)
return L.DomEvent.getMousePosition(e, this._container);
},
mouseEventToLayerPoint: function (e) { // (MouseEvent)
return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
},
mouseEventToLatLng: function (e) { // (MouseEvent)
return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
},
// map initialization methods
_initContainer: function (id) {
var container = this._container = L.DomUtil.get(id);
if (!container) {
throw new Error('Map container not found.');
} else if (container._leaflet) {
throw new Error('Map container is already initialized.');
}
container._leaflet = true;
},
_initLayout: function () {
var container = this._container;
L.DomUtil.addClass(container, 'leaflet-container' +
(L.Browser.touch ? ' leaflet-touch' : '') +
(L.Browser.retina ? ' leaflet-retina' : '') +
(L.Browser.ielt9 ? ' leaflet-oldie' : '') +
(this.options.fadeAnimation ? ' leaflet-fade-anim' : ''));
var position = L.DomUtil.getStyle(container, 'position');
if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
container.style.position = 'relative';
}
this._initPanes();
if (this._initControlPos) {
this._initControlPos();
}
},
_initPanes: function () {
var panes = this._panes = {};
this._mapPane = panes.mapPane = this._createPane('leaflet-map-pane', this._container);
this._tilePane = panes.tilePane = this._createPane('leaflet-tile-pane', this._mapPane);
panes.objectsPane = this._createPane('leaflet-objects-pane', this._mapPane);
panes.shadowPane = this._createPane('leaflet-shadow-pane');
panes.overlayPane = this._createPane('leaflet-overlay-pane');
panes.markerPane = this._createPane('leaflet-marker-pane');
panes.popupPane = this._createPane('leaflet-popup-pane');
var zoomHide = ' leaflet-zoom-hide';
if (!this.options.markerZoomAnimation) {
L.DomUtil.addClass(panes.markerPane, zoomHide);
L.DomUtil.addClass(panes.shadowPane, zoomHide);
L.DomUtil.addClass(panes.popupPane, zoomHide);
}
},
_createPane: function (className, container) {
return L.DomUtil.create('div', className, container || this._panes.objectsPane);
},
_clearPanes: function () {
this._container.removeChild(this._mapPane);
},
_addLayers: function (layers) {
layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];
for (var i = 0, len = layers.length; i < len; i++) {
this.addLayer(layers[i]);
}
},
// private methods that modify map state
_resetView: function (center, zoom, preserveMapOffset, afterZoomAnim) {
var zoomChanged = (this._zoom !== zoom);
if (!afterZoomAnim) {
this.fire('movestart');
if (zoomChanged) {
this.fire('zoomstart');
}
}
this._zoom = zoom;
this._initialCenter = center;
this._initialTopLeftPoint = this._getNewTopLeftPoint(center);
if (!preserveMapOffset) {
L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
} else {
this._initialTopLeftPoint._add(this._getMapPanePos());
}
this._tileLayersToLoad = this._tileLayersNum;
var loading = !this._loaded;
this._loaded = true;
if (loading) {
this.fire('load');
this.eachLayer(this._layerAdd, this);
}
this.fire('viewreset', {hard: !preserveMapOffset});
this.fire('move');
if (zoomChanged || afterZoomAnim) {
this.fire('zoomend');
}
this.fire('moveend', {hard: !preserveMapOffset});
},
_rawPanBy: function (offset) {
L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
},
_getZoomSpan: function () {
return this.getMaxZoom() - this.getMinZoom();
},
_updateZoomLevels: function () {
var i,
minZoom = Infinity,
maxZoom = -Infinity,
oldZoomSpan = this._getZoomSpan();
for (i in this._zoomBoundLayers) {
var layer = this._zoomBoundLayers[i];
if (!isNaN(layer.options.minZoom)) {
minZoom = Math.min(minZoom, layer.options.minZoom);
}
if (!isNaN(layer.options.maxZoom)) {
maxZoom = Math.max(maxZoom, layer.options.maxZoom);
}
}
if (i === undefined) { // we have no tilelayers
this._layersMaxZoom = this._layersMinZoom = undefined;
} else {
this._layersMaxZoom = maxZoom;
this._layersMinZoom = minZoom;
}
if (oldZoomSpan !== this._getZoomSpan()) {
this.fire('zoomlevelschange');
}
},
_panInsideMaxBounds: function () {
this.panInsideBounds(this.options.maxBounds);
},
_checkIfLoaded: function () {
if (!this._loaded) {
throw new Error('Set map center and zoom first.');
}
},
// map events
_initEvents: function (onOff) {
if (!L.DomEvent) { return; }
onOff = onOff || 'on';
L.DomEvent[onOff](this._container, 'click', this._onMouseClick, this);
var events = ['dblclick', 'mousedown', 'mouseup', 'mouseenter',
'mouseleave', 'mousemove', 'contextmenu'],
i, len;
for (i = 0, len = events.length; i < len; i++) {
L.DomEvent[onOff](this._container, events[i], this._fireMouseEvent, this);
}
if (this.options.trackResize) {
L.DomEvent[onOff](window, 'resize', this._onResize, this);
}
},
_onResize: function () {
L.Util.cancelAnimFrame(this._resizeRequest);
this._resizeRequest = L.Util.requestAnimFrame(
function () { this.invalidateSize({debounceMoveend: true}); }, this, false, this._container);
},
_onMouseClick: function (e) {
if (!this._loaded || (!e._simulated &&
((this.dragging && this.dragging.moved()) ||
(this.boxZoom && this.boxZoom.moved()))) ||
L.DomEvent._skipped(e)) { return; }
this.fire('preclick');
this._fireMouseEvent(e);
},
_fireMouseEvent: function (e) {
if (!this._loaded || L.DomEvent._skipped(e)) { return; }
var type = e.type;
type = (type === 'mouseenter' ? 'mouseover' : (type === 'mouseleave' ? 'mouseout' : type));
if (!this.hasEventListeners(type)) { return; }
if (type === 'contextmenu') {
L.DomEvent.preventDefault(e);
}
var containerPoint = this.mouseEventToContainerPoint(e),
layerPoint = this.containerPointToLayerPoint(containerPoint),
latlng = this.layerPointToLatLng(layerPoint);
this.fire(type, {
latlng: latlng,
layerPoint: layerPoint,
containerPoint: containerPoint,
originalEvent: e
});
},
_onTileLayerLoad: function () {
this._tileLayersToLoad--;
if (this._tileLayersNum && !this._tileLayersToLoad) {
this.fire('tilelayersload');
}
},
_clearHandlers: function () {
for (var i = 0, len = this._handlers.length; i < len; i++) {
this._handlers[i].disable();
}
},
whenReady: function (callback, context) {
if (this._loaded) {
callback.call(context || this, this);
} else {
this.on('load', callback, context);
}
return this;
},
_layerAdd: function (layer) {
layer.onAdd(this);
this.fire('layeradd', {layer: layer});
},
// private methods for getting map state
_getMapPanePos: function () {
return L.DomUtil.getPosition(this._mapPane);
},
_moved: function () {
var pos = this._getMapPanePos();
return pos && !pos.equals([0, 0]);
},
_getTopLeftPoint: function () {
return this.getPixelOrigin().subtract(this._getMapPanePos());
},
_getNewTopLeftPoint: function (center, zoom) {
var viewHalf = this.getSize()._divideBy(2);
// TODO round on display, not calculation to increase precision?
return this.project(center, zoom)._subtract(viewHalf)._round();
},
_latLngToNewLayerPoint: function (latlng, newZoom, newCenter) {
var topLeft = this._getNewTopLeftPoint(newCenter, newZoom).add(this._getMapPanePos());
return this.project(latlng, newZoom)._subtract(topLeft);
},
// layer point of the current center
_getCenterLayerPoint: function () {
return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
},
// offset of the specified place to the current center in pixels
_getCenterOffset: function (latlng) {
return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
},
// adjust center for view to get inside bounds
_limitCenter: function (center, zoom, bounds) {
if (!bounds) { return center; }
var centerPoint = this.project(center, zoom),
viewHalf = this.getSize().divideBy(2),
viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
offset = this._getBoundsOffset(viewBounds, bounds, zoom);
return this.unproject(centerPoint.add(offset), zoom);
},
// adjust offset for view to get inside bounds
_limitOffset: function (offset, bounds) {
if (!bounds) { return offset; }
var viewBounds = this.getPixelBounds(),
newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
return offset.add(this._getBoundsOffset(newBounds, bounds));
},
// returns offset needed for pxBounds to get inside maxBounds at a specified zoom
_getBoundsOffset: function (pxBounds, maxBounds, zoom) {
var nwOffset = this.project(maxBounds.getNorthWest(), zoom).subtract(pxBounds.min),
seOffset = this.project(maxBounds.getSouthEast(), zoom).subtract(pxBounds.max),
dx = this._rebound(nwOffset.x, -seOffset.x),
dy = this._rebound(nwOffset.y, -seOffset.y);
return new L.Point(dx, dy);
},
_rebound: function (left, right) {
return left + right > 0 ?
Math.round(left - right) / 2 :
Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
},
_limitZoom: function (zoom) {
var min = this.getMinZoom(),
max = this.getMaxZoom();
return Math.max(min, Math.min(max, zoom));
}
});
L.map = function (id, options) {
return new L.Map(id, options);
};
/*
* L.DomEvent contains functions for working with DOM events.
*/
L.DomEvent = {
/* inspired by John Resig, Dean Edwards and YUI addEvent implementations */
addListener: function (obj, type, fn, context) { // (HTMLElement, String, Function[, Object])
var id = L.stamp(fn),
key = '_leaflet_' + type + id,
handler, originalHandler, newType;
if (obj[key]) { return this; }
handler = function (e) {
return fn.call(context || obj, e || L.DomEvent._getEvent());
};
if (L.Browser.pointer && type.indexOf('touch') === 0) {
return this.addPointerListener(obj, type, handler, id);
}
if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
this.addDoubleTapListener(obj, handler, id);
}
if ('addEventListener' in obj) {
if (type === 'mousewheel') {
obj.addEventListener('DOMMouseScroll', handler, false);
obj.addEventListener(type, handler, false);
} else if ((type === 'mouseenter') || (type === 'mouseleave')) {
originalHandler = handler;
newType = (type === 'mouseenter' ? 'mouseover' : 'mouseout');
handler = function (e) {
if (!L.DomEvent._checkMouse(obj, e)) { return; }
return originalHandler(e);
};
obj.addEventListener(newType, handler, false);
} else if (type === 'click' && L.Browser.android) {
originalHandler = handler;
handler = function (e) {
return L.DomEvent._filterClick(e, originalHandler);
};
obj.addEventListener(type, handler, false);
} else {
obj.addEventListener(type, handler, false);
}
} else if ('attachEvent' in obj) {
obj.attachEvent('on' + type, handler);
}
obj[key] = handler;
return this;
},
removeListener: function (obj, type, fn) { // (HTMLElement, String, Function)
var id = L.stamp(fn),
key = '_leaflet_' + type + id,
handler = obj[key];
if (!handler) { return this; }
if (L.Browser.pointer && type.indexOf('touch') === 0) {
this.removePointerListener(obj, type, id);
} else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
this.removeDoubleTapListener(obj, id);
} else if ('removeEventListener' in obj) {
if (type === 'mousewheel') {
obj.removeEventListener('DOMMouseScroll', handler, false);
obj.removeEventListener(type, handler, false);
} else if ((type === 'mouseenter') || (type === 'mouseleave')) {
obj.removeEventListener((type === 'mouseenter' ? 'mouseover' : 'mouseout'), handler, false);
} else {
obj.removeEventListener(type, handler, false);
}
} else if ('detachEvent' in obj) {
obj.detachEvent('on' + type, handler);
}
obj[key] = null;
return this;
},
stopPropagation: function (e) {
if (e.stopPropagation) {
e.stopPropagation();
} else {
e.cancelBubble = true;
}
L.DomEvent._skipped(e);
return this;
},
disableScrollPropagation: function (el) {
var stop = L.DomEvent.stopPropagation;
return L.DomEvent
.on(el, 'mousewheel', stop)
.on(el, 'MozMousePixelScroll', stop);
},
disableClickPropagation: function (el) {
var stop = L.DomEvent.stopPropagation;
for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
L.DomEvent.on(el, L.Draggable.START[i], stop);
}
return L.DomEvent
.on(el, 'click', L.DomEvent._fakeStop)
.on(el, 'dblclick', stop);
},
preventDefault: function (e) {
if (e.preventDefault) {
e.preventDefault();
} else {
e.returnValue = false;
}
return this;
},
stop: function (e) {
return L.DomEvent
.preventDefault(e)
.stopPropagation(e);
},
getMousePosition: function (e, container) {
if (!container) {
return new L.Point(e.clientX, e.clientY);
}
var rect = container.getBoundingClientRect();
return new L.Point(
e.clientX - rect.left - container.clientLeft,
e.clientY - rect.top - container.clientTop);
},
getWheelDelta: function (e) {
var delta = 0;
if (e.wheelDelta) {
delta = e.wheelDelta / 120;
}
if (e.detail) {
delta = -e.detail / 3;
}
return delta;
},
_skipEvents: {},
_fakeStop: function (e) {
// fakes stopPropagation by setting a special event flag, checked/reset with L.DomEvent._skipped(e)
L.DomEvent._skipEvents[e.type] = true;
},
_skipped: function (e) {
var skipped = this._skipEvents[e.type];
// reset when checking, as it's only used in map container and propagates outside of the map
this._skipEvents[e.type] = false;
return skipped;
},
// check if element really left/entered the event target (for mouseenter/mouseleave)
_checkMouse: function (el, e) {
var related = e.relatedTarget;
if (!related) { return true; }
try {
while (related && (related !== el)) {
related = related.parentNode;
}
} catch (err) {
return false;
}
return (related !== el);
},
_getEvent: function () { // evil magic for IE
/*jshint noarg:false */
var e = window.event;
if (!e) {
var caller = arguments.callee.caller;
while (caller) {
e = caller['arguments'][0];
if (e && window.Event === e.constructor) {
break;
}
caller = caller.caller;
}
}
return e;
},
// this is a horrible workaround for a bug in Android where a single touch triggers two click events
_filterClick: function (e, handler) {
var timeStamp = (e.timeStamp || e.originalEvent.timeStamp),
elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);
// are they closer together than 1000ms yet more than 100ms?
// Android typically triggers them ~300ms apart while multiple listeners
// on the same event should be triggered far faster;
// or check if click is simulated on the element, and if it is, reject any non-simulated events
if ((elapsed && elapsed > 100 && elapsed < 1000) || (e.target._simulatedClick && !e._simulated)) {
L.DomEvent.stop(e);
return;
}
L.DomEvent._lastClick = timeStamp;
return handler(e);
}
};
L.DomEvent.on = L.DomEvent.addListener;
L.DomEvent.off = L.DomEvent.removeListener;
/*
* L.Draggable allows you to add dragging capabilities to any element. Supports mobile devices too.
*/
L.Draggable = L.Class.extend({
includes: L.Mixin.Events,
statics: {
START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'],
END: {
mousedown: 'mouseup',
touchstart: 'touchend',
pointerdown: 'touchend',
MSPointerDown: 'touchend'
},
MOVE: {
mousedown: 'mousemove',
touchstart: 'touchmove',
pointerdown: 'touchmove',
MSPointerDown: 'touchmove'
}
},
initialize: function (element, dragStartTarget) {
this._element = element;
this._dragStartTarget = dragStartTarget || element;
},
enable: function () {
if (this._enabled) { return; }
for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
L.DomEvent.on(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);
}
this._enabled = true;
},
disable: function () {
if (!this._enabled) { return; }
for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
L.DomEvent.off(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);
}
this._enabled = false;
this._moved = false;
},
_onDown: function (e) {
this._moved = false;
if (e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
L.DomEvent.stopPropagation(e);
if (L.Draggable._disabled) { return; }
L.DomUtil.disableImageDrag();
L.DomUtil.disableTextSelection();
if (this._moving) { return; }
var first = e.touches ? e.touches[0] : e;
this._startPoint = new L.Point(first.clientX, first.clientY);
this._startPos = this._newPos = L.DomUtil.getPosition(this._element);
L.DomEvent
.on(document, L.Draggable.MOVE[e.type], this._onMove, this)
.on(document, L.Draggable.END[e.type], this._onUp, this);
},
_onMove: function (e) {
if (e.touches && e.touches.length > 1) {
this._moved = true;
return;
}
var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
newPoint = new L.Point(first.clientX, first.clientY),
offset = newPoint.subtract(this._startPoint);
if (!offset.x && !offset.y) { return; }
L.DomEvent.preventDefault(e);
if (!this._moved) {
this.fire('dragstart');
this._moved = true;
this._startPos = L.DomUtil.getPosition(this._element).subtract(offset);
L.DomUtil.addClass(document.body, 'leaflet-dragging');
L.DomUtil.addClass((e.target || e.srcElement), 'leaflet-drag-target');
}
this._newPos = this._startPos.add(offset);
this._moving = true;
L.Util.cancelAnimFrame(this._animRequest);
this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true, this._dragStartTarget);
},
_updatePosition: function () {
this.fire('predrag');
L.DomUtil.setPosition(this._element, this._newPos);
this.fire('drag');
},
_onUp: function (e) {
L.DomUtil.removeClass(document.body, 'leaflet-dragging');
L.DomUtil.removeClass((e.target || e.srcElement), 'leaflet-drag-target');
for (var i in L.Draggable.MOVE) {
L.DomEvent
.off(document, L.Draggable.MOVE[i], this._onMove)
.off(document, L.Draggable.END[i], this._onUp);
}
L.DomUtil.enableImageDrag();
L.DomUtil.enableTextSelection();
if (this._moved && this._moving) {
// ensure drag is not fired after dragend
L.Util.cancelAnimFrame(this._animRequest);
this.fire('dragend', {
distance: this._newPos.distanceTo(this._startPos)
});
}
this._moving = false;
}
});
/*
L.Handler is a base class for handler classes that are used internally to inject
interaction features like dragging to classes like Map and Marker.
*/
L.Handler = L.Class.extend({
initialize: function (map) {
this._map = map;
},
enable: function () {
if (this._enabled) { return; }
this._enabled = true;
this.addHooks();
},
disable: function () {
if (!this._enabled) { return; }
this._enabled = false;
this.removeHooks();
},
enabled: function () {
return !!this._enabled;
}
});
/*
* L.Control is a base class for implementing map controls. Handles positioning.
* All other controls extend from this class.
*/
L.Control = L.Class.extend({
options: {
position: 'topright'
},
initialize: function (options) {
L.setOptions(this, options);
},
getPosition: function () {
return this.options.position;
},
setPosition: function (position) {
var map = this._map;
if (map) {
map.removeControl(this);
}
this.options.position = position;
if (map) {
map.addControl(this);
}
return this;
},
getContainer: function () {
return this._container;
},
addTo: function (map) {
this._map = map;
var container = this._container = this.onAdd(map),
pos = this.getPosition(),
corner = map._controlCorners[pos];
L.DomUtil.addClass(container, 'leaflet-control');
if (pos.indexOf('bottom') !== -1) {
corner.insertBefore(container, corner.firstChild);
} else {
corner.appendChild(container);
}
return this;
},
removeFrom: function (map) {
var pos = this.getPosition(),
corner = map._controlCorners[pos];
corner.removeChild(this._container);
this._map = null;
if (this.onRemove) {
this.onRemove(map);
}
return this;
},
_refocusOnMap: function () {
if (this._map) {
this._map.getContainer().focus();
}
}
});
L.control = function (options) {
return new L.Control(options);
};
// adds control-related methods to L.Map
L.Map.include({
addControl: function (control) {
control.addTo(this);
return this;
},
removeControl: function (control) {
control.removeFrom(this);
return this;
},
_initControlPos: function () {
var corners = this._controlCorners = {},
l = 'leaflet-',
container = this._controlContainer =
L.DomUtil.create('div', l + 'control-container', this._container);
function createCorner(vSide, hSide) {
var className = l + vSide + ' ' + l + hSide;
corners[vSide + hSide] = L.DomUtil.create('div', className, container);
}
createCorner('top', 'left');
createCorner('top', 'right');
createCorner('bottom', 'left');
createCorner('bottom', 'right');
},
_clearControlPos: function () {
this._container.removeChild(this._controlContainer);
}
});
/*
* L.TileLayer is used for standard xyz-numbered tile layers.
*/
L.TileLayer = L.Class.extend({
includes: L.Mixin.Events,
options: {
minZoom: 0,
maxZoom: 18,
tileSize: 256,
subdomains: 'abc',
errorTileUrl: '',
attribution: '',
zoomOffset: 0,
opacity: 1,
/*
maxNativeZoom: null,
zIndex: null,
tms: false,
continuousWorld: false,
noWrap: false,
zoomReverse: false,
detectRetina: false,
reuseTiles: false,
bounds: false,
*/
unloadInvisibleTiles: L.Browser.mobile,
updateWhenIdle: L.Browser.mobile
},
initialize: function (url, options) {
options = L.setOptions(this, options);
// detecting retina displays, adjusting tileSize and zoom levels
if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {
options.tileSize = Math.floor(options.tileSize / 2);
options.zoomOffset++;
if (options.minZoom > 0) {
options.minZoom--;
}
this.options.maxZoom--;
}
if (options.bounds) {
options.bounds = L.latLngBounds(options.bounds);
}
this._url = url;
var subdomains = this.options.subdomains;
if (typeof subdomains === 'string') {
this.options.subdomains = subdomains.split('');
}
},
onAdd: function (map) {
this._map = map;
this._animated = map._zoomAnimated;
// create a container div for tiles
this._initContainer();
// set up events
map.on({
'viewreset': this._reset,
'moveend': this._update
}, this);
if (this._animated) {
map.on({
'zoomanim': this._animateZoom,
'zoomend': this._endZoomAnim
}, this);
}
if (!this.options.updateWhenIdle) {
this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this);
map.on('move', this._limitedUpdate, this);
}
this._reset();
this._update();
},
addTo: function (map) {
map.addLayer(this);
return this;
},
onRemove: function (map) {
this._container.parentNode.removeChild(this._container);
map.off({
'viewreset': this._reset,
'moveend': this._update
}, this);
if (this._animated) {
map.off({
'zoomanim': this._animateZoom,
'zoomend': this._endZoomAnim
}, this);
}
if (!this.options.updateWhenIdle) {
map.off('move', this._limitedUpdate, this);
}
this._container = null;
this._map = null;
},
bringToFront: function () {
var pane = this._map._panes.tilePane;
if (this._container) {
pane.appendChild(this._container);
this._setAutoZIndex(pane, Math.max);
}
return this;
},
bringToBack: function () {
var pane = this._map._panes.tilePane;
if (this._container) {
pane.insertBefore(this._container, pane.firstChild);
this._setAutoZIndex(pane, Math.min);
}
return this;
},
getAttribution: function () {
return this.options.attribution;
},
getContainer: function () {
return this._container;
},
setOpacity: function (opacity) {
this.options.opacity = opacity;
if (this._map) {
this._updateOpacity();
}
return this;
},
setZIndex: function (zIndex) {
this.options.zIndex = zIndex;
this._updateZIndex();
return this;
},
setUrl: function (url, noRedraw) {
this._url = url;
if (!noRedraw) {
this.redraw();
}
return this;
},
redraw: function () {
if (this._map) {
this._reset({hard: true});
this._update();
}
return this;
},
_updateZIndex: function () {
if (this._container && this.options.zIndex !== undefined) {
this._container.style.zIndex = this.options.zIndex;
}
},
_setAutoZIndex: function (pane, compare) {
var layers = pane.children,
edgeZIndex = -compare(Infinity, -Infinity), // -Infinity for max, Infinity for min
zIndex, i, len;
for (i = 0, len = layers.length; i < len; i++) {
if (layers[i] !== this._container) {
zIndex = parseInt(layers[i].style.zIndex, 10);
if (!isNaN(zIndex)) {
edgeZIndex = compare(edgeZIndex, zIndex);
}
}
}
this.options.zIndex = this._container.style.zIndex =
(isFinite(edgeZIndex) ? edgeZIndex : 0) + compare(1, -1);
},
_updateOpacity: function () {
var i,
tiles = this._tiles;
if (L.Browser.ielt9) {
for (i in tiles) {
L.DomUtil.setOpacity(tiles[i], this.options.opacity);
}
} else {
L.DomUtil.setOpacity(this._container, this.options.opacity);
}
},
_initContainer: function () {
var tilePane = this._map._panes.tilePane;
if (!this._container) {
this._container = L.DomUtil.create('div', 'leaflet-layer');
this._updateZIndex();
if (this._animated) {
var className = 'leaflet-tile-container';
this._bgBuffer = L.DomUtil.create('div', className, this._container);
this._tileContainer = L.DomUtil.create('div', className, this._container);
} else {
this._tileContainer = this._container;
}
tilePane.appendChild(this._container);
if (this.options.opacity < 1) {
this._updateOpacity();
}
}
},
_reset: function (e) {
for (var key in this._tiles) {
this.fire('tileunload', {tile: this._tiles[key]});
}
this._tiles = {};
this._tilesToLoad = 0;
if (this.options.reuseTiles) {
this._unusedTiles = [];
}
this._tileContainer.innerHTML = '';
if (this._animated && e && e.hard) {
this._clearBgBuffer();
}
this._initContainer();
},
_getTileSize: function () {
var map = this._map,
zoom = map.getZoom() + this.options.zoomOffset,
zoomN = this.options.maxNativeZoom,
tileSize = this.options.tileSize;
if (zoomN && zoom > zoomN) {
tileSize = Math.round(map.getZoomScale(zoom) / map.getZoomScale(zoomN) * tileSize);
}
return tileSize;
},
_update: function () {
if (!this._map) { return; }
var map = this._map,
bounds = map.getPixelBounds(),
zoom = map.getZoom(),
tileSize = this._getTileSize();
if (isNaN(map.getZoom())) {
zoom = this.options.minZoom || 1;
map.setZoom(zoom);
}
if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
return;
}
var tileBounds = L.bounds(
bounds.min.divideBy(tileSize)._floor(),
bounds.max.divideBy(tileSize)._floor());
this._addTilesFromCenterOut(tileBounds);
if (this.options.unloadInvisibleTiles || this.options.reuseTiles) {
this._removeOtherTiles(tileBounds);
}
},
_addTilesFromCenterOut: function (bounds) {
var queue = [],
center = bounds.getCenter();
var j, i, point;
for (j = bounds.min.y; j <= bounds.max.y; j++) {
for (i = bounds.min.x; i <= bounds.max.x; i++) {
point = new L.Point(i, j);
if (!(this._tilePointIsCached()) && this._tileShouldBeLoaded(point)) {
queue.push(point);
}
}
}
var tilesToLoad = queue.length;
if (tilesToLoad === 0) { return; }
// load tiles in order of their distance to center
queue.sort(function (a, b) {
return a.distanceTo(center) - b.distanceTo(center);
});
var fragment = document.createDocumentFragment();
// if its the first batch of tiles to load
if (!this._tilesToLoad) {
this.fire('loading');
}
this._tilesToLoad += tilesToLoad;
for (i = 0; i < tilesToLoad; i++) {
this._addTile(queue[i], fragment);
}
this._tileContainer.appendChild(fragment);
},
_tilePointIsCached: function (tilePoint) {
if (typeof(tilePoint) == "undefined") return false;
return ((tilePoint.x + ':' + tilePoint.y) in this._tiles);
},
_tileShouldBeLoaded: function (tilePoint) {
var options = this.options;
if (!options.continuousWorld) {
var limit = this._getWrapTileNum();
// don't load if exceeds world bounds
if ((options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit.x)) ||
tilePoint.y < 0 || tilePoint.y >= limit.y) { return false; }
}
if (options.bounds) {
var tileSize = options.tileSize,
nwPoint = tilePoint.multiplyBy(tileSize),
sePoint = nwPoint.add([tileSize, tileSize]),
nw = this._map.unproject(nwPoint),
se = this._map.unproject(sePoint);
// TODO temporary hack, will be removed after refactoring projections
// https://github.com/Leaflet/Leaflet/issues/1618
if (!options.continuousWorld && !options.noWrap) {
nw = nw.wrap();
se = se.wrap();
}
if (!options.bounds.intersects([nw, se])) { return false; }
}
return true;
},
_removeOtherTiles: function (bounds) {
var kArr, x, y, key;
for (key in this._tiles) {
kArr = key.split(':');
x = parseInt(kArr[0], 10);
y = parseInt(kArr[1], 10);
// remove tile if it's out of bounds
if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) {
this._removeTile(key);
}
}
},
_removeTile: function (key) {
var tile = this._tiles[key];
this.fire('tileunload', {tile: tile, url: tile.src});
if (this.options.reuseTiles) {
L.DomUtil.removeClass(tile, 'leaflet-tile-loaded');
this._unusedTiles.push(tile);
} else if (tile.parentNode === this._tileContainer) {
this._tileContainer.removeChild(tile);
}
// for https://github.com/CloudMade/Leaflet/issues/137
if (!L.Browser.android) {
tile.onload = null;
tile.src = L.Util.emptyImageUrl;
}
delete this._tiles[key];
},
_addTile: function (tilePoint, container) {
var tilePos = this._getTilePos(tilePoint);
// get unused tile - or create a new tile
var tile = this._getTile();
/*
Chrome 20 layouts much faster with top/left (verify with timeline, frames)
Android 4 browser has display issues with top/left and requires transform instead
(other browsers don't currently care) - see debug/hacks/jitter.html for an example
*/
L.DomUtil.setPosition(tile, tilePos, L.Browser.chrome);
this._tiles[tilePoint.x + ':' + tilePoint.y] = tile;
this._loadTile(tile, tilePoint);
if (tile.parentNode !== this._tileContainer) {
container.appendChild(tile);
}
},
_getZoomForUrl: function () {
var options = this.options,
zoom = this._map.getZoom();
if (options.zoomReverse) {
zoom = options.maxZoom - zoom;
}
zoom += options.zoomOffset;
return options.maxNativeZoom ? Math.min(zoom, options.maxNativeZoom) : zoom;
},
_getTilePos: function (tilePoint) {
var origin = this._map.getPixelOrigin(),
tileSize = this._getTileSize();
return tilePoint.multiplyBy(tileSize).subtract(origin);
},
// image-specific code (override to implement e.g. Canvas or SVG tile layer)
getTileUrl: function (tilePoint) {
return L.Util.template(this._url, L.extend({
s: this._getSubdomain(tilePoint),
z: tilePoint.z,
x: tilePoint.x,
y: tilePoint.y
}, this.options));
},
_getWrapTileNum: function () {
var crs = this._map.options.crs,
size = crs.getSize(this._map.getZoom());
return size.divideBy(this._getTileSize())._floor();
},
_adjustTilePoint: function (tilePoint) {
var limit = this._getWrapTileNum();
// wrap tile coordinates
if (!this.options.continuousWorld && !this.options.noWrap) {
tilePoint.x = ((tilePoint.x % limit.x) + limit.x) % limit.x;
}
if (this.options.tms) {
tilePoint.y = limit.y - tilePoint.y - 1;
}
tilePoint.z = this._getZoomForUrl();
},
_getSubdomain: function (tilePoint) {
var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
return this.options.subdomains[index];
},
_getTile: function () {
if (this.options.reuseTiles && this._unusedTiles.length > 0) {
var tile = this._unusedTiles.pop();
this._resetTile(tile);
return tile;
}
return this._createTile();
},
// Override if data stored on a tile needs to be cleaned up before reuse
_resetTile: function (/*tile*/) {},
_createTile: function () {
var tile = L.DomUtil.create('img', 'leaflet-tile');
tile.style.width = tile.style.height = this._getTileSize() + 'px';
tile.galleryimg = 'no';
tile.onselectstart = tile.onmousemove = L.Util.falseFn;
if (L.Browser.ielt9 && this.options.opacity !== undefined) {
L.DomUtil.setOpacity(tile, this.options.opacity);
}
// without this hack, tiles disappear after zoom on Chrome for Android
// https://github.com/Leaflet/Leaflet/issues/2078
if (L.Browser.mobileWebkit3d) {
tile.style.WebkitBackfaceVisibility = 'hidden';
}
return tile;
},
_loadTile: function (tile, tilePoint) {
tile._layer = this;
tile.onload = this._tileOnLoad;
tile.onerror = this._tileOnError;
this._adjustTilePoint(tilePoint);
tile.src = this.getTileUrl(tilePoint);
this.fire('tileloadstart', {
tile: tile,
url: tile.src
});
},
_tileLoaded: function () {
this._tilesToLoad--;
if (this._animated) {
L.DomUtil.addClass(this._tileContainer, 'leaflet-zoom-animated');
}
if (!this._tilesToLoad) {
this.fire('load');
if (this._animated) {
// clear scaled tiles after all new tiles are loaded (for performance)
clearTimeout(this._clearBgBufferTimer);
this._clearBgBufferTimer = setTimeout(L.bind(this._clearBgBuffer, this), 500);
}
}
},
_tileOnLoad: function () {
var layer = this._layer;
//Only if we are loading an actual image
if (this.src !== L.Util.emptyImageUrl) {
L.DomUtil.addClass(this, 'leaflet-tile-loaded');
layer.fire('tileload', {
tile: this,
url: this.src
});
}
layer._tileLoaded();
},
_tileOnError: function () {
var layer = this._layer;
layer.fire('tileerror', {
tile: this,
url: this.src
});
var newUrl = layer.options.errorTileUrl;
if (newUrl) {
this.src = newUrl;
}
layer._tileLoaded();
}
});
L.tileLayer = function (url, options) {
return new L.TileLayer(url, options);
};
/*
* L.TileLayer.Canvas is a class that you can use as a base for creating
* dynamically drawn Canvas-based tile layers.
*/
L.TileLayer.Canvas = L.TileLayer.extend({
options: {
async: false
},
initialize: function (options) {
L.setOptions(this, options);
},
redraw: function () {
if (this._map) {
this._reset({hard: true});
this._update();
}
for (var i in this._tiles) {
this._redrawTile(this._tiles[i]);
}
return this;
},
_redrawTile: function (tile) {
this.drawTile(tile, tile._tilePoint, this._map._zoom);
},
_createTile: function () {
var tile = L.DomUtil.create('canvas', 'leaflet-tile');
tile.width = tile.height = this.options.tileSize;
tile.onselectstart = tile.onmousemove = L.Util.falseFn;
return tile;
},
_loadTile: function (tile, tilePoint) {
tile._layer = this;
tile._tilePoint = tilePoint;
this._redrawTile(tile);
if (!this.options.async) {
this.tileDrawn(tile);
}
},
drawTile: function (/*tile, tilePoint*/) {
// override with rendering code
},
tileDrawn: function (tile) {
this._tileOnLoad.call(tile);
}
});
L.tileLayer.canvas = function (options) {
return new L.TileLayer.Canvas(options);
};
/*
* L.ImageOverlay is used to overlay images over the map (to specific geographical bounds).
*/
L.ImageOverlay = L.Class.extend({
includes: L.Mixin.Events,
options: {
opacity: 1
},
initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
this._url = url;
this._bounds = L.latLngBounds(bounds);
L.setOptions(this, options);
},
onAdd: function (map) {
this._map = map;
if (!this._image) {
this._initImage();
}
map._panes.overlayPane.appendChild(this._image);
map.on('viewreset', this._reset, this);
if (map.options.zoomAnimation && L.Browser.any3d) {
map.on('zoomanim', this._animateZoom, this);
}
this._reset();
},
onRemove: function (map) {
map.getPanes().overlayPane.removeChild(this._image);
map.off('viewreset', this._reset, this);
if (map.options.zoomAnimation) {
map.off('zoomanim', this._animateZoom, this);
}
},
addTo: function (map) {
map.addLayer(this);
return this;
},
setOpacity: function (opacity) {
this.options.opacity = opacity;
this._updateOpacity();
return this;
},
// TODO remove bringToFront/bringToBack duplication from TileLayer/Path
bringToFront: function () {
if (this._image) {
this._map._panes.overlayPane.appendChild(this._image);
}
return this;
},
bringToBack: function () {
var pane = this._map._panes.overlayPane;
if (this._image) {
pane.insertBefore(this._image, pane.firstChild);
}
return this;
},
setUrl: function (url) {
this._url = url;
this._image.src = this._url;
},
getAttribution: function () {
return this.options.attribution;
},
_initImage: function () {
this._image = L.DomUtil.create('img', 'leaflet-image-layer');
if (this._map.options.zoomAnimation && L.Browser.any3d) {
L.DomUtil.addClass(this._image, 'leaflet-zoom-animated');
} else {
L.DomUtil.addClass(this._image, 'leaflet-zoom-hide');
}
this._updateOpacity();
//TODO createImage util method to remove duplication
L.extend(this._image, {
galleryimg: 'no',
onselectstart: L.Util.falseFn,
onmousemove: L.Util.falseFn,
onload: L.bind(this._onImageLoad, this),
src: this._url
});
},
_animateZoom: function (e) {
var map = this._map,
image = this._image,
scale = map.getZoomScale(e.zoom),
nw = this._bounds.getNorthWest(),
se = this._bounds.getSouthEast(),
topLeft = map._latLngToNewLayerPoint(nw, e.zoom, e.center),
size = map._latLngToNewLayerPoint(se, e.zoom, e.center)._subtract(topLeft),
origin = topLeft._add(size._multiplyBy((1 / 2) * (1 - 1 / scale)));
image.style[L.DomUtil.TRANSFORM] =
L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') ';
},
_reset: function () {
var image = this._image,
topLeft = this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
size = this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(topLeft);
L.DomUtil.setPosition(image, topLeft);
image.style.width = size.x + 'px';
image.style.height = size.y + 'px';
},
_onImageLoad: function () {
this.fire('load');
},
_updateOpacity: function () {
L.DomUtil.setOpacity(this._image, this.options.opacity);
}
});
L.imageOverlay = function (url, bounds, options) {
return new L.ImageOverlay(url, bounds, options);
};
/*
* L.Icon is an image-based icon class that you can use with L.Marker for custom markers.
*/
L.Icon = L.Class.extend({
options: {
/*
iconUrl: (String) (required)
iconRetinaUrl: (String) (optional, used for retina devices if detected)
iconSize: (Point) (can be set through CSS)
iconAnchor: (Point) (centered by default, can be set in CSS with negative margins)
popupAnchor: (Point) (if not specified, popup opens in the anchor point)
shadowUrl: (String) (no shadow by default)
shadowRetinaUrl: (String) (optional, used for retina devices if detected)
shadowSize: (Point)
shadowAnchor: (Point)
*/
className: ''
},
initialize: function (options) {
L.setOptions(this, options);
},
createIcon: function (oldIcon) {
return this._createIcon('icon', oldIcon);
},
createShadow: function (oldIcon) {
return this._createIcon('shadow', oldIcon);
},
_createIcon: function (name, oldIcon) {
var src = this._getIconUrl(name);
if (!src) {
if (name === 'icon') {
throw new Error('iconUrl not set in Icon options (see the docs).');
}
return null;
}
var img;
if (!oldIcon || oldIcon.tagName !== 'IMG') {
img = this._createImg(src);
} else {
img = this._createImg(src, oldIcon);
}
this._setIconStyles(img, name);
return img;
},
_setIconStyles: function (img, name) {
var options = this.options,
size = L.point(options[name + 'Size']),
anchor;
if (name === 'shadow') {
anchor = L.point(options.shadowAnchor || options.iconAnchor);
} else {
anchor = L.point(options.iconAnchor);
}
if (!anchor && size) {
anchor = size.divideBy(2, true);
}
img.className = 'leaflet-marker-' + name + ' ' + options.className;
if (anchor) {
img.style.marginLeft = (-anchor.x) + 'px';
img.style.marginTop = (-anchor.y) + 'px';
}
if (size) {
img.style.width = size.x + 'px';
img.style.height = size.y + 'px';
}
},
_createImg: function (src, el) {
el = el || document.createElement('img');
el.src = src;
return el;
},
_getIconUrl: function (name) {
if (L.Browser.retina && this.options[name + 'RetinaUrl']) {
return this.options[name + 'RetinaUrl'];
}
return this.options[name + 'Url'];
}
});
L.icon = function (options) {
return new L.Icon(options);
};
/*
* L.Icon.Default is the blue marker icon used by default in Leaflet.
*/
L.Icon.Default = L.Icon.extend({
options: {
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
},
_getIconUrl: function (name) {
var key = name + 'Url';
if (this.options[key]) {
return this.options[key];
}
if (L.Browser.retina && name === 'icon') {
name += '-2x';
}
var path = L.Icon.Default.imagePath;
if (!path) {
throw new Error('Couldn\'t autodetect L.Icon.Default.imagePath, set it manually.');
}
return path + '/marker-' + name + '.png';
}
});
L.Icon.Default.imagePath = (function () {
var scripts = document.getElementsByTagName('script'),
leafletRe = /[\/^]leaflet[\-\._]?([\w\-\._]*)\.js\??/;
var i, len, src, matches, path;
for (i = 0, len = scripts.length; i < len; i++) {
src = scripts[i].src;
matches = src.match(leafletRe);
if (matches) {
path = src.split(leafletRe)[0];
return (path ? path + '/' : '') + 'images';
}
}
}());
/*
* L.Marker is used to display clickable/draggable icons on the map.
*/
L.Marker = L.Class.extend({
includes: L.Mixin.Events,
options: {
icon: new L.Icon.Default(),
title: '',
alt: '',
clickable: true,
draggable: false,
keyboard: true,
zIndexOffset: 0,
opacity: 1,
riseOnHover: false,
riseOffset: 250
},
initialize: function (latlng, options) {
L.setOptions(this, options);
this._latlng = L.latLng(latlng);
},
onAdd: function (map) {
this._map = map;
map.on('viewreset', this.update, this);
this._initIcon();
this.update();
this.fire('add');
if (map.options.zoomAnimation && map.options.markerZoomAnimation) {
map.on('zoomanim', this._animateZoom, this);
}
},
addTo: function (map) {
map.addLayer(this);
return this;
},
onRemove: function (map) {
if (this.dragging) {
this.dragging.disable();
}
this._removeIcon();
this._removeShadow();
this.fire('remove');
map.off({
'viewreset': this.update,
'zoomanim': this._animateZoom
}, this);
this._map = null;
},
getLatLng: function () {
return this._latlng;
},
setLatLng: function (latlng) {
this._latlng = L.latLng(latlng);
this.update();
return this.fire('move', { latlng: this._latlng });
},
setZIndexOffset: function (offset) {
this.options.zIndexOffset = offset;
this.update();
return this;
},
setIcon: function (icon) {
this.options.icon = icon;
if (this._map) {
this._initIcon();
this.update();
}
if (this._popup) {
this.bindPopup(this._popup);
}
return this;
},
update: function () {
if (this._icon) {
var pos = this._map.latLngToLayerPoint(this._latlng).round();
this._setPos(pos);
}
return this;
},
_initIcon: function () {
var options = this.options,
map = this._map,
animation = (map.options.zoomAnimation && map.options.markerZoomAnimation),
classToAdd = animation ? 'leaflet-zoom-animated' : 'leaflet-zoom-hide';
var icon = options.icon.createIcon(this._icon),
addIcon = false;
// if we're not reusing the icon, remove the old one and init new one
if (icon !== this._icon) {
if (this._icon) {
this._removeIcon();
}
addIcon = true;
if (options.title) {
icon.title = options.title;
}
if (options.alt) {
icon.alt = options.alt;
}
}
L.DomUtil.addClass(icon, classToAdd);
if (options.keyboard) {
icon.tabIndex = '0';
}
this._icon = icon;
this._initInteraction();
if (options.riseOnHover) {
L.DomEvent
.on(icon, 'mouseover', this._bringToFront, this)
.on(icon, 'mouseout', this._resetZIndex, this);
}
var newShadow = options.icon.createShadow(this._shadow),
addShadow = false;
if (newShadow !== this._shadow) {
this._removeShadow();
addShadow = true;
}
if (newShadow) {
L.DomUtil.addClass(newShadow, classToAdd);
}
this._shadow = newShadow;
if (options.opacity < 1) {
this._updateOpacity();
}
var panes = this._map._panes;
if (addIcon) {
panes.markerPane.appendChild(this._icon);
}
if (newShadow && addShadow) {
panes.shadowPane.appendChild(this._shadow);
}
},
_removeIcon: function () {
if (this.options.riseOnHover) {
L.DomEvent
.off(this._icon, 'mouseover', this._bringToFront)
.off(this._icon, 'mouseout', this._resetZIndex);
}
this._map._panes.markerPane.removeChild(this._icon);
this._icon = null;
},
_removeShadow: function () {
if (this._shadow) {
this._map._panes.shadowPane.removeChild(this._shadow);
}
this._shadow = null;
},
_setPos: function (pos) {
L.DomUtil.setPosition(this._icon, pos);
if (this._shadow) {
L.DomUtil.setPosition(this._shadow, pos);
}
this._zIndex = pos.y + this.options.zIndexOffset;
this._resetZIndex();
},
_updateZIndex: function (offset) {
this._icon.style.zIndex = this._zIndex + offset;
},
_animateZoom: function (opt) {
var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
this._setPos(pos);
},
_initInteraction: function () {
if (!this.options.clickable) { return; }
// TODO refactor into something shared with Map/Path/etc. to DRY it up
var icon = this._icon,
events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu'];
L.DomUtil.addClass(icon, 'leaflet-clickable');
L.DomEvent.on(icon, 'click', this._onMouseClick, this);
L.DomEvent.on(icon, 'keypress', this._onKeyPress, this);
for (var i = 0; i < events.length; i++) {
L.DomEvent.on(icon, events[i], this._fireMouseEvent, this);
}
if (L.Handler.MarkerDrag) {
this.dragging = new L.Handler.MarkerDrag(this);
if (this.options.draggable) {
this.dragging.enable();
}
}
},
_onMouseClick: function (e) {
var wasDragged = this.dragging && this.dragging.moved();
if (this.hasEventListeners(e.type) || wasDragged) {
L.DomEvent.stopPropagation(e);
}
if (wasDragged) { return; }
if ((!this.dragging || !this.dragging._enabled) && this._map.dragging && this._map.dragging.moved()) { return; }
this.fire(e.type, {
originalEvent: e,
latlng: this._latlng
});
},
_onKeyPress: function (e) {
if (e.keyCode === 13) {
this.fire('click', {
originalEvent: e,
latlng: this._latlng
});
}
},
_fireMouseEvent: function (e) {
this.fire(e.type, {
originalEvent: e,
latlng: this._latlng
});
// TODO proper custom event propagation
// this line will always be called if marker is in a FeatureGroup
if (e.type === 'contextmenu' && this.hasEventListeners(e.type)) {
L.DomEvent.preventDefault(e);
}
if (e.type !== 'mousedown') {
L.DomEvent.stopPropagation(e);
} else {
L.DomEvent.preventDefault(e);
}
},
setOpacity: function (opacity) {
this.options.opacity = opacity;
if (this._map) {
this._updateOpacity();
}
return this;
},
_updateOpacity: function () {
L.DomUtil.setOpacity(this._icon, this.options.opacity);
if (this._shadow) {
L.DomUtil.setOpacity(this._shadow, this.options.opacity);
}
},
_bringToFront: function () {
this._updateZIndex(this.options.riseOffset);
},
_resetZIndex: function () {
this._updateZIndex(0);
}
});
L.marker = function (latlng, options) {
return new L.Marker(latlng, options);
};
/*
* L.DivIcon is a lightweight HTML-based icon class (as opposed to the image-based L.Icon)
* to use with L.Marker.
*/
L.DivIcon = L.Icon.extend({
options: {
iconSize: [12, 12], // also can be set through CSS
/*
iconAnchor: (Point)
popupAnchor: (Point)
html: (String)
bgPos: (Point)
*/
className: 'leaflet-div-icon',
html: false
},
createIcon: function (oldIcon) {
var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
options = this.options;
if (options.html !== false) {
div.innerHTML = options.html;
} else {
div.innerHTML = '';
}
if (options.bgPos) {
div.style.backgroundPosition =
(-options.bgPos.x) + 'px ' + (-options.bgPos.y) + 'px';
}
this._setIconStyles(div, 'icon');
return div;
},
createShadow: function () {
return null;
}
});
L.divIcon = function (options) {
return new L.DivIcon(options);
};
/*
* L.LayerGroup is a class to combine several layers into one so that
* you can manipulate the group (e.g. add/remove it) as one layer.
*/
L.LayerGroup = L.Class.extend({
initialize: function (layers) {
this._layers = {};
var i, len;
if (layers) {
for (i = 0, len = layers.length; i < len; i++) {
this.addLayer(layers[i]);
}
}
},
addLayer: function (layer) {
var id = this.getLayerId(layer);
this._layers[id] = layer;
if (this._map) {
this._map.addLayer(layer);
}
return this;
},
removeLayer: function (layer) {
var id = layer in this._layers ? layer : this.getLayerId(layer);
if (this._map && this._layers[id]) {
this._map.removeLayer(this._layers[id]);
}
delete this._layers[id];
return this;
},
hasLayer: function (layer) {
if (!layer) { return false; }
return (layer in this._layers || this.getLayerId(layer) in this._layers);
},
clearLayers: function () {
this.eachLayer(this.removeLayer, this);
return this;
},
invoke: function (methodName) {
var args = Array.prototype.slice.call(arguments, 1),
i, layer;
for (i in this._layers) {
layer = this._layers[i];
if (layer[methodName]) {
layer[methodName].apply(layer, args);
}
}
return this;
},
onAdd: function (map) {
this._map = map;
this.eachLayer(map.addLayer, map);
},
onRemove: function (map) {
this.eachLayer(map.removeLayer, map);
this._map = null;
},
addTo: function (map) {
map.addLayer(this);
return this;
},
eachLayer: function (method, context) {
for (var i in this._layers) {
method.call(context, this._layers[i]);
}
return this;
},
getLayer: function (id) {
return this._layers[id];
},
getLayers: function () {
var layers = [];
for (var i in this._layers) {
layers.push(this._layers[i]);
}
return layers;
},
setZIndex: function (zIndex) {
return this.invoke('setZIndex', zIndex);
},
getLayerId: function (layer) {
return L.stamp(layer);
}
});
L.layerGroup = function (layers) {
return new L.LayerGroup(layers);
};
/*
* L.FeatureGroup extends L.LayerGroup by introducing mouse events and additional methods
* shared between a group of interactive layers (like vectors or markers).
*/
L.FeatureGroup = L.LayerGroup.extend({
includes: L.Mixin.Events,
statics: {
EVENTS: 'click dblclick mouseover mouseout mousemove contextmenu popupopen popupclose'
},
addLayer: function (layer) {
if (this.hasLayer(layer)) {
return this;
}
if ('on' in layer) {
layer.on(L.FeatureGroup.EVENTS, this._propagateEvent, this);
}
L.LayerGroup.prototype.addLayer.call(this, layer);
if (this._popupContent && layer.bindPopup) {
layer.bindPopup(this._popupContent, this._popupOptions);
}
return this.fire('layeradd', {layer: layer});
},
removeLayer: function (layer) {
if (!this.hasLayer(layer)) {
return this;
}
if (layer in this._layers) {
layer = this._layers[layer];
}
layer.off(L.FeatureGroup.EVENTS, this._propagateEvent, this);
L.LayerGroup.prototype.removeLayer.call(this, layer);
if (this._popupContent) {
this.invoke('unbindPopup');
}
return this.fire('layerremove', {layer: layer});
},
bindPopup: function (content, options) {
this._popupContent = content;
this._popupOptions = options;
return this.invoke('bindPopup', content, options);
},
openPopup: function (latlng) {
// open popup on the first layer
for (var id in this._layers) {
this._layers[id].openPopup(latlng);
break;
}
return this;
},
setStyle: function (style) {
return this.invoke('setStyle', style);
},
bringToFront: function () {
return this.invoke('bringToFront');
},
bringToBack: function () {
return this.invoke('bringToBack');
},
getBounds: function () {
var bounds = new L.LatLngBounds();
this.eachLayer(function (layer) {
bounds.extend(layer instanceof L.Marker ? layer.getLatLng() : layer.getBounds());
});
return bounds;
},
_propagateEvent: function (e) {
e = L.extend({
layer: e.target,
target: this
}, e);
this.fire(e.type, e);
}
});
L.featureGroup = function (layers) {
return new L.FeatureGroup(layers);
};
/*
* L.Path is a base class for rendering vector paths on a map. Inherited by Polyline, Circle, etc.
*/
L.Path = L.Class.extend({
includes: [L.Mixin.Events],
statics: {
// how much to extend the clip area around the map view
// (relative to its size, e.g. 0.5 is half the screen in each direction)
// set it so that SVG element doesn't exceed 1280px (vectors flicker on dragend if it is)
CLIP_PADDING: (function () {
var max = L.Browser.mobile ? 1280 : 2000,
target = (max / Math.max(window.outerWidth, window.outerHeight) - 1) / 2;
return Math.max(0, Math.min(0.5, target));
})()
},
options: {
stroke: true,
color: '#0033ff',
dashArray: null,
lineCap: null,
lineJoin: null,
weight: 5,
opacity: 0.5,
fill: false,
fillColor: null, //same as color by default
fillOpacity: 0.2,
clickable: true
},
initialize: function (options) {
L.setOptions(this, options);
},
onAdd: function (map) {
this._map = map;
if (!this._container) {
this._initElements();
this._initEvents();
}
this.projectLatlngs();
this._updatePath();
if (this._container) {
this._map._pathRoot.appendChild(this._container);
}
this.fire('add');
map.on({
'viewreset': this.projectLatlngs,
'moveend': this._updatePath
}, this);
},
addTo: function (map) {
map.addLayer(this);
return this;
},
onRemove: function (map) {
map._pathRoot.removeChild(this._container);
// Need to fire remove event before we set _map to null as the event hooks might need the object
this.fire('remove');
this._map = null;
if (L.Browser.vml) {
this._container = null;
this._stroke = null;
this._fill = null;
}
map.off({
'viewreset': this.projectLatlngs,
'moveend': this._updatePath
}, this);
},
projectLatlngs: function () {
// do all projection stuff here
},
setStyle: function (style) {
L.setOptions(this, style);
if (this._container) {
this._updateStyle();
}
return this;
},
redraw: function () {
if (this._map) {
this.projectLatlngs();
this._updatePath();
}
return this;
}
});
L.Map.include({
_updatePathViewport: function () {
var p = L.Path.CLIP_PADDING,
size = this.getSize(),
panePos = L.DomUtil.getPosition(this._mapPane),
min = panePos.multiplyBy(-1)._subtract(size.multiplyBy(p)._round()),
max = min.add(size.multiplyBy(1 + p * 2)._round());
this._pathViewport = new L.Bounds(min, max);
}
});
/*
* Extends L.Path with SVG-specific rendering code.
*/
L.Path.SVG_NS = 'http://www.w3.org/2000/svg';
L.Browser.svg = !!(document.createElementNS && document.createElementNS(L.Path.SVG_NS, 'svg').createSVGRect);
L.Path = L.Path.extend({
statics: {
SVG: L.Browser.svg
},
bringToFront: function () {
var root = this._map._pathRoot,
path = this._container;
if (path && root.lastChild !== path) {
root.appendChild(path);
}
return this;
},
bringToBack: function () {
var root = this._map._pathRoot,
path = this._container,
first = root.firstChild;
if (path && first !== path) {
root.insertBefore(path, first);
}
return this;
},
getPathString: function () {
// form path string here
},
_createElement: function (name) {
return document.createElementNS(L.Path.SVG_NS, name);
},
_initElements: function () {
this._map._initPathRoot();
this._initPath();
this._initStyle();
},
_initPath: function () {
this._container = this._createElement('g');
this._path = this._createElement('path');
if (this.options.className) {
L.DomUtil.addClass(this._path, this.options.className);
}
this._container.appendChild(this._path);
},
_initStyle: function () {
if (this.options.stroke) {
this._path.setAttribute('stroke-linejoin', 'round');
this._path.setAttribute('stroke-linecap', 'round');
}
if (this.options.fill) {
this._path.setAttribute('fill-rule', 'evenodd');
}
if (this.options.pointerEvents) {
this._path.setAttribute('pointer-events', this.options.pointerEvents);
}
if (!this.options.clickable && !this.options.pointerEvents) {
this._path.setAttribute('pointer-events', 'none');
}
this._updateStyle();
},
_updateStyle: function () {
if (this.options.stroke) {
this._path.setAttribute('stroke', this.options.color);
this._path.setAttribute('stroke-opacity', this.options.opacity);
this._path.setAttribute('stroke-width', this.options.weight);
if (this.options.dashArray) {
this._path.setAttribute('stroke-dasharray', this.options.dashArray);
} else {
this._path.removeAttribute('stroke-dasharray');
}
if (this.options.lineCap) {
this._path.setAttribute('stroke-linecap', this.options.lineCap);
}
if (this.options.lineJoin) {
this._path.setAttribute('stroke-linejoin', this.options.lineJoin);
}
} else {
this._path.setAttribute('stroke', 'none');
}
if (this.options.fill) {
this._path.setAttribute('fill', this.options.fillColor || this.options.color);
this._path.setAttribute('fill-opacity', this.options.fillOpacity);
} else {
this._path.setAttribute('fill', 'none');
}
},
_updatePath: function () {
var str = this.getPathString();
if (!str) {
// fix webkit empty string parsing bug
str = 'M0 0';
}
this._path.setAttribute('d', str);
},
// TODO remove duplication with L.Map
_initEvents: function () {
if (this.options.clickable) {
if (L.Browser.svg || !L.Browser.vml) {
L.DomUtil.addClass(this._path, 'leaflet-clickable');
}
L.DomEvent.on(this._container, 'click', this._onMouseClick, this);
var events = ['dblclick', 'mousedown', 'mouseover',
'mouseout', 'mousemove', 'contextmenu'];
for (var i = 0; i < events.length; i++) {
L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);
}
}
},
_onMouseClick: function (e) {
if (this._map.dragging && this._map.dragging.moved()) { return; }
this._fireMouseEvent(e);
},
_fireMouseEvent: function (e) {
if (!this.hasEventListeners(e.type)) { return; }
var map = this._map,
containerPoint = map.mouseEventToContainerPoint(e),
layerPoint = map.containerPointToLayerPoint(containerPoint),
latlng = map.layerPointToLatLng(layerPoint);
this.fire(e.type, {
latlng: latlng,
layerPoint: layerPoint,
containerPoint: containerPoint,
originalEvent: e
});
if (e.type === 'contextmenu') {
L.DomEvent.preventDefault(e);
}
if (e.type !== 'mousemove') {
L.DomEvent.stopPropagation(e);
}
}
});
L.Map.include({
_initPathRoot: function () {
if (!this._pathRoot) {
this._pathRoot = L.Path.prototype._createElement('svg');
this._panes.overlayPane.appendChild(this._pathRoot);
if (this.options.zoomAnimation && L.Browser.any3d) {
L.DomUtil.addClass(this._pathRoot, 'leaflet-zoom-animated');
this.on({
'zoomanim': this._animatePathZoom,
'zoomend': this._endPathZoom
});
} else {
L.DomUtil.addClass(this._pathRoot, 'leaflet-zoom-hide');
}
this.on('moveend', this._updateSvgViewport);
this._updateSvgViewport();
}
},
_animatePathZoom: function (e) {
var scale = this.getZoomScale(e.zoom),
offset = this._getCenterOffset(e.center)._multiplyBy(-scale)._add(this._pathViewport.min);
this._pathRoot.style[L.DomUtil.TRANSFORM] =
L.DomUtil.getTranslateString(offset) + ' scale(' + scale + ') ';
this._pathZooming = true;
},
_endPathZoom: function () {
this._pathZooming = false;
},
_updateSvgViewport: function () {
if (this._pathZooming) {
// Do not update SVGs while a zoom animation is going on otherwise the animation will break.
// When the zoom animation ends we will be updated again anyway
// This fixes the case where you do a momentum move and zoom while the move is still ongoing.
return;
}
this._updatePathViewport();
var vp = this._pathViewport,
min = vp.min,
max = vp.max,
width = max.x - min.x,
height = max.y - min.y,
root = this._pathRoot,
pane = this._panes.overlayPane;
// Hack to make flicker on drag end on mobile webkit less irritating
if (L.Browser.mobileWebkit) {
pane.removeChild(root);
}
L.DomUtil.setPosition(root, min);
root.setAttribute('width', width);
root.setAttribute('height', height);
root.setAttribute('viewBox', [min.x, min.y, width, height].join(' '));
if (L.Browser.mobileWebkit) {
pane.appendChild(root);
}
}
});
/*
* Vector rendering for all browsers that support canvas.
*/
L.Browser.canvas = (function () {
return !!document.createElement('canvas').getContext;
}());
L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path : L.Path.extend({
statics: {
//CLIP_PADDING: 0.02, // not sure if there's a need to set it to a small value
CANVAS: true,
SVG: false
},
redraw: function () {
if (this._map) {
this.projectLatlngs();
this._requestUpdate();
}
return this;
},
setStyle: function (style) {
L.setOptions(this, style);
if (this._map) {
this._updateStyle();
this._requestUpdate();
}
return this;
},
onRemove: function (map) {
map
.off('viewreset', this.projectLatlngs, this)
.off('moveend', this._updatePath, this);
if (this.options.clickable) {
this._map.off('click', this._onClick, this);
this._map.off('mousemove', this._onMouseMove, this);
}
this._requestUpdate();
this._map = null;
},
_requestUpdate: function () {
if (this._map && !L.Path._updateRequest) {
L.Path._updateRequest = L.Util.requestAnimFrame(this._fireMapMoveEnd, this._map);
}
},
_fireMapMoveEnd: function () {
L.Path._updateRequest = null;
this.fire('moveend');
},
_initElements: function () {
this._map._initPathRoot();
this._ctx = this._map._canvasCtx;
},
_updateStyle: function () {
var options = this.options;
if (options.stroke) {
this._ctx.lineWidth = options.weight;
this._ctx.strokeStyle = options.color;
}
if (options.fill) {
this._ctx.fillStyle = options.fillColor || options.color;
}
},
_drawPath: function () {
var i, j, len, len2, point, drawMethod;
this._ctx.beginPath();
for (i = 0, len = this._parts.length; i < len; i++) {
for (j = 0, len2 = this._parts[i].length; j < len2; j++) {
point = this._parts[i][j];
drawMethod = (j === 0 ? 'move' : 'line') + 'To';
this._ctx[drawMethod](point.x, point.y);
}
// TODO refactor ugly hack
if (this instanceof L.Polygon) {
this._ctx.closePath();
}
}
},
_checkIfEmpty: function () {
return !this._parts.length;
},
_updatePath: function () {
if (this._checkIfEmpty()) { return; }
var ctx = this._ctx,
options = this.options;
this._drawPath();
ctx.save();
this._updateStyle();
if (options.fill) {
ctx.globalAlpha = options.fillOpacity;
ctx.fill();
}
if (options.stroke) {
ctx.globalAlpha = options.opacity;
ctx.stroke();
}
ctx.restore();
// TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
},
_initEvents: function () {
if (this.options.clickable) {
// TODO dblclick
this._map.on('mousemove', this._onMouseMove, this);
this._map.on('click', this._onClick, this);
}
},
_onClick: function (e) {
if (this._containsPoint(e.layerPoint)) {
this.fire('click', e);
}
},
_onMouseMove: function (e) {
if (!this._map || this._map._animatingZoom) { return; }
// TODO don't do on each move
if (this._containsPoint(e.layerPoint)) {
this._ctx.canvas.style.cursor = 'pointer';
this._mouseInside = true;
this.fire('mouseover', e);
} else if (this._mouseInside) {
this._ctx.canvas.style.cursor = '';
this._mouseInside = false;
this.fire('mouseout', e);
}
}
});
L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {} : {
_initPathRoot: function () {
var root = this._pathRoot,
ctx;
if (!root) {
root = this._pathRoot = document.createElement('canvas');
root.style.position = 'absolute';
ctx = this._canvasCtx = root.getContext('2d');
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
this._panes.overlayPane.appendChild(root);
if (this.options.zoomAnimation) {
this._pathRoot.className = 'leaflet-zoom-animated';
this.on('zoomanim', this._animatePathZoom);
this.on('zoomend', this._endPathZoom);
}
this.on('moveend', this._updateCanvasViewport);
this._updateCanvasViewport();
}
},
_updateCanvasViewport: function () {
// don't redraw while zooming. See _updateSvgViewport for more details
if (this._pathZooming) { return; }
this._updatePathViewport();
var vp = this._pathViewport,
min = vp.min,
size = vp.max.subtract(min),
root = this._pathRoot;
//TODO check if this works properly on mobile webkit
L.DomUtil.setPosition(root, min);
root.width = size.x;
root.height = size.y;
root.getContext('2d').translate(-min.x, -min.y);
}
});
/*
* L.LineUtil contains different utility functions for line segments
* and polylines (clipping, simplification, distances, etc.)
*/
/*jshint bitwise:false */ // allow bitwise operations for this file
L.LineUtil = {
// Simplify polyline with vertex reduction and Douglas-Peucker simplification.
// Improves rendering performance dramatically by lessening the number of points to draw.
simplify: function (/*Point[]*/ points, /*Number*/ tolerance) {
if (!tolerance || !points.length) {
return points.slice();
}
var sqTolerance = tolerance * tolerance;
// stage 1: vertex reduction
points = this._reducePoints(points, sqTolerance);
// stage 2: Douglas-Peucker simplification
points = this._simplifyDP(points, sqTolerance);
return points;
},
// distance from a point to a segment between two points
pointToSegmentDistance: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));
},
closestPointOnSegment: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
return this._sqClosestPointOnSegment(p, p1, p2);
},
// Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
_simplifyDP: function (points, sqTolerance) {
var len = points.length,
ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
markers = new ArrayConstructor(len);
markers[0] = markers[len - 1] = 1;
this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
var i,
newPoints = [];
for (i = 0; i < len; i++) {
if (markers[i]) {
newPoints.push(points[i]);
}
}
return newPoints;
},
_simplifyDPStep: function (points, markers, sqTolerance, first, last) {
var maxSqDist = 0,
index, i, sqDist;
for (i = first + 1; i <= last - 1; i++) {
sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);
if (sqDist > maxSqDist) {
index = i;
maxSqDist = sqDist;
}
}
if (maxSqDist > sqTolerance) {
markers[index] = 1;
this._simplifyDPStep(points, markers, sqTolerance, first, index);
this._simplifyDPStep(points, markers, sqTolerance, index, last);
}
},
// reduce points that are too close to each other to a single point
_reducePoints: function (points, sqTolerance) {
var reducedPoints = [points[0]];
for (var i = 1, prev = 0, len = points.length; i < len; i++) {
if (this._sqDist(points[i], points[prev]) > sqTolerance) {
reducedPoints.push(points[i]);
prev = i;
}
}
if (prev < len - 1) {
reducedPoints.push(points[len - 1]);
}
return reducedPoints;
},
// Cohen-Sutherland line clipping algorithm.
// Used to avoid rendering parts of a polyline that are not currently visible.
clipSegment: function (a, b, bounds, useLastCode) {
var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
codeB = this._getBitCode(b, bounds),
codeOut, p, newCode;
// save 2nd code to avoid calculating it on the next segment
this._lastCode = codeB;
while (true) {
// if a,b is inside the clip window (trivial accept)
if (!(codeA | codeB)) {
return [a, b];
// if a,b is outside the clip window (trivial reject)
} else if (codeA & codeB) {
return false;
// other cases
} else {
codeOut = codeA || codeB;
p = this._getEdgeIntersection(a, b, codeOut, bounds);
newCode = this._getBitCode(p, bounds);
if (codeOut === codeA) {
a = p;
codeA = newCode;
} else {
b = p;
codeB = newCode;
}
}
}
},
_getEdgeIntersection: function (a, b, code, bounds) {
var dx = b.x - a.x,
dy = b.y - a.y,
min = bounds.min,
max = bounds.max;
if (code & 8) { // top
return new L.Point(a.x + dx * (max.y - a.y) / dy, max.y);
} else if (code & 4) { // bottom
return new L.Point(a.x + dx * (min.y - a.y) / dy, min.y);
} else if (code & 2) { // right
return new L.Point(max.x, a.y + dy * (max.x - a.x) / dx);
} else if (code & 1) { // left
return new L.Point(min.x, a.y + dy * (min.x - a.x) / dx);
}
},
_getBitCode: function (/*Point*/ p, bounds) {
var code = 0;
if (p.x < bounds.min.x) { // left
code |= 1;
} else if (p.x > bounds.max.x) { // right
code |= 2;
}
if (p.y < bounds.min.y) { // bottom
code |= 4;
} else if (p.y > bounds.max.y) { // top
code |= 8;
}
return code;
},
// square distance (to avoid unnecessary Math.sqrt calls)
_sqDist: function (p1, p2) {
var dx = p2.x - p1.x,
dy = p2.y - p1.y;
return dx * dx + dy * dy;
},
// return closest point on segment or distance to that point
_sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
var x = p1.x,
y = p1.y,
dx = p2.x - x,
dy = p2.y - y,
dot = dx * dx + dy * dy,
t;
if (dot > 0) {
t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
if (t > 1) {
x = p2.x;
y = p2.y;
} else if (t > 0) {
x += dx * t;
y += dy * t;
}
}
dx = p.x - x;
dy = p.y - y;
return sqDist ? dx * dx + dy * dy : new L.Point(x, y);
}
};
/*
* L.Polyline is used to display polylines on a map.
*/
L.Polyline = L.Path.extend({
initialize: function (latlngs, options) {
L.Path.prototype.initialize.call(this, options);
this._latlngs = this._convertLatLngs(latlngs);
},
options: {
// how much to simplify the polyline on each zoom level
// more = better performance and smoother look, less = more accurate
smoothFactor: 1.0,
noClip: false
},
projectLatlngs: function () {
this._originalPoints = [];
for (var i = 0, len = this._latlngs.length; i < len; i++) {
this._originalPoints[i] = this._map.latLngToLayerPoint(this._latlngs[i]);
}
},
getPathString: function () {
for (var i = 0, len = this._parts.length, str = ''; i < len; i++) {
str += this._getPathPartStr(this._parts[i]);
}
return str;
},
getLatLngs: function () {
return this._latlngs;
},
setLatLngs: function (latlngs) {
this._latlngs = this._convertLatLngs(latlngs);
return this.redraw();
},
addLatLng: function (latlng) {
this._latlngs.push(L.latLng(latlng));
return this.redraw();
},
spliceLatLngs: function () { // (Number index, Number howMany)
var removed = [].splice.apply(this._latlngs, arguments);
this._convertLatLngs(this._latlngs, true);
this.redraw();
return removed;
},
closestLayerPoint: function (p) {
var minDistance = Infinity, parts = this._parts, p1, p2, minPoint = null;
for (var j = 0, jLen = parts.length; j < jLen; j++) {
var points = parts[j];
for (var i = 1, len = points.length; i < len; i++) {
p1 = points[i - 1];
p2 = points[i];
var sqDist = L.LineUtil._sqClosestPointOnSegment(p, p1, p2, true);
if (sqDist < minDistance) {
minDistance = sqDist;
minPoint = L.LineUtil._sqClosestPointOnSegment(p, p1, p2);
}
}
}
if (minPoint) {
minPoint.distance = Math.sqrt(minDistance);
}
return minPoint;
},
getBounds: function () {
return new L.LatLngBounds(this.getLatLngs());
},
_convertLatLngs: function (latlngs, overwrite) {
var i, len, target = overwrite ? latlngs : [];
for (i = 0, len = latlngs.length; i < len; i++) {
if (L.Util.isArray(latlngs[i]) && typeof latlngs[i][0] !== 'number') {
return;
}
target[i] = L.latLng(latlngs[i]);
}
return target;
},
_initEvents: function () {
L.Path.prototype._initEvents.call(this);
},
_getPathPartStr: function (points) {
var round = L.Path.VML;
for (var j = 0, len2 = points.length, str = '', p; j < len2; j++) {
p = points[j];
if (round) {
p._round();
}
str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
}
return str;
},
_clipPoints: function () {
var points = this._originalPoints,
len = points.length,
i, k, segment;
if (this.options.noClip) {
this._parts = [points];
return;
}
this._parts = [];
var parts = this._parts,
vp = this._map._pathViewport,
lu = L.LineUtil;
for (i = 0, k = 0; i < len - 1; i++) {
segment = lu.clipSegment(points[i], points[i + 1], vp, i);
if (!segment) {
continue;
}
parts[k] = parts[k] || [];
parts[k].push(segment[0]);
// if segment goes out of screen, or it's the last one, it's the end of the line part
if ((segment[1] !== points[i + 1]) || (i === len - 2)) {
parts[k].push(segment[1]);
k++;
}
}
},
// simplify each clipped part of the polyline
_simplifyPoints: function () {
var parts = this._parts,
lu = L.LineUtil;
for (var i = 0, len = parts.length; i < len; i++) {
parts[i] = lu.simplify(parts[i], this.options.smoothFactor);
}
},
_updatePath: function () {
if (!this._map) { return; }
this._clipPoints();
this._simplifyPoints();
L.Path.prototype._updatePath.call(this);
}
});
L.polyline = function (latlngs, options) {
return new L.Polyline(latlngs, options);
};
/*
* L.PolyUtil contains utility functions for polygons (clipping, etc.).
*/
/*jshint bitwise:false */ // allow bitwise operations here
L.PolyUtil = {};
/*
* Sutherland-Hodgeman polygon clipping algorithm.
* Used to avoid rendering parts of a polygon that are not currently visible.
*/
L.PolyUtil.clipPolygon = function (points, bounds) {
var clippedPoints,
edges = [1, 4, 2, 8],
i, j, k,
a, b,
len, edge, p,
lu = L.LineUtil;
for (i = 0, len = points.length; i < len; i++) {
points[i]._code = lu._getBitCode(points[i], bounds);
}
// for each edge (left, bottom, right, top)
for (k = 0; k < 4; k++) {
edge = edges[k];
clippedPoints = [];
for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
a = points[i];
b = points[j];
// if a is inside the clip window
if (!(a._code & edge)) {
// if b is outside the clip window (a->b goes out of screen)
if (b._code & edge) {
p = lu._getEdgeIntersection(b, a, edge, bounds);
p._code = lu._getBitCode(p, bounds);
clippedPoints.push(p);
}
clippedPoints.push(a);
// else if b is inside the clip window (a->b enters the screen)
} else if (!(b._code & edge)) {
p = lu._getEdgeIntersection(b, a, edge, bounds);
p._code = lu._getBitCode(p, bounds);
clippedPoints.push(p);
}
}
points = clippedPoints;
}
return points;
};
/*
* L.Polygon is used to display polygons on a map.
*/
L.Polygon = L.Polyline.extend({
options: {
fill: true
},
initialize: function (latlngs, options) {
L.Polyline.prototype.initialize.call(this, latlngs, options);
this._initWithHoles(latlngs);
},
_initWithHoles: function (latlngs) {
var i, len, hole;
if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) {
this._latlngs = this._convertLatLngs(latlngs[0]);
this._holes = latlngs.slice(1);
for (i = 0, len = this._holes.length; i < len; i++) {
hole = this._holes[i] = this._convertLatLngs(this._holes[i]);
if (hole[0].equals(hole[hole.length - 1])) {
hole.pop();
}
}
}
// filter out last point if its equal to the first one
latlngs = this._latlngs;
if (latlngs.length >= 2 && latlngs[0].equals(latlngs[latlngs.length - 1])) {
latlngs.pop();
}
},
projectLatlngs: function () {
L.Polyline.prototype.projectLatlngs.call(this);
// project polygon holes points
// TODO move this logic to Polyline to get rid of duplication
this._holePoints = [];
if (!this._holes) { return; }
var i, j, len, len2;
for (i = 0, len = this._holes.length; i < len; i++) {
this._holePoints[i] = [];
for (j = 0, len2 = this._holes[i].length; j < len2; j++) {
this._holePoints[i][j] = this._map.latLngToLayerPoint(this._holes[i][j]);
}
}
},
setLatLngs: function (latlngs) {
if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) {
this._initWithHoles(latlngs);
return this.redraw();
} else {
return L.Polyline.prototype.setLatLngs.call(this, latlngs);
}
},
_clipPoints: function () {
var points = this._originalPoints,
newParts = [];
this._parts = [points].concat(this._holePoints);
if (this.options.noClip) { return; }
for (var i = 0, len = this._parts.length; i < len; i++) {
var clipped = L.PolyUtil.clipPolygon(this._parts[i], this._map._pathViewport);
if (clipped.length) {
newParts.push(clipped);
}
}
this._parts = newParts;
},
_getPathPartStr: function (points) {
var str = L.Polyline.prototype._getPathPartStr.call(this, points);
return str + (L.Browser.svg ? 'z' : 'x');
}
});
L.polygon = function (latlngs, options) {
return new L.Polygon(latlngs, options);
};
/*
* Contains L.MultiPolyline and L.MultiPolygon layers.
*/
(function () {
function createMulti(Klass) {
return L.FeatureGroup.extend({
initialize: function (latlngs, options) {
this._layers = {};
this._options = options;
this.setLatLngs(latlngs);
},
setLatLngs: function (latlngs) {
var i = 0,
len = latlngs.length;
this.eachLayer(function (layer) {
if (i < len) {
layer.setLatLngs(latlngs[i++]);
} else {
this.removeLayer(layer);
}
}, this);
while (i < len) {
this.addLayer(new Klass(latlngs[i++], this._options));
}
return this;
},
getLatLngs: function () {
var latlngs = [];
this.eachLayer(function (layer) {
latlngs.push(layer.getLatLngs());
});
return latlngs;
}
});
}
L.MultiPolyline = createMulti(L.Polyline);
L.MultiPolygon = createMulti(L.Polygon);
L.multiPolyline = function (latlngs, options) {
return new L.MultiPolyline(latlngs, options);
};
L.multiPolygon = function (latlngs, options) {
return new L.MultiPolygon(latlngs, options);
};
}());
/*
* L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
*/
L.Rectangle = L.Polygon.extend({
initialize: function (latLngBounds, options) {
L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
},
setBounds: function (latLngBounds) {
this.setLatLngs(this._boundsToLatLngs(latLngBounds));
},
_boundsToLatLngs: function (latLngBounds) {
latLngBounds = L.latLngBounds(latLngBounds);
return [
latLngBounds.getSouthWest(),
latLngBounds.getNorthWest(),
latLngBounds.getNorthEast(),
latLngBounds.getSouthEast()
];
}
});
L.rectangle = function (latLngBounds, options) {
return new L.Rectangle(latLngBounds, options);
};
/*
* L.Circle is a circle overlay (with a certain radius in meters).
*/
L.Circle = L.Path.extend({
initialize: function (latlng, radius, options) {
L.Path.prototype.initialize.call(this, options);
this._latlng = L.latLng(latlng);
this._mRadius = radius;
},
options: {
fill: true
},
setLatLng: function (latlng) {
this._latlng = L.latLng(latlng);
return this.redraw();
},
setRadius: function (radius) {
this._mRadius = radius;
return this.redraw();
},
projectLatlngs: function () {
var lngRadius = this._getLngRadius(),
latlng = this._latlng,
pointLeft = this._map.latLngToLayerPoint([latlng.lat, latlng.lng - lngRadius]);
this._point = this._map.latLngToLayerPoint(latlng);
this._radius = Math.max(this._point.x - pointLeft.x, 1);
},
getBounds: function () {
var lngRadius = this._getLngRadius(),
latRadius = (this._mRadius / 40075017) * 360,
latlng = this._latlng;
return new L.LatLngBounds(
[latlng.lat - latRadius, latlng.lng - lngRadius],
[latlng.lat + latRadius, latlng.lng + lngRadius]);
},
getLatLng: function () {
return this._latlng;
},
getPathString: function () {
var p = this._point,
r = this._radius;
if (this._checkIfEmpty()) {
return '';
}
if (L.Browser.svg) {
return 'M' + p.x + ',' + (p.y - r) +
'A' + r + ',' + r + ',0,1,1,' +
(p.x - 0.1) + ',' + (p.y - r) + ' z';
} else {
p._round();
r = Math.round(r);
return 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r + ' 0,' + (65535 * 360);
}
},
getRadius: function () {
return this._mRadius;
},
// TODO Earth hardcoded, move into projection code!
_getLatRadius: function () {
return (this._mRadius / 40075017) * 360;
},
_getLngRadius: function () {
return this._getLatRadius() / Math.cos(L.LatLng.DEG_TO_RAD * this._latlng.lat);
},
_checkIfEmpty: function () {
if (!this._map) {
return false;
}
var vp = this._map._pathViewport,
r = this._radius,
p = this._point;
return p.x - r > vp.max.x || p.y - r > vp.max.y ||
p.x + r < vp.min.x || p.y + r < vp.min.y;
}
});
L.circle = function (latlng, radius, options) {
return new L.Circle(latlng, radius, options);
};
/*
* L.CircleMarker is a circle overlay with a permanent pixel radius.
*/
L.CircleMarker = L.Circle.extend({
options: {
radius: 10,
weight: 2
},
initialize: function (latlng, options) {
L.Circle.prototype.initialize.call(this, latlng, null, options);
this._radius = this.options.radius;
},
projectLatlngs: function () {
this._point = this._map.latLngToLayerPoint(this._latlng);
},
_updateStyle : function () {
L.Circle.prototype._updateStyle.call(this);
this.setRadius(this.options.radius);
},
setLatLng: function (latlng) {
L.Circle.prototype.setLatLng.call(this, latlng);
if (this._popup && this._popup._isOpen) {
this._popup.setLatLng(latlng);
}
return this;
},
setRadius: function (radius) {
this.options.radius = this._radius = radius;
return this.redraw();
},
getRadius: function () {
return this._radius;
}
});
L.circleMarker = function (latlng, options) {
return new L.CircleMarker(latlng, options);
};
/*
* Extends L.Polyline to be able to manually detect clicks on Canvas-rendered polylines.
*/
L.Polyline.include(!L.Path.CANVAS ? {} : {
_containsPoint: function (p, closed) {
var i, j, k, len, len2, dist, part,
w = this.options.weight / 2;
if (L.Browser.touch) {
w += 10; // polyline click tolerance on touch devices
}
for (i = 0, len = this._parts.length; i < len; i++) {
part = this._parts[i];
for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
if (!closed && (j === 0)) {
continue;
}
dist = L.LineUtil.pointToSegmentDistance(p, part[k], part[j]);
if (dist <= w) {
return true;
}
}
}
return false;
}
});
/*
* L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
*/
L.Map.mergeOptions({
dragging: true,
inertia: !L.Browser.android23,
inertiaDeceleration: 3400, // px/s^2
inertiaMaxSpeed: Infinity, // px/s
inertiaThreshold: L.Browser.touch ? 32 : 18, // ms
easeLinearity: 0.25,
// TODO refactor, move to CRS
worldCopyJump: false
});
L.Map.Drag = L.Handler.extend({
addHooks: function () {
if (!this._draggable) {
var map = this._map;
this._draggable = new L.Draggable(map._mapPane, map._container);
this._draggable.on({
'dragstart': this._onDragStart,
'drag': this._onDrag,
'dragend': this._onDragEnd
}, this);
if (map.options.worldCopyJump) {
this._draggable.on('predrag', this._onPreDrag, this);
map.on('viewreset', this._onViewReset, this);
map.whenReady(this._onViewReset, this);
}
}
this._draggable.enable();
},
removeHooks: function () {
this._draggable.disable();
},
moved: function () {
return this._draggable && this._draggable._moved;
},
_onDragStart: function () {
var map = this._map;
if (map._panAnim) {
map._panAnim.stop();
}
map
.fire('movestart')
.fire('dragstart');
if (map.options.inertia) {
this._positions = [];
this._times = [];
}
},
_onDrag: function () {
if (this._map.options.inertia) {
var time = this._lastTime = +new Date(),
pos = this._lastPos = this._draggable._newPos;
this._positions.push(pos);
this._times.push(time);
if (time - this._times[0] > 200) {
this._positions.shift();
this._times.shift();
}
}
this._map
.fire('move')
.fire('drag');
},
_onViewReset: function () {
// TODO fix hardcoded Earth values
var pxCenter = this._map.getSize()._divideBy(2),
pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
this._worldWidth = this._map.project([0, 180]).x;
},
_onPreDrag: function () {
// TODO refactor to be able to adjust map pane position after zoom
var worldWidth = this._worldWidth,
halfWidth = Math.round(worldWidth / 2),
dx = this._initialWorldOffset,
x = this._draggable._newPos.x,
newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
this._draggable._newPos.x = newX;
},
_onDragEnd: function (e) {
var map = this._map,
options = map.options,
delay = +new Date() - this._lastTime,
noInertia = !options.inertia || delay > options.inertiaThreshold || !this._positions[0];
map.fire('dragend', e);
if (noInertia) {
map.fire('moveend');
} else {
var direction = this._lastPos.subtract(this._positions[0]),
duration = (this._lastTime + delay - this._times[0]) / 1000,
ease = options.easeLinearity,
speedVector = direction.multiplyBy(ease / duration),
speed = speedVector.distanceTo([0, 0]),
limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
if (!offset.x || !offset.y) {
map.fire('moveend');
} else {
offset = map._limitOffset(offset, map.options.maxBounds);
L.Util.requestAnimFrame(function () {
map.panBy(offset, {
duration: decelerationDuration,
easeLinearity: ease,
noMoveStart: true
});
});
}
}
}
});
L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag);
/*
* L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
*/
L.Map.mergeOptions({
doubleClickZoom: true
});
L.Map.DoubleClickZoom = L.Handler.extend({
addHooks: function () {
this._map.on('dblclick', this._onDoubleClick, this);
},
removeHooks: function () {
this._map.off('dblclick', this._onDoubleClick, this);
},
_onDoubleClick: function (e) {
var map = this._map,
zoom = map.getZoom() + (e.originalEvent.shiftKey ? -1 : 1);
if (map.options.doubleClickZoom === 'center') {
map.setZoom(zoom);
} else {
map.setZoomAround(e.containerPoint, zoom);
}
}
});
L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom);
/*
* L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
*/
L.Map.mergeOptions({
scrollWheelZoom: true
});
L.Map.ScrollWheelZoom = L.Handler.extend({
addHooks: function () {
L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this);
L.DomEvent.on(this._map._container, 'MozMousePixelScroll', L.DomEvent.preventDefault);
this._delta = 0;
},
removeHooks: function () {
L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll);
L.DomEvent.off(this._map._container, 'MozMousePixelScroll', L.DomEvent.preventDefault);
},
_onWheelScroll: function (e) {
var delta = L.DomEvent.getWheelDelta(e);
this._delta += delta;
this._lastMousePos = this._map.mouseEventToContainerPoint(e);
if (!this._startTime) {
this._startTime = +new Date();
}
var left = Math.max(40 - (+new Date() - this._startTime), 0);
clearTimeout(this._timer);
this._timer = setTimeout(L.bind(this._performZoom, this), left);
L.DomEvent.preventDefault(e);
L.DomEvent.stopPropagation(e);
},
_performZoom: function () {
var map = this._map,
delta = this._delta,
zoom = map.getZoom();
delta = delta > 0 ? Math.ceil(delta) : Math.floor(delta);
delta = Math.max(Math.min(delta, 4), -4);
delta = map._limitZoom(zoom + delta) - zoom;
this._delta = 0;
this._startTime = null;
if (!delta) { return; }
if (map.options.scrollWheelZoom === 'center') {
map.setZoom(zoom + delta);
} else {
map.setZoomAround(this._lastMousePos, zoom + delta);
}
}
});
L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
/*
* Extends the event handling code with double tap support for mobile browsers.
*/
L.extend(L.DomEvent, {
_touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart',
_touchend: L.Browser.msPointer ? 'MSPointerUp' : L.Browser.pointer ? 'pointerup' : 'touchend',
// inspired by Zepto touch code by Thomas Fuchs
addDoubleTapListener: function (obj, handler, id) {
var last,
doubleTap = false,
delay = 250,
touch,
pre = '_leaflet_',
touchstart = this._touchstart,
touchend = this._touchend,
trackedTouches = [];
function onTouchStart(e) {
var count;
if (L.Browser.pointer) {
trackedTouches.push(e.pointerId);
count = trackedTouches.length;
} else {
count = e.touches.length;
}
if (count > 1) {
return;
}
var now = Date.now(),
delta = now - (last || now);
touch = e.touches ? e.touches[0] : e;
doubleTap = (delta > 0 && delta <= delay);
last = now;
}
function onTouchEnd(e) {
if (L.Browser.pointer) {
var idx = trackedTouches.indexOf(e.pointerId);
if (idx === -1) {
return;
}
trackedTouches.splice(idx, 1);
}
if (doubleTap) {
if (L.Browser.pointer) {
// work around .type being readonly with MSPointer* events
var newTouch = { },
prop;
// jshint forin:false
for (var i in touch) {
prop = touch[i];
if (typeof prop === 'function') {
newTouch[i] = prop.bind(touch);
} else {
newTouch[i] = prop;
}
}
touch = newTouch;
}
touch.type = 'dblclick';
handler(touch);
last = null;
}
}
obj[pre + touchstart + id] = onTouchStart;
obj[pre + touchend + id] = onTouchEnd;
// on pointer we need to listen on the document, otherwise a drag starting on the map and moving off screen
// will not come through to us, so we will lose track of how many touches are ongoing
var endElement = L.Browser.pointer ? document.documentElement : obj;
obj.addEventListener(touchstart, onTouchStart, false);
endElement.addEventListener(touchend, onTouchEnd, false);
if (L.Browser.pointer) {
endElement.addEventListener(L.DomEvent.POINTER_CANCEL, onTouchEnd, false);
}
return this;
},
removeDoubleTapListener: function (obj, id) {
var pre = '_leaflet_';
obj.removeEventListener(this._touchstart, obj[pre + this._touchstart + id], false);
(L.Browser.pointer ? document.documentElement : obj).removeEventListener(
this._touchend, obj[pre + this._touchend + id], false);
if (L.Browser.pointer) {
document.documentElement.removeEventListener(L.DomEvent.POINTER_CANCEL, obj[pre + this._touchend + id],
false);
}
return this;
}
});
/*
* Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
*/
L.extend(L.DomEvent, {
//static
POINTER_DOWN: L.Browser.msPointer ? 'MSPointerDown' : 'pointerdown',
POINTER_MOVE: L.Browser.msPointer ? 'MSPointerMove' : 'pointermove',
POINTER_UP: L.Browser.msPointer ? 'MSPointerUp' : 'pointerup',
POINTER_CANCEL: L.Browser.msPointer ? 'MSPointerCancel' : 'pointercancel',
_pointers: [],
_pointerDocumentListener: false,
// Provides a touch events wrapper for (ms)pointer events.
// Based on changes by veproza https://github.com/CloudMade/Leaflet/pull/1019
//ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
addPointerListener: function (obj, type, handler, id) {
switch (type) {
case 'touchstart':
return this.addPointerListenerStart(obj, type, handler, id);
case 'touchend':
return this.addPointerListenerEnd(obj, type, handler, id);
case 'touchmove':
return this.addPointerListenerMove(obj, type, handler, id);
default:
throw 'Unknown touch event type';
}
},
addPointerListenerStart: function (obj, type, handler, id) {
var pre = '_leaflet_',
pointers = this._pointers;
var cb = function (e) {
L.DomEvent.preventDefault(e);
var alreadyInArray = false;
for (var i = 0; i < pointers.length; i++) {
if (pointers[i].pointerId === e.pointerId) {
alreadyInArray = true;
break;
}
}
if (!alreadyInArray) {
pointers.push(e);
}
e.touches = pointers.slice();
e.changedTouches = [e];
handler(e);
};
obj[pre + 'touchstart' + id] = cb;
obj.addEventListener(this.POINTER_DOWN, cb, false);
// need to also listen for end events to keep the _pointers list accurate
// this needs to be on the body and never go away
if (!this._pointerDocumentListener) {
var internalCb = function (e) {
for (var i = 0; i < pointers.length; i++) {
if (pointers[i].pointerId === e.pointerId) {
pointers.splice(i, 1);
break;
}
}
};
//We listen on the documentElement as any drags that end by moving the touch off the screen get fired there
document.documentElement.addEventListener(this.POINTER_UP, internalCb, false);
document.documentElement.addEventListener(this.POINTER_CANCEL, internalCb, false);
this._pointerDocumentListener = true;
}
return this;
},
addPointerListenerMove: function (obj, type, handler, id) {
var pre = '_leaflet_',
touches = this._pointers;
function cb(e) {
// don't fire touch moves when mouse isn't down
if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
for (var i = 0; i < touches.length; i++) {
if (touches[i].pointerId === e.pointerId) {
touches[i] = e;
break;
}
}
e.touches = touches.slice();
e.changedTouches = [e];
handler(e);
}
obj[pre + 'touchmove' + id] = cb;
obj.addEventListener(this.POINTER_MOVE, cb, false);
return this;
},
addPointerListenerEnd: function (obj, type, handler, id) {
var pre = '_leaflet_',
touches = this._pointers;
var cb = function (e) {
for (var i = 0; i < touches.length; i++) {
if (touches[i].pointerId === e.pointerId) {
touches.splice(i, 1);
break;
}
}
e.touches = touches.slice();
e.changedTouches = [e];
handler(e);
};
obj[pre + 'touchend' + id] = cb;
obj.addEventListener(this.POINTER_UP, cb, false);
obj.addEventListener(this.POINTER_CANCEL, cb, false);
return this;
},
removePointerListener: function (obj, type, id) {
var pre = '_leaflet_',
cb = obj[pre + type + id];
switch (type) {
case 'touchstart':
obj.removeEventListener(this.POINTER_DOWN, cb, false);
break;
case 'touchmove':
obj.removeEventListener(this.POINTER_MOVE, cb, false);
break;
case 'touchend':
obj.removeEventListener(this.POINTER_UP, cb, false);
obj.removeEventListener(this.POINTER_CANCEL, cb, false);
break;
}
return this;
}
});
/*
* L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
*/
L.Map.mergeOptions({
touchZoom: L.Browser.touch && !L.Browser.android23,
bounceAtZoomLimits: true
});
L.Map.TouchZoom = L.Handler.extend({
addHooks: function () {
L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
},
removeHooks: function () {
L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
},
_onTouchStart: function (e) {
var map = this._map;
if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
var p1 = map.mouseEventToLayerPoint(e.touches[0]),
p2 = map.mouseEventToLayerPoint(e.touches[1]),
viewCenter = map._getCenterLayerPoint();
this._startCenter = p1.add(p2)._divideBy(2);
this._startDist = p1.distanceTo(p2);
this._moved = false;
this._zooming = true;
this._centerOffset = viewCenter.subtract(this._startCenter);
if (map._panAnim) {
map._panAnim.stop();
}
L.DomEvent
.on(document, 'touchmove', this._onTouchMove, this)
.on(document, 'touchend', this._onTouchEnd, this);
L.DomEvent.preventDefault(e);
},
_onTouchMove: function (e) {
var map = this._map;
if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
var p1 = map.mouseEventToLayerPoint(e.touches[0]),
p2 = map.mouseEventToLayerPoint(e.touches[1]);
this._scale = p1.distanceTo(p2) / this._startDist;
this._delta = p1._add(p2)._divideBy(2)._subtract(this._startCenter);
if (this._scale === 1) { return; }
if (!map.options.bounceAtZoomLimits) {
if ((map.getZoom() === map.getMinZoom() && this._scale < 1) ||
(map.getZoom() === map.getMaxZoom() && this._scale > 1)) { return; }
}
if (!this._moved) {
L.DomUtil.addClass(map._mapPane, 'leaflet-touching');
map
.fire('movestart')
.fire('zoomstart');
this._moved = true;
}
L.Util.cancelAnimFrame(this._animRequest);
this._animRequest = L.Util.requestAnimFrame(
this._updateOnMove, this, true, this._map._container);
L.DomEvent.preventDefault(e);
},
_updateOnMove: function () {
var map = this._map,
origin = this._getScaleOrigin(),
center = map.layerPointToLatLng(origin),
zoom = map.getScaleZoom(this._scale);
map._animateZoom(center, zoom, this._startCenter, this._scale, this._delta);
},
_onTouchEnd: function () {
if (!this._moved || !this._zooming) {
this._zooming = false;
return;
}
var map = this._map;
this._zooming = false;
L.DomUtil.removeClass(map._mapPane, 'leaflet-touching');
L.Util.cancelAnimFrame(this._animRequest);
L.DomEvent
.off(document, 'touchmove', this._onTouchMove)
.off(document, 'touchend', this._onTouchEnd);
var origin = this._getScaleOrigin(),
center = map.layerPointToLatLng(origin),
oldZoom = map.getZoom(),
floatZoomDelta = map.getScaleZoom(this._scale) - oldZoom,
roundZoomDelta = (floatZoomDelta > 0 ?
Math.ceil(floatZoomDelta) : Math.floor(floatZoomDelta)),
zoom = map._limitZoom(oldZoom + roundZoomDelta),
scale = map.getZoomScale(zoom) / this._scale;
map._animateZoom(center, zoom, origin, scale);
},
_getScaleOrigin: function () {
var centerOffset = this._centerOffset.subtract(this._delta).divideBy(this._scale);
return this._startCenter.add(centerOffset);
}
});
L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);
/*
* L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
*/
L.Map.mergeOptions({
tap: true,
tapTolerance: 15
});
L.Map.Tap = L.Handler.extend({
addHooks: function () {
L.DomEvent.on(this._map._container, 'touchstart', this._onDown, this);
},
removeHooks: function () {
L.DomEvent.off(this._map._container, 'touchstart', this._onDown, this);
},
_onDown: function (e) {
if (!e.touches) { return; }
L.DomEvent.preventDefault(e);
this._fireClick = true;
// don't simulate click or track longpress if more than 1 touch
if (e.touches.length > 1) {
this._fireClick = false;
clearTimeout(this._holdTimeout);
return;
}
var first = e.touches[0],
el = first.target;
this._startPos = this._newPos = new L.Point(first.clientX, first.clientY);
// if touching a link, highlight it
if (el.tagName && el.tagName.toLowerCase() === 'a') {
L.DomUtil.addClass(el, 'leaflet-active');
}
// simulate long hold but setting a timeout
this._holdTimeout = setTimeout(L.bind(function () {
if (this._isTapValid()) {
this._fireClick = false;
this._onUp();
this._simulateEvent('contextmenu', first);
}
}, this), 1000);
L.DomEvent
.on(document, 'touchmove', this._onMove, this)
.on(document, 'touchend', this._onUp, this);
},
_onUp: function (e) {
clearTimeout(this._holdTimeout);
L.DomEvent
.off(document, 'touchmove', this._onMove, this)
.off(document, 'touchend', this._onUp, this);
if (this._fireClick && e && e.changedTouches) {
var first = e.changedTouches[0],
el = first.target;
if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
L.DomUtil.removeClass(el, 'leaflet-active');
}
// simulate click if the touch didn't move too much
if (this._isTapValid()) {
this._simulateEvent('click', first);
}
}
},
_isTapValid: function () {
return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
},
_onMove: function (e) {
var first = e.touches[0];
this._newPos = new L.Point(first.clientX, first.clientY);
},
_simulateEvent: function (type, e) {
var simulatedEvent = document.createEvent('MouseEvents');
simulatedEvent._simulated = true;
e.target._simulatedClick = true;
simulatedEvent.initMouseEvent(
type, true, true, window, 1,
e.screenX, e.screenY,
e.clientX, e.clientY,
false, false, false, false, 0, null);
e.target.dispatchEvent(simulatedEvent);
}
});
if (L.Browser.touch && !L.Browser.pointer) {
L.Map.addInitHook('addHandler', 'tap', L.Map.Tap);
}
/*
* L.Control.Zoom is used for the default zoom buttons on the map.
*/
L.Control.Zoom = L.Control.extend({
options: {
position: 'topleft',
zoomInText: '+',
zoomInTitle: 'Zoom in',
zoomOutText: '-',
zoomOutTitle: 'Zoom out'
},
onAdd: function (map) {
var zoomName = 'leaflet-control-zoom',
container = L.DomUtil.create('div', zoomName + ' leaflet-bar');
this._map = map;
this._zoomInButton = this._createButton(
this.options.zoomInText, this.options.zoomInTitle,
zoomName + '-in', container, this._zoomIn, this);
this._zoomOutButton = this._createButton(
this.options.zoomOutText, this.options.zoomOutTitle,
zoomName + '-out', container, this._zoomOut, this);
this._updateDisabled();
map.on('zoomend zoomlevelschange', this._updateDisabled, this);
return container;
},
onRemove: function (map) {
map.off('zoomend zoomlevelschange', this._updateDisabled, this);
},
_zoomIn: function (e) {
this._map.zoomIn(e.shiftKey ? 3 : 1);
},
_zoomOut: function (e) {
this._map.zoomOut(e.shiftKey ? 3 : 1);
},
_createButton: function (html, title, className, container, fn, context) {
var link = L.DomUtil.create('a', className, container);
link.innerHTML = html;
link.href = '#';
link.title = title;
var stop = L.DomEvent.stopPropagation;
L.DomEvent
.on(link, 'click', stop)
.on(link, 'mousedown', stop)
.on(link, 'dblclick', stop)
.on(link, 'click', L.DomEvent.preventDefault)
.on(link, 'click', fn, context)
.on(link, 'click', this._refocusOnMap, context);
return link;
},
_updateDisabled: function () {
var map = this._map,
className = 'leaflet-disabled';
L.DomUtil.removeClass(this._zoomInButton, className);
L.DomUtil.removeClass(this._zoomOutButton, className);
if (map._zoom === map.getMinZoom()) {
L.DomUtil.addClass(this._zoomOutButton, className);
}
if (map._zoom === map.getMaxZoom()) {
L.DomUtil.addClass(this._zoomInButton, className);
}
}
});
L.Map.mergeOptions({
zoomControl: true
});
L.Map.addInitHook(function () {
if (this.options.zoomControl) {
this.zoomControl = new L.Control.Zoom();
this.addControl(this.zoomControl);
}
});
L.control.zoom = function (options) {
return new L.Control.Zoom(options);
};
/*
* L.Control.Attribution is used for displaying attribution on the map (added by default).
*/
L.Control.Attribution = L.Control.extend({
options: {
position: 'bottomright',
prefix: 'Leaflet'
},
initialize: function (options) {
L.setOptions(this, options);
this._attributions = {};
},
onAdd: function (map) {
this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
L.DomEvent.disableClickPropagation(this._container);
for (var i in map._layers) {
if (map._layers[i].getAttribution) {
this.addAttribution(map._layers[i].getAttribution());
}
}
map
.on('layeradd', this._onLayerAdd, this)
.on('layerremove', this._onLayerRemove, this);
this._update();
return this._container;
},
onRemove: function (map) {
map
.off('layeradd', this._onLayerAdd)
.off('layerremove', this._onLayerRemove);
},
setPrefix: function (prefix) {
this.options.prefix = prefix;
this._update();
return this;
},
addAttribution: function (text) {
if (!text) { return; }
if (!this._attributions[text]) {
this._attributions[text] = 0;
}
this._attributions[text]++;
this._update();
return this;
},
removeAttribution: function (text) {
if (!text) { return; }
if (this._attributions[text]) {
this._attributions[text]--;
this._update();
}
return this;
},
_update: function () {
if (!this._map) { return; }
var attribs = [];
for (var i in this._attributions) {
if (this._attributions[i]) {
attribs.push(i);
}
}
var prefixAndAttribs = [];
if (this.options.prefix) {
prefixAndAttribs.push(this.options.prefix);
}
if (attribs.length) {
prefixAndAttribs.push(attribs.join(', '));
}
this._container.innerHTML = prefixAndAttribs.join(' | ');
},
_onLayerAdd: function (e) {
if (e.layer.getAttribution) {
this.addAttribution(e.layer.getAttribution());
}
},
_onLayerRemove: function (e) {
if (e.layer.getAttribution) {
this.removeAttribution(e.layer.getAttribution());
}
}
});
L.Map.mergeOptions({
attributionControl: true
});
L.Map.addInitHook(function () {
if (this.options.attributionControl) {
this.attributionControl = (new L.Control.Attribution()).addTo(this);
}
});
L.control.attribution = function (options) {
return new L.Control.Attribution(options);
};
/*
* L.PosAnimation is used by Leaflet internally for pan animations.
*/
L.PosAnimation = L.Class.extend({
includes: L.Mixin.Events,
run: function (el, newPos, duration, easeLinearity) { // (HTMLElement, Point[, Number, Number])
this.stop();
this._el = el;
this._inProgress = true;
this._newPos = newPos;
this.fire('start');
el.style[L.DomUtil.TRANSITION] = 'all ' + (duration || 0.25) +
's cubic-bezier(0,0,' + (easeLinearity || 0.5) + ',1)';
L.DomEvent.on(el, L.DomUtil.TRANSITION_END, this._onTransitionEnd, this);
L.DomUtil.setPosition(el, newPos);
// toggle reflow, Chrome flickers for some reason if you don't do this
L.Util.falseFn(el.offsetWidth);
// there's no native way to track value updates of transitioned properties, so we imitate this
this._stepTimer = setInterval(L.bind(this._onStep, this), 50);
},
stop: function () {
if (!this._inProgress) { return; }
// if we just removed the transition property, the element would jump to its final position,
// so we need to make it stay at the current position
L.DomUtil.setPosition(this._el, this._getPos());
this._onTransitionEnd();
L.Util.falseFn(this._el.offsetWidth); // force reflow in case we are about to start a new animation
},
_onStep: function () {
var stepPos = this._getPos();
if (!stepPos) {
this._onTransitionEnd();
return;
}
// jshint camelcase: false
// make L.DomUtil.getPosition return intermediate position value during animation
this._el._leaflet_pos = stepPos;
this.fire('step');
},
// you can't easily get intermediate values of properties animated with CSS3 Transitions,
// we need to parse computed style (in case of transform it returns matrix string)
_transformRe: /([-+]?(?:\d*\.)?\d+)\D*, ([-+]?(?:\d*\.)?\d+)\D*\)/,
_getPos: function () {
var left, top, matches,
el = this._el,
style = window.getComputedStyle(el);
if (L.Browser.any3d) {
matches = style[L.DomUtil.TRANSFORM].match(this._transformRe);
if (!matches) { return; }
left = parseFloat(matches[1]);
top = parseFloat(matches[2]);
} else {
left = parseFloat(style.left);
top = parseFloat(style.top);
}
return new L.Point(left, top, true);
},
_onTransitionEnd: function () {
L.DomEvent.off(this._el, L.DomUtil.TRANSITION_END, this._onTransitionEnd, this);
if (!this._inProgress) { return; }
this._inProgress = false;
this._el.style[L.DomUtil.TRANSITION] = '';
// jshint camelcase: false
// make sure L.DomUtil.getPosition returns the final position value after animation
this._el._leaflet_pos = this._newPos;
clearInterval(this._stepTimer);
this.fire('step').fire('end');
}
});
/*
* Extends L.Map to handle panning animations.
*/
L.Map.include({
setView: function (center, zoom, options) {
zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
center = this._limitCenter(L.latLng(center), zoom, this.options.maxBounds);
options = options || {};
if (this._panAnim) {
this._panAnim.stop();
}
if (this._loaded && !options.reset && options !== true) {
if (options.animate !== undefined) {
options.zoom = L.extend({animate: options.animate}, options.zoom);
options.pan = L.extend({animate: options.animate}, options.pan);
}
// try animating pan or zoom
var animated = (this._zoom !== zoom) ?
this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
this._tryAnimatedPan(center, options.pan);
if (animated) {
// prevent resize handler call, the view will refresh after animation anyway
clearTimeout(this._sizeTimer);
return this;
}
}
// animation didn't start, just reset the map view
this._resetView(center, zoom);
return this;
},
panBy: function (offset, options) {
offset = L.point(offset).round();
options = options || {};
if (!offset.x && !offset.y) {
return this;
}
if (!this._panAnim) {
this._panAnim = new L.PosAnimation();
this._panAnim.on({
'step': this._onPanTransitionStep,
'end': this._onPanTransitionEnd
}, this);
}
// don't fire movestart if animating inertia
if (!options.noMoveStart) {
this.fire('movestart');
}
// animate pan unless animate: false specified
if (options.animate !== false) {
L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
var newPos = this._getMapPanePos().subtract(offset);
this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
} else {
this._rawPanBy(offset);
this.fire('move').fire('moveend');
}
return this;
},
_onPanTransitionStep: function () {
this.fire('move');
},
_onPanTransitionEnd: function () {
L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
this.fire('moveend');
},
_tryAnimatedPan: function (center, options) {
// difference between the new and current centers in pixels
var offset = this._getCenterOffset(center)._floor();
// don't animate too far unless animate: true specified in options
if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
this.panBy(offset, options);
return true;
}
});
/*
* L.PosAnimation fallback implementation that powers Leaflet pan animations
* in browsers that don't support CSS3 Transitions.
*/
L.PosAnimation = L.DomUtil.TRANSITION ? L.PosAnimation : L.PosAnimation.extend({
run: function (el, newPos, duration, easeLinearity) { // (HTMLElement, Point[, Number, Number])
this.stop();
this._el = el;
this._inProgress = true;
this._duration = duration || 0.25;
this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
this._startPos = L.DomUtil.getPosition(el);
this._offset = newPos.subtract(this._startPos);
this._startTime = +new Date();
this.fire('start');
this._animate();
},
stop: function () {
if (!this._inProgress) { return; }
this._step();
this._complete();
},
_animate: function () {
// animation loop
this._animId = L.Util.requestAnimFrame(this._animate, this);
this._step();
},
_step: function () {
var elapsed = (+new Date()) - this._startTime,
duration = this._duration * 1000;
if (elapsed < duration) {
this._runFrame(this._easeOut(elapsed / duration));
} else {
this._runFrame(1);
this._complete();
}
},
_runFrame: function (progress) {
var pos = this._startPos.add(this._offset.multiplyBy(progress));
L.DomUtil.setPosition(this._el, pos);
this.fire('step');
},
_complete: function () {
L.Util.cancelAnimFrame(this._animId);
this._inProgress = false;
this.fire('end');
},
_easeOut: function (t) {
return 1 - Math.pow(1 - t, this._easeOutPower);
}
});
/*
* Extends L.Map to handle zoom animations.
*/
L.Map.mergeOptions({
zoomAnimation: true,
zoomAnimationThreshold: 4
});
if (L.DomUtil.TRANSITION) {
L.Map.addInitHook(function () {
// don't animate on browsers without hardware-accelerated transitions or old Android/Opera
this._zoomAnimated = this.options.zoomAnimation && L.DomUtil.TRANSITION &&
L.Browser.any3d && !L.Browser.android23 && !L.Browser.mobileOpera;
// zoom transitions run with the same duration for all layers, so if one of transitionend events
// happens after starting zoom animation (propagating to the map pane), we know that it ended globally
if (this._zoomAnimated) {
L.DomEvent.on(this._mapPane, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
}
});
}
L.Map.include(!L.DomUtil.TRANSITION ? {} : {
_catchTransitionEnd: function (e) {
if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
this._onZoomTransitionEnd();
}
},
_nothingToAnimate: function () {
return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
},
_tryAnimatedZoom: function (center, zoom, options) {
if (this._animatingZoom) { return true; }
options = options || {};
// don't animate if disabled, not supported or zoom difference is too large
if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
// offset is the pixel coords of the zoom origin relative to the current center
var scale = this.getZoomScale(zoom),
offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale),
origin = this._getCenterLayerPoint()._add(offset);
// don't animate if the zoom origin isn't within one screen from the current center, unless forced
if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
this
.fire('movestart')
.fire('zoomstart');
this._animateZoom(center, zoom, origin, scale, null, true);
return true;
},
_animateZoom: function (center, zoom, origin, scale, delta, backwards) {
this._animatingZoom = true;
// put transform transition on all layers with leaflet-zoom-animated class
L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
// remember what center/zoom to set after animation
this._animateToCenter = center;
this._animateToZoom = zoom;
// disable any dragging during animation
if (L.Draggable) {
L.Draggable._disabled = true;
}
this.fire('zoomanim', {
center: center,
zoom: zoom,
origin: origin,
scale: scale,
delta: delta,
backwards: backwards
});
},
_onZoomTransitionEnd: function () {
this._animatingZoom = false;
L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
this._resetView(this._animateToCenter, this._animateToZoom, true, true);
if (L.Draggable) {
L.Draggable._disabled = false;
}
}
});
/*
Zoom animation logic for L.TileLayer.
*/
L.TileLayer.include({
_animateZoom: function (e) {
if (!this._animating) {
this._animating = true;
this._prepareBgBuffer();
}
var bg = this._bgBuffer,
transform = L.DomUtil.TRANSFORM,
initialTransform = e.delta ? L.DomUtil.getTranslateString(e.delta) : bg.style[transform],
scaleStr = L.DomUtil.getScaleString(e.scale, e.origin);
bg.style[transform] = e.backwards ?
scaleStr + ' ' + initialTransform :
initialTransform + ' ' + scaleStr;
},
_endZoomAnim: function () {
var front = this._tileContainer,
bg = this._bgBuffer;
front.style.visibility = '';
front.parentNode.appendChild(front); // Bring to fore
// force reflow
L.Util.falseFn(bg.offsetWidth);
this._animating = false;
},
_clearBgBuffer: function () {
var map = this._map;
if (map && !map._animatingZoom && !map.touchZoom._zooming) {
this._bgBuffer.innerHTML = '';
this._bgBuffer.style[L.DomUtil.TRANSFORM] = '';
}
},
_prepareBgBuffer: function () {
var front = this._tileContainer,
bg = this._bgBuffer;
// if foreground layer doesn't have many tiles but bg layer does,
// keep the existing bg layer and just zoom it some more
var bgLoaded = this._getLoadedTilesPercentage(bg),
frontLoaded = this._getLoadedTilesPercentage(front);
if (bg && bgLoaded > 0.5 && frontLoaded < 0.5) {
front.style.visibility = 'hidden';
this._stopLoadingImages(front);
return;
}
// prepare the buffer to become the front tile pane
bg.style.visibility = 'hidden';
bg.style[L.DomUtil.TRANSFORM] = '';
// switch out the current layer to be the new bg layer (and vice-versa)
this._tileContainer = bg;
bg = this._bgBuffer = front;
this._stopLoadingImages(bg);
//prevent bg buffer from clearing right after zoom
clearTimeout(this._clearBgBufferTimer);
},
_getLoadedTilesPercentage: function (container) {
var tiles = container.getElementsByTagName('img'),
i, len, count = 0;
for (i = 0, len = tiles.length; i < len; i++) {
if (tiles[i].complete) {
count++;
}
}
return count / len;
},
// stops loading all tiles in the background layer
_stopLoadingImages: function (container) {
var tiles = Array.prototype.slice.call(container.getElementsByTagName('img')),
i, len, tile;
for (i = 0, len = tiles.length; i < len; i++) {
tile = tiles[i];
if (!tile.complete) {
tile.onload = L.Util.falseFn;
tile.onerror = L.Util.falseFn;
tile.src = L.Util.emptyImageUrl;
tile.parentNode.removeChild(tile);
}
}
}
});
/*
* L.TileLayer.Zoomify display Zoomify tiles with Leaflet
* Modified from https://github.com/turban/Leaflet.Zoomify
*/
L.TileLayer.Zoomify = L.TileLayer.extend({
options: {
continuousWorld: true,
tolerance: 0.8
},
initialize: function (url, options) {
options = L.setOptions(this, options);
this._url = url;
var imageSize = L.point(options.width, options.height),
tileSize = options.tileSize;
this._imageSize = [imageSize];
this._gridSize = [this._getGridSize(imageSize)];
while (imageSize.x > tileSize || imageSize.y > tileSize) {
imageSize = imageSize.divideBy(2).floor();
this._imageSize.push(imageSize);
this._gridSize.push(this._getGridSize(imageSize));
}
this._imageSize.reverse();
this._gridSize.reverse();
this.options.maxZoom = this._gridSize.length - 1;
},
onAdd: function (map) {
L.TileLayer.prototype.onAdd.call(this, map);
var mapSize = map.getSize(),
zoom = this._getBestFitZoom(mapSize),
imageSize = this._imageSize[zoom],
center = map.options.crs.pointToLatLng(L.point(imageSize.x / 2, imageSize.y / 2), zoom);
//map.setView(center, zoom, true);
},
getZoomifyBounds: function(map) {
//return "getZoomifyBounds";
var imageSize = this._imageSize[0],
topleft = map.options.crs.pointToLatLng(L.point(0, 0), 0),
bottomright = map.options.crs.pointToLatLng(L.point(imageSize.x, imageSize.y), 0),
bounds = L.latLngBounds(topleft, bottomright);
return bounds;
//[[75, -132], [-30, 128]]
},
getCenterZoom: function(map) {
var mapSize = map.getSize(),
zoom = this._getBestFitZoom(mapSize),
imageSize = this._imageSize[zoom],
center = map.options.crs.pointToLatLng(L.point(imageSize.x / 2, imageSize.y / 2), zoom);
return {
center: center,
lat: center.lat,
lon: center.lng,
zoom: zoom
};
},
_getGridSize: function (imageSize) {
var tileSize = this.options.tileSize;
return L.point(Math.ceil(imageSize.x / tileSize), Math.ceil(imageSize.y / tileSize));
},
_getBestFitZoom: function (mapSize) {
var tolerance = this.options.tolerance,
zoom = this._imageSize.length - 1,
imageSize, zoom;
while (zoom) {
imageSize = this._imageSize[zoom];
if (imageSize.x * tolerance < mapSize.x && imageSize.y * tolerance < mapSize.y) {
return zoom;
}
zoom--;
}
return zoom;
},
_tileShouldBeLoaded: function (tilePoint) {
var gridSize = this._gridSize[this._map.getZoom()];
if (gridSize) {
return (tilePoint.x >= 0 && tilePoint.x < gridSize.x && tilePoint.y >= 0 && tilePoint.y < gridSize.y);
} else {
console.log("_tileShouldBeLoaded: No gridSize for " + this._map.getZoom());
return false;
}
},
_addTile: function (tilePoint, container) {
var tilePos = this._getTilePos(tilePoint),
tile = this._getTile(),
zoom = this._map.getZoom(),
imageSize = this._imageSize[zoom],
gridSize = this._gridSize[zoom],
tileSize = this.options.tileSize;
if (tilePoint.x === gridSize.x - 1) {
tile.style.width = imageSize.x - (tileSize * (gridSize.x - 1)) + 'px';
}
if (tilePoint.y === gridSize.y - 1) {
tile.style.height = imageSize.y - (tileSize * (gridSize.y - 1)) + 'px';
}
L.DomUtil.setPosition(tile, tilePos, L.Browser.chrome || L.Browser.android23);
this._tiles[tilePoint.x + ':' + tilePoint.y] = tile;
this._loadTile(tile, tilePoint);
if (tile.parentNode !== this._tileContainer) {
container.appendChild(tile);
}
},
getTileUrl: function (tilePoint) {
return this._url + 'TileGroup' + this._getTileGroup(tilePoint) + '/' + this._map.getZoom() + '-' + tilePoint.x + '-' + tilePoint.y + '.jpg';
},
_getTileGroup: function (tilePoint) {
var zoom = this._map.getZoom(),
num = 0,
gridSize;
for (z = 0; z < zoom; z++) {
gridSize = this._gridSize[z];
num += gridSize.x * gridSize.y;
}
num += tilePoint.y * this._gridSize[zoom].x + tilePoint.x;
return Math.floor(num / 256);;
}
});
L.tileLayer.zoomify = function (url, options) {
return new L.TileLayer.Zoomify(url, options);
};
/*
https://github.com/Norkart/Leaflet-MiniMap
*/
L.Control.MiniMap = L.Control.extend({
options: {
position: 'bottomright',
toggleDisplay: false,
zoomLevelOffset: -5,
zoomLevelFixed: false,
zoomAnimation: false,
autoToggleDisplay: false,
show_view: true,
width: 150,
height: 150,
aimingRectOptions: {
color: "#c34528",
weight: 1,
clickable: false,
stroke:true
},
shadowRectOptions: {
color: "#000000",
weight: 1,
clickable: false,
opacity: 0,
fillOpacity: 0
}
},
hideText: 'Hide MiniMap',
showText: 'Show MiniMap',
//layer is the map layer to be shown in the minimap
initialize: function(layer, options) {
L.Util.setOptions(this, options);
//Make sure the aiming rects are non-clickable even if the user tries to set them clickable (most likely by forgetting to specify them false)
this.options.aimingRectOptions.clickable = false;
this.options.shadowRectOptions.clickable = false;
this._layer = layer;
},
onAdd: function(map) {
this._mainMap = map;
//Creating the container and stopping events from spilling through to the main map.
this._container = L.DomUtil.create('div', 'leaflet-control-minimap');
this._container.style.width = this.options.width + 'px';
this._container.style.height = this.options.height + 'px';
L.DomEvent.disableClickPropagation(this._container);
L.DomEvent.on(this._container, 'mousewheel', L.DomEvent.stopPropagation);
this._miniMap = new L.Map(this._container, {
attributionControl: false,
zoomControl: false,
zoomAnimation: this.options.zoomAnimation,
autoToggleDisplay: this.options.autoToggleDisplay,
touchZoom: false,
scrollWheelZoom: false,
doubleClickZoom: false,
boxZoom: false,
dragging:false,
crs: map.options.crs
});
this._miniMap.addLayer(this._layer);
//These bools are used to prevent infinite loops of the two maps notifying each other that they've moved.
this._mainMapMoving = false;
this._miniMapMoving = false;
//Keep a record of this to prevent auto toggling when the user explicitly doesn't want it.
this._userToggledDisplay = false;
this._minimized = false;
if (this.options.toggleDisplay) {
this._addToggleButton();
}
this._miniMap.whenReady(L.Util.bind(function() {
this._aimingRect = L.rectangle(this._mainMap.getBounds(), this.options.aimingRectOptions).addTo(this._miniMap);
this._shadowRect = L.rectangle(this._mainMap.getBounds(), this.options.shadowRectOptions).addTo(this._miniMap);
this._locationCircle = L.circleMarker(this._mainMap.getCenter(), {
fillColor: "#c34528",
color: "#FFFFFF",
weight:2,
radius: 10,
fill:true,
fillOpacity: 1,
stroke:true,
clickable: false
}).addTo(this._miniMap);
this._locationCircle.setRadius(5);
this._mainMap.on('moveend', this._onMainMapMoved, this);
this._mainMap.on('move', this._onMainMapMoving, this);
//this._miniMap.on('movestart', this._onMiniMapMoveStarted, this);
//this._miniMap.on('move', this._onMiniMapMoving, this);
//this._miniMap.on('moveend', this._onMiniMapMoved, this);
if (this.options.bounds_array) {
this._miniMap.fitBounds(this.options.bounds_array, {padding:[15,15]});
}
}, this));
return this._container;
},
minimize: function(hide_completely) {
if (!this._minimized) {
this._minimize();
}
},
restore: function() {
if (this._minimized) {
this._restore();
this._miniMap.fitBounds(this.options.bounds_array, {padding:[15,15]});
}
},
addTo: function(map) {
L.Control.prototype.addTo.call(this, map);
this._miniMap.setView(this._mainMap.getCenter(), this._decideZoom(true));
this._setDisplay(this._decideMinimized());
return this;
},
onRemove: function(map) {
this._mainMap.off('moveend', this._onMainMapMoved, this);
this._mainMap.off('move', this._onMainMapMoving, this);
this._miniMap.off('moveend', this._onMiniMapMoved, this);
this._miniMap.removeLayer(this._layer);
},
_addToggleButton: function() {
this._toggleDisplayButton = this.options.toggleDisplay ? this._createButton('', this.hideText, 'leaflet-control-minimap-toggle-display', this._container, this._toggleDisplayButtonClicked, this) : undefined;
},
_createButton: function(html, title, className, container, fn, context) {
var link = L.DomUtil.create('a', className, container);
link.innerHTML = html;
link.href = '#';
link.title = title;
var stop = L.DomEvent.stopPropagation;
L.DomEvent.on(link, 'click', stop)
.on(link, 'mousedown', stop)
.on(link, 'dblclick', stop)
.on(link, 'click', L.DomEvent.preventDefault)
.on(link, 'click', fn, context);
return link;
},
_toggleDisplayButtonClicked: function() {
this._userToggledDisplay = true;
if (!this._minimized) {
this._minimize();
this._toggleDisplayButton.title = this.showText;
} else {
this._restore();
this._toggleDisplayButton.title = this.hideText;
}
},
_setDisplay: function(minimize) {
if (minimize != this._minimized) {
if (!this._minimized) {
this._minimize();
} else {
this._restore();
}
}
},
_minimize: function() {
this._container.style.width = '0px';
this._container.style.height = '0px';
this._minimized = true;
},
_restore: function() {
this._container.style.width = this.options.width + 'px';
this._container.style.height = this.options.height + 'px';
this._minimized = false;
},
_onMainMapMoved: function(e) {
if (!this._miniMapMoving) {
var zoom = this._decideZoom(true);
if (zoom != 0) {
//this._miniMap.setView(this._mainMap.getCenter(), this._decideZoom(true));
//this._miniMap.setView(this._mainMap.getCenter());
}
this._mainMapMoving = true;
this._setDisplay(this._decideMinimized());
} else {
this._miniMapMoving = false;
}
if (this.options.show_view) {
this._aimingRect.setBounds(this._mainMap.getBounds());
}
this._locationCircle.setLatLng(this._mainMap.getCenter());
},
_onMainMapMoving: function(e) {
if (this.options.show_view) {
this._aimingRect.setBounds(this._mainMap.getBounds());
}
this._locationCircle.setLatLng(this._mainMap.getCenter());
},
_onMiniMapMoveStarted: function(e) {
var lastAimingRect = this._aimingRect.getBounds();
var sw = this._miniMap.latLngToContainerPoint(lastAimingRect.getSouthWest());
var ne = this._miniMap.latLngToContainerPoint(lastAimingRect.getNorthEast());
this._lastAimingRectPosition = {
sw: sw,
ne: ne
};
},
_onMiniMapMoving: function(e) {
if (!this._mainMapMoving && this._lastAimingRectPosition) {
this._shadowRect.setBounds(new L.LatLngBounds(this._miniMap.containerPointToLatLng(this._lastAimingRectPosition.sw), this._miniMap.containerPointToLatLng(this._lastAimingRectPosition.ne)));
this._shadowRect.setStyle({
opacity: 1,
fillOpacity: 0.3
});
}
},
_onMiniMapMoved: function(e) {
if (!this._mainMapMoving) {
this._miniMapMoving = true;
this._mainMap.setView(this._miniMap.getCenter(), this._decideZoom(false));
this._shadowRect.setStyle({
opacity: 0,
fillOpacity: 0
});
} else {
this._mainMapMoving = false;
}
},
_decideZoom: function(fromMaintoMini) {
if (!this.options.zoomLevelFixed && this.options.zoomLevelFixed != 0) {
if (fromMaintoMini) {
return this._mainMap.getZoom() + this.options.zoomLevelOffset;
} else {
var currentDiff = this._miniMap.getZoom() - this._mainMap.getZoom();
var proposedZoom = this._miniMap.getZoom() - this.options.zoomLevelOffset;
var toRet;
if (currentDiff > this.options.zoomLevelOffset && this._mainMap.getZoom() < this._miniMap.getMinZoom() - this.options.zoomLevelOffset) {
//This means the miniMap is zoomed out to the minimum zoom level and can't zoom any more.
if (this._miniMap.getZoom() > this._lastMiniMapZoom) {
//This means the user is trying to zoom in by using the minimap, zoom the main map.
toRet = this._mainMap.getZoom() + 1;
//Also we cheat and zoom the minimap out again to keep it visually consistent.
this._miniMap.setZoom(this._miniMap.getZoom() - 1);
} else {
//Either the user is trying to zoom out past the mini map's min zoom or has just panned using it, we can't tell the difference.
//Therefore, we ignore it!
toRet = this._mainMap.getZoom();
}
} else {
//This is what happens in the majority of cases, and always if you configure the min levels + offset in a sane fashion.
toRet = proposedZoom;
}
this._lastMiniMapZoom = this._miniMap.getZoom();
return toRet;
}
} else {
if (fromMaintoMini) {
return this.options.zoomLevelFixed;
} else {
return this._mainMap.getZoom();
}
}
},
_decideMinimized: function() {
if (this._userToggledDisplay) {
return this._minimized;
}
if (this.options.autoToggleDisplay) {
if (this._mainMap.getBounds().contains(this._miniMap.getBounds())) {
return true;
}
return false;
}
return this._minimized;
}
});
L.Map.mergeOptions({
miniMapControl: false
});
L.Map.addInitHook(function() {
if (this.options.miniMapControl) {
this.miniMapControl = (new L.Control.MiniMap()).addTo(this);
}
});
L.control.minimap = function(options) {
return new L.Control.MiniMap(options);
};
/* VCO.TyleLayer.Stamen
Makes Stamen Map tiles available
http://maps.stamen.com/
================================================== */
(function(exports) {
/* tile.stamen.js v1.2.3
================================================== */
var SUBDOMAINS = "a b c d".split(" "),
MAKE_PROVIDER = function(layer, type, minZoom, maxZoom) {
return {
"url": ["http://stamen-tiles-{S}.a.ssl.fastly.net/", layer, "/{Z}/{X}/{Y}.", type].join(""),
"type": type,
"subdomains": SUBDOMAINS.slice(),
"minZoom": minZoom,
"maxZoom": maxZoom,
"attribution": [
"Leaflet | ",
'Map tiles by Stamen Design, ',
'under CC BY 3.0. ',
'Data by OpenStreetMap, ',
'under CC BY SA.'
].join("")
};
},
PROVIDERS = {
"toner": MAKE_PROVIDER("toner", "png", 0, 20),
"terrain": MAKE_PROVIDER("terrain", "jpg", 4, 18),
"watercolor": MAKE_PROVIDER("watercolor", "jpg", 1, 16),
"trees-cabs-crime": {
"url": "http://{S}.tiles.mapbox.com/v3/stamen.trees-cabs-crime/{Z}/{X}/{Y}.png",
"type": "png",
"subdomains": "a b c d".split(" "),
"minZoom": 11,
"maxZoom": 18,
"extent": [
{"lat": 37.853, "lon": -122.577},
{"lat": 37.684, "lon": -122.313}
],
"attribution": [
'Design by Shawn Allen at Stamen.',
'Data courtesy of FuF,',
'Yellow Cab',
'& SFPD.'
].join(" ")
}
};
// set up toner and terrain flavors
setupFlavors("toner", ["hybrid", "labels", "lines", "background", "lite"]);
// toner 2010
setupFlavors("toner", ["2010"]);
// toner 2011 flavors
setupFlavors("toner", ["2011", "2011-lines", "2011-labels", "2011-lite"]);
setupFlavors("terrain", ["background"]);
setupFlavors("terrain", ["labels", "lines"], "png");
/* Export stamen.tile to the provided namespace.
================================================== */
exports.stamen = exports.stamen || {};
exports.stamen.tile = exports.stamen.tile || {};
exports.stamen.tile.providers = PROVIDERS;
exports.stamen.tile.getProvider = getProvider;
/* A shortcut for specifying "flavors" of a style, which are assumed to have the
same type and zoom range.
================================================== */
function setupFlavors(base, flavors, type) {
var provider = getProvider(base);
for (var i = 0; i < flavors.length; i++) {
var flavor = [base, flavors[i]].join("-");
PROVIDERS[flavor] = MAKE_PROVIDER(flavor, type || provider.type, provider.minZoom, provider.maxZoom);
}
}
/* Get the named provider, or throw an exception
if it doesn't exist.
================================================== */
function getProvider(name) {
if (name in PROVIDERS) {
return PROVIDERS[name];
} else {
throw 'No such provider (' + name + ')';
}
}
/* StamenTileLayer for modestmaps-js
Works with both 1.x and 2.x by checking for the existence of MM.Template.
================================================== */
if (typeof MM === "object") {
var ModestTemplate = (typeof MM.Template === "function")
? MM.Template
: MM.TemplatedMapProvider;
MM.StamenTileLayer = function(name) {
var provider = getProvider(name);
this._provider = provider;
MM.Layer.call(this, new ModestTemplate(provider.url, provider.subdomains));
this.provider.setZoomRange(provider.minZoom, provider.maxZoom);
this.attribution = provider.attribution;
};
MM.StamenTileLayer.prototype = {
setCoordLimits: function(map) {
var provider = this._provider;
if (provider.extent) {
map.coordLimits = [
map.locationCoordinate(provider.extent[0]).zoomTo(provider.minZoom),
map.locationCoordinate(provider.extent[1]).zoomTo(provider.maxZoom)
];
return true;
} else {
return false;
}
}
};
MM.extend(MM.StamenTileLayer, MM.Layer);
}
/* StamenTileLayer for Leaflet
Tested with version 0.3 and 0.4, but should work on all 0.x releases.
================================================== */
if (typeof L === "object") {
L.StamenTileLayer = L.TileLayer.extend({
initialize: function(name, options) {
var provider = getProvider(name),
url = provider.url.replace(/({[A-Z]})/g, function(s) {
return s.toLowerCase();
}),
_options = {
minZoom: provider.minZoom,
maxZoom: provider.maxZoom,
subdomains: provider.subdomains,
scheme: "xyz",
attribution: provider.attribution
};
if (options) {
VCO.Util.mergeData(_options, options);
}
L.TileLayer.prototype.initialize.call(this, url, _options);
}
});
}
/* StamenMapType for Google Maps API V3
================================================== */
if (typeof google === "object" && typeof google.maps === "object") {
google.maps.StamenMapType = function(name) {
var provider = getProvider(name),
subdomains = provider.subdomains;
return google.maps.ImageMapType.call(this, {
"getTileUrl": function(coord, zoom) {
var numTiles = 1 << zoom,
wx = coord.x % numTiles,
x = (wx < 0) ? wx + numTiles : wx,
y = coord.y,
index = (zoom + x + y) % subdomains.length;
return provider.url
.replace("{S}", subdomains[index])
.replace("{Z}", zoom)
.replace("{X}", x)
.replace("{Y}", y);
},
"tileSize": new google.maps.Size(256, 256),
"name": name,
"minZoom": provider.minZoom,
"maxZoom": provider.maxZoom
});
};
// FIXME: is there a better way to extend classes in Google land?
google.maps.StamenMapType.prototype = new google.maps.ImageMapType("_");
}
})(typeof exports === "undefined" ? this : exports);
/* VCO.MapMarker
Creates a marker. Takes a data object and
populates the marker with content.
================================================== */
VCO.MapMarker = VCO.Class.extend({
includes: [VCO.Events],
/* Constructor
================================================== */
initialize: function(data, options) {
// DOM Elements
this._el = {
container: {},
content_container: {},
content: {}
};
// Components
this._marker = {};
// Icon
this._icon = {};
this._custom_icon = false;
this._custom_icon_url = "";
this._custom_image_icon = false;
// Marker Number
this.marker_number = 0;
// Media Icon
this.media_icon_class = "";
// Timer
this.timer = {};
// Data
this.data = {};
// Options
this.options = {
// animation
duration: 1000,
ease: VCO.Ease.easeInSpline,
width: 600,
height: 600,
map_popup: false,
use_custom_markers: false
};
// Animation Object
this.animator = null;
// Merge Data and Options
VCO.Util.mergeData(this.options, options);
VCO.Util.mergeData(this.data, data);
this._initLayout();
},
/* Public
================================================== */
show: function() {
},
hide: function() {
},
addTo: function(m) {
this._addTo(m);
},
removeFrom: function(m) {
this._removeFrom(m)
},
updateDisplay: function(w, h, a) {
this._updateDisplay(w, h, a);
},
createMarker: function(d, o) {
this._createMarker(d, o);
},
createPopup: function(d, o) {
this._createPopup(d, o);
},
active: function(a) {
this._active(a);
},
location: function() {
return this._location();
},
/* Marker Specific
Specific to Map API
================================================== */
_createMarker: function(d, o) {
},
_addTo: function(m) {
},
_removeFrom: function(m) {
},
_createPopup: function(d, o) {
},
_active: function(a) {
},
_location: function() {
return {lat:0, lng:0}
},
/* Events
================================================== */
_onMarkerClick: function(e) {
this.fire("markerclick", {marker_number: this.marker_number});
},
/* Private Methods
================================================== */
_initLayout: function () {
this._createMarker(this.data, this.options);
},
// Update Display
_updateDisplay: function(width, height, animate) {
}
});
/* VCO.Map
Makes a Map
Events:
markerAdded
markerRemoved
================================================== */
VCO.Map = VCO.Class.extend({
includes: [VCO.Events, VCO.DomMixins],
_el: {},
/* Constructor
================================================== */
initialize: function(elem, data, options) {
// DOM ELEMENTS
this._el = {
container: {},
map: {},
map_mask: {}
};
if (typeof elem === 'object') {
this._el.container = elem;
} else {
this._el.container = VCO.Dom.get(elem);
}
// LOADED
this._loaded = {
data: false,
map: false
};
// MAP
this._map = null;
// MINI MAP
this._mini_map = null;
// Markers
this._markers = [];
// Marker Zoom Miniumum and Maximum
this.zoom_min_max = {
min: null,
max: null
};
// Line
this._line = null;
this._line_active = null;
// Current Marker
this.current_marker = 0;
// Markers Bounds Array
this.bounds_array = null;
// Map Tiles Layer
this._tile_layer = null;
// Map Tiles Layer for Mini Map
this._tile_layer_mini = null;
// Image Layer (for zoomify)
this._image_layer = null;
// Data
this.data = {
uniqueid: "",
slides: [{test:"yes"}, {test:"yes"}, {test:"yes"}]
};
//Options
this.options = {
map_type: "stamen:toner-lite",
map_as_image: false,
map_mini: false,
map_background_color: "#d9d9d9",
map_subdomains: "",
zoomify: {
path: "",
width: "",
height: "",
tolerance: 0.8,
attribution: ""
},
skinny_size: 650,
less_bounce: true,
path_gfx: "gfx",
start_at_slide: 0,
map_popup: false,
zoom_distance: 100,
calculate_zoom: true, // Allow map to determine best zoom level between markers (recommended)
line_follows_path: true, // Map history path follows default line, if false it will connect previous and current only
line_color: "#333",
line_color_inactive: "#000",
line_weight: 5,
line_opacity: 0.20,
line_dash: "5,5",
line_join: "miter",
show_lines: true,
show_history_line: true,
use_custom_markers: false,
map_center_offset: null // takes object {top:0,left:0}
};
// Animation
this.animator = null;
// Timer
this.timer = null;
// Touchpad Events
this.touch_scale = 1;
this.scroll = {
start_time: null
};
// Merge Data and Options
VCO.Util.mergeData(this.options, options);
VCO.Util.mergeData(this.data, data);
this._initLayout();
this._initEvents();
this._createMap();
this._initData();
},
/* Public
================================================== */
updateDisplay: function(w, h, animate, d, offset) {
this._updateDisplay(w, h, animate, d, offset);
},
goTo: function(n, change) {
if (n < this._markers.length && n >= 0) {
var zoom = 0,
previous_marker = this.current_marker;
this.current_marker = n;
var marker = this._markers[this.current_marker];
// Stop animation
if (this.animator) {
this.animator.stop();
}
// Reset Active Markers
this._resetMarkersActive();
// Check to see if it's an overview
if (marker.data.type && marker.data.type == "overview") {
this._markerOverview();
if (!change) {
this._onMarkerChange();
}
} else {
// Make marker active
marker.active(true);
if (change) {
// Set Map View
if (marker.data.location) {
this._viewTo(marker.data.location);
} else {
}
} else {
if (marker.data.location && marker.data.location.lat) {
// Calculate Zoom
zoom = this._calculateZoomChange(this._getMapCenter(true), marker.location());
// Set Map View
this._viewTo(marker.data.location, {calculate_zoom: this.options.calculate_zoom, zoom:zoom});
// Show Line
if (this.options.line_follows_path) {
if (this.options.show_history_line && marker.data.real_marker && this._markers[previous_marker].data.real_marker) {
var lines_array = [],
line_num = previous_marker,
point;
if (line_num < this.current_marker) {
while (line_num < this.current_marker) {
if (this._markers[line_num].data.location && this._markers[line_num].data.location.lat) {
point = {
lat:this._markers[line_num].data.location.lat,
lon:this._markers[line_num].data.location.lon
}
lines_array.push(point);
}
line_num++;
}
} else if (line_num > this.current_marker) {
while (line_num > this.current_marker) {
if (this._markers[line_num].data.location && this._markers[line_num].data.location.lat) {
point = {
lat:this._markers[line_num].data.location.lat,
lon:this._markers[line_num].data.location.lon
}
lines_array.push(point);
}
line_num--;
}
}
lines_array.push({
lat:marker.data.location.lat,
lon:marker.data.location.lon
});
this._replaceLines(this._line_active, lines_array);
}
} else {
// Show Line
if (this.options.show_history_line && marker.data.real_marker && this._markers[previous_marker].data.real_marker) {
this._replaceLines(this._line_active, [
{
lat:marker.data.location.lat,
lon:marker.data.location.lon
},
{
lat:this._markers[previous_marker].data.location.lat,
lon:this._markers[previous_marker].data.location.lon
}
])
}
}
} else {
this._markerOverview();
if (!change) {
this._onMarkerChange();
}
}
// Fire Event
this._onMarkerChange();
}
}
}
},
panTo: function(loc, animate) {
this._panTo(loc, animate);
},
zoomTo: function(z, animate) {
this._zoomTo(z, animate);
},
viewTo: function(loc, opts) {
this._viewTo(loc, opts);
},
getBoundsZoom: function(m1, m2, inside, padding) {
this.__getBoundsZoom(m1, m2, inside, padding); // (LatLngBounds[, Boolean, Point]) -> Number
},
markerOverview: function() {
this._markerOverview();
},
calculateMarkerZooms: function() {
this._calculateMarkerZooms();
},
createMiniMap: function() {
this._createMiniMap();
},
setMapOffset: function(left, top) {
// Update Component Displays
this.options.map_center_offset.left = left;
this.options.map_center_offset.top = top;
},
calculateMinMaxZoom: function() {
for (var i = 0; i < this._markers.length; i++) {
if (this._markers[i].data.location && this._markers[i].data.location.zoom) {
this.updateMinMaxZoom(this._markers[i].data.location.zoom);
}
}
trace("MAX ZOOM: " + this.zoom_min_max.max + " MIN ZOOM: " + this.zoom_min_max.min);
},
updateMinMaxZoom: function(zoom) {
if (!this.zoom_min_max.max) {
this.zoom_min_max.max = zoom;
}
if (!this.zoom_min_max.min) {
this.zoom_min_max.min = zoom;
}
if (this.zoom_min_max.max < zoom) {
this.zoom_min_max.max = zoom;
}
if (this.zoom_min_max.min > zoom) {
this.zoom_min_max.min = zoom;
}
},
initialMapLocation: function() {
if (this._loaded.data && this._loaded.map) {
this.goTo(this.options.start_at_slide, true);
this._initialMapLocation();
}
},
/* Adding, Hiding, Showing etc
================================================== */
show: function() {
},
hide: function() {
},
addTo: function(container) {
container.appendChild(this._el.container);
this.onAdd();
},
removeFrom: function(container) {
container.removeChild(this._el.container);
this.onRemove();
},
/* Adding and Removing Markers
================================================== */
createMarkers: function(array) {
this._createMarkers(array)
},
createMarker: function(d) {
this._createMarker(d);
},
_destroyMarker: function(marker) {
this._removeMarker(marker);
for (var i = 0; i < this._markers.length; i++) {
if (this._markers[i] == marker) {
this._markers.splice(i, 1);
}
}
this.fire("markerRemoved", marker);
},
_createMarkers: function(array) {
for (var i = 0; i < array.length; i++) {
this._createMarker(array[i]);
if (array[i].location && array[i].location.lat && this.options.show_lines) {
this._addToLine(this._line, array[i]);
}
};
},
_createLines: function(array) {
},
/* Map Specific
================================================== */
/* Map Specific Create
================================================== */
// Extend this map class and use this to create the map using preferred API
_createMap: function() {
},
/* Mini Map Specific Create
================================================== */
// Extend this map class and use this to create the map using preferred API
_createMiniMap: function() {
},
/* Map Specific Marker
================================================== */
// Specific Marker Methods based on preferred Map API
_createMarker: function(d) {
var marker = {};
marker.on("markerclick", this._onMarkerClick);
this._addMarker(marker);
this._markers.push(marker);
marker.marker_number = this._markers.length - 1;
this.fire("markerAdded", marker);
},
_addMarker: function(marker) {
},
_removeMarker: function(marker) {
},
_resetMarkersActive: function() {
for (var i = 0; i < this._markers.length; i++) {
this._markers[i].active(false);
};
},
_calculateMarkerZooms: function() {
},
/* Map Specific Line
================================================== */
_createLine: function(d) {
return {data: d};
},
_addToLine: function(line, d) {
},
_replaceLines: function(line, d) {
},
_addLineToMap: function(line) {
},
/* Map Specific Methods
================================================== */
_panTo: function(loc, animate) {
},
_zoomTo: function(z, animate) {
},
_viewTo: function(loc, opts) {
},
_updateMapDisplay: function(animate, d) {
},
_refreshMap: function() {
},
_getMapLocation: function(m) {
return {x:0, y:0};
},
_getMapZoom: function() {
return 1;
},
_getMapCenter: function() {
return {lat:0, lng:0};
},
_getBoundsZoom: function(m1, m2, inside, padding) {
},
_markerOverview: function() {
},
_initialMapLocation: function() {
},
/* Events
================================================== */
_onMarkerChange: function(e) {
this.fire("change", {current_marker:this.current_marker});
},
_onMarkerClick: function(e) {
if (this.current_marker != e.marker_number) {
this.goTo(e.marker_number);
}
},
_onMapLoaded: function(e) {
this._loaded.map = true;
if (this.options.calculate_zoom) {
this.calculateMarkerZooms();
}
this.calculateMinMaxZoom();
if (this.options.map_mini && !VCO.Browser.touch) {
this.createMiniMap();
}
this.initialMapLocation();
this.fire("loaded", this.data);
},
_onWheel: function(e) {
// borrowed from http://jsbin.com/qiyaseza/5/edit
var self = this;
if (e.ctrlKey) {
var s = Math.exp(-e.deltaY/100);
this.touch_scale *= s;
e.preventDefault();
e.stopPropagation(e);
}
if (!this.scroll.start_time) {
this.scroll.start_time = +new Date();
};
var time_left = Math.max(40 - (+new Date() - this.scroll.start_time), 0);
clearTimeout(this.scroll.timer);
this.scroll.timer = setTimeout(function() {
self._scollZoom();
//e.preventDefault();
//e.stopPropagation(e);
}, time_left);
},
_scollZoom: function(e) {
var self = this,
current_zoom = this._getMapZoom();
this.scroll.start_time = null;
//VCO.DomUtil.addClass(this._el.container, 'vco-map-touch-zoom');
clearTimeout(this.scroll.timer);
clearTimeout(this.scroll.timer_done);
this.scroll.timer_done = setTimeout(function() {
self._scollZoomDone();
}, 1000);
this.zoomTo(Math.round(current_zoom * this.touch_scale));
},
_scollZoomDone: function(e) {
//VCO.DomUtil.removeClass(this._el.container, 'vco-map-touch-zoom');
this.touch_scale = 1;
},
/* Private Methods
================================================== */
_calculateZoomChange: function(origin, destination, correct_for_center) {
return this._getBoundsZoom(origin, destination, correct_for_center);
},
_updateDisplay: function(w, h, animate, d) {
// Update Map Display
this._updateMapDisplay(animate, d);
},
_initLayout: function() {
// Create Layout
this._el.map_mask = VCO.Dom.create("div", "vco-map-mask", this._el.container);
if (this.options.map_as_image) {
this._el.map = VCO.Dom.create("div", "vco-map-display vco-mapimage-display", this._el.map_mask);
} else {
this._el.map = VCO.Dom.create("div", "vco-map-display", this._el.map_mask);
}
},
_initData: function() {
if (this.data.slides) {
this._createMarkers(this.data.slides);
this._resetMarkersActive();
this._markers[this.current_marker].active(true);
this._loaded.data = true;
this._initialMapLocation();
}
},
_initEvents: function() {
var self = this;
this._el.map.addEventListener('wheel', function(e) {
self._onWheel(e);
});
//this.on("wheel", this._onWheel, this);
}
});
/* VCO.MapMarker.Leaflet
Produces a marker for Leaflet Maps
================================================== */
VCO.MapMarker.Leaflet = VCO.MapMarker.extend({
/* Create Marker
================================================== */
_createMarker: function(d, o) {
var icon = {}; //new L.Icon.Default();
if (d.location && d.location.lat && d.location.lon) {
this.data.real_marker = true;
if (o.use_custom_markers && d.location.icon && d.location.icon != "") {
this._custom_icon = true;
this._custom_icon_url = d.location.icon;
this._icon = new L.icon({iconUrl: this._custom_icon_url, iconSize: [48], iconAnchor:[24, 48]});
} else if (o.use_custom_markers && d.location.image && d.location.image != "") {
this._custom_image_icon = true;
this._custom_icon_url = d.location.image;
this._icon = new L.icon({iconUrl: this._custom_icon_url, iconSize: [48], iconAnchor:[24, 48], shadowSize: [68, 95], shadowAnchor: [22, 94], className:"vco-mapmarker-image-icon"});
} else {
this._icon = new L.divIcon({className: 'vco-mapmarker ' + this.media_icon_class, iconAnchor:[10, 10]});
}
this._marker = new L.marker([d.location.lat, d.location.lon], {
title: d.text.headline,
icon: this._icon
});
this._marker.on("click", this._onMarkerClick, this);
if (o.map_popup) {
this._createPopup(d, o);
}
}
},
_addTo: function(m) {
if (this.data.real_marker) {
this._marker.addTo(m);
}
},
_createPopup: function(d, o) {
/*
var html = "";
html += "" + this.data.text.headline + "
";
this._marker.bindPopup(html, {closeButton:false, offset:[0, 43]});
*/
},
_active: function(a) {
var self = this;
if (this.data.media && this.data.media.mediatype) {
this.media_icon_class = "vco-mapmarker-icon vco-icon-" + this.data.media.mediatype.type;
} else {
this.media_icon_class = "vco-mapmarker-icon vco-icon-plaintext";
}
if (this.data.real_marker) {
if (a) {
this._marker.setZIndexOffset(100);
if (this._custom_image_icon) {
this._icon = new L.icon({iconUrl: this._custom_icon_url, iconSize: [48], iconAnchor:[24, 48], shadowSize: [68, 95], shadowAnchor: [22, 94], className:"vco-mapmarker-image-icon-active"});
} else {
this._icon = new L.divIcon({className: 'vco-mapmarker-active ' + this.media_icon_class, iconAnchor:[10, 10]});
}
//this.timer = setTimeout(function() {self._openPopup();}, this.options.duration + 200);
this._setIcon();
} else {
//this._marker.closePopup();
clearTimeout(this.timer);
this._marker.setZIndexOffset(0);
if (this._custom_image_icon) {
this._icon = new L.icon({iconUrl: this._custom_icon_url, iconSize: [48], iconAnchor:[24, 48], shadowSize: [68, 95], shadowAnchor: [22, 94], className:"vco-mapmarker-image-icon"});
} else {
this._icon = new L.divIcon({className: 'vco-mapmarker ' + this.media_icon_class, iconAnchor:[10, 10]});
}
this._setIcon();
}
}
},
_openPopup: function() {
this._marker.openPopup();
},
_setIcon: function() {
this._marker.setIcon(this._icon);
},
_location: function() {
if (this.data.real_marker) {
return this._marker.getLatLng();
} else {
return {};
}
}
});
/* VCO.Map.Leaflet
Creates a Map using Leaflet
================================================== */
VCO.Map.Leaflet = VCO.Map.extend({
includes: [VCO.Events],
/* Create the Map
================================================== */
_createMap: function() {
this._map = new L.map(this._el.map, {scrollWheelZoom:false, zoomControl:!this.options.map_mini});
this._map.on("load", this._onMapLoaded, this);
this._map.on("moveend", this._onMapMoveEnd, this);
this._map.attributionControl.setPrefix("◼ StoryMapJS");
var map_type_arr = this.options.map_type.split(':');
// Create Tile Layer
this._tile_layer = this._createTileLayer(this.options.map_type);
this._tile_layer.on("load", this._onTilesLoaded, this);
// Add Tile Layer
this._map.addLayer(this._tile_layer);
// Add Zoomify Image Layer
if (this._image_layer) {
this._map.addLayer(this._image_layer);
}
// Create Overall Connection Line
this._line = this._createLine(this._line);
this._line.setStyle({color:this.options.line_color_inactive});
this._addLineToMap(this._line);
// Create Active Line
this._line_active = this._createLine(this._line_active);
this._line_active.setStyle({opacity:1});
this._addLineToMap(this._line_active);
if (this.options.map_as_image) {
this._line_active.setStyle({opacity:0});
this._line.setStyle({opacity:0});
}
},
/* Create Mini Map
================================================== */
_createMiniMap: function() {
if (this.options.map_as_image) {
this.zoom_min_max.min = 0;
}
if (!this.bounds_array) {
this.bounds_array = this._getAllMarkersBounds(this._markers);
}
this._tile_layer_mini = this._createTileLayer(this.options.map_type);
this._mini_map = new L.Control.MiniMap(this._tile_layer_mini, {
width: 150,
height: 100,
position: "topleft",
bounds_array: this.bounds_array,
zoomLevelFixed: this.zoom_min_max.min,
zoomAnimation: true,
aimingRectOptions: {
fillColor: "#FFFFFF",
color: "#FFFFFF",
opacity: 0.4,
weight: 1,
stroke: true
}
}).addTo(this._map);
this._mini_map.getContainer().style.backgroundColor = this.options.map_background_color;
},
/* Create Background Map
================================================== */
_createBackgroundMap: function(tiles) {
// TODO Check width and height
trace("CREATE BACKGROUND MAP");
if (!this._image_layer) {
// Make Image Layer a Group
this._image_layer = new L.layerGroup();
// Add Layer Group to Map
this._map.addLayer(this._image_layer);
} else {
this._image_layer.clearLayers();
}
if (tiles) {
// Create Image Overlay for each tile in the group
for (x in tiles) {
var target_tile = tiles[x],
image = {},
tile = {
x: 0,
y: 0,
url: target_tile.src,
height: parseInt(target_tile.style.height.split("px")[0]),
width: parseInt(target_tile.style.width.split("px")[0]),
pos: {
start: 0,
end: 0
}
};
if (target_tile.style.left || target_tile.style.top) {
if (target_tile.style.left) {
tile.x = parseInt(target_tile.style.left.split("px")[0]);
}
if (target_tile.style.top) {
tile.y = parseInt(target_tile.style.top.split("px")[0]);
}
} else if (target_tile.style["-webkit-transform"] || target_tile.style["transform"] || target_tile.style["-ms-transform"]) {
var t_array;
if (target_tile.style["-webkit-transform"]) {
t_array = target_tile.style["-webkit-transform"].split("3d(")[1].split(", 0)")[0].split(", ");
} else if (target_tile.style["transform"]) {
t_array = target_tile.style["transform"].split("3d(")[1].split(", 0)")[0].split(", ");
} else if (target_tile.style["-ms-transform"]) {
t_array = target_tile.style["-ms-transform"].split("3d(")[1].split(", 0)")[0].split(", ");
}
tile.x = parseInt(t_array[0].split("px")[0]);
tile.y = parseInt(t_array[1].split("px")[0]);
}
// If using toner, switch to toner lines
if (tile.url.match("toner")) {
//tile.url = tile.url.replace("/toner-lite/","/toner-lines/");
tile.url = tile.url.replace("/toner-hybrid/","/toner-lines/");
tile.url = tile.url.replace("/toner/","/toner-background/");
}
tile.pos.start = this._map.containerPointToLatLng([tile.x, tile.y]);
tile.pos.end = this._map.containerPointToLatLng([tile.x + tile.width, tile.y + tile.height]);
image = new L.imageOverlay(tile.url, [tile.pos.start, tile.pos.end]);
this._image_layer.addLayer(image);
}
}
},
/* Create Tile Layer
================================================== */
_createTileLayer: function(map_type, options) {
var _tilelayer = null,
_map_type_arr = map_type.split(':'),
_options = {},
_attribution_knightlab = "Leaflet | "
if (options) {
_options = options;
}
// Set Tiles
switch(_map_type_arr[0]) {
case 'mapbox':
var mapbox_name = _map_type_arr[1] || 'nuknightlab.hif6ioi4';
_options.subdomains = 'abcd';
_options.attribution = _attribution_knightlab + "© Mapbox © OpenStreetMap";
_tilelayer = new L.TileLayer("https://{s}.tiles.mapbox.com/v2/" + mapbox_name + "/{z}/{x}/{y}.png", _options);
break;
case 'stamen':
_tilelayer = new L.StamenTileLayer(_map_type_arr[1] || 'toner-lite', _options);
this._map.getContainer().style.backgroundColor = "#FFFFFF";
break;
case 'zoomify':
_options.width = this.options.zoomify.width;
_options.height = this.options.zoomify.height;
_options.tolerance = this.options.zoomify.tolerance;
_options.attribution = _attribution_knightlab + this.options.zoomify.attribution;
_tilelayer = new L.tileLayer.zoomify(this.options.zoomify.path, _options);
//this._image_layer = new L.imageOverlay(this.options.zoomify.path + "TileGroup0/0-0-0.jpg", _tilelayer.getZoomifyBounds(this._map));
break;
case 'osm':
_options.subdomains = 'ab';
_options.attribution = _attribution_knightlab + "© OpenStreetMap and contributors, under an open license";
_tilelayer = new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', _options);
break;
case 'http':
case 'https':
_options.subdomains = this.options.map_subdomains;
_tilelayer = new L.TileLayer(this.options.map_type, _options);
break;
default:
_tilelayer = new L.StamenTileLayer('toner', _options);
break;
}
return _tilelayer;
},
/* Events
================================================== */
_onMapMoveEnd: function(e) {
},
_onTilesLoaded: function(e) {
this._createBackgroundMap(e.target._tiles);
this._tile_layer.off("load", this._onTilesLoaded, this);
},
_onMapZoomed:function(e) {
trace("FIRST ZOOM");
this._map.off("zoomend", this._onMapZoomed, this);
},
_onMapZoom:function(e) {
},
/* Marker
================================================== */
_createMarker: function(d) {
var marker = new VCO.MapMarker.Leaflet(d, this.options);
marker.on('markerclick', this._onMarkerClick, this);
this._addMarker(marker);
this._markers.push(marker);
marker.marker_number = this._markers.length - 1;
this.fire("markerAdded", marker);
},
_addMarker: function(marker) {
marker.addTo(this._map);
},
_removeMarker: function(marker) {
},
_markerOverview: function() {
var _location, _zoom;
// Hide Active Line
this._line_active.setStyle({opacity:0});
if (this.options.map_type == "zoomify" && this.options.map_as_image) {
var _center_zoom = this._tile_layer.getCenterZoom(this._map);
_location = _center_zoom.center;
if (this.options.map_center_offset && this.options.map_center_offset.left != 0 || this.options.map_center_offset.top != 0) {
_center_zoom.zoom = _center_zoom.zoom - 1;
_location = this._getMapCenterOffset(_location, _center_zoom.zoom);
}
this._map.setView(_location, _center_zoom.zoom, {
pan:{animate: true, duration: this.options.duration/1000, easeLinearity:.10},
zoom:{animate: true, duration: this.options.duration/1000, easeLinearity:.10}
});
} else {
this.bounds_array = this._getAllMarkersBounds(this._markers);
if (this.options.map_center_offset && this.options.map_center_offset.left != 0 || this.options.map_center_offset.top != 0) {
var the_bounds = new L.latLngBounds(this.bounds_array);
_location = the_bounds.getCenter();
_zoom = this._map.getBoundsZoom(the_bounds)
_location = this._getMapCenterOffset(_location, _zoom - 1);
this._map.setView(_location, _zoom -1, {
pan:{animate: true, duration: this.options.duration/1000, easeLinearity:.10},
zoom:{animate: true, duration: this.options.duration/1000, easeLinearity:.10}
});
} else {
this._map.fitBounds(this.bounds_array, {padding:[15,15]});
}
}
if (this._mini_map) {
this._mini_map.minimize();
}
},
_getAllMarkersBounds: function(markers_array) {
var bounds_array = [];
for (var i = 0; i < markers_array.length; i++) {
if (markers_array[i].data.real_marker) {
bounds_array.push( [markers_array[i].data.location.lat, markers_array[i].data.location.lon]);
}
};
return bounds_array;
},
_calculateMarkerZooms: function() {
for (var i = 0; i < this._markers.length; i++) {
if (this._markers[i].data.location) {
var marker = this._markers[i],
prev_marker,
next_marker,
marker_location,
prev_marker_zoom,
next_marker_zoom,
calculated_zoom;
// MARKER LOCATION
if (marker.data.type && marker.data.type == "overview") {
marker_location = this._getMapCenter(true);
} else {
marker_location = marker.location();
}
// PREVIOUS MARKER ZOOM
if (i > 0 ) {
prev_marker = this._markers[i-1].location();
} else {
prev_marker = this._getMapCenter(true);
}
prev_marker_zoom = this._calculateZoomChange(prev_marker, marker_location);
// NEXT MARKER ZOOM
if (i < (this._markers.length - 1)) {
next_marker = this._markers[i+1].location();
} else {
next_marker = this._getMapCenter(true);
}
next_marker_zoom = this._calculateZoomChange(next_marker, marker_location);
if (prev_marker_zoom && prev_marker_zoom < next_marker_zoom) {
calculated_zoom = prev_marker_zoom;
} else if (next_marker_zoom){
calculated_zoom = next_marker_zoom;
} else {
calculated_zoom = prev_marker_zoom;
}
if (this.options.map_center_offset && this.options.map_center_offset.left != 0 || this.options.map_center_offset.top != 0) {
calculated_zoom = calculated_zoom -1;
}
marker.data.location.zoom = calculated_zoom;
}
};
},
/* Line
================================================== */
_createLine: function(d) {
return new L.Polyline([], {
clickable: false,
color: this.options.line_color,
weight: this.options.line_weight,
opacity: this.options.line_opacity,
dashArray: this.options.line_dash,
lineJoin: this.options.line_join,
className: "vco-map-line"
} );
},
_addLineToMap: function(line) {
this._map.addLayer(line);
},
_addToLine: function(line, d) {
line.addLatLng({lon: d.location.lon, lat: d.location.lat});
},
_replaceLines: function(line, array) {
line.setLatLngs(array);
},
/* Map
================================================== */
_panTo: function(loc, animate) {
this._map.panTo({lat:loc.lat, lon:loc.lon}, {animate: true, duration: this.options.duration/1000, easeLinearity:.10});
},
_zoomTo: function(z, animate) {
this._map.setZoom(z);
},
_viewTo: function(loc, opts) {
var _animate = true,
_duration = this.options.duration/1000,
_zoom = this._getMapZoom(),
_location = {lat:loc.lat, lon:loc.lon};
// Show Active Line
if (!this.options.map_as_image) {
this._line_active.setStyle({opacity:1});
}
if (loc.zoom) {
_zoom = loc.zoom;
}
// Options
if (opts) {
if (opts.duration) {
if (opts.duration == 0) {
_animate = false;
} else {
_duration = duration;
}
}
if (opts.zoom && this.options.calculate_zoom) {
_zoom = opts.zoom;
}
}
// OFFSET
if (this.options.map_center_offset) {
_location = this._getMapCenterOffset(_location, _zoom);
}
this._map.setView(
_location,
_zoom,
{
pan:{animate: _animate, duration: _duration, easeLinearity:.10},
zoom:{animate: _animate, duration: _duration, easeLinearity:.10}
}
)
if (this._mini_map && this.options.width > this.options.skinny_size) {
if ((_zoom - 1) <= this.zoom_min_max.min ) {
this._mini_map.minimize();
} else {
this._mini_map.restore();
//this._mini_map.updateDisplay(_location, _zoom, _duration);
}
}
},
_getMapLocation: function(m) {
return this._map.latLngToContainerPoint(m);
},
_getMapZoom: function() {
return this._map.getZoom();
},
_getMapCenter: function(offset) {
if (offset) {
}
return this._map.getCenter();
},
_getMapCenterOffset: function(location, zoom) {
var target_point,
target_latlng;
target_point = this._map.project(location, zoom).subtract([this.options.map_center_offset.left, this.options.map_center_offset.top]);
target_latlng = this._map.unproject(target_point, zoom);
return target_latlng;
},
_getBoundsZoom: function(origin, destination, correct_for_center) {
var _origin = origin,
_padding = [(Math.abs(this.options.map_center_offset.left)*3),(Math.abs(this.options.map_center_offset.top)*3)];
//_padding = [0,0];
//_padding = [0,0];
if (correct_for_center) {
var _lat = _origin.lat + (_origin.lat - destination.lat)/2,
_lng = _origin.lng + (_origin.lng - destination.lng)/2;
_origin = new L.LatLng(_lat, _lng);
}
var bounds = new L.LatLngBounds([_origin, destination]);
if (this.options.less_bounce) {
return this._map.getBoundsZoom(bounds, false, _padding);
} else {
return this._map.getBoundsZoom(bounds, true, _padding);
}
},
_getZoomifyZoom: function() {
},
_initialMapLocation: function() {
this._map.on("zoomend", this._onMapZoomed, this);
},
/* Display
================================================== */
_updateMapDisplay: function(animate, d) {
if (animate) {
var duration = this.options.duration,
self = this;
if (d) {duration = d };
if (this.timer) {clearTimeout(this.timer)};
this.timer = setTimeout(function() {
self._refreshMap();
}, duration);
} else {
if (!this.timer) {
this._refreshMap();
};
}
if (this._mini_map && this._el.container.offsetWidth < this.options.skinny_size ) {
this._mini_map.true_hide = true;
//this._mini_map.minimize();
} else if (this._mini_map) {
this._mini_map.true_hide = false;
}
},
_refreshMap: function() {
if (this._map) {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
};
this._map.invalidateSize();
// Check to see if it's an overview
if (this._markers[this.current_marker].data.type && this._markers[this.current_marker].data.type == "overview") {
this._markerOverview();
} else {
this._viewTo(this._markers[this.current_marker].data.location, {zoom:this._getMapZoom()});
}
};
}
});
/* Overwrite and customize Leaflet functions
================================================== */
L.Map.include({
_tryAnimatedPan: function (center, options) {
var offset = this._getCenterOffset(center)._floor();
this.panBy(offset, options);
return true;
},
_tryAnimatedZoom: function (center, zoom, options) {
if (this._animatingZoom) { return true; }
options = options || {};
// offset is the pixel coords of the zoom origin relative to the current center
var scale = this.getZoomScale(zoom),
offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale),
origin = this._getCenterLayerPoint()._add(offset);
this
.fire('movestart')
.fire('zoomstart');
this._animateZoom(center, zoom, origin, scale, null, true);
return true;
},
getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
bounds = L.latLngBounds(bounds);
var zoom = this.getMinZoom() - (inside ? 1 : 0),
minZoom = this.getMinZoom(),
maxZoom = this.getMaxZoom(),
size = this.getSize(),
nw = bounds.getNorthWest(),
se = bounds.getSouthEast(),
zoomNotFound = true,
boundsSize,
zoom_array = [],
best_zoom = {x:0,y:0},
smallest_zoom = {},
final_zoom = 0;
padding = L.point(padding || [0, 0]);
size = this.getSize();
// Calculate Zoom Level Differences
for (var i = 0; i < maxZoom; i++) {
zoom++;
boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)).add(padding);
zoom_array.push({
x:Math.abs(size.x - boundsSize.x),
y:Math.abs(size.y - boundsSize.y)
})
}
// Determine closest match
smallest_zoom = zoom_array[0];
for (var j = 0; j < zoom_array.length; j++) {
if (zoom_array[j].y <= smallest_zoom.y) {
smallest_zoom.y = zoom_array[j].y;
best_zoom.y = j;
}
if (zoom_array[j].x <= smallest_zoom.x) {
smallest_zoom.x = zoom_array[j].x;
best_zoom.x = j;
}
}
final_zoom = Math.round((best_zoom.y + best_zoom.x) / 2)
return final_zoom;
}
});
L.TileLayer.include({
getTiles: function() {
return this._tiles;
}
});
/* StoryMap
Designed and built by Zach Wise at VéritéCo
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
================================================== */
/*
TODO
Message for Data Loading
*/
/* Required Files
CodeKit Import
http://incident57.com/codekit/
================================================== */
// CORE
// @codekit-prepend "core/VCO.js";
// @codekit-prepend "core/VCO.Util.js";
// @codekit-prepend "data/VCO.Data.js";
// @codekit-prepend "core/VCO.Class.js";
// @codekit-prepend "core/VCO.Events.js";
// @codekit-prepend "core/VCO.Browser.js";
// @codekit-prepend "core/VCO.Load.js";
// LANGUAGE
// @codekit-prepend "language/VCO.Language.js";
// LIBRARY
// @codekit-prepend "library/VCO.Emoji.js";
// ANIMATION
// @codekit-prepend "animation/VCO.Ease.js";
// @codekit-prepend "animation/VCO.Animate.js";
// DOM
// @codekit-prepend "dom/VCO.Point.js";
// @codekit-prepend "dom/VCO.DomMixins.js";
// @codekit-prepend "dom/VCO.Dom.js";
// @codekit-prepend "dom/VCO.DomUtil.js";
// @codekit-prepend "dom/VCO.DomEvent.js";
// UI
// @codekit-prepend "ui/VCO.Draggable.js";
// @codekit-prepend "ui/VCO.Swipable.js";
// @codekit-prepend "ui/VCO.MenuBar.js";
// @codekit-prepend "ui/VCO.Message.js";
// MEDIA
// @codekit-prepend "media/VCO.MediaType.js";
// @codekit-prepend "media/VCO.Media.js";
// MEDIA TYPES
// @codekit-prepend "media/types/VCO.Media.Blockquote.js";
// @codekit-prepend "media/types/VCO.Media.Flickr.js";
// @codekit-prepend "media/types/VCO.Media.Instagram.js";
// @codekit-prepend "media/types/VCO.Media.Profile.js";
// @codekit-prepend "media/types/VCO.Media.GoogleDoc.js";
// @codekit-prepend "media/types/VCO.Media.GooglePlus.js";
// @codekit-prepend "media/types/VCO.Media.IFrame.js";
// @codekit-prepend "media/types/VCO.Media.Image.js";
// @codekit-prepend "media/types/VCO.Media.SoundCloud.js";
// @codekit-prepend "media/types/VCO.Media.Storify.js";
// @codekit-prepend "media/types/VCO.Media.Text.js";
// @codekit-prepend "media/types/VCO.Media.Twitter.js";
// @codekit-prepend "media/types/VCO.Media.Vimeo.js";
// @codekit-prepend "media/types/VCO.Media.DailyMotion.js";
// @codekit-prepend "media/types/VCO.Media.Vine.js";
// @codekit-prepend "media/types/VCO.Media.Website.js";
// @codekit-prepend "media/types/VCO.Media.Wikipedia.js";
// @codekit-prepend "media/types/VCO.Media.YouTube.js";
// @codekit-prepend "media/types/VCO.Media.Slider.js";
// STORYSLIDER
// @codekit-prepend "slider/VCO.Slide.js";
// @codekit-prepend "slider/VCO.SlideNav.js";
// @codekit-prepend "slider/VCO.StorySlider.js";
// LEAFLET
// LEAFLET SRC
// Leaflet Core
// @codekit-prepend "map/leaflet/leaflet-src/Leaflet.js";
// @codekit-prepend "map/leaflet/leaflet-src/core/Util.js";
// @codekit-prepend "map/leaflet/leaflet-src/core/Class.js";
// @codekit-prepend "map/leaflet/leaflet-src/core/Events.js";
// @codekit-prepend "map/leaflet/leaflet-src/core/Browser.js";
// @codekit-prepend "map/leaflet/leaflet-src/geometry/Point.js";
// @codekit-prepend "map/leaflet/leaflet-src/geometry/Bounds.js";
// @codekit-prepend "map/leaflet/leaflet-src/geometry/Transformation.js";
// @codekit-prepend "map/leaflet/leaflet-src/dom/DomUtil.js";
// @codekit-prepend "map/leaflet/leaflet-src/geo/LatLng.js";
// @codekit-prepend "map/leaflet/leaflet-src/geo/LatLngBounds.js";
// @codekit-prepend "map/leaflet/leaflet-src/geo/projection/Projection.js";
// @codekit-prepend "map/leaflet/leaflet-src/geo/projection/Projection.SphericalMercator.js";
// @codekit-prepend "map/leaflet/leaflet-src/geo/projection/Projection.LonLat.js";
// @codekit-prepend "map/leaflet/leaflet-src/geo/crs/CRS.js";
// @codekit-prepend "map/leaflet/leaflet-src/geo/crs/CRS.Simple.js";
// @codekit-prepend "map/leaflet/leaflet-src/geo/crs/CRS.EPSG3857.js";
// @codekit-prepend "map/leaflet/leaflet-src/geo/crs/CRS.EPSG4326.js";
// @codekit-prepend "map/leaflet/leaflet-src/map/Map.js";
// @codekit-prepend "map/leaflet/leaflet-src/dom/DomEvent.js";
// @codekit-prepend "map/leaflet/leaflet-src/dom/Draggable.js";
// @codekit-prepend "map/leaflet/leaflet-src/core/Handler.js";
// @codekit-prepend "map/leaflet/leaflet-src/control/Control.js";
// Additonal Projections EPSG:3395 projection (used by some map providers).
// "map/leaflet/leaflet-src/geo/projection/Projection.Mercator.js";
// "map/leaflet/leaflet-src/geo/crs/CRS.EPSG3395.js";
// TileLayerWMS WMS tile layer.
// @codekit-prepend "map/leaflet/leaflet-src/layer/tile/TileLayer.js";
// TileLayerCanvas Tile layer made from canvases (for custom drawing purposes)
// @codekit-prepend "map/leaflet/leaflet-src/layer/tile/TileLayer.Canvas.js";
// ImageOverlay Used to display an image over a particular rectangular area of the map.
// @codekit-prepend "map/leaflet/leaflet-src/layer/ImageOverlay.js";
// Marker Markers to put on the map.
// @codekit-prepend "map/leaflet/leaflet-src/layer/marker/Icon.js";
// @codekit-prepend "map/leaflet/leaflet-src/layer/marker/Icon.Default.js";
// @codekit-prepend "map/leaflet/leaflet-src/layer/marker/Marker.js";
// DivIcon Lightweight div-based icon for markers.
// @codekit-prepend "map/leaflet/leaflet-src/layer/marker/DivIcon.js";
// Popup Used to display the map popup (used mostly for binding HTML data to markers and paths on click).
// "map/leaflet/leaflet-src/layer/Popup.js";
// "map/leaflet/leaflet-src/layer/marker/Marker.Popup.js";
// LayerGroup Allows grouping several layers to handle them as one.
// @codekit-prepend "map/leaflet/leaflet-src/layer/LayerGroup.js";
// FeatureGroup Extends LayerGroup with mouse events and bindPopup method shared between layers.
// @codekit-prepend "map/leaflet/leaflet-src/layer/FeatureGroup.js";
// Path Vector rendering core (SVG-powered), enables overlaying the map with SVG paths.
// @codekit-prepend "map/leaflet/leaflet-src/layer/vector/Path.js";
// @codekit-prepend "map/leaflet/leaflet-src/layer/vector/Path.SVG.js";
// "map/leaflet/leaflet-src/layer/vector/Path.Popup.js";
// PathVML VML fallback for vector rendering core (IE 6-8)
// "map/leaflet/leaflet-src/layer/vector/Path.VML.js";
// Path Canvas fallback for vector rendering core (makes it work on Android 2+)
// @codekit-prepend "map/leaflet/leaflet-src/layer/vector/canvas/Path.Canvas.js";
// Polyline Polyline overlays.
// @codekit-prepend "map/leaflet/leaflet-src/geometry/LineUtil.js";
// @codekit-prepend "map/leaflet/leaflet-src/layer/vector/Polyline.js";
// Polygon Polygon overlays
// @codekit-prepend "map/leaflet/leaflet-src/geometry/PolyUtil.js";
// @codekit-prepend "map/leaflet/leaflet-src/layer/vector/Polygon.js";
// MultiPoly MultiPolygon and MultyPolyline layers.
// @codekit-prepend "map/leaflet/leaflet-src/layer/vector/MultiPoly.js";
// Rectangle
// @codekit-prepend "map/leaflet/leaflet-src/layer/vector/Rectangle.js";
// Circle
// @codekit-prepend "map/leaflet/leaflet-src/layer/vector/Circle.js";
// CircleMarker
// @codekit-prepend "map/leaflet/leaflet-src/layer/vector/CircleMarker.js";
// VectorsCanvas Canvas fallback for vector layers (polygons, polylines, circles, circlemarkers)
// @codekit-prepend "map/leaflet/leaflet-src/layer/vector/canvas/Polyline.Canvas.js";
// "map/leaflet/leaflet-src/layer/vector/canvas/Polygon.Canvas.js";
// "map/leaflet/leaflet-src/layer/vector/canvas/Circle.Canvas.js";
// "map/leaflet/leaflet-src/layer/vector/canvas/CircleMarker.Canvas.js";
// GeoJSON GeoJSON layer, parses the data and adds corresponding layers above.
// "map/leaflet/leaflet-src/layer/GeoJSON.js";
// MapDrag Makes the map draggable (by mouse or touch).
// @codekit-prepend "map/leaflet/leaflet-src/map/handler/Map.Drag.js";
// MouseZoom Scroll wheel zoom and double click zoom on the map.
// @codekit-prepend "map/leaflet/leaflet-src/map/handler/Map.DoubleClickZoom.js";
// @codekit-prepend "map/leaflet/leaflet-src/map/handler/Map.ScrollWheelZoom.js";
// TouchZoom Enables smooth touch zoom / tap / longhold / doubletap on iOS, IE10, Android
// @codekit-prepend "map/leaflet/leaflet-src/dom/DomEvent.DoubleTap.js";
// @codekit-prepend "map/leaflet/leaflet-src/dom/DomEvent.Pointer.js";
// @codekit-prepend "map/leaflet/leaflet-src/map/handler/Map.TouchZoom.js";
// @codekit-prepend "map/leaflet/leaflet-src/map/handler/Map.Tap.js";
// BoxZoom Enables zooming to bounding box by shift-dragging the map.
// "map/leaflet/leaflet-src/map/handler/Map.BoxZoom.js";
// Keyboard Enables keyboard pan/zoom when the map is focused.
// "map/leaflet/leaflet-src/map/handler/Map.Keyboard.js";
// ControlZoom Basic zoom control with two buttons (zoom in / zoom out).
// @codekit-prepend "map/leaflet/leaflet-src/control/Control.Zoom.js";
// ControlAttrib Attribution control.
// @codekit-prepend "map/leaflet/leaflet-src/control/Control.Attribution.js";
// ControlScale Scale control.
// "map/leaflet/leaflet-src/control/Control.Scale.js";
// ControlLayers Layer Switcher control.
// "map/leaflet/leaflet-src/control/Control.Layers.js";
// AnimationPan Core panning animation support.
// @codekit-prepend "map/leaflet/leaflet-src/dom/PosAnimation.js";
// @codekit-prepend "map/leaflet/leaflet-src/map/anim/Map.PanAnimation.js";
// AnimationTimer Timer-based pan animation fallback for browsers that don\'t support CSS3 transitions.
// @codekit-prepend "map/leaflet/leaflet-src/dom/PosAnimation.Timer.js";
// AnimationZoom Smooth zooming animation. Works only on browsers that support CSS3 Transitions.
// @codekit-prepend "map/leaflet/leaflet-src/map/anim/Map.ZoomAnimation.js";
// @codekit-prepend "map/leaflet/leaflet-src/layer/tile/TileLayer.Anim.js";
// Geolocation Adds Map#locate method and related events to make geolocation easier.'
// "map/leaflet/leaflet-src/map/ext/Map.Geolocation.js";
// LEAFLET EXTENTIONS
// @codekit-prepend "map/leaflet/extentions/VCO.Leaflet.TileLayer.Zoomify.js";
// @codekit-prepend "map/leaflet/extentions/VCO.Leaflet.MiniMap.js";
// TILES
// "map/tile/VCO.TileLayer.Mapbox.js"; NOT READY YET
// @codekit-prepend "map/tile/VCO.TileLayer.Stamen.js";
// MAP
// @codekit-prepend "map/VCO.MapMarker.js";
// @codekit-prepend "map/VCO.Map.js";
// LEAFLET IMPLIMENTATION
// @codekit-prepend "map/leaflet/VCO.MapMarker.Leaflet.js";
// @codekit-prepend "map/leaflet/VCO.Map.Leaflet.js";
VCO.StoryMap = VCO.Class.extend({
includes: VCO.Events,
/* Private Methods
================================================== */
initialize: function (elem, data, options,listeners) {
for (key in listeners) {
var callbacks = listeners[key];
if (typeof(callbacks) == 'function') {
this.on(key,callbacks);
} else {
for (var idx in callbacks) {
this.on(key,callbacks[idx]);
}
}
}
var self = this;
// Version
this.version = "0.1.16";
// Ready
this.ready = false;
// DOM ELEMENTS
this._el = {
container: {},
storyslider: {},
map: {},
menubar: {}
};
// Determine Container Element
if (typeof elem === 'object') {
this._el.container = elem;
} else {
this._el.container = VCO.Dom.get(elem);
}
// Slider
this._storyslider = {};
// Map
this._map = {};
this.map = {}; // For direct access to Leaflet Map
// Menu Bar
this._menubar = {};
// Loaded State
this._loaded = {storyslider:false, map:false};
// Data Object
// Test Data compiled from http://www.pbs.org/marktwain/learnmore/chronology.html
this.data = {};
this.options = {
script_path: "",
height: this._el.container.offsetHeight,
width: this._el.container.offsetWidth,
layout: "landscape", // portrait or landscape
base_class: "",
default_bg_color: {r:256, g:256, b:256},
map_size_sticky: 2.5, // Set as division 1/3 etc
map_center_offset: null, // takes object {top:0,left:0}
less_bounce: false, // Less map bounce when calculating zoom, false is good when there are clusters of tightly grouped markers
start_at_slide: 0,
call_to_action: false,
call_to_action_text: "",
menubar_height: 0,
skinny_size: 650,
relative_date: false, // Use momentjs to show a relative date from the slide.text.date.created_time field
// animation
duration: 1000,
ease: VCO.Ease.easeInOutQuint,
// interaction
dragging: true,
trackResize: true,
map_type: "stamen:toner-lite",
map_mini: true,
map_subdomains: "",
map_as_image: false,
map_background_color: "#d9d9d9",
zoomify: {
path: "",
width: "",
height: "",
tolerance: 0.8,
attribution: ""
},
map_height: 300,
storyslider_height: 600,
slide_padding_lr: 45, // padding on slide of slide
slide_default_fade: "0%", // landscape fade
menubar_default_y: 0,
path_gfx: "gfx",
map_popup: false,
zoom_distance: 100,
calculate_zoom: true, // Allow map to determine best zoom level between markers (recommended)
use_custom_markers: false, // Allow use of custom map marker icons
line_follows_path: true, // Map history path follows default line, if false it will connect previous and current only
line_color: "#c34528", //"#DA0000",
line_color_inactive: "#CCC",
line_join: "miter",
line_weight: 3,
line_opacity: 0.80,
line_dash: "5,5",
show_lines: true,
show_history_line: true,
api_key_flickr: "f2cc870b4d233dd0a5bfe73fd0d64ef0",
language: "en"
};
// Current Slide
this.current_slide = this.options.start_at_slide;
// Animation Objects
this.animator_map = null;
this.animator_storyslider = null;
// Merge Options
VCO.Util.mergeData(this.options, options);
if (this.options.layout == "landscape") {
this.options.map_center_offset = {left: -200, top: 0};
}
// Zoomify Layout
if (this.options.map_type == "zoomify" && this.options.map_as_image) {
this.options.map_size_sticky = 2;
}
// Map as Image
if (this.options.map_as_image) {
this.options.calculate_zoom = false;
}
// Use Relative Date Calculations
if(this.options.relative_date) {
if (typeof(moment) !== 'undefined') {
self._loadLanguage(data);
} else {
VCO.Load.js(this.options.script_path + "/library/moment.js", function() {
self._loadLanguage(data);
trace("LOAD MOMENTJS")
});
}
} else {
self._loadLanguage(data);
}
// Emoji Support to Chrome
if (VCO.Browser.chrome) {
VCO.Load.css(this.options.script_path + "../css/fonts/font.emoji.css", function() {
trace("LOADED EMOJI CSS FOR CHROME")
});
}
return this;
},
/* Load Language
================================================== */
_loadLanguage: function(data) {
var self = this;
if(this.options.language == 'en') {
this.options.language = VCO.Language;
this._initData(data);
} else {
VCO.Load.js(this.options.script_path + "/locale/" + this.options.language + ".js", function() {
self._initData(data);
});
}
},
/* Navigation
================================================== */
goTo: function(n) {
if (n != this.current_slide) {
this.current_slide = n;
this._storyslider.goTo(this.current_slide);
this._map.goTo(this.current_slide);
}
},
updateDisplay: function() {
if (this.ready) {
this._updateDisplay();
}
},
/* Private Methods
================================================== */
// Initialize the data
_initData: function(data) {
var self = this;
if (typeof data === 'string') {
VCO.getJSON(data, function(d) {
if (d && d.storymap) {
VCO.Util.mergeData(self.data, d.storymap);
}
self._onDataLoaded();
});
} else if (typeof data === 'object') {
if (data.storymap) {
self.data = data.storymap;
} else {
trace("data must have a storymap property")
}
self._onDataLoaded();
} else {
self._onDataLoaded();
}
},
// Initialize the layout
_initLayout: function () {
var self = this;
this._el.container.className += ' vco-storymap';
this.options.base_class = this._el.container.className;
// Create Layout
this._el.menubar = VCO.Dom.create('div', 'vco-menubar', this._el.container);
this._el.map = VCO.Dom.create('div', 'vco-map', this._el.container);
this._el.storyslider = VCO.Dom.create('div', 'vco-storyslider', this._el.container);
// Initial Default Layout
this.options.width = this._el.container.offsetWidth;
this.options.height = this._el.container.offsetHeight;
this._el.map.style.height = "1px";
this._el.storyslider.style.top = "1px";
// Create Map using preferred Map API
this._map = new VCO.Map.Leaflet(this._el.map, this.data, this.options);
this.map = this._map._map; // For access to Leaflet Map.
this._map.on('loaded', this._onMapLoaded, this);
// Map Background Color
this._el.map.style.backgroundColor = this.options.map_background_color;
// Create Menu Bar
this._menubar = new VCO.MenuBar(this._el.menubar, this._el.container, this.options);
// Create StorySlider
this._storyslider = new VCO.StorySlider(this._el.storyslider, this.data, this.options);
this._storyslider.on('loaded', this._onStorySliderLoaded, this);
this._storyslider.on('title', this._onTitle, this);
this._storyslider.init();
// LAYOUT
if (this.options.layout == "portrait") {
// Set Default Component Sizes
this.options.map_height = (this.options.height / this.options.map_size_sticky);
this.options.storyslider_height = (this.options.height - this._el.menubar.offsetHeight - this.options.map_height - 1);
this._menubar.setSticky(0);
} else {
this.options.menubar_height = this._el.menubar.offsetHeight;
// Set Default Component Sizes
this.options.map_height = this.options.height;
this.options.storyslider_height = (this.options.height - this._el.menubar.offsetHeight - 1);
this._menubar.setSticky(this.options.menubar_height);
}
// Update Display
this._updateDisplay(this.options.map_height, true, 2000);
// Animate Menu Bar to Default Location
this._menubar.show(2000);
},
_initEvents: function () {
// Sidebar Events
this._menubar.on('collapse', this._onMenuBarCollapse, this);
this._menubar.on('back_to_start', this._onBackToStart, this);
this._menubar.on('overview', this._onOverview, this);
// StorySlider Events
this._storyslider.on('change', this._onSlideChange, this);
this._storyslider.on('colorchange', this._onColorChange, this);
// Map Events
this._map.on('change', this._onMapChange, this);
},
// Update View
_updateDisplay: function(map_height, animate, d) {
var duration = this.options.duration,
display_class = this.options.base_class,
self = this;
if (d) {
duration = d;
}
// Update width and height
this.options.width = this._el.container.offsetWidth;
this.options.height = this._el.container.offsetHeight;
// Check if skinny
if (this.options.width <= this.options.skinny_size) {
this.options.layout = "portrait";
//display_class += " vco-skinny";
} else {
this.options.layout = "landscape";
}
// Map Height
if (map_height) {
this.options.map_height = map_height;
}
// Detect Mobile and Update Orientation on Touch devices
if (VCO.Browser.touch) {
this.options.layout = VCO.Browser.orientation();
display_class += " vco-mobile";
}
// LAYOUT
if (this.options.layout == "portrait") {
display_class += " vco-skinny";
// Map Offset
this._map.setMapOffset(0, 0);
this.options.map_height = (this.options.height / this.options.map_size_sticky);
this.options.storyslider_height = (this.options.height - this.options.map_height - 1);
this._menubar.setSticky(0);
// Portrait
display_class += " vco-layout-portrait";
if (animate) {
// Animate Map
if (this.animator_map) {
this.animator_map.stop();
}
this.animator_map = VCO.Animate(this._el.map, {
height: (this.options.map_height) + "px",
duration: duration,
easing: VCO.Ease.easeOutStrong,
complete: function () {
self._map.updateDisplay(self.options.width, self.options.map_height, animate, d, self.options.menubar_height);
}
});
// Animate StorySlider
if (this.animator_storyslider) {
this.animator_storyslider.stop();
}
this.animator_storyslider = VCO.Animate(this._el.storyslider, {
height: this.options.storyslider_height + "px",
duration: duration,
easing: VCO.Ease.easeOutStrong
});
} else {
// Map
this._el.map.style.height = Math.ceil(this.options.map_height) + "px";
// StorySlider
this._el.storyslider.style.height = this.options.storyslider_height + "px";
}
// Update Component Displays
this._menubar.updateDisplay(this.options.width, this.options.height, animate);
this._map.updateDisplay(this.options.width, this.options.height, false);
this._storyslider.updateDisplay(this.options.width, this.options.storyslider_height, animate, this.options.layout);
} else {
// Landscape
display_class += " vco-layout-landscape";
this.options.menubar_height = this._el.menubar.offsetHeight;
// Set Default Component Sizes
this.options.map_height = this.options.height;
this.options.storyslider_height = this.options.height;
this._menubar.setSticky(this.options.menubar_height);
// Set Sticky state of MenuBar
this._menubar.setSticky(this.options.menubar_height);
this._el.map.style.height = this.options.height + "px";
// Update Component Displays
this._map.setMapOffset(-(this.options.width/4), 0);
// StorySlider
this._el.storyslider.style.top = 0;
this._el.storyslider.style.height = this.options.storyslider_height + "px";
this._menubar.updateDisplay(this.options.width, this.options.height, animate);
this._map.updateDisplay(this.options.width, this.options.height, animate, d);
this._storyslider.updateDisplay(this.options.width/2, this.options.storyslider_height, animate, this.options.layout);
}
// Apply class
this._el.container.className = display_class;
},
/* Events
================================================== */
_onDataLoaded: function(e) {
this.fire("dataloaded");
this._initLayout();
this._initEvents();
this.ready = true;
},
_onTitle: function(e) {
this.fire("title", e);
},
_onColorChange: function(e) {
if (e.color || e.image) {
this._menubar.setColor(true);
} else {
this._menubar.setColor(false);
}
},
_onSlideChange: function(e) {
if (this.current_slide != e.current_slide) {
this.current_slide = e.current_slide;
this._map.goTo(this.current_slide);
this.fire("change", {current_slide: this.current_slide}, this);
}
},
_onMapChange: function(e) {
if (this.current_slide != e.current_marker) {
this.current_slide = e.current_marker;
this._storyslider.goTo(this.current_slide);
this.fire("change", {current_slide: this.current_slide}, this);
}
},
_onOverview: function(e) {
this._map.markerOverview();
},
_onBackToStart: function(e) {
this.current_slide = 0;
this._map.goTo(this.current_slide);
this._storyslider.goTo(this.current_slide);
this.fire("change", {current_slide: this.current_slide}, this);
},
_onMenuBarCollapse: function(e) {
this._updateDisplay(e.y, true);
},
_onMouseClick: function(e) {
},
_fireMouseEvent: function (e) {
if (!this._loaded) {
return;
}
var type = e.type;
type = (type === 'mouseenter' ? 'mouseover' : (type === 'mouseleave' ? 'mouseout' : type));
if (!this.hasEventListeners(type)) {
return;
}
if (type === 'contextmenu') {
VCO.DomEvent.preventDefault(e);
}
this.fire(type, {
latlng: "something", //this.mouseEventToLatLng(e),
layerPoint: "something else" //this.mouseEventToLayerPoint(e)
});
},
_onMapLoaded: function() {
this._loaded.map = true;
this._onLoaded();
},
_onStorySliderLoaded: function() {
this._loaded.storyslider = true;
this._onLoaded();
},
_onLoaded: function() {
if (this._loaded.storyslider && this._loaded.map) {
this.fire("loaded", this.data);
}
}
});